From af803440b116723a9e1d462c12354c7e754c9db7 Mon Sep 17 00:00:00 2001 From: Mysaa Date: Wed, 19 May 2021 19:41:47 +0200 Subject: [PATCH] =?UTF-8?q?Premier=20commit=20-=20Int=C3=A9gration=20?= =?UTF-8?q?=C3=A0=20git?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 14 + LaRoueDeLInfortune.iml | 19 ++ app/.gitignore | 1 + app/app.iml | 132 ++++++++++ app/build.gradle | 43 +++ app/src/main/AndroidManifest.xml | 22 ++ .../BanditManchotAnimator.java | 154 +++++++++++ .../larouedelinfortune/EditExosActivity.java | 221 ++++++++++++++++ .../larouedelinfortune/JeuDeQuestions.java | 248 ++++++++++++++++++ .../larouedelinfortune/MainActivity.java | 126 +++++++++ .../larouedelinfortune/QuestionsView.java | 150 +++++++++++ .../main/res/layout/activity_edit_exos.xml | 29 ++ app/src/main/res/layout/activity_main.xml | 36 +++ app/src/main/res/layout/question_element.xml | 7 + .../main/res/layout/questionview_element.xml | 12 + app/src/main/res/menu/menu_edition.xml | 7 + app/src/main/res/menu/menu_principal.xml | 7 + .../res/mipmap-hdpi/ic_launcher_richard2.png | Bin 0 -> 3549 bytes .../ic_launcher_richard2_round.png | Bin 0 -> 5844 bytes .../res/mipmap-mdpi/ic_launcher_richard2.png | Bin 0 -> 2007 bytes .../ic_launcher_richard2_round.png | Bin 0 -> 3245 bytes .../res/mipmap-xhdpi/ic_launcher_richard2.png | Bin 0 -> 5596 bytes .../ic_launcher_richard2_round.png | Bin 0 -> 9163 bytes .../mipmap-xxhdpi/ic_launcher_richard2.png | Bin 0 -> 10339 bytes .../ic_launcher_richard2_round.png | Bin 0 -> 16329 bytes .../mipmap-xxxhdpi/ic_launcher_richard2.png | Bin 0 -> 15619 bytes .../ic_launcher_richard2_round.png | Bin 0 -> 24300 bytes app/src/main/res/raw/default_infortune | 137 ++++++++++ app/src/main/res/values-en/strings.xml | 19 ++ app/src/main/res/values/colors.xml | 6 + .../ic_launcher_richard2_background.xml | 4 + app/src/main/res/values/strings.xml | 19 ++ app/src/main/res/values/styles.xml | 11 + app/src/test/res/jeuMP.bernard.infortune | 11 + build.gradle | 27 ++ gradle.properties | 20 ++ settings.gradle | 2 + 37 files changed, 1484 insertions(+) create mode 100644 .gitignore create mode 100644 LaRoueDeLInfortune.iml create mode 100644 app/.gitignore create mode 100644 app/app.iml create mode 100644 app/build.gradle create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/bernard/larouedelinfortune/BanditManchotAnimator.java create mode 100644 app/src/main/java/com/bernard/larouedelinfortune/EditExosActivity.java create mode 100644 app/src/main/java/com/bernard/larouedelinfortune/JeuDeQuestions.java create mode 100644 app/src/main/java/com/bernard/larouedelinfortune/MainActivity.java create mode 100644 app/src/main/java/com/bernard/larouedelinfortune/QuestionsView.java create mode 100644 app/src/main/res/layout/activity_edit_exos.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/question_element.xml create mode 100644 app/src/main/res/layout/questionview_element.xml create mode 100644 app/src/main/res/menu/menu_edition.xml create mode 100644 app/src/main/res/menu/menu_principal.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_richard2.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_richard2_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_richard2.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_richard2_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_richard2.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_richard2_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_richard2.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_richard2_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_richard2.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_richard2_round.png create mode 100644 app/src/main/res/raw/default_infortune create mode 100644 app/src/main/res/values-en/strings.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/ic_launcher_richard2_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/res/jeuMP.bernard.infortune create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d239cc7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +MCQFormQuizSheetParser/bin/ +app/build +MCQFormQuizSheetParser/.settings + +gradle/ +gradlew +gradlew.bat diff --git a/LaRoueDeLInfortune.iml b/LaRoueDeLInfortune.iml new file mode 100644 index 0000000..9cff0d4 --- /dev/null +++ b/LaRoueDeLInfortune.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..bee20f2 --- /dev/null +++ b/app/app.iml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..1f5aed3 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + defaultConfig { + applicationId "com.bernard.larouedelinfortune" + minSdkVersion 24 + targetSdkVersion 29 + versionCode 5 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + packagingOptions{ + exclude 'META-INF/LICENSE' + exclude 'META-INF/COPYING' + + } +} + + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation 'io.github.kexanie.library:MathView:0.0.6' + //implementation 'com.github.judemanutd:katexview:1.0.2' + //implementation 'org.scilab.forge:jlatexmath:1.0.7' +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c7abc9c --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/bernard/larouedelinfortune/BanditManchotAnimator.java b/app/src/main/java/com/bernard/larouedelinfortune/BanditManchotAnimator.java new file mode 100644 index 0000000..a3be18d --- /dev/null +++ b/app/src/main/java/com/bernard/larouedelinfortune/BanditManchotAnimator.java @@ -0,0 +1,154 @@ +package com.bernard.larouedelinfortune; + +import android.content.Context; +import android.util.Log; +import android.view.Choreographer; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.GridLayout; + +import java.util.Random; + +public class BanditManchotAnimator implements View.OnTouchListener { + + private Choreographer choreographer; + private Random rand; + private QuestionsView[] views; + private JeuDeQuestions jeu; + GridLayout layout; + private int questionCount; + private int[] results; + int rollingMinIndex = -1; + int deceleratingQuestion = -1; + long t00 = -1; + long t0 = -1; + long t1 = -1; + int deltat0 = 1000; + int deltat = 500; + int deltax = 1000; + double epsilon = 0.008; + double omega = 0.02*Math.PI; + + public int speed = 511; + public int speedRand = 10; + + + + public BanditManchotAnimator(GridLayout layout, Context ctx,int questionCount, JeuDeQuestions questions, Button triggerer) { + this.questionCount = questionCount; + rand = new Random(); + views = null; + results = new int[questionCount]; + choreographer = Choreographer.getInstance(); + this.layout = layout; + this.jeu = questions; + triggerer.setOnTouchListener(this); + views = new QuestionsView[questionCount]; + LayoutInflater inflater = (LayoutInflater)ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + layout.removeAllViews(); + layout.setRowCount(questionCount); + layout.setColumnCount(1); + for (int i = 0; i < questionCount; i++) { + views[i] = (QuestionsView)inflater.inflate(R.layout.questionview_element,layout,false); + views[i].setQuestions(questions.getQuestions()); + + views[i].setLayoutParams(new ViewGroup.LayoutParams(100,100)); + + GridLayout.Spec rowSpan = GridLayout.spec(i); + GridLayout.Spec colSpan = GridLayout.spec(0); + GridLayout.LayoutParams lp = new GridLayout.LayoutParams(rowSpan,colSpan); + + layout.addView(views[i],i,lp); + } + + layout.postInvalidate(); + } + + + private void onFrameDraw(){ + + //Log.d("Triggered","Et PAF !!! "); + + long t = System.currentTimeMillis(); + + if(rollingMinIndext00) { + int pos = views[rollingMinIndex].position; + int x0 = views[rollingMinIndex].width * results[rollingMinIndex]; + int totalwidth = views[rollingMinIndex].width * views[rollingMinIndex].getQCount(); + if ((pos + speed - deltax - x0) % totalwidth < speed) { + deceleratingQuestion = rollingMinIndex; + t00 = Long.MAX_VALUE; + t0 = t; + t1 = t + (long) (Math.log(deltax)/ epsilon) + 1; + + rollingMinIndex++; + } + } + if(deceleratingQuestion!=-1) { + int x0 = views[deceleratingQuestion].width * results[deceleratingQuestion]; + if(t<=t1) { + //Log.d("Decelerating",deceleratingQuestion+"->"+(x0 + (int) (Math.exp(-epsilon * (t - t0)) * Math.cos(omega * (t - t0))))); + views[deceleratingQuestion].position = x0 + (int) (deltax*Math.exp(-epsilon * (t - t0)) * Math.cos(omega * (t - t0))); + views[deceleratingQuestion].invalidate(); + }else { + views[deceleratingQuestion].position = x0; + views[deceleratingQuestion].invalidate(); + deceleratingQuestion=-1; + t00=t+deltat; + } + } + + for (int i = rollingMinIndex; i < questionCount; i++) { + views[i].position += speed + rand.nextInt(speedRand*2+1)-speedRand; + views[i].invalidate(); + } + if(rollingMinIndex!=questionCount || deceleratingQuestion!=-1){ + choreographer.postFrameCallback(frameTimeNanos -> onFrameDraw()); + } + } + + private void amorceFrameUpdate(){ + + choreographer.postFrameCallback(frameTimeNanos -> onFrameDraw()); + } + + private void newResults(){ + results = jeu.auHasards(rand,questionCount); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + v.performClick(); + if(event.getAction()==MotionEvent.ACTION_DOWN){ + newResults(); + deceleratingQuestion=-1; + rollingMinIndex=0; + t00=Long.MAX_VALUE; + amorceFrameUpdate(); + }else if(event.getAction()==MotionEvent.ACTION_UP){ + t00 = System.currentTimeMillis()+deltat0; + } + return false; + } + + public void clearAnim(){ + rollingMinIndex=questionCount; + deceleratingQuestion=-1; + t00=-1; + t0=-1; + t1=-1; + } + + + public void changeJeu(JeuDeQuestions jeuDeQuestions) { + clearAnim(); + this.jeu = jeuDeQuestions; + for (QuestionsView v : views) { + v.setQuestions(jeuDeQuestions.getQuestions()); + } + } +} diff --git a/app/src/main/java/com/bernard/larouedelinfortune/EditExosActivity.java b/app/src/main/java/com/bernard/larouedelinfortune/EditExosActivity.java new file mode 100644 index 0000000..15e10ce --- /dev/null +++ b/app/src/main/java/com/bernard/larouedelinfortune/EditExosActivity.java @@ -0,0 +1,221 @@ +package com.bernard.larouedelinfortune; + +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +public class EditExosActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener, View.OnClickListener { + + Spinner jeuSelector; + ArrayAdapter jeuSelectorAdapter; + EditText editingJeu; + String currentJeu; + Button renameJeuButton; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_exos); + + jeuSelector = findViewById(R.id.jeuSeletor); + editingJeu = findViewById(R.id.exoText); + renameJeuButton = findViewById(R.id.renameButton); + + jeuSelector.setOnItemSelectedListener(this); + renameJeuButton.setOnClickListener(this); + + Set nomDesJeuxDispos = JeuDeQuestions.nomDesJeuxDispos(this); + + if(nomDesJeuxDispos.isEmpty()) + JeuDeQuestions.getAndSetDefaultQuestions(this); + nomDesJeuxDispos = JeuDeQuestions.nomDesJeuxDispos(this); + + jeuSelectorAdapter = new ArrayAdapter<>(this,android.R.layout.simple_spinner_dropdown_item,new ArrayList()); + jeuSelector.setAdapter(jeuSelectorAdapter); + + updateJeux(nomDesJeuxDispos); + String name = nomDesJeuxDispos.stream().findAny().get(); + String jeuToBeSelected = getIntent().getStringExtra(MainActivity.EXTRA_SELECTED_JEU); + if(jeuToBeSelected!=null)jeuSelector.setSelection(jeuSelectorAdapter.getPosition(jeuToBeSelected)); + updateJeu(name); + + + } + + @Override + protected void onPause() { + super.onPause(); + try { + sauvegarderJeu(); + } catch (IOException e) { + Log.e("onPauseAutosave", "Impossible de sauvegarder le jeu",e); + } + } + + public void sauvegarderJeu() throws IOException { + + String currentText = editingJeu.getText().toString(); + ByteArrayInputStream baos = new ByteArrayInputStream(currentText.getBytes()); + JeuDeQuestions jdq = JeuDeQuestions.read(baos); + baos.close(); + jdq.sauvegarderJeu(this,currentJeu); + + } + + public void updateJeux(Set jeuxDeQuestion){ + List jeuxNamesAlphabetically = new ArrayList<>(jeuxDeQuestion); + jeuxNamesAlphabetically.sort(Comparator.naturalOrder()); + + String selected = jeuSelector.getSelectedItem()!=null?jeuSelector.getSelectedItem().toString():jeuxNamesAlphabetically.get(0); + jeuSelectorAdapter.clear(); + jeuSelectorAdapter.addAll(jeuxNamesAlphabetically); + + jeuSelectorAdapter.notifyDataSetChanged(); + jeuSelector.setSelection(jeuxNamesAlphabetically.indexOf(selected)); + } + + public void updateJeu(String name){ + Log.d("UpdateJeu","OldJeu : "+currentJeu); + try { + JeuDeQuestions jdq = JeuDeQuestions.lireJeu(this, name); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + jdq.save(baos); + baos.flush(); + String jeuText = new String(baos.toByteArray(), StandardCharsets.UTF_8); + baos.close(); + Log.i("SelectGameForEditing","Je remplace votre texte par "+ jeuText); + editingJeu.getText().clear(); + editingJeu.getText().append(jeuText); + currentJeu = name; + jeuSelector.setSelection(jeuSelectorAdapter.getPosition(currentJeu)); + }catch(IOException| NullPointerException e){ + Log.e("SelectGameForEditing","Impossible de charger le jeu",e); + e.printStackTrace(); + } + Log.d("UpdateJeu","NewJeu : "+currentJeu); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_edition,menu); + return true; + } + + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()){ + + case R.id.newQuestion: + Log.d("NewJeu","Création d'un jeu"); + try{ + JeuDeQuestions.lireJeu(this,getString(R.string.jeuParDefaut)); + }catch(IllegalArgumentException e){ + //Le jeu par défaut n'existe pas + try { + sauvegarderJeu(); + JeuDeQuestions.getAndSetDefaultQuestions(this); + updateJeux(JeuDeQuestions.nomDesJeuxDispos(this)); + updateJeu(getString(R.string.jeuParDefaut)); + return true; + } catch (IOException ex) { + Log.w("UnparsableJeu","Impossible de comprendre le texte",e); + Toast.makeText(this,R.string.cantSaveUnparsable,Toast.LENGTH_SHORT).show(); + } + } + Log.i("NewJeu","Le jeu éxistait déjà !"); + Toast.makeText(this,getString(R.string.alreadyDefaultJeu,getString(R.string.jeuParDefaut)),Toast.LENGTH_SHORT).show(); + updateJeux(JeuDeQuestions.nomDesJeuxDispos(this)); + return true; + + case R.id.refresh: + new AlertDialog.Builder(this).setTitle(R.string.refresh) + .setMessage(R.string.youWannaSave) + .setCancelable(true) + .setPositiveButton(android.R.string.yes, (dialog,which) -> { + try { + sauvegarderJeu(); + }catch (IOException e){ + Log.e("RefreshingEdit","Impossible de sauvegarder"); + } + updateJeux(JeuDeQuestions.nomDesJeuxDispos(this)); + updateJeu(currentJeu); + }) + .setNegativeButton(android.R.string.no, (dialog,which) -> updateJeux(JeuDeQuestions.nomDesJeuxDispos(this))).show(); + + return true; + default: + return super.onOptionsItemSelected(item); + + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + Log.d("OnItemSelected","Checking "+parent+"=="+jeuSelector); + if(parent == jeuSelector){ + try { + sauvegarderJeu(); + updateJeu(jeuSelectorAdapter.getItem(position)); + }catch (Exception e){ + Log.w("UnparsableJeu","Impossible de comprendre le texte",e); + Toast.makeText(this,R.string.cantSaveUnparsable,Toast.LENGTH_SHORT).show(); + } + } + } + + @Override + public void onClick(View v) { + if(v==renameJeuButton){ + final EditText newNameText = new EditText(this); + newNameText.setText(currentJeu); + new AlertDialog.Builder(this) + .setTitle(R.string.nouveauNom) + .setView(newNameText) + .setCancelable(true) + .setPositiveButton(android.R.string.ok,(dialog,which) -> { + String newName = filize(newNameText.getText().toString()); + JeuDeQuestions.lireJeu(this,currentJeu).sauvegarderJeu(this,newName); + JeuDeQuestions.detruireJeu(this,currentJeu); + updateJeux(JeuDeQuestions.nomDesJeuxDispos(this)); + updateJeu(newName); + }) + .show(); + } + } + + public String filize(String s){ + return s.replace(' ','_') + .replace('è','e') + .replace('é','e') + .replace('à','a') + .replace('ù','u') + .replaceAll(JeuDeQuestions.JEUNAME_CHAR_PATTERN,""); + } +} diff --git a/app/src/main/java/com/bernard/larouedelinfortune/JeuDeQuestions.java b/app/src/main/java/com/bernard/larouedelinfortune/JeuDeQuestions.java new file mode 100644 index 0000000..ce3f19e --- /dev/null +++ b/app/src/main/java/com/bernard/larouedelinfortune/JeuDeQuestions.java @@ -0,0 +1,248 @@ +package com.bernard.larouedelinfortune; + +import android.content.Context; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.xml.transform.stream.StreamSource; + +public class JeuDeQuestions { + + public static final String jeuxSubFolder = "jeux"; + + + private String[] questions; + private double[] poids; + private double somme; + + public JeuDeQuestions(String[] questions, double[] poids) { + this.questions = questions; + this.poids = poids; + this.somme = 0; + for (double p : this.poids) this.somme += p; + } + + + public int auHasard(Random r) { + double resultat = r.nextDouble() * somme; + for (int i = 0; i < poids.length; i++) { + if (resultat < poids[i]) + return i; + resultat -= poids[i]; + } + return -1; + } + + public int[] auHasards(Random r,int qcount){ + if(qcount>questions.length){ + int[] results = new int[qcount]; + for (int i = 0; i < qcount; i++) { + results[i] = this.auHasard(r); + } + return results; + } + int[] results = new int[qcount]; + double[] pds = Arrays.copyOf(poids,poids.length); + Arrays.fill(results,-1); + double currentSomme = somme; + for(int i = 0;i questions = new ArrayList<>(); + List poids = new ArrayList<>(); + String pr; + while ((pr = rs.readLine()) != null) { + String[] splitted = pr.split(" "); + double pds = 1; + String qstons = pr; + if(splitted.length>0){ + try{ + pds = Double.parseDouble(splitted[0]); + qstons = pr.substring(splitted[0].length() + 1);} + catch(NumberFormatException e){} + } + poids.add(pds); + questions.add(qstons); + } + String[] questionsA = new String[questions.size()]; + questions.toArray(questionsA); + double[] poidsA = doubleListToDoubleArray(poids); + return new JeuDeQuestions(questionsA, poidsA); + } + + public int questionCount() { + return questions.length; + } + + public String[] getQuestions() { + return questions; + } + + public String toDouble(double d){ + if(Math.round(d)==d) + return Integer.toString((int)d); + return Double.toString(d); + } + + @Override + @NonNull + public String toString() { + return "JeuDeQuestions{" + + "questions=" + Arrays.toString(questions) + + ", poids=" + Arrays.toString(poids) + + '}'; + } + + public static int[] integerListToIntArray(List l) { + int[] out = new int[l.size()]; + Iterator it = l.iterator(); + for (int i = 0; i < out.length; i++) + out[i] = it.next(); + return out; + } + + public static double[] doubleListToDoubleArray(List l) { + double[] out = new double[l.size()]; + Iterator it = l.iterator(); + for (int i = 0; i < out.length; i++) + out[i] = it.next(); + return out; + } + + private static Pattern JEUNAME_PATTERN = Pattern.compile("^[0-9a-zA-Z._-]+$"); + public static String JEUNAME_CHAR_PATTERN ="[^0-9a-zA-Z._-]+"; + + public void sauvegarderJeu(Context ctx,String nom){ + if(!JEUNAME_PATTERN.matcher(nom).matches()){ + Toast.makeText(ctx,R.string.nomInvalide,Toast.LENGTH_LONG).show(); + throw new IllegalArgumentException("Le nom est invalide : "+nom); + } + try { + File ffolder = new File(ctx.getApplicationContext().getFilesDir(), jeuxSubFolder); + ffolder.mkdir(); + File toSaveTo = new File(ffolder, nom + ".jeuDeQuestions"); + toSaveTo.delete(); + FileOutputStream fos = new FileOutputStream(toSaveTo); + this.save(fos); + fos.close(); + }catch (IOException err){ + Log.e("sauvegarderJeu","Le jeu n'a as pu être sauvegardé",err); + } + + } + + public static boolean detruireJeu(Context ctx,String nom){ + File ffolder = new File(ctx.getApplicationContext().getFilesDir(), jeuxSubFolder); + File toDelete = new File(ffolder, nom + ".jeuDeQuestions"); + if(!toDelete.exists())return false; + return toDelete.delete(); + } + + public static JeuDeQuestions lireJeu(Context ctx,String nom){ + try { + File ffolder = new File(ctx.getApplicationContext().getFilesDir(), jeuxSubFolder); + File toRead = new File(ffolder, nom + ".jeuDeQuestions"); + if(!toRead.exists())throw new IllegalArgumentException("Le jeu n'existe pas : "+nom); + FileInputStream fis = new FileInputStream(toRead); + JeuDeQuestions jeu = JeuDeQuestions.read(fis); + fis.close(); + if(jeu.questionCount()==0) { + jeu.questions = new String[]{ctx.getString(R.string.questionParDéfaut)}; + jeu.poids = new double[]{1}; + } + return jeu; + }catch (IOException err){ + Log.e("lireJeu","Le jeu n'a as pu être lu",err); + } + return null; + } + + public static void sauvegarderTousLesJeux(Context ctx,Map jeuxDeQuestions){ + for (Map.Entry entry : jeuxDeQuestions.entrySet()){ + entry.getValue().sauvegarderJeu(ctx,entry.getKey()); + } + } + + public static Map lireTousLesJeux(Context ctx){ + Map output = new HashMap<>(); + for(String nom : nomDesJeuxDispos(ctx)){ + output.put(nom,lireJeu(ctx,nom)); + } + + return output; + } + + public static void getAndSetDefaultQuestions(Context ctx){ + try { + InputStream fis = ctx.getResources().openRawResource(R.raw.default_infortune); + JeuDeQuestions jeu = JeuDeQuestions.read(fis); + fis.close(); + jeu.sauvegarderJeu(ctx,ctx.getString(R.string.jeuParDefaut)); + Log.i("DefaultJeu","Le jeu par défaut a été créé !!!"); + }catch (IOException err){ + Log.e("defaultJeu","Le jeu n'a as pu être lu",err); + } + + } + + public static final String DOTTED_EXTENSION = ".jeuDeQuestions"; + + public static Set nomDesJeuxDispos(Context ctx) { + File ffolder = new File(ctx.getApplicationContext().getFilesDir(), jeuxSubFolder); + String[] nomsA = ffolder.list((dir, name) -> name.endsWith(DOTTED_EXTENSION)); + Log.i("JeuxDispos",Arrays.toString(nomsA)); + if(nomsA==null)return new HashSet<>(); + return Arrays.stream(nomsA).map(s->s.substring(0,s.length()-DOTTED_EXTENSION.length())).collect(Collectors.toSet()); + } + + +} diff --git a/app/src/main/java/com/bernard/larouedelinfortune/MainActivity.java b/app/src/main/java/com/bernard/larouedelinfortune/MainActivity.java new file mode 100644 index 0000000..b28e1a6 --- /dev/null +++ b/app/src/main/java/com/bernard/larouedelinfortune/MainActivity.java @@ -0,0 +1,126 @@ +package com.bernard.larouedelinfortune; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.GridLayout; +import android.widget.Spinner; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener { + + + + Button spinbutton; + GridLayout exosGrid; + Spinner jeuSelector; + ArrayAdapter jeuSelectorAdapter; + BanditManchotAnimator animator; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + spinbutton = findViewById(R.id.spinButton); + exosGrid = findViewById(R.id.exosGrid); + jeuSelector = findViewById(R.id.jeuSeletor); + + jeuSelector.setOnItemSelectedListener(this); + + Set nomDesJeuxDispos = JeuDeQuestions.nomDesJeuxDispos(this); + + if(nomDesJeuxDispos.isEmpty()) + JeuDeQuestions.getAndSetDefaultQuestions(this); + nomDesJeuxDispos = JeuDeQuestions.nomDesJeuxDispos(this); + jeuSelectorAdapter = new ArrayAdapter<>(this,android.R.layout.simple_spinner_dropdown_item,new ArrayList<>()); + jeuSelector.setAdapter(jeuSelectorAdapter); + String name = nomDesJeuxDispos.stream().findAny().get(); + JeuDeQuestions jeu = JeuDeQuestions.lireJeu(this,name); + Log.i("MainActivity","Le jeu qui vient d'être chargé : "+name+"->"+jeu); + animator = new BanditManchotAnimator(exosGrid,this,Integer.parseInt(getString(R.string.questionCount)),jeu,spinbutton); + } + + @Override + protected void onResume() { + super.onResume(); + Set nomDesJeuxDispos = JeuDeQuestions.nomDesJeuxDispos(this); + + if(nomDesJeuxDispos.isEmpty()) + JeuDeQuestions.getAndSetDefaultQuestions(this); + nomDesJeuxDispos = JeuDeQuestions.nomDesJeuxDispos(this); + updateJeux(nomDesJeuxDispos); + } + + public void updateJeux(Set jeuxDeQuestion){ + List jeuxNamesAlphabetically = new ArrayList<>(jeuxDeQuestion); + jeuxNamesAlphabetically.sort(Comparator.naturalOrder()); + + jeuSelectorAdapter.clear(); + jeuSelectorAdapter.addAll(jeuxNamesAlphabetically); + + jeuSelectorAdapter.notifyDataSetChanged(); + + if(jeuSelector.getSelectedItem()!=null) + updateJeu(jeuSelector.getSelectedItem().toString()); + } + + public void updateJeu(String name){ + animator.changeJeu(JeuDeQuestions.lireJeu(this,name)); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_principal,menu); + return true; + } + + + public static final String EXTRA_SELECTED_JEU = "extra.selectedJeu"; + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()){ + + case R.id.editQuestions: + Intent intent = new Intent(this,EditExosActivity.class); + jeuSelector.getSelectedItem(); + intent.putExtra(EXTRA_SELECTED_JEU,jeuSelector.getSelectedItem().toString()); + startActivity(intent); + return true; + case R.id.refresh: + updateJeux(JeuDeQuestions.nomDesJeuxDispos(this)); + return true; + default: + return super.onOptionsItemSelected(item); + + } + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if(parent == jeuSelector){ + updateJeu(jeuSelector.getSelectedItem().toString()); + } + } + + + +} diff --git a/app/src/main/java/com/bernard/larouedelinfortune/QuestionsView.java b/app/src/main/java/com/bernard/larouedelinfortune/QuestionsView.java new file mode 100644 index 0000000..9599944 --- /dev/null +++ b/app/src/main/java/com/bernard/larouedelinfortune/QuestionsView.java @@ -0,0 +1,150 @@ +package com.bernard.larouedelinfortune; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + + +public class QuestionsView extends View { + + String[] questions; + Bitmap[] images; + int position = 100; + int width = 0; + Paint bmpPainter = new Paint(Paint.ANTI_ALIAS_FLAG); + AttributeSet theseAttrs; + + public QuestionsView(Context ctx,AttributeSet attrs){ + super(ctx,attrs); + Log.d("QuestionView","I have been born"); + theseAttrs = attrs; + } + + public void setQuestions(String[] questions){ + this.questions = questions; + this.position = (int)(Math.random()*questions.length)*width; + this.images = new Bitmap[questions.length]; + updateQuestions(); + } + + + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + updateQuestions(); + invalidate(); + requestLayout(); + } + + private Rect rectSrc0 = new Rect(0,0,0,0), + rectSrc1 = new Rect(0,0,0,0), + rectDst0 = new Rect(0,0,0,0), + rectDst1 = new Rect(0,0,0,0); + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec,heightMeasureSpec); + int askedWidth = MeasureSpec.getSize(widthMeasureSpec); + int askedHeight = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(askedWidth,askedHeight/3); + //Log.d("QuestionView","Requesting questions to "+MeasureSpec.toString(widthMeasureSpec)+":"+MeasureSpec.toString(heightMeasureSpec)+" but set to "+width); + } + + public void updateQuestions(){ + width = getWidth(); + Log.d("QuestionView","Updating questions to width "+width); + if(width==0)return; + LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + for (int i = 0; i < questions.length; i++) { + //MathView mv = new MathView(getContext(),theseAttrs); + //MathView mv = findViewById(R.id.questionElement); + //KatexView mv = inflater.inflate(R.layout.question_element,(ViewGroup)this.getRootView(),false).findViewById(R.id.questionElement); + //MathView mv = new MathView(getContext(),theseAttrs); + //mv.setText(questions[i]); + //mv.setTextColor(android.R.color.darker_gray); + //mv.setVisibility(VISIBLE); + + + TextView mv = new TextView(getContext(),theseAttrs); + mv.setTextSize(20); + mv.setGravity(Gravity.CENTER); + + mv.setText(questions[i]); + //mv.setEngine(MathView.Engine.KATEX); + //mv.setBackgroundColor(getResources().getColor(android.R.color.darker_gray)); + + /* + TeXFormula formula = new TeXFormula(questions[i]); + TeXIcon icon = formula.new TeXIconBuilder().setStyle(TeXConstants.STYLE_DISPLAY).setSize(20).build(); + icon.setInsets(Insets.of(5,5,5,5)); + */ + + mv.measure(MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY),MeasureSpec.makeMeasureSpec(getHeight(),MeasureSpec.EXACTLY)); + mv.layout(0,0,mv.getMeasuredWidth(),mv.getMeasuredHeight()); + + Bitmap bmp = Bitmap.createBitmap(width,getHeight(), Bitmap.Config.ARGB_8888); + Canvas cvas = new Canvas(bmp); + Drawable background = mv.getBackground(); + if(background!=null)background.draw(cvas); + mv.layout(0,0,width,getHeight()); + mv.draw(cvas); + cvas.save(); + images[i] = bmp; + //Log.d("QuestionView","Le bitmap chargé : "+bmp.getWidth()+"*"+bmp.getHeight()); + } + rectSrc0.right = rectDst1.right = width; + rectSrc0.bottom = rectSrc1.bottom = rectDst0.bottom = rectDst1.bottom = getHeight(); + invalidate(); + requestLayout(); + } + + @Override + protected void onDraw(Canvas canvas) { + if(width != getWidth()) + updateQuestions(); + //super.onDraw(canvas); + //Log.d("QuestionView","Dessination sur "+width+"*"+getHeight()+" à "+getX()+":"+getY()+" de "+ Arrays.toString(images)); + + //canvas.drawCircle(50,50,50,bmpPainter); + + int position = this.position%(images.length*width); + int bmp0 = position/width; + int bmp1 = (bmp0+1)%images.length; + rectSrc0.left = position%width; + rectDst0.right = width - position%width; + rectSrc1.right = position%width; + rectDst1.left = width - position%width; + //Log.d("QuestionView","TropBien:"+position+"->"+rectSrc0+rectDst0+rectSrc1+rectDst1); + bmpPainter.setColor(getResources().getColor(android.R.color.holo_blue_dark)); + //canvas.drawRect(rectDst0,bmpPainter); + bmpPainter.setColor(getResources().getColor(android.R.color.holo_green_dark)); + //canvas.drawRect(rectDst1,bmpPainter); + bmpPainter.setColor(getResources().getColor(android.R.color.black)); + bmpPainter.setStyle(Paint.Style.STROKE); + canvas.drawRect(rectDst0,bmpPainter); + canvas.drawRect(rectDst1,bmpPainter); + + bmpPainter.setColor(getResources().getColor(android.R.color.darker_gray)); + bmpPainter.setStyle(Paint.Style.FILL); + bmpPainter.setAntiAlias(true); + + canvas.drawBitmap(images[bmp0],rectSrc0,rectDst0,null); + canvas.drawBitmap(images[bmp1],rectSrc1,rectDst1,null); + + + } + + public int getQCount() { + return questions.length; + } +} diff --git a/app/src/main/res/layout/activity_edit_exos.xml b/app/src/main/res/layout/activity_edit_exos.xml new file mode 100644 index 0000000..eb897f0 --- /dev/null +++ b/app/src/main/res/layout/activity_edit_exos.xml @@ -0,0 +1,29 @@ + + + + +