gautrais před 5 roky
rodič
revize
66ac8a16d1
100 změnil soubory, kde provedl 10717 přidání a 0 odebrání
  1. 33 0
      app/build.gradle
  2. 21 0
      app/proguard-rules.pro
  3. 26 0
      app/src/androidTest/java/fanch/multijeu/ExampleInstrumentedTest.java
  4. 52 0
      app/src/main/AndroidManifest.xml
  5. 23 0
      app/src/main/java/fanch/multijeu/MainActivity.java
  6. 55 0
      app/src/main/java/fanch/multijeu/MenuActivity.java
  7. 17 0
      app/src/main/java/fanch/multijeu/common/core/AnimationEndListener.java
  8. 68 0
      app/src/main/java/fanch/multijeu/common/core/ByteVector.java
  9. 37 0
      app/src/main/java/fanch/multijeu/common/core/BytesOutpuStream.java
  10. 64 0
      app/src/main/java/fanch/multijeu/common/core/CustomViewModel.java
  11. 72 0
      app/src/main/java/fanch/multijeu/common/core/DataLoader.java
  12. 68 0
      app/src/main/java/fanch/multijeu/common/core/ObjectVector.java
  13. 65 0
      app/src/main/java/fanch/multijeu/common/core/Saveable.java
  14. 59 0
      app/src/main/java/fanch/multijeu/common/core/SerializedObject.java
  15. 40 0
      app/src/main/java/fanch/multijeu/common/core/SortedArray.java
  16. 13 0
      app/src/main/java/fanch/multijeu/common/core/Vector.java
  17. 95 0
      app/src/main/java/fanch/multijeu/common/core/Word.java
  18. 27 0
      app/src/main/java/fanch/multijeu/common/exceptions/BadChildClass.java
  19. 23 0
      app/src/main/java/fanch/multijeu/common/exceptions/BadParametterException.java
  20. 27 0
      app/src/main/java/fanch/multijeu/common/exceptions/ForbiddenSuperCall.java
  21. 14 0
      app/src/main/java/fanch/multijeu/common/utils/ArraysUtils.java
  22. 14 0
      app/src/main/java/fanch/multijeu/common/utils/DoubleUtils.java
  23. 9 0
      app/src/main/java/fanch/multijeu/common/utils/IntUtils.java
  24. 61 0
      app/src/main/java/fanch/multijeu/common/utils/Latin15.java
  25. 164 0
      app/src/main/java/fanch/multijeu/common/utils/LetterUtils.java
  26. 31 0
      app/src/main/java/fanch/multijeu/common/utils/StringUtils.java
  27. 48 0
      app/src/main/java/fanch/multijeu/common/utils/TimeUtils.java
  28. 122 0
      app/src/main/java/fanch/multijeu/common/view/AutoSizeButton.java
  29. 114 0
      app/src/main/java/fanch/multijeu/common/view/ChronoView.java
  30. 167 0
      app/src/main/java/fanch/multijeu/common/view/ComboBox.java
  31. 40 0
      app/src/main/java/fanch/multijeu/common/view/Line.java
  32. 99 0
      app/src/main/java/fanch/multijeu/findwords/activity/AbstractWordActivity.java
  33. 119 0
      app/src/main/java/fanch/multijeu/findwords/activity/DumbActivity.java
  34. 157 0
      app/src/main/java/fanch/multijeu/findwords/activity/WordActivity.java
  35. 185 0
      app/src/main/java/fanch/multijeu/findwords/data/LetterGenerator.java
  36. 9 0
      app/src/main/java/fanch/multijeu/findwords/data/TryState.java
  37. 44 0
      app/src/main/java/fanch/multijeu/findwords/data/WordPair.java
  38. 195 0
      app/src/main/java/fanch/multijeu/findwords/data/WordSet.java
  39. 7 0
      app/src/main/java/fanch/multijeu/findwords/data/WordState.java
  40. 23 0
      app/src/main/java/fanch/multijeu/findwords/data/checker/AbstractWordCheckerList.java
  41. 64 0
      app/src/main/java/fanch/multijeu/findwords/data/checker/RandomWordChekerList.java
  42. 88 0
      app/src/main/java/fanch/multijeu/findwords/data/checker/SimpleWordCheckerList.java
  43. 269 0
      app/src/main/java/fanch/multijeu/findwords/data/checker/WordChecker.java
  44. 12 0
      app/src/main/java/fanch/multijeu/findwords/data/checker/WordCheckerList.java
  45. 83 0
      app/src/main/java/fanch/multijeu/findwords/data/dictionaire/Dictionaire.java
  46. 81 0
      app/src/main/java/fanch/multijeu/findwords/data/dictionaire/DictionaireIndex.java
  47. 257 0
      app/src/main/java/fanch/multijeu/findwords/data/dictionaire/DictionaireReader.java
  48. 63 0
      app/src/main/java/fanch/multijeu/findwords/fragments/ComposeFragment.java
  49. 104 0
      app/src/main/java/fanch/multijeu/findwords/fragments/SimpleWordViewFragment.java
  50. 12 0
      app/src/main/java/fanch/multijeu/findwords/fragments/model/SimpleWordViewViewModel.java
  51. 7 0
      app/src/main/java/fanch/multijeu/findwords/fragments/ui/compose/ComposeViewModel.java
  52. 123 0
      app/src/main/java/fanch/multijeu/findwords/widget/CircularLayout.java
  53. 20 0
      app/src/main/java/fanch/multijeu/findwords/widget/ComposeWordWidget.java
  54. 35 0
      app/src/main/java/fanch/multijeu/findwords/widget/CustomLinearLayout.java
  55. 8 0
      app/src/main/java/fanch/multijeu/findwords/widget/ILettersSelect.java
  56. 6 0
      app/src/main/java/fanch/multijeu/findwords/widget/IResultView.java
  57. 97 0
      app/src/main/java/fanch/multijeu/findwords/widget/LetterButton.java
  58. 165 0
      app/src/main/java/fanch/multijeu/findwords/widget/LetterView.java
  59. 105 0
      app/src/main/java/fanch/multijeu/findwords/widget/LetterWidget.java
  60. 82 0
      app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderButton.java
  61. 215 0
      app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderLayout.java
  62. 211 0
      app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderLayout2.java
  63. 168 0
      app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderListLayout.java
  64. 118 0
      app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderRootLayout.java
  65. 38 0
      app/src/main/java/fanch/multijeu/numbers/activity/MenuNumberActivity.java
  66. 307 0
      app/src/main/java/fanch/multijeu/numbers/activity/NumberActivity.java
  67. 93 0
      app/src/main/java/fanch/multijeu/numbers/activity/SelectNumbersActivity.java
  68. 13 0
      app/src/main/java/fanch/multijeu/numbers/data/Expression.java
  69. 122 0
      app/src/main/java/fanch/multijeu/numbers/data/ExpressionLexer.java
  70. 107 0
      app/src/main/java/fanch/multijeu/numbers/data/ExpressionParser.java
  71. 9 0
      app/src/main/java/fanch/multijeu/numbers/data/ExpressionSet.java
  72. 284 0
      app/src/main/java/fanch/multijeu/numbers/data/ExpressionSetGenerator.java
  73. 37 0
      app/src/main/java/fanch/multijeu/numbers/data/NumberExpr.java
  74. 122 0
      app/src/main/java/fanch/multijeu/numbers/data/OperationExpr.java
  75. 51 0
      app/src/main/java/fanch/multijeu/numbers/data/ParentheseExpr.java
  76. 373 0
      app/src/main/java/fanch/multijeu/numbers/fragment/NumberFragment.java
  77. 14 0
      app/src/main/java/fanch/multijeu/numbers/fragment/NumberViewModel.java
  78. 314 0
      app/src/main/java/fanch/multijeu/numbers/widget/AssistedCalcWidget.java
  79. 320 0
      app/src/main/java/fanch/multijeu/numbers/widget/CalcButton.java
  80. 335 0
      app/src/main/java/fanch/multijeu/numbers/widget/CalcWidget.java
  81. 82 0
      app/src/main/java/fanch/multijeu/numbers/widget/CorrectionOverlay.java
  82. 171 0
      app/src/main/java/fanch/multijeu/numbers/widget/CorrectionWidget.java
  83. 127 0
      app/src/main/java/fanch/multijeu/numbers/widget/ExpressionLayout.java
  84. 106 0
      app/src/main/java/fanch/multijeu/numbers/widget/GridLayout.java
  85. 232 0
      app/src/main/java/fanch/multijeu/numbers/widget/NonAssistedCalcWidget.java
  86. 16 0
      app/src/main/java/fanch/multijeu/numbers/widget/SimpleCorrectionWidget.java
  87. 37 0
      app/src/main/java/fanch/multijeu/numbers/widget/ViewNumber.java
  88. 114 0
      app/src/main/java/fanch/multijeu/sudoku/activity/SudokuActivity.java
  89. 68 0
      app/src/main/java/fanch/multijeu/sudoku/activity/SudokuLauncher.java
  90. 610 0
      app/src/main/java/fanch/multijeu/sudoku/data/OldSudoku.java
  91. 349 0
      app/src/main/java/fanch/multijeu/sudoku/data/SimpleSudokuResolver.java
  92. 148 0
      app/src/main/java/fanch/multijeu/sudoku/data/SmartOldSudoku.java
  93. 166 0
      app/src/main/java/fanch/multijeu/sudoku/data/Sudoku.java
  94. 174 0
      app/src/main/java/fanch/multijeu/sudoku/data/SudokuGenerator.java
  95. 140 0
      app/src/main/java/fanch/multijeu/sudoku/data/SudokuValue.java
  96. 7 0
      app/src/main/java/fanch/multijeu/sudoku/listener/SudokuDriver.java
  97. 102 0
      app/src/main/java/fanch/multijeu/sudoku/view/SudokuCase.java
  98. 266 0
      app/src/main/java/fanch/multijeu/sudoku/view/SudokuOverlay.java
  99. 362 0
      app/src/main/java/fanch/multijeu/sudoku/view/SudokuView.java
  100. 1 0
      app/src/main/res/anim/shake.xml

+ 33 - 0
app/build.gradle

@@ -0,0 +1,33 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 28
+    defaultConfig {
+        applicationId "fanch.multijeu"
+        minSdkVersion 15
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+    implementation 'com.android.support:support-v4:28.0.0'
+    implementation 'com.google.android.gms:play-services-plus:16.0.0'
+    implementation 'android.arch.lifecycle:extensions:1.1.1'
+    implementation 'com.android.support:design:28.0.0'
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+    implementation 'com.android.support:gridlayout-v7:28.0.0'
+}

+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
app/src/androidTest/java/fanch/multijeu/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package fanch.multijeu;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("fanch.multijeu", appContext.getPackageName());
+    }
+}

+ 52 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="fanch.multijeu">
+    <!-- To access Google+ APIs: -->
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:hardwareAccelerated="true"
+        android:theme="@style/AppTheme">
+        <activity android:name=".sudoku.activity.SudokuLauncher"
+            android:theme="@style/AppTheme.NoActionBar" ></activity>
+        <activity
+            android:name=".sudoku.activity.SudokuActivity"
+            android:theme="@style/AppTheme.NoActionBar"
+            android:screenOrientation="portrait" />
+        <activity android:name=".numbers.activity.SelectNumbersActivity" />
+        <activity
+            android:name=".numbers.activity.NumberActivity"
+            android:theme="@style/AppTheme.NoActionBar" />
+        <activity android:name=".MainActivity" />
+
+        <activity
+            android:name=".findwords.activity.DumbActivity"
+            android:screenOrientation="portrait"
+            android:theme="@style/AppTheme.NoActionBar" />
+        <activity
+            android:name=".findwords.activity.WordActivity"
+            android:screenOrientation="portrait"
+            android:theme="@style/AppTheme.NoActionBar" />
+        <activity
+            android:name=".numbers.activity.MenuNumberActivity"
+            android:screenOrientation="portrait"
+            android:theme="@style/AppTheme.NoActionBar" />
+        <activity
+            android:name=".MenuActivity"
+            android:label="@string/title_activity_menu"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>

+ 23 - 0
app/src/main/java/fanch/multijeu/MainActivity.java

@@ -0,0 +1,23 @@
+package fanch.multijeu;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+
+import fanch.multijeu.findwords.data.dictionaire.DictionaireReader;
+
+public class MainActivity extends AppCompatActivity {
+
+    DictionaireReader dc ;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        //setContentView(R.layout.activity_main);
+
+
+        Log.e("____", "debut-3");
+        dc=new DictionaireReader(this, R.raw.dictionaire);
+
+
+    }
+}

+ 55 - 0
app/src/main/java/fanch/multijeu/MenuActivity.java

@@ -0,0 +1,55 @@
+package fanch.multijeu;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.Toast;
+
+import java.io.FileNotFoundException;
+
+import fanch.multijeu.findwords.data.checker.RandomWordChekerList;
+import fanch.multijeu.findwords.data.dictionaire.Dictionaire;
+import fanch.multijeu.findwords.activity.WordActivity;
+import fanch.multijeu.numbers.activity.SelectNumbersActivity;
+import fanch.multijeu.numbers.activity.MenuNumberActivity;
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionParser;
+import fanch.multijeu.sudoku.activity.SudokuActivity;
+import fanch.multijeu.sudoku.activity.SudokuLauncher;
+
+public class MenuActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_menu);
+        ExpressionParser parser = new ExpressionParser("10*10-(50-1)");
+        Expression e = parser.parse();
+        int x = e.compute();
+        Toast.makeText(this, "Value="+x, Toast.LENGTH_SHORT).show();
+
+    }
+
+    public void onNumber(View view) {
+        //Intent in = new Intent(this, MenuNumberActivity.class);
+        //startActivity(in);
+        SelectNumbersActivity.start(this);
+    }
+
+    public void onText(View view) {
+        try {
+            Dictionaire d = new Dictionaire(this, R.raw.liste_francais_latin1);
+            RandomWordChekerList rwcl = new RandomWordChekerList();
+            WordActivity.start(this, d, rwcl);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    public void onSudoku(View view) {
+        SudokuLauncher.start(this);
+    }
+}

+ 17 - 0
app/src/main/java/fanch/multijeu/common/core/AnimationEndListener.java

@@ -0,0 +1,17 @@
+package fanch.multijeu.common.core;
+
+import android.view.animation.Animation;
+
+public abstract class AnimationEndListener implements Animation.AnimationListener {
+    @Override
+    public void onAnimationStart(Animation animation) {
+
+    }
+
+
+
+    @Override
+    public void onAnimationRepeat(Animation animation) {
+
+    }
+}

+ 68 - 0
app/src/main/java/fanch/multijeu/common/core/ByteVector.java

@@ -0,0 +1,68 @@
+package fanch.multijeu.common.core;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.Vector;
+
+public class ByteVector  {
+
+    protected byte[] mData;
+    protected int mSize=16;
+    protected int mAllocate=16;
+
+    public ByteVector( int size)
+    {
+        mSize=size;
+        mData = new byte[size];
+        mAllocate=size;
+    }
+
+    public void resize(int n)
+    {
+        int size=mAllocate;
+        while (size<n)
+            size=size*2;
+        mAllocate=size;
+        byte[] old = mData;
+        mData= new byte[n];
+        mAllocate=n;
+        System.arraycopy(old, 0, mData, 0, mSize);
+    }
+
+    public void add(byte[] t)
+    {
+        int tl = t.length;
+        int n = tl+mSize;
+        if(n>mAllocate)
+            resize(n);
+        System.arraycopy(t, 0, mData, mSize, tl);
+        mSize+=tl;
+    }
+
+    public void add(byte[] t, int offset, int len)
+    {
+        int tl = len;
+        int n = tl+mSize;
+        if(n>mAllocate)
+            resize(n);
+        System.arraycopy(t, offset, mData, mSize, tl);
+        mSize+=tl;
+    }
+
+    public void add(byte t)
+    {
+        if(mSize+1>mAllocate)
+            resize(mSize+1);
+        mData[mSize]=t;
+        mSize++;
+    }
+
+    public byte[] toArray()
+    {
+        return Arrays.copyOfRange(mData, 0, mSize-1);
+    }
+
+
+
+}

+ 37 - 0
app/src/main/java/fanch/multijeu/common/core/BytesOutpuStream.java

@@ -0,0 +1,37 @@
+package fanch.multijeu.common.core;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class BytesOutpuStream extends OutputStream {
+
+    protected ByteVector mBytes = new ByteVector(256);
+
+
+    public byte[] toByteArray()
+    {
+        return mBytes.toArray();
+    }
+
+
+    public void write(int var1) throws IOException
+    {
+        mBytes.add((byte)var1);
+    }
+
+    public void write(byte[] b) throws IOException {
+        mBytes.add(b);
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+        mBytes.add(b, off, len);
+    }
+
+    public void flush() throws IOException {
+    }
+
+    public void close() throws IOException {
+    }
+
+
+}

+ 64 - 0
app/src/main/java/fanch/multijeu/common/core/CustomViewModel.java

@@ -0,0 +1,64 @@
+package fanch.multijeu.common.core;
+
+import android.app.Activity;
+import android.arch.lifecycle.ViewModel;
+import android.content.Context;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+public class CustomViewModel extends ViewModel implements DataLoader.ISaveable {
+    public boolean save(OutputStream stream)
+    {
+        try {
+            ObjectOutputStream os = new ObjectOutputStream(stream);
+            os.writeObject(this);
+            os.close();
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    public boolean save(Activity c, String key)
+    {
+        boolean d;
+        File f  = c.getFilesDir();
+        try {
+            FileOutputStream fos = c.openFileOutput(key, Context.MODE_PRIVATE);
+            d=save(fos);
+            fos.close();
+            return d;
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return false;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    @Override
+    public SerializedObject saveToSerializedObject() {
+        return new SerializedObject(saveToSerializedObject());
+    }
+
+    public byte[] saveToByteArray()
+    {
+        ByteArrayOutputStream bv = new ByteArrayOutputStream();
+        //BytesOutpuStream bv = new BytesOutpuStream();
+        save(bv);
+        try {
+            bv.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return bv.toByteArray();
+    }
+}

+ 72 - 0
app/src/main/java/fanch/multijeu/common/core/DataLoader.java

@@ -0,0 +1,72 @@
+package fanch.multijeu.common.core;
+
+import android.app.Activity;
+import android.content.Context;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+public class DataLoader {
+
+    public static interface ISaveable extends Serializable{
+        boolean save(OutputStream stream);
+        boolean save(Activity c, String key);
+        SerializedObject saveToSerializedObject();
+        byte[] saveToByteArray();
+    }
+
+
+
+    protected static ISaveable load(InputStream fis)
+    {
+        ObjectInputStream is = null;
+        try {
+            is = new ObjectInputStream(fis);
+            ISaveable obj = (ISaveable) is.readObject();
+            is.close();
+            return obj;
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static ISaveable load(Activity s, String key)
+    {
+        File f  = s.getFilesDir();
+        try {
+            FileInputStream fis = s.openFileInput(key);
+            ISaveable obj = load(fis);
+            fis.close();
+            return obj;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static ISaveable load(SerializedObject obj)
+    {
+        ByteArrayInputStream bv = new ByteArrayInputStream(obj.getBytes());
+        ISaveable s = load(bv);
+        try {
+            bv.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return s;
+    }
+
+}

+ 68 - 0
app/src/main/java/fanch/multijeu/common/core/ObjectVector.java

@@ -0,0 +1,68 @@
+package fanch.multijeu.common.core;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+public class ObjectVector<T> implements Serializable {
+
+    protected Class<T> mClass;
+    protected T[] mData;
+    protected int mSize=16;
+    protected int mAllocate=16;
+
+    public ObjectVector(Class<T> c, int size)
+    {
+        mClass=c;
+        mSize=size;
+        mData = (T[])Array.newInstance(mClass, size);
+        mAllocate=size;
+    }
+
+    public void resize(int n)
+    {
+        int size=mAllocate;
+        while (size<n)
+            size=size*2;
+        mAllocate=size;
+        T[] old = mData;
+        mData= (T[])Array.newInstance(mClass, n);
+        mAllocate=n;
+        System.arraycopy(old, 0, mData, 0, mSize);
+    }
+
+    public void add(T[] t)
+    {
+        int tl = t.length;
+        int n = tl+mSize;
+        if(n>mAllocate)
+            resize(n);
+        System.arraycopy(t, 0, mData, mSize, tl);
+        mSize+=tl;
+    }
+
+    public void add(T[] t, int offset, int len)
+    {
+        int tl = len;
+        int n = tl+mSize;
+        if(n>mAllocate)
+            resize(n);
+        System.arraycopy(t, offset, mData, mSize, tl);
+        mSize+=tl;
+    }
+
+    public void add(T t)
+    {
+        if(mSize+1>mAllocate)
+            resize(mSize+1);
+        mData[mSize]=t;
+        mSize++;
+    }
+
+    public T[] toArray()
+    {
+        return Arrays.copyOfRange(mData, 0, mSize-1);
+    }
+
+
+}

+ 65 - 0
app/src/main/java/fanch/multijeu/common/core/Saveable.java

@@ -0,0 +1,65 @@
+package fanch.multijeu.common.core;
+
+import android.app.Activity;
+import android.content.Context;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+public class Saveable implements DataLoader.ISaveable {
+
+    public boolean save(OutputStream stream)
+    {
+        try {
+            ObjectOutputStream os = new ObjectOutputStream(stream);
+            os.writeObject(this);
+            os.close();
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    public boolean save(Activity c, String key)
+    {
+        boolean d;
+        File f  = c.getFilesDir();
+        try {
+            FileOutputStream fos = c.openFileOutput(key, Context.MODE_PRIVATE);
+            d=save(fos);
+            fos.close();
+            return d;
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+            return false;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+    @Override
+    public SerializedObject saveToSerializedObject() {
+        return new SerializedObject(saveToByteArray());
+    }
+
+    public byte[] saveToByteArray()
+    {
+        ByteArrayOutputStream bv = new ByteArrayOutputStream();
+        //BytesOutpuStream bv = new BytesOutpuStream();
+        save(bv);
+        try {
+            bv.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return bv.toByteArray();
+    }
+
+}

+ 59 - 0
app/src/main/java/fanch/multijeu/common/core/SerializedObject.java

@@ -0,0 +1,59 @@
+package fanch.multijeu.common.core;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class SerializedObject<T extends DataLoader.ISaveable> extends Saveable implements Parcelable {
+    protected byte[] mData;
+
+    public SerializedObject(byte[] b)
+    {
+        mData=b;
+    }
+
+    public SerializedObject(T x)
+    {
+        mData=x.saveToByteArray();
+    }
+
+
+    public byte[] getBytes()
+    {
+        return mData;
+    }
+
+    public T newObject()
+    {
+        return (T)DataLoader.load(this);
+    }
+
+
+    protected SerializedObject(Parcel in) {
+        mData = in.createByteArray();
+    }
+
+    public static final Creator<SerializedObject> CREATOR = new Creator<SerializedObject>() {
+        @Override
+        public SerializedObject createFromParcel(Parcel in) {
+            return new SerializedObject(in);
+        }
+
+        @Override
+        public SerializedObject[] newArray(int size) {
+            return new SerializedObject[size];
+        }
+    };
+
+
+
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int i) {
+        parcel.writeByteArray(mData);
+    }
+}

+ 40 - 0
app/src/main/java/fanch/multijeu/common/core/SortedArray.java

@@ -0,0 +1,40 @@
+package fanch.multijeu.common.core;
+
+import java.util.ArrayList;
+
+
+public class SortedArray<T> extends ArrayList<T> {
+    @Override
+    public void add(int position, T e) {
+        add(e);
+    }
+
+    public boolean add(T c)
+    {
+        for(int i=0; i<size(); i++)
+        {
+            if( ((Comparable)get(i)).compareTo(c)>=0 )
+            {
+                super.add(i, c);
+                return true;
+            }
+        }
+        return true;
+    }
+
+    public int count(T c)
+    {
+        int n=0;
+        for(int i=0; i<size(); i++)
+        {
+            final int x = ((Comparable)get(i)).compareTo(c);
+            if(x==0)
+                n++;
+            else if(x>0)
+                return n;
+        }
+        return n;
+    }
+
+
+}

+ 13 - 0
app/src/main/java/fanch/multijeu/common/core/Vector.java

@@ -0,0 +1,13 @@
+package fanch.multijeu.common.core;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+public interface Vector<T> {
+        void resize(int n);
+        public void add(T[] t);
+        public void add(T[] t, int offset, int len);
+        public void add(T t);
+        public T[] toArray();
+}

+ 95 - 0
app/src/main/java/fanch/multijeu/common/core/Word.java

@@ -0,0 +1,95 @@
+package fanch.multijeu.common.core;
+
+import fanch.multijeu.common.utils.StringUtils;
+
+public class Word extends Saveable {
+
+
+    public int compareTo(String s) {
+        return mString.compareTo(s);
+    }
+
+    public int indexOf(String prefix) {
+        return indexOf(prefix);
+    }
+
+    public String toLowerCase() {
+        return mString.toLowerCase();
+    }
+
+
+    public String toUpperCase() {
+        return mString.toLowerCase();
+    }
+
+    public static class WordType extends Saveable {
+        public static final int NOUN = 1;
+        public static final int VERB = 2;
+
+        private int value=0;
+    }
+
+    protected String   mString="";
+    protected int      mCount=0;
+    protected WordType mType=new WordType();
+
+    public Word(){}
+    public Word(String s){mString=s;}
+    public Word(String s, int c){mString=s; mCount=c;}
+    public Word(String s, int c, WordType t){mString=s; mCount=c; mType=t;}
+
+
+    public String toString()
+    {
+        return mString;
+    }
+
+    public int count()
+    {
+        return mCount;
+    }
+
+
+    public char[] toCharArray() {
+        return mString.toCharArray();
+    }
+
+    public WordType type()
+    {
+        return mType;
+    }
+
+    public boolean equals(Word w, boolean ignoreCase, boolean accentSensitive)
+    {
+        if(accentSensitive)
+        {
+            if(ignoreCase) return mString.toLowerCase().equals(w.mString.toLowerCase());
+            else return mString.equals(w.mString);
+        }else
+        {
+            if(ignoreCase)
+                return toAscii().toLowerCase().equals(w.toAscii());
+            else
+                return toAscii().equals(w.toAscii());
+
+        }
+    }
+
+    public int length()
+    {
+        return mString.length();
+    }
+
+    public char charAt(int i)
+    {
+        return mString.charAt(i);
+    }
+
+    public String toAscii()
+    {
+        return StringUtils.toAscii(mString);
+    }
+
+
+
+}

+ 27 - 0
app/src/main/java/fanch/multijeu/common/exceptions/BadChildClass.java

@@ -0,0 +1,27 @@
+package fanch.multijeu.common.exceptions;
+
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+public class BadChildClass extends RuntimeException {
+    public BadChildClass() {
+        super("Bad class of child");
+    }
+
+    public BadChildClass(String message) {
+        super(message);
+    }
+
+    public BadChildClass(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BadChildClass(Throwable cause) {
+        super(cause);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    protected BadChildClass(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 23 - 0
app/src/main/java/fanch/multijeu/common/exceptions/BadParametterException.java

@@ -0,0 +1,23 @@
+package fanch.multijeu.common.exceptions;
+
+public class BadParametterException extends RuntimeException {
+    public BadParametterException() {
+        super("Bad parameter");
+    }
+
+    public BadParametterException(String message) {
+        super(message);
+    }
+
+    public BadParametterException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BadParametterException(Throwable cause) {
+        super(cause);
+    }
+
+    public BadParametterException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 27 - 0
app/src/main/java/fanch/multijeu/common/exceptions/ForbiddenSuperCall.java

@@ -0,0 +1,27 @@
+package fanch.multijeu.common.exceptions;
+
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+public class ForbiddenSuperCall extends RuntimeException {
+    public ForbiddenSuperCall() {
+        super("Error: this superclass call is forbidden");
+    }
+
+    public ForbiddenSuperCall(String message) {
+        super(message);
+    }
+
+    public ForbiddenSuperCall(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ForbiddenSuperCall(Throwable cause) {
+        super(cause);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    public ForbiddenSuperCall(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 14 - 0
app/src/main/java/fanch/multijeu/common/utils/ArraysUtils.java

@@ -0,0 +1,14 @@
+package fanch.multijeu.common.utils;
+
+import java.util.ArrayList;
+
+public class ArraysUtils {
+
+    public static Object[] toArray(ArrayList d)
+    {
+        Object[] arr = new Object[d.size()];
+        for(int i=0; i<d.size(); i++)
+            arr[i]=d.get(i);
+        return arr;
+    }
+}

+ 14 - 0
app/src/main/java/fanch/multijeu/common/utils/DoubleUtils.java

@@ -0,0 +1,14 @@
+package fanch.multijeu.common.utils;
+
+public class DoubleUtils {
+    public static double rand(double x, double y, int precision)
+    {
+        double resolution = (y-x)/((double)precision);
+        return x+((double)IntUtils.rand(0, precision-1))*resolution;
+    }
+
+    public static double rand(double x, double y)
+    {
+        return rand(x,y, 1000000);
+    }
+}

+ 9 - 0
app/src/main/java/fanch/multijeu/common/utils/IntUtils.java

@@ -0,0 +1,9 @@
+package fanch.multijeu.common.utils;
+
+public class IntUtils {
+
+    public static int rand(int x, int y)
+    {
+        return (int) ((Math.random()*(y-x+1))+x);
+    }
+}

+ 61 - 0
app/src/main/java/fanch/multijeu/common/utils/Latin15.java

@@ -0,0 +1,61 @@
+package fanch.multijeu.common.utils;
+
+public class Latin15{
+    
+    public static enum TriState{
+        TRUE,
+        FALSE,
+        ASCII
+    }
+    
+    public static final String TABLE[] = {
+            "","","","","","","","","","","","","","","","",
+            "","","","","","","","","","","","","","","","",
+            "","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/",
+            "0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?",
+            "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O",
+            "P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_",
+            "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",
+            "p","q","r","s","t","u","v","w","x","y","z","{","|","}","~","",
+            "","","","","","","","","","","","","","","","",
+            "","","","","","","","","","","","","","","","",
+            "¡","¢","£","¤","¥","¦","§","¨","©","ª","«","¬","","®","¯",
+            "°","±","²","³","´","µ","¶","·","¸","¹","º","»","¼","½","¾","¿",
+            "À","Á","Â","Ã","Ä","Å","Æ","Ç","È","É","Ê","Ë","Ì","Í","Î","Ï",
+            "Ð","Ñ","Ò","Ó","Ô","Õ","Ö","×","Ø","Ù","Ú","Û","Ü","Ý","Þ","ß",
+            "à","á","â","ã","ä","å","æ","ç","è","é","ê","ë","ì","í","î","ï",
+            "ð","ñ","ò","ó","ô","õ","ö","÷","ø","ù","ú","û","ü","ý","þ","ÿ"
+    };
+
+    public static final String ASCII_TABLE[] = {
+            "","","","","","","","","","","","","","","","",
+            "","","","","","","","","","","","","","","","",
+            " ","!","\"","#","$","%","&","'","(",")","*","+",",","-",".","/",
+            "0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?",
+            "@","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O",
+            "P","Q","R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_",
+            "`","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o",
+            "p","q","r","s","t","u","v","w","x","y","z","","|","","~","",
+            "","","","","","","","","","","","","","","","",
+            "","","","","","","","","","","","","","","","",
+            "","","","","","","","","","","","","","","","",
+            "","","","","","","","","","","","","OE","oe","","",
+            "A","A","A","A","A","A","AE","C","E","E","E","E","I","I","I","I",
+            "D","N","O","O","O","O","O","x","O","U","U","U","U","Y","",
+            "a","a","a","a","a","a","ae","c","e","e","e","e","i","i","i","i",
+            "o","n","o","o","o","o","o","","o","u","u","u","u","y","","y"
+    };
+
+    public static String resolveAscii(char c)
+    {
+        if(c>0xff) return null;
+        return ASCII_TABLE[(int)c];
+    }
+    
+    public static TriState is(char c)
+    {
+        if(c<0x80 && c>=0x20) return TriState.ASCII;
+        if(ASCII_TABLE[(int)c].length()>0) return TriState.TRUE;
+        return TriState.FALSE;
+    }
+}

+ 164 - 0
app/src/main/java/fanch/multijeu/common/utils/LetterUtils.java

@@ -0,0 +1,164 @@
+package fanch.multijeu.common.utils;
+
+import java.text.Normalizer;
+
+public class LetterUtils {
+    public static final int     LETTER_ASCII_TOTAL=8443;
+    public static final int     LETTER_ASCII_V=2876;
+    public static final int     LETTER_ASCII_C=LETTER_ASCII_TOTAL-LETTER_ASCII_V;
+    public static final int[][] LETTER_ASCII_FREQ = {
+            {'a', 840, 1},//a
+            {'b',106, 0}, //b
+            {'c',303, 0}, //c
+            {'d',418, 0}, //d
+            {'e',172, 1}, //e
+            {'f',112, 0}, //f
+            {'g',127, 0}, //g
+            {'h', 92, 0}, //h
+            {'i',734, 1}, //i
+            {'j',31, 0}, //j
+            {'k',5, 0}, //k
+            {'l',601, 0}, //l
+            {'m',296, 0}, //m
+            {'n',713,  0},//n
+            {'o',526, 1}, //o
+            {'p',301, 0}, //p
+            {'q',99,  0},//q
+            {'r',655, 0},//r
+            {'s',808, 0}, //s
+            {'t',707, 0}, //t
+            {'u',574, 1}, //u
+            {'v',132, 0}, //v
+            {'w',4, 0}, //w
+            {'x',45, 0}, //x
+            {'y',30, 1}, //y
+            {'z',12, 0} //z
+    };
+
+
+    public static final int     LETTER_FREQ_TOTAL=7726;
+    public static final int     LETTER_FREQ_V=2725;
+    public static final int     LETTER_FREQ_C=LETTER_FREQ_TOTAL-LETTER_FREQ_V;
+    public static final int[][] LETTER_FREQ = {
+            {'e' ,121,1},
+            {'a' ,711,1},
+            {'i' ,659,1},
+            {'s' ,651,0},
+            {'n' ,639,0},
+            {'r' ,607,0},
+            {'t' ,592,0},
+            {'o' ,502,1},
+            {'l' ,496,0},
+            {'u' ,449,1},
+            {'d' ,367,0},
+            {'c' ,318,0},
+            {'m' ,262,0},
+            {'p' ,249,0},
+            {'é' ,194,1},
+            {'g' ,123,0},
+            {'b' ,114,0},
+            {'v' ,111,0},
+            {'h' ,111,0},
+            {'f' ,111,0},
+            {'q',65,0},
+            {'y',46,0},
+            {'x',38,0},
+            {'j',34,0},
+            {'è',31,1},
+            {'à',31,1},
+            {'k',29,0},
+            {'w',17,0},
+            {'z',15,0},
+            {'ê',8,1},
+            {'ç',6,0},
+            {'ô',4,1},
+            {'â',3,1},
+            {'î',3,1},
+            {'û',2,1},
+            {'ù',2,1},
+            {'ï',1,1},
+            {'ü',1,1},
+            {'ë',1,1},
+            {'ö',1,1},
+            {'í',1,1}
+    };
+
+
+
+    public static char randLetter(boolean accent)
+    {
+        int total=(accent)?LETTER_FREQ_TOTAL:LETTER_ASCII_TOTAL;
+        int arr[][] = (accent)?LETTER_ASCII_FREQ:LETTER_ASCII_FREQ;
+        int rand = IntUtils.rand(0,total);
+        for(int i=0; i<arr.length; i++)
+        {
+            rand-=arr[i][1];
+            if(rand<0) return (char)arr[i][0];
+        }
+        return (char)arr[arr.length-1][0];
+    }
+
+    public static char randVoyelle(boolean accent)
+    {
+        int total=(accent)?LETTER_FREQ_V:LETTER_FREQ_V;
+        int arr[][] = (accent)?LETTER_ASCII_FREQ:LETTER_ASCII_FREQ;
+        int rand = IntUtils.rand(0,total);
+        for(int i=0; i<arr.length; i++)
+        {
+            if(arr[i][0]==1) {
+                rand -= arr[i][1];
+                if (rand < 0) return (char) arr[i][0];
+            }
+        }
+        return (char)'a';
+    }
+
+    public static char randConsonne(boolean accent)
+    {
+        int total=(accent)?LETTER_FREQ_C:LETTER_FREQ_C;
+        int arr[][] = (accent)?LETTER_ASCII_FREQ:LETTER_ASCII_FREQ;
+        int rand = IntUtils.rand(0,total);
+        for(int i=0; i<arr.length; i++)
+        {
+            if(arr[i][0]==0) {
+                rand -= arr[i][1];
+                if (rand < 0) return (char) arr[i][0];
+            }
+        }
+        return (char)'z';
+    }
+
+
+    public static boolean isConsonne(char cc)
+    {
+        char c = Character.toLowerCase(cc);
+        for(int i=0; i<LETTER_FREQ.length; i++) {
+            if(LETTER_FREQ[i][0]==c)
+                return LETTER_FREQ[i][2]==0;
+        }
+        return false;
+    }
+
+    public static boolean isVoyelle(char cc)
+    {
+        char c = Character.toLowerCase(cc);
+        for(int i=0; i<LETTER_FREQ.length; i++) {
+            if(LETTER_FREQ[i][0]==c)
+                return LETTER_FREQ[i][2]==1;
+        }
+        return false;
+    }
+
+    public static String toAscii(char c)
+    {
+        return toAscii(""+c);
+    }
+
+    public static String toAscii(String c)
+    {
+        String s = Normalizer.normalize(c, Normalizer.Form.NFD);
+        return s.replaceAll("[^\\p{ASCII}]", "");
+    }
+
+
+}

+ 31 - 0
app/src/main/java/fanch/multijeu/common/utils/StringUtils.java

@@ -0,0 +1,31 @@
+package fanch.multijeu.common.utils;
+
+import java.text.Normalizer;
+
+public class StringUtils {
+    public static int countVoyelle(String s)
+    {
+        int x = 0;
+        int l=s.length();
+        for(int i=0; i<l; i++)
+            if(LetterUtils.isVoyelle(s.charAt(i)))
+                x++;
+        return x;
+    }
+
+    public static int countConsonne(String s)
+    {
+        int x = 0;
+        int l=s.length();
+        for(int i=0; i<l; i++)
+            if(LetterUtils.isConsonne(s.charAt(i)))
+                x++;
+        return x;
+    }
+
+    public static String toAscii(String c)
+    {
+        String s = Normalizer.normalize(c, Normalizer.Form.NFD);
+        return s.replaceAll("[^\\p{ASCII}]", "");
+    }
+}

+ 48 - 0
app/src/main/java/fanch/multijeu/common/utils/TimeUtils.java

@@ -0,0 +1,48 @@
+package fanch.multijeu.common.utils;
+
+public class TimeUtils {
+
+    public static double time()
+    {
+        return usToS(timeUs());
+    }
+
+    public static long timeUs()
+    {
+        return System.currentTimeMillis();
+    }
+
+    public static long timeMs()
+    {
+        return System.currentTimeMillis()/1000;
+    }
+
+    private static double usToS(long d)
+    {
+        return ((double)d)/1000;
+    }
+
+    public static class TimeInterval
+    {
+        protected long last;
+
+        public TimeInterval()
+        {
+            last=timeUs();
+        }
+
+        public long tickUs()
+        {
+            long c = timeUs() - last;
+            last=c;
+            return c;
+        }
+
+        public double tick()
+        {
+            return usToS(tickUs());
+        }
+
+    }
+
+}

+ 122 - 0
app/src/main/java/fanch/multijeu/common/view/AutoSizeButton.java

@@ -0,0 +1,122 @@
+package fanch.multijeu.common.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+import fanch.multijeu.R;
+
+public class AutoSizeButton extends android.support.v7.widget.AppCompatButton
+{
+    private static final float THRESHOLD = 0.5f;
+
+    private enum Mode { Width, Height, Both, None }
+
+    private int minTextSize = 1;
+    private int maxTextSize = 50;
+
+    private Mode mode = Mode.None;
+    private boolean inComputation;
+    private int widthMeasureSpec;
+    private int heightMeasureSpec;
+
+    public AutoSizeButton(Context context) {
+        super(context);
+    }
+
+    public AutoSizeButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AutoSizeButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    private void resizeText() {
+        if (getWidth() <= 0 || getHeight() <= 0)
+            return;
+        if(mode == Mode.None)
+            return;
+
+        final int targetWidth = getWidth()-getPaddingLeft()-getPaddingRight();
+        final int targetHeight = getHeight()-getPaddingBottom()-getPaddingTop();
+
+        inComputation = true;
+        float higherSize = maxTextSize;
+        float lowerSize = minTextSize;
+        float textSize = getTextSize();
+        while(higherSize - lowerSize > THRESHOLD) {
+            textSize = (higherSize + lowerSize) / 2;
+            if (isTooBig(textSize, targetWidth, targetHeight)) {
+                higherSize = textSize;
+            } else {
+                lowerSize = textSize;
+            }
+        }
+        setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
+        measure(widthMeasureSpec, heightMeasureSpec);
+        inComputation = false;
+    }
+
+    private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
+        setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+        measure(0, 0);
+        if(mode == Mode.Both)
+            return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
+        if(mode == Mode.Width)
+            return getMeasuredWidth() >= targetWidth;
+        else
+            return getMeasuredHeight() >= targetHeight;
+    }
+
+    private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
+            return Mode.Both;
+        if(widthMode == MeasureSpec.EXACTLY)
+            return Mode.Width;
+        if(heightMode == MeasureSpec.EXACTLY)
+            return Mode.Height;
+        return Mode.None;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if(!inComputation) {
+            this.widthMeasureSpec = widthMeasureSpec;
+            this.heightMeasureSpec = heightMeasureSpec;
+            mode = getMode(widthMeasureSpec, heightMeasureSpec);
+            resizeText();
+        }
+    }
+
+    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
+        resizeText();
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        if (w != oldw || h != oldh)
+            resizeText();
+    }
+
+    public int getMinTextSize() {
+        return minTextSize;
+    }
+
+    public void setMinTextSize(int minTextSize) {
+        this.minTextSize = minTextSize;
+        resizeText();
+    }
+
+    public int getMaxTextSize() {
+        return maxTextSize;
+    }
+
+    public void setMaxTextSize(int maxTextSize) {
+        this.maxTextSize = maxTextSize;
+        resizeText();
+    }
+}

+ 114 - 0
app/src/main/java/fanch/multijeu/common/view/ChronoView.java

@@ -0,0 +1,114 @@
+package fanch.multijeu.common.view;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+import fanch.multijeu.R;
+import fanch.multijeu.common.core.AnimationEndListener;
+import fanch.multijeu.common.utils.TimeUtils;
+
+public class ChronoView extends View {
+
+    public interface ChronoCallback
+    {
+        void onEnd();
+    }
+
+    protected int mEndTime;
+    protected int mDuration=1;
+    protected ChronoCallback mListener;
+    protected boolean mSignSent=false;
+    public ChronoView(Context context) {
+        super(context);
+    }
+
+    public ChronoView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mSignSent=true;
+    }
+
+    public void setDuration(int d)
+    {
+        mDuration=d;
+    }
+
+    public void start(){
+        mEndTime=((int)(TimeUtils.timeMs()))+mDuration;
+        mSignSent=false;
+    }
+
+    public void stop()
+    {
+        mSignSent=true;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        final int w = getWidth();
+        final int h = getHeight();
+        final int min = (w<h)?w:h;
+        final int size = (int) (min*0.9);
+        final int offset = (w-size)/2;
+        int delta = 0;//(min-size)/2;
+        final Activity c = (Activity) getContext();
+        Paint p = new Paint();
+        p.setColor(0xff00afaf);
+        p.setAntiAlias(true);
+        RectF r = new RectF(delta+offset,delta,offset+delta+size, delta+size);
+
+        if(TimeUtils.timeMs()<mEndTime && !mSignSent)
+        {
+            final int x = (int) (((mEndTime-TimeUtils.timeMs())*360)/mDuration);
+            canvas.drawArc(r, -90, x, true, p);
+
+            Paint p2 = new Paint();
+            p2.setTextSize(36);
+            p2.setTextAlign(Paint.Align.CENTER);
+            p2.setColor(0xff000000);
+            p2.setAntiAlias(true);
+            canvas.drawText(""+(mEndTime-TimeUtils.timeMs()), w/2+delta, h/2+delta, p2);
+        }
+
+        postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                c.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if(TimeUtils.timeMs()>=mEndTime && mListener!=null && !mSignSent) {
+                            mListener.onEnd();
+                            mSignSent=true;
+                        }
+                        invalidate();
+                    }
+                });
+            }
+        }, 100);
+    }
+
+    public void setListener(ChronoCallback mListener) {
+        this.mListener = mListener;
+    }
+
+    public void hide()
+    {
+        if(getVisibility()==GONE) return;
+        setVisibility(GONE);
+    }
+
+    public void show()
+    {
+        if(getVisibility()==VISIBLE || getVisibility()==INVISIBLE) return;
+        setVisibility(VISIBLE);
+    }
+}

+ 167 - 0
app/src/main/java/fanch/multijeu/common/view/ComboBox.java

@@ -0,0 +1,167 @@
+package fanch.multijeu.common.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.SpinnerAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.R;
+
+public class ComboBox extends LinearLayout implements View.OnClickListener, AdapterView.OnItemClickListener, AdapterView.OnItemSelectedListener {
+
+    protected ArrayList<String> mData = new ArrayList<String>();
+    protected int mSelected=0;
+
+    protected String mText;
+    protected Button mLeft;
+    protected Button mRight;
+    public Spinner mSpinner;
+
+    public ComboBox(Context context) {
+        super(context);
+        init();
+    }
+
+    public ComboBox(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init();
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
+                R.styleable.ComboBox, 0, 0);
+
+        try {
+            String text = a.getString(R.styleable.ComboBox_description);
+            if(text!=null)
+                setText(text);
+        } finally {
+            a.recycle();
+        }
+    }
+
+
+    private void init()
+    {
+        mText="";
+
+        mLeft=new Button(getContext());
+        mLeft.setText("<");
+        mLeft.setLayoutParams(new LayoutParams(70,-2,0));
+
+        mRight=new Button(getContext());
+        mRight.setText(">");
+        mRight.setLayoutParams(new LayoutParams(70,-2,0));
+
+        mSpinner=new Spinner(getContext());
+        mSpinner.setLayoutParams(new LayoutParams(-2,-1,1));
+        mSpinner.setGravity(Gravity.CENTER);
+
+        addView(mLeft);
+        addView(mSpinner);
+        addView(mRight);
+
+        mRight.setOnClickListener(this);
+        mLeft.setOnClickListener(this);
+        mSpinner.setOnItemSelectedListener(this);
+    }
+
+    public void setData(ArrayList<String> list)
+    {
+        if(mText==null || mText.length()==0)
+        {
+            mData=list;
+        }else {
+            mData.clear();
+            for(int i=0; i<list.size(); i++)
+                mData.add(mText+" : "+list.get(i));
+
+        }
+        mSelected=0;
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(),
+                                                R.layout.spinner_item,
+                    mData){
+
+        };
+
+        adapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item);
+        mSpinner.setAdapter(adapter);
+    }
+
+    public void setData(ArrayList<String> list, int d) {
+        setData(list);
+        mSelected=d;
+        mSpinner.setSelection(mSelected, false);
+    }
+
+
+    public void setData(String[] list)
+    {
+        setData(list, 0);
+    }
+
+    public void setData(String[] list, int d) {
+        ArrayList l = new ArrayList();
+        for(int i=0; i<list.length; i++)
+            l.add(list[i]);
+        setData(l, d);
+    }
+
+    public void setText(String s)
+    {
+        mText=s;
+    }
+
+
+    public int getSelected() {
+        return mSelected;
+    }
+
+    public void setSelected(int mSelected) {
+        this.mSelected = mSelected;
+        if(mSelected<0 || mSelected>=mData.size()) return;
+        mSpinner.setSelection(mSelected, true);
+    }
+
+
+
+    @Override
+    public void onClick(View v) {
+        if(v==mRight)
+        {
+            setSelected((mSelected+1)%mData.size());
+        } else {
+            int x;
+            if(mSelected-1<0)
+                x=mSelected-1+mData.size();
+            else
+                x=mSelected-1;
+            setSelected(x);
+        }
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+
+    }
+
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+        mSelected=position;
+    }
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) {
+
+    }
+}

+ 40 - 0
app/src/main/java/fanch/multijeu/common/view/Line.java

@@ -0,0 +1,40 @@
+package fanch.multijeu.common.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class Line extends View {
+
+    protected int mColor=0xff000000;
+
+    public Line(Context context) {
+        super(context);
+    }
+
+    public Line(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        Paint p = new Paint();
+        p.setColor(mColor);
+        canvas.drawRect(0, 0,
+                getRight()-getLeft(),
+                getBottom()-getTop(), p);
+    }
+
+
+
+    public int getColor() {
+        return mColor;
+    }
+
+    public void setColor(int mColor) {
+        this.mColor = mColor;
+    }
+}

+ 99 - 0
app/src/main/java/fanch/multijeu/findwords/activity/AbstractWordActivity.java

@@ -0,0 +1,99 @@
+package fanch.multijeu.findwords.activity;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+
+import fanch.multijeu.common.core.Word;
+import fanch.multijeu.common.exceptions.ForbiddenSuperCall;
+import fanch.multijeu.findwords.data.dictionaire.Dictionaire;
+import fanch.multijeu.findwords.data.TryState;
+import fanch.multijeu.findwords.data.checker.WordChecker;
+import fanch.multijeu.findwords.data.checker.WordCheckerList;
+
+public abstract class AbstractWordActivity extends AppCompatActivity  {
+
+
+
+
+    public static class WordActivityAttrs {
+        public int viewResId=-1;
+        public View view;
+        public boolean useView=false;
+        public WordCheckerList list;
+        public Dictionaire dico;
+        public boolean setContentView=true;
+
+
+        public WordActivityAttrs(Dictionaire d){ dico=d; setContentView=false;}
+        public WordActivityAttrs(Dictionaire d, int res) { dico=d;useView=false; viewResId=res; }
+        public WordActivityAttrs(Dictionaire d, View v) { dico=d;useView=true; view=v; }
+
+    }
+
+    protected WordActivityAttrs mAttrs;
+    private   WordChecker mChecker;
+    protected WordCheckerList mCheckerList;
+
+    protected void onCreate(Bundle savedInstanceState, WordActivityAttrs w) {
+        super.onCreate(savedInstanceState);
+        mAttrs = w;
+        if(w.setContentView) {
+            if(w.useView)
+                setContentView(w.view);
+            else
+                setContentView(w.viewResId);
+        }
+        mCheckerList = mAttrs.list;
+        mChecker =  mCheckerList.next(mAttrs.dico);
+    }
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        throw new ForbiddenSuperCall("AbstractWordActivity::onCreate(Bundle) from "+this.getClass().toString());
+    }
+
+    public void setNewChecker(WordChecker w)
+    {
+        mChecker =w;
+        onCheckerChange();
+    }
+
+    public abstract void onCheckerChange();
+
+    public final WordChecker getChecker()
+    {
+        return mChecker;
+    }
+
+    public void onTryWord(Word w) {
+        TryState ts = mChecker.tryWord(w);
+        switch(ts)
+        {
+            case YES:
+                onYes(w.toString());
+                if(mChecker.expectedLeft()<=0) {
+                    onDone();
+                    if (mCheckerList.hasNext())
+                        setNewChecker(mCheckerList.next(mAttrs.dico));
+                    else
+                        onFinish();
+                }
+                break;
+
+            case YES_AGAIN: onYesAgain(w.toString()); break;
+            case BONUS: onBonus(w.toString()); break;
+            case BONUS_AGAIN: onBonusAgain(w.toString()); break;
+            case NO: onNo(w.toString()); break;
+        }
+    }
+
+    protected void onYes(String s){}
+    protected void onYesAgain(String s){}
+    protected void onBonus(String s){}
+    protected void onBonusAgain(String s){}
+    protected void onNo(String s){}
+    protected abstract void onDone();
+    protected abstract void onFinish();
+
+}

+ 119 - 0
app/src/main/java/fanch/multijeu/findwords/activity/DumbActivity.java

@@ -0,0 +1,119 @@
+package fanch.multijeu.findwords.activity;
+
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+import fanch.multijeu.R;
+import fanch.multijeu.common.core.Word;
+import fanch.multijeu.findwords.data.dictionaire.Dictionaire;
+import fanch.multijeu.common.utils.TimeUtils;
+import fanch.multijeu.findwords.widget.LetterWidget;
+import fanch.multijeu.findwords.widget.PlaceHolderLayout2;
+import fanch.multijeu.numbers.activity.MenuNumberActivity;
+
+public class DumbActivity extends AppCompatActivity implements TextView.OnEditorActionListener {
+
+    EditText m_et;
+    TextView m_tv;
+    Dictionaire m_dict;
+    boolean mLayoutFinish=false;
+
+    LetterWidget lv;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_dumb);
+
+        /*m_et=findViewById(R.id.editText);
+        m_tv=findViewById(R.id.textView);
+
+        m_et.setOnEditorActionListener(this);*/
+       /* PlaceHolderRootLayout phll = new PlaceHolderRootLayout(this);
+        //lv=findViewById(R.id.letterwidget);
+        LinearLayout lv = findViewById(R.id.dumb);
+        String list[]={
+                "Bonjour",
+                "a",
+                "tout",
+                "le",
+                "a",
+                "et",
+                "a",
+                "b"
+        };
+        phll.setWords(list);
+        lv.addView(phll);*/
+
+       /* lv.addView(xxx("abv"));
+        lv.addView(xxx("Bds"));
+        lv.addView(xxx("Cfsd"));
+        lv.addView(xxx("Dfd"));*/
+       // lv.addView(new Button(this));
+
+
+        try {
+            m_dict = new Dictionaire(this, R.raw.liste_francais_latin1);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+        onJeu(null);
+
+    }
+
+    View xxx(String txt)
+    {
+        PlaceHolderLayout2 phl = new PlaceHolderLayout2(this);
+        phl.setText(txt);
+        return  phl;
+    }
+
+
+    @Override
+    public boolean onEditorAction(TextView textView, int k, KeyEvent keyEvent) {
+        valid();
+        return false;
+    }
+
+    public void valid()
+    {
+        TimeUtils.TimeInterval ti = new TimeUtils.TimeInterval();
+        ArrayList<Word> list = m_dict.constructWithIgnoreAccent(m_et.getText().toString());
+        m_tv.setText(list.size()+" mots trouvés en "+ti.tick()+" s\n");
+        for(int i=0; i<list.size(); i++)
+            m_tv.append(list.get(i)+"\n");
+    }
+
+
+
+    public void onClick(View view) {
+        valid();
+    }
+
+    public void onJeu(View view) {
+       /* String list[] = {
+                "laz",
+                "laz"
+        };
+        WordCheckerList w = new RandomWordChekerList();
+        //WordCheckerList w = new SimpleWordCheckerList(m_dict,list);
+        //DictionaireReader rd = new DictionaireReader(this, R.raw.ligthdico);
+
+        //m_dict.save(this, "test3");
+        //rd.getIndex().save(this, "test3");
+
+        WordActivity.start(this, m_dict, w);*/
+
+        Intent in = new Intent(this, MenuNumberActivity.class);
+        startActivity(in);
+
+
+    }
+}

+ 157 - 0
app/src/main/java/fanch/multijeu/findwords/activity/WordActivity.java

@@ -0,0 +1,157 @@
+package fanch.multijeu.findwords.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+
+import java.io.Serializable;
+
+import fanch.multijeu.R;
+import fanch.multijeu.common.core.SerializedObject;
+import fanch.multijeu.common.core.Word;
+import fanch.multijeu.findwords.data.TryState;
+import fanch.multijeu.findwords.data.dictionaire.Dictionaire;
+import fanch.multijeu.findwords.data.checker.WordChecker;
+import fanch.multijeu.findwords.data.checker.WordCheckerList;
+import fanch.multijeu.findwords.fragments.ComposeFragment;
+import fanch.multijeu.findwords.fragments.SimpleWordViewFragment;
+import fanch.multijeu.findwords.widget.LetterWidget;
+
+public class WordActivity extends AppCompatActivity  implements LetterWidget.LetterWidgetListener {
+
+    protected static final String ARG_DICT="dictionnaire";
+    protected static final String ARG_WORD_CHECKER_LIST="wordCheckerList";
+
+    protected SimpleWordViewFragment mView;
+    protected ComposeFragment       mCompose;
+
+
+
+
+    protected class Model {
+        protected Dictionaire                 dict;
+        protected WordCheckerList             factory;
+        protected WordChecker                 current;
+    }
+
+    private enum DataHolder {
+        INSTANCE;
+
+        private Object mObject;
+
+        public static boolean hasData() {
+            return INSTANCE.mObject != null;
+        }
+
+        public static void setData(final Object object) {
+            INSTANCE.mObject = object;
+        }
+
+        public static Object getData() {
+            final Object retList = INSTANCE.mObject;
+            INSTANCE.mObject = null;
+            return retList;
+        }
+    }
+
+    protected Model m = new Model();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+       Intent in = getIntent();
+        if(in.hasExtra(ARG_WORD_CHECKER_LIST)) {
+            m.factory = (WordCheckerList) ((SerializedObject) in.getSerializableExtra(ARG_WORD_CHECKER_LIST)).newObject();
+        }
+
+        if(DataHolder.hasData())
+            m.dict= (Dictionaire) DataHolder.getData();
+
+        m.current=m.factory.next(m.dict);
+        setContentView(R.layout.activity_word);
+
+        mCompose=(ComposeFragment)getSupportFragmentManager().findFragmentById(R.id.composeFragment);
+        if (savedInstanceState == null) {
+
+            getSupportFragmentManager().beginTransaction().replace(R.id.view_container,
+                    mView=SimpleWordViewFragment.newInstance(m.current.getExpected().keys())).commitNow();
+        }
+    }
+
+    public static void start(Context c, Dictionaire d, WordCheckerList w)
+    {
+        Intent in = new Intent(c, WordActivity.class);
+        DataHolder.setData(d);
+        SerializedObject<WordCheckerList> m = w.saveToSerializedObject();
+        in.putExtra(ARG_WORD_CHECKER_LIST, (Serializable) m);
+
+        c.startActivity(in);
+    }
+
+
+    @Override
+    public void onReady() {
+        mCompose.setLetters(m.current.getLetters());
+    }
+
+    @Override
+    public void onTryWord(Word w) {
+        TryState ts = m.current.tryWord(w);
+        switch(ts)
+        {
+            case YES:
+                onYes(w.toString());
+                if(m.current.expectedLeft()<=0) {
+                    onDone();
+                    if (m.factory.hasNext()) {
+                        setNewChecker(m.factory.next(m.dict));
+                    }
+                    else
+                        onFinish();
+                }
+                break;
+
+            case YES_AGAIN: onYesAgain(w.toString()); break;
+            case BONUS: onBonus(w.toString()); break;
+            case BONUS_AGAIN: onBonusAgain(w.toString()); break;
+            case NO: onNo(w.toString()); break;
+        }
+    }
+
+    private void setNewChecker(WordChecker next) {
+        m.current=next;
+        mCompose.setLetters(next.getLetters());
+        mView.setWordList(next.getExpected().keys());
+    }
+
+    public void onTip(View view) {
+        mView.nextTip();
+    }
+
+    private void onYes(String s) {
+        mView.discoverWord(s,true);
+    }
+
+    private void onYesAgain(String s) {
+    }
+
+    private void onBonus(String s) {
+    }
+
+    private void onBonusAgain(String s) {
+    }
+
+    private void onNo(String s) {
+    }
+
+    private void onFinish() {
+    }
+
+    private void onDone() {
+    }
+
+
+}

+ 185 - 0
app/src/main/java/fanch/multijeu/findwords/data/LetterGenerator.java

@@ -0,0 +1,185 @@
+package fanch.multijeu.findwords.data;
+
+import fanch.multijeu.findwords.data.checker.WordChecker;
+import fanch.multijeu.common.utils.DoubleUtils;
+import fanch.multijeu.common.utils.IntUtils;
+import fanch.multijeu.common.utils.LetterUtils;
+
+public class LetterGenerator {
+
+
+
+    public static char randEquiChar(boolean accent)
+    {
+        int tab[][] = (accent?LetterUtils.LETTER_FREQ:LetterUtils.LETTER_ASCII_FREQ);
+        return (char)tab[IntUtils.rand(0, tab.length-1)][0];
+    }
+
+    public static char randEquiV(boolean accent)
+    {
+        int tab[][] = (accent?LetterUtils.LETTER_FREQ:LetterUtils.LETTER_ASCII_FREQ);
+        int count = (accent?LetterUtils.LETTER_FREQ_V:LetterUtils.LETTER_FREQ_V);
+        int nth = IntUtils.rand(1,count);
+
+        for(int i=0; i<tab.length; i++){
+            if(tab[i][2]==0) nth--;
+            if(nth==0) return (char) tab[i][0];
+        }
+        return 'a';
+    }
+
+    public static char randEquiC(boolean accent)
+    {
+        int tab[][] = (accent?LetterUtils.LETTER_FREQ:LetterUtils.LETTER_ASCII_FREQ);
+        int count = (accent?LetterUtils.LETTER_FREQ_C:LetterUtils.LETTER_FREQ_C);
+        int nth = IntUtils.rand(1,count);
+
+        for(int i=0; i<tab.length; i++){
+            if(tab[i][2]==0) nth--;
+            if(nth==1) return (char) tab[i][0];
+        }
+        return 'a';
+    }
+
+
+
+    public enum Strategy {
+        PONDERATE,
+        EQUIPROBABLE
+    }
+
+    public enum CVStrategy {
+        CONSTRAINT,
+        RAND_RATIO
+    }
+
+    public static class Attrs extends WordChecker.Attrs {
+
+
+        public Strategy strategy=Strategy.PONDERATE;
+        public CVStrategy cvstrategy=CVStrategy.CONSTRAINT;
+
+        public boolean useAccent=false;
+        public int minLettersCount=3;
+        public int maxLettersCount=7;
+
+        //pour CVStrategy=CONSTRAINT
+        public int minVoyellesCount=1;
+        public int minConsonnesCount=1;
+        //fin
+
+
+        //pour CVStrategy=RAND_RATIO = (NVoy/(NVoy+NCons))
+        public double minCVRatio=0.2;
+        public double maxCVRatio=0.5;
+        //fin
+
+
+
+    }
+
+    protected transient StringBuilder mString = new StringBuilder();
+    protected int   mVCount=0;
+    protected int  mCCount=0;
+    protected int  mEndSize=0;
+
+    protected Attrs mAttrs=new Attrs();
+
+    public LetterGenerator() { }
+    public LetterGenerator(Attrs a) { mAttrs=a;}
+
+    protected String generatePonderateConstraint()
+    {
+        while(mCCount+mVCount<mEndSize) {
+            if (mCCount + mVCount + 1 == mEndSize &&
+                    (mCCount < mAttrs.minConsonnesCount || mVCount < mAttrs.minVoyellesCount)) {
+                if (mCCount < mAttrs.minConsonnesCount)
+                    append(LetterUtils.randConsonne(mAttrs.useAccent));
+                else
+                    append(LetterUtils.randVoyelle(mAttrs.useAccent));
+            }
+            append(LetterUtils.randLetter(mAttrs.useAccent));
+        }
+        return mString.toString();
+    }
+
+    protected String generateEquiprobableConstraint()
+    {
+        while(mCCount+mVCount<mEndSize) {
+            if (mCCount + mVCount + 1 == mEndSize &&
+                    (mCCount < mAttrs.minConsonnesCount || mVCount < mAttrs.minVoyellesCount)) {
+                if (mCCount < mAttrs.minConsonnesCount)
+                    append(randEquiC(mAttrs.useAccent));
+                else
+                    append(randEquiV(mAttrs.useAccent));
+            }
+            append(randEquiChar(mAttrs.useAccent));
+        }
+        return mString.toString();
+    }
+
+    protected String generatePonderateRR()
+    {
+        double ratio = DoubleUtils.rand(mAttrs.maxCVRatio, mAttrs.maxCVRatio);
+        int nv = (int)(ratio*mEndSize);
+        int nc = mEndSize-nv;
+
+        for(int i=0; i<nc; i++)
+            append(LetterUtils.randVoyelle(mAttrs.useAccent));
+
+        for(int i=0; i<nv; i++)
+            append(LetterUtils.randConsonne(mAttrs.useAccent));
+
+        return mString.toString();
+    }
+
+
+    protected String generateEquiprobableRR()
+    {
+        double ratio = DoubleUtils.rand(mAttrs.maxCVRatio, mAttrs.maxCVRatio);
+        int nv = (int)(ratio*mEndSize);
+        int nc = mEndSize-nv;
+
+        for(int i=0; i<nc; i++)
+            append(randEquiV(mAttrs.useAccent));
+
+        for(int i=0; i<nv; i++)
+            append(randEquiC(mAttrs.useAccent));
+
+        return mString.toString();
+    }
+
+    public String generate()
+    {
+        mCCount=mVCount=0;
+        mString=new StringBuilder();
+        mEndSize=IntUtils.rand(mAttrs.minLettersCount, mAttrs.maxLettersCount);
+
+
+        switch(mAttrs.strategy)
+        {
+            case PONDERATE:
+                switch(mAttrs.cvstrategy){
+                    case CONSTRAINT: return generatePonderateConstraint();
+                    case RAND_RATIO: return generatePonderateRR();
+                }
+            case EQUIPROBABLE:
+                switch(mAttrs.cvstrategy){
+                    case CONSTRAINT: return generatePonderateConstraint();
+                    case RAND_RATIO: return generateEquiprobableRR();
+                }
+            default:
+                return null;
+        }
+    }
+
+
+    protected void append(char c)
+    {
+        mString.append(c);
+        if(LetterUtils.isVoyelle(c)) mVCount++;
+        else mCCount++;
+    }
+
+
+}

+ 9 - 0
app/src/main/java/fanch/multijeu/findwords/data/TryState.java

@@ -0,0 +1,9 @@
+package fanch.multijeu.findwords.data;
+
+public enum TryState {
+    YES,
+    YES_AGAIN,
+    NO,
+    BONUS,
+    BONUS_AGAIN
+}

+ 44 - 0
app/src/main/java/fanch/multijeu/findwords/data/WordPair.java

@@ -0,0 +1,44 @@
+package fanch.multijeu.findwords.data;
+
+import android.support.annotation.NonNull;
+
+import java.io.Serializable;
+
+import fanch.multijeu.common.core.Word;
+
+public class WordPair implements Comparable<WordPair>, Serializable {
+    public Word first=new Word();
+    public WordState second;
+
+
+    public WordPair() {
+    }
+
+    public WordPair(Word first) {
+        this.first=first;
+    }
+
+    public WordPair(Word first, WordState second) {
+        this.first=first;
+        this.second=second;
+
+    }
+
+
+
+    @Override
+    public int compareTo(@NonNull WordPair tWordPair) {
+        int me = first.length();
+        int o = tWordPair.first.length();
+        if(me==o) return 0;
+        if(me<o) return -1;
+        return 1;
+    }
+
+    public String toString()
+    {
+        return "('"+first+"', "+second.toString()+")\n";
+    }
+
+
+}

+ 195 - 0
app/src/main/java/fanch/multijeu/findwords/data/WordSet.java

@@ -0,0 +1,195 @@
+package fanch.multijeu.findwords.data;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.Word;
+
+public class WordSet extends ArrayList<WordPair>  {
+
+
+    protected int m_found=0;
+
+
+    public WordSet()
+    {
+
+    }
+
+    public WordSet(ArrayList<Word> res)
+    {
+        for(int i=0; i<res.size(); i++)
+            add(res.get(i));
+    }
+
+    public WordSet(Word[] res)
+    {
+        for(int i=0; i<res.length; i++)
+            add(res[i]);
+    }
+
+    public WordSet(ArrayList<Word> res, int min)
+    {
+        for(int i=0; i<res.size(); i++)
+        {
+            Word s = res.get(i);
+            if(s.length()>=min)
+                add(s);
+        }
+    }
+
+    public WordSet(ArrayList<Word> res, int min, int max)
+    {
+        for (Word s : res)
+        {
+            if(s.length()>=min && s.length()<=max)
+                add(s);
+        }
+    }
+
+    public synchronized void setMin(int min)
+    {
+        for (int i=size()-1; i>=0; i--) {
+            if(get(i).first.length()<min)
+                remove(get(i));
+        }
+    }
+
+
+    public synchronized void setMax(int max)
+    {
+        for (int i=size()-1; i>=0; i--) {
+            if(get(i).first.length()>max)
+                remove(get(i));
+        }
+    }
+
+    public synchronized void setMinMax(int min, int max)
+    {
+        for (int i=size()-1; i>=0; i--) {
+            if(get(i).first.length()>max || get(i).first.length()<min)
+                remove(get(i));
+        }
+    }
+
+    public synchronized boolean add(Word s)
+    {
+        if(contains(s)) return false;
+        super.add(new WordPair(s, WordState.NOT_FOUND));
+        return true;
+    }
+
+    public synchronized boolean add(Word s, WordState x)
+    {
+        if(contains(s)) return false;
+        super.add(new WordPair(s, x));
+        return true;
+    }
+
+    public synchronized boolean contains(Word s, boolean accentSensitive)
+    {
+        return getPair(s, accentSensitive)!=null;
+    }
+
+    protected synchronized boolean set(Word s, WordState value)
+    {
+        for (WordPair t : this) {
+            if(t.first.equals(s)) {
+                t = new WordPair(s, value);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public int countFound(){return m_found;}
+    public int countNotFound(){return size()-m_found;}
+
+    public synchronized WordState get(Word s, boolean accentSensitive)
+    {
+        WordPair p = getPair(s, accentSensitive);
+        if(p!=null) return p.second;
+        return WordState.ERROR;
+    }
+
+    public synchronized ArrayList<WordPair> getAll(Word s, boolean accentSensitive)
+    {
+        ArrayList<WordPair> p = new ArrayList<>();
+        for (WordPair t : this) {
+            if( t.first.equals(s, false, accentSensitive))
+                p.add(t);
+        }
+        return p;
+    }
+
+
+
+    public synchronized boolean found(Word s, boolean accentSensitive)
+    {
+        WordPair p = getPair(s,  accentSensitive);
+        if(p!=null)
+        {
+            if(p.second.equals(WordState.NOT_FOUND)) m_found++;
+            p.second=WordState.FOUND;
+            return true;
+        }
+        return false;
+    }
+
+    public synchronized ArrayList<String> keys()
+    {
+        ArrayList<String> out = new ArrayList<String>();
+        for (WordPair t : this)
+            out.add(t.first.toString());
+        return out;
+    }
+
+    public synchronized ArrayList values()
+    {
+        ArrayList out = new ArrayList();
+        for (WordPair t : this)
+            out.add(t.second);
+        return out;
+    }
+
+
+    public synchronized void exclude(WordSet toExclude)
+    {
+        for(WordPair t:toExclude)
+            remove(t.first);
+    }
+
+    public synchronized void include(WordSet inc)
+    {
+        for(WordPair t:inc)
+            add(t.first, t.second);
+    }
+
+    private synchronized void remove(Word s, boolean accentSensitive)
+    {
+        for (WordPair t : this) {
+            if(t.first.equals(s, false, accentSensitive))
+                remove(t);
+        }
+    }
+
+    protected synchronized WordPair getPair(Word s, boolean accentSensitive)
+    {
+        for (WordPair t : this) {
+            if( t.first.equals(s, false, accentSensitive))
+                return t;
+        }
+        return null;
+    }
+
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{");
+        for(int i=0; i<size(); i++) {
+            if(i>0)builder.append(", ");
+            builder.append(get(i));
+        }
+        return builder.toString();
+    }
+
+}

+ 7 - 0
app/src/main/java/fanch/multijeu/findwords/data/WordState.java

@@ -0,0 +1,7 @@
+package fanch.multijeu.findwords.data;
+
+public enum WordState {
+    NOT_FOUND,
+    FOUND,
+    ERROR
+}

+ 23 - 0
app/src/main/java/fanch/multijeu/findwords/data/checker/AbstractWordCheckerList.java

@@ -0,0 +1,23 @@
+package fanch.multijeu.findwords.data.checker;
+
+import fanch.multijeu.common.core.Saveable;
+
+public abstract class AbstractWordCheckerList extends Saveable implements WordCheckerList {
+
+
+    AbstractWordCheckerList(WordChecker.Attrs a)
+    {
+        if(a==null) a=new WordChecker.Attrs();
+        setAttrs(a);
+    }
+
+    protected WordChecker.Attrs mAttrs;
+    public WordChecker.Attrs getAttrs() {
+        return mAttrs;
+    }
+
+    public void setAttrs(WordChecker.Attrs mAttrs) {
+        if(mAttrs!=null) this.mAttrs = mAttrs;
+        else mAttrs=new WordChecker.Attrs();
+    }
+}

+ 64 - 0
app/src/main/java/fanch/multijeu/findwords/data/checker/RandomWordChekerList.java

@@ -0,0 +1,64 @@
+package fanch.multijeu.findwords.data.checker;
+
+import android.util.Log;
+
+import fanch.multijeu.findwords.data.dictionaire.Dictionaire;
+import fanch.multijeu.findwords.data.LetterGenerator;
+
+public class RandomWordChekerList extends AbstractWordCheckerList {
+
+    public static class Attrs extends LetterGenerator.Attrs {
+        public int generationCount=10;
+    }
+
+    protected LetterGenerator.Attrs mAttrs=new LetterGenerator.Attrs();
+    protected WordChecker   mLastChecker=null;
+
+
+    public RandomWordChekerList()
+    {
+        super(null);
+    }
+
+    public RandomWordChekerList(Attrs attrs)
+    {
+        super(attrs);
+    }
+
+
+    public WordChecker getLastChecker() {
+        return mLastChecker;
+    }
+
+    public void setLastChecker(WordChecker mLastChecker) {
+        this.mLastChecker = mLastChecker;
+    }
+
+
+    @Override
+    public WordChecker next(Dictionaire dico) {
+        mLastChecker=WordChecker.generate(dico, mAttrs);
+        Log.e("------", "Lettres : "+mLastChecker.getLetters()
+                +" -> "+mLastChecker.getExpected().size()
+                +" / "+mLastChecker.getBonus().size());
+
+        return mLastChecker;
+    }
+
+
+
+    @Override
+    public WordChecker peek() {
+        return mLastChecker;
+    }
+
+    @Override
+    public boolean hasNext() {
+        return true;
+    }
+
+    @Override
+    public void reset() {
+
+    }
+}

+ 88 - 0
app/src/main/java/fanch/multijeu/findwords/data/checker/SimpleWordCheckerList.java

@@ -0,0 +1,88 @@
+package fanch.multijeu.findwords.data.checker;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.findwords.data.dictionaire.Dictionaire;
+
+public class SimpleWordCheckerList extends AbstractWordCheckerList {
+
+    protected ArrayList<WordChecker> mCheecker = new ArrayList<>();
+    protected int                    mIndex=-1;
+
+    public SimpleWordCheckerList(Dictionaire d, ArrayList<String> c)
+    {
+        super(null);
+        add(d,c);
+    }
+
+    public SimpleWordCheckerList(Dictionaire d, ArrayList<String> c, WordChecker.Attrs a)
+    {
+        super(a);
+        add(d,c);
+    }
+
+    public SimpleWordCheckerList(Dictionaire d, String[] c)
+    {
+        super(null);
+        add(d,c);
+    }
+
+    public SimpleWordCheckerList(Dictionaire d, String c)
+    {
+        super(null);
+        add(d,c);
+    }
+
+    public SimpleWordCheckerList(Dictionaire d, String[] c, WordChecker.Attrs a)
+    {
+        super(a);
+        add(d,c);
+    }
+
+    public SimpleWordCheckerList(Dictionaire d, String c, WordChecker.Attrs a)
+    {
+        super(a);
+        add(d,c);
+    }
+
+
+
+    public void add(Dictionaire d, String wc)
+    {
+
+        mCheecker.add(new WordChecker(d, wc, mAttrs));
+    }
+
+    public void add(Dictionaire d, String[] c)
+    {
+        for(int i=0; i<c.length; i++)
+            add(d,c[i]);
+    }
+
+    public void add(Dictionaire d, ArrayList<String> c)
+    {
+        for(int i=0; i<c.size(); i++)
+           add(d,c.get(i));
+    }
+
+    @Override
+    public WordChecker next(Dictionaire d) {
+        ++mIndex;
+        return peek();
+    }
+
+    @Override
+    public WordChecker peek() {
+        return mCheecker.get(mIndex);
+    }
+
+    @Override
+    public boolean hasNext() {
+        return mIndex+1<mCheecker.size();
+    }
+
+    @Override
+    public void reset() {
+        mIndex=-1;
+    }
+}

+ 269 - 0
app/src/main/java/fanch/multijeu/findwords/data/checker/WordChecker.java

@@ -0,0 +1,269 @@
+package fanch.multijeu.findwords.data.checker;
+
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.common.core.Word;
+import fanch.multijeu.common.exceptions.BadParametterException;
+import fanch.multijeu.findwords.data.dictionaire.Dictionaire;
+import fanch.multijeu.findwords.data.LetterGenerator;
+import fanch.multijeu.findwords.data.TryState;
+import fanch.multijeu.findwords.data.WordPair;
+import fanch.multijeu.findwords.data.WordSet;
+import fanch.multijeu.findwords.data.WordState;
+import fanch.multijeu.common.utils.IntUtils;
+
+public class WordChecker extends Saveable {
+
+    public static class Attrs extends Saveable
+    {
+        public int minWordSize=2;
+        public int maxWordSize=7;
+        public int maxWordsCount=8;
+        public int minWordsCount=1;
+        public boolean accentSensitive=true;
+    }
+
+
+    protected String      m_letters;
+    protected WordSet     m_bonus;
+    protected WordSet     m_expected;
+    protected int         m_found=0;
+    private   Attrs       mAttrs = new Attrs();
+    private transient     Object  m_lock = new Object();
+
+
+
+
+    public WordChecker(Dictionaire d, String letters, WordSet expected)
+    {
+        reset(d, letters, expected);
+    }
+
+    public WordChecker(Dictionaire d, String letters)
+    {
+        reset(d, letters, null);
+    }
+
+
+    public WordChecker(Dictionaire d, String letters, WordSet expected, Attrs a)
+    {
+        mAttrs=a;
+        reset(d, letters, expected);
+    }
+
+    public WordChecker(Dictionaire d, String letters, Attrs a)
+    {
+        mAttrs=a;
+        reset(d, letters, null);
+    }
+
+
+    public String getLetters()
+    {
+        return m_letters;
+    }
+
+    public void addSolution(Word s)
+    {
+        m_expected.add(s);
+    }
+
+    public WordSet getExpected()
+    {
+        return m_expected;
+    }
+
+    public WordSet getBonus()
+    {
+        return m_bonus;
+    }
+
+
+    public synchronized void reset(Dictionaire dict, String letters, WordSet exp)
+    {
+        m_letters = letters.toLowerCase();
+        m_expected = exp;
+        if(mAttrs.accentSensitive)
+            m_bonus = new WordSet(dict.constructWith(m_letters));
+        else
+            m_bonus = new WordSet(dict.constructWithIgnoreAccent(m_letters));
+
+        synchronized (m_bonus) {
+            if(m_expected==null)
+                m_expected=m_bonus;
+            else
+                m_bonus.exclude(m_expected);
+        }
+    }
+
+    public int expectedLeft()
+    {
+        return m_expected.size()-m_found;
+    }
+
+    public synchronized TryState tryWord(Word s)
+    {
+        synchronized (m_bonus)
+        {
+            TryState t = TryState.NO;
+            WordPair pair;
+            if(m_expected.contains(s,mAttrs.accentSensitive))
+            {
+                ArrayList<WordPair> p = m_expected.getAll(s, mAttrs.accentSensitive);
+                for(int i=0; i<p.size(); i++)
+                {
+                    pair=p.get(i);
+                    if(pair.second==WordState.NOT_FOUND)
+                    {
+                        m_expected.found(s, mAttrs.accentSensitive);
+                        m_found++;
+                        t=TryState.YES;
+                    }else
+                    {
+                        if(t==TryState.NO) t=TryState.YES_AGAIN;
+                    }
+                }
+                return t;
+            }
+            else if( m_bonus.contains(s,mAttrs.accentSensitive))
+            {
+                ArrayList<WordPair> p = m_bonus.getAll(s, mAttrs.accentSensitive);
+                for(int i=0; i<p.size(); i++)
+                {
+                    pair=p.get(i);
+                    if(pair.second==WordState.NOT_FOUND)
+                    {
+                        m_bonus.found(s, mAttrs.accentSensitive);
+                        t=TryState.BONUS;
+                    }else
+                    {
+                        if(t==TryState.NO) t=TryState.BONUS_AGAIN;
+                    }
+                }
+                return t;
+            }
+            else return TryState.NO;
+        }
+    }
+
+
+
+    public  void setMinCharcters(int min)
+    {
+        synchronized(m_bonus) {
+            m_bonus.setMin(min);
+        }
+    }
+
+    public synchronized void setMaxCharcters(int max)
+    {
+        synchronized(m_bonus) {
+            m_bonus.setMax(max);
+        }
+    }
+
+    public synchronized void setMinMaxCharcters(int min, int max)
+    {
+        synchronized(m_bonus) {
+            m_bonus.setMinMax(min, max);
+        }
+    }
+
+    public synchronized void setMaxWordCount(int maxWordsCount) {
+        while(m_expected.size()>maxWordsCount)
+        {
+            m_expected.remove(IntUtils.rand(0, m_expected.size()-1));
+        }
+    }
+
+    /*
+     * Generate
+     */
+
+    protected static void generateCheck(LetterGenerator.Attrs a)
+    {
+        if(a.minWordSize>0 && a.maxWordSize>0 && a.minWordSize>a.maxWordSize)
+            throw new BadParametterException("WordChecker::generate : minWordSize>maxWordSize "
+                    +"("+a.minWordSize+">"+a.maxWordSize+")");
+
+        if(a.minVoyellesCount>0 && a.maxLettersCount>0 && a.minVoyellesCount>a.maxLettersCount)
+            throw new BadParametterException("WordChecker::generate : minVoyellesCount>maxLettersCount "
+                    +"("+a.minVoyellesCount+">"+a.maxLettersCount+")");
+
+        if(a.minConsonnesCount>0 && a.maxLettersCount>0 && a.minConsonnesCount>a.maxLettersCount)
+            throw new BadParametterException("WordChecker::generate : minConsonnesCount>maxLettersCount "
+                    +"("+a.minConsonnesCount+">"+a.maxLettersCount+")");
+
+        if(a.minWordSize>0 && a.maxLettersCount>0 && a.minWordSize>a.maxLettersCount)
+            throw new BadParametterException("WordChecker::generate : minWordSize>maxLettersCount "
+                    +"("+a.minWordSize+">"+a.maxLettersCount+")");
+
+        if(a.minLettersCount>0 && a.maxWordSize>0 && a.minLettersCount>a.maxWordSize)
+            throw new BadParametterException("WordChecker::generate : minLettersCount>maxWordSize "
+                    +"("+a.minLettersCount+">"+a.maxWordSize+")");
+
+        if(a.minLettersCount>0 && a.maxLettersCount>0 && a.minLettersCount>a.maxLettersCount)
+            throw new BadParametterException("WordChecker::generate : minLettersCount>maxLettersCount "
+                    +"("+a.minLettersCount+">"+a.maxLettersCount+")");
+
+
+        if(a.minWordsCount>0 && a.maxWordsCount>0 && a.minWordsCount>a.maxWordsCount)
+            throw new BadParametterException("WordChecker::generate : minWordsCount>maxWordsCount "
+                    +"("+a.minWordsCount+">"+a.maxWordsCount+")");
+    }
+
+
+    public static WordChecker generate(@NonNull Dictionaire d, LetterGenerator.Attrs a)
+    {
+        String s="";
+        WordChecker chk;
+        LetterGenerator gen = new LetterGenerator(a);
+
+        if(a==null) a=new LetterGenerator.Attrs();
+        generateCheck(a);
+
+
+
+
+        do{
+            s=gen.generate();
+            chk=new WordChecker(d,s);
+
+            if(a.minWordSize>=0 && a.maxWordSize>=0)
+                chk.setMinMaxCharcters(a.minWordSize, a.maxWordSize);
+            else if(a.minWordSize>=0)
+                chk.setMinCharcters(a.minWordSize);
+            else if(a.maxWordSize>=0)
+                chk.setMaxCharcters(a.maxWordSize);
+
+            if(a.maxWordsCount>0)
+                chk.setMaxWordCount(a.maxWordsCount);
+        }while(a.minWordsCount>0 && chk.getExpected().size()<a.minWordsCount);
+
+        return chk;
+    }
+
+    public static WordChecker generate(@NonNull Dictionaire d) {
+        return generate(d,null);
+    }
+
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Letters: ");
+        builder.append(m_letters);
+        builder.append("\nExpected: ");
+        builder.append(m_expected);
+        builder.append("\nBonus: ");
+        builder.append(m_bonus);
+        builder.append("\nFound: ");
+        builder.append(m_found);
+        builder.append("\n");
+
+        return builder.toString();
+    }
+
+}

+ 12 - 0
app/src/main/java/fanch/multijeu/findwords/data/checker/WordCheckerList.java

@@ -0,0 +1,12 @@
+package fanch.multijeu.findwords.data.checker;
+
+import fanch.multijeu.common.core.DataLoader;
+import fanch.multijeu.findwords.data.dictionaire.Dictionaire;
+
+public interface WordCheckerList extends DataLoader.ISaveable {
+
+    WordChecker  next(Dictionaire dico);
+    WordChecker  peek();
+    boolean     hasNext();
+    void        reset();
+}

+ 83 - 0
app/src/main/java/fanch/multijeu/findwords/data/dictionaire/Dictionaire.java

@@ -0,0 +1,83 @@
+package fanch.multijeu.findwords.data.dictionaire;
+
+import android.content.Context;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.common.core.Word;
+
+public class Dictionaire extends Saveable {
+
+    public enum Source {
+        RESOURCE_RAW,
+        APP_FILE,
+        NON_APP_FILE
+    }
+    protected Source mSource;
+    protected int mRes=-1;
+    protected String mFile=null;
+    protected DictionaireReader mReader;
+    protected DictionaireIndex mIndex;
+
+    public Dictionaire(Context c, int res) throws FileNotFoundException {
+        mSource=Source.RESOURCE_RAW;
+        mRes=res;
+        mReader=new DictionaireReader(newInputStream(c));
+        mIndex=mReader.getIndex();
+    }
+
+    public Dictionaire(Context c, String res) throws FileNotFoundException {
+        mSource=Source.APP_FILE;
+        mFile=res;
+        mReader=new DictionaireReader(newInputStream(c));
+        mIndex=mReader.getIndex();
+    }
+
+    public Dictionaire(String res) throws FileNotFoundException {
+        mSource=Source.NON_APP_FILE;
+        mFile=res;
+        mReader=new DictionaireReader(newInputStream(null));
+        mIndex=mReader.getIndex();
+    }
+
+    protected InputStream newInputStream(Context c) throws FileNotFoundException {
+        switch (mSource)
+        {
+            case NON_APP_FILE:
+            {
+                FileInputStream fis;
+                fis = new FileInputStream(mFile);
+                return fis;
+            }
+
+
+            case APP_FILE:
+            {
+                FileInputStream fis = c.openFileInput(mFile);
+                return fis;
+            }
+
+            case RESOURCE_RAW:
+                return c.getResources().openRawResource(mRes);
+
+            default:
+                return null;
+        }
+    }
+
+    public void resetStream(Context c, boolean close) throws FileNotFoundException {
+        if(close) mReader.close();
+        mReader=new DictionaireReader(newInputStream(c));
+    }
+
+
+
+    public ArrayList<Word> constructWith(String m_letters) {return mReader.constructWith(m_letters); }
+    public ArrayList<Word> constructWithIgnoreAccent(String m_letters) { return mReader.constructWithIgnoreAccent(m_letters);    }
+
+
+}

+ 81 - 0
app/src/main/java/fanch/multijeu/findwords/data/dictionaire/DictionaireIndex.java

@@ -0,0 +1,81 @@
+package fanch.multijeu.findwords.data.dictionaire;
+
+import android.content.Context;
+
+import java.io.InputStream;
+
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.common.core.Word;
+
+public class DictionaireIndex extends Saveable {
+
+    protected int mCharLentgh;
+
+    public DictionaireIndex()
+    {
+    }
+
+    DictionaireIndex(int depth)
+    {
+        if(depth>0)
+        {
+            child=new DictionaireIndex[27];
+            for(int i=0; i<27; i++)
+                child[i]=new DictionaireIndex(depth-1);
+        }
+    }
+
+    int getIndex(char x)
+    {
+        if(x>='a' && x<='z') return x-'a'+1;
+        if(x>='A' && x<='Z') return x-'A'+1;
+        return 0;
+    }
+
+    void set(long ind, Word s, int offset)
+    {
+        if(index<0) index=ind;
+        if(s.length()>offset && child!=null)
+            child[getIndex(s.charAt(offset))].set(ind, s, offset+1);
+    }
+
+    void set(long ind, Word s)
+    {
+        set(ind, s,0);
+    }
+
+    long find(String s)
+    {
+        return find(s, 0);
+    }
+
+    long find(String s, int offset)
+    {
+        if(offset<s.length() && child!=null)
+            return child[getIndex(s.charAt(offset))].find(s, offset+1);
+        return index;
+    }
+
+
+    DictionaireIndex child[]=null;
+    long index=-1;
+
+    public DictionaireReader newDictionaireReader(InputStream is)
+    {
+        return new DictionaireReader(is, this);
+    }
+
+
+    public DictionaireReader newDictionaireReader(Context c, int res)
+    {
+        return new DictionaireReader(c.getResources().openRawResource(res));
+    }
+
+    public int getCharLentgh() {
+        return mCharLentgh;
+    }
+
+    public void setCharLentgh(int mCharLentgh) {
+        this.mCharLentgh = mCharLentgh;
+    }
+}

+ 257 - 0
app/src/main/java/fanch/multijeu/findwords/data/dictionaire/DictionaireReader.java

@@ -0,0 +1,257 @@
+package fanch.multijeu.findwords.data.dictionaire;
+
+import android.content.Context;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.Word;
+import fanch.multijeu.common.utils.IntUtils;
+import fanch.multijeu.common.utils.StringUtils;
+
+
+public class DictionaireReader{
+
+    protected InputStream       m_is;
+
+    protected int               m_depth=2;
+    protected int               m_current;
+    protected long              m_nchar=0;
+    protected long              m_prevNchar=0;
+    protected DictionaireIndex m_data;
+
+
+    public DictionaireReader(Context c, int file)
+    {
+        m_is = c.getResources().openRawResource(file);
+
+        m_data=new DictionaireIndex(m_depth);
+        m_is.mark(100000000);
+        doIndex();
+    }
+
+    public DictionaireReader(InputStream is, int depth)
+    {
+        m_is = is;
+        m_depth=depth;
+        m_data=new DictionaireIndex(m_depth);
+        m_is.mark(100000000);
+        doIndex();
+    }
+
+    public DictionaireReader(InputStream is)
+    {
+        m_is = is;
+        m_data=new DictionaireIndex(m_depth);
+        m_is.mark(100000000);
+        doIndex();
+    }
+
+    public DictionaireReader(InputStream is, DictionaireIndex index)
+    {
+        m_is = is;
+        m_is.mark(100000000);
+        m_data=index;
+    }
+
+    public DictionaireIndex getIndex()
+    {
+        return m_data;
+    }
+
+
+    public long find(String s)
+    {
+        int min = m_depth<s.length()?m_depth:s.length();
+        String prefix = s.substring(0, m_depth);
+        long start = m_data.find(s);
+        seek(start);
+
+        Word iter;
+        do{
+            iter=readWord();
+            if(iter.compareTo(s)==0) return m_prevNchar;
+        }while(iter.indexOf(prefix)==0);
+        return -1;
+    }
+
+    public ArrayList<Word> constructWith(String in)
+    {
+        ArrayList<Word> out = new ArrayList<Word>();
+        seek(0);
+        Word s;
+
+        do {
+            s=readWord();
+            int n=0;
+
+            if(s==null) break;
+            if(in.length()<s.length()) continue;
+            char word[] = s.toCharArray();
+
+
+            for(int i=0; i<in.length(); i++){
+                for(int j=0; j<word.length; j++) {
+                    if (word[j] == in.charAt(i)) {
+                        n++;
+                        word[j]=0;
+                        break;
+                    }
+                }
+            }
+
+            if(n==s.length())
+            {
+                out.add(s);
+            }
+
+        }while (s!=null && s.length()>0);
+        return out;
+    }
+
+    public ArrayList<Word> constructWithIgnoreAccent(String iin)
+    {
+        ArrayList<Word> out = new ArrayList<Word>();
+        seek(0);
+        Word s;
+        String in = StringUtils.toAscii(iin);
+
+        do {
+            s=readWord();
+            int n=0;
+
+            if(s==null) break;
+            if(in.length()<s.length()) continue;
+            char word[] = s.toAscii().toCharArray();
+
+
+            for(int i=0; i<in.length(); i++){
+                for(int j=0; j<word.length; j++) {
+                    if (word[j] == in.charAt(i)) {
+                        n++;
+                        word[j]=0;
+                        break;
+                    }
+                }
+            }
+
+            if(n==s.length())
+            {
+                out.add(s);
+            }
+
+        }while (s!=null && s.length()>0);
+        return out;
+    }
+
+
+
+
+    protected boolean seek(long start)
+    {
+        try {
+            m_is.reset();
+            m_prevNchar=start;
+            m_nchar=start;
+            m_is.skip(start);
+
+            return true;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+
+    protected  void doIndex()
+    {
+        Word s;
+        int  i=0;
+        do {
+            int a=0,b=0,c=0;
+            s=readWord();
+            if(s==null) break;
+            m_data.set(m_prevNchar, s);
+        }while (s!=null && s.length()>0);
+        m_data.setCharLentgh((int) m_nchar);
+
+    }
+
+    protected void reset()
+    {
+        try {
+            m_is.reset();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        m_prevNchar=0;
+        m_nchar=0;
+    }
+
+
+
+
+
+    protected int nextChar() {
+
+        try {
+
+            m_current=m_is.read();
+            m_nchar++;
+
+            return m_current;
+        } catch (IOException e) {
+            e.printStackTrace();
+            return m_current=-1;
+        }
+    }
+
+    protected int getCurrentChar(){return m_current;}
+
+
+    public Word randWord()
+    {
+        int n = IntUtils.rand(0, m_data.getCharLentgh());
+        seek(n);
+        goToNextWord();
+        return readWord();
+    }
+
+    protected void goToNextWord()
+    {
+        int i;
+        m_prevNchar=m_nchar;
+        String out = "";
+        do {
+            char c=(char)nextChar();
+            if(c!='\n') out+=c;
+        }while(getCurrentChar()!=-1 && getCurrentChar()!='\n');
+    }
+
+    public Word readWord()
+    {
+        int i;
+         m_prevNchar=m_nchar;
+        String out = "";
+        do {
+            char c=(char)nextChar();
+            if(c!='\n') out+=c;
+        }while(getCurrentChar()!=-1 && getCurrentChar()!='\n');
+        if(out.length()==0 || getCurrentChar()==-1) return null;
+
+        return  new Word(out);
+    }
+
+    public void close()
+    {
+        try {
+            m_is.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+
+
+}

+ 63 - 0
app/src/main/java/fanch/multijeu/findwords/fragments/ComposeFragment.java

@@ -0,0 +1,63 @@
+package fanch.multijeu.findwords.fragments;
+
+import android.arch.lifecycle.ViewModelProviders;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import fanch.multijeu.R;
+import fanch.multijeu.findwords.data.checker.WordChecker;
+import fanch.multijeu.findwords.fragments.ui.compose.ComposeViewModel;
+import fanch.multijeu.findwords.widget.LetterWidget;
+
+public class ComposeFragment extends Fragment {
+
+    private ComposeViewModel mViewModel;
+
+    public static ComposeFragment newInstance() {
+        return new ComposeFragment();
+    }
+    protected LetterWidget mCompose;
+
+
+
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        View v = inflater.inflate(R.layout.compose_fragment, container, false);
+        mCompose=v.findViewById(R.id.compose_word_view);
+        if(!(getActivity() instanceof LetterWidget.LetterWidgetListener))
+            throw new ClassCastException("Activity must implements LetterWidgetListener");
+        mCompose.setListener((LetterWidget.LetterWidgetListener) getActivity());
+
+        return v;
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mViewModel = ViewModelProviders.of(this).get(ComposeViewModel.class);
+        // TODO: Use the ViewModel
+    }
+
+
+
+    public void setLetters(String s)
+    {
+        mViewModel.mLetters=s;
+        mCompose.setLetters(s);
+    }
+
+
+    //TODO
+    public static ComposeFragment newInstance(WordChecker c) {
+        ComposeFragment f = new ComposeFragment();
+        return f;
+    }
+
+}

+ 104 - 0
app/src/main/java/fanch/multijeu/findwords/fragments/SimpleWordViewFragment.java

@@ -0,0 +1,104 @@
+package fanch.multijeu.findwords.fragments;
+
+import android.arch.lifecycle.ViewModelProviders;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.R;
+import fanch.multijeu.findwords.fragments.model.SimpleWordViewViewModel;
+import fanch.multijeu.findwords.widget.PlaceHolderRootLayout;
+
+public class SimpleWordViewFragment extends Fragment {
+
+    private SimpleWordViewViewModel mViewModel;
+
+    public static SimpleWordViewFragment newInstance() {
+        return new SimpleWordViewFragment();
+    }
+
+    protected ViewGroup mRoot;
+    protected Bundle    mArgs;
+    protected PlaceHolderRootLayout mLayout;
+
+    protected static final String ARG_WORDLIST="wordList";
+
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        View ret = inflater.inflate(R.layout.simple_word_view_fragment, container, false);
+
+        mArgs=getArguments();
+        mRoot=ret.findViewById(R.id.container);
+        mLayout=new PlaceHolderRootLayout(getContext());
+        mRoot.addView(mLayout);
+
+        if(savedInstanceState==null)
+        {
+            mViewModel=new SimpleWordViewViewModel();
+            if(mArgs!=null) doArgs();
+        }else
+        {
+            init(savedInstanceState);
+        }
+
+        return ret;
+    }
+
+    protected void init(Bundle savedInstanceState) {
+    }
+
+    protected void doArgs()
+    {
+        ArrayList<String> x = mArgs.getStringArrayList(ARG_WORDLIST);
+        setWordList(x);
+    }
+
+    public void setWordList(ArrayList<String> words)
+    {
+        mViewModel.mFound=new ArrayList<>();
+        mViewModel.mTips=0;
+        mViewModel.mWordList = words;
+        mLayout.setWords(words);
+    }
+
+    public void nextTip()
+    {
+        mLayout.nextTip();
+        mViewModel.mTips++;
+    }
+
+
+    public boolean hasNextTip()
+    {
+        return mLayout.hasNextTip();
+    }
+
+    public void discoverWord(String s, boolean accentSensitive)
+    {
+        mLayout.discoverWord(s, accentSensitive);
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mViewModel = ViewModelProviders.of(this).get(SimpleWordViewViewModel.class);
+    }
+
+    public static SimpleWordViewFragment newInstance(ArrayList<String> wordList) {
+        SimpleWordViewFragment f = new SimpleWordViewFragment();
+        // Supply index input as an argument.
+        Bundle args = new Bundle();
+        args.putStringArrayList(ARG_WORDLIST, wordList);
+        f.setArguments(args);
+        return f;
+    }
+
+
+}

+ 12 - 0
app/src/main/java/fanch/multijeu/findwords/fragments/model/SimpleWordViewViewModel.java

@@ -0,0 +1,12 @@
+package fanch.multijeu.findwords.fragments.model;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.CustomViewModel;
+
+public class SimpleWordViewViewModel extends CustomViewModel {
+    public ArrayList<String> mWordList;
+    public ArrayList<String> mFound=new ArrayList<>();
+    public int               mTips=0;
+
+}

+ 7 - 0
app/src/main/java/fanch/multijeu/findwords/fragments/ui/compose/ComposeViewModel.java

@@ -0,0 +1,7 @@
+package fanch.multijeu.findwords.fragments.ui.compose;
+
+import fanch.multijeu.common.core.CustomViewModel;
+
+public class ComposeViewModel extends CustomViewModel {
+    public String mLetters;
+}

+ 123 - 0
app/src/main/java/fanch/multijeu/findwords/widget/CircularLayout.java

@@ -0,0 +1,123 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import java.util.ArrayList;
+
+public class CircularLayout extends ViewGroup{
+    protected float mRadiusRatio= (float) 0.7;
+
+    protected int mFixedSize=-1;
+
+    public CircularLayout(Context context) {
+        super(context);
+
+    }
+
+    public CircularLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public Button newButton()
+    {
+        Button b = new Button(getContext());
+        b.setLayoutParams(new ViewGroup.LayoutParams(50,50) );
+        return b;
+    }
+
+    public float getRadiusRatio() {
+        return mRadiusRatio;
+    }
+
+    public void setRadiusRatio(float mRadiusRatio) {
+        this.mRadiusRatio = mRadiusRatio;
+    }
+
+    public int getFixedSize() {
+        return mFixedSize;
+    }
+
+    public void setFixedSize(int mFixedSize) {
+        this.mFixedSize = mFixedSize;
+        if(mFixedSize<=0) return;
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            child.setLayoutParams(new ViewGroup.LayoutParams(mFixedSize, mFixedSize));
+        }
+    }
+
+    public class LayoutParams extends ViewGroup.LayoutParams
+    {
+        public int top=0;
+        public int left=0;
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(int x, int y, int width, int height) {
+            super(width, height);
+            top=y;
+            left=x;
+        }
+    }
+
+    protected void onLayout(boolean b, int left, int top, int right, int bottom) {
+        int w = getWidth();
+        int h = getHeight();
+        int min = (w<h)?w:h;
+        final int radius = (int) (0.5*min*mRadiusRatio);
+        final int count = getChildCount();
+        final double angle = 2*Math.PI/count;
+        final int xmiddle = left+ (right-left)/2;
+        final int ymiddle = top+(bottom-top)/2;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                int width = child.getLayoutParams().width;
+                int height = child.getLayoutParams().height;
+                if(mFixedSize>0)
+                {
+                    width=height=mFixedSize;
+                }
+                final double cangle = angle*i;
+                int childLeft;
+                int childTop;
+
+                childLeft = xmiddle+(int)(radius*Math.cos(i*angle-Math.PI/2))-width/2;
+                childTop = ymiddle+(int)(radius*Math.sin(i*angle-Math.PI/2))-height/2;
+
+                child.layout(childLeft, childTop, childLeft + width, childTop + height);
+                //child.layout(0,0, 200,100);
+
+            }
+        }
+    }
+
+    ArrayList<View> getChildrenAt(int x, int y)
+    {
+        ArrayList<View> l = new ArrayList<>();
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            final int top = child.getTop();
+            final int left = child.getLeft();
+            final int right = child.getLeft()+child.getWidth();
+            final int bottom = child.getTop()+child.getHeight();
+
+            if( x>=left && x<=right && y>=top && y<=bottom)
+            {
+                l.add(child);
+            }
+        }
+        return l;
+    }
+
+
+
+}

+ 20 - 0
app/src/main/java/fanch/multijeu/findwords/widget/ComposeWordWidget.java

@@ -0,0 +1,20 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+public class ComposeWordWidget extends FrameLayout {
+
+    protected LinearLayout mLayout;
+    public ComposeWordWidget(@NonNull Context context) {
+        super(context);
+        mLayout=new LinearLayout(getContext());
+    }
+    public ComposeWordWidget(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+}

+ 35 - 0
app/src/main/java/fanch/multijeu/findwords/widget/CustomLinearLayout.java

@@ -0,0 +1,35 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+abstract class CustomLinearLayout extends LinearLayout {
+
+    protected ArrayList<View> mChildren = new ArrayList<View>();
+
+    public CustomLinearLayout(Context context) {
+        super(context);
+    }
+
+    public CustomLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+
+    protected void add(View v)
+    {
+        addView(v);
+        mChildren.add(v);
+    }
+
+    public void clear()
+    {
+        removeAllViews();
+        mChildren.clear();
+    }
+}

+ 8 - 0
app/src/main/java/fanch/multijeu/findwords/widget/ILettersSelect.java

@@ -0,0 +1,8 @@
+package fanch.multijeu.findwords.widget;
+
+public interface ILettersSelect {
+
+    public void setResultView(IResultView rv);
+
+
+}

+ 6 - 0
app/src/main/java/fanch/multijeu/findwords/widget/IResultView.java

@@ -0,0 +1,6 @@
+package fanch.multijeu.findwords.widget;
+
+public interface IResultView {
+    public void sendTry(String s);
+
+}

+ 97 - 0
app/src/main/java/fanch/multijeu/findwords/widget/LetterButton.java

@@ -0,0 +1,97 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.RotateAnimation;
+import android.view.animation.TranslateAnimation;
+
+import fanch.multijeu.R;
+
+public class LetterButton extends android.support.v7.widget.AppCompatButton {
+
+
+    protected char mLetter;
+    protected Animation mShake;
+
+    public LetterButton(Context context) {
+        super(context);
+        setLetterBackground();
+
+        setGravity(Gravity.CENTER);
+        setPadding(0,0,0,0);
+
+    }
+
+
+    public LetterButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setLetterBackground();
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.LetterButton, 0, 0);
+        try {
+            setLetter(a.getString(R.styleable.LetterButton_letter).charAt(0));
+        } finally {
+            a.recycle();
+        }
+
+    }
+
+
+    protected void setLetterBackground()
+    {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            setBackground(getResources().getDrawable(R.drawable.lettre));
+        }
+    }
+
+
+    public void setLetter(char l) {
+        this.mLetter = l;
+        super.setText(""+mLetter);
+    }
+
+    public char getLetter() {
+        return mLetter;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+
+        final int w = right - left;
+        final int h = bottom -top;
+        final int size = (w<h)?w:h;
+        setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) (((double)size)*0.75));
+        super.onLayout(changed, left, top, right, bottom);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+    }
+
+    public void shake() {
+        if(mShake==null || mShake.hasEnded()) {
+            mShake = AnimationUtils.loadAnimation(getContext(), R.anim.shake);
+
+            startAnimation(mShake);
+        }
+    }
+
+/*
+    @Override
+    protected void onMeasure(int ww, int hh) {
+        final int w = ww&MEASURED_SIZE_MASK;
+        final int h = hh&MEASURED_SIZE_MASK;
+        final int size = (w<h)?w:h;
+        setMeasuredDimension(size,size);
+    }*/
+
+
+
+}

+ 165 - 0
app/src/main/java/fanch/multijeu/findwords/widget/LetterView.java

@@ -0,0 +1,165 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.common.core.Word;
+
+public class LetterView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener {
+    public interface LetterViewDriver {
+        LetterButton getLetterAt(int x, int y);
+        void onPathCompleted(String w);
+    }
+
+    public static class Attrs extends Saveable {
+        public boolean animate=true;
+    }
+
+    protected LetterViewDriver mListener;
+    protected ArrayList<LetterButton> m_path = new ArrayList<LetterButton>();
+    protected int   m_endPathX;
+    protected int   m_endPathY;
+    protected Attrs m_attrs=new Attrs();
+
+    public LetterView(Context context) {
+        super(context);
+        setZOrderOnTop(true);
+        getHolder().setFormat(PixelFormat.TRANSPARENT);
+        getHolder().addCallback(this);
+        setOnTouchListener(this);
+    }
+
+    public LetterView(Context context, LetterViewDriver d) {
+        super(context);
+        setZOrderOnTop(true);
+        getHolder().setFormat(PixelFormat.TRANSPARENT);
+        getHolder().addCallback(this);
+        setOnTouchListener(this);
+        setListener(d);
+    }
+
+    public void setAttrs(Attrs a){ m_attrs=a;}
+    public Attrs getAttrs(){ return m_attrs;}
+
+
+
+    public void setListener(LetterViewDriver mListener) {
+        this.mListener = mListener;
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        reDraw();
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+
+    }
+
+    private void clearPath()
+    {
+        m_path.clear();
+    }
+
+    private String pathToString()
+    {
+        String s="";
+        for(int i=0; i<m_path.size(); i++)
+        {
+            s+=m_path.get(i).getLetter();
+        }
+        return s;
+    }
+
+    private void addToPath(LetterButton b)
+    {
+        for(int i=0; i<m_path.size(); i++)
+            if(m_path.get(i)==b)
+                return;
+        m_path.add(b);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent me) {
+
+        int x = (int)me.getX();
+        int y = (int)me.getY();
+        switch (me.getAction())
+        {
+            case MotionEvent.ACTION_UP: {
+                Word s = new Word(pathToString());
+                clearPath();
+                if(mListener !=null)
+                    mListener.onPathCompleted(s.toString());
+            }
+            break;
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE: {
+                    if(mListener!=null)
+                    {
+                        LetterButton btn =   mListener.getLetterAt(x,y);
+
+                        if(btn!=null) {
+                            if(m_attrs.animate) btn.shake();
+                            addToPath(btn);
+                        }
+                    }
+                    m_endPathX = x;
+                    m_endPathY = y;
+                }
+                break;
+
+        }
+        reDraw();
+        return true;
+    }
+
+
+
+    protected void reDraw()
+    {
+        Canvas c= getHolder().lockCanvas();
+        if(c==null) return;
+        c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
+
+        Paint p = new Paint();
+        p.setColor(Color.WHITE);
+        p.setStrokeWidth(10);
+        p.setAntiAlias(true);
+        if(m_path.size()>0) {
+
+            int l = m_path.size() - 1;
+            for (int i = 0; i < l; i++) {
+                LetterButton a = m_path.get(i);
+                LetterButton b = m_path.get(i + 1);
+
+                c.drawLine(a.getLeft() + a.getWidth() / 2,
+                        a.getTop() + a.getWidth() / 2, b.getLeft() + b.getWidth() / 2,
+                        b.getTop() + b.getWidth() / 2, p);
+            }
+            LetterButton last = m_path.get(l);
+
+            c.drawLine(last.getLeft() + last.getWidth() / 2,
+                    last.getTop() + last.getWidth() / 2,
+                        m_endPathX, m_endPathY, p);
+        }
+        getHolder().unlockCanvasAndPost(c);
+    }
+}

+ 105 - 0
app/src/main/java/fanch/multijeu/findwords/widget/LetterWidget.java

@@ -0,0 +1,105 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.Word;
+
+public class LetterWidget extends FrameLayout implements LetterView.LetterViewDriver {
+
+    public interface LetterWidgetListener{
+        void onTryWord(Word s);
+        void onReady();
+    }
+
+    protected CircularLayout mLettersLayout;
+    protected LetterView mOverlay;
+    protected LetterWidgetListener mListener;
+    protected boolean mLayoutIsReady=false;
+
+
+    public LetterWidget(Context context) {
+        super(context);
+        mLettersLayout = new CircularLayout(context);
+        mLettersLayout.setLayoutParams(new FrameLayout.LayoutParams(-1,-1));
+        addView(mLettersLayout);
+        mOverlay = new LetterView(context, this);
+        addView(mOverlay);
+        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                onLayoutReady();
+            }
+        });
+    }
+
+
+    public LetterWidget(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mLettersLayout = new CircularLayout(context);
+        mLettersLayout.setLayoutParams(new FrameLayout.LayoutParams(-1,-1));
+        addView(mLettersLayout);
+        mOverlay = new LetterView(context, this);
+        addView(mOverlay);
+        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                if(mLayoutIsReady)return;
+                onLayoutReady();
+                mLayoutIsReady=true;
+            }
+        });
+    }
+
+    private void onLayoutReady()
+    {
+        if(mListener!=null)
+            mListener.onReady();
+    }
+
+    private void addLetter(char c)
+    {
+        LetterButton btn = new LetterButton(getContext());
+        btn.setLetter(c);
+        mLettersLayout.addView(btn);
+    }
+
+    public void setLetters(String s)
+    {
+        int w=getHeight();
+        int h=getWidth();
+        int minwh=(w>h)?h:w;
+        int size=minwh/6;
+        if(s.length()>8) size=minwh/8;
+        if(s.length()>12) size=minwh/10;
+        mLettersLayout.removeAllViews();
+        for(int i=0; i<s.length(); i++)
+            addLetter(s.charAt(i));
+        mLettersLayout.setFixedSize(size);
+
+    }
+
+    public void setListener(LetterWidgetListener mListener) {
+        this.mListener = mListener;
+    }
+
+    @Override
+    public LetterButton getLetterAt(int x, int y) {
+        ArrayList<View> v =  mLettersLayout.getChildrenAt(x,y);
+        for(int i=0; i<v.size(); i++)
+            if(v.get(i) instanceof LetterButton)
+                return (LetterButton) v.get(i);
+        return null;
+    }
+
+    @Override
+    public void onPathCompleted(String w) {
+        if(mListener!=null)
+            mListener.onTryWord(new Word(w));
+    }
+}

+ 82 - 0
app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderButton.java

@@ -0,0 +1,82 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import fanch.multijeu.R;
+
+public class PlaceHolderButton extends LetterButton {
+
+
+    public static final int STATE_HIDDEN=0;
+    public static final int STATE_TIP=1;
+    public static final int STATE_SHOWN=2;
+
+
+    protected int mState=STATE_HIDDEN;
+
+    public PlaceHolderButton(Context context) {
+        super(context);
+        setPlaceHolderBackground();
+        setState(mState);
+    }
+
+    public PlaceHolderButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setPlaceHolderBackground();
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.LetterButton, 0, 0);
+        setState(mState);
+        try {
+            mState = a.getInteger(R.styleable.LetterButton_state, 0);
+        } finally {
+            a.recycle();
+        }
+
+    }
+
+    public int getState() {
+        return mState;
+    }
+
+    public boolean isDiscover()
+    {
+        return mState==STATE_TIP || mState==STATE_SHOWN;
+    }
+
+    public void setState(int mState) {
+        this.mState = mState;
+        int alpha=0;
+        switch(mState)
+        {
+            case STATE_HIDDEN:
+                alpha=0;
+                setPlaceHolderBackground();
+                break;
+            case STATE_TIP:
+                setPlaceHolderBackground();
+                alpha=60;
+                break;
+            case STATE_SHOWN:
+            default:
+                alpha=255;
+                setLetterBackground();
+
+
+        }
+        setTextColor(Color.argb(alpha,0,0,0));
+    }
+
+
+    protected void setPlaceHolderBackground()
+    {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            setBackground(getResources().getDrawable(R.drawable.placeholder));
+        }
+    }
+
+
+
+}

+ 215 - 0
app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderLayout.java

@@ -0,0 +1,215 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import fanch.multijeu.R;
+import fanch.multijeu.common.exceptions.BadParametterException;
+
+public class PlaceHolderLayout extends ViewGroup implements View.OnClickListener {
+
+    protected String    mText = "";
+    protected boolean   mIsShown = false;
+    protected int       mTipsCount = 0;
+    protected int       mMaxSize = -1;
+
+    public PlaceHolderLayout(Context context) {
+        super(context);
+        setOnClickListener(this);
+    }
+
+    public PlaceHolderLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        setOnClickListener(this);
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.LetterButton, 0, 0);
+
+        try {
+            setText(a.getString(R.styleable.PlaceHolderButton_text));
+        } finally {
+            a.recycle();
+        }
+    }
+
+    /*protected PlaceHolderButton newButton(char c)
+    {
+        PlaceHolderButton button = new PlaceHolderButton(getContext());
+        button.setLetter(c);
+        //button.setSize(50);
+        button.setOnClickListener(this);
+        return button;
+    }
+*/
+    protected PlaceHolderButton newButton(char c)
+    {
+        PlaceHolderButton button = new PlaceHolderButton(getContext());
+        button.setLetter(c);
+        //button.setSize(50);
+        button.setOnClickListener(this);
+        return button;
+    }
+    @Override
+    protected void onMeasure(int ww, int hh) {
+
+        final int W = MeasureSpec.getSize(ww);
+        final int WM = MeasureSpec.getMode(ww);
+
+        final int H = MeasureSpec.getSize(hh);
+        final int HM = MeasureSpec.getMode(hh);
+
+        final int count = getChildCount();
+        int wSpace=255555;
+        int hSpace=255555;
+
+
+
+        for(int i=0; i<count; i++)
+        {
+            final View child = getChildAt(i);
+            child.measure(MeasureSpec.makeMeasureSpec(W, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(H, MeasureSpec.EXACTLY));
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final int h = child.getMeasuredHeight();
+            final int w = child.getMeasuredWidth();
+            if(h<hSpace) hSpace=h;
+            if(w<wSpace) wSpace=w;
+        }
+
+        if(hSpace>H)hSpace=H;
+        if(wSpace*count>W) wSpace=W/count;
+
+        //element need to be squares
+        if(wSpace<hSpace) hSpace=wSpace;
+        else wSpace=hSpace;
+
+        Log.e("/////////", W+","+ hSpace);
+        setMeasuredDimension(W, hSpace);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int W = r-l;
+        final int H = b-t;
+        final int count = getChildCount();
+        int wSpace=255555;
+        int hSpace=255555;
+        int left = l;
+        int pad;
+
+
+
+        for(int i=0; i<count; i++)
+        {
+            final View child = getChildAt(i);
+            child.measure(MeasureSpec.makeMeasureSpec(H, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(H, MeasureSpec.EXACTLY));
+
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final int h = child.getMeasuredHeight();
+            final int w = child.getMeasuredWidth();
+            if(h<hSpace) hSpace=h;
+            if(w<wSpace) wSpace=w;
+        }
+
+        if(hSpace>H)hSpace=H;
+        if(wSpace*count>W) wSpace=W/count;
+
+        //element need to be squares
+        if(wSpace<hSpace) hSpace=wSpace;
+        else wSpace=hSpace;
+
+
+        left+=(W-(wSpace*count))/2;
+        for(int i=0; i<count; i++)
+        {
+            getChildAt(i).layout(left, 0,left+wSpace, wSpace );
+            /*Log.e("/////////", "\t"+left+" "+t+" "+(left+wSpace)+" "+b
+                                    +"  ("+wSpace+","+(b-t)+")" );*/
+            left+=wSpace;
+        }
+    }
+
+
+    public void onClick(View v)
+    {
+        requestLayout();
+    }
+
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+    }
+
+    @Override
+    public void addView(View v)
+    {
+        if( !(v instanceof PlaceHolderButton) )
+            throw new BadParametterException("Expect PlaceHolderButton here");
+        super.addView(v);
+    }
+
+    protected int getButtonHeight()
+    {
+        if(getChildCount()==0) return 0;
+        int min=25555;
+        for(int i=0; i<getChildCount(); i++)
+            if(getChildAt(i).getHeight()<min)
+                min=getChildAt(i).getHeight();
+        return min;
+    }
+
+
+    public void setText(String s)
+    {
+        if(s==null) s="";
+        mText=s.toUpperCase();
+        removeAllViews();
+        for(int i=0; i<mText.length(); i++) {
+            addView(newButton(mText.charAt(i)));
+        }
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public boolean isIsShown() {
+        return mIsShown;
+    }
+
+    public int getTipsCount() {
+        return mTipsCount;
+    }
+
+    public void show()
+    {
+        for(int i=0; i<getChildCount(); i++)
+            ((PlaceHolderButton)getChildAt(i)).setState(PlaceHolderButton.STATE_SHOWN);
+        mIsShown=true;
+        invalidate();
+        requestLayout();
+    }
+
+    public void nextTip()
+    {
+        if(!hasTip()) return;
+        ((PlaceHolderButton)getChildAt(mTipsCount)).setState(PlaceHolderButton.STATE_TIP);
+        mTipsCount++;
+        invalidate();
+        requestLayout();
+    }
+
+    public boolean hasTip()
+    {
+        for(int i=0; i<getChildCount()-1; i++)
+            if(!((PlaceHolderButton)getChildAt(i)).isDiscover())
+                return true;
+        return false;
+    }
+}

+ 211 - 0
app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderLayout2.java

@@ -0,0 +1,211 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import fanch.multijeu.R;
+
+public class PlaceHolderLayout2 extends CustomLinearLayout implements View.OnClickListener {
+
+    protected String    mText = "";
+    protected boolean   mIsShown = false;
+    protected int       mTipsCount = 0;
+    protected int       mMaxSize = -1;
+
+    public PlaceHolderLayout2(Context context) {
+        super(context);
+        setOrientation(HORIZONTAL);
+        setOnClickListener(this);
+    }
+
+    public PlaceHolderLayout2(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        setOrientation(HORIZONTAL);
+        setOnClickListener(this);
+        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.LetterButton, 0, 0);
+
+        try {
+            setText(a.getString(R.styleable.PlaceHolderButton_text));
+        } finally {
+            a.recycle();
+        }
+    }
+
+    /*protected PlaceHolderButton newButton(char c)
+    {
+        PlaceHolderButton button = new PlaceHolderButton(getContext());
+        button.setLetter(c);
+        //button.setSize(50);
+        button.setOnClickListener(this);
+        return button;
+    }
+*/
+    protected PlaceHolderButton newButton(char c)
+    {
+        PlaceHolderButton button = new PlaceHolderButton(getContext());
+        button.setLetter(c);
+        //button.setSize(50);
+        button.setOnClickListener(this);
+        return button;
+    }
+    @Override
+    protected void onMeasure(int ww, int hh) {
+
+        final int W = MeasureSpec.getSize(ww);
+        final int WM = MeasureSpec.getMode(ww);
+
+        final int H = MeasureSpec.getSize(hh);
+        final int HM = MeasureSpec.getMode(hh);
+
+        final int count = mChildren.size();
+        int wSpace=255555;
+        int hSpace=255555;
+
+
+
+        for(int i=0; i<count; i++)
+        {
+            final View child = children(i);
+            child.measure(MeasureSpec.makeMeasureSpec(W, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(H, MeasureSpec.EXACTLY));
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final int h = child.getMeasuredHeight();
+            final int w = child.getMeasuredWidth();
+            if(h<hSpace) hSpace=h;
+            if(w<wSpace) wSpace=w;
+        }
+
+        if(hSpace>H)hSpace=H;
+        if(wSpace*count>W) wSpace=W/count;
+
+        //element need to be squares
+        if(wSpace<hSpace) hSpace=wSpace;
+        else wSpace=hSpace;
+
+        setMeasuredDimension(W, hSpace);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int W = r-l;
+        final int H = b-t;
+        final int count = mChildren.size();
+        int wSpace=255555;
+        int hSpace=255555;
+        int left = l;
+        int pad;
+
+        Log.e("/////////", l+" "+t+" "+(r-l)+" "+(b-t));
+
+
+        for(int i=0; i<count; i++)
+        {
+            final View child = children(i);
+            //child.measure(MeasureSpec.makeMeasureSpec(H, MeasureSpec.EXACTLY),
+            //        MeasureSpec.makeMeasureSpec(H, MeasureSpec.EXACTLY));
+
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final int h = child.getMeasuredHeight();
+            final int w = child.getMeasuredWidth();
+            if(h<hSpace) hSpace=h;
+            if(w<wSpace) wSpace=w;
+        }
+
+        if(hSpace>H)hSpace=H;
+        if(wSpace*count>W) wSpace=W/count;
+
+        //element need to be squares
+        if(wSpace<hSpace) hSpace=wSpace;
+        else wSpace=hSpace;
+
+
+        left+=(W-(wSpace*count))/2;
+        for(int i=0; i<count; i++)
+        {
+            children(i).layout(left,0,left+wSpace, hSpace );
+            left+=wSpace;
+        }
+    }
+
+
+    private PlaceHolderButton children(int i)
+    {
+        return (PlaceHolderButton)mChildren.get(i);
+    }
+
+    public void onClick(View v)
+    {
+        requestLayout();
+    }
+
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+    }
+
+    protected int getButtonHeight()
+    {
+        if(mChildren.size()==0) return 0;
+        int min=25555;
+        for(int i=0; i<mChildren.size(); i++)
+            if(children(i).getHeight()<min)
+                min=children(i).getHeight();
+        return min;
+    }
+
+
+    public void setText(String s)
+    {
+        if(s==null) s="";
+        mText=s.toUpperCase();
+        clear();
+        for(int i=0; i<mText.length(); i++) {
+            add(newButton(mText.charAt(i)));
+        }
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public boolean isIsShown() {
+        return mIsShown;
+    }
+
+    public int getTipsCount() {
+        return mTipsCount;
+    }
+
+    public void show()
+    {
+        for(int i=0; i<mChildren.size(); i++)
+            children(i).setState(PlaceHolderButton.STATE_SHOWN);
+        mIsShown=true;
+        invalidate();
+        requestLayout();
+    }
+
+    public void nextTip()
+    {
+        if(!hasTip()) return;
+        children(mTipsCount).setState(PlaceHolderButton.STATE_TIP);
+        mTipsCount++;
+        invalidate();
+        requestLayout();
+    }
+
+    public boolean hasTip()
+    {
+        for(int i=0; i<mChildren.size()-1; i++)
+            if(!children(i).isDiscover())
+                return true;
+        return false;
+    }
+}
+

+ 168 - 0
app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderListLayout.java

@@ -0,0 +1,168 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import fanch.multijeu.common.utils.StringUtils;
+
+public class PlaceHolderListLayout extends CustomLinearLayout implements View.OnClickListener {
+
+    protected int mMinButtonHeight =0x7fffffff;
+
+    public PlaceHolderListLayout(Context context) {
+        super(context);
+        setOrientation(VERTICAL);
+        LayoutParams ll = new LayoutParams(-1,-1,1);
+        ll.setMargins(0,0,0,0);
+        setPadding(0,0,0,0);
+        setLayoutParams(ll);
+
+    }
+
+    public PlaceHolderListLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        setOrientation(VERTICAL);
+        LayoutParams ll = new LayoutParams(-1,-2,1);
+        ll.setMargins(0,0,0,0);
+        setPadding(0,0,0,0);
+        setLayoutParams(ll);
+
+    }
+
+
+    private PlaceHolderLayout children(int i)
+    {
+        return (PlaceHolderLayout)mChildren.get(i);
+    }
+
+    protected PlaceHolderLayout newLayout(String s)
+    {
+        PlaceHolderLayout pl = new PlaceHolderLayout(getContext());
+        pl.setText(s);
+        pl.setOnClickListener(this);
+        return pl;
+    }
+
+    public void addWord(String w)
+    {
+        add(newLayout(w));
+        requestLayout();
+    }
+
+
+    @Override
+    protected void onMeasure(int ww, int hh) {
+        final int W = ww&MEASURED_SIZE_MASK;
+        final int H = hh&MEASURED_SIZE_MASK;
+        final int count = mChildren.size();
+
+
+        for(int i=0; i<count; i++) {
+            final View child = children(i);
+            child.measure(MeasureSpec.makeMeasureSpec(W, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(H/count, MeasureSpec.AT_MOST));
+            final int x = children(i).getMeasuredHeight();
+            if(x< mMinButtonHeight) mMinButtonHeight =x;
+        }
+        setMeasuredDimension(ww&MEASURED_SIZE_MASK, hh&MEASURED_SIZE_MASK);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        if(getHeight()==0) return;
+        final int W = r-l;
+        final int H = b-t;
+        final int count = mChildren.size();
+
+
+        int size=250000;
+        for(int i=0; i<count; i++) {
+            final View child = children(i);
+            child.measure(MeasureSpec.makeMeasureSpec(W, MeasureSpec.AT_MOST),
+                     MeasureSpec.makeMeasureSpec(H/count, MeasureSpec.AT_MOST));
+            final int x = children(i).getMeasuredHeight();
+            if(x<size) size=x;
+        }
+
+        if(size*count>H)
+            size=H/count;
+
+        if(size> mMinButtonHeight)
+            size= mMinButtonHeight;
+
+        int restant =H-count*size;
+
+        int marge=0;
+        if(restant<=0)
+        {
+            size= (int) (W/(count*1.5));
+            marge=size/6;
+        }else
+        {
+            marge = (restant)/(count+1);
+        }
+
+        int y = t+marge;
+        for (int i = 0; i < count; i++) {
+            final View child = children(i);
+
+            child.layout(0, y, W, y+size);
+            y+=size+marge;
+        }
+    }
+
+
+    public void nextTip()
+    {
+        for(int i=0; i<mChildren.size(); i++)
+            if(children(i).hasTip())
+            {
+                children(i).nextTip();
+                return;
+            }
+    }
+
+    public boolean hasNextTip()
+    {
+        for(int i=0; i<mChildren.size(); i++)
+            if(children(i).hasTip()) return true;
+        return false;
+    }
+
+    @Override
+    public void onClick(View view) {
+
+    }
+
+    public void discoverWord(String s, boolean accentSensitive)
+    {
+        for(int i=0; i<mChildren.size(); i++)
+        {
+            if(accentSensitive)
+            {
+                if(children(i).getText().toUpperCase().equals(s.toUpperCase()))
+                    children(i).show();
+            }else
+            {
+                String a = StringUtils.toAscii(children(i).getText().toUpperCase());
+                String b = StringUtils.toAscii(s.toUpperCase());
+                if(a.equals(b))
+                    children(i).show();
+            }
+        }
+    }
+
+    public void setMinButtonHeight(int x)
+    {
+        mMinButtonHeight =x;
+    }
+
+
+    public int getMinButtonHeight()
+    {
+        return mMinButtonHeight;
+    }
+
+}

+ 118 - 0
app/src/main/java/fanch/multijeu/findwords/widget/PlaceHolderRootLayout.java

@@ -0,0 +1,118 @@
+package fanch.multijeu.findwords.widget;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+public class PlaceHolderRootLayout extends CustomLinearLayout {
+    PlaceHolderListLayout mLeft;
+    PlaceHolderListLayout mRight;
+    protected boolean  mIsUpdating=false;
+
+    public PlaceHolderRootLayout(Context context) {
+        super(context);
+        setOrientation(HORIZONTAL);
+        //setLayoutParams(new LayoutParams(-1,-1));
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(-1,-1);
+        lp.gravity = Gravity.CENTER;
+        setLayoutParams(lp);
+        mLeft = new PlaceHolderListLayout(getContext());
+        mRight= new PlaceHolderListLayout(getContext());
+
+
+
+
+        addView(mLeft);
+        addView(mRight);
+    }
+
+    public void setWords(ArrayList<String> strs)
+    {
+        String[] s = new String[strs.size()];
+        for(int i=0; i<s.length; i++)
+            s[i]=strs.get(i);
+        setWords(s);
+    }
+    public void setWords(String[] strs)
+    {
+        mLeft.clear();
+        mRight.clear();
+        int n = strs.length;
+        if(n<5)
+        {
+            mRight.setVisibility(View.GONE);
+            for(int i=0; i<n; i++) mLeft.addWord(strs[i]);
+        }
+        else
+        {
+            mRight.setVisibility(View.VISIBLE);
+            int first = n-(n/2);
+            for(int i=0; i<first; i++) mLeft.addWord(strs[i]);
+            for(int i=first; i<n; i++) mRight.addWord(strs[i]);
+        }
+    }
+
+
+
+
+
+    public PlaceHolderRootLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        /*mLeft = new PlaceHolderListLayout(getContext());
+        mRight= new PlaceHolderListLayout(getContext());
+        addView(mLeft);
+        addView(mRight);*/
+    }
+
+
+    @Override
+    protected void onLayout(boolean changed, int ll, int tt, int rr, int b) {
+
+        mLeft.measure(rr-ll, b-tt);
+        mRight.measure(rr-ll, b-tt);
+        final int l = mLeft.getMinButtonHeight();
+        final int r = mRight.getMinButtonHeight();
+
+        final int W = rr-ll;
+        int marge = W/40;
+        int size = (l>r)?r:l;
+        mLeft.setMinButtonHeight(size);
+        mRight.setMinButtonHeight(size);
+
+        if(mRight.getVisibility()==View.VISIBLE) {
+            mLeft.layout(marge, 0,
+                    W / 2 - marge, b - tt);
+
+            mRight.layout(W / 2 + marge, 0,
+                    W - marge, b - tt);
+        }else
+        {
+            mLeft.layout(marge*2, 0,
+                    W  - marge*2, b - tt);
+        }
+        //super.onLayout(changed, ll, tt, rr, b);
+    }
+
+    public boolean hasNextTip()
+    {
+        return mLeft.hasNextTip() || mRight.hasNextTip();
+    }
+
+    public void nextTip()
+    {
+        if(mLeft.hasNextTip()) mLeft.nextTip();
+        else if(mRight.hasNextTip()) mRight.nextTip();
+    }
+
+
+    public void discoverWord(String s, boolean accentSensitive)
+    {
+        mLeft.discoverWord(s, accentSensitive);
+        mRight.discoverWord(s, accentSensitive);
+    }
+}

+ 38 - 0
app/src/main/java/fanch/multijeu/numbers/activity/MenuNumberActivity.java

@@ -0,0 +1,38 @@
+package fanch.multijeu.numbers.activity;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+
+import fanch.multijeu.R;
+import fanch.multijeu.numbers.data.ExpressionSetGenerator;
+
+public class MenuNumberActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_menu_number);
+    }
+
+    public void onVE(View v)
+    {
+        NumberActivity.start(this, ExpressionSetGenerator.VERY_EASY);
+    }
+    public void onE(View v)
+    {
+        NumberActivity.start(this, ExpressionSetGenerator.EASY);
+    }
+    public void onM(View v)
+    {
+        NumberActivity.start(this, ExpressionSetGenerator.MEDIUM);
+    }
+    public void onH(View v)
+    {
+        NumberActivity.start(this, ExpressionSetGenerator.HARD);
+    }
+    public void onVH(View v)
+    {
+        NumberActivity.start(this, ExpressionSetGenerator.VERY_HARD);
+    }
+}

+ 307 - 0
app/src/main/java/fanch/multijeu/numbers/activity/NumberActivity.java

@@ -0,0 +1,307 @@
+package fanch.multijeu.numbers.activity;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.io.Serializable;
+
+import fanch.multijeu.R;
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.common.core.SerializedObject;
+import fanch.multijeu.findwords.fragments.SimpleWordViewFragment;
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionSet;
+import fanch.multijeu.numbers.data.ExpressionSetGenerator;
+import fanch.multijeu.numbers.fragment.NumberFragment;
+
+import static android.view.View.GONE;
+import static fanch.multijeu.numbers.data.ExpressionSetGenerator.EASY;
+
+public class NumberActivity extends AppCompatActivity implements NumberFragment.NumberFragmentDriver {
+
+    public static final int RESULT_CODE = 0x10203040;
+
+    protected NumberFragment mFragment;
+
+
+    protected Button mPass;
+    protected TextView mPoints;
+    protected TextView mPointsLabel;
+    protected TextView mScore;
+    protected TextView mScoreLabel;
+
+    public enum GameMode {
+        FREE,
+        SCORE,
+        ONE_SHOT
+    }
+
+    public static class Result extends Saveable {
+        public int           best;
+        public boolean       won;
+        public Attrs         attrs;
+        public int           result;
+    }
+
+    public static class Attrs extends NumberFragment.Attrs{
+        public int difficulty=EASY;
+        public boolean isCorrectable=true;
+        public GameMode mode=GameMode.SCORE;
+    }
+
+    protected static class Model{
+        ExpressionSetGenerator esg;
+        int score;
+        int point=10;
+        int best;
+        public Attrs attrs=new Attrs();
+    }
+    protected Model m = new Model();
+
+
+    public static void start(Context c, Attrs a)
+    {
+        Intent in = new Intent(c, NumberActivity.class);
+        in.putExtra("attrs", (Serializable) a.saveToSerializedObject());
+        c.startActivity(in);
+    }
+
+    public static void start(Context c, int d)
+    {
+        Attrs a = new Attrs();
+        a.difficulty=d;
+        a.isCorrectable=true;
+        a.time=-1;
+        a.mode=GameMode.FREE;
+        a.style= NumberFragment.PadStyle.NON_ASSISTED;
+        start(c, a);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_number);
+
+        mPass=findViewById(R.id.btn_pass);
+        mPoints=findViewById(R.id.tv_points);
+        mPointsLabel=findViewById(R.id.tv_points_label);
+        mScore=findViewById(R.id.tv_score);
+        mScoreLabel=findViewById(R.id.tv_score_label);
+
+        m.attrs = ((SerializedObject<Attrs>)getIntent().getSerializableExtra("attrs")).newObject();
+        if(m.attrs==null) m.attrs=new Attrs();
+        initMode();
+
+        m.esg = new ExpressionSetGenerator(m.attrs.difficulty);
+        NumberFragment.Attrs attr =
+                ((SerializedObject<NumberFragment.Attrs>)getIntent().
+                        getSerializableExtra("attrs")).newObject();
+        if (savedInstanceState == null) {
+            mFragment= NumberFragment.newInstance(attr);
+            getSupportFragmentManager().beginTransaction().replace(R.id.fragmentNumber, mFragment).commitNow();
+            mFragment.setListener(this);
+        }
+    }
+
+    protected void initMode()
+    {
+        switch(m.attrs.mode)
+        {
+            case FREE:
+                mPass.setVisibility(View.VISIBLE);
+                mScore.setVisibility(View.VISIBLE);
+                mScoreLabel.setVisibility(View.VISIBLE);
+                mPoints.setVisibility(GONE);
+                mPointsLabel.setVisibility(GONE);
+                break;
+
+            case SCORE:
+                mPass.setVisibility(View.VISIBLE);
+                mScore.setVisibility(View.VISIBLE);
+                mScoreLabel.setVisibility(View.VISIBLE);
+                mPoints.setVisibility(View.VISIBLE);
+                mPointsLabel.setVisibility(View.VISIBLE);
+                break;
+
+            case ONE_SHOT:
+            default:
+                mPass.setVisibility(GONE);
+                mScore.setVisibility(GONE);
+                mScoreLabel.setVisibility(GONE);
+                mPoints.setVisibility(GONE);
+                mPointsLabel.setVisibility(GONE);
+                break;
+        }
+    }
+
+
+
+    public void setMode(GameMode mm)
+    {
+        m.attrs.mode=mm;
+        initMode();
+    }
+
+    public void setPadStyle(NumberFragment.PadStyle p)
+    {
+        m.attrs.style=p;
+    }
+
+
+
+
+    public void finish(boolean status, Expression e)
+    {
+        int x =0;
+        if(e==null) x = m.esg.peek().resultInt;
+        else x=e.compute();
+        finish(status, x);
+    }
+
+    public void finish(boolean status, int e)
+    {
+        Intent n = new Intent();
+        Result r = new Result();
+        r.result=m.esg.peek().resultInt;
+        r.best=e;
+        r.won=status;
+        r.attrs=m.attrs;
+        n.putExtra("result", r);
+        setResult(RESULT_CODE, n);
+        finish();
+    }
+
+    @Override
+    public void onLoose(ExpressionSet s, Expression e) {
+        if(m.attrs.isCorrectable) mFragment.correct();
+        if(m.attrs.mode==GameMode.SCORE)
+        {
+            m.score-=2;
+            int b=m.esg.peek().resultInt;
+            if(e!=null){
+                b=m.esg.peek().resultInt-e.compute();
+            }
+            if(b<0) b=-b;
+            m.point-=b;
+            if(!m.attrs.isCorrectable && m.point<0)
+                finish(false, e);
+        }else if(m.attrs.mode==GameMode.ONE_SHOT)
+        {
+            if(!m.attrs.isCorrectable)
+                finish(false, e);
+        }else if(m.attrs.mode==GameMode.FREE)
+        {
+            m.score-=2;
+        }
+        mPass.setVisibility(GONE);
+
+    }
+
+    @Override
+    public void onWin(ExpressionSet s, Expression e) {
+        if(m.attrs.mode==GameMode.ONE_SHOT)
+        {
+            finish(true, e);
+        }else{
+            m.score++;
+            m.point++;
+            newGame();
+        }
+    }
+
+    @Override
+    public void onCorrectFinish() {
+        if( (m.attrs.mode==GameMode.SCORE && m.point<0)
+                || m.attrs.mode==GameMode.ONE_SHOT)
+        {
+            finish(false, m.best);
+        }
+        else
+            newGame();
+    }
+
+    @Override
+    public void onAnimationFinish() {
+        updateUi(true);
+    }
+
+    @Override
+    public void onFragmentInit() {
+        newGame();
+    }
+
+    @Override
+    public void onBestChanged(int value) {
+        m.best=value;
+        updateUi(true);
+    }
+
+    public void updateUi(boolean showPass)
+    {
+        mPoints.setText(""+m.point);
+        mScore.setText(""+m.score);
+    }
+
+    protected void newGame()
+    {
+        ExpressionSet es = m.esg.generate();
+        m.best=es.resultInt;
+        mFragment.newGame(es);
+        initMode();
+        updateUi(false);
+    }
+
+    public void onNew(View view) {
+        final Context c = this;
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle("Êtes-vous sur ?");
+
+        if(m.attrs.mode==GameMode.SCORE) {
+            int b = m.esg.peek().resultInt-m.best;
+            if(b<0) b=-b;
+            if(m.point-b<0)
+                builder.setMessage("Si vous passez, vous perdrez ! voulez vous continuer ?");
+            else
+                builder.setMessage("Si vous passez, vous perdrez "+b+" points, voulez vous continuer");
+        }
+        else builder.setMessage("Si vous passez partie, elle sera perdue ?");
+        builder.setPositiveButton("Oui", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int id) {
+                AlertDialog.Builder builder2 = new AlertDialog.Builder(c);
+                builder2.setTitle("Voir la réponse ?");
+                builder2.setMessage("Voulez vous voir la réponse ?");
+                builder2.setPositiveButton("Oui", new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+
+                        if(m.attrs.isCorrectable)
+                            mFragment.correct();
+                        else
+                            onCorrectFinish();
+                    }
+                });
+                builder2.setNegativeButton("Non", new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        newGame();
+                    }
+                });
+                AlertDialog dialog2 = builder2.create();
+                dialog2.show();
+            }
+        });
+        builder.setNegativeButton("Non", new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int id) {
+                // User cancelled the dialog
+            }
+        });
+
+        AlertDialog dialog = builder.create();
+        dialog.show();
+    }
+}

+ 93 - 0
app/src/main/java/fanch/multijeu/numbers/activity/SelectNumbersActivity.java

@@ -0,0 +1,93 @@
+package fanch.multijeu.numbers.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+
+import fanch.multijeu.R;
+import fanch.multijeu.common.view.ComboBox;
+import fanch.multijeu.numbers.fragment.NumberFragment;
+
+public class SelectNumbersActivity extends AppCompatActivity {
+    protected ComboBox mDiff;
+    protected String[] mDiffList={ "Très Facile", "Facile", "Moyen", "Difficile",
+            "Très difficile"};
+    protected int[] mDiffValues={0,1,2,3,4,5};
+    protected int mDiffDefault = 0;
+
+    protected ComboBox mTemps;
+    protected String[] mTempsList={ "Pas de limite", "15\"", "30\""
+            , "45\"", "1'", "1' 30\"", "2'", "3'", "4'", "5'", "10'"};
+    protected int[] mTempsValues={-1, 15, 30, 45, 60 ,90 , 120, 180, 240, 300, 600};
+    protected int mTempsDefault = 3;
+
+    protected ComboBox mInterface;
+    protected String[] mInterfaceList={ "Assistée", "Non assistée"};
+    protected NumberFragment.PadStyle[] mInterfaceValues={
+            NumberFragment.PadStyle.ASSISTED,
+            NumberFragment.PadStyle.NON_ASSISTED};
+    protected int mInterfaceDefault = 0;
+
+    protected ComboBox mMode;
+    protected String[] mModeList={ "One Shot", "Arcade", "Libre"};
+    protected NumberActivity.GameMode[] mModeValues={
+            NumberActivity.GameMode.ONE_SHOT,
+            NumberActivity.GameMode.SCORE,
+            NumberActivity.GameMode.FREE
+    };
+    protected int mModeDefault = 1;
+
+    protected ComboBox mCorrection;
+    protected String[] mCorrectionList={ "Oui", "Non"};
+    protected boolean[] mCorrectionValues={true, false};
+    protected int mCorrectionDefault = 0;
+
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_select_numbers);
+        mDiff=findViewById(R.id.cb_diff);
+        mDiff.setText("Difficulté");
+        mDiff.setData(mDiffList,mDiffDefault);
+
+        mTemps=findViewById(R.id.cb_temps);
+        mTemps.setText("Temps");
+        mTemps.setData(mTempsList,mTempsDefault);
+
+
+        mInterface=findViewById(R.id.cb_interface);
+        mInterface.setText("Interface");
+        mInterface.setData(mInterfaceList,mInterfaceDefault);
+
+
+        mMode=findViewById(R.id.cb_mode);
+        mMode.setText("Mode");
+        mMode.setData(mModeList, mModeDefault);
+
+        mCorrection=findViewById(R.id.cb_correction);
+        mCorrection.setText("Correction");
+        mCorrection.setData(mCorrectionList, mCorrectionDefault);
+
+    }
+
+    public static void start(Context c)
+    {
+        Intent in = new Intent(c, SelectNumbersActivity.class);
+        c.startActivity(in);
+    }
+
+    public void onStart(View view) {
+        NumberActivity.Attrs attrs = new NumberActivity.Attrs();
+        attrs.mode=mModeValues[mMode.getSelected()];
+        attrs.style=mInterfaceValues[mInterface.getSelected()];
+        attrs.time=mTempsValues[mTemps.getSelected()];
+        attrs.difficulty=mDiffValues[mDiff.getSelected()];
+        attrs.isCorrectable=mCorrectionValues[mCorrection.getSelected()];
+
+        NumberActivity.start(this, attrs);
+    }
+}

+ 13 - 0
app/src/main/java/fanch/multijeu/numbers/data/Expression.java

@@ -0,0 +1,13 @@
+package fanch.multijeu.numbers.data;
+
+public abstract class Expression {
+    public Expression()
+    {
+
+    }
+    public abstract String toString();
+    public abstract int compute();
+    public abstract Expression simplify();
+    public abstract Expression clone();
+    public abstract int opCount();
+}

+ 122 - 0
app/src/main/java/fanch/multijeu/numbers/data/ExpressionLexer.java

@@ -0,0 +1,122 @@
+package fanch.multijeu.numbers.data;
+
+import android.content.Intent;
+
+import java.text.ParseException;
+
+public class ExpressionLexer {
+
+    public static final char EOF = 0xff;
+    protected static final String ALLOWED = "0123456789+-/%*()";
+    public enum Lexem {
+        P_OPEN,
+        P_CLOSE,
+        PLUS,
+        MINUS,
+        MULT,
+        NUMBER,
+        ERROR,
+        EOF
+    }
+
+    protected String mData="";
+    protected int    mIndex;
+    protected Lexem  mLexem;
+    protected String mString;
+    protected char   mCurrent;
+
+
+    public ExpressionLexer(String str)
+    {
+        mData=str;
+        mIndex=-1;
+    }
+
+    public Lexem next()
+    {
+        mString="";
+        while(!isValid() && !eof()) nextChar();
+
+        if(eof()) return Lexem.EOF;
+        if( (mCurrent>='0' && mCurrent<='9'))
+        {
+            /*if(mCurrent=='-')
+            {
+                mString+=mCurrent;
+                nextChar();
+            }*/
+            while((mCurrent>='0' && mCurrent<='9') && !eof())
+            {
+                mString+=mCurrent;
+                nextChar();
+            }
+            return mLexem=Lexem.NUMBER;
+        }
+
+        switch(mCurrent)
+        {
+            case '(': return doOp(Lexem.P_OPEN,'(');
+            case ')': return doOp(Lexem.P_CLOSE,')');
+            case '+': return doOp(Lexem.PLUS,'+');
+            case '-': return doOp(Lexem.MINUS,'-');
+            case '*': return doOp(Lexem.MULT,'*');
+        }
+
+        return doOp(Lexem.ERROR, "");
+    }
+
+
+    private Lexem doOp(Lexem l, char op)
+    {
+        return doOp(l, ""+op);
+    }
+
+    private Lexem doOp(Lexem l, String op)
+    {
+        mLexem=l;
+        mString=op;
+        nextChar();
+        return mLexem;
+    }
+
+    public Lexem peek()
+    {
+        return mLexem;
+    }
+
+    public int getInt()
+    {
+        return Integer.parseInt(mString);
+    }
+
+    public String getString()
+    {
+        return mString;
+    }
+
+    private char nextChar()
+    {
+        mIndex++;
+        if(mIndex>=mData.length()) return EOF;
+        return mCurrent=mData.charAt(mIndex);
+    }
+
+    private boolean eof()
+    {
+        return mIndex>=mData.length();
+    }
+
+    private boolean isValid()
+    {
+        return isValid(mCurrent);
+    }
+
+    private boolean isValid(char c)
+    {
+        if(mIndex<0) return false;
+        return ALLOWED.indexOf(c)>0;
+    }
+
+
+
+}

+ 107 - 0
app/src/main/java/fanch/multijeu/numbers/data/ExpressionParser.java

@@ -0,0 +1,107 @@
+package fanch.multijeu.numbers.data;
+
+public class ExpressionParser {
+
+    protected ExpressionLexer mLexer;
+
+    protected String mString;
+    protected ExpressionLexer.Lexem mLex;
+
+    public ExpressionParser(String s)
+    {
+        mLexer=new ExpressionLexer(s);
+    }
+
+    public Expression parse()
+    {
+        next();
+        return expr();
+    }
+
+    public Expression secureParse()
+    {
+        Expression ex;
+        try{
+            ex=parse();
+            return ex;
+        }catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    private void next()
+    {
+        mLex=mLexer.next();
+        mString=mLexer.getString();
+    }
+
+    private Expression expr() {
+        Expression first;
+        Expression sec;
+        first= expr1();
+        switch(mLex)
+        {
+            case PLUS:
+                next();
+                sec= expr();
+                return new OperationExpr(OperationExpr.Operation.ADD, first, sec);
+
+            case MINUS:
+                next();
+                sec= expr();
+                return new OperationExpr(OperationExpr.Operation.SUB, first, sec);
+
+            default:
+                return first;
+        }
+    }
+
+    private Expression expr1() {
+        Expression first;
+        Expression sec;
+        first= expr2();
+        switch(mLex)
+        {
+            case MULT:
+                next();
+                sec= expr1();
+                return new OperationExpr(OperationExpr.Operation.MULT, first, sec);
+
+            default:
+                return first;
+        }
+    }
+
+    private Expression expr2() {
+        Expression expr;
+        switch (mLex)
+        {
+            case P_OPEN:
+                next();
+                expr=expr();
+                if(mLex== ExpressionLexer.Lexem.P_CLOSE)
+                {
+                    next();
+                    return expr;
+                }
+            case NUMBER:
+                int x = mLexer.getInt();
+                next();
+                return new NumberExpr(x);
+
+            case MINUS:
+                next();
+                int y = mLexer.getInt();
+                next();
+                return new NumberExpr(-y);
+        }
+        throw new RuntimeException("Parser error expected Number' or parethesis : "+mLex);
+    }
+
+    public static Expression parse(String s)
+    {
+        ExpressionParser xp = new ExpressionParser(s);
+        return xp.secureParse();
+    }
+}

+ 9 - 0
app/src/main/java/fanch/multijeu/numbers/data/ExpressionSet.java

@@ -0,0 +1,9 @@
+package fanch.multijeu.numbers.data;
+
+import fanch.multijeu.common.core.Saveable;
+
+public class ExpressionSet extends Saveable {
+    public int resultInt;
+    public Expression resultExpr;
+    public NumberExpr allowedNumber[];
+}

+ 284 - 0
app/src/main/java/fanch/multijeu/numbers/data/ExpressionSetGenerator.java

@@ -0,0 +1,284 @@
+package fanch.multijeu.numbers.data;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.common.utils.IntUtils;
+
+public class ExpressionSetGenerator extends Saveable {
+
+    public static final int VERY_EASY=0;
+    public static final int EASY=1;
+    public static final int MEDIUM=2;
+    public static final int HARD=3;
+    public static final int VERY_HARD=4;
+    public static final int MAX_OP_FAIL =4;
+
+    protected DifficultyAttrs mDiff[] = initDifficulty();
+
+    protected static class DifficultyAttrs {
+        int minNumberCount;
+        int maxNumberCount;
+        int minOp;
+        int maxOp;
+        int minNumberValue;
+        int maxNumberValue;
+        int minResultValue;
+        int maxResultValue;
+        int addWeight;
+        int subWeight;
+        int multWeight;
+
+    }
+
+    protected int mDifficulty;
+    protected ExpressionSet mOut;
+    protected ArrayList<Expression> mPossible = new ArrayList<>();
+    protected int mNOp=0;
+
+    protected DifficultyAttrs dif()
+    {
+        return mDiff[mDifficulty];
+    }
+
+    public ExpressionSetGenerator(int difficulty)
+    {
+        mDifficulty=difficulty;
+    }
+
+    public ExpressionSetGenerator()
+    {
+        mDifficulty=EASY;
+    }
+
+
+    protected boolean next()
+    {
+        boolean ok = false;
+        Expression exp1 = nextNumber();
+        Expression exp2 = nextNumber();
+        OperationExpr op = genOperation();
+        op.mFirst=exp1;
+        op.mSecond=exp2;
+
+        if(op.getOperande()==OperationExpr.Operation.SUB)
+        {
+            if(exp1.compute()<exp2.compute())
+            {
+                op.mSecond=exp1;
+                op.mFirst=exp2;
+            }
+        }
+
+
+        int x = op.compute();
+        //if(x<=dif().maxNumberValue )
+        {
+            mNOp++;
+            addExpression(op);
+            return true;
+        } /*else {
+            addExpression(exp1);
+            addExpression(exp2);
+            return false;
+        }*/
+    }
+
+    public ExpressionSet generate()
+    {
+        mOut = new ExpressionSet();
+        final int numberCount = genNumberCount();
+        final int opCount = genOpCount();
+        mNOp=0;
+        mPossible.clear();
+
+        mOut.allowedNumber=new NumberExpr[numberCount];
+        for(int i=0; i<numberCount; i++) {
+            int x = genNumberValue();
+            mOut.allowedNumber[i]=new NumberExpr(x);
+            mPossible.add(new NumberExpr(x));
+        }
+
+        while(true) {
+            for (int j = 0; j < MAX_OP_FAIL; j++)
+                if (next()) break;
+
+            Expression exp = findMaxOpExpression();
+            if (exp != null)
+            {
+                int n = exp.opCount();
+                if (n >= opCount && n <= dif().maxOp) {
+                    mOut.resultExpr = exp;
+                    mOut.resultInt = exp.compute();
+                    break;
+                }
+            }
+
+            if(mPossible.size()==1) {
+                Log.e("/////////////", "Fail: "+ mPossible.get(0).compute()+" = "+mPossible.get(0));
+                return generate();
+            }
+        }
+
+        return mOut;
+    }
+
+    public ExpressionSet peek()
+    {
+        return mOut;
+    }
+
+    public int genNumberCount()
+    {
+        return IntUtils.rand(dif().minNumberCount, dif().maxNumberCount);
+    }
+
+    public int genNumberValue()
+    {
+        return IntUtils.rand(dif().minNumberValue, dif().maxNumberValue);
+    }
+
+    public int genResultValue()
+    {
+        return IntUtils.rand(dif().minResultValue, dif().maxResultValue);
+    }
+
+    public int genOpCount()
+    {
+        return IntUtils.rand(dif().minOp, dif().maxOp);
+    }
+
+    protected Expression nextNumber()
+    {
+        int i = IntUtils.rand(0, mPossible.size()-1);
+        Expression x = mPossible.get(i);
+        mPossible.remove(i);
+        return x;
+    }
+
+    protected void addExpression(Expression n)
+    {
+        mPossible.add(n);
+    }
+
+    public OperationExpr genOperation()
+    {
+        int tot = dif().addWeight+dif().multWeight+dif().subWeight;
+        int x = IntUtils.rand(0,tot-1);
+        if(x<dif().addWeight)
+            return new OperationExpr(OperationExpr.Operation.ADD);
+        if(x<dif().addWeight+dif().subWeight)
+            return new OperationExpr(OperationExpr.Operation.SUB);
+        return new OperationExpr(OperationExpr.Operation.MULT);
+    }
+
+    protected Expression findMaxOpExpression()
+    {
+        int max=-1;
+        int imax=-1;
+
+        for(int i=mPossible.size()-1; i>=0; i--)
+        {
+            int x = mPossible.get(i).opCount();
+            int n=mPossible.get(i).compute();
+            if(x>max && n>=dif().minResultValue && n<=dif().maxResultValue )
+            {
+                max=x;
+                imax=i;
+            }
+        }
+        if(imax==-1) return null;
+        return mPossible.get(imax);
+    }
+
+    public int getDifficulty() {
+        return mDifficulty;
+    }
+
+    public void setDifficulty(int mDifficulty) {
+        this.mDifficulty = mDifficulty;
+    }
+
+    protected static DifficultyAttrs[] initDifficulty()
+    {
+        DifficultyAttrs[] x = new DifficultyAttrs[5];
+        int d;
+        for(int i=0; i<x.length; i++) x[i]=new DifficultyAttrs();
+
+        //very easy
+        d=VERY_EASY;
+        x[d].minNumberCount=4;
+        x[d].maxNumberCount=4;
+        x[d].minNumberValue=1;
+        x[d].maxNumberValue=10;
+        x[d].minResultValue=25;
+        x[d].maxResultValue=100;
+        x[d].minOp=1;
+        x[d].maxOp=2;
+        x[d].addWeight=2;
+        x[d].subWeight=1;
+        x[d].multWeight=1;
+
+        //easy
+        d=EASY;
+        x[d].minNumberCount=5;
+        x[d].maxNumberCount=5;
+        x[d].minNumberValue=1;
+        x[d].maxNumberValue=10;
+        x[d].minResultValue=50;
+        x[d].maxResultValue=100;
+        x[d].minOp=2;
+        x[d].maxOp=3;
+        x[d].addWeight=1;
+        x[d].subWeight=1;
+        x[d].multWeight=1;
+
+
+        //d
+        d=MEDIUM;
+        x[d].minNumberCount=5;
+        x[d].maxNumberCount=6;
+        x[d].minNumberValue=1;
+        x[d].maxNumberValue=15;
+        x[d].minResultValue=50;
+        x[d].maxResultValue=250;
+        x[d].minOp=3;
+        x[d].maxOp=4;
+        x[d].addWeight=3;
+        x[d].subWeight=1;
+        x[d].multWeight=4;
+
+        //d
+        d=HARD;
+        x[d].minNumberCount=5;
+        x[d].maxNumberCount=6;
+        x[d].minNumberValue=1;
+        x[d].maxNumberValue=25;
+        x[d].minResultValue=50;
+        x[d].maxResultValue=500;
+        x[d].minOp=4;
+        x[d].maxOp=5;
+        x[d].addWeight=3;
+        x[d].subWeight=1;
+        x[d].multWeight=4;
+
+
+        //very d
+        d=VERY_HARD;
+        x[d].minNumberCount=5;
+        x[d].maxNumberCount=6;
+        x[d].minNumberValue=1;
+        x[d].maxNumberValue=50;
+        x[d].minResultValue=50;
+        x[d].maxResultValue=1000;
+        x[d].minOp=5;
+        x[d].maxOp=6;
+        x[d].addWeight=3;
+        x[d].subWeight=1;
+        x[d].multWeight=4;
+
+        return x;
+    }
+}

+ 37 - 0
app/src/main/java/fanch/multijeu/numbers/data/NumberExpr.java

@@ -0,0 +1,37 @@
+package fanch.multijeu.numbers.data;
+
+public class NumberExpr extends Expression {
+    protected int mValue;
+
+    public NumberExpr(int x)
+    {
+        super();
+        mValue=x;
+    }
+
+
+    @Override
+    public String toString() {
+        return ""+compute();
+    }
+
+    @Override
+    public int compute() {
+        return mValue;
+    }
+
+    @Override
+    public Expression simplify() {
+        return new NumberExpr(mValue);
+    }
+
+    @Override
+    public Expression clone() {
+        return new NumberExpr(mValue);
+    }
+
+    @Override
+    public int opCount() {
+        return 0;
+    }
+}

+ 122 - 0
app/src/main/java/fanch/multijeu/numbers/data/OperationExpr.java

@@ -0,0 +1,122 @@
+package fanch.multijeu.numbers.data;
+
+public class OperationExpr extends Expression {
+
+    public boolean isEmpty() {
+        return mFirst==null || mSecond==null;
+    }
+
+    public static enum Operation{
+        ADD,
+        SUB,
+        MULT,
+        DIV
+    };
+
+    protected Expression mFirst;
+    protected Expression mSecond;
+    protected Operation  mOperande;
+
+
+    public OperationExpr(Operation op)
+    {
+        super();
+        mOperande=op;
+    }
+
+
+    public OperationExpr(Operation op, Expression f, Expression s)
+    {
+        super();
+        mFirst=f;
+        mSecond=s;
+        mOperande=op;
+    }
+
+    @Override
+    public Expression clone() {
+        OperationExpr ex = new OperationExpr(mOperande);
+        if(mFirst!=null) ex.mFirst=mFirst.clone();
+        if(mSecond!=null) ex.mSecond=mSecond.clone();
+        return ex;
+    }
+
+    @Override
+    public int opCount() {
+        if(!isEmpty())
+            return mFirst.opCount()+mSecond.opCount()+1;
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        String out = "("+((mFirst!=null)?mFirst.toString():"");
+        switch(mOperande)
+        {
+            case ADD:
+                out+="+";
+                break;
+            case SUB:
+                out+="-";
+                break;
+            case DIV:
+                out+="/";
+                break;
+            case MULT:
+            default:
+                out+="*";
+                break;
+        }
+        return out+((mSecond!=null)?mSecond.toString():"")+")";
+    }
+
+    @Override
+    public int compute() {
+        if(mSecond==null && mFirst!=null) return mFirst.compute();
+        switch(mOperande)
+        {
+            case ADD:
+                return mFirst.compute()+mSecond.compute();
+            case SUB:
+                return mFirst.compute()-mSecond.compute();
+            case DIV:
+                return mFirst.compute()/mSecond.compute();
+            case MULT:
+            default:
+                return mFirst.compute()*mSecond.compute();
+        }
+    }
+
+    @Override
+    public Expression simplify() {
+        return new NumberExpr(compute());
+    }
+
+
+
+
+    public Expression getFirst() {
+        return mFirst;
+    }
+
+    public void setFirst(Expression mFirst) {
+        this.mFirst = mFirst;
+    }
+
+    public Expression getSecond() {
+        return mSecond;
+    }
+
+    public void setSecond(Expression mSecond) {
+        this.mSecond = mSecond;
+    }
+
+    public Operation getOperande() {
+        return mOperande;
+    }
+
+    public void setOperande(Operation mOperande) {
+        this.mOperande = mOperande;
+    }
+
+}

+ 51 - 0
app/src/main/java/fanch/multijeu/numbers/data/ParentheseExpr.java

@@ -0,0 +1,51 @@
+package fanch.multijeu.numbers.data;
+
+public class ParentheseExpr extends Expression {
+    protected char mChar;
+
+    protected ParentheseExpr(char c)
+    {
+        super();
+        mChar=c;
+    }
+
+    @Override
+    public String toString() {
+        return ""+mChar;
+    }
+
+    @Override
+    public int compute() {
+        return 0;
+    }
+
+    public boolean isOpen()
+    {
+        return mChar=='(';
+    }
+
+    @Override
+    public Expression simplify() {
+        return null;
+    }
+
+    @Override
+    public Expression clone() {
+        return new ParentheseExpr(mChar);
+    }
+
+    @Override
+    public int opCount() {
+        return 0;
+    }
+
+    public static ParentheseExpr open()
+    {
+        return new ParentheseExpr('(');
+    }
+
+    public static ParentheseExpr close()
+    {
+        return new ParentheseExpr(')');
+    }
+}

+ 373 - 0
app/src/main/java/fanch/multijeu/numbers/fragment/NumberFragment.java

@@ -0,0 +1,373 @@
+package fanch.multijeu.numbers.fragment;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.transition.Slide;
+import android.support.transition.Transition;
+import android.support.transition.TransitionManager;
+import android.support.transition.Visibility;
+import android.support.v4.app.Fragment;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.TranslateAnimation;
+import android.widget.TextView;
+
+import fanch.multijeu.R;
+import fanch.multijeu.common.core.AnimationEndListener;
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.common.view.ChronoView;
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionSet;
+import fanch.multijeu.numbers.data.OperationExpr;
+import fanch.multijeu.numbers.widget.AssistedCalcWidget;
+import fanch.multijeu.numbers.widget.CalcWidget;
+import fanch.multijeu.numbers.widget.CorrectionWidget;
+import fanch.multijeu.numbers.widget.NonAssistedCalcWidget;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+public class NumberFragment extends Fragment
+        implements  AssistedCalcWidget.CalcWidgetListener,
+        CorrectionWidget.CorrectionListener,
+        ChronoView.ChronoCallback {
+
+    protected NumberViewModel m;
+
+    protected View mRoot;
+    protected TextView mTvResult;
+    protected TextView mTvPerdu;
+    protected TextView mTvBest;
+    protected ChronoView mChrono;
+    protected CalcWidget mWidget;
+    protected AssistedCalcWidget mWidgetSimple;
+    protected NonAssistedCalcWidget mWidgetComplexe;
+    protected CorrectionWidget mCorrection;
+    protected NumberFragmentDriver mListener;
+    protected Boolean mIsInit=false;
+
+
+    public enum PadStyle {
+        ASSISTED,
+        NON_ASSISTED
+    }
+
+    public static class Attrs extends Saveable {
+        public int time=-1;
+        public PadStyle style=PadStyle.ASSISTED;
+    }
+
+    public interface NumberFragmentDriver{
+        void onLoose(ExpressionSet s, Expression e);
+        void onWin(ExpressionSet s, Expression e);
+        void onCorrectFinish();
+        void onAnimationFinish();
+        void onFragmentInit();
+        void onBestChanged(int value);
+    }
+
+    public static NumberFragment newInstance(Attrs a) {
+        NumberFragment x  = new NumberFragment();
+        x.m=new NumberViewModel();
+        Bundle b = new Bundle();
+        b.putSerializable("attrs", a);
+        x.setArguments(b);
+        return x;
+    }
+
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        View v =  inflater.inflate(R.layout.number_fragment, container, false);
+        mRoot=v;
+        init(null);
+        return v;
+    }
+
+    public void init(Attrs  attrs)
+    {
+        m=new NumberViewModel();
+        m.attrs=((Attrs)getArguments().getSerializable("attrs"));
+        //m.attrs=attrs;
+        if(m.attrs==null) m.attrs=new Attrs();
+
+
+        mTvResult=mRoot.findViewById(R.id.tv_result);
+        mWidgetSimple=mRoot.findViewById(R.id.cw_number_simple);
+        mWidgetComplexe=mRoot.findViewById(R.id.cw_number_complexe);
+        mTvBest=mRoot.findViewById(R.id.tv_best);
+        mChrono=mRoot.findViewById(R.id.chrono);
+        mChrono.setListener(this);
+        mTvPerdu=mRoot.findViewById(R.id.perdu);
+        mTvPerdu.setVisibility(GONE);
+
+        mCorrection=mRoot.findViewById(R.id.cw_correction);
+        mCorrection.setListener(this);
+
+        mTvResult.setText("    ");
+
+        setStyle();
+
+        mTvResult.setVisibility(View.INVISIBLE);
+        synchronized (mIsInit)
+        {
+            mIsInit=true;
+            if(mListener!=null)
+                mListener.onFragmentInit();
+
+        }
+    }
+
+    public void setStyle(PadStyle p)
+    {
+        m.attrs.style=p;
+        setStyle();
+    }
+
+    protected void setStyle()
+    {
+        mWidget=(m.attrs.style==PadStyle.ASSISTED)?mWidgetSimple:mWidgetComplexe;
+        mWidget.setListener(this);
+    }
+
+    @Override
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        // TODO: Use the ViewModel
+    }
+
+
+    String fillString(int x , int length)
+    {
+        String s = x+"";
+        while(s.length()<length)
+            s+=" ";
+        return s;
+    }
+
+    @Override
+    public void onValid() {
+
+        if(m.bestResult!=null && m.attrs.style==PadStyle.ASSISTED) {
+            if (m.bestResult.compute() == m.currentSet.resultInt) {
+                mChrono.stop();
+                win();
+                return;
+            }
+        }else if(m.bestResult!=null){
+            if (m.bestResult.compute() == m.currentSet.resultInt) {
+                mChrono.stop();
+                win();
+                return;
+            }else {
+                loose();
+                return;
+            }
+        }
+    }
+
+    @Override
+    public void onClear()
+    {
+    }
+
+    @Override
+    public void onUndo()
+    {
+        mTvBest.setText("    ");
+        mTvResult.setText("    ");
+    }
+
+    public void correct()
+    {
+        mChrono.stop();
+        Transition tr = new Slide(Gravity.BOTTOM);
+        tr.setDuration(1000);
+        tr.addListener(new Transition.TransitionListener() {
+            public void onTransitionStart(@NonNull Transition transition) {}
+            public void onTransitionEnd(@NonNull Transition transition) {
+                mChrono.hide();
+                mCorrection.correction(m.currentSet);
+            }
+            public void onTransitionCancel(@NonNull Transition transition) {}
+            public void onTransitionPause(@NonNull Transition transition) {}
+            public void onTransitionResume(@NonNull Transition transition) {}
+        });
+
+        Transition tr2 = new Slide(Gravity.BOTTOM);
+        tr2.setDuration(1000);
+        TransitionManager.beginDelayedTransition(mWidget, tr);
+        mWidget.setVisibility(GONE);
+    }
+
+    @Override
+    public void onExpressionDone(Expression expr) {
+        if(m.bestResult==null
+                || Math.abs(m.currentSet.resultInt-expr.compute())
+                <
+                Math.abs(m.currentSet.resultInt-m.bestResult.compute())
+        )
+        {
+            m.bestResult=expr.clone();
+            if(mListener!=null)
+                mListener.onBestChanged(m.bestResult.compute());
+            mTvBest.setText(fillString(m.bestResult.compute(),4));
+        }
+
+        if(expr.compute()==m.currentSet.resultInt)
+            onValid();
+    }
+
+
+
+    @Override
+    public void onAnimEnd() {
+        mTvResult.setVisibility(VISIBLE);
+        Animation animation = new TranslateAnimation(512, 0,0, 0);
+        animation.setDuration(500);
+        animation.setAnimationListener(new AnimationEndListener() {
+            public void onAnimationEnd(Animation animation) {
+                startChrono();
+                if(mListener!=null)
+                    mListener.onAnimationFinish();
+            }
+        });
+        mTvResult.startAnimation(animation);
+    }
+
+    protected void startChrono()
+    {
+        if(m.attrs.time<=0) return;
+        mChrono.setDuration(m.attrs.time);
+        mChrono.start();
+        mChrono.invalidate();
+    }
+
+
+    public void newGame(ExpressionSet s)
+    {
+        showWidget();
+        mTvResult.setVisibility(View.INVISIBLE);
+        m.currentSet=s;
+        mWidget.setNumbers(m.currentSet);
+        mTvResult.setText(fillString(m.currentSet.resultInt,4));
+        mTvBest.setText("    ");
+        m.bestResult=null;
+    }
+
+
+
+    protected void showWidget()
+    {
+        if(m.attrs.time<0)
+        {
+            mChrono.hide();
+        }else{
+            mChrono.show();
+        }
+
+
+        Transition tr = new Slide(Gravity.BOTTOM);
+        ((Slide) tr).setMode(Visibility.MODE_IN);
+        tr.setDuration(500);
+
+        TransitionManager.beginDelayedTransition(mWidget, tr);
+        mWidget.setVisibility(VISIBLE);
+    }
+
+    @Override
+    public void onDone() {
+        if(mListener!=null)
+            mListener.onCorrectFinish();
+    }
+
+    @Override
+    public void onSend(Expression expr) {
+
+    }
+
+    @Override
+    public void onEnd() {
+        int r = m.currentSet.resultInt;
+        int a = (m.bestResult!=null) ?(m.bestResult.compute()):0;
+        Expression e = mWidget.getResult();
+        int b = (e!=null)?e.compute():0;
+        int aa=Math.abs(r-a);
+        int bb=Math.abs(r-b);
+        if(bb<aa)
+            m.bestResult=e;
+        if(aa==0 || bb==0)
+            win();
+        else
+            loose();
+    }
+
+    protected void win()
+    {
+        final Activity va = getActivity();
+        mTvPerdu.setText("Bien joué !");
+        mTvPerdu.setTextColor(0xff5f9e2b);
+        mTvPerdu.setVisibility(VISIBLE);
+
+        Animation an = AnimationUtils.loadAnimation(getActivity(), R.anim.scale);
+        an.setDuration(1000);
+        an.setAnimationListener(new AnimationEndListener() {
+            @Override
+            public void onAnimationEnd(final Animation animation) {
+                getActivity().runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTvPerdu.clearAnimation();
+                        mTvPerdu.setVisibility(View.INVISIBLE);
+                        if(mListener!=null)
+                            mListener.onWin(m.currentSet, m.bestResult);
+                    }
+                });
+            }
+        });
+        mTvPerdu.startAnimation(an);
+    }
+
+    protected void loose()
+    {
+        final Activity va = getActivity();
+        mTvPerdu.setText("Perdu !");
+        mTvPerdu.setTextColor(0xffcb2121);
+        mTvPerdu.setVisibility(VISIBLE);
+
+
+        Animation an = AnimationUtils.loadAnimation(getActivity(), R.anim.scale);
+        an.setDuration(1000);
+        an.setAnimationListener(new AnimationEndListener() {
+            @Override
+            public void onAnimationEnd(final Animation animation) {
+                va.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mTvPerdu.clearAnimation();
+                        mTvPerdu.setVisibility(View.INVISIBLE);
+
+                        if(mListener!=null)
+                            mListener.onLoose(m.currentSet, m.bestResult);
+                    }
+                });
+            }
+        });
+        mTvPerdu.startAnimation(an);
+    }
+
+    public void setListener(NumberFragmentDriver mListener) {
+        this.mListener = mListener;
+        synchronized (mIsInit)
+        {
+            if(mIsInit)
+                mListener.onFragmentInit();
+        }
+    }
+}

+ 14 - 0
app/src/main/java/fanch/multijeu/numbers/fragment/NumberViewModel.java

@@ -0,0 +1,14 @@
+package fanch.multijeu.numbers.fragment;
+
+import android.arch.lifecycle.ViewModel;
+
+import fanch.multijeu.common.core.CustomViewModel;
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionSet;
+import fanch.multijeu.numbers.data.ExpressionSetGenerator;
+
+public class NumberViewModel extends CustomViewModel {
+    public ExpressionSet currentSet;
+    public Expression bestResult = null;
+    public NumberFragment.Attrs attrs;
+}

+ 314 - 0
app/src/main/java/fanch/multijeu/numbers/widget/AssistedCalcWidget.java

@@ -0,0 +1,314 @@
+package fanch.multijeu.numbers.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.exceptions.BadChildClass;
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionSet;
+import fanch.multijeu.numbers.data.OperationExpr;
+
+public class AssistedCalcWidget extends CalcWidget {
+
+    protected ArrayList<CalcButton> mExpression = new ArrayList<CalcButton>();
+    protected ArrayList<State> mHistory = new ArrayList<>();
+
+
+    protected static final CalcButton.Type SHOW_IDLE[] = {
+            CalcButton.Type.CLEAR, CalcButton.Type.NUMBER, CalcButton.Type.UNDO,
+            CalcButton.Type.RESULT, CalcButton.Type.VALID
+    };
+
+    protected static final CalcButton.Type SHOW_ON_NUMBER[] = {
+            CalcButton.Type.OPERATION, CalcButton.Type.CLEAR,  CalcButton.Type.VALID, CalcButton.Type.UNDO
+    };
+
+    protected static final CalcButton.Type SHOW_ON_OPERATION[] = {
+            CalcButton.Type.CLEAR, CalcButton.Type.NUMBER, CalcButton.Type.VALID,
+            CalcButton.Type.UNDO, CalcButton.Type.RESULT
+    };
+
+    protected static final CalcButton.Type SHOW_RESULT[] = {
+            CalcButton.Type.CLEAR, CalcButton.Type.NUMBER, CalcButton.Type.UNDO,
+            CalcButton.Type.RESULT, CalcButton.Type.VALID, CalcButton.Type.OPERATION
+    };
+
+
+    public AssistedCalcWidget(Context context) {
+        super(context);
+        initControlButton();
+    }
+
+    public AssistedCalcWidget(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initControlButton();
+    }
+
+
+
+    private void pushHistory()
+    {
+        State s = new State();
+        s.expression= (ArrayList<CalcButton>) mExpression.clone();
+        s.state=mState;
+        s.states=new ArrayList<CalcButton.CalcButtonState>();
+        for(int i=0; i<mNumbers.size(); i++)
+        {
+            s.states.add(mNumbers.get(i).getState());
+        }
+        mHistory.add(s);
+    }
+
+    private void restoreState(State s)
+    {
+        mExpression= (ArrayList<CalcButton>) s.expression.clone();
+        mState=s.state;
+        for(int i=0; i<mNumbers.size(); i++) {
+            mNumbers.get(i).setState(s.states.get(i));
+        }
+    }
+
+    private void pushExpression(CalcButton c)
+    {
+        mExpression.add(c);
+    }
+
+    protected void clearExpression()
+    {
+        mExpression.clear();
+        mState=TypeState.IDLE;
+
+        mScreen.setText("");
+
+        if(mListener!=null)
+            mListener.onClear();
+    }
+
+    protected void updateShow()
+    {
+        CalcButton.Type[] btns = null;
+        switch (mState)
+        {
+            case IDLE:
+                btns= SHOW_IDLE;
+                /*for(int i=0; i<mNumbers.size(); i++)
+                    mNumbers.get(i).markDisabled(false);*/
+                break;
+            case ON_NUMBER:
+                btns=SHOW_ON_NUMBER; break;
+            case ON_OP:
+                btns=SHOW_ON_OPERATION; break;
+            case RESULT:
+                btns=SHOW_RESULT; break;
+        }
+        final int count = getChildCount(); //-1 for mScreen
+        for(int i=0; i<count; i++)
+        {
+            final View c = getChildAt(i);
+            if( (c instanceof CalcButton) ){
+                CalcButton btn  = (CalcButton)c;
+                btn.setEnableIf(btns);
+            }
+
+        }
+    }
+
+    @Override
+    public Expression getResult() {
+        return null;
+    }
+
+    public void undo()
+    {
+        if(mHistory.size()>1) {
+            clearExpression();
+            restoreState(mHistory.get(mHistory.size() - 2));
+            if (mHistory.size() > 1) mHistory.remove(mHistory.size() - 1);
+            updateShow();
+            requestLayout();
+
+            if (mListener == null)
+                mListener.onUndo();
+        }
+
+    }
+
+    public void clear()
+    {
+        if(mHistory.size()<=0) return;
+        clearExpression();
+        restoreState(mHistory.get(0));
+        mHistory.clear();
+        pushHistory();
+
+        updateShow();
+        if(mListener==null)
+            mListener.onClear();
+    }
+
+    public void valid()
+    {
+        if(mExpression.size()==0) {
+            mState = TypeState.IDLE;
+            clearExpression();
+            updateShow();
+        }
+        if(mListener!=null)
+            mListener.onValid();
+    }
+
+    public void replace(CalcButton old, Expression newExpr)
+    {
+        CalcButton.CalcButtonState ca = old.getState();
+        ca.isDisabled=false;
+        ca.text=newExpr.compute()+"";
+        ca.exp.value=newExpr;
+        ca.exp.type=CalcButton.Type.RESULT;
+        old.setState(ca);
+        updateShow();
+    }
+
+    private void removeNumber(CalcButton cb)
+    {
+        mNumbers.remove(cb);
+        removeView(cb);
+    }
+
+    public void setNumbers(int x[])
+    {
+        clearExpression();
+        clearNumbers();
+        mState=TypeState.IDLE;
+        mHistory.clear();
+        super.setNumbers(x);
+        pushHistory();
+        updateShow();
+        showNumber(0);
+    }
+
+    public void setNumbers(Expression x[])
+    {
+        clearExpression();
+        clearNumbers();
+        mState=TypeState.IDLE;
+        mHistory.clear();
+        super.setNumbers(x);
+        pushHistory();
+        mScreen.setText("");
+        updateShow();
+        showNumber(0);
+
+    }
+
+    public void setNumbers(ExpressionSet x)
+    {
+        setNumbers(x.allowedNumber);
+    }
+
+    @Override
+    public void onClick(View v) {
+        super.onClick(v);
+        final CalcButton.CalcButtonContent cb = ((CalcButton)v).getContent();
+        switch(cb.type)
+        {
+            case UNDO:
+                onUndo();
+                break;
+            case RESULT:
+                onNumber(((CalcButton)v));
+                break;
+        }
+    }
+
+
+    protected void onUndo() {
+        undo();
+    }
+
+    public void setExpression(Expression expr) {
+        if(expr!=null)
+        {
+            int res = expr.compute();
+            String x = expr+"";
+
+            if( expr instanceof OperationExpr)
+                x+=" = "+res;
+
+            mScreen.setText(x);
+        }
+        else mScreen.setText("");
+    }
+
+
+    protected void number(CalcButton v) {
+        if(mState==TypeState.RESULT){
+            clearExpression();
+        }
+
+        if(mState==TypeState.ON_OP)
+        {
+            mState=TypeState.RESULT;
+            if(mExpression.size()!=2)
+                throw new IllegalStateException("Ne devrait pas etre possible");
+            pushExpression(v);
+            OperationExpr op = (OperationExpr) mExpression.get(1).getExpression().clone();
+            op.setFirst(mExpression.get(0).getExpression().clone());
+            op.setSecond(v.getExpression().clone());
+            onOperationDone(op);
+            setExpression(op);
+            if(mListener!=null) {
+                mListener.onExpressionDone(op);
+            }
+            updateShow();
+        }else
+        {
+            mState=TypeState.ON_NUMBER;
+            v.markDisabled(true);
+            pushExpression(v);
+            updateShow();
+            setExpression(v.getExpression());
+
+        }
+    }
+
+    private void onOperationDone(OperationExpr op) {
+        mExpression.get(2).markDisabled(true);
+        replace(mExpression.get(0), op);
+        mExpression.get(0).markDisabled(false);
+        mExpression.remove(2);
+        mExpression.remove(1);
+        pushHistory();
+        requestLayout();
+    }
+
+    protected void operation(CalcButton v) {
+        mState=TypeState.ON_OP;
+        pushExpression(v);
+        updateShow();
+
+
+        OperationExpr e = (OperationExpr) mExpression.get(1).getExpression().clone();
+        e.setFirst(mExpression.get(0).getExpression().clone());
+        setExpression(e);
+    }
+
+
+    protected void initControlButton()
+    {
+        mMinGridHeight=3;
+
+        super.initControlButton();
+        mOperandes = new CalcButton[]{mAdd, mSub, mMult/*, mDiv*/};
+
+        updateShow();
+    }
+}

+ 320 - 0
app/src/main/java/fanch/multijeu/numbers/widget/CalcButton.java

@@ -0,0 +1,320 @@
+package fanch.multijeu.numbers.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionParser;
+import fanch.multijeu.numbers.data.NumberExpr;
+import fanch.multijeu.numbers.data.OperationExpr;
+import fanch.multijeu.numbers.data.ParentheseExpr;
+
+public class CalcButton extends android.support.v7.widget.AppCompatButton {
+
+    protected static final int COLOR_VALID=0xff00ff00;
+    protected static final int COLOR_UNDO=0xffaf0000;
+    protected static final int COLOR_CLEAR=0xffff0000;
+    protected static final int COLOR_NUMBER=0xff00afaf;
+    protected static final int COLOR_RESULT=0xff00af55;
+    protected static final int COLOR_OPERATION=0xffffffff;
+
+    public static enum Type {
+        CLEAR,
+        OPERATION,
+        OTHER,
+        RESULT,
+        VALID,
+        UNDO,
+        NUMBER
+    }
+
+    public static class CalcButtonContent {
+        public Type type;
+        public Expression value;
+    }
+
+    public static class CalcButtonState {
+        CalcButtonContent exp=new CalcButtonContent();
+        String text;
+        boolean isDisabled;
+
+    }
+
+    protected CalcButtonContent mContent;
+    protected boolean           mIsMarkedDisabled=false;
+
+    public CalcButton(Context context) {
+        super(context);
+        setGravity(Gravity.CENTER_HORIZONTAL|Gravity.CENTER_VERTICAL);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            setTextAlignment(TEXT_ALIGNMENT_GRAVITY);
+        }
+        setPadding(0,0,0,0);
+    }
+
+    public CalcButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setGravity(Gravity.LEFT|Gravity.CENTER_VERTICAL);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            setTextAlignment(TEXT_ALIGNMENT_GRAVITY);
+        }
+        setPadding(0,0,0,0);
+
+    }
+
+    public CalcButtonContent getContent() {
+        return mContent;
+    }
+
+    protected void setText(String c)
+    {
+        super.setText(c);
+    }
+
+    public void setContent(Expression x)
+    {
+        mContent = new CalcButtonContent();
+        if(x instanceof NumberExpr)
+        {
+            mContent.type=Type.NUMBER;
+            mContent.value=x;
+        }else if(x instanceof OperationExpr){
+            if( ((OperationExpr)x).isEmpty() )
+                mContent.type=Type.OPERATION;
+            else
+                mContent.type=Type.RESULT;
+            mContent.value=x;
+        }else if(x instanceof ParentheseExpr){
+            mContent.type=Type.OTHER;
+            mContent.value=x;
+        }
+        setColor();
+    }
+
+    public void setContent(OperationExpr.Operation op)
+    {
+        mContent = new CalcButtonContent();
+        mContent.type=Type.OPERATION;
+        mContent.value=new OperationExpr(op);
+        setColor();
+    }
+
+    public void setParenthese(boolean open)
+    {
+        mContent = new CalcButtonContent();
+        mContent.type=Type.OTHER;
+        mContent.value=(open)? ParentheseExpr.open():ParentheseExpr.close();
+        setColor();
+    }
+
+    public void setContent(Type t)
+    {
+        mContent = new CalcButtonContent();
+        mContent.type=t;
+        setColor();
+    }
+
+    protected void setColor()
+    {
+        switch(mContent.type)
+        {
+            case UNDO: setBackgroundColor(COLOR_UNDO); break;
+            case VALID: setBackgroundColor(COLOR_VALID); break;
+            case CLEAR: setBackgroundColor(COLOR_CLEAR); break;
+            case OTHER:
+            case OPERATION: setBackgroundColor(COLOR_OPERATION); break;
+            case NUMBER:setBackgroundColor(COLOR_NUMBER); break;
+            case RESULT:
+            default:
+                setBackgroundColor(COLOR_RESULT); break;
+        }
+    }
+
+    public void setAdd()
+    {
+        setContent(OperationExpr.Operation.ADD);
+        setText("+");
+    }
+    public void setMult()
+    {
+        setContent(OperationExpr.Operation.MULT);
+        setText("*");
+    }
+    public void setDiv() {
+        setContent(OperationExpr.Operation.DIV);
+        setText("/");
+    }
+    public void setSub() {
+        setContent(OperationExpr.Operation.SUB);
+        setText("-");
+    }
+    public void setClear()
+    {
+        setContent(Type.CLEAR);
+        setText("C");
+    }
+
+    public void setOpen()
+    {
+        setParenthese(true);
+        setText("(");
+    }
+    public void setClose()
+    {
+        setParenthese(false);
+        setText(")");
+    }
+
+    public void setUndo()
+    {
+        setContent(Type.UNDO);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            setBackground(getResources().getDrawable(android.R.drawable.ic_menu_revert));
+        }else{
+            setBackgroundDrawable(getResources().getDrawable(android.R.drawable.ic_menu_revert));
+        }
+    }
+    public void setValid()
+    {
+        setContent(Type.VALID);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            setBackground(getResources().getDrawable(android.R.drawable.ic_menu_send));
+        }else{
+            setBackgroundDrawable(getResources().getDrawable(android.R.drawable.ic_menu_send));
+        }
+    }
+
+    public void setNumber(Expression ex)
+    {
+        setContent(ex);
+        setText(ex.compute()+"");
+    }
+
+    public boolean isOperation() { return mContent.type==Type.OPERATION; }
+    public boolean isNumber() { return mContent.type==Type.NUMBER; }
+    public boolean isControl() { return mContent.type==Type.CLEAR
+                                        || mContent.type==Type.UNDO
+                                        || mContent.type==Type.VALID; }
+
+
+    public void setEnableIf(Type t[])
+    {
+        boolean show = false;
+        for(int i=0; i<t.length; i++)
+            if(t[i]==mContent.type) {
+                show=true;
+                break;
+            }
+        if(mIsMarkedDisabled) show=false;
+        setEnabled(show);
+        if(show)
+        {
+            setAnimAlpha(1);
+        }
+        else
+        {
+            setAnimAlpha((float) 0.3);
+        }
+    }
+
+    public void setEnable(boolean show)
+    {
+        super.setEnabled(show);
+        markDisabled(!show);
+        if(show)
+        {
+            setAnimAlpha(1);
+        }
+        else
+        {
+            setAnimAlpha((float) 0.3);
+        }
+    }
+
+    public Expression getExpression()
+    {
+        switch (mContent.type)
+        {
+            case OTHER:
+            case RESULT:
+            case OPERATION:
+            case NUMBER:
+                return mContent.value.clone();
+            default:
+                return null;
+        }
+    }
+
+    public void markDisabled(boolean dis)
+    {
+        mIsMarkedDisabled=dis;
+    }
+
+    public CalcButtonState getState()
+    {
+        CalcButtonState state = new CalcButtonState();
+        state.exp.type=mContent.type;
+        state.exp.value=mContent.value.clone();
+        state.text= (String) getText();
+        state.isDisabled=mIsMarkedDisabled;
+        return state;
+    }
+
+    public void setState(CalcButtonState cb)
+    {
+        mContent=new CalcButtonContent();
+        setContent(cb.exp.value.clone());
+        setText(cb.text);
+        mIsMarkedDisabled=cb.isDisabled;
+    }
+
+    public void setAnimAlpha(float f)
+    {
+        ObjectAnimator animation = ObjectAnimator.ofFloat(this, "alpha", f);
+        animation.setDuration(500);
+        animation.start();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+
+        final int W = right-left;
+        final int H =bottom-top;
+
+
+
+        super.onLayout(changed, left, top, right, bottom);
+    }
+
+    @Override
+    public void setText(CharSequence text, BufferType type) {
+        final int x = text.length();
+        final int t = TypedValue.COMPLEX_UNIT_SP;
+        if(x==1) setTextSize(t, 26);
+        else if(x==2) setTextSize(t,26);
+        else if(x==3) setTextSize(t, 24);
+        else setTextSize(t,22);
+        setGravity(Gravity.CENTER_HORIZONTAL|Gravity.CENTER_VERTICAL);
+
+        super.setText(text, type);
+    }
+
+    public void show()
+    {
+        setVisibility(VISIBLE);
+        PropertyValuesHolder pvhAlpha = PropertyValuesHolder.ofFloat("alpha", getAlpha(),  0);
+    }
+
+    public void hide()
+    {
+        setVisibility(GONE);
+    }
+}

+ 335 - 0
app/src/main/java/fanch/multijeu/numbers/widget/CalcWidget.java

@@ -0,0 +1,335 @@
+package fanch.multijeu.numbers.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.annotation.CallSuper;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.exceptions.BadChildClass;
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionSet;
+import fanch.multijeu.numbers.data.NumberExpr;
+
+public abstract class CalcWidget extends GridLayout  implements View.OnClickListener {
+    protected TextView   mScreen;
+    protected CalcButton mClear;
+    protected CalcButton mUndo;
+    protected CalcButton mValid;
+    protected CalcButton mAdd;
+    protected CalcButton mMult;
+    protected CalcButton mSub;
+    protected int        mMinGridHeight=3;
+
+
+    protected CalcButton mOperandes[];
+    protected CalcButton mControl[];
+    protected ArrayList<CalcButton> mNumbers = new ArrayList<CalcButton>();
+    protected TypeState mState= TypeState.IDLE;
+
+    protected AssistedCalcWidget.CalcWidgetListener mListener;
+
+    public static interface CalcWidgetListener {
+        void onValid();
+        void onClear();
+        void onUndo();
+        void onExpressionDone(Expression expr);
+        void onAnimEnd();
+    }
+
+    protected enum TypeState {
+        IDLE,       //au début ou après un clear
+        ON_NUMBER,  //apres qu'on ai tappé sur un nombre
+        ON_OP,      //apres une operation
+        RESULT      //apres le résultat
+    }
+
+    protected static class State {
+        TypeState state;
+        ArrayList<CalcButton> expression;
+        ArrayList<CalcButton.CalcButtonState> states;
+    }
+
+
+    public CalcWidget(Context context) {
+        super(context);
+    }
+
+    public CalcWidget(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    protected void clearNumbers()
+    {
+        final int count = mNumbers.size();
+        for(int i=count-1; i>=0; i--)
+        {
+            removeView(mNumbers.get(i));
+            mNumbers.remove(i);
+        }
+    }
+
+
+
+    protected abstract void updateShow();
+    public abstract Expression getResult();
+
+    public void setNumbers(int x[])
+    {
+        for(int i=0; i<x.length; i++)
+            addNumber(x[i]);
+    }
+
+    public void setNumbers(Expression x[])
+    {
+        for(int i=0; i<x.length; i++)
+            addNumber(x[i]);
+    }
+
+
+
+    public void setNumbers(ExpressionSet x)
+    {
+        setNumbers(x.allowedNumber);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if( !(v instanceof CalcButton) )
+            throw new BadChildClass("Need to be CalcButton");
+        final CalcButton.CalcButtonContent cb = ((CalcButton)v).getContent();
+        switch(cb.type)
+        {
+            case CLEAR:
+                onClear();
+                break;
+            case VALID:
+                onValid();
+                break;
+            case NUMBER:
+                onNumber(((CalcButton)v));
+                break;
+            case OPERATION:
+                onOperation(((CalcButton)v));
+        }
+    }
+
+
+    private void onValid()
+    {
+        valid();
+    }
+
+    protected void onClear() {
+        clear();
+    }
+
+    protected void onNumber(CalcButton v)
+    {
+        number(v);
+    }
+
+    protected void onOperation(CalcButton v)
+    {
+        operation(v);
+    }
+
+    protected abstract void clear();
+    protected abstract void valid();
+    protected abstract void number(CalcButton v);
+    protected abstract void operation(CalcButton v);
+
+    @CallSuper
+    protected void initControlButton()
+    {
+
+        mScreen = new TextView(getContext());
+        mScreen.setTextSize(30);
+        mScreen.setBackgroundColor(0x80000000);
+        mScreen.setLayoutParams(new LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT));
+        mScreen.setText("");
+        mScreen.setPadding(8,8,8,8);
+        mScreen.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
+        addView(mScreen);
+
+        mClear=new CalcButton(getContext());
+        mClear.setClear();
+        mClear.setOnClickListener(this);
+
+
+        mUndo=new CalcButton(getContext());
+        mUndo.setUndo();
+        mUndo.setOnClickListener(this);
+
+        mValid=new CalcButton(getContext());
+        mValid.setValid();
+        mValid.setOnClickListener(this);
+
+        mAdd=new CalcButton(getContext());
+        mAdd.setAdd();
+        mAdd.setOnClickListener(this);
+
+        mSub=new CalcButton(getContext());
+        mSub.setSub();
+        mSub.setOnClickListener(this);
+
+        mMult=new CalcButton(getContext());
+        mMult.setMult();
+        mMult.setOnClickListener(this);
+
+
+        mControl = new CalcButton[]{mClear, mUndo, mValid};
+        mScreen.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
+
+        addView(mClear);
+        addView(mUndo);
+        addView(mValid);
+        addView(mAdd);
+        addView(mSub);
+        addView(mMult);
+    }
+
+    protected void addNumber(int x)
+    {
+        CalcButton cb = newButton(x);
+        addView(cb);
+        cb.setVisibility(GONE);
+        mNumbers.add(cb);
+        requestLayout();
+    }
+
+    protected void addNumber(Expression x)
+    {
+        CalcButton cb = newButton(x.compute());
+        cb.setVisibility(GONE);
+        addView(cb);
+        mNumbers.add(cb);
+        requestLayout();
+    }
+
+
+    protected CalcButton newButton(int x)
+    {
+        CalcButton cb = new CalcButton(getContext());
+        cb.setNumber(new NumberExpr(x));
+        cb.setOnClickListener(this);
+        return cb;
+    }
+
+    protected void showNumber(final int i)
+    {
+        if(i<mNumbers.size())
+        {
+            final CalcButton btn = mNumbers.get(i);
+            int w = ((Activity)getContext()).getWindow().getDecorView().getHeight();
+            Animation animation = new TranslateAnimation(0, 0,512, 0);
+            animation.setDuration(500);
+            animation.setAnimationListener(new Animation.AnimationListener() {
+                public void onAnimationStart(Animation animation) {}
+                public void onAnimationEnd(Animation animation) {
+                    showNumber(i+1);
+                }
+                public void onAnimationRepeat(Animation animation) {}
+            });
+            btn.setVisibility(VISIBLE);
+            btn.startAnimation(animation);
+        } else {
+            if(mListener!=null)
+                mListener.onAnimEnd();
+        }
+    }
+
+    protected void onControlLayout(int w, int h)
+    {
+        for(int i=0; i<mControl.length; i++)
+            mControl[i].setLayoutParams(new LayoutParams(i,1));
+    }
+
+    protected void onScreenLayout(int w, int h)
+    {
+        if(mScreen!=null)
+        {
+            mScreen.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(h, MeasureSpec.AT_MOST));
+            LayoutParams lp = new LayoutParams(0,0, 4,1);
+            int wm = w-mScreen.getMeasuredWidth();
+            int hm=h-mScreen.getMeasuredHeight();
+            lp.marin_right=wm/2;
+            lp.marin_left=wm/2;
+            lp.marin_top=hm/4;
+            lp.marin_bottom=hm/2;
+            mScreen.setLayoutParams(lp);
+        }
+    }
+
+    protected void onOperandeLayout(int w, int h)
+    {
+        for(int i=0; i<mOperandes.length; i++)
+            mOperandes[i].setLayoutParams(new LayoutParams(3,i+1));
+    }
+
+    protected void onNumberLayout(int w, int h)
+    {
+        for(int i=0; i<mNumbers.size(); i++)
+        {
+            final int x =  (i%3);
+            final int y =  2+(i/3);
+            mNumbers.get(i).setLayoutParams(new LayoutParams(x,y));
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int count = getChildCount();
+        final int W = r-l;
+        final int H = b-t;
+        int h = 3;
+        //if(count>0 && count<=12)
+
+        if(mNumbers.size()%mMinGridHeight==0)
+            h=(mNumbers.size()/3)+2;
+        else
+            h=(mNumbers.size()/3)+3;
+
+        if(h<mMinGridHeight) h=mMinGridHeight;
+
+        setGridDimension(4,h);
+        onScreenLayout(W,H/h);
+        onControlLayout(W,H/h);
+        onOperandeLayout(W,H/h);
+        onNumberLayout(W,H/h);
+
+        super.onLayout(changed, l,t,r,b);
+    }
+
+    @Override
+    protected void onMeasure(int ww, int hh) {
+        final int w = MeasureSpec.getSize(ww);
+        final int wm = MeasureSpec.getMode(ww);
+        final int h = MeasureSpec.getSize(hh);
+        final int hm = MeasureSpec.getMode(hh);
+
+        //int size=(w<h)?(w/getGridWidth()):(h/getGridHeight());
+
+
+       /* setMeasuredDimension(size*getGridWidth(),
+                size*getGridHeight());*/
+        setMeasuredDimension(w,h);
+    }
+
+    public void setListener(CalcWidgetListener mListener) {
+        this.mListener = mListener;
+    }
+}

+ 82 - 0
app/src/main/java/fanch/multijeu/numbers/widget/CorrectionOverlay.java

@@ -0,0 +1,82 @@
+package fanch.multijeu.numbers.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+public class CorrectionOverlay extends LinearLayout implements View.OnClickListener {
+
+    protected Button mNext;
+    protected Button mSkip;
+    protected CorrectionOverlayListener mListener;
+
+
+
+    public interface CorrectionOverlayListener {
+        void onNext();
+        void onSkip();
+    }
+
+    public CorrectionOverlay(Context context) {
+        super(context);
+        mNext=new Button(context);
+        mNext.setText("Suivant");
+        mNext.setOnClickListener(this);
+        addView(mNext);
+
+        mSkip=new Button(context);
+        mSkip.setText("Passer");
+        mSkip.setOnClickListener(this);
+        addView(mSkip);
+    }
+
+    public CorrectionOverlay(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mNext=new Button(context);
+        mNext.setText("Suivant");
+        mNext.setOnClickListener(this);
+        addView(mNext);
+
+        mSkip=new Button(context);
+        mSkip.setText("Passer");
+        mSkip.setOnClickListener(this);
+        addView(mSkip);
+    }
+
+    @Override
+    protected void onMeasure(int w, int h) {
+        setMeasuredDimension(w&MEASURED_SIZE_MASK, h&MEASURED_SIZE_MASK);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int W = r-l;
+        final int H = b-t;
+        mNext.measure(MeasureSpec.makeMeasureSpec(W, MeasureSpec.AT_MOST),
+                MeasureSpec.makeMeasureSpec(H, MeasureSpec.AT_MOST));
+        mSkip.measure(MeasureSpec.makeMeasureSpec(W, MeasureSpec.AT_MOST),
+                MeasureSpec.makeMeasureSpec(H, MeasureSpec.AT_MOST));
+        setFrame(mNext,W-mNext.getMeasuredWidth()-16, H-mNext.getMeasuredHeight()-16,
+                mNext.getMeasuredWidth(), mNext.getMeasuredHeight());
+
+        setFrame(mSkip,16, H-mSkip.getMeasuredHeight()-16,
+                mSkip.getMeasuredWidth(), mSkip.getMeasuredHeight());
+    }
+
+    protected void setFrame(View c, int x, int y, int w, int h)
+    {
+        c.layout(x,y,x+w, y+h);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if(v==mSkip && mListener!=null) mListener.onSkip();
+        if(v==mNext && mListener!=null) mListener.onNext();
+    }
+
+    public void setListener(CorrectionOverlayListener mListener) {
+        this.mListener = mListener;
+    }
+}

+ 171 - 0
app/src/main/java/fanch/multijeu/numbers/widget/CorrectionWidget.java

@@ -0,0 +1,171 @@
+package fanch.multijeu.numbers.widget;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.transition.Slide;
+import android.support.transition.Transition;
+import android.support.transition.TransitionManager;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionSet;
+
+public class CorrectionWidget extends FrameLayout implements CorrectionOverlay.CorrectionOverlayListener {
+
+    protected LinearLayout mRootLayout;
+    protected CorrectionOverlay mOverlay;
+    protected ExpressionLayout mExpLayout;
+    protected Expression mExpression;
+    protected int        mStep=0;
+    protected ArrayList<ExpressionLayout> mExpList = new ArrayList<>();
+    protected ArrayList<ExpressionLayout> mNumberList = new ArrayList<>();
+    protected CorrectionListener mListener;
+
+
+    public interface CorrectionListener {
+        void onDone();
+        void onSend(Expression expr);
+    }
+
+    public CorrectionWidget(@NonNull Context context) {
+        super(context);
+        mRootLayout=new LinearLayout(getContext());
+        mRootLayout.setOrientation(LinearLayout.VERTICAL);
+        mOverlay = new CorrectionOverlay(getContext());
+        mOverlay.setLayoutParams(new ViewGroup.LayoutParams(-1,-1));
+
+        addView(mOverlay);
+        addView(mRootLayout);
+
+    }
+
+    public CorrectionWidget(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        mRootLayout=new LinearLayout(getContext());
+        mRootLayout.setOrientation(LinearLayout.VERTICAL);
+        mOverlay = new CorrectionOverlay(getContext());
+        mOverlay.setLayoutParams(new ViewGroup.LayoutParams(-1,-1));
+        mOverlay.setListener(this);
+        addView(mOverlay);
+        addView(mRootLayout);
+    }
+
+    protected void setExpression(ExpressionSet expr)
+    {
+        mExpression=expr.resultExpr;
+        mExpLayout=new ExpressionLayout(getContext(), mExpression);
+        mExpList.clear();
+        mNumberList.clear();
+
+        setExpressionList(mExpLayout);
+        TextView correction = new TextView(getContext());
+        correction.setText("Correction");
+        correction.setGravity(Gravity.CENTER_HORIZONTAL);
+        correction.setTextSize(30);
+
+        mRootLayout.addView(correction);
+
+        LinearLayout ll = new LinearLayout(getContext());
+        for(int i=0; i<mNumberList.size(); i++)
+        {
+
+            TextView tv = new TextView(getContext());
+            if(i==0) tv.setText("Nombres: ");
+            else tv.setText(", ");
+            ll.addView(tv);
+
+            ll.addView(mNumberList.get(i));
+        }
+        mRootLayout.addView(ll);
+        mStep=0;
+    }
+
+    protected void setExpressionList(ExpressionLayout exp)
+    {
+        if(!(exp.isNumber()))
+        {
+            setExpressionList(exp.getFirst());
+            setExpressionList(exp.getSecond());
+            mExpList.add(exp);
+        }else{
+            mNumberList.add(exp);
+        }
+
+    }
+
+
+
+    protected void nextStep()
+    {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            if(mStep==mExpList.size())
+            {
+                mExpList.get(mStep-1).setFinish();
+                mStep++;
+                return;
+
+            }else if(mStep>=mExpList.size())
+            {
+                onSkip();
+                return;
+            }
+        }
+        if(mStep==mExpList.size())
+        {
+            onSkip();
+            return;
+        }
+        ExpressionLayout el = mExpList.get(mStep);
+        LinearLayout ll = new LinearLayout(getContext());
+        TextView tv = new TextView(getContext());
+        tv.setText((mStep+1)+" : ");
+        ll.addView(tv);
+        ll.addView(el);
+        el.setHighlight(true);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            Transition t =  new Slide(Gravity.BOTTOM);
+            t.setDuration(300);
+            TransitionManager.beginDelayedTransition(mRootLayout, t);
+        }
+        mRootLayout.addView(ll);
+        mStep++;
+
+    }
+
+    public void correction(ExpressionSet exp)
+    {
+        setExpression(exp);
+        setVisibility(VISIBLE);
+    }
+
+    @Override
+    public void onNext() {
+        nextStep();
+    }
+
+    @Override
+    public void onSkip() {
+        mExpression=null;
+        mStep=0;
+        mExpList.clear();
+        mNumberList.clear();
+        mRootLayout.removeAllViews();
+        setVisibility(GONE);
+        if(mListener!=null)
+            mListener.onDone();
+    }
+
+    public void setListener(CorrectionListener mListener) {
+        this.mListener = mListener;
+    }
+}

+ 127 - 0
app/src/main/java/fanch/multijeu/numbers/widget/ExpressionLayout.java

@@ -0,0 +1,127 @@
+package fanch.multijeu.numbers.widget;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.NumberExpr;
+import fanch.multijeu.numbers.data.OperationExpr;
+
+public class ExpressionLayout extends LinearLayout {
+    protected Expression mExpression;
+    protected TextView mBefore;
+    protected TextView mViewFirst;
+    protected TextView mViewOp;
+    protected TextView mViewSecond;
+    protected TextView mViewEgal;
+    protected TextView mViewResult;
+    protected ExpressionLayout mFirst;
+    protected ExpressionLayout mSecond;
+    protected int mOldColor=DEFAULT_COLOR;
+
+    protected static final int DEFAULT_COLOR = 0xff000000;
+    protected static final int FIRST_COLOR   = 0xff00afaf;
+    protected static final int SECOND_COLOR  = 0xff00af55;
+
+    public ExpressionLayout(Context c, Expression e){
+        super(c);
+        mExpression =e;
+        mViewResult = createTextView(c, mExpression.compute()+"");
+        if(mExpression instanceof OperationExpr)
+        {
+            final OperationExpr ee = (OperationExpr) e;
+            mFirst=new ExpressionLayout(c, ((OperationExpr)e).getFirst());
+            mSecond=new ExpressionLayout(c, ((OperationExpr)e).getSecond());
+
+            mBefore = createTextView(c, ee.getFirst().compute()+"");
+            mViewFirst = createTextView(c, ee.getFirst().compute()+"");
+            switch(ee.getOperande()){
+                case ADD: mViewOp=createTextView(c," + "); break;
+                case SUB: mViewOp=createTextView(c," - "); break;
+                case MULT: mViewOp=createTextView(c," * "); break;
+                case DIV: mViewOp=createTextView(c," / "); break;
+            }
+            mViewSecond = createTextView(c, ee.getSecond().compute()+"");
+            mViewEgal = createTextView(c, " = ");
+
+            addView(mViewFirst);
+            addView(mViewOp);
+            addView(mViewSecond);
+            addView(mViewEgal);
+        }
+        addView(mViewResult);
+    }
+
+
+
+    protected static TextView createTextView(Context c, String s)
+    {
+        TextView tv = new TextView(c);
+        tv.setText(s);
+        return tv;
+    }
+
+    public void setHighlight(boolean b)
+    {
+        if(b) {
+            if(mExpression instanceof OperationExpr)
+            {
+                mFirst.setHighlight(false);
+                mSecond.setHighlight(false);
+                mFirst.setBackgroundColor(FIRST_COLOR & 0x7fffffff);
+                mSecond.setBackgroundColor(SECOND_COLOR& 0x7fffffff);
+                mOldColor=mViewFirst.getCurrentTextColor();
+                mViewFirst.setTextColor(FIRST_COLOR);
+                mViewSecond.setTextColor(SECOND_COLOR);
+                mViewFirst.setTypeface(null, Typeface.BOLD);
+                mViewSecond.setTypeface(null, Typeface.BOLD);
+            }
+        } else {
+            if(mExpression instanceof OperationExpr) {
+                mFirst.setBackgroundColor(0);
+                mSecond.setBackgroundColor(0);
+                mViewFirst.setTextColor(mOldColor);
+                mViewSecond.setTextColor(mOldColor);
+                mViewFirst.setTypeface(null, Typeface.NORMAL);
+                mViewSecond.setTypeface(null, Typeface.NORMAL);
+            }else
+            {
+                setBackgroundColor(0);
+            }
+        }
+    }
+
+    public boolean isNumber()
+    {
+        return mExpression instanceof NumberExpr;
+    }
+
+    public ExpressionLayout getFirst()
+    {
+        return mFirst;
+    }
+
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void setFinish()
+    {
+        GradientDrawable gd = new GradientDrawable();
+        //gd.setColor(0xFF00FF00); // Changes this drawbale to use a single color instead of a gradient
+        //gd.setCornerRadius(5);
+        gd.setStroke(3, 0xFFFF0000);
+        setPadding(5,5,5,5);
+        setBackground(gd);
+    }
+
+    public ExpressionLayout getSecond()
+    {
+        return mSecond;
+    }
+
+
+}

+ 106 - 0
app/src/main/java/fanch/multijeu/numbers/widget/GridLayout.java

@@ -0,0 +1,106 @@
+package fanch.multijeu.numbers.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+public class GridLayout extends ViewGroup {
+
+    protected int mGridHeight=4;
+    protected int mGridWidth=4;
+
+    protected Rect mChildMargin = new Rect(5,5,5,5);
+
+    public static class LayoutParams extends ViewGroup.LayoutParams {
+        public int x=4;
+        public int y=4;
+        public int marin_top=0;
+        public int marin_bottom=0;
+        public int marin_left=0;
+        public int marin_right=0;
+
+        public LayoutParams() {
+            super(1,1);
+        }
+
+        public LayoutParams(int _x, int _y) {
+            super(1,1);
+            x=_x;
+            y=_y;
+        }
+
+        public LayoutParams(int _x, int _y, int w, int h) {
+            super(w,h);
+            x=_x;
+            y=_y;
+        }
+    }
+
+    public GridLayout(Context context) {
+        super(context);
+    }
+
+    public GridLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+
+    public void setGridDimension(int x, int y)
+    {
+        mGridHeight=y;
+        mGridWidth=x;
+        requestLayout();
+    }
+
+    public int getGridHeight() {
+        return mGridHeight;
+    }
+
+    public int getGridWidth() {
+        return mGridWidth;
+    }
+
+    @Override
+    protected void onMeasure(int w, int h) {
+        setMeasuredDimension(w&MEASURED_SIZE_MASK,
+                h&MEASURED_SIZE_MASK);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int count = getChildCount();
+        final int W = r-l;
+        final int H = b-t;
+        final int hSize = H/mGridHeight;
+        final int wSize = W/mGridWidth;
+
+
+        for(int i=0; i<count; i++)
+        {
+            final View child = getChildAt(i);
+            final ViewGroup.LayoutParams vglp = child.getLayoutParams();
+            if(vglp == null || !(vglp instanceof  LayoutParams)) continue;
+            final LayoutParams lp = (LayoutParams) vglp;
+
+            final int x=lp.x;
+            final int y=lp.y;
+            final int w=lp.width;
+            final int h=lp.height;
+
+            setChildFrame(child, x*wSize+lp.marin_left,
+                    y*hSize+lp.marin_top,
+                    w*wSize-lp.marin_right,
+                    h*hSize-lp.marin_bottom);
+        }
+    }
+
+    private void setChildFrame(View child, int x, int y, int w, int h)
+    {
+        child.layout(x+mChildMargin.left, y+mChildMargin.top,
+                x+w-mChildMargin.right,y+h-mChildMargin.bottom);
+    }
+
+}

+ 232 - 0
app/src/main/java/fanch/multijeu/numbers/widget/NonAssistedCalcWidget.java

@@ -0,0 +1,232 @@
+package fanch.multijeu.numbers.widget;
+
+import android.content.Context;
+import android.support.annotation.CallSuper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.exceptions.BadChildClass;
+import fanch.multijeu.numbers.data.Expression;
+import fanch.multijeu.numbers.data.ExpressionParser;
+import fanch.multijeu.numbers.data.NumberExpr;
+import fanch.multijeu.numbers.data.OperationExpr;
+import fanch.multijeu.numbers.data.ParentheseExpr;
+
+public class NonAssistedCalcWidget extends CalcWidget {
+
+
+
+    private CalcButton mPOpen;
+    private CalcButton mPClose;
+    protected ArrayList<Action> mHistory = new ArrayList();
+    private String mCurrent="";
+
+    private static class Action {
+        public Action(String s, CalcButton b) {string=s; button=b;}
+        public Action(String s) {string=s; button=null;}
+        String string;
+        CalcButton button;
+    }
+
+    public NonAssistedCalcWidget(Context context) {
+        super(context);
+        initControlButton();
+    }
+
+    public NonAssistedCalcWidget(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initControlButton();
+    }
+
+    public void initControlButton()
+    {
+        mMinGridHeight=5;
+        super.initControlButton();
+        mPOpen=new CalcButton(getContext());
+        mPOpen.setOpen();
+        mPOpen.setOnClickListener(this);
+
+        mPClose=new CalcButton(getContext());
+        mPClose.setClose();
+        mPClose.setOnClickListener(this);
+
+
+        mOperandes = new CalcButton[]{mAdd, mSub, mMult, mPClose, mPOpen};
+
+        addView(mPOpen);
+        addView(mPClose);
+
+        updateShow();
+    }
+
+    public void pushHistory(CalcButton pressed)
+    {
+        if(pressed!=null && pressed.getExpression() instanceof NumberExpr)
+            pressed.setEnable(false);
+        else
+            pressed=null;
+        mHistory.add(new Action(mCurrent, pressed));
+    }
+
+
+    public void clearHistory()
+    {
+        mHistory.clear();
+        for(int i=0; i<mNumbers.size(); i++)
+            mNumbers.get(i).setEnable(true);
+        mScreen.setText("");
+        mCurrent="";
+    }
+
+
+    public void revert()
+    {
+        final int n = mHistory.size();
+        if(n>0) {
+            Action e = mHistory.get(n - 1);
+            mCurrent = e.string;
+            if(e.button!=null)
+            {
+                e.button.setEnable(true);
+            }
+            mHistory.remove(n - 1);
+            updateText();
+        }
+    }
+
+
+
+    public Expression getResult() {
+        return getExpression();
+    }
+
+    @Override
+    public void onClick(View v) {
+        super.onClick(v);
+        final CalcButton.CalcButtonContent cb = ((CalcButton)v).getContent();
+        switch(cb.type)
+        {
+            case OTHER:
+                if(cb.value instanceof ParentheseExpr)
+                {
+                    final ParentheseExpr pe = (ParentheseExpr) cb.value;
+                    parenthese(((CalcButton)v), pe.isOpen());
+                }
+                break;
+            case UNDO:
+                revert();
+                break;
+        }
+    }
+
+    @Override
+    protected void updateShow() {
+
+    }
+
+    @Override
+    protected void clear() {
+        clearHistory();
+        updateText();
+    }
+
+    protected Expression getExpression()
+    {
+        //return ExpressionParser.parse(mScreen.getText()+"");
+        String s = mScreen.getText()+"";
+        Log.e("///////", "------>'"+s+"'");
+        if( s==null || s.length()==0) return null;
+        ExpressionParser parser = new ExpressionParser(s);
+        Expression e = parser.parse();
+        Log.e("///////", "---------->'"+e+"' = "+e.compute());
+        Log.e("///////", "");
+        return e;
+    }
+
+    @Override
+    protected void valid() {
+        Expression e = getExpression();
+        if(e!=null)
+        {
+            if(mListener!=null)
+                mListener.onExpressionDone(e);
+        }
+
+        if(mListener!=null)
+            mListener.onValid();
+    }
+
+    @Override
+    protected void number(CalcButton v) {
+        pushHistory(v);
+        mCurrent+=v.getExpression().compute();
+        updateText();
+    }
+
+    @Override
+    protected void operation(CalcButton v) {
+        pushHistory(v);
+        String c="";
+        final OperationExpr e = (OperationExpr) v.getState().exp.value;
+        switch(e.getOperande())
+        {
+            case ADD:c="+"; break;
+            case MULT:c="*"; break;
+            case DIV: c="*"; break;
+            case SUB: c="-"; break;
+        }
+        mCurrent+=c;
+        updateText();
+    }
+
+    protected void parenthese(CalcButton v, boolean open)
+    {
+        pushHistory(v);
+        if(open)
+            mCurrent+="(";
+        else
+            mCurrent+=")";
+        updateText();
+    }
+
+    protected void updateText()
+    {
+        mScreen.setText(mCurrent);
+    }
+
+    public void setNumbers(int x[])
+    {
+        clearNumbers();
+        clearHistory();
+        mState=TypeState.IDLE;
+        super.setNumbers(x);
+        updateShow();
+        mScreen.setText("");
+        showNumber(0);
+    }
+
+    public void setNumbers(Expression x[])
+    {
+        clearNumbers();
+        clearHistory();
+        mState=TypeState.IDLE;
+        super.setNumbers(x);
+        updateShow();
+        mScreen.setText("");
+        showNumber(0);
+    }
+
+    @Override
+    protected void onOperandeLayout(int w, int h) {
+        for(int i=0; i<mOperandes.length-1; i++)
+            mOperandes[i].setLayoutParams(new LayoutParams(3,i+1));
+        final int x = mOperandes.length-1;
+        mOperandes[x].setLayoutParams(new LayoutParams(2,x));
+    }
+
+}

+ 16 - 0
app/src/main/java/fanch/multijeu/numbers/widget/SimpleCorrectionWidget.java

@@ -0,0 +1,16 @@
+package fanch.multijeu.numbers.widget;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+
+public class SimpleCorrectionWidget extends CorrectionWidget {
+    public SimpleCorrectionWidget(@NonNull Context context) {
+        super(context);
+    }
+
+    public SimpleCorrectionWidget(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+}

+ 37 - 0
app/src/main/java/fanch/multijeu/numbers/widget/ViewNumber.java

@@ -0,0 +1,37 @@
+package fanch.multijeu.numbers.widget;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class ViewNumber extends View {
+    protected int mValue=0;
+    protected int mNnumber=3;
+
+    public ViewNumber(Context context) {
+        super(context);
+    }
+
+    public ViewNumber(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public int getValue() {
+        return mValue;
+    }
+
+    public void setValue(int mValue) {
+        this.mValue = mValue;
+    }
+
+    public int getNnumber() {
+        return mNnumber;
+    }
+
+    public void setNnumber(int mNnumber) {
+        this.mNnumber = mNnumber;
+    }
+
+
+}

+ 114 - 0
app/src/main/java/fanch/multijeu/sudoku/activity/SudokuActivity.java

@@ -0,0 +1,114 @@
+package fanch.multijeu.sudoku.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import fanch.multijeu.R;
+import fanch.multijeu.sudoku.data.SimpleSudokuResolver;
+import fanch.multijeu.sudoku.data.Sudoku;
+import fanch.multijeu.sudoku.data.SudokuGenerator;
+import fanch.multijeu.sudoku.view.SudokuCase;
+import fanch.multijeu.sudoku.view.SudokuOverlay;
+import fanch.multijeu.sudoku.view.SudokuView;
+
+public class SudokuActivity extends AppCompatActivity implements SudokuView.SudokuListener {
+
+
+
+    public static class Attrs extends SudokuView.Attrs {
+        public SudokuOverlay.Attrs overlayAttr = new SudokuOverlay.Attrs();
+        public int difficulty=2;
+        public boolean canResolve=true;
+        public boolean canIndices=true;
+
+    }
+
+    protected final static int DIFFICULTIES[]= {45, 40, 35, 30, 27, 25};
+
+    protected SudokuView mGrid;
+    protected SudokuOverlay mOverlay;
+    protected Button        mBtnResolve;
+    protected Button        mBtnIndices;
+    protected Attrs         mAttrs = new Attrs();
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_sudoku2);
+
+        mGrid=findViewById(R.id.sudoku_grid);
+        mBtnIndices=findViewById(R.id.btn_indices);
+        mBtnResolve=findViewById(R.id.btn_reslove);
+        mOverlay=findViewById(R.id.sudoku_overlay);
+
+        mGrid.setListener(this);
+        mOverlay.setListener(mGrid);
+
+        Attrs a = (Attrs) getIntent().getSerializableExtra("attrs");
+
+        setAttrs(a);
+        //OldSudoku s = new OldSudoku("000900001302600000700000960080500400005100007070003009800026040000001690020090700");
+        //OldSudoku s = new OldSudoku("403067100000304000092000006000580304050040010806073000100000740000701000008430201");
+        //Sudoku s = new Sudoku("..8..94......7....93.2...8.....6.9.8.7.......49..38.71.....6.17....4.89........2.");
+        //Sudoku s = new Sudoku("008009400000070000930200080000060908070000000490038071000006017000040890000000020");
+       Sudoku s = new SudokuGenerator();
+       ((SudokuGenerator) s).generate(DIFFICULTIES[a.difficulty]);
+        mGrid.setSudoku(s);
+
+    }
+
+    static void start(Context c)
+    {
+        Intent in = new Intent(c, SudokuActivity.class);
+        c.startActivity(in);
+    }
+
+    static void start(Context c, Attrs attrs)
+    {
+        Intent in = new Intent(c, SudokuActivity.class);
+        in.putExtra("attrs", attrs);
+        c.startActivity(in);
+    }
+
+    @Override
+    public void onClick(SudokuCase c, int x, int y) {
+        if(c.getState()!= SudokuCase.CaseState.LOCKED &&
+            c.getState()!= SudokuCase.CaseState.INDICE)
+        {
+            mOverlay.click(c, x, y, mGrid.possibleValues(x,y));
+        }
+    }
+
+    @Override
+    public void onSudokuDone(boolean ok) {
+        if(ok)
+        {
+            Toast.makeText(this, "Bien joué !", Toast.LENGTH_SHORT).show();
+        }else{
+            Toast.makeText(this, "Erreur !", Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    protected void setAttrs(Attrs a)
+    {
+        mAttrs=a;
+        mGrid.setAttrs(mAttrs);
+        mOverlay.setAttrs(mAttrs.overlayAttr);
+        mBtnIndices.setVisibility( a.canIndices?View.VISIBLE:View.INVISIBLE);
+        mBtnResolve.setVisibility( a.canResolve?View.VISIBLE:View.INVISIBLE);
+    }
+
+    public void onResolve(View view) {
+        mGrid.resolve();
+    }
+
+    public void onIndice(View view) {
+        mGrid.nextIndice();
+    }
+}

+ 68 - 0
app/src/main/java/fanch/multijeu/sudoku/activity/SudokuLauncher.java

@@ -0,0 +1,68 @@
+package fanch.multijeu.sudoku.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import fanch.multijeu.R;
+import fanch.multijeu.sudoku.view.SudokuOverlay;
+import fanch.multijeu.sudoku.view.SudokuView;
+
+public class SudokuLauncher extends AppCompatActivity {
+
+
+    protected RadioGroup mRG;
+    protected RadioButton mRB[]=new RadioButton[6];
+    protected CheckBox mCbIndice;
+    protected CheckBox mCbResolve;
+    protected CheckBox mCbFilter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_sudoku_launcher);
+
+        mRG=findViewById(R.id.rg_difficulty);
+
+        int ids[]={R.id.rb_lvl1, R.id.rb_lvl2, R.id.rb_lvl3, R.id.rb_lvl4, R.id.rb_lvl5,R.id.rb_lvl6};
+        for(int i =0; i<mRB.length; i++)
+            mRB[i] = findViewById(ids[i]);
+
+        mCbFilter=findViewById(R.id.cb_filter);
+        mCbResolve=findViewById(R.id.cb_resolve);
+        mCbIndice=findViewById(R.id.cb_indices);
+
+
+    }
+
+    public void onStart(View view) {
+        SudokuActivity.Attrs attrs = new SudokuActivity.Attrs();
+        attrs.overlayAttr.select=(mCbFilter.isChecked()?
+                SudokuOverlay.SelectMode.DISABLE_BUTTON:
+                SudokuOverlay.SelectMode.NO_ASSIST);
+
+        attrs.canIndices=mCbIndice.isChecked();
+        attrs.canResolve=mCbResolve.isChecked();
+        int id = mRG.getCheckedRadioButtonId();
+        int lvl = 2;
+        for(int i=0; i<mRB.length; i++) {
+            Log.e("--------------", "i="+i);
+            if (mRB[i].getId() == id)
+                lvl = i;
+        }
+        attrs.difficulty=lvl;
+        SudokuActivity.start(this, attrs);
+    }
+
+    public static void start(Context c)
+    {
+        Intent in = new Intent(c, SudokuLauncher.class);
+        c.startActivity(in);
+    }
+}

+ 610 - 0
app/src/main/java/fanch/multijeu/sudoku/data/OldSudoku.java

@@ -0,0 +1,610 @@
+package fanch.multijeu.sudoku.data;
+
+/**
+ * Created by Francois Gautrais on 16/10/14.
+ * Gestion (génération, résolution, détections d'erreurs) de sudoku
+ */
+
+import java.util.Random;
+
+public class OldSudoku {
+    public class Case
+    {
+        public int x,y;
+        public byte c;
+        Case(int _x, int _y, byte _c){x=_x; y=_y; c=_c;}
+    }
+    private final static int N=9;
+    private final static int N_TOTAL=81;
+    private static Random generator = new Random(System.currentTimeMillis());
+
+    protected byte grid[] = new byte[N*N];
+    private int nb_try=0;
+    private long time=0;
+    protected String mDefault="";
+
+    public static final int DONE=0;
+    public static final int VERY_VERY_EASY=20;
+    public static final int VERY_EASY=30;
+    public static final int EASY=40;
+    public static final int NORMAL=45;
+    public static final int HARD=50;
+    public static final int VERY_HARD=55;
+    public static final int VERY_VERY_HARD=60;
+
+    private OldSudoku resolved=null;
+
+    public int getNb_try() {
+        return nb_try;
+    }
+
+
+    /**
+     * Remplie une case non remplie jusque la
+     */
+    public Case help()
+    {
+        _resolve();
+        int tmp[] = new int[N_TOTAL];
+        int i,j=0,k;
+        for(i=0; i<1000; i++)
+        {
+            int x=randint(0,8);
+            int y=randint(0,8);
+            if(get(x,y)==0)
+                return new Case(x,y,resolved.get(x,y));
+        }
+        return null;
+    }
+
+    /**
+     * Met a jour le champs resolved
+     */
+    private void _resolve()
+    {
+        if(resolved==null)
+        {
+            resolved=new OldSudoku(toString());
+            resolved.resolve();
+        }
+    }
+
+    /**
+     * Retourne les erreurs
+     * @param str La chaine qui contient le sudoku a verifier
+     * @return La chaine (1=erreur, 0=ok)
+     */
+    public String errors(String str)
+    {
+        String ret="";
+        _resolve();
+        String ok=resolved.toString();
+        int k=Math.min(ok.length(), str.length());
+        for(int i=0; i<k; i++)
+        {
+            int a=str.charAt(i)-'0', o=ok.charAt(i)-'0';
+            if(a==0 || a==o) ret+='0';
+            else ret+='1';
+        }
+        return ret;
+    }
+
+    public void setNb_try(int nb_try) {
+        this.nb_try = nb_try;
+    }
+
+    public long getTime() {
+        return time;
+    }
+
+    public void setTime(long time) {
+        this.time = time;
+    }
+
+    /**
+     * Remplie la grille de zero
+     */
+    private void clear()
+    {
+        int i;
+        for(i=0; i<N_TOTAL; i++) grid[i]=0;
+    }
+
+    /**
+     * Modifie la valeur de la case
+     * @param i Ligne
+     * @param j Colonne
+     * @param c La valeur de la case (si =0: non remplie)
+     */
+    public void set(int i, int j, byte c)
+    {
+        grid[j*N+i]=c;
+    }
+
+
+    public void set(int i, byte c)
+    {
+        grid[i]=c;
+    }
+
+    public OldSudoku copy()
+    {
+        return new OldSudoku(toString());
+    }
+
+    /**
+     * Recupere la valeur de la grille
+     * @param i Ligne
+     * @param j Colonne
+     * @return La valeur de la case(si =0: non remplie)
+     */
+    public byte get(int i, int j)
+    {
+        return grid[j*N+i];
+    }
+
+    /**
+     * Recupere la valeur de la grille
+     * @param i le nombre
+     * @return La valeur de la case(si =0: non remplie)
+     */
+    public byte get(int i)
+    {
+        return grid[i];
+    }
+
+    /**
+     * Verifie si la colonne c est valide pour la case (x,y)
+     * @param x Adresse de la colonne
+     * @param c Solution propose
+     * @return False si la solution est bonne, sinon true
+     */
+    private boolean check_column( int x,  byte c)
+    {
+        int i, j;
+        for(i=0; i<9; i++)
+            if(get(x, i)==c) return true;
+        return false;
+    }
+
+    /**
+     * Verifie si la ligne c est valide pour la case (x,y)
+     * @param y Adresse de la ligne
+     * @param c Solution propose
+     * @return False si la solution est bonne, sinon true
+     */
+    private boolean check_line( int y,  byte c)
+    {
+        int i, j;
+        for(i=0; i<9; i++)
+            if(get(i, y)==c) return true;
+        return false;
+    }
+
+    /**
+     * Verifie si le carre c est valide pour la case (x,y)
+     * @param x Adresse de la case
+     * @param y Adresse de la case
+     * @param c Solution propose
+     * @return False si la solution est bonne, sinon true
+     */
+    private boolean check_square(int x,  int y,  byte c)
+    {
+        int dx,  dy;
+        int i, j;
+        if(x>=6) dx=6; else if(x>=3) dx=3; else dx=0;
+        if(y>=6) dy=6; else if(y>=3) dy=3; else dy=0;
+        for(i=dx; i<dx+3; i++)
+            for(j=dy; j<dy+3; j++)
+                if(x!=i && j!=y && get( i, j)==c) return true;
+        return false;
+    }
+
+    /**
+     * Verifie si la solution c est valide pour la case (x,y)
+     * @param x Adresse de la case
+     * @param y Adresse de la case
+     * @param c Solution propose
+     * @return True si la solution est bonne, sinon false
+     */
+    public boolean check(int x, int y, byte c)
+    {
+
+        return !(check_line(y,c) || check_column(x,c)
+                || check_square(x, y, c));
+    }
+
+    public byte resolved(int x, int y)
+    {
+        return resolved.get(x, y);
+    }
+
+
+    public byte resolved(int i)
+    {
+        return resolved.get(i);
+    }
+
+
+
+    /**
+     * Genere un entier aleatoirement dans l'interval [x .. y]
+     * avec x>=y
+     * @param x Minimum
+     * @param y Maximum
+     * @return L'entier
+     */
+    private int randint(int x,  int y)
+    {
+        return (int) ((Math.random()*(y-x+1))+x);
+    }
+
+
+    /**
+     * Tente d'inserer une valeur pseudo aleatoire respectant les
+     * contraintes du jeu dans une case precise
+     * @param x Adresse de la case
+     * @param y Adresse de la case
+     * @return -1 si aucune solutions n'est possible
+     * 			Sinon la valeur inserer
+     */
+    private byte insert(int x,  int y)
+    {
+        int i, j=0;
+        byte c=-1;
+        byte tmp[]= new byte[N];
+        for(i=0; i<9; i++)
+            if(check( x, y, (byte) (i+1)))
+                tmp[j++]=(byte)(i+1);
+
+        if(j==0) return -1;
+        c=tmp[randint(0, j-1)];
+        set(x, y, c);
+        return c;
+    }
+
+
+    /**
+     * Renvoie le nombre d'erreur dans le sudoku
+     * @return Nombre d'erreur dans le sudoku (si =0 sudoku valide)
+     */
+    private int nbfail()
+    {
+        int i, j=0;
+        for(i=0; i<81; i++)
+            j+=((get(i%9, i/9 )==0)?1:0);
+        return j;
+    }
+
+    /**
+     * Cherche une combinaison de sudoku de maniere stochastique
+     * @param clear Si true: la grille est vider (mise a 0)
+     * @return le nombre d'erreur (si =0 le sudoku est valide)
+     */
+    private int search_sudoku(boolean clear)
+    {
+        int i,j;
+        if(clear) clear();
+        for(j=0; j<9; j++)
+        {
+            for(i=0; i<9; i++)
+                if(insert(i, j)<0) break;
+        }
+        return nbfail();
+    }
+
+    /**
+     * Cherche une combinaison de sudoku de maniere stochastique
+     * @return le nombre d'erreur (si =0 le sudoku est valide)
+     */
+    private int search_sudoku()
+    {
+        return search_sudoku(true);
+    }
+
+    /**
+     * Genere une grille de sudoku
+     * @param max Le nombre maxial d'iteration (avant echec)
+     * @return -1 si la generation a echoue
+     * 			Sinon, le nombre d'essais
+     */
+    protected int find_sudoku(int max)
+    {
+        long a=System.currentTimeMillis();
+        if(max==0) max=10000;
+        int i=0,x=1;
+        for(i=0; i<max && x>0; i++)
+            x=search_sudoku();
+        if(x>0) i=-1;
+        time=System.currentTimeMillis()-a;
+        nb_try=i;
+        return i;
+    }
+
+    /**
+     * Genere une grille de sudoku
+     * @return -1 si la generation a echoue
+     * 			Sinon, le nombre d'essais
+     */
+    protected int find_sudoku()
+    {
+        int x = find_sudoku(0);
+        mDefault=toString();
+        resolved=new OldSudoku(toString());
+        return x;
+    }
+
+    protected OldSudoku(){}
+
+    /**
+     * Cree un sudoku a partir d'un chaine
+     *
+     * @param str Le sudoku:  suite de 81 numeros ('0':case vide, '1-9' numero)
+     */
+    public OldSudoku(String str)
+    {
+        loadFromString(str);
+        mDefault=toString();
+    }
+
+    public void loadFromString(String str)
+    {
+        for(int i=0; i<N_TOTAL; i++)
+        {
+            byte b = (byte) (str.charAt(i) - '0');
+            if(b<0 || b>N) b=0;
+            set(i%N,i/N,b);
+        }
+    }
+
+    public void reset()
+    {
+        loadFromString(mDefault);
+    }
+
+    public boolean isDefault(int x, int y)
+    {
+        if(mDefault==null || mDefault.length()<81) return false;
+        return mDefault.charAt(y*9+x)!='0';
+    }
+
+    /**
+     * Met une case aleatoire a 0
+     * (200 essais max)
+     */
+    protected boolean clearRandom()
+    {
+        if(nbfail()==N_TOTAL-1) return false;
+        int i,x;
+        for(i=0; i<200; i++)
+        {
+            x=randint(0, N_TOTAL-1);
+            if(grid[x]==0) continue;
+            grid[x]=0;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Met a 0 des case aleatoirement
+     * @param n Nombre de case a mettre a 0
+     */
+    protected void clearRandom(int n)
+    {
+        for(int i=0; i<n; i++)
+            clearRandom();
+    }
+
+    /**
+     * Permet de savoir si le sudoku est resolu
+     * @return true si il est resolu sinon false
+     */
+    public boolean isResolved()
+    {
+        int i, j;
+        for(j=0; j<N; j++)
+            for(i=0; i<N; i++)
+                if(check(i,j, get(i,j)) || get(i,j)==0) return false;
+        return true;
+    }
+
+    /**
+     * Affiche le sudoku: suite de 81 numeros ('0':case vide, '1-9' numero)
+     * @return la fameuse chaine
+     */
+    public String toString()
+    {
+        String s="";
+        for(int i=0; i<N_TOTAL; i++)
+            s+=""+get(i%N, i/N);
+        return s;
+    }
+
+    /**
+     * Affiche le sudoku sur la sortie standart
+     */
+    public void print_grid()
+    {
+        int i, j;
+        for(j=0; j<9; j++)
+        {
+            if(j==3 || j==6) System.out.print("______________________\n");
+            for(i=0; i<9; i++)
+            {
+                if(i==3 || i==6) System.out.print("| ");
+                if(get( i, j)>0) System.out.print(get( i, j)+" ");
+                else System.out.print("- ");
+            }
+            System.out.print("\n");
+        }
+    }
+
+    /**
+     * Savoir si une chaine de sudoku est resolue
+     * @param str Le sudoku: suite de 81 numeros ('0':case vide, '1-9' numero)
+     * @return La string de meme format que le parametre, mais resolue !
+     * 		   Si le sudoku n'est pas resolvable: null
+     */
+    public static  boolean isResolved(String str)
+    {
+        OldSudoku s = new OldSudoku(str);
+        return s.isResolved();
+    }
+
+    /**
+     * Resoudre le sudoku passe en parametre
+     * @param str Le sudoku: suite de 81 numeros ('0':case vide, '1-9' numero)
+     * @return La string de meme format que le parametre, mais resolue !
+     * 		   Si le sudoku n'est pas resolvable: null
+     */
+    public static String resolve(String str)
+    {
+        OldSudoku s = new OldSudoku(str);
+        boolean b = s.resolve();
+        return (b)?s.toString():null;
+    }
+
+    /**
+     * Genere un sudoku
+     * @param k Difficulte (nombre de case a enlever)
+     * @return Un objet sudoku
+     */
+    public static OldSudoku generate(int k)
+    {
+        OldSudoku s=new OldSudoku();
+        int x = s.find_sudoku();
+        if(k>=0)
+            s.clearRandom(k);
+        s.mDefault=s.toString();
+
+        if(x>=0) return s;
+        return null;
+    }
+
+
+    public boolean resolve()
+    {
+        for(int j=0; j<9; j++)
+        {
+            for(int i=0; i<9; i++)
+            {
+                if(get(i,j)==0) continue;
+                byte x=(byte)get(i,j);
+                set(i,j,(byte)0);
+                if(!check( i, j, x))
+                    return false;
+                set(i,j,(byte)x);
+
+            }
+        }
+       return resolverec(0);
+    }
+
+    /**
+     * Resoudre le sudoku
+     * @return false si le probleme en insoluble
+     */
+    private boolean resolverec(int k)
+    {
+        int x=0, y=0, i, j=0;
+        byte c=-1;
+        boolean b=true;
+        byte tmp[]= new byte[N];
+
+		/*
+		 * Choix de la premiere case libre
+		 */
+        for(j=0; j<9 && b; j++)
+            for(i=0; i<9 && b; i++)
+                if(get(i,j)==0)
+                {
+                    x=i;
+                    y=j;
+                    b=false;
+                    break;
+                }
+
+		/*
+		 * S'il n'y a plus de cases libres, c'est que le sudoku
+		 * est fini !!
+		 */
+        if(nbfail()==0) return true;
+        i=x;
+        j=0;
+
+		/*
+		 * Recuperer toutes les solution possible pour
+		 * la case courrante
+		 */
+        for(i=0; i<9; i++)
+            if(check( x, y, (byte) (i+1)))
+                tmp[j++]=(byte)(i+1);
+
+		/*
+		 * Essayer toute les solutions possibles pour la case courrante
+		 * puis appeler recursivement
+		 */
+        for(i=0; i<j; i++)
+        {
+            set(x,y,tmp[i]);
+            if(resolverec(k+1)) return true;
+        }
+
+		/*
+		 * Si aucune solutions n'est possible,
+		 * c'est que le probleme vient d'en haut.
+		 * Mettre la case a 0 et return false
+		 */
+        set(x,y,(byte)0);
+        return false;
+
+
+    }
+
+    public static OldSudoku generate()
+    {
+        return generate(NORMAL);
+    }
+
+    public static OldSudoku generateFilled()
+    {
+        return generate(-1);
+    }
+
+
+}
+
+
+/*
+
+   9    1
+3 26
+7     96
+ 8 5  4
+  51    7
+ 7   3  9
+8   26 4
+     169
+ 2  9 7
+
+
+
+403067100000304000092000006000580304050040010806073000100000740000701000008430201
+
+
+705008200001620000020100000006040830054000720087030100000004090000012300002300501
+
+
+008009400000070000930200080000060908070000000490038071000006017000040890000000020
+
+718659432
+642873159
+935214786
+523761948
+871492365
+496538271
+384926517
+267145893
+159387624
+ */

+ 349 - 0
app/src/main/java/fanch/multijeu/sudoku/data/SimpleSudokuResolver.java

@@ -0,0 +1,349 @@
+package fanch.multijeu.sudoku.data;
+
+import android.util.Log;
+
+import fanch.multijeu.common.utils.TimeUtils;
+//import fanch.multijeu.sudoku.data.OldSudoku;
+
+public class SimpleSudokuResolver {
+
+    protected static final int[][] INDEXES = {
+            {0,1,2,9,10,11,18,19,20}, {3,4,5,12,13,14,21,22,23}, {6,7,8,15,16,17,24,25,26},
+            {27,28,29,36,37,38,45,46,47}, {30,31,32,39,40,41,48,49,50}, {33,34,35,42,43,44,51,52,53},
+            {54,55,56,63,64,65,72,73,74}, {57,58,59,66,67,68,75,76,77}, {60,61,62,69,70,71,78,79,80}
+    };
+
+    protected Sudoku mSudoku;
+    protected long mStart=0;
+
+    protected SudokuValue[] mValues = new SudokuValue[81];
+
+    public SimpleSudokuResolver()
+    {
+    }
+
+    public SimpleSudokuResolver(Sudoku s)
+    {
+        init(s);
+    }
+
+    public void init(Sudoku s)
+    {
+        mStart= TimeUtils.timeUs();
+        mValues = new SudokuValue[81];
+        for(int i=0; i<81; i++)
+            mValues[i]=SudokuValue.filled();
+        mSudoku=s;
+
+        for(int i=0; i<81; i++)
+        {
+            final int x = mSudoku.get(i);
+            if(x>0)
+                mValues[i]=new SudokuValue(x);
+        }
+    }
+
+    public void populate()
+    {
+
+    }
+
+
+/*
+                        int N =  mValues[x].size();
+                        for(int k=0; k<N; k++)
+                        {
+                            final int val = mValues[x].get(k);
+                            if(hasOneNotPresent(i,j,val))
+                            {
+                                mValues[x].set(val);
+                                ok++;
+                            }
+                        }*/
+
+
+
+    public void lastChoiceDeduce(int i, int j)
+    {
+        final int x = j*9+i;
+        final int N =  mValues[x].size();
+        for(int k=0; k<N; k++)
+        {
+            final int val = mValues[x].get(k);
+            if(hasOneNotPresent(i,j,val)) {
+                mValues[x].set(val);
+                return;
+            }
+        }
+    }
+
+
+    public void updatePossiblilities()
+    {
+        int modified=0, n=0, ok=0;
+        do {
+            Log.e("////////", "Pass: "+n+" ("+modified+","+ok+") "+": "+toString());
+            modified = 0;
+            ok=0;
+
+            for (int j = 0; j < 9; j++) {
+                for (int i = 0; i < 9; i++) {
+                    int x = j * 9 + i;
+                    int  old = mValues[x].hash();
+                    if(x==32)
+                    {
+                        int t=0;
+                        t++;
+                    }
+
+                    // appel des deducteur
+                    lastChoiceDeduce(i,j);
+                    simpleDeduce(i,j);
+
+
+                    if(old != mValues[x].hash()) {
+                        modified++;
+                        if(mValues[x].size()==1)
+                        {
+                            ok++;
+                            mSudoku.set(i,j, (byte) mValues[x].get(0));
+                        }
+                    }
+                }
+            }
+
+            n++;
+
+            if(modified==0) {
+                int tt=0;
+                tt++;
+                Log.e("///////","Deadlok");
+                if(!tryRemoveDeadlock())
+                    return;
+            }
+
+        }while (true);
+    }
+
+    public boolean isResolved()
+    {
+        for(int i=0; i<mValues.length; i++)
+            if(mValues.length!=1)
+                return false;
+        return true;
+    }
+
+    public SudokuValue valueForLine(int x, int y)
+    {
+        SudokuValue val = SudokuValue.filled();
+        for(int i=0; i<9; i++)
+        {
+            if(i!=x) val.remove(mSudoku.get(i, y));
+        }
+        return val;
+    }
+
+    public SudokuValue valueForColumn(int x, int y)
+    {
+        SudokuValue val = SudokuValue.filled();
+        for(int i=0; i<9; i++)
+        {
+            if(i!=y)
+                val.remove(mSudoku.get(x, i));
+        }
+        return val;
+    }
+
+    public SudokuValue valueForSquare(int x, int y)
+    {
+
+        SudokuValue val = SudokuValue.filled();
+        int square = (y/3)*3+(x/3);
+        int offset = (y*9)+x;
+        final int arr[] = INDEXES[square];
+        for(int i=0; i<arr.length; i++) {
+            if(arr[i]!=offset)
+                val.remove(mSudoku.get(arr[i]));
+        }
+        return val;
+    }
+
+    /**
+     * Enleve toutes les possibilités déja présente dans les lignes, colonnes, square
+     * @param i L'indice [0-8] de labicsse de la case
+     * @param j L'indice [0-8] de l'ordonée
+     * @return true si une information a été éliminé
+     */
+    public void simpleDeduce(int i, int j)
+    {
+        if(j*9+i==16 || j*9+i==13)
+        {
+            int t=0;
+            t++;
+        }
+        mValues[j*9+i].intersect(valueForColumn(i,j));
+        mValues[j*9+i].intersect(valueForLine(i,j));
+        mValues[j*9+i].intersect(valueForSquare(i,j));
+    }
+
+    public String toString()
+    {
+        String s = "";
+        for(int j=0; j<9; j++)
+        {
+            for(int i=0; i<9; i++)
+            {
+                s+=mValues[j*9+i]+" ";
+            }
+            s+="\n";
+        }
+        return s;
+    }
+
+    public boolean hasNoPresentInLine(int x, int y, int val)
+    {
+        for(int i=0; i<9; i++)
+            if(x!=i)
+                if(mValues[y*9+i].has(val))
+                    return false;
+        return true;
+    }
+
+    public boolean hasNoPresentInColumn(int x, int y, int val)
+    {
+        for(int i=0; i<9; i++)
+            if(y!=i)
+                if(mValues[i*9+x].has(val))
+                    return false;
+        return true;
+    }
+
+    public boolean hasNoPresentSquare(int x, int y, int val)
+    {
+        int square = (y/3)*3+(x/3);
+        int offset = (y*9)+x;
+        final int arr[] = INDEXES[square];
+        for(int i=0; i<arr.length; i++) {
+            if(arr[i]!=offset)
+                if(mValues[arr[i]].has(val))
+                    return false;
+
+        }
+        return true;
+    }
+
+
+    public boolean hasOneNotPresent(int x, int y, int val)
+    {
+        return hasNoPresentInLine(x,y,val) || hasNoPresentInColumn(x,y,val)
+                    || hasNoPresentSquare(x,y,val);
+    }
+
+    public void resolve() {
+        updatePossiblilities();
+        long s = TimeUtils.timeUs()-mStart;
+        Log.e("///////", "Time: "+s+" us");
+    }
+
+    public SudokuValue[] getLine(int y)
+    {
+        SudokuValue val[] = new SudokuValue[9];
+        for(int i=0; i<9; i++)
+            val[i]=mValues[y*9+i];
+        return val;
+    }
+
+    public int[] getLineIndexes(int y)
+    {
+        int val[] = new int[9];
+        for(int i=0; i<9; i++)
+            val[i]=y*9+i;
+        return val;
+    }
+
+    public SudokuValue[] getColumn(int x)
+    {
+        SudokuValue val[] = new SudokuValue[9];
+        for(int i=0; i<9; i++)
+            val[i]=mValues[i*9+x];
+        return val;
+    }
+
+    public int[] getColumnIndexes(int x)
+    {
+        int val[] = new int[9];
+        for(int i=0; i<9; i++)
+            val[i]=i*9+x;
+        return val;
+    }
+
+    public SudokuValue[] getSquare(int n)
+    {
+        SudokuValue val[] = new SudokuValue[9];
+        int indexes[] = INDEXES[n];
+        for(int i=0; i<9; i++)
+            val[i]=mValues[indexes[i]];
+        return val;
+    }
+
+    public int[] getSquareIndexes(int n)
+    {
+        return INDEXES[n];
+    }
+
+    public boolean checkDeadlock(SudokuValue[] values, int[] indexes)
+    {
+        int N = values.length;
+        for(int i=0; i<N; i++)
+        {
+            final SudokuValue v = values[i];
+            if(v.size()<2) continue;
+
+            int n=0;
+            for(int j=0; j<N; j++)
+            {
+                if(v.equals(values[j]))
+                    n++;
+            }
+            if(n==v.size())
+            {
+                boolean ok=true;
+                for(int k=0; k<v.size() && ok; k++)
+                {
+                    for(int l=0; l<N && ok; l++)
+                    {
+                        if(!v.equals(values[l]) && values[l].has(v.get(k)))
+                            ok=false;
+                    }
+                }
+                if(ok)
+                {
+                    v.set(v.get());
+                    mSudoku.set(indexes[i], v.get());
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean tryRemoveDeadlock()
+    {
+        for(int i=0; i<9; i++)
+        {
+            SudokuValue[] v = getLine(i);
+            int[] vi = getLineIndexes(i);
+
+            SudokuValue[] v2 = getColumn(i);
+            int[] v2i = getColumnIndexes(i);
+
+            SudokuValue[] v3 = getSquare(i);
+            int[] v3i = getSquareIndexes(i);
+
+            if(checkDeadlock(v, vi))  return true;
+            if(checkDeadlock(v2, v2i))  return true;
+            if(checkDeadlock(v3, v3i))  return true;
+        }
+        return false;
+    }
+}

+ 148 - 0
app/src/main/java/fanch/multijeu/sudoku/data/SmartOldSudoku.java

@@ -0,0 +1,148 @@
+package fanch.multijeu.sudoku.data;
+
+import fanch.multijeu.common.utils.IntUtils;
+
+public class SmartOldSudoku extends OldSudoku {
+    /**
+     * Cree un sudoku a partir d'un chaine
+     *
+     * @param str Le sudoku:  suite de 81 numeros ('0':case vide, '1-9' numero)
+     */
+    public SmartOldSudoku(String str) {
+        super(str);
+    }
+
+    public SmartOldSudoku() {
+        super();
+    }
+
+    public SmartOldSudoku copy(){
+        return new SmartOldSudoku(toString());
+    }
+
+    @Override
+    protected boolean clearRandom() {
+        int histo[] = new int[9];
+        int total =0;
+        final int MIN=2;
+        final int FACTOR=3;
+
+        for(int i=0; i<81; i++)
+        {
+            final int x = get(i);
+            if(x>=1 && x<=9)
+                histo[x-1]++;
+        }
+
+        for(int i=0; i<9; i++) {
+            if(histo[i]>MIN) total += histo[i]*FACTOR;
+        }
+
+
+        int n = IntUtils.rand(0, total-1);
+        int val=0;
+        for(int i=0; i<9; i++)
+        {
+            n-=histo[i]*FACTOR;
+            if(n<0)
+            {
+                val=i+1;
+                break;
+            }
+        }
+        if(val==0)
+            return false;
+
+        int max=0;
+        int imax=-1;
+        for(int i=0; i<81; i++)
+        {
+            if(get(i)==val)
+            {
+                final int x = count(i);
+                if(x>max)
+                {
+                    max=x;
+                    imax=i;
+                }
+            }
+        }
+        if(imax==-1)
+            return false;
+        grid[imax]=0;
+
+        return true;
+    }
+
+    protected int countLine(int y)
+    {
+        int n = 0;
+        for(int i=0; i<9; i++)
+            if(get(i, y)!=0)
+                n++;
+        return n;
+    }
+
+    protected int countColumn(int x)
+    {
+        int n = 0;
+        for(int i=0; i<9; i++)
+            if(get(x, i)!=0)
+                n++;
+        return n;
+    }
+
+    protected int countSquare(int x, int y)
+    {
+        int n = 0;
+        int dx,  dy;
+        int i, j;
+        if(x>=6) dx=6; else if(x>=3) dx=3; else dx=0;
+        if(y>=6) dy=6; else if(y>=3) dy=3; else dy=0;
+        for(i=dx; i<dx+3; i++)
+            for(j=dy; j<dy+3; j++)
+                if(get(i,j)!=0) n++;
+        return n;
+    }
+
+    protected int count(int x, int y)
+    {
+        return countSquare(x,y)+countColumn(x)+countLine(y);
+    }
+
+    protected int count(int i)
+    {
+        return count(i%9, i/9);
+    }
+
+    /*public static OldSudoku generate(int k)
+    {
+        OldSudoku s=new SmartOldSudoku();
+        int x = s.find_sudoku();
+        s.clearRandom(81-k);
+        s.mDefault=s.toString();
+        SimpleSudokuResolver sr = new SimpleSudokuResolver(s);
+        sr.resolve();
+        Log.e("//////////", s.toString());
+        if(x>=0) return s;
+        return null;
+    }*/
+
+    public static SmartOldSudoku newSudoku(){
+        SmartOldSudoku s = new SmartOldSudoku();
+        int x= s.find_sudoku(0);
+        if (x>=0)
+        {
+            return s;
+        }
+        throw new RuntimeException("Impossible de générer le Sudoku: Nombre max de test dépassé");
+    }
+
+
+    public Sudoku toStdSudoku()
+    {
+        return new Sudoku(toString());
+    }
+
+
+}

+ 166 - 0
app/src/main/java/fanch/multijeu/sudoku/data/Sudoku.java

@@ -0,0 +1,166 @@
+package fanch.multijeu.sudoku.data;
+
+public class Sudoku {
+    public static int LINE_COUNT=9;
+    public static int COLUMN_COUNT=9;
+    public static int CELL_COUNT=LINE_COUNT*COLUMN_COUNT;
+
+    protected static final int[][] INDEXES = {
+            {0,1,2,9,10,11,18,19,20}, {3,4,5,12,13,14,21,22,23}, {6,7,8,15,16,17,24,25,26},
+            {27,28,29,36,37,38,45,46,47}, {30,31,32,39,40,41,48,49,50}, {33,34,35,42,43,44,51,52,53},
+            {54,55,56,63,64,65,72,73,74}, {57,58,59,66,67,68,75,76,77}, {60,61,62,69,70,71,78,79,80}
+    };
+
+    protected int[] mPuzzle = new int[81];
+
+    protected Sudoku(){}
+
+    public Sudoku(String str)
+    {
+        loadFromString(str);
+    }
+
+    public Sudoku copy()
+    {
+        Sudoku s = new Sudoku();
+        for(int i=0; i<CELL_COUNT; i++)
+            s.set(i, get(i));
+        return s;
+    }
+
+    public boolean resolve()
+    {
+        SimpleSudokuResolver sr = new SimpleSudokuResolver(this);
+        sr.resolve();
+        return isFinish();
+    }
+
+    public String toString()
+    {
+        String s="";
+        for(int i=0; i<CELL_COUNT; i++)
+            s+=""+get(i%LINE_COUNT, i/LINE_COUNT);
+        return s;
+    }
+
+    public void loadFromString(String str)
+    {
+        for(int i=0; i<CELL_COUNT; i++)
+        {
+            byte b = (byte) (str.charAt(i) - '0');
+            if(b<0 || b>LINE_COUNT) b=0;
+            set(i%LINE_COUNT,i/LINE_COUNT,b);
+        }
+    }
+
+
+    public void clear()
+    {
+        int i;
+        for(i=0; i<CELL_COUNT; i++) mPuzzle[i]=0;
+    }
+
+    public void set(int i, int j, int c)
+    {
+        mPuzzle[j*LINE_COUNT+i]=c;
+    }
+
+    public void set(int i, int c)
+    {
+        mPuzzle[i]=c;
+    }
+
+    public int get(int i)
+    {
+        return mPuzzle[i];
+    }
+
+    public int get(int i, int j)
+    {
+        return mPuzzle[j*LINE_COUNT+i];
+    }
+
+
+    protected boolean checkColumn( int x, int y, int c)
+    {
+        int i;
+        for(i=0; i<9; i++)
+            if(y!=i && get(x, i)==c) return false;
+        return true;
+    }
+
+    protected boolean checkColumn( int x, int y)
+    {
+        return checkColumn(x, y, get(x,y));
+    }
+
+    private boolean checkLine( int x, int y,  int c)
+    {
+        int i;
+        for(i=0; i<9; i++)
+            if(x!=i && get(i, y)==c) return false;
+        return true;
+    }
+
+    protected boolean checkLine( int x, int y)
+    {
+        return checkLine(x, y, get(x,y));
+    }
+
+    /*private boolean checkSquare(int x,  int y,  int c)
+    {
+        int dx,  dy;
+        int i, j;
+        if(x>=6) dx=6; else if(x>=3) dx=3; else dx=0;
+        if(y>=6) dy=6; else if(y>=3) dy=3; else dy=0;
+        for(i=dx; i<dx+3; i++)
+            for(j=dy; j<dy+3; j++)
+                if(x!=i && j!=y && get( i, j)==c) return false;
+        return true;
+    }*/
+    private boolean checkSquare(int x,  int y,  int c)
+    {
+        int square[] = INDEXES[(y/3)*3+x/3];
+        int offset = y*9+x;
+        for(int i=0; i<square.length; i++)
+                if(offset!=square[i] && get(square[i])==c)
+                    return false;
+        return true;
+    }
+
+    protected boolean checkSquare( int x, int y)
+    {
+        return checkSquare(x, y, get(x,y));
+    }
+
+    public boolean checkCell(int x, int y, int c)
+    {
+        return checkLine(x,y,c) && checkColumn(x, y,c) && checkSquare(x, y, c);
+    }
+
+    public boolean check(){
+        for(int i=0; i<CELL_COUNT; i++)
+        {
+            final int x = i%LINE_COUNT;
+            final int y = i/LINE_COUNT;
+            final int val = get(i);
+
+            if(!checkCell(x,y,val))
+                return false;
+        }
+        return true;
+    }
+
+    public boolean isFinish(){
+        for(int i=0; i<CELL_COUNT; i++)
+        {
+            final int x = i%LINE_COUNT;
+            final int y = i/LINE_COUNT;
+            final int val = get(i);
+            if(val==0 || !checkCell(x,y,val))
+                return false;
+        }
+        return true;
+    }
+
+}

+ 174 - 0
app/src/main/java/fanch/multijeu/sudoku/data/SudokuGenerator.java

@@ -0,0 +1,174 @@
+package fanch.multijeu.sudoku.data;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.utils.IntUtils;
+
+public class SudokuGenerator extends Sudoku {
+
+    private Sudoku mResolved = new Sudoku();
+
+    public SudokuGenerator()
+    {
+        super();
+    }
+
+    public Sudoku getResolved()
+    {
+        return mResolved;
+    }
+
+
+    public void generate(int n)
+    {
+        int k = 81-n;
+        OldSudoku sos = OldSudoku.generateFilled();
+        mResolved=new Sudoku(sos.toString());
+        loadFromString(sos.toString());
+        while(clearRandom(k)<k);
+    }
+
+    protected boolean clearRandom2() {
+        int histo[] = new int[9];
+        final int MIN=1;
+        ArrayList<Integer> l = new ArrayList<>();
+        for(int i=0; i<81; i++)
+        {
+            final int x = get(i);
+            if(x>=1 && x<=9)
+                histo[x-1]++;
+        }
+
+        for(int i=0; i<81; i++)
+        {
+            final int x = get(i);
+            if(x>0 && histo[x-1]>MIN) l.add(i);
+        }
+
+        int x = IntUtils.rand(0, l.size()-1);
+        set(l.get(x),0);
+        return true;
+    }
+
+    protected boolean clearRandom() {
+        int histo[] = new int[9];
+        int total =0;
+        final int MIN=2;
+        final int FACTOR=3;
+
+        for(int i=0; i<81; i++)
+        {
+            final int x = get(i);
+            if(x>=1 && x<=9)
+                histo[x-1]++;
+        }
+
+        for(int i=0; i<9; i++) {
+            if(histo[i]>MIN) total += histo[i]*FACTOR;
+        }
+
+
+        int n = IntUtils.rand(0, total-1);
+        int val=0;
+        for(int i=0; i<9; i++)
+        {
+            n-=histo[i]*FACTOR;
+            if(n<0)
+            {
+                val=i+1;
+                break;
+            }
+        }
+        if(val==0)
+            return false;
+
+        int max=0;
+        int imax=-1;
+        for(int i=0; i<81; i++)
+        {
+            if(get(i)==val)
+            {
+                final int x = count(i);
+                if(x>max)
+                {
+                    max=x;
+                    imax=i;
+                }
+            }
+        }
+        if(imax==-1)
+            return false;
+        mPuzzle[imax]=0;
+
+        return true;
+    }
+
+    protected int clearRandom(int n)
+    {
+        int K=81*2;
+        String last=toString();
+        for(int i=0; i<n; i++)
+        {
+            int k;
+            for( k=0; k<K; k++)
+            {
+                boolean b = clearRandom2();
+                Sudoku test = copy();
+                try{
+                    if(!test.resolve()) throw new Exception();
+                    last=toString();
+                    break;
+                }catch (Exception e)
+                {
+                    loadFromString(last);
+                }
+            }
+            if(k==K) return i;
+            //throw new RuntimeException("Erreur nombre d'echec trop important i="+i+"  "+toString());
+        }
+        return n;
+    }
+
+    protected int countLine(int y)
+    {
+        int n = 0;
+        for(int i=0; i<9; i++)
+            if(get(i, y)!=0)
+                n++;
+        return n;
+    }
+
+    protected int countColumn(int x)
+    {
+        int n = 0;
+        for(int i=0; i<9; i++)
+            if(get(x, i)!=0)
+                n++;
+        return n;
+    }
+
+    protected int countSquare(int x, int y)
+    {
+        int n = 0;
+        int dx,  dy;
+        int i, j;
+        if(x>=6) dx=6; else if(x>=3) dx=3; else dx=0;
+        if(y>=6) dy=6; else if(y>=3) dy=3; else dy=0;
+        for(i=dx; i<dx+3; i++)
+            for(j=dy; j<dy+3; j++)
+                if(get(i,j)!=0) n++;
+        return n;
+    }
+
+
+    protected int count(int x, int y)
+    {
+        return countSquare(x,y)+countColumn(x)+countLine(y);
+    }
+
+    protected int count(int i)
+    {
+        return count(i%9, i/9);
+    }
+
+}

+ 140 - 0
app/src/main/java/fanch/multijeu/sudoku/data/SudokuValue.java

@@ -0,0 +1,140 @@
+package fanch.multijeu.sudoku.data;
+
+import java.util.Arrays;
+
+import fanch.multijeu.common.utils.IntUtils;
+
+public class SudokuValue{
+
+    protected static final int ALL=0x1ff;
+    protected int mValue=0;
+
+    public SudokuValue(){
+
+    }
+
+    public SudokuValue(int n){
+        add(n);
+    }
+
+
+
+    public boolean has(int x)
+    {
+        x--;
+        return ((1<<x)&mValue)>0;
+    }
+
+    public void add(int x)
+    {
+        x--;
+        mValue|=(1<<x);
+    }
+
+    public void remove(int x)
+    {
+        x--;
+        mValue&= ~(1<<x);
+    }
+
+    public void set(int n)
+    {
+        mValue=1<<(n-1);
+    }
+
+    public void intersect(SudokuValue v)
+    {
+        mValue&=v.mValue;
+    }
+
+    public SudokuValue copy()
+    {
+        SudokuValue v = new  SudokuValue();
+        v.mValue=mValue;
+        return v;
+    }
+
+    public static SudokuValue filled()
+    {
+        SudokuValue v = new SudokuValue();
+        v.mValue=ALL;
+        return v;
+    }
+
+    public int hash()
+    {
+        return mValue;
+    }
+
+    public boolean equals(SudokuValue v)
+    {
+        return (hash()==v.hash());
+    }
+
+    public String toString()
+    {
+        int b[] = values();
+        String s = "{";
+        for(int i=0; i<b.length; i++)
+        {
+            s+=b[i];
+            if(i<b.length-1)
+                s+=",";
+        }
+        return  s+"}";
+    }
+
+    public int[] values()
+    {
+        int x[] = new int[9];
+        int n=0;
+        for(int i=0; i<9; i++)
+            if(((1<<i)&mValue)>0)
+                x[n++]=i+1;
+        return Arrays.copyOfRange(x,0, n);
+    }
+
+    public int size()
+    {
+        int x=0;
+        for(int i=0; i<9; i++)
+            if( ((1<<i)&mValue)>0 )
+                x++;
+
+        return x;
+    }
+
+    public byte get(int n)
+    {
+        int i;
+        for(i=0; i<9; i++)
+            if( (mValue&(1<<i)) > 0 ) {
+                if(n==0) return (byte) (i+1);
+                n--;
+            }
+        return 0;
+    }
+
+    public byte get()
+    {
+        return get(0);
+    }
+
+    public byte rand()
+    {
+        final int x = size();
+        return get(IntUtils.rand(0, x-1));
+    }
+
+}
+/*
+5    2 79
+9  5  31
+ 7  49 6
+618 2 7
+4  68 2
+ 35 7   6
+859436 27
+1 4  86
+763 51984
+ */

+ 7 - 0
app/src/main/java/fanch/multijeu/sudoku/listener/SudokuDriver.java

@@ -0,0 +1,7 @@
+package fanch.multijeu.sudoku.listener;
+
+public interface SudokuDriver {
+    void setValue(int x, int y, int newVal);
+    boolean isError(int x, int y, int val);
+    void valueChanged(int x, int y, int val);
+}

+ 102 - 0
app/src/main/java/fanch/multijeu/sudoku/view/SudokuCase.java

@@ -0,0 +1,102 @@
+package fanch.multijeu.sudoku.view;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.Button;
+
+import fanch.multijeu.common.view.AutoSizeButton;
+
+public class SudokuCase extends android.support.v7.widget.AppCompatButton {
+
+    public static final int COLOR_LOCK=0xffcccccc;
+    public static final int COLOR_EMPTY=0x80ffffff;
+    public static final int COLOR_ERROR=0xffff0000;
+    public static final int COLOR_VALUE=0x80ffffff;
+    public static final int COLOR_INDICE=0xfffff7ab;
+
+    public static enum CaseState{
+        EMPTY,
+        LOCKED,
+        ERROR,
+        INDICE,
+        VALUE
+    }
+
+    protected CaseState mState=CaseState.EMPTY;
+    protected int       mValue=0;
+
+
+    public SudokuCase(Context context) {
+        super(context);
+        updateUi();
+        setPadding(0,0,0,0);
+    }
+
+    public SudokuCase(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        updateUi();
+    }
+
+
+    public CaseState getState() {
+        return mState;
+    }
+
+    public void setState(CaseState mState) {
+        this.mState = mState;
+        updateUi();
+    }
+
+    public int getValue() {
+        return mValue;
+    }
+
+    public void setValue(int mValue) {
+        this.mValue = mValue;
+        if(mValue>0 && mValue<=9)
+            mState=CaseState.VALUE;
+        else if(mValue==0 && mState!=CaseState.LOCKED)
+            mState=CaseState.EMPTY;
+        updateUi();
+    }
+
+    protected void updateUi()
+    {
+        switch(mState)
+        {
+            case EMPTY:
+                setBackgroundColor(COLOR_EMPTY);
+                setText("");
+                mValue=0;
+                break;
+            case LOCKED:
+                setBackgroundColor(COLOR_LOCK);
+                setText(""+mValue);
+                break;
+            case VALUE:
+                setBackgroundColor(COLOR_VALUE);
+                setText(""+mValue);
+                break;
+            case INDICE:
+                setBackgroundColor(COLOR_INDICE);
+                setText(""+mValue);
+                break;
+            case ERROR:
+            default:
+                setBackgroundColor(COLOR_ERROR);
+                setText(""+mValue);
+                break;
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        if(changed)
+        {
+            setTextSize(TypedValue.COMPLEX_UNIT_PX,(float) ((bottom-top)*0.7));
+        }
+        super.onLayout(changed, left, top, right, bottom);
+    }
+}

+ 266 - 0
app/src/main/java/fanch/multijeu/sudoku/view/SudokuOverlay.java

@@ -0,0 +1,266 @@
+package fanch.multijeu.sudoku.view;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.numbers.widget.GridLayout;
+import fanch.multijeu.sudoku.listener.SudokuDriver;
+
+public class SudokuOverlay extends ViewGroup implements View.OnClickListener {
+
+    protected static final int BACKGROUND_COLOR=0x80000000;
+    protected static final int BUTTON_OK_COLOR=0xff00afaf;
+    protected static final int BUTTON_ERROR_COLOR=0xffff0000;
+    protected static final int BUTTON_CLEAR_COLOR =0xffff0000;
+    protected static final int ALL[]={0, 1,2,3,4,5,6,7,8,9};
+    public static final int    CLEAR_CODE=0;
+
+    public enum SelectMode {
+        NO_ASSIST,
+        DISABLE_BUTTON,
+        COLOR_BUTTON
+    }
+
+    public static class Attrs extends Saveable {
+        public SelectMode select=SelectMode.NO_ASSIST;
+    }
+
+
+
+
+    protected GridLayout mGrid;
+    protected Button     mButtons[];
+    protected int mGridWidth=3*66;
+    protected int mGridHeight=4*66;
+    protected int mLastClickX=0;
+    protected int mLastClickY=0;
+
+    protected SudokuCase mCurrentCase=null;
+    protected int        mCurrentX=-1;
+    protected int        mCurrentY=-1;
+
+    private SudokuDriver mListener;
+    protected Attrs      mAttrs = new Attrs();
+
+
+
+    public SudokuOverlay(Context context) {
+        super(context);
+        init();
+    }
+
+    public SudokuOverlay(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public void init()
+    {
+        setVisibility(GONE);
+        setBackgroundColor(BACKGROUND_COLOR);
+        mGrid=new GridLayout(getContext());
+        mGrid.setGridDimension(3,4);
+        addView(mGrid);
+
+        mButtons=new Button[10];
+        for(int i=0; i<9; i++){
+            mButtons[i+1] = createButton(i);
+            mButtons[i+1].setText(""+(i+1));
+            mGrid.addView(mButtons[i+1]);
+        }
+
+        mButtons[CLEAR_CODE] = createButton(9);
+        mButtons[CLEAR_CODE].setText("C");
+        mGrid.addView(mButtons[CLEAR_CODE]);
+
+        setOnClickListener(this);
+
+    }
+
+    private Button createButton(int x) {
+        Button b = new Button(getContext());
+        b.setBackgroundColor(0xff00afaf);
+        b.setPadding(0,0,0,0);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            b.setTextAlignment(TEXT_ALIGNMENT_CENTER);
+        }
+        b.setLayoutParams(new GridLayout.LayoutParams(x%3, x/3));
+        b.setOnClickListener(this);
+        return b;
+    }
+
+
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int W = r-l;
+        final int H = b-t;
+
+        double pcx = 0.5;
+        double pcy = 0.5;
+
+        mGridWidth=(int)(((float)getWidth())/3);
+        mGridHeight= (int) (mGridWidth*1.333);
+
+        int x =mLastClickX;
+        int y = mLastClickY;
+        final int dleft = (int) (mGridWidth*pcx);
+        final int dright = (int) (mGridWidth*(1-pcx));
+        final int dtop = (int) (mGridHeight*pcy);
+        final int dbottom = (int) (mGridHeight*(1-pcy));
+
+
+
+        if(x-dleft<0) x=dleft;
+        if(y-dtop<0) y=dtop;
+        if(x+dright>=W) x=W-dright;
+        if(y+dbottom>=H) y= H-dbottom;
+        mGrid.layout(x-dleft,y-dtop,
+                x-dleft+mGridWidth,
+                y-dtop+mGridHeight);
+
+    }
+
+    public void click(SudokuCase ca, int x, int y)
+    {
+        click(ca, x, y, ALL);
+    }
+
+    public void click(SudokuCase ca, int x, int y, int possible[])
+    {
+        if(mCurrentCase!=null)
+        {
+            cancel();
+            return;
+        }
+
+        mCurrentCase=ca;
+        mLastClickX=ca.getLeft();
+        mLastClickY=ca.getTop();
+        mCurrentX=x;
+        mCurrentY=y;
+
+        setAlpha(0);
+        setVisibility(VISIBLE);
+        setAnimAlpha(this, 1);
+        for(int i=1; i<10; i++)
+        {
+            boolean present = false;
+            for(int j=0; j<possible.length; j++)
+                if(possible[j]==i){ present=true; break;}
+            if(i!=CLEAR_CODE) {
+                if (mAttrs.select == SelectMode.NO_ASSIST) {
+                    showButton(mButtons[i]);
+                    mButtons[i].setBackgroundColor(BUTTON_OK_COLOR);
+                } else if (mAttrs.select == SelectMode.DISABLE_BUTTON) {
+                    if (present) {
+                        showButton(mButtons[i]);
+                        mButtons[i].setBackgroundColor(BUTTON_OK_COLOR);
+                    } else {
+                        hideButton(mButtons[i]);
+                        mButtons[i].setBackgroundColor(BUTTON_OK_COLOR);
+                    }
+                } else if (mAttrs.select == SelectMode.COLOR_BUTTON) {
+                    showButton(mButtons[i]);
+                    if (present) {
+                        mButtons[i].setBackgroundColor(BUTTON_OK_COLOR);
+                    } else {
+                        mButtons[i].setBackgroundColor(BUTTON_ERROR_COLOR);
+                    }
+                }
+            }else{
+                if(present)
+                {
+                    showButton(mButtons[i]);
+                    mButtons[CLEAR_CODE].setBackgroundColor(BUTTON_CLEAR_COLOR);
+                }else{
+                    hideButton(mButtons[i]);
+                }
+
+            }
+        }
+
+    }
+
+    public void cancel()
+    {
+        final SudokuOverlay over = this;
+        ObjectAnimator animation = ObjectAnimator.ofFloat(this, "alpha", 0);
+        animation.setDuration(500);
+        animation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                over.setVisibility(GONE);
+
+                mCurrentCase=null;
+                mCurrentX=0;
+                mCurrentY=0;
+            }
+        });
+        animation.start();
+    }
+
+
+
+    @Override
+    public void onClick(View v) {
+        if(mCurrentCase==null) return;
+        if(v instanceof Button) {
+            final int id = viewToId(v);
+            mCurrentCase.setValue(id);
+            if(mListener!=null) {
+                mListener.setValue(mCurrentX, mCurrentY, id);
+                mListener.valueChanged(mCurrentX, mCurrentY, id);
+            }
+            cancel();
+        }else if(v==this)
+        {
+            cancel();
+        }
+
+    }
+
+    public void setListener(SudokuDriver mListener) {
+        this.mListener = mListener;
+    }
+
+
+    public void showButton(Button b)
+    {
+        setAnimAlpha(b,1);
+        b.setEnabled(true);
+    }
+
+    public void hideButton(Button b)
+    {
+        setAnimAlpha(b, (float) 0.4);
+        b.setEnabled(false);
+    }
+
+    public void setAnimAlpha(View b, float f)
+    {
+        ObjectAnimator animation = ObjectAnimator.ofFloat(b, "alpha", f);
+        animation.setDuration(500);
+        animation.start();
+    }
+
+    private int viewToId(View v)
+    {
+        for(int i=0; i<mButtons.length; i++)
+            if(v==mButtons[i]) return i;
+        return -1;
+    }
+
+    public void setAttrs(Attrs mAttrs) {
+        this.mAttrs = mAttrs;
+    }
+}

+ 362 - 0
app/src/main/java/fanch/multijeu/sudoku/view/SudokuView.java

@@ -0,0 +1,362 @@
+package fanch.multijeu.sudoku.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+import fanch.multijeu.common.core.Saveable;
+import fanch.multijeu.common.core.SerializedObject;
+import fanch.multijeu.common.utils.IntUtils;
+import fanch.multijeu.common.view.Line;
+import fanch.multijeu.sudoku.data.Sudoku;
+import fanch.multijeu.sudoku.listener.SudokuDriver;
+
+public class SudokuView extends ViewGroup implements View.OnClickListener, SudokuDriver {
+
+    protected static final int BIG_PADDING =5;
+    protected static final int LITTLE_PADDING =2;
+
+
+    protected static final int[][] BUTTONS = {
+        {0,1,2,9,10,11,18,19,20}, {3,4,5,12,13,14,21,22,23}, {6,7,8,15,16,17,24,25,26},
+        {27,28,29,36,37,38,45,46,47}, {30,31,32,39,40,41,48,49,50}, {33,34,35,42,43,44,51,52,53},
+        {54,55,56,63,64,65,72,73,74}, {57,58,59,66,67,68,75,76,77}, {60,61,62,69,70,71,78,79,80}
+    };
+
+    protected static final int LITTLE_PADDINGS[] = {0,1,2,2,3,4,4,5,6, 6};
+    protected static final int BIG_PADDINGS[]    = {1,1,1,2,2,2,3,3,3, 4};
+
+    public static class Attrs extends Saveable {
+        public boolean rtCorrect=true;
+        public boolean squareGrid=true;
+    }
+
+    public static interface SudokuListener {
+        void onClick(SudokuCase c, int x, int y);
+        void onSudokuDone(boolean ok);
+    }
+
+    protected SudokuCase[] mButtons;
+    protected SudokuListener mListener;
+    protected Line[]  mHLines = new Line[10];
+    protected Line[]  mVLines = new Line[10];
+    protected Line[]  mBigLines = new Line[4];
+    protected Sudoku  mSudoku;
+    protected Sudoku  mSudokuDefault;
+    protected Sudoku  mSudokuResolved;
+
+    protected Attrs   mAttrs = new Attrs();
+    protected boolean mCheat = false;
+    protected int     mMaxIndice = 3;
+    protected int     mCurrentIndice=0;
+
+    public SudokuView(Context context) {
+        super(context);
+        init();
+    }
+
+    public SudokuView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    private SudokuCase createButton()
+    {
+        SudokuCase c = new SudokuCase(getContext());
+        c.setOnClickListener(this);
+        return c;
+    }
+
+    public Attrs getAttrs() {
+        return mAttrs;
+    }
+
+    public void setAttrs(Attrs mAttrs) {
+        this.mAttrs = mAttrs;
+    }
+
+    private Line createLine()
+    {
+        Line l = new Line(getContext());
+        return l;
+    }
+
+    private void init() {
+        mButtons=new SudokuCase[81];
+
+        for(int i=0; i<mHLines.length; i++)
+        {
+            mHLines[i]=createLine();
+            mVLines[i]=createLine();
+            addView(mHLines[i]);
+            addView(mVLines[i]);
+        }
+
+        for(int i=0; i<mBigLines.length; i++)
+        {
+            mBigLines[i]=createLine();
+            addView(mBigLines[i]);
+        }
+
+        for(int i=0; i<81; i++) {
+            mButtons[i] = createButton();
+            addView(mButtons[i]);
+        }
+    }
+
+    public void setSudoku(Sudoku sud)
+    {
+        mSudoku =sud;
+        mSudokuDefault=sud.copy();
+        mSudokuResolved= sud.copy();
+
+        boolean ok = mSudokuResolved.resolve();
+
+        if(!ok)
+        {
+            Log.e("--------", "Sudoku="+mSudokuDefault.toString());
+            throw new RuntimeException("Unable to resolve Sudoku");
+        }
+
+        mCheat=false;
+        mCurrentIndice=0;
+
+        for(int i=0; i<81; i++) {
+            final int x = mSudoku.get(i);
+            final int def = mSudokuDefault.get(i);
+            mButtons[i].setValue(x);
+            if(def>0) mButtons[i].setState(SudokuCase.CaseState.LOCKED);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int w = MeasureSpec.getSize(widthMeasureSpec);
+        int h = MeasureSpec.getSize(heightMeasureSpec);
+        if(mAttrs.squareGrid)
+        {
+            if(w<h) h=w;
+            if(h<w) w=h;
+        }
+
+        setMeasuredDimension(w,h);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int _W = r-l- BIG_PADDING *4-6*LITTLE_PADDING;
+        final int _H = b-t- BIG_PADDING *4-6*LITTLE_PADDING;
+        final int subWidth = _W/3;
+        final int subHeight = _H/3;
+        final float fsubWidth = (float) (_W/3.0);
+        final float fsubHeight = (float) (_H/3.0);
+
+        //draw lines
+        for(int i=0; i<10; i++)
+            drawLittleLine(i, (float) (_W/9.0), (float) (_H/9.0), r-l-LITTLE_PADDING, b-t-LITTLE_PADDING);
+
+        //draw case
+        for(int i=0; i<9; i++)
+        {
+            subLayout(i, (float) (_W/9.0), (float) (_H/9.0));
+
+        }
+    }
+
+    protected void drawLittleLine(int i, float w, float h, int W, int H)
+    {
+        final float x = LITTLE_PADDINGS[i]*LITTLE_PADDING+BIG_PADDINGS[i]*BIG_PADDING+i*w;
+        final float y = LITTLE_PADDINGS[i]*LITTLE_PADDING+BIG_PADDINGS[i]*BIG_PADDING+i*h;
+        int d;
+        if(i==0 || i==3 || i==6 || i==9)
+        {
+            d=BIG_PADDING;
+        }else{
+            d=LITTLE_PADDING;
+        }
+        childFrame(mHLines[i], 0, y-d, W, d);
+        childFrame(mVLines[i], x-d, 0, d, H);
+    }
+
+
+
+    protected void subLayout(int n, float w, float h)
+    {
+        final int indices[] = BUTTONS[n];
+        final float cw = w/3;
+        final float ch = h/3;
+
+        final int dx = n%3;
+        final int dy = n/3;
+        for(int j=0; j<3; j++)
+        {
+            for(int i=0; i<3; i++)
+            {
+                final int di = dx*3+i;
+                final int dj = dy*3+j;
+                final float xx = LITTLE_PADDINGS[di]*LITTLE_PADDING+
+                        BIG_PADDINGS[di]*BIG_PADDING+di*w;
+                final float yy = LITTLE_PADDINGS[dj]*LITTLE_PADDING+
+                        BIG_PADDINGS[dj]*BIG_PADDING+dj*h;
+
+                childFrame(indices[j*3+i],xx,yy,w,h);
+            }
+        }
+    }
+
+    protected void childFrame(int i, float x, float y, float w, float h)
+    {
+        mButtons[i].layout((int)x,(int)y,(int)(x+w),(int)(y+h));
+    }
+
+    protected void childFrame(View i, float x, float y, float w, float h)
+    {
+        i.layout((int)x,(int)y,(int)(x+w),(int)(y+h));
+    }
+
+    protected SudokuCase button(int i, int j)
+    {
+        return button(j*9+i);
+    }
+
+    protected SudokuCase button(int i)
+    {
+        return mButtons[i];
+    }
+
+    public void setListener(SudokuListener mListener) {
+        this.mListener = mListener;
+    }
+
+
+    @Override
+    public void onClick(View v) {
+        final SudokuCase c = (SudokuCase) v;
+        final int id = findId(c);
+        final int x = id%9;
+        final int y = id/9;
+
+        if(((SudokuCase) v).mState!= SudokuCase.CaseState.LOCKED) {
+            if (mListener != null) {
+                mListener.onClick(c, x, y);
+            }
+        }
+    }
+
+    public int findId(SudokuCase v)
+    {
+        for(int i=0; i<mButtons.length; i++)
+            if(mButtons[i]==v) return i;
+        return -1;
+    }
+
+    @Override
+    public void setValue(int x, int y, int newVal) {
+        button(x,y).setValue(newVal);
+        mSudoku.set(x, y, (byte) newVal);
+    }
+
+    @Override
+    public boolean isError(int x, int y, int val) {
+        return !mSudoku.checkCell(x,y, (byte) val);
+    }
+
+    public boolean isError(int x, int y) {
+        return !mSudoku.checkCell(x,y, (byte) button(x,y).getValue());
+    }
+
+    @Override
+    public void valueChanged(int x, int y, int val) {
+        int count =0;
+        for(int i=0; i<81; i++)
+            if(button(i).getValue()!=0)
+                count++;
+
+        if(count==81){
+            int nerr=0;
+            for(int i=0; i<81; i++)
+            {
+                if(button(i).getValue()!= mSudokuResolved.get(i))
+                {
+                    button(i).setState(SudokuCase.CaseState.ERROR);
+                    nerr++;
+                }
+            }
+
+            if(mListener!=null)
+            {
+                mListener.onSudokuDone(nerr==0);
+            }
+        }
+    }
+
+    public int[] possibleValues(int x, int y)
+    {
+        int out[];
+        ArrayList<Integer> l = new ArrayList();
+        for(int i=1; i<=9; i++)
+        {
+            if(!isError(x, y, i))
+                l.add(i);
+        }
+        out=new int[l.size()];
+        for(int i=0; i<l.size(); i++)
+            out[i]=l.get(i);
+        return out;
+    }
+
+
+
+    public void nextIndice()
+    {
+        if(mMaxIndice>0 && mCurrentIndice+1<=mMaxIndice) {
+            mCurrentIndice++;
+            int n = 0;
+            for (int i = 0; i < mButtons.length; i++)
+                if (button(i).getValue() == 0)
+                    n++;
+            if (n == 0) return;
+            int x = IntUtils.rand(0, n - 1);
+
+            for (int i = 0; i < mButtons.length; i++)
+                if (button(i).getValue() == 0) {
+                    n--;
+                    if (n == 0) {
+                        button(i).setValue(mSudokuResolved.get(i));
+                        button(i).setState(SudokuCase.CaseState.INDICE);
+                        valueChanged(i % 9, i / 9, mSudokuResolved.get(i));
+                    }
+                }
+        }
+
+        if(mMaxIndice>0 && mCurrentIndice==mMaxIndice)
+        {
+            //hide button
+        }
+    }
+
+    public void resolve()
+    {
+        mCheat=true;
+        for(int i=0; i<mButtons.length; i++)
+        {
+            if(button(i).getValue()==0){
+                button(i).setValue(mSudokuResolved.get(i));
+                button(i).setState(SudokuCase.CaseState.INDICE);
+                valueChanged(i%9, i/9, mSudokuResolved.get(i));
+            }else if(button(i).getState()== SudokuCase.CaseState.VALUE)
+            {
+                if(mSudokuResolved.get(i)!=button(i).getValue())
+                {
+                    button(i).setState(SudokuCase.CaseState.ERROR);
+                }
+            }
+        }
+    }
+
+
+}

+ 1 - 0
app/src/main/res/anim/shake.xml

@@ -4,6 +4,7 @@
     android:fromDegrees="-5"
     android:pivotX="50%"
     android:pivotY="50%"
+    android:interpolator="@android:anim/decelerate_interpolator"
     android:repeatCount="1"
     android:repeatMode="reverse"
     android:toDegrees="5" />

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů