commit 3eedf418d4900eaacdcea3cbddf1af134e5c4004
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Tue Feb 21 01:48:11 2023 +0100

    Ivy Solver: implementations of getIndex() and getQuats() remain.

diff --git a/src/main/java/org/distorted/solvers/ImplementedSolversList.java b/src/main/java/org/distorted/solvers/ImplementedSolversList.java
index 27bc98d1..4a4afd15 100644
--- a/src/main/java/org/distorted/solvers/ImplementedSolversList.java
+++ b/src/main/java/org/distorted/solvers/ImplementedSolversList.java
@@ -18,8 +18,8 @@ public enum ImplementedSolversList
 {
   CUBE3_KOCIEMBA (ObjectSignatures.CUBE_3, R.string.solver_cube3_title, R.string.solver_cube3_description, true),
   PYRAMINX_DUO   (ObjectSignatures.PDUO_2, R.string.solver_pduo2_title, R.string.solver_pduo2_description, false),
-  PYRAMINX       (ObjectSignatures.PYRA_3, R.string.solver_pyra3_title, R.string.solver_pyra3_description, false),
   IVY            (ObjectSignatures.IVY_2 , R.string.solver_ivy_title, R.string.solver_ivy_description, false),
+  PYRAMINX       (ObjectSignatures.PYRA_3, R.string.solver_pyra3_title, R.string.solver_pyra3_description, false),
   TOWER          (ObjectSignatures.CU_232, R.string.solver_cu232_title, R.string.solver_cu232_description, false),
   DIAMOND        (ObjectSignatures.DIAM_2, R.string.solver_diam2_title, R.string.solver_diam2_description, false),
   ;
diff --git a/src/main/java/org/distorted/solvers/SolverIvyCube.java b/src/main/java/org/distorted/solvers/SolverIvyCube.java
new file mode 100644
index 00000000..d46e93e9
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverIvyCube.java
@@ -0,0 +1,314 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.ObjectType;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverIvyCube extends SolverTablebase
+{
+  private static final int ERROR_CENTER_O_MISSING = -1;
+  private static final int ERROR_CENTER_W_MISSING = -2;
+  private static final int ERROR_CENTER_G_MISSING = -3;
+  private static final int ERROR_CENTER_Y_MISSING = -4;
+  private static final int ERROR_CENTER_B_MISSING = -5;
+  private static final int ERROR_CENTER_R_MISSING = -6;
+  private static final int ERROR_TWO_CENTERS_SWAP = -7;
+  private static final int ERROR_CORNERS_CANNOT   = -8;
+
+  TablebasesAbstract mSolver;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCentersPresent(int[] centers)
+    {
+    int numCenters = centers.length;
+
+    for(int color=0; color<numCenters; color++)
+      {
+      boolean error = true;
+      int center;
+
+      for(center=0; center<numCenters; center++)
+        if( centers[center]==color )
+          {
+          error = false;
+          break;
+          }
+
+      if( error )
+        {
+        switch(color)
+          {
+          case 0: return ERROR_CENTER_Y_MISSING;
+          case 1: return ERROR_CENTER_W_MISSING;
+          case 2: return ERROR_CENTER_B_MISSING;
+          case 3: return ERROR_CENTER_G_MISSING;
+          case 4: return ERROR_CENTER_R_MISSING;
+          case 5: return ERROR_CENTER_O_MISSING;
+          }
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean checkCommonColor(int[] buffer, int index, int[] c1, int[] c2)
+    {
+    if( c1[0]==c1[1] || c1[0]==c1[2] || c1[1]==c1[2] ) return false;
+    if( c2[0]==c2[1] || c2[0]==c2[2] || c2[1]==c2[2] ) return false;
+
+    if( c1[0]==c2[0] && c1[1]!=c2[1] && c1[1]!=c2[2] && c1[2]!=c2[1] && c1[2]!=c2[2] )
+      {
+      buffer[index] = c1[0];
+      return true;
+      }
+    if( c1[0]==c2[1] && c1[1]!=c2[0] && c1[1]!=c2[2] && c1[2]!=c2[0] && c1[2]!=c2[2] )
+      {
+      buffer[index] = c1[0];
+      return true;
+      }
+    if( c1[0]==c2[2] && c1[1]!=c2[0] && c1[1]!=c2[1] && c1[2]!=c2[0] && c1[2]!=c2[1] )
+      {
+      buffer[index] = c1[0];
+      return true;
+      }
+
+    if( c1[1]==c2[0] && c1[0]!=c2[1] && c1[0]!=c2[2] && c1[2]!=c2[1] && c1[2]!=c2[2] )
+      {
+      buffer[index] = c1[1];
+      return true;
+      }
+    if( c1[1]==c2[1] && c1[0]!=c2[0] && c1[0]!=c2[2] && c1[2]!=c2[0] && c1[2]!=c2[2] )
+      {
+      buffer[index] = c1[1];
+      return true;
+      }
+    if( c1[1]==c2[2] && c1[0]!=c2[0] && c1[0]!=c2[1] && c1[2]!=c2[0] && c1[2]!=c2[1] )
+      {
+      buffer[index] = c1[1];
+      return true;
+      }
+
+    if( c1[2]==c2[0] && c1[1]!=c2[1] && c1[1]!=c2[2] && c1[0]!=c2[1] && c1[0]!=c2[2] )
+      {
+      buffer[index] = c1[2];
+      return true;
+      }
+    if( c1[2]==c2[1] && c1[1]!=c2[0] && c1[1]!=c2[2] && c1[0]!=c2[0] && c1[0]!=c2[2] )
+      {
+      buffer[index] = c1[2];
+      return true;
+      }
+    if( c1[2]==c2[2] && c1[1]!=c2[0] && c1[1]!=c2[1] && c1[0]!=c2[0] && c1[0]!=c2[1] )
+      {
+      buffer[index] = c1[2];
+      return true;
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeFaceColor(int[] buffer, int[][] corners)
+    {
+    boolean success0 = checkCommonColor(buffer,0,corners[0],corners[3]);
+    if( !success0 ) return ERROR_CORNERS_CANNOT;
+    boolean success1 = checkCommonColor(buffer,1,corners[1],corners[2]);
+    if( !success1 ) return ERROR_CORNERS_CANNOT;
+    boolean success2 = checkCommonColor(buffer,2,corners[0],corners[1]);
+    if( !success2 ) return ERROR_CORNERS_CANNOT;
+    boolean success3 = checkCommonColor(buffer,3,corners[2],corners[3]);
+    if( !success3 ) return ERROR_CORNERS_CANNOT;
+    boolean success4 = checkCommonColor(buffer,4,corners[0],corners[2]);
+    if( !success4 ) return ERROR_CORNERS_CANNOT;
+    boolean success5 = checkCommonColor(buffer,5,corners[1],corners[3]);
+    if( !success5 ) return ERROR_CORNERS_CANNOT;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int swaps(int val, int[] buffer, int len)
+    {
+    int ret = 0;
+
+    for(int i=0; i<len; i++)
+      {
+           if( buffer[i] >val ) ret++;
+      else if( buffer[i]==val ) return ret;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean permutationIsEven(int[] buffer)
+    {
+    int len = buffer.length;
+    int numOfSwaps = 0;
+    for(int i=0; i<len; i++) numOfSwaps += swaps(i,buffer,len);
+    return (numOfSwaps%2==0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeCenterColors(int[] buffer, int[] faceColors, int[] centers)
+    {
+    int numCenters = faceColors.length;
+
+    for(int color=0; color<numCenters; color++)
+      for(int center=0; center<numCenters; center++)
+        if( centers[center]==faceColors[color] )
+          {
+          buffer[center]=color;
+          }
+
+    return permutationIsEven(buffer) ? 0 : ERROR_TWO_CENTERS_SWAP;
+    }
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeCornerTwist(int[] corner, int color)
+    {
+    if( corner[0]==color ) return 0;
+    if( corner[1]==color ) return 1;
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeOrder(int[] permutation, int index)
+    {
+    int numOfSmaller = 0;
+    int val = permutation[index];
+
+    for(int i=0; i<index; i++)
+      if( permutation[i]<val ) numOfSmaller++;
+
+    return index-numOfSmaller;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeEvenPermutationNum(int[] permutation)
+    {
+    int n0 = computeOrder(permutation,0);
+    int n1 = computeOrder(permutation,1);
+    int n2 = computeOrder(permutation,2);
+    int n3 = computeOrder(permutation,3);
+
+    return n0 + 6*(n1+ 5*(n2+4*n3));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverIvyCube(Resources res, TwistyObject object)
+    {
+    super(res,object);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners     = new int[4][3];
+    int[] centers       = new int[6];
+    int[] twist         = new int[4];
+    int[] face_colors   = new int[6];
+    int[] center_colors = new int[6];
+
+    for(int i=0; i<4; i++)
+      for(int j=0; j<3; j++)
+        corners[i][j] = object.getCubitFaceStickerIndex(i,j);
+
+    for(int i=0; i<6; i++)
+      centers[i] = object.getCubitFaceStickerIndex(i+4,0) - 6;
+
+/*
+for(int i=0; i<4; i++)
+  {
+  android.util.Log.e("D", "corner "+i+" : "+corners[i][0]+" "+corners[i][1]+" "+corners[i][2]);
+  }
+for(int i=0; i<6; i++)
+  {
+  android.util.Log.e("D", "center "+i+" : "+centers[i]);
+  }
+*/
+    int result1 = checkAllCentersPresent(centers);
+    if( result1<0 ) return result1;
+
+    int result2 = computeFaceColor(face_colors,corners);
+    if( result2<0 ) return result2;
+
+    int result3 = computeCenterColors(center_colors, face_colors, centers);
+    if( result3<0 ) return result3;
+
+    twist[0] = computeCornerTwist(corners[0],face_colors[4]);
+    twist[1] = computeCornerTwist(corners[1],face_colors[5]);
+    twist[2] = computeCornerTwist(corners[2],face_colors[4]);
+    twist[3] = computeCornerTwist(corners[3],face_colors[5]);
+
+    int perm_num = computeEvenPermutationNum(center_colors);
+
+//  android.util.Log.e("D", "twist : "+twist[0]+" "+twist[1]+" "+twist[2]+" "+twist[3]);
+
+    return perm_num + 360*(twist[0]+ 3*(twist[1]+ 3*(twist[2]+ 3*twist[3])));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CENTER_W_MISSING  : String colorW = res.getString(R.string.color_white2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorW);
+      case ERROR_CENTER_O_MISSING  : String colorO = res.getString(R.string.color_orange2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorO);
+      case ERROR_CENTER_G_MISSING  : String colorG = res.getString(R.string.color_green2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorG);
+      case ERROR_CENTER_Y_MISSING  : String colorY = res.getString(R.string.color_yellow2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorY);
+      case ERROR_CENTER_B_MISSING  : String colorB = res.getString(R.string.color_blue2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorB);
+      case ERROR_CENTER_R_MISSING  : String colorR = res.getString(R.string.color_red2);
+                                     return res.getString(R.string.solver_generic_missing_center,colorR);
+      case ERROR_TWO_CENTERS_SWAP  : return res.getString(R.string.solver_generic_two_centers);
+      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, Resources res)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(res,ObjectType.IVY_2);
+      }
+
+    return mSolver!=null ? mSolver.solution(index) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverMain.java b/src/main/java/org/distorted/solvers/SolverMain.java
index 2b1e4a06..579d022d 100644
--- a/src/main/java/org/distorted/solvers/SolverMain.java
+++ b/src/main/java/org/distorted/solvers/SolverMain.java
@@ -81,6 +81,11 @@ public class SolverMain implements Runnable
       SolverTablebase solver = new SolverPyraminxDuo(mRes,mObject);
       solver.solve(screen);
       }
+    else if( mSignature==ObjectSignatures.IVY_2 )
+      {
+      SolverTablebase solver = new SolverIvyCube(mRes,mObject);
+      solver.solve(screen);
+      }
     else
       {
       screen.displayErrorDialog(mRes.getString(R.string.solver_generic_not_implemented));
