commit 420eb96dd0abfb25b848f799c9554e16841196ba
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Feb 13 02:24:37 2023 +0100

    Beginnings of the Pyraminx Duo solver.

diff --git a/src/main/java/org/distorted/solvers/SolverCube3.java b/src/main/java/org/distorted/solvers/SolverCube3.java
new file mode 100644
index 00000000..9d401aaf
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverCube3.java
@@ -0,0 +1,217 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.solvers;
+
+import android.content.res.Resources;
+
+import org.distorted.main.R;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.screens.RubikScreenSolver;
+import org.distorted.solvers.cube3.SolverSearch;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverCube3
+{
+  private final Resources mRes;
+  private final TwistyObject mObject;
+  private int mColorID;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int mapCubitToFace(int cubit, int face)
+    {
+    if( cubit<8 )
+      {
+      switch(face)
+        {
+        case 0: return 1;
+        case 1: if( cubit==2 ) return 5;
+                if( cubit==1 ) return 3;
+                return 1;
+        case 2: return cubit==7 ? 5 : 3;
+        case 3: if( cubit==1 ) return 1;
+                return cubit==4 ? 5 : 3;
+        case 4: return cubit==7 ? 3 : 5;
+        case 5: if( cubit==2 ) return 1;
+                if( cubit==4 ) return 3;
+                return 5;
+        }
+      }
+
+    if( cubit>19 ) return 4;
+
+    switch(face)
+      {
+      case 0: return cubit==15 || cubit==18 ? 3 : 5;
+      case 1: return cubit==13 || cubit==16 ? 3 : 5;
+      case 2: return cubit==10              ? 5 : 3;
+      case 3: return cubit== 8              ? 3 : 5;
+      case 4: return cubit== 9              ? 3 : 5;
+      case 5: return cubit== 8              ? 5 : 3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkPosition(String position)
+    {
+    int[] numColors = new int[6];
+    int len = position.length();
+
+    for(int i=0; i<len; i++)
+      {
+      char ch = position.charAt(i);
+
+      switch(ch)
+        {
+        case 'R': numColors[0]++; break;
+        case 'L': numColors[1]++; break;
+        case 'U': numColors[2]++; break;
+        case 'D': numColors[3]++; break;
+        case 'F': numColors[4]++; break;
+        case 'B': numColors[5]++; break;
+        }
+      }
+
+    if( numColors[0]<9 ) { mColorID = R.string.color_yellow; return numColors[0]; }
+    if( numColors[1]<9 ) { mColorID = R.string.color_white ; return numColors[1]; }
+    if( numColors[2]<9 ) { mColorID = R.string.color_blue  ; return numColors[2]; }
+    if( numColors[3]<9 ) { mColorID = R.string.color_green ; return numColors[3]; }
+    if( numColors[4]<9 ) { mColorID = R.string.color_red   ; return numColors[4]; }
+    if( numColors[5]<9 ) { mColorID = R.string.color_orange; return numColors[5]; }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// order: Up --> Right --> Front --> Down --> Left --> Back
+// (because the first implemented Solver - the two-phase Cube3 one - expects such order)
+//
+// Solved 3x3x3 Cube maps to "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB"
+
+  private String preparePosition()
+    {
+    StringBuilder objectString = new StringBuilder();
+
+    final int R = 0;
+    final int L = 1;
+    final int U = 2;
+    final int D = 3;
+    final int F = 4;
+    final int B = 5;
+
+    // 'I' - interior, theoretically can happen
+    final char[] FACE_NAMES = { 'R', 'L', 'U', 'D', 'F', 'B', 'I'};
+
+    final int[] U_INDEX = { 2,10, 6,17,22,19, 3,11, 7};
+    final int[] R_INDEX = { 7,19, 6,15,20,14, 5,18, 4};
+    final int[] F_INDEX = { 3,11, 7,13,24,15, 1, 9, 5};
+    final int[] D_INDEX = { 1, 9, 5,16,23,18, 0, 8, 4};
+    final int[] L_INDEX = { 2,17, 3,12,21,13, 0,16, 1};
+    final int[] B_INDEX = { 6,10, 2,14,25,12, 4, 8, 0};
+
+    for(int i=0; i<9; i++)
+      {
+      int face = mapCubitToFace(U_INDEX[i],U);
+      int color = mObject.getCubitFaceColorIndex(U_INDEX[i], face);
+      objectString.append(FACE_NAMES[color]);
+      }
+    for(int i=0; i<9; i++)
+      {
+      int face = mapCubitToFace(R_INDEX[i],R);
+      int color = mObject.getCubitFaceColorIndex(R_INDEX[i], face);
+      objectString.append(FACE_NAMES[color]);
+      }
+    for(int i=0; i<9; i++)
+      {
+      int face = mapCubitToFace(F_INDEX[i],F);
+      int color = mObject.getCubitFaceColorIndex(F_INDEX[i], face);
+      objectString.append(FACE_NAMES[color]);
+      }
+    for(int i=0; i<9; i++)
+      {
+      int face = mapCubitToFace(D_INDEX[i],D);
+      int color = mObject.getCubitFaceColorIndex(D_INDEX[i], face);
+      objectString.append(FACE_NAMES[color]);
+      }
+    for(int i=0; i<9; i++)
+      {
+      int face = mapCubitToFace(L_INDEX[i],L);
+      int color = mObject.getCubitFaceColorIndex(L_INDEX[i], face);
+      objectString.append(FACE_NAMES[color]);
+      }
+    for(int i=0; i<9; i++)
+      {
+      int face = mapCubitToFace(B_INDEX[i],B);
+      int color = mObject.getCubitFaceColorIndex(B_INDEX[i], face);
+      objectString.append(FACE_NAMES[color]);
+      }
+
+    return objectString.toString();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverCube3(Resources res, TwistyObject object)
+    {
+    mRes   = res;
+    mObject= object;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void solve(RubikScreenSolver solver)
+    {
+    String result;
+
+    SolverSearch.prepare(mRes);
+    String objectPosition = preparePosition();
+    int check = checkPosition(objectPosition);
+
+    if( check<0 )
+      {
+      result = SolverSearch.solution(objectPosition, 24, 20);
+
+      if (result.contains("Error"))
+        {
+        switch (result.charAt(result.length() - 1))
+          {
+          case '1': result = mRes.getString(R.string.solver_cube3_error1); break;
+          case '2': result = mRes.getString(R.string.solver_cube3_error2); break;
+          case '3': result = mRes.getString(R.string.solver_cube3_error3); break;
+          case '4': result = mRes.getString(R.string.solver_cube3_error4); break;
+          case '5': result = mRes.getString(R.string.solver_cube3_error5); break;
+          case '6': result = mRes.getString(R.string.solver_cube3_error6); break;
+          case '7': result = mRes.getString(R.string.solver_cube3_error7); break;
+          case '8': result = mRes.getString(R.string.solver_cube3_error8); break;
+          case '9': result = mRes.getString(R.string.solver_cube3_error9); break;
+          }
+
+        solver.displayErrorDialog(result);
+        }
+      else
+        {
+        solver.setSolved(result);
+        }
+      }
+    else
+      {
+      String color = mRes.getString(mColorID);
+      result = mRes.getString(R.string.solver_cube3_error1,check,color);
+      solver.displayErrorDialog(result);
+      }
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverMain.java b/src/main/java/org/distorted/solvers/SolverMain.java
index 652c95f7..da26b7eb 100644
--- a/src/main/java/org/distorted/solvers/SolverMain.java
+++ b/src/main/java/org/distorted/solvers/SolverMain.java
@@ -17,16 +17,15 @@ import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.main.R;
 import org.distorted.screens.ScreenList;
 import org.distorted.screens.RubikScreenSolver;
-import org.distorted.solvers.cube3.SolverSearch;
+import org.distorted.solvers.pduo.SolverPyraminxDuo;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class SolverMain implements Runnable
 {
   private final Resources mRes;
-  private final TwistyObject mObject;
   private final long mSignature;
-  private int mColorID;
+  private final TwistyObject mObject;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -59,186 +58,6 @@ public class SolverMain implements Runnable
     return -1;
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void solveCube3(RubikScreenSolver solver)
-    {
-    String result;
-
-    SolverSearch.prepare(mRes);
-    String objectPosition = prepareCube3position();
-    int check = checkPosition(objectPosition);
-
-    if( check<0 )
-      {
-      result = SolverSearch.solution(objectPosition, 24, 20);
-
-      if (result.contains("Error"))
-        {
-        switch (result.charAt(result.length() - 1))
-          {
-          case '1': result = mRes.getString(R.string.solver_cube3_error1); break;
-          case '2': result = mRes.getString(R.string.solver_cube3_error2); break;
-          case '3': result = mRes.getString(R.string.solver_cube3_error3); break;
-          case '4': result = mRes.getString(R.string.solver_cube3_error4); break;
-          case '5': result = mRes.getString(R.string.solver_cube3_error5); break;
-          case '6': result = mRes.getString(R.string.solver_cube3_error6); break;
-          case '7': result = mRes.getString(R.string.solver_cube3_error7); break;
-          case '8': result = mRes.getString(R.string.solver_cube3_error8); break;
-          case '9': result = mRes.getString(R.string.solver_cube3_error9); break;
-          }
-
-        solver.displayErrorDialog(result);
-        }
-      else
-        {
-        solver.setSolved(result);
-        }
-      }
-    else
-      {
-      String color = mRes.getString(mColorID);
-      result = mRes.getString(R.string.solver_cube3_error1,check,color);
-      solver.displayErrorDialog(result);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int mapCubitToFace(int cubit, int face)
-    {
-    if( cubit<8 )
-      {
-      switch(face)
-        {
-        case 0: return 1;
-        case 1: if( cubit==2 ) return 5;
-                if( cubit==1 ) return 3;
-                return 1;
-        case 2: return cubit==7 ? 5 : 3;
-        case 3: if( cubit==1 ) return 1;
-                return cubit==4 ? 5 : 3;
-        case 4: return cubit==7 ? 3 : 5;
-        case 5: if( cubit==2 ) return 1;
-                if( cubit==4 ) return 3;
-                return 5;
-        }
-      }
-
-    if( cubit>19 ) return 4;
-
-    switch(face)
-      {
-      case 0: return cubit==15 || cubit==18 ? 3 : 5;
-      case 1: return cubit==13 || cubit==16 ? 3 : 5;
-      case 2: return cubit==10              ? 5 : 3;
-      case 3: return cubit== 8              ? 3 : 5;
-      case 4: return cubit== 9              ? 3 : 5;
-      case 5: return cubit== 8              ? 5 : 3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkPosition(String position)
-    {
-    int[] numColors = new int[6];
-    int len = position.length();
-
-    for(int i=0; i<len; i++)
-      {
-      char ch = position.charAt(i);
-
-      switch(ch)
-        {
-        case 'R': numColors[0]++; break;
-        case 'L': numColors[1]++; break;
-        case 'U': numColors[2]++; break;
-        case 'D': numColors[3]++; break;
-        case 'F': numColors[4]++; break;
-        case 'B': numColors[5]++; break;
-        }
-      }
-
-    if( numColors[0]<9 ) { mColorID = R.string.color_yellow; return numColors[0]; }
-    if( numColors[1]<9 ) { mColorID = R.string.color_white ; return numColors[1]; }
-    if( numColors[2]<9 ) { mColorID = R.string.color_blue  ; return numColors[2]; }
-    if( numColors[3]<9 ) { mColorID = R.string.color_green ; return numColors[3]; }
-    if( numColors[4]<9 ) { mColorID = R.string.color_red   ; return numColors[4]; }
-    if( numColors[5]<9 ) { mColorID = R.string.color_orange; return numColors[5]; }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// order: Up --> Right --> Front --> Down --> Left --> Back
-// (because the first implemented Solver - the two-phase Cube3 one - expects such order)
-//
-// Solved 3x3x3 Cube maps to "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB"
-
-  private String prepareCube3position()
-    {
-    StringBuilder objectString = new StringBuilder();
-
-    final int R = 0;
-    final int L = 1;
-    final int U = 2;
-    final int D = 3;
-    final int F = 4;
-    final int B = 5;
-
-    // 'I' - interior, theoretically can happen
-    final char[] FACE_NAMES = { 'R', 'L', 'U', 'D', 'F', 'B', 'I'};
-
-    final int[] U_INDEX = { 2,10, 6,17,22,19, 3,11, 7};
-    final int[] R_INDEX = { 7,19, 6,15,20,14, 5,18, 4};
-    final int[] F_INDEX = { 3,11, 7,13,24,15, 1, 9, 5};
-    final int[] D_INDEX = { 1, 9, 5,16,23,18, 0, 8, 4};
-    final int[] L_INDEX = { 2,17, 3,12,21,13, 0,16, 1};
-    final int[] B_INDEX = { 6,10, 2,14,25,12, 4, 8, 0};
-
-    for(int i=0; i<9; i++)
-      {
-      int face = mapCubitToFace(U_INDEX[i],U);
-      int color = mObject.getCubitFaceColorIndex(U_INDEX[i], face);
-      objectString.append(FACE_NAMES[color]);
-      }
-    for(int i=0; i<9; i++)
-      {
-      int face = mapCubitToFace(R_INDEX[i],R);
-      int color = mObject.getCubitFaceColorIndex(R_INDEX[i], face);
-      objectString.append(FACE_NAMES[color]);
-      }
-    for(int i=0; i<9; i++)
-      {
-      int face = mapCubitToFace(F_INDEX[i],F);
-      int color = mObject.getCubitFaceColorIndex(F_INDEX[i], face);
-      objectString.append(FACE_NAMES[color]);
-      }
-    for(int i=0; i<9; i++)
-      {
-      int face = mapCubitToFace(D_INDEX[i],D);
-      int color = mObject.getCubitFaceColorIndex(D_INDEX[i], face);
-      objectString.append(FACE_NAMES[color]);
-      }
-    for(int i=0; i<9; i++)
-      {
-      int face = mapCubitToFace(L_INDEX[i],L);
-      int color = mObject.getCubitFaceColorIndex(L_INDEX[i], face);
-      objectString.append(FACE_NAMES[color]);
-      }
-    for(int i=0; i<9; i++)
-      {
-      int face = mapCubitToFace(B_INDEX[i],B);
-      int color = mObject.getCubitFaceColorIndex(B_INDEX[i], face);
-      objectString.append(FACE_NAMES[color]);
-      }
-
-    return objectString.toString();
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void start()
@@ -251,15 +70,21 @@ public class SolverMain implements Runnable
 
   public void run()
     {
-    RubikScreenSolver solver = (RubikScreenSolver) ScreenList.SVER.getScreenClass();
+    RubikScreenSolver screen = (RubikScreenSolver) ScreenList.SVER.getScreenClass();
 
     if( mSignature==ObjectSignatures.CUBE_3 )
       {
-      solveCube3(solver);
+      SolverCube3 solver = new SolverCube3(mRes,mObject);
+      solver.solve(screen);
+      }
+    if( mSignature==ObjectSignatures.PDUO_2 )
+      {
+      SolverTablebase solver = new SolverPyraminxDuo(mRes,mObject);
+      solver.solve(screen);
       }
     else
       {
-      solver.displayErrorDialog(mRes.getString(R.string.solver_generic_error1));
+      screen.displayErrorDialog(mRes.getString(R.string.solver_generic_error1));
       }
     }
 }  
diff --git a/src/main/java/org/distorted/solvers/SolverTablebase.java b/src/main/java/org/distorted/solvers/SolverTablebase.java
new file mode 100644
index 00000000..ead11c2b
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebase.java
@@ -0,0 +1,60 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.solvers;
+
+import android.content.res.Resources;
+
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.screens.RubikScreenSolver;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class SolverTablebase
+{
+  private final Resources mRes;
+  private final TwistyObject mObject;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public abstract String constructError();
+  public abstract int[] prepareQuats(TwistyObject object);
+  public abstract String solution(int[] quats, Resources res);
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebase(Resources res, TwistyObject object)
+    {
+    mRes   = res;
+    mObject= object;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void solve(RubikScreenSolver solver)
+    {
+    String result;
+
+    int[] quats = prepareQuats(mObject);
+
+    if( quats!=null )
+      {
+      result = solution(quats,mRes);
+      solver.setSolved(result);
+      }
+    else
+      {
+      String error = constructError();
+      solver.displayErrorDialog(error);
+      }
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/pduo/SolverPyraminxDuo.java b/src/main/java/org/distorted/solvers/pduo/SolverPyraminxDuo.java
new file mode 100644
index 00000000..94601dd8
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/pduo/SolverPyraminxDuo.java
@@ -0,0 +1,51 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.solvers.pduo;
+
+import android.content.res.Resources;
+
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.solvers.SolverTablebase;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverPyraminxDuo extends SolverTablebase
+{
+  private int mErrorID;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverPyraminxDuo(Resources res, TwistyObject object)
+    {
+    super(res,object);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+  public String constructError()
+    {
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+  public int[] prepareQuats(TwistyObject object)
+    {
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+  public String solution(int[] quats, Resources res)
+    {
+    return null;
+    }
+}  
+
