commit 1d1f9ccfe10f770ee0a82616c5c52d5ccb9976bc
Author: leszek <leszek@koltunski.pl>
Date:   Sat Dec 21 00:46:39 2024 +0100

    Progeress towards automatically integrating the Algorithmic solvers into the list of solvers in the App.

diff --git a/src/main/java/org/distorted/dialogs/RubikDialogStarsStatus.java b/src/main/java/org/distorted/dialogs/RubikDialogStarsStatus.java
deleted file mode 100644
index de758524..00000000
--- a/src/main/java/org/distorted/dialogs/RubikDialogStarsStatus.java
+++ /dev/null
@@ -1,94 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.dialogs;
-
-import android.app.Dialog;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.fragment.app.FragmentActivity;
-
-import org.distorted.external.RubikScores;
-import org.distorted.main.R;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class RubikDialogStarsStatus extends RubikDialogAbstract
-  {
-  @Override
-  public void onResume()
-    {
-    super.onResume();
-
-    Window window = getDialog().getWindow();
-
-    if( window!=null )
-      {
-      WindowManager.LayoutParams params = window.getAttributes();
-      params.width  = (int)Math.min( mHeight*0.65f,mWidth*0.95f );
-      params.height = WindowManager.LayoutParams.WRAP_CONTENT;
-      window.setAttributes(params);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getResource()      { return R.layout.dialog_stars_status; }
-  public int getTitleResource() { return -1; }
-  public boolean hasArgument()  { return false; }
-  public int getPositive()      { return R.string.ok; }
-  public int getNegative()      { return -1; }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void positiveAction()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void negativeAction()
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void prepareBody(Dialog dialog, View view, FragmentActivity act, float size)
-    {
-    int height = (int)(mHeight*0.110f);
-    int margin = (int)(mHeight*0.007f);
-
-    LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
-    params1.setMargins(margin, margin, margin, margin);
-    LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,height);
-    params2.setMargins(margin, margin, margin, margin);
-
-    LinearLayout lm = view.findViewById(R.id.stars_main);
-    lm.setPadding(margin,margin,margin,margin);
-
-    LinearLayout ls = view.findViewById(R.id.stars_strings);
-    ls.setLayoutParams(params1);
-    LinearLayout l1 = view.findViewById(R.id.stars_layout_1);
-    l1.setLayoutParams(params2);
-    LinearLayout l2 = view.findViewById(R.id.stars_layout_2);
-    l2.setLayoutParams(params2);
-    LinearLayout l3 = view.findViewById(R.id.stars_layout_3);
-    l3.setLayoutParams(params2);
-
-    int number = RubikScores.getInstance().getNumStars();
-    TextView v = view.findViewById(R.id.stars_string1);
-    v.setText(getString(R.string.buy_string1,number));
-    }
-  }
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index acc95369..39213f21 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -27,7 +27,7 @@ import org.distorted.main.R;
 import org.distorted.objectlib.metadata.ListObjects;
 import org.distorted.objectlib.metadata.Metadata;
 import org.distorted.objectlib.patterns.RubikPatternList;
-import org.distorted.solvers.ImplementedSolversList;
+import org.distorted.solvers.SolvingList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -81,7 +81,7 @@ public class RubikObject
     int patternOrdinal  = RubikPatternList.getOrdinal(mObjectIndex);
     mPatterns = RubikPatternList.getPatterns(patternOrdinal);
 
-    mSolverOrdinal = ImplementedSolversList.getSolverOrdinal(mObjectIndex);
+    mSolverOrdinal = SolvingList.getSolverOrdinal(mObjectIndex);
     mExtrasOrdinal = -1;
 
     mObjectVersion = meta.objectVersion();
diff --git a/src/main/java/org/distorted/overlays/DataGeneric.java b/src/main/java/org/distorted/overlays/DataGeneric.java
deleted file mode 100644
index 6b121a06..00000000
--- a/src/main/java/org/distorted/overlays/DataGeneric.java
+++ /dev/null
@@ -1,18 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.overlays;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public abstract class DataGeneric
-{
-
-
-}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/overlays/DataStars.java b/src/main/java/org/distorted/overlays/DataStars.java
deleted file mode 100644
index e28d4d01..00000000
--- a/src/main/java/org/distorted/overlays/DataStars.java
+++ /dev/null
@@ -1,50 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.overlays;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-import android.content.res.Resources;
-
-public class DataStars extends DataGeneric
-{
-  private final int mTotStars, mNewStars;
-  private final Resources mRes;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public DataStars(int totStars, int newStars, Resources res)
-    {
-    mTotStars = totStars;
-    mNewStars = newStars;
-    mRes = res;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getTotal()
-    {
-    return mTotStars;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getNew()
-    {
-    return mNewStars;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  Resources getResources()
-    {
-    return mRes;
-    }
-}
diff --git a/src/main/java/org/distorted/overlays/ListenerOverlay.java b/src/main/java/org/distorted/overlays/ListenerOverlay.java
deleted file mode 100644
index e22ae7eb..00000000
--- a/src/main/java/org/distorted/overlays/ListenerOverlay.java
+++ /dev/null
@@ -1,17 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.overlays;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public interface ListenerOverlay
-{
-  void overlayFinished(long id);
-}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/overlays/OverlayGeneric.java b/src/main/java/org/distorted/overlays/OverlayGeneric.java
deleted file mode 100644
index ee0d0b3b..00000000
--- a/src/main/java/org/distorted/overlays/OverlayGeneric.java
+++ /dev/null
@@ -1,65 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.overlays;
-
-import org.distorted.library.main.DistortedScreen;
-
-import java.lang.reflect.Method;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public abstract class OverlayGeneric
-{
-   private enum Overlays
-      {
-      STARS ( OverlayStars.class )
-      ;
-
-      private final Class<? extends OverlayGeneric> mClass;
-
-      Overlays(Class<? extends OverlayGeneric> clazz)
-         {
-         mClass = clazz;
-         }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public static void enableEffects()
-     {
-     Method method;
-
-     for(Overlays overlay: Overlays.values() )
-        {
-        try
-          {
-          method = overlay.mClass.getDeclaredMethod("enableEffects");
-          }
-        catch(NoSuchMethodException ex)
-          {
-          android.util.Log.e("OverlayGeneric", overlay.mClass.getSimpleName()+": exception getting method: "+ex.getMessage());
-          method = null;
-          }
-
-        try
-          {
-          if( method!=null ) method.invoke(null);
-          }
-        catch(Exception ex)
-          {
-          android.util.Log.e("OverlayGeneric", overlay.mClass.getSimpleName()+": exception invoking method: "+ex.getMessage());
-          }
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public abstract long startOverlay(DistortedScreen screen, ListenerOverlay listener, DataGeneric data);
-}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/overlays/OverlayStars.java b/src/main/java/org/distorted/overlays/OverlayStars.java
deleted file mode 100644
index 0bf959bb..00000000
--- a/src/main/java/org/distorted/overlays/OverlayStars.java
+++ /dev/null
@@ -1,396 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.overlays;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-
-import org.distorted.library.effect.EffectQuality;
-import org.distorted.library.effect.FragmentEffectAlpha;
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.effect.MatrixEffectScale;
-import org.distorted.library.effect.PostprocessEffectGlow;
-import org.distorted.library.effect.VertexEffectMove;
-import org.distorted.library.effect.VertexEffectScale;
-import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.main.DistortedNode;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.library.main.DistortedTexture;
-import org.distorted.library.main.InternalOutputSurface;
-import org.distorted.library.mesh.MeshJoined;
-import org.distorted.library.mesh.MeshQuad;
-import org.distorted.library.message.EffectListener;
-import org.distorted.library.type.Dynamic;
-import org.distorted.library.type.Dynamic1D;
-import org.distorted.library.type.Dynamic2D;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Dynamic4D;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static2D;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-import org.distorted.main.R;
-
-import java.util.Random;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class OverlayStars extends OverlayGeneric implements EffectListener
-{
-   private static final int DUR_APP =  1500;
-   private static final int DUR_MOV =  3000;
-   private static final int DUR_GLO =   600;
-
-   private static final int MAX_FALLING = 50;
-
-   private ListenerOverlay mListener;
-   private DistortedNode mNodeFalling, mNodeCentral;
-   private DistortedScreen mScreen;
-   private DistortedTexture mTexture;
-   private Bitmap mCountBitmap, mStarBitmap;
-   private Canvas mCountCanvas;
-   private Paint mPaint;
-   private int mBmpW, mBmpH;
-   private float mWidth, mHeight;
-   private Random mRandom;
-   private long mMoveID, mGlow1ID, mGlow2ID, mAlphaID;
-   private int mTotalStars, mNewStars;
-   private FragmentEffectAlpha mAlpha;
-   private Dynamic1D mAlphaStrength;
-   private boolean mIncrease;
-   private float mTextHeight;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private Dynamic3D createRandomMove(boolean increase)
-      {
-      Dynamic3D move = new Dynamic3D();
-      move.setMode(Dynamic.MODE_PATH);
-      move.setDuration(DUR_MOV);
-      move.setCount( increase ? 0.40f : 0.50f );
-
-      float widthS = (mRandom.nextFloat()-0.5f)*mWidth*1.10f;
-      float widthM = widthS + (mRandom.nextFloat()-0.5f)*mWidth*0.2f;
-      float heighS = mRandom.nextFloat()*mHeight*0.2f;
-      float heighM = (mRandom.nextFloat()-0.5f)*mHeight*0.2f;
-
-      Static3D pointS = new Static3D(widthS,mHeight*0.60f+heighS,0);
-      Static3D pointM = new Static3D(widthM,mHeight*0.25f+heighM,0);
-      Static3D pointE = new Static3D(0,0,0);
-      Static3D pointF = new Static3D(0,0,-10000);
-
-      if( increase )
-         {
-         move.add(pointS);
-         move.add(pointM);
-         move.add(pointE);
-         move.add(pointF);
-         }
-      else
-         {
-         move.add(pointF);
-         move.add(pointE);
-         move.add(pointM);
-         move.add(pointS);
-         }
-
-      return move;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void createAlphaEffect(boolean appear)
-      {
-      if( mAlpha==null )
-         {
-         mAlphaStrength = new Dynamic1D();
-         mAlphaStrength.setMode(Dynamic.MODE_PATH);
-         mAlphaStrength.setDuration( mNewStars==0 ? 3*DUR_APP : DUR_APP);
-         mAlphaStrength.setCount(0.5f);
-         equipAlpha(mAlphaStrength,appear);
-         mAlpha = new FragmentEffectAlpha(mAlphaStrength);
-
-         if( mNewStars==0 )
-            {
-            mMoveID = mAlpha.getID();
-            mAlpha.notifyWhenFinished(this);
-            }
-         }
-      else
-         {
-         mAlphaStrength.resetToBeginning();
-         equipAlpha(mAlphaStrength,appear);
-         }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void equipAlpha(Dynamic1D alpha, boolean appear)
-      {
-      Static1D point0 = new Static1D(0.0f);
-      Static1D point1 = new Static1D(1.0f);
-
-      alpha.removeAll();
-
-      if( appear )
-        {
-        alpha.add(point0);
-        if( mNewStars==0 ) alpha.add(point1);
-        alpha.add(point1);
-        }
-      else
-        {
-        alpha.add(point1);
-        if( mNewStars==0 ) alpha.add(point1);
-        alpha.add(point0);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private float computeQuotientAndTextHeight(int total)
-      {
-      if( total>=10000 )
-         {
-         mTextHeight = 0.610f;
-         return 0.16f;
-         }
-      if( total>= 1000 )
-         {
-         mTextHeight = 0.625f;
-         return 0.20f;
-         }
-      if( total>=  100 )
-         {
-         mTextHeight = 0.640f;
-         return 0.26f;
-         }
-
-      mTextHeight = 0.655f;
-      return 0.28f;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void createBitmap(Resources res, int total)
-      {
-      mStarBitmap = BitmapFactory.decodeResource(res, R.drawable.star);
-      mBmpW = mStarBitmap.getWidth();
-      mBmpH = mStarBitmap.getHeight();
-      mCountBitmap = Bitmap.createBitmap(mBmpW,mBmpH,Bitmap.Config.ARGB_8888);
-      mCountCanvas = new Canvas(mCountBitmap);
-
-      float quotient = computeQuotientAndTextHeight(total);
-
-      mPaint = new Paint();
-      mPaint.setColor(0xff000000);
-      mPaint.setTextSize(mBmpH*quotient);
-      mPaint.setAntiAlias(true);
-      mPaint.setTextAlign(Paint.Align.CENTER);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private DistortedNode createNodeFalling(boolean increase)
-      {
-      DistortedTexture texture = new DistortedTexture();
-      texture.setTexture(mStarBitmap);
-      int numFalling = Math.min(mNewStars,MAX_FALLING);
-
-      MeshQuad[] mesh = new MeshQuad[numFalling];
-
-      for(int i=0; i<numFalling; i++)
-         {
-         mesh[i] = new MeshQuad();
-         mesh[i].setEffectAssociation(0,1,i+1);
-         }
-
-      MeshJoined wholeMesh = new MeshJoined(mesh);
-
-      DistortedEffects effects = new DistortedEffects();
-      VertexEffectScale scaleE = new VertexEffectScale(mHeight*0.10f);
-      scaleE.setMeshAssociation(1,-1);
-      effects.apply(scaleE);
-
-      for(int i=0; i<numFalling; i++)
-        {
-        Dynamic3D moveP = createRandomMove(increase);
-        VertexEffectMove moveE= new VertexEffectMove(moveP);
-        moveE.setMeshAssociation(0,i+1);
-        effects.apply(moveE);
-
-        if( i==0 )
-           {
-           mMoveID = moveE.getID();
-           moveE.notifyWhenFinished(this);
-           }
-        }
-
-      return new DistortedNode(texture,effects,wholeMesh);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private DistortedNode createNodeCentral(int numStars)
-      {
-      mTexture = new DistortedTexture();
-      renderStars(numStars);
-
-      MeshQuad mesh = new MeshQuad();
-
-      DistortedEffects effects = new DistortedEffects();
-      float scaleM  = mHeight*0.22f;
-      Static3D moveM= new Static3D(0,0,1);
-      MatrixEffectMove move  = new MatrixEffectMove(moveM);
-      MatrixEffectScale scale= new MatrixEffectScale(scaleM);
-      effects.apply(move);
-      effects.apply(scale);
-      createAlphaEffect(true);
-      effects.apply(mAlpha);
-
-      return new DistortedNode(mTexture,effects,mesh);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void renderStars(int numStars)
-      {
-      String txt = ""+numStars;
-      mCountBitmap.eraseColor(0x00000000);
-      mCountCanvas.drawBitmap(mStarBitmap,0,0,null);
-      mCountCanvas.drawText(txt,mBmpW*0.5f,mBmpH*mTextHeight,mPaint);
-      mTexture.setTexture(mCountBitmap);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public long startOverlay(DistortedScreen screen, ListenerOverlay listener, DataGeneric data)
-      {
-      mRandom = new Random();
-      mScreen = screen;
-      mListener= listener;
-      DataStars stars = (DataStars)data;
-      mTotalStars = stars.getTotal();
-      mNewStars   = stars.getNew();
-      Resources res = stars.getResources();
-      mWidth = mScreen.getWidth();
-      mHeight= mScreen.getHeight();
-      mIncrease = mNewStars>0;
-      if( !mIncrease ) mNewStars = -mNewStars;
-
-      createBitmap(res, mTotalStars);
-
-      if( mNewStars!=0 )
-         {
-         mNodeFalling = createNodeFalling(mIncrease);
-         mNodeFalling.enableDepthStencil(InternalOutputSurface.NO_DEPTH_NO_STENCIL);
-         mScreen.attach(mNodeFalling);
-         }
-
-      mNodeCentral = createNodeCentral(mTotalStars);
-      mNodeCentral.glDepthMask(false);
-      mScreen.attach(mNodeCentral);
-
-      return 0;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private PostprocessEffectGlow constructGlow(boolean firstPhase)
-      {
-      Dynamic2D haloRadius = new Dynamic2D( mNewStars==0 ? 2*DUR_GLO : DUR_GLO,0.5f);
-      Static2D point20 = new Static2D( 0, 0);
-      Static2D point21 = new Static2D(15,50);
-      Dynamic4D color = new Dynamic4D(DUR_GLO, 0.5f);
-      Static4D point40 = new Static4D(1,1,1,0.0f);
-      Static4D point41 = new Static4D(1,1,1,0.8f);
-
-      if( firstPhase )
-         {
-         haloRadius.add(point20);
-         haloRadius.add(point21);
-         color.add(point40);
-         color.add(point41);
-         }
-      else
-         {
-         haloRadius.add(point21);
-         haloRadius.add(point20);
-         color.add(point41);
-         color.add(point40);
-         }
-
-      PostprocessEffectGlow glow = new PostprocessEffectGlow(haloRadius,color);
-      glow.setQuality(EffectQuality.MEDIUM);
-      glow.notifyWhenFinished(this);
-
-      return glow;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void effectFinished(long id)
-      {
-      if( id==mMoveID )
-         {
-         PostprocessEffectGlow glow = constructGlow(true);
-         mGlow1ID = glow.getID();
-         DistortedEffects effects = mNodeCentral.getEffects();
-         effects.apply(glow);
-         }
-      if( id==mGlow1ID )
-         {
-         renderStars(mTotalStars+(mIncrease ? mNewStars : -mNewStars));
-         PostprocessEffectGlow glow = constructGlow(false);
-         mGlow2ID = glow.getID();
-         DistortedEffects effects = mNodeCentral.getEffects();
-         effects.abortById(mGlow1ID);
-         effects.apply(glow);
-         }
-      if( id==mGlow2ID )
-         {
-         DistortedEffects effects = mNodeCentral.getEffects();
-         effects.abortById(mGlow2ID);
-         createAlphaEffect(false);
-         mAlphaID = mAlpha.getID();
-         mAlpha.notifyWhenFinished(this);
-         effects.apply(mAlpha);
-         }
-      if( id==mAlphaID )
-         {
-         mScreen.detach(mNodeCentral);
-         mNodeCentral.markForDeletion();
-         mNodeCentral=null;
-
-         if( mNodeFalling!=null )
-            {
-            mScreen.detach(mNodeFalling);
-            mNodeFalling.markForDeletion();
-            mNodeFalling=null;
-            }
-
-         if( mListener!=null ) mListener.overlayFinished(id);
-         }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @SuppressWarnings("unused")
-  public static void enableEffects()
-     {
-     FragmentEffectAlpha.enable();
-     VertexEffectMove.enable();
-     VertexEffectScale.enable();
-     PostprocessEffectGlow.enable();
-     }
-}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/solvers/ImplementedSolversList.java b/src/main/java/org/distorted/solvers/ImplementedSolversList.java
deleted file mode 100644
index 06eff000..00000000
--- a/src/main/java/org/distorted/solvers/ImplementedSolversList.java
+++ /dev/null
@@ -1,120 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 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 org.distorted.main.R;
-import org.distorted.objectlib.metadata.MetadataCUBE_2;
-import org.distorted.objectlib.metadata.MetadataCUBE_3;
-import org.distorted.objectlib.metadata.MetadataCU_232;
-import org.distorted.objectlib.metadata.MetadataCU_323;
-import org.distorted.objectlib.metadata.MetadataDIAM_2;
-import org.distorted.objectlib.metadata.MetadataDIN4_3;
-import org.distorted.objectlib.metadata.MetadataDINO_3;
-import org.distorted.objectlib.metadata.MetadataIVY_2;
-import org.distorted.objectlib.metadata.MetadataJING_2;
-import org.distorted.objectlib.metadata.MetadataPDIA_3;
-import org.distorted.objectlib.metadata.MetadataPDUO_2;
-import org.distorted.objectlib.metadata.MetadataPYRA_3;
-import org.distorted.objectlib.metadata.MetadataSKEW_2;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public enum ImplementedSolversList
-{
-  CUBE2          (MetadataCUBE_2.INDEX, R.string.solver_cube2_title, R.string.solver_cube2_description, true),
-  CUBE3_KOCIEMBA (MetadataCUBE_3.INDEX, R.string.solver_cube3_title, R.string.solver_cube3_description, true),
-  CU_232         (MetadataCU_232.INDEX, R.string.solver_cu232_title, R.string.solver_cu232_description, true),
-  CU_323         (MetadataCU_323.INDEX, R.string.solver_cu323_title, R.string.solver_cu323_description, true),
-  PYRAMINX       (MetadataPYRA_3.INDEX, R.string.solver_pyra3_title, R.string.solver_pyra3_description, true),
-  SKEWB          (MetadataSKEW_2.INDEX, R.string.solver_skew2_title, R.string.solver_skew2_description, true),
-  PYRAMINX_DUO   (MetadataPDUO_2.INDEX, R.string.solver_pduo2_title, R.string.solver_pduo2_description, true),
-  IVY            (MetadataIVY_2.INDEX , R.string.solver_ivy_title, R.string.solver_ivy_description, true),
-  DIAMOND        (MetadataDIAM_2.INDEX, R.string.solver_diam2_title, R.string.solver_diam2_description, true),
-  JING2          (MetadataJING_2.INDEX, R.string.solver_jing2_title, R.string.solver_jing2_description, true),
-  DINO6          (MetadataDINO_3.INDEX, R.string.solver_dino6_title, R.string.solver_dino6_description, true),
-  DINO4          (MetadataDIN4_3.INDEX, R.string.solver_dino4_title, R.string.solver_dino4_description, true),
-  PDIA           (MetadataPDIA_3.INDEX, R.string.solver_pdia_title, R.string.solver_pdia_description, true),
-  ;
-
-  public static final int NUM_OBJECTS = values().length;
-
-  private final int mObject;
-  private final int mTitle;
-  private final int mDescription;
-  private final boolean mImplemented;
-
-  private static final ImplementedSolversList[] objects;
-
-  static
-    {
-    objects = new ImplementedSolversList[NUM_OBJECTS];
-    int i=0;
-
-    for(ImplementedSolversList object: ImplementedSolversList.values())
-      {
-      objects[i++] = object;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static ImplementedSolversList getSolver(int solverOrdinal)
-    {
-    return objects[solverOrdinal];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static int getSolverOrdinal(int objectOrdinal)
-    {
-    for(int o=0; o<NUM_OBJECTS; o++)
-      if( objects[o].mObject==objectOrdinal ) return o;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  ImplementedSolversList(int object, int title, int descripton, boolean implemented)
-    {
-    mObject      = object;
-    mTitle       = title;
-    mDescription = descripton;
-    mImplemented = implemented;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getObject()
-    {
-    return mObject;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getTitle()
-    {
-    return mTitle;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getDescription()
-    {
-    return mDescription;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public boolean isImplemented()
-    {
-    return mImplemented;
-    }
-}
diff --git a/src/main/java/org/distorted/solvers/SolverCube2.java b/src/main/java/org/distorted/solvers/SolverCube2.java
deleted file mode 100644
index 02f3563a..00000000
--- a/src/main/java/org/distorted/solvers/SolverCube2.java
+++ /dev/null
@@ -1,297 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-import org.distorted.objectlib.tablebases.TBCube2;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverCube2 extends SolverTablebase
-{
-  private static final int ERROR_CORNER_135_MISSING = -1;
-  private static final int ERROR_CORNER_134_MISSING = -2;
-  private static final int ERROR_CORNER_125_MISSING = -3;
-  private static final int ERROR_CORNER_124_MISSING = -4;
-  private static final int ERROR_CORNER_035_MISSING = -5;
-  private static final int ERROR_CORNER_034_MISSING = -6;
-  private static final int ERROR_CORNER_025_MISSING = -7;
-  private static final int ERROR_CORNER_024_MISSING = -8;
-  private static final int ERROR_CORNERS_CANNOT     = -9;
-  private static final int ERROR_CORNER_TWISTED     = -10;
-
-  TablebasesAbstract mSolver;
-  private final int[] mFaceColors;
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private void fillCornerTwists(int[] output, int[][] corners, int[] perm)
-    {
-    for(int i=0; i<8; i++)
-      {
-      int[] c = corners[perm[i]];
-
-           if( c[0]==mFaceColors[0] || c[0]==mFaceColors[1] ) output[i] = 0;
-      else if( c[1]==mFaceColors[0] || c[1]==mFaceColors[1] ) output[i] = 1;
-      else                                                    output[i] = 2;
-      }
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int cornerIndex(int[][] corners, int i1, int i2, int i3)
-    {
-    int c1 = mFaceColors[i1];
-    int c2 = mFaceColors[i2];
-    int c3 = mFaceColors[i3];
-
-    for(int i=0; i<8; i++)
-      {
-      int[] cor = corners[i];
-
-      if( (cor[0]==c1 && cor[1]==c2 && cor[2]==c3) ||
-          (cor[0]==c2 && cor[1]==c3 && cor[2]==c1) ||
-          (cor[0]==c3 && cor[1]==c1 && cor[2]==c2)  ) return i;
-      }
-
-    return -1;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retCornerPermutation(int[] output, int[][] corners)
-    {
-    for(int i=0; i<8; i++) output[i] = -1;
-
-    output[0] = cornerIndex(corners, 1,2,5);
-    output[1] = cornerIndex(corners, 1,4,2);
-    output[2] = cornerIndex(corners, 1,5,3);
-    output[3] = cornerIndex(corners, 1,3,4);
-    output[4] = cornerIndex(corners, 0,5,2);
-    output[5] = cornerIndex(corners, 0,2,4);
-    output[6] = cornerIndex(corners, 0,3,5);
-    output[7] = cornerIndex(corners, 0,4,3);
-
-    if( output[0]==-1 ) return ERROR_CORNER_125_MISSING;
-    if( output[1]==-1 ) return ERROR_CORNER_124_MISSING;
-    if( output[2]==-1 ) return ERROR_CORNER_135_MISSING;
-    if( output[3]==-1 ) return ERROR_CORNER_134_MISSING;
-    if( output[4]==-1 ) return ERROR_CORNER_025_MISSING;
-    if( output[5]==-1 ) return ERROR_CORNER_024_MISSING;
-    if( output[6]==-1 ) return ERROR_CORNER_035_MISSING;
-    if( output[7]==-1 ) return ERROR_CORNER_034_MISSING;
-
-    return 0;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int findColor(int[][] corners, int c1, int c2)
-    {
-    for(int i=0; i<8; i++)
-      {
-      int[] cor = corners[i];
-
-      if( cor[0]==c1 && cor[1]==c2 ) return cor[2];
-      if( cor[1]==c1 && cor[2]==c2 ) return cor[0];
-      if( cor[2]==c1 && cor[0]==c2 ) return cor[1];
-      }
-
-    return -1;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeFaceColors(int[][] corners)
-    {
-    mFaceColors[1] = corners[1][0];
-    mFaceColors[2] = corners[1][2];
-    mFaceColors[4] = corners[1][1];
-
-    mFaceColors[0] = findColor(corners,mFaceColors[2],mFaceColors[4]);
-    mFaceColors[3] = findColor(corners,mFaceColors[4],mFaceColors[1]);
-    mFaceColors[5] = findColor(corners,mFaceColors[1],mFaceColors[2]);
-
-    for(int i=0; i<6; i++)
-      {
-      int color = mFaceColors[i];
-      if( color<0 ) return ERROR_CORNERS_CANNOT;
-
-      for(int j=i+1; j<6; j++)
-        if( mFaceColors[j]==color ) return ERROR_CORNERS_CANNOT;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverCube2(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    mFaceColors = new int[6];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCorners(TwistyObject object, int[][] corners)
-    {
-    corners[0][0] = object.getCubitFaceStickerIndex(0,1);
-    corners[0][1] = object.getCubitFaceStickerIndex(0,3);
-    corners[0][2] = object.getCubitFaceStickerIndex(0,5);
-
-    corners[1][0] = object.getCubitFaceStickerIndex(1,3);
-    corners[1][1] = object.getCubitFaceStickerIndex(1,5);
-    corners[1][2] = object.getCubitFaceStickerIndex(1,1);
-
-    corners[2][0] = object.getCubitFaceStickerIndex(2,5);
-    corners[2][1] = object.getCubitFaceStickerIndex(2,1);
-    corners[2][2] = object.getCubitFaceStickerIndex(2,3);
-
-    corners[3][0] = object.getCubitFaceStickerIndex(3,1);
-    corners[3][1] = object.getCubitFaceStickerIndex(3,3);
-    corners[3][2] = object.getCubitFaceStickerIndex(3,5);
-
-    corners[4][0] = object.getCubitFaceStickerIndex(4,1);
-    corners[4][1] = object.getCubitFaceStickerIndex(4,3);
-    corners[4][2] = object.getCubitFaceStickerIndex(4,5);
-
-    corners[5][0] = object.getCubitFaceStickerIndex(5,1);
-    corners[5][1] = object.getCubitFaceStickerIndex(5,3);
-    corners[5][2] = object.getCubitFaceStickerIndex(5,5);
-
-    corners[6][0] = object.getCubitFaceStickerIndex(6,1);
-    corners[6][1] = object.getCubitFaceStickerIndex(6,3);
-    corners[6][2] = object.getCubitFaceStickerIndex(6,5);
-
-    corners[7][0] = object.getCubitFaceStickerIndex(7,1);
-    corners[7][1] = object.getCubitFaceStickerIndex(7,3);
-    corners[7][2] = object.getCubitFaceStickerIndex(7,5);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners= new int[8][3];
-    getCorners(object,corners);
-
-    int result0 = computeFaceColors(corners);
-    if( result0<0 ) return result0;
-
-    int[] perm = new int[8];
-    int result1 = retCornerPermutation(perm,corners);
-    if( result1<0 ) return result1;
-
-    int[] perm7 = TBCube2.shrinkPerm(perm);
-    int permNum = TablebaseHelpers.computePermutationNum(perm7);
-    int[] twist = new int[8];
-    fillCornerTwists(twist,corners,perm);
-
-    int totalTwist = 0;
-    for(int i=0; i<8; i++) totalTwist += twist[i];
-    if( ((totalTwist)%3) != 0 ) return ERROR_CORNER_TWISTED;
-
-    int twistNum = twist[0] + 3*(twist[2] + 3*(twist[3] + 3*(twist[4] + 3*(twist[5] + 3*twist[6]))));
-
-    return twistNum + 729*permNum;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex4(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_yellow4;
-      case 1: return R.string.color_white4;
-      case 2: return R.string.color_blue4;
-      case 3: return R.string.color_green4;
-      case 4: return R.string.color_red4;
-      case 5: return R.string.color_orange4;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_yellow3;
-      case 1: return R.string.color_white3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_green3;
-      case 4: return R.string.color_red3;
-      case 5: return R.string.color_orange3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerError(Resources res, int face0, int face1, int face2)
-    {
-    int j0 = getColorIndex3(face0);
-    int j1 = getColorIndex3(face1);
-    int j2 = getColorIndex4(face2);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-    String c2 = res.getString(j2);
-
-    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_135_MISSING: return cornerError(res,1,3,5);
-      case ERROR_CORNER_134_MISSING: return cornerError(res,1,3,4);
-      case ERROR_CORNER_125_MISSING: return cornerError(res,1,2,5);
-      case ERROR_CORNER_124_MISSING: return cornerError(res,1,2,4);
-      case ERROR_CORNER_035_MISSING: return cornerError(res,0,3,5);
-      case ERROR_CORNER_034_MISSING: return cornerError(res,0,3,4);
-      case ERROR_CORNER_025_MISSING: return cornerError(res,0,2,5);
-      case ERROR_CORNER_024_MISSING: return cornerError(res,0,2,4);
-      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
-      case ERROR_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.CUBE_2.name() );
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverCube3.java b/src/main/java/org/distorted/solvers/SolverCube3.java
deleted file mode 100644
index 9e29919d..00000000
--- a/src/main/java/org/distorted/solvers/SolverCube3.java
+++ /dev/null
@@ -1,220 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.solverui.ScreenSolver;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.kociemba.SolverSearch;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverCube3
-{
-  private final Resources mRes;
-  private final OperatingSystemInterface mOS;
-  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_yellow1; return numColors[0]; }
-    if( numColors[1]<9 ) { mColorID = R.string.color_white1 ; return numColors[1]; }
-    if( numColors[2]<9 ) { mColorID = R.string.color_blue1  ; return numColors[2]; }
-    if( numColors[3]<9 ) { mColorID = R.string.color_green1 ; return numColors[3]; }
-    if( numColors[4]<9 ) { mColorID = R.string.color_red1   ; return numColors[4]; }
-    if( numColors[5]<9 ) { mColorID = R.string.color_orange1; 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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(B_INDEX[i], face);
-      objectString.append(FACE_NAMES[color]);
-      }
-
-    return objectString.toString();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverCube3(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    mRes   = res;
-    mOS    = os;
-    mObject= object;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void solve(ScreenSolver solver)
-    {
-    String result;
-
-    SolverSearch.prepare(mOS);
-    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_generic_edge_twist); break;
-          case '4': result = mRes.getString(R.string.solver_cube3_error4); break;
-          case '5': result = mRes.getString(R.string.solver_generic_corner_twist); 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.displayImpossibleDialog(result);
-        }
-      else
-        {
-        solver.setSolved(result);
-        }
-      }
-    else
-      {
-      String color = mRes.getString(mColorID);
-      result = mRes.getString(R.string.solver_cube3_error1,check,color);
-      solver.displayImpossibleDialog(result);
-      }
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverCuboid232.java b/src/main/java/org/distorted/solvers/SolverCuboid232.java
deleted file mode 100644
index 5fd51d0e..00000000
--- a/src/main/java/org/distorted/solvers/SolverCuboid232.java
+++ /dev/null
@@ -1,418 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverCuboid232 extends SolverTablebase
-{
-  private static final int ERROR_CORNER_135_MISSING = -1;
-  private static final int ERROR_CORNER_134_MISSING = -2;
-  private static final int ERROR_CORNER_125_MISSING = -3;
-  private static final int ERROR_CORNER_124_MISSING = -4;
-  private static final int ERROR_CORNER_035_MISSING = -5;
-  private static final int ERROR_CORNER_034_MISSING = -6;
-  private static final int ERROR_CORNER_025_MISSING = -7;
-  private static final int ERROR_CORNER_024_MISSING = -8;
-
-  private static final int ERROR_EDGE_15_MISSING = -9;
-  private static final int ERROR_EDGE_14_MISSING = -10;
-  private static final int ERROR_EDGE_05_MISSING = -11;
-  private static final int ERROR_EDGE_04_MISSING = -12;
-
-  private static final int ERROR_CORNERS_CANNOT  = -13;
-  private static final int ERROR_EDGE_TWISTED    = -14;
-  private static final int ERROR_CORNER_TWISTED  = -15;
-
-  TablebasesAbstract mSolver;
-  private final int[] mFaceColors;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int edgeIs(int[] edge, int i0, int i1)
-    {
-    int c0 = mFaceColors[i0];
-    int c1 = mFaceColors[i1];
-
-    if( edge[0]==c0 && edge[1]==c1 ) return 0;
-    if( edge[0]==c1 && edge[1]==c0 ) return 1;
-    return 2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retEdgePermutation(int[] output, int[][] edges)
-    {
-    for(int i=0; i<4; i++) output[i] = -1;
-
-    for(int i=0; i<4; i++)
-      {
-      int edge0 = edgeIs(edges[i],1,5);
-           if( edge0==0 ) output[0]=i;
-      else if( edge0==1 ) return ERROR_EDGE_TWISTED;
-
-      int edge1 = edgeIs(edges[i],1,4);
-           if( edge1==0 ) output[1]=i;
-      else if( edge1==1 ) return ERROR_EDGE_TWISTED;
-
-      int edge2 = edgeIs(edges[i],0,5);
-           if( edge2==0 ) output[2]=i;
-      else if( edge2==1 ) return ERROR_EDGE_TWISTED;
-
-      int edge3 = edgeIs(edges[i],0,4);
-           if( edge3==0 ) output[3]=i;
-      else if( edge3==1 ) return ERROR_EDGE_TWISTED;
-      }
-
-    if( output[0]==-1 ) return ERROR_EDGE_15_MISSING;
-    if( output[1]==-1 ) return ERROR_EDGE_14_MISSING;
-    if( output[2]==-1 ) return ERROR_EDGE_05_MISSING;
-    if( output[3]==-1 ) return ERROR_EDGE_04_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int cornerIs(int[] corner, int i0, int i1, int i2)
-    {
-    int c0 = mFaceColors[i0];
-    int c1 = mFaceColors[i1];
-    int c2 = mFaceColors[i2];
-
-    if( corner[0]==c0 && corner[1]==c1 && corner[2]==c2 ) return 0;
-
-    if( corner[0]==c0 && corner[1]==c2 && corner[2]==c1 ||
-        corner[0]==c1 && corner[1]==c0 && corner[2]==c2 ||
-        corner[0]==c1 && corner[1]==c2 && corner[2]==c0 ||
-        corner[0]==c2 && corner[1]==c0 && corner[2]==c1 ||
-        corner[0]==c2 && corner[1]==c1 && corner[2]==c0  ) return 1;
-
-    return 2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retCornerPermutation(int[] output, int[][] corners)
-    {
-    for(int i=0; i<8; i++) output[i] = -1;
-
-    for(int i=0; i<8; i++)
-      {
-      int corner7 = cornerIs(corners[i],2,4,0);
-           if( corner7==0 ) output[7]=i;
-      else if( corner7==1 ) return ERROR_CORNER_TWISTED;
-
-      int corner6 = cornerIs(corners[i],2,0,5);
-           if( corner6==0 ) output[6]=i;
-      else if( corner6==1 ) return ERROR_CORNER_TWISTED;
-
-      int corner5 = cornerIs(corners[i],3,0,4);
-           if( corner5==0 ) output[5]=i;
-      else if( corner5==1 ) return ERROR_CORNER_TWISTED;
-
-      int corner4 = cornerIs(corners[i],3,5,0);
-           if( corner4==0 ) output[4]=i;
-      else if( corner4==1 ) return ERROR_CORNER_TWISTED;
-
-      int corner3 = cornerIs(corners[i],2,1,4);
-           if( corner3==0 ) output[3]=i;
-      else if( corner3==1 ) return ERROR_CORNER_TWISTED;
-
-      int corner2 = cornerIs(corners[i],2,5,1);
-           if( corner2==0 ) output[2]=i;
-      else if( corner2==1 ) return ERROR_CORNER_TWISTED;
-
-      int corner1 = cornerIs(corners[i],3,4,1);
-           if( corner1==0 ) output[1]=i;
-      else if( corner1==1 ) return ERROR_CORNER_TWISTED;
-
-      int corner0 = cornerIs(corners[i],3,1,5);
-           if( corner0==0 ) output[0]=i;
-      else if( corner0==1 ) return ERROR_CORNER_TWISTED;
-      }
-
-    if( output[0]==-1 ) return ERROR_CORNER_135_MISSING;
-    if( output[1]==-1 ) return ERROR_CORNER_134_MISSING;
-    if( output[2]==-1 ) return ERROR_CORNER_125_MISSING;
-    if( output[3]==-1 ) return ERROR_CORNER_124_MISSING;
-    if( output[4]==-1 ) return ERROR_CORNER_035_MISSING;
-    if( output[5]==-1 ) return ERROR_CORNER_034_MISSING;
-    if( output[6]==-1 ) return ERROR_CORNER_025_MISSING;
-    if( output[7]==-1 ) return ERROR_CORNER_024_MISSING;
-
-    return 0;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeFaceColors(int[][] corners, int[][] edges)
-    {
-    mFaceColors[1] = edges[1][0];
-    mFaceColors[4] = edges[1][1];
-
-         if( edges[0][0]==mFaceColors[1] ) mFaceColors[5] = edges[0][1];
-    else if( edges[2][0]==mFaceColors[1] ) mFaceColors[5] = edges[2][1];
-    else if( edges[3][0]==mFaceColors[1] ) mFaceColors[5] = edges[3][1];
-    else return ERROR_EDGE_TWISTED;
-
-         if( edges[0][1]==mFaceColors[4] ) mFaceColors[0] = edges[0][0];
-    else if( edges[2][1]==mFaceColors[4] ) mFaceColors[0] = edges[2][0];
-    else if( edges[3][1]==mFaceColors[4] ) mFaceColors[0] = edges[3][0];
-    else return ERROR_EDGE_TWISTED;
-
-    boolean found2 = false;
-    boolean found3 = false;
-
-    for(int c=0; c<8; c++)
-      {
-      if( !found3 && corners[c][1]==mFaceColors[4] && corners[c][2]==mFaceColors[1] )
-        {
-        found3=true;
-        mFaceColors[3] = corners[c][0];
-        }
-      if( !found2 && corners[c][1]==mFaceColors[1] && corners[c][2]==mFaceColors[4] )
-        {
-        found2=true;
-        mFaceColors[2] = corners[c][0];
-        }
-      }
-
-    if( !found2 || !found3 ) return ERROR_CORNERS_CANNOT;
-
-    for(int i=0; i<6; i++)
-      for(int j=i+1; j<6; j++)
-        if( mFaceColors[i]==mFaceColors[j] ) return ERROR_CORNERS_CANNOT;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] correctEdgePerm(int[] perm)
-    {
-    int[] ret = new int[3];
-
-    ret[0] = perm[0];
-    ret[1] = perm[2];
-    ret[2] = perm[3];
-
-    if( ret[0]>1 ) ret[0]--;
-    if( ret[1]>1 ) ret[1]--;
-    if( ret[2]>1 ) ret[2]--;
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverCuboid232(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    mFaceColors = new int[6];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners= new int[8][3];
-    int[][] edges  = new int[4][2];
-
-    corners[0][0] = object.getCubitFaceStickerIndex(0,3);
-    corners[0][1] = object.getCubitFaceStickerIndex(0,1);
-    corners[0][2] = object.getCubitFaceStickerIndex(0,5);
-
-    corners[1][0] = object.getCubitFaceStickerIndex(1,1);
-    corners[1][1] = object.getCubitFaceStickerIndex(1,5);
-    corners[1][2] = object.getCubitFaceStickerIndex(1,3);
-
-    corners[2][0] = object.getCubitFaceStickerIndex(2,3);
-    corners[2][1] = object.getCubitFaceStickerIndex(2,1);
-    corners[2][2] = object.getCubitFaceStickerIndex(2,5);
-
-    corners[3][0] = object.getCubitFaceStickerIndex(3,3);
-    corners[3][1] = object.getCubitFaceStickerIndex(3,1);
-    corners[3][2] = object.getCubitFaceStickerIndex(3,5);
-
-    corners[4][0] = object.getCubitFaceStickerIndex(4,5);
-    corners[4][1] = object.getCubitFaceStickerIndex(4,3);
-    corners[4][2] = object.getCubitFaceStickerIndex(4,1);
-
-    corners[5][0] = object.getCubitFaceStickerIndex(5,3);
-    corners[5][1] = object.getCubitFaceStickerIndex(5,1);
-    corners[5][2] = object.getCubitFaceStickerIndex(5,5);
-
-    corners[6][0] = object.getCubitFaceStickerIndex(6,3);
-    corners[6][1] = object.getCubitFaceStickerIndex(6,1);
-    corners[6][2] = object.getCubitFaceStickerIndex(6,5);
-
-    corners[7][0] = object.getCubitFaceStickerIndex(7,5);
-    corners[7][1] = object.getCubitFaceStickerIndex(7,3);
-    corners[7][2] = object.getCubitFaceStickerIndex(7,1);
-
-    edges[0][0] = object.getCubitFaceStickerIndex(8,5);
-    edges[0][1] = object.getCubitFaceStickerIndex(8,3);
-    edges[1][0] = object.getCubitFaceStickerIndex(9,3);
-    edges[1][1] = object.getCubitFaceStickerIndex(9,5);
-    edges[2][0] = object.getCubitFaceStickerIndex(10,5);
-    edges[2][1] = object.getCubitFaceStickerIndex(10,3);
-    edges[3][0] = object.getCubitFaceStickerIndex(11,3);
-    edges[3][1] = object.getCubitFaceStickerIndex(11,5);
-
-    int result0 = computeFaceColors(corners, edges);
-    if( result0<0 ) return result0;
-
-    int[] corner_perm = new int[8];
-    int result1 = retCornerPermutation(corner_perm,corners);
-    if( result1<0 ) return result1;
-
-    int[] edge_perm = new int[4];
-    int result2 = retEdgePermutation(edge_perm,edges);
-    if( result2<0 ) return result2;
-
-    int[] edge_perm2 = correctEdgePerm(edge_perm); // edge1 is fixed!
-
-    int corner_perm_num = TablebaseHelpers.computePermutationNum(corner_perm);
-    int edge_perm_num = TablebaseHelpers.computePermutationNum(edge_perm2);
-
-    return edge_perm_num + 6*corner_perm_num;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex4(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_yellow4;
-      case 1: return R.string.color_white4;
-      case 2: return R.string.color_blue4;
-      case 3: return R.string.color_green4;
-      case 4: return R.string.color_red4;
-      case 5: return R.string.color_orange4;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_yellow3;
-      case 1: return R.string.color_white3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_green3;
-      case 4: return R.string.color_red3;
-      case 5: return R.string.color_orange3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex6(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_yellow6;
-      case 1: return R.string.color_white6;
-      case 2: return R.string.color_blue6;
-      case 3: return R.string.color_green6;
-      case 4: return R.string.color_red6;
-      case 5: return R.string.color_orange6;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeError(Resources res, int face0, int face1)
-    {
-    int j0 = getColorIndex3(face0);
-    int j1 = getColorIndex6(face1);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerError(Resources res, int face0, int face1, int face2)
-    {
-    int j0 = getColorIndex3(face0);
-    int j1 = getColorIndex3(face1);
-    int j2 = getColorIndex4(face2);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-    String c2 = res.getString(j2);
-
-    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_135_MISSING: return cornerError(res,1,3,5);
-      case ERROR_CORNER_134_MISSING: return cornerError(res,1,3,4);
-      case ERROR_CORNER_125_MISSING: return cornerError(res,1,2,5);
-      case ERROR_CORNER_124_MISSING: return cornerError(res,1,2,4);
-      case ERROR_CORNER_035_MISSING: return cornerError(res,0,3,5);
-      case ERROR_CORNER_034_MISSING: return cornerError(res,0,3,4);
-      case ERROR_CORNER_025_MISSING: return cornerError(res,0,2,5);
-      case ERROR_CORNER_024_MISSING: return cornerError(res,0,2,4);
-
-      case ERROR_EDGE_15_MISSING   : return edgeError(res,1,5);
-      case ERROR_EDGE_14_MISSING   : return edgeError(res,1,4);
-      case ERROR_EDGE_05_MISSING   : return edgeError(res,0,5);
-      case ERROR_EDGE_04_MISSING   : return edgeError(res,0,4);
-
-      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
-      case ERROR_EDGE_TWISTED      : return res.getString(R.string.solver_generic_edge_twist);
-      case ERROR_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.CU_232.name() );
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverCuboid323.java b/src/main/java/org/distorted/solvers/SolverCuboid323.java
deleted file mode 100644
index ba59a20f..00000000
--- a/src/main/java/org/distorted/solvers/SolverCuboid323.java
+++ /dev/null
@@ -1,456 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TBCuboid323;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverCuboid323 extends SolverTablebase
-{
-  private static final int ERROR_CORNER_MISSING = -1;
-  private static final int ERROR_EDGE_MISSING   = -2;
-  private static final int ERROR_CORNERS_CANNOT = -3;
-  private static final int ERROR_EDGE_TWISTED   = -4;
-  private static final int ERROR_CORNER_TWISTED = -5;
-
-  TablebasesAbstract mSolver;
-  private final int[] mFaceColors;
-  private int mErrorColor1, mErrorColor2, mErrorColor3;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverCuboid323(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    mFaceColors = new int[6];
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int findCorner(int[][] corners, int c1, int c2)
-    {
-    for(int i=0; i<8; i++)
-      {
-      int[] c = corners[i];
-
-      if( c[0]==c1 && c[1]==c2 ) return c[2];
-      if( c[1]==c1 && c[2]==c2 ) return c[0];
-      if( c[2]==c1 && c[0]==c2 ) return c[1];
-      }
-
-    return ERROR_CORNERS_CANNOT;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int missingColor()
-    {
-    boolean[] present = new boolean[6];
-    for(int i=0; i<5; i++) present[mFaceColors[i]] = true;
-
-    int indexFalse = -1;
-
-    for(int i=0; i<6; i++)
-      if( !present[i] )
-        {
-        if( indexFalse<0 ) indexFalse=i;
-        else return ERROR_CORNERS_CANNOT;
-        }
-
-    return indexFalse;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int edgePresent(int[][] edges, int f0, int f1)
-    {
-    int c0 = mFaceColors[f0];
-    int c1 = mFaceColors[f1];
-
-    for(int i=0; i<8; i++ )
-      {
-      int[] edge = edges[i];
-
-      if( edge[0]==c0 && edge[1]==c1 ) return i;
-      if( edge[0]==c1 && edge[1]==c0 )
-        {
-        mErrorColor1 = c0;
-        mErrorColor2 = c1;
-        return ERROR_EDGE_TWISTED;
-        }
-      }
-
-    mErrorColor1 = c0;
-    mErrorColor2 = c1;
-    return ERROR_EDGE_MISSING;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int cornerPresent(int[][] corners, int f0, int f1, int f2)
-    {
-    int c0 = mFaceColors[f0];
-    int c1 = mFaceColors[f1];
-    int c2 = mFaceColors[f2];
-
-    for(int i=0; i<8; i++)
-      {
-      int[] corner = corners[i];
-
-      if(  corner[0]==c0 && corner[1]==c1 && corner[2]==c2 ) return i;
-      if( (corner[0]==c1 && corner[1]==c2 && corner[2]==c0 ) ||
-          (corner[0]==c2 && corner[1]==c0 && corner[2]==c1 )  )
-        {
-        mErrorColor1 = c0;
-        mErrorColor2 = c1;
-        mErrorColor3 = c2;
-        return ERROR_CORNER_TWISTED;
-        }
-      }
-
-    mErrorColor1 = c0;
-    mErrorColor2 = c1;
-    mErrorColor3 = c2;
-    return ERROR_CORNER_MISSING;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retEdgePermutation(int[] output, int[][] edges)
-    {
-    int[][] e = { {5,3}, {4,3}, {5,2}, {4,2}, {1,3}, {1,2}, {0,3}, {0,2} };
-
-    for(int i=0; i<8; i++)
-      {
-      int[] ee = e[i];
-      output[i] = edgePresent(edges,ee[0],ee[1]);
-      if( output[i]<0 ) return output[i];
-      }
-
-    return 0;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retCornerPermutation(int[] output, int[][] corners)
-    {
-    int[][] c = { {5,1,3}, {1,4,3}, {1,5,2}, {4,1,2}, {0,5,3}, {4,0,3}, {5,0,2}, {0,4,2} };
-
-    for(int i=0; i<8; i++)
-      {
-      int[] cc = c[i];
-      output[i] = cornerPresent(corners,cc[0],cc[1],cc[2]);
-      if( output[i]<0 ) return output[i];
-      }
-
-    return 0;
-    }
-
-////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeFaceColors(int[][] corners, int[][] edges, int[] centers)
-    {
-    mFaceColors[4] = edges[1][0];
-    mFaceColors[3] = edges[1][1];
-
-         if( centers[0]!=mFaceColors[3] ) mFaceColors[2] = centers[0];
-    else if( centers[1]!=mFaceColors[3] ) mFaceColors[2] = centers[1];
-    else return ERROR_CORNERS_CANNOT;
-
-    mFaceColors[0] = findCorner(corners,mFaceColors[4],mFaceColors[2]);
-    if( mFaceColors[0]<0 ) return mFaceColors[0];
-
-    mFaceColors[1] = findCorner(corners,mFaceColors[2],mFaceColors[4]);
-    if( mFaceColors[1]<0 ) return mFaceColors[1];
-
-    mFaceColors[5] = missingColor();
-    if( mFaceColors[5]<0 ) return mFaceColors[5];
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCorners(TwistyObject object, int[][] corners)
-    {
-    corners[0][0] = object.getCubitFaceStickerIndex(0,5);
-    corners[0][1] = object.getCubitFaceStickerIndex(0,1);
-    corners[0][2] = object.getCubitFaceStickerIndex(0,3);
-
-    corners[1][0] = object.getCubitFaceStickerIndex(1,3);
-    corners[1][1] = object.getCubitFaceStickerIndex(1,5);
-    corners[1][2] = object.getCubitFaceStickerIndex(1,1);
-
-    corners[2][0] = object.getCubitFaceStickerIndex(2,5);
-    corners[2][1] = object.getCubitFaceStickerIndex(2,1);
-    corners[2][2] = object.getCubitFaceStickerIndex(2,3);
-
-    corners[3][0] = object.getCubitFaceStickerIndex(3,5);
-    corners[3][1] = object.getCubitFaceStickerIndex(3,1);
-    corners[3][2] = object.getCubitFaceStickerIndex(3,3);
-
-    corners[4][0] = object.getCubitFaceStickerIndex(4,1);
-    corners[4][1] = object.getCubitFaceStickerIndex(4,3);
-    corners[4][2] = object.getCubitFaceStickerIndex(4,5);
-
-    corners[5][0] = object.getCubitFaceStickerIndex(5,5);
-    corners[5][1] = object.getCubitFaceStickerIndex(5,1);
-    corners[5][2] = object.getCubitFaceStickerIndex(5,3);
-
-    corners[6][0] = object.getCubitFaceStickerIndex(6,5);
-    corners[6][1] = object.getCubitFaceStickerIndex(6,1);
-    corners[6][2] = object.getCubitFaceStickerIndex(6,3);
-
-    corners[7][0] = object.getCubitFaceStickerIndex(7,1);
-    corners[7][1] = object.getCubitFaceStickerIndex(7,3);
-    corners[7][2] = object.getCubitFaceStickerIndex(7,5);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getEdges(TwistyObject object, int[][] edges)
-    {
-    edges[0][0] = object.getCubitFaceStickerIndex(8,5);
-    edges[0][1] = object.getCubitFaceStickerIndex(8,3);
-    edges[1][0] = object.getCubitFaceStickerIndex(9,3);
-    edges[1][1] = object.getCubitFaceStickerIndex(9,5);
-    edges[2][0] = object.getCubitFaceStickerIndex(10,3);
-    edges[2][1] = object.getCubitFaceStickerIndex(10,5);
-    edges[3][0] = object.getCubitFaceStickerIndex(11,5);
-    edges[3][1] = object.getCubitFaceStickerIndex(11,3);
-    edges[4][0] = object.getCubitFaceStickerIndex(12,3);
-    edges[4][1] = object.getCubitFaceStickerIndex(12,5);
-    edges[5][0] = object.getCubitFaceStickerIndex(13,5);
-    edges[5][1] = object.getCubitFaceStickerIndex(13,3);
-    edges[6][0] = object.getCubitFaceStickerIndex(14,3);
-    edges[6][1] = object.getCubitFaceStickerIndex(14,5);
-    edges[7][0] = object.getCubitFaceStickerIndex(15,5);
-    edges[7][1] = object.getCubitFaceStickerIndex(15,3);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCenters(TwistyObject object, int[] centers)
-    {
-    centers[0] = object.getCubitFaceStickerIndex(16,4);
-    centers[1] = object.getCubitFaceStickerIndex(17,4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners= new int[8][3];
-    int[][] edges  = new int[8][2];
-    int[] centers  = new int[2];
-
-    getCorners(object,corners);
-    getEdges(object,edges);
-    getCenters(object,centers);
-
-//for(int i=0; i<8; i++) android.util.Log.e("D", "corner: "+i+" : "+corners[i][0]+" "+corners[i][1]+" "+corners[i][2]);
-//for(int i=0; i<8; i++) android.util.Log.e("D", "edge: "+i+" : "+edges[i][0]+" "+edges[i][1]);
-
-    int result0 = computeFaceColors(corners, edges, centers);
-    if( result0<0 ) return result0;
-
-    int[] corner_perm = new int[8];
-    int result1 = retCornerPermutation(corner_perm,corners);
-    if( result1<0 ) return result1;
-
-//android.util.Log.e("D", "upper: "+mUpper);
-//for(int i=0; i<6; i++) android.util.Log.e("D", "face color: "+mFaceColors[i]);
-
-    int[] edge_perm8 = new int[8];
-    int result2 = retEdgePermutation(edge_perm8,edges);
-    if( result2<0 ) return result2;
-
-    int[] edge_perm7 = TBCuboid323.edgePermTo7(edge_perm8);
-/*
-    TablebaseHelpers.displayTable(corner_perm, "CORNER PERM");
-    TablebaseHelpers.displayTable(edge_perm8, "EDGE PERM8");
-    TablebaseHelpers.displayTable(edge_perm7, "EDGE PERM7");
-*/
-    int corner_perm_num = TablebaseHelpers.computePermutationNum(corner_perm);
-    int edge_perm_num = TablebaseHelpers.computePermutationNum(edge_perm7);
-    int centersInPlace = (centers[0]==mFaceColors[2]) ? 0 : 1;
-
-//android.util.Log.e("D", "corner_perm_num: "+corner_perm_num+" edge_perm_num: "+edge_perm_num+" inPlace: "+centersInPlace);
-//android.util.Log.e("D", "index="+(corner_perm_num + 40320*( centersInPlace + 2*edge_perm_num)));
-
-    return corner_perm_num + 40320*( centersInPlace + 2*edge_perm_num);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex4(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_yellow4;
-      case 1: return R.string.color_white4;
-      case 2: return R.string.color_blue4;
-      case 3: return R.string.color_green4;
-      case 4: return R.string.color_red4;
-      case 5: return R.string.color_orange4;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_yellow3;
-      case 1: return R.string.color_white3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_green3;
-      case 4: return R.string.color_red3;
-      case 5: return R.string.color_orange3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex5(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_yellow5;
-      case 1: return R.string.color_white5;
-      case 2: return R.string.color_blue5;
-      case 3: return R.string.color_green5;
-      case 4: return R.string.color_red5;
-      case 5: return R.string.color_orange5;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex6(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_yellow6;
-      case 1: return R.string.color_white6;
-      case 2: return R.string.color_blue6;
-      case 3: return R.string.color_green6;
-      case 4: return R.string.color_red6;
-      case 5: return R.string.color_orange6;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeTwistedError(Resources res, int color0, int color1)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex6(color1);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_twisted_edge,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerTwistedError(Resources res, int color0, int color1, int color2)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex3(color1);
-    int j2 = getColorIndex5(color2);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-    String c2 = res.getString(j2);
-
-    return res.getString(R.string.solver_generic_twisted_corner,c0,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeMissingError(Resources res, int color0, int color1)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex6(color1);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerMissingError(Resources res, int color0, int color1, int color2)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex3(color1);
-    int j2 = getColorIndex4(color2);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-    String c2 = res.getString(j2);
-
-    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_MISSING : return cornerMissingError(res,mErrorColor1,mErrorColor2,mErrorColor3);
-      case ERROR_EDGE_MISSING   : return edgeMissingError(res,mErrorColor1,mErrorColor2);
-      case ERROR_CORNERS_CANNOT : return res.getString(R.string.solver_generic_corners_cannot);
-      case ERROR_EDGE_TWISTED   : return edgeTwistedError(res,mErrorColor1,mErrorColor2);
-      case ERROR_CORNER_TWISTED : return cornerTwistedError(res,mErrorColor1,mErrorColor2,mErrorColor3);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// a few cu_323 max (depth 18) indices:
-// 1180633, 1180642, 1182044, 1190482, 128151851, 128190028
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.CU_323.name() );
-      }
-
-    ((TBCuboid323)mSolver).initialize();
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverDino4.java b/src/main/java/org/distorted/solvers/SolverDino4.java
deleted file mode 100644
index 0a17bcaf..00000000
--- a/src/main/java/org/distorted/solvers/SolverDino4.java
+++ /dev/null
@@ -1,185 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.shape.ShapeColors;
-import org.distorted.objectlib.shape.ShapeHexahedron;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TBDino4;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverDino4 extends SolverTablebase
-{
-  private static final int ERROR_EDGE_THREE  = -1;
-  private TablebasesAbstract mSolver;
-  private int mErrorColor;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverDino4(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os, res,object);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getEdges(TwistyObject object, int[][] edges)
-    {
-    for(int i=0; i<12; i++)
-      {
-      edges[0][i] = object.getCubitFaceStickerIndex(i,0);
-      edges[1][i] = object.getCubitFaceStickerIndex(i,1);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkEdges(int[][] edges)
-    {
-    int numB=0,numR=0, numY=0, numW=0;
-    int indB=0,indY=0, indW=0, indR=0;
-
-    int[] hexColors = ShapeHexahedron.FACE_COLORS;
-
-    for(int i=0; i<6; i++)
-      {
-      int color = hexColors[i];
-
-      if( color == ShapeColors.COLOR_WHITE) indW = i;
-      if( color == ShapeColors.COLOR_YELLOW) indY = i;
-      if( color == ShapeColors.COLOR_RED) indR = i;
-      if( color == ShapeColors.COLOR_BLUE) indB = i;
-      }
-
-    for(int i=0; i<12; i++)
-      {
-      int e = edges[0][i];
-
-      if( e==edges[1][i] )
-        {
-        if( e==indY ) numY++;
-        if( e==indW ) numW++;
-        if( e==indR ) numR++;
-        if( e==indB ) numB++;
-        }
-      }
-
-    if( numY !=3 ) { mErrorColor=0; return ERROR_EDGE_THREE; }
-    if( numW !=3 ) { mErrorColor=1; return ERROR_EDGE_THREE; }
-    if( numB !=3 ) { mErrorColor=2; return ERROR_EDGE_THREE; }
-    if( numR !=3 ) { mErrorColor=3; return ERROR_EDGE_THREE; }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void remap(int[] perm, int[] edges, int index, int section)
-    {
-    int val = edges[index];
-
-    for(int i=index;i<12; i++)
-      if( edges[i]==val )
-        {
-        edges[i]=-1;
-        perm[i] = section;
-        }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] getPermutation(int[] edges)
-    {
-    int[] perm = new int[12];
-
-    int index0 = 0;
-    remap(perm,edges,index0,0);
-
-    int index1 = index0+1;
-
-    for(;index1<12;index1++)
-      if( edges[index1]>=0 ) break;
-    remap(perm,edges,index1,1);
-
-    int index2 = index1+1;
-
-    for(;index2<12;index2++)
-      if( edges[index2]>=0 ) break;
-    remap(perm,edges,index2,2);
-
-    int index3 = index2+1;
-
-    for(;index3<12;index3++)
-      if( edges[index3]>=0 ) break;
-    remap(perm,edges,index3,3);
-
-    return perm;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] edges = new int[2][12];
-    getEdges(object,edges);
-
-    int result1 = checkEdges(edges);
-    if( result1<0 ) return result1;
-
-    int[] perm = getPermutation(edges[0]);
-    return TBDino4.indexFromPartition(perm);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex7(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_yellow7;
-      case 1: return R.string.color_white7;
-      case 2: return R.string.color_blue7;
-      case 3: return R.string.color_red7;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    int i = getColorIndex7(mErrorColor);
-    String color = res.getString(i);
-
-    return res.getString(R.string.solver_generic_edge_three,color);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.DIN4_3.name() );
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverDino6.java b/src/main/java/org/distorted/solvers/SolverDino6.java
deleted file mode 100644
index b0660e5e..00000000
--- a/src/main/java/org/distorted/solvers/SolverDino6.java
+++ /dev/null
@@ -1,451 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TBDino6;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverDino6 extends SolverTablebase
-{
-  private static final int ERROR_EDGE_MISSING        = -1;
-  private static final int ERROR_EDGE_CANNOT         = -2;
-  private static final int ERROR_EDGE_TWISTED        = -3;
-  private static final int ERROR_EDGE_MONOCHROMATIC  = -4;
-  private static final int ERROR_EDGE_TWICE          = -5;
-  private static final int ERROR_TWO_EDGES           = -6;
-
-  int[][] EDGE_MAP = {
-                      {4,2},{0,4},{4,3},{1,4},
-                      {2,0},{3,0},{3,1},{2,1},
-                      {5,2},{0,5},{5,3},{1,5}
-                     };
-
-  private TablebasesAbstract mSolver;
-  private final int[] mFaceColors;
-  private int mErrorColor1, mErrorColor2;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverDino6(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    mFaceColors = new int[6];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getEdges(TwistyObject object, int[][] edges)
-    {
-    for(int i=0; i<12; i++)
-      {
-      edges[i][0] = object.getCubitFaceStickerIndex(i,0);
-      edges[i][1] = object.getCubitFaceStickerIndex(i,1);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeOpposite(int color, int[][] edges)
-    {
-    int[] buffer = new int[6];
-    for(int i=0; i<6; i++) buffer[i] = 0;
-
-    for(int i=0; i<12; i++)
-      {
-      int e0 = edges[i][0];
-      int e1 = edges[i][1];
-
-      if( e0==color && e1==color)
-        {
-        mErrorColor1 = color;
-        return ERROR_EDGE_MONOCHROMATIC;
-        }
-
-      if( e0==color )
-        {
-        buffer[e1]++;
-
-        if( buffer[e1]>1 )
-          {
-          mErrorColor1 = color;
-          mErrorColor2 = e1;
-          return ERROR_EDGE_TWICE;
-          }
-        }
-
-      if( e1==color )
-        {
-        buffer[e0]++;
-
-        if( buffer[e0]>1 )
-          {
-          mErrorColor1 = color;
-          mErrorColor2 = e0;
-          return ERROR_EDGE_TWICE;
-          }
-        }
-      }
-
-    int total = 0;
-
-    for(int i=0; i<6; i++)
-      if( buffer[i]==1 )
-        {
-        if( i!=color) total++;
-        else
-          {
-          mErrorColor1 = i;
-          return ERROR_EDGE_MONOCHROMATIC;
-          }
-        }
-
-    if( total==4 )
-      for(int i=0; i<6; i++)
-        if( buffer[i]==0 && i!=color ) return i;
-
-    return ERROR_EDGE_CANNOT;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int fillUpRemainingFaceColors(boolean[] buffer)
-    {
-    for(int i=0; i<6; i++)
-      {
-      buffer[i] = false;
-      }
-    for(int i=0; i<6; i++)
-      {
-      int color = mFaceColors[i];
-      if( color>=0 ) buffer[color] = true;
-      }
-
-    int total = 0;
-    for(int i=0; i<6; i++)
-      if( buffer[i] ) total++;
-
-    if( total==4 )
-      {
-      for(int i=0; i<6; i++)
-        if( !buffer[i] )
-          {
-          buffer[i]=true;
-          mFaceColors[0] = i;
-          break;
-          }
-      for(int i=0; i<6; i++)
-        if( !buffer[i] )
-          {
-          buffer[i]=true;
-          mFaceColors[1] = i;
-          break;
-          }
-
-      return 0;
-      }
-
-    return ERROR_EDGE_CANNOT;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int figureOutFaceColors(int[][] edges)
-    {
-    boolean[] present = new boolean[6];
-
-    for(int i=0; i<6; i++) mFaceColors[i] = -1;
-
-    for(int i=0; i<12; i++)
-      {
-      int[] edge = edges[i];
-      int c0 = edge[0];
-      int c1 = edge[1];
-
-      if( c0>=0 && c0<6 ) present[c0] = true;
-      if( c1>=0 && c1<6 ) present[c1] = true;
-      }
-
-    for(int i=0; i<6; i++)
-      if( !present[i] )
-        {
-        mErrorColor1 = i;
-        mErrorColor2 = (i<4 ? i+2 : i-2 );
-        return ERROR_EDGE_MISSING;
-        }
-
-    if( edges[0][0]==edges[0][1] ) return ERROR_EDGE_CANNOT;
-
-    mFaceColors[4] = edges[0][0];
-    mFaceColors[2] = edges[0][1];
-    mFaceColors[5] = computeOpposite(mFaceColors[4], edges);
-    if( mFaceColors[5]<0 ) return mFaceColors[5];
-
-    mFaceColors[3] = computeOpposite(mFaceColors[2], edges);
-    if( mFaceColors[3]<0 ) return mFaceColors[3];
-
-    int success = fillUpRemainingFaceColors(present);
-    if( success<0 ) return success;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean notPresent(int face0, int face1, int[][] edges)
-    {
-    int c0=mFaceColors[face0];
-    int c1=mFaceColors[face1];
-
-    for(int i=0; i<12; i++)
-      {
-      int[] edge = edges[i];
-      if( edge[0]==c0 && edge[1]==c1 ) return false;
-      if( edge[0]==c1 && edge[1]==c0 ) return false;
-      }
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int checkAllPresent(int[][] edges)
-    {
-    for(int i=0; i<12; i++)
-      {
-      int[] map = EDGE_MAP[i];
-      int m1 = map[0];
-      int m2 = map[1];
-
-      if( notPresent(m1,m2,edges) )
-        {
-        mErrorColor1 = mFaceColors[m1];
-        mErrorColor2 = mFaceColors[m2];
-        return ERROR_EDGE_MISSING;
-        }
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean wrongTwist(int face0, int face1, int[][] edges)
-    {
-    int c0=mFaceColors[face0];
-    int c1=mFaceColors[face1];
-
-    for(int i=0; i<12; i++)
-      {
-      int[] edge = edges[i];
-      if( edge[1]==c0 && edge[0]==c1 ) return true;
-      }
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int checkTwist(int[][] edges)
-    {
-    for(int i=0; i<12; i++)
-      {
-      int[] map = EDGE_MAP[i];
-      int m1 = map[0];
-      int m2 = map[1];
-
-      if( wrongTwist(m1,m2,edges) )
-        {
-        mErrorColor1 = mFaceColors[m1];
-        mErrorColor2 = mFaceColors[m2];
-        return ERROR_EDGE_TWISTED;
-        }
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getPerm(int face0, int face1, int[][] edges)
-    {
-    int c0=mFaceColors[face0];
-    int c1=mFaceColors[face1];
-
-    for(int i=0; i<12; i++)
-      {
-      int[] edge = edges[i];
-      if( edge[0]==c0 && edge[1]==c1 ) return i;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computePermutation(int[][] edges, int[] perm)
-    {
-    for(int i=0; i<12; i++)
-      {
-      int[] map = EDGE_MAP[i];
-      int m1 = map[0];
-      int m2 = map[1];
-      perm[i] = getPerm(m1,m2,edges);
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] edges = new int[12][2];
-    getEdges(object,edges);
-
-    int result1 = figureOutFaceColors(edges);
-    if( result1<0 ) return result1;
-
-    int result2 = checkAllPresent(edges);
-    if( result2<0 ) return result2;
-
-    int result3 = checkTwist(edges);
-    if( result3<0 ) return result3;
-
-    int[] perm = new int[12];
-    int result4 = computePermutation(edges,perm);
-    if( result4<0 ) return result4;
-
-    if( !TablebaseHelpers.permutationIsEven(perm) ) return ERROR_TWO_EDGES;
-
-    return TBDino6.getIndexFromPerm(perm);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_yellow3;
-      case 1: return R.string.color_white3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_green3;
-      case 4: return R.string.color_red3;
-      case 5: return R.string.color_orange3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex6(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_yellow6;
-      case 1: return R.string.color_white6;
-      case 2: return R.string.color_blue6;
-      case 3: return R.string.color_green6;
-      case 4: return R.string.color_red6;
-      case 5: return R.string.color_orange6;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeMissingError(Resources res, int color0, int color1)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex6(color1);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeTwistedError(Resources res, int color0, int color1)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex6(color1);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_twisted_edge,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeMonoError(Resources res, int color)
-    {
-    int j0 = getColorIndex3(color);
-    int j1 = getColorIndex6(color);
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_edge_mono,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeTwiceError(Resources res, int color0, int color1)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex6(color1);
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_edge_twice,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_EDGE_MISSING      : return edgeMissingError(res,mErrorColor1,mErrorColor2);
-      case ERROR_EDGE_TWISTED      : return edgeTwistedError(res,mErrorColor1,mErrorColor2);
-      case ERROR_EDGE_MONOCHROMATIC: return edgeMonoError(res,mErrorColor1);
-      case ERROR_EDGE_TWICE        : return edgeTwiceError(res,mErrorColor1,mErrorColor2);
-      case ERROR_EDGE_CANNOT       : return res.getString(R.string.solver_generic_edges_cannot);
-      case ERROR_TWO_EDGES         : return res.getString(R.string.solver_generic_two_edges);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.DINO_3.name() );
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverIvyCube.java b/src/main/java/org/distorted/solvers/SolverIvyCube.java
deleted file mode 100644
index 32bd3153..00000000
--- a/src/main/java/org/distorted/solvers/SolverIvyCube.java
+++ /dev/null
@@ -1,254 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-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 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 TablebaseHelpers.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;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverIvyCube(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,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;
-
-    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 = TablebaseHelpers.computeEvenPermutationNum(center_colors);
-
-    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, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.IVY_2.name() );
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverJing.java b/src/main/java/org/distorted/solvers/SolverJing.java
deleted file mode 100644
index 7ba8863b..00000000
--- a/src/main/java/org/distorted/solvers/SolverJing.java
+++ /dev/null
@@ -1,536 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TBPyraminx;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverJing extends SolverTablebase
-{
-  private static final int ERROR_CORNER_GYB_MISSING = -1;
-  private static final int ERROR_CORNER_GYR_MISSING = -2;
-  private static final int ERROR_CORNER_GBR_MISSING = -3;
-  private static final int ERROR_CORNER_YBR_MISSING = -4;
-
-  private static final int ERROR_EDGE_RB_MISSING    = -5;
-  private static final int ERROR_EDGE_RY_MISSING    = -6;
-  private static final int ERROR_EDGE_RG_MISSING    = -7;
-  private static final int ERROR_EDGE_YB_MISSING    = -8;
-  private static final int ERROR_EDGE_GB_MISSING    = -9;
-  private static final int ERROR_EDGE_GY_MISSING    = -10;
-
-  private static final int ERROR_CENTER_G_MISSING   = -11;
-  private static final int ERROR_CENTER_Y_MISSING   = -12;
-  private static final int ERROR_CENTER_B_MISSING   = -13;
-  private static final int ERROR_CENTER_R_MISSING   = -14;
-
-  private static final int ERROR_CORNERS_CANNOT     = -15;
-  private static final int ERROR_EDGE_TWISTED       = -16;
-  private static final int ERROR_CORNER_TWISTED     = -17;
-  private static final int ERROR_TWO_EDGES          = -18;
-  private static final int ERROR_TWO_CENTERS        = -19;
-
-  private TablebasesAbstract mSolver;
-  private int[] mFaceColors;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverJing(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean pieceEqual3(int[] piece, int c1, int c2, int c3)
-    {
-    return ( (piece[0]==c1 && piece[1]==c2 && piece[2]==c3) ||
-             (piece[0]==c1 && piece[2]==c2 && piece[1]==c3) ||
-             (piece[1]==c1 && piece[0]==c2 && piece[2]==c3) ||
-             (piece[1]==c1 && piece[2]==c2 && piece[0]==c3) ||
-             (piece[2]==c1 && piece[1]==c2 && piece[0]==c3) ||
-             (piece[2]==c1 && piece[0]==c2 && piece[1]==c3)  );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean pieceEqual2(int[] piece, int[] colors)
-    {
-    return ( (piece[0]==colors[0] && piece[1]==colors[1]) ||
-             (piece[0]==colors[1] && piece[1]==colors[0])  );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllCornersPresent(int[][] corners)
-    {
-    boolean ybr = false;
-    boolean gbr = false;
-    boolean gyr = false;
-    boolean gyb = false;
-
-    for(int i=0; i<4; i++)
-      {
-      if( pieceEqual3(corners[i],0,1,2) ) gyb = true;
-      if( pieceEqual3(corners[i],0,1,3) ) gyr = true;
-      if( pieceEqual3(corners[i],0,2,3) ) gbr = true;
-      if( pieceEqual3(corners[i],1,2,3) ) ybr = true;
-      }
-
-    if( !ybr ) return ERROR_CORNER_YBR_MISSING;
-    if( !gbr ) return ERROR_CORNER_GBR_MISSING;
-    if( !gyr ) return ERROR_CORNER_GYR_MISSING;
-    if( !gyb ) return ERROR_CORNER_GYB_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllEdgesPresent(int[][] edges, int[][] edgeColors)
-    {
-    boolean[] present = new boolean[6];
-    for(int i=0; i<6; i++) present[i] = false;
-
-    for(int i=0; i<6; i++)
-      for(int j=0; j<6; j++)
-        if (pieceEqual2(edges[i], edgeColors[j]))
-          {
-          present[j] = true;
-          break;
-          }
-
-    if( !present[0] ) return ERROR_EDGE_RB_MISSING;
-    if( !present[1] ) return ERROR_EDGE_GB_MISSING;
-    if( !present[2] ) return ERROR_EDGE_RG_MISSING;
-    if( !present[3] ) return ERROR_EDGE_YB_MISSING;
-    if( !present[4] ) return ERROR_EDGE_RY_MISSING;
-    if( !present[5] ) return ERROR_EDGE_GY_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllCentersPresent(int[] centers)
-    {
-    boolean[] present = new boolean[4];
-    for(int i=0; i<4; i++) present[i] = false;
-
-    for(int i=0; i<4; i++)
-      {
-      present[centers[i]]= true;
-      }
-
-    if( !present[0] ) return ERROR_CENTER_G_MISSING;
-    if( !present[1] ) return ERROR_CENTER_Y_MISSING;
-    if( !present[2] ) return ERROR_CENTER_B_MISSING;
-    if( !present[3] ) return ERROR_CENTER_R_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] computeFaceColors(int[][] corners)
-    {
-    int[] ret = new int[4];
-
-    for(int i=0; i<4; i++)
-      for(int j=0; j<4; j++)
-        if( corners[i][0]!=j && corners[i][1]!=j && corners[i][2]!=j ) ret[i]=j;
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computePieceTwist(int index, int[] corner, int[] faceColor)
-    {
-    int twist1=0, twist2=0, twist3=0;
-
-    switch(index)
-      {
-      case 0: if( corner[1]==faceColor[1] ) twist1=1;
-              if( corner[2]==faceColor[1] ) twist1=2;
-              if( corner[0]==faceColor[2] ) twist2=2;
-              if( corner[2]==faceColor[2] ) twist2=1;
-              if( corner[0]==faceColor[3] ) twist3=1;
-              if( corner[1]==faceColor[3] ) twist3=2;
-              break;
-      case 1: if( corner[1]==faceColor[0] ) twist1=1;
-              if( corner[2]==faceColor[0] ) twist1=2;
-              if( corner[0]==faceColor[2] ) twist2=1;
-              if( corner[1]==faceColor[2] ) twist2=2;
-              if( corner[0]==faceColor[3] ) twist3=2;
-              if( corner[2]==faceColor[3] ) twist3=1;
-              break;
-      case 2: if( corner[1]==faceColor[0] ) twist1=1;
-              if( corner[2]==faceColor[0] ) twist1=2;
-              if( corner[0]==faceColor[1] ) twist2=2;
-              if( corner[2]==faceColor[1] ) twist2=1;
-              if( corner[0]==faceColor[3] ) twist3=1;
-              if( corner[1]==faceColor[3] ) twist3=2;
-              break;
-      case 3: if( corner[1]==faceColor[0] ) twist1=1;
-              if( corner[2]==faceColor[0] ) twist1=2;
-              if( corner[0]==faceColor[1] ) twist2=1;
-              if( corner[1]==faceColor[1] ) twist2=2;
-              if( corner[0]==faceColor[2] ) twist3=2;
-              if( corner[2]==faceColor[2] ) twist3=1;
-              break;
-      }
-
-    return ( twist1!=twist2 || twist1!=twist3 ) ? ERROR_CORNERS_CANNOT : twist1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int locateEdge(int[][] edges, int[] colors)
-    {
-    for(int i=0; i<6; i++)
-      if( edges[i][0]==colors[0] && edges[i][1]==colors[1] ||
-          edges[i][0]==colors[1] && edges[i][1]==colors[0]  ) return i;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int edgeTwist(int[] edge, int[] colors)
-    {
-    return edge[0]==colors[0] ? 0:1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] computeEdgeQuats(int[][] edges, int[][] edgeColors)
-    {
-    int[] quats = new int[6];
-
-    for(int i=0; i<6; i++)
-      {
-      int pos   = locateEdge(edges,edgeColors[i]);
-      int twist = edgeTwist(edges[pos],edgeColors[i]);
-      quats[i]  = TBPyraminx.EDGE_QUATS[pos][twist];
-      }
-
-    return quats;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// GRB YBR YGB YRG
-
-  private void getCorners(TwistyObject object, int[][] corners)
-    {
-    for(int i=0; i<4; i++)
-      for(int j=0; j<3; j++)
-        corners[i][j] = object.getCubitFaceStickerIndex(i,j);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// RB GB RG YB RY GY
-
-  private void getEdges(TwistyObject object, int[][] edges)
-    {
-    for(int i=0; i<6; i++)
-      for(int j=0; j<2; j++)
-        edges[i][j] = object.getCubitFaceStickerIndex(i+4,j) -4;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// G Y B R
-
-  private void getCenters(TwistyObject object, int[] centers)
-    {
-    for(int i=0; i<4; i++)
-      centers[i] = object.getCubitFaceStickerIndex(i+10,0) - 8;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[][] computeEdgeColors(int[] faceColor)
-    {
-    // The first pair being (2,3) means 'the first edge's first face is on the tetrahedron
-    // face which opposes corner number 2, and its second face on tetra face which opposes
-    // corner number 3'
-    // Order of those pairs determines edge twist.
-
-    final int[][] edgeColorIndices = new int[][] { {2,3},{1,3},{2,1},{0,3},{2,0},{1,0}  };
-    int[][] ret = new int[6][2];
-
-    for(int i=0; i<6; i++)
-      {
-      ret[i][0] = faceColor[edgeColorIndices[i][0]];
-      ret[i][1] = faceColor[edgeColorIndices[i][1]];
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeCenterTwist(int[] centers, int color1, int color2)
-    {
-    if( centers[0]==color1 )
-      {
-      if( centers[1]==color2 ) return 1;
-      if( centers[2]==color2 ) return 2;
-      if( centers[3]==color2 ) return 0;
-
-      return -1;
-      }
-    if( centers[1]==color1 )
-      {
-      if( centers[0]==color2 ) return 1;
-      if( centers[2]==color2 ) return 0;
-      if( centers[3]==color2 ) return 2;
-
-      return -1;
-      }
-    if( centers[2]==color1 )
-      {
-      if( centers[0]==color2 ) return 2;
-      if( centers[1]==color2 ) return 0;
-      if( centers[3]==color2 ) return 1;
-
-      return -1;
-      }
-    if( centers[3]==color1 )
-      {
-      if( centers[0]==color2 ) return 0;
-      if( centers[1]==color2 ) return 2;
-      if( centers[2]==color2 ) return 1;
-
-      return -1;
-      }
-
-    return -2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getFaceOfCenter(int color, int[] centers)
-    {
-    if( centers[0]==color ) return 0;
-    if( centers[1]==color ) return 1;
-    if( centers[2]==color ) return 2;
-    if( centers[3]==color ) return 3;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners   = new int[4][3];
-    int[][] edges     = new int[6][2];
-    int[] centers     = new int[4];
-    int[] corner_twist= new int[4];
-
-    getCorners(object,corners);
-
-    int result1 = checkAllCornersPresent(corners);
-    if( result1<0 ) return result1;
-
-    mFaceColors = computeFaceColors(corners);
-    int[][] edgeColors = computeEdgeColors(mFaceColors);
-
-    getEdges(object,edges);
-    int result2 = checkAllEdgesPresent(edges,edgeColors);
-    if( result2<0 ) return result2;
-
-    getCenters(object,centers);
-    int result3 = checkAllCentersPresent(centers);
-    if( result3<0 ) return result3;
-
-    for(int i=0; i<4; i++)
-      {
-      corner_twist[i] = computePieceTwist(i,corners[i],mFaceColors);
-      if( corner_twist[i]<0 ) return ERROR_CORNERS_CANNOT;
-      }
-
-    int[] quats = computeEdgeQuats(edges,edgeColors);
-    int[] permutation = new int[6];
-    TBPyraminx.getEdgePermutation(permutation,quats,0);
-    boolean even = TablebaseHelpers.permutationIsEven(permutation);
-    if( !even ) return ERROR_TWO_EDGES;
-    int[] edge_twist = new int[6];
-    TBPyraminx.getEdgeTwist(edge_twist,quats,0);
-
-    int twist_gr = computeCenterTwist(centers,mFaceColors[1],mFaceColors[2]);
-    int twist_ry = computeCenterTwist(centers,mFaceColors[2],mFaceColors[0]);
-
-    if( (twist_ry-twist_gr+1)%3 != 0 ) return ERROR_TWO_CENTERS;
-
-    int total_twist=0;
-    for(int i=0; i<4; i++) total_twist += corner_twist[i];
-
-    if( (total_twist-twist_gr)%3 !=0 ) return ERROR_CORNER_TWISTED;
-
-    int green_face = getFaceOfCenter(mFaceColors[1],centers);
-
-    int totalEdgeTwist=0;
-    for(int i=0; i<6; i++) totalEdgeTwist += edge_twist[i];
-    if( (totalEdgeTwist%2)!=0 ) return ERROR_EDGE_TWISTED;
-
-    int vertexTwist = corner_twist[0]+ 3*(corner_twist[1]+ 3*(corner_twist[2]+ 3*corner_twist[3]));
-    int edgeTwist = edge_twist[0]+ 2*(edge_twist[1]+ 2*(edge_twist[2]+ 2*(edge_twist[3]+ 2*edge_twist[4])));
-    int perm_num = TablebaseHelpers.computeEvenPermutationNum(permutation);
-
-    return green_face + 4*(vertexTwist + 81*(edgeTwist + 32*perm_num));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_green3;
-      case 1: return R.string.color_yellow3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_red3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex4(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_green4;
-      case 1: return R.string.color_yellow4;
-      case 2: return R.string.color_blue4;
-      case 3: return R.string.color_red4;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getFaceIndex3(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_green3;
-      case 1: return R.string.color_yellow3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_red3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getFaceIndex6(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_green6;
-      case 1: return R.string.color_yellow6;
-      case 2: return R.string.color_blue6;
-      case 3: return R.string.color_red6;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerError(Resources res, int color0, int color1, int color2)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex3(color1);
-    int j2 = getColorIndex4(color2);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-    String c2 = res.getString(j2);
-
-    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeError(Resources res, int face0, int face1)
-    {
-    int j0 = getFaceIndex3(face0);
-    int j1 = getFaceIndex6(face1);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_YBR_MISSING: return cornerError(res,3,2,1);
-      case ERROR_CORNER_GBR_MISSING: return cornerError(res,3,2,0);
-      case ERROR_CORNER_GYR_MISSING: return cornerError(res,3,1,0);
-      case ERROR_CORNER_GYB_MISSING: return cornerError(res,2,1,0);
-      case ERROR_EDGE_RB_MISSING   : return edgeError(res,3,2);
-      case ERROR_EDGE_RY_MISSING   : return edgeError(res,2,0);
-      case ERROR_EDGE_RG_MISSING   : return edgeError(res,2,1);
-      case ERROR_EDGE_YB_MISSING   : return edgeError(res,3,0);
-      case ERROR_EDGE_GB_MISSING   : return edgeError(res,3,1);
-      case ERROR_EDGE_GY_MISSING   : return edgeError(res,1,0);
-      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_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
-      case ERROR_EDGE_TWISTED      : return res.getString(R.string.solver_generic_edge_twist);
-      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
-      case ERROR_TWO_EDGES         : return res.getString(R.string.solver_generic_two_edges);
-      case ERROR_TWO_CENTERS       : return res.getString(R.string.solver_generic_two_centers);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.JING_2.name());
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverKociembaCUBE3.java b/src/main/java/org/distorted/solvers/SolverKociembaCUBE3.java
new file mode 100644
index 00000000..20364843
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverKociembaCUBE3.java
@@ -0,0 +1,220 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.solverui.ScreenSolver;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.kociemba.SolverSearch;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverKociembaCUBE3 implements SolvingInterface
+{
+  private final Resources mRes;
+  private final OperatingSystemInterface mOS;
+  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_yellow1; return numColors[0]; }
+    if( numColors[1]<9 ) { mColorID = R.string.color_white1 ; return numColors[1]; }
+    if( numColors[2]<9 ) { mColorID = R.string.color_blue1  ; return numColors[2]; }
+    if( numColors[3]<9 ) { mColorID = R.string.color_green1 ; return numColors[3]; }
+    if( numColors[4]<9 ) { mColorID = R.string.color_red1   ; return numColors[4]; }
+    if( numColors[5]<9 ) { mColorID = R.string.color_orange1; 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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(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.getCubitFaceStickerIndex(B_INDEX[i], face);
+      objectString.append(FACE_NAMES[color]);
+      }
+
+    return objectString.toString();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverKociembaCUBE3(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    mRes   = res;
+    mOS    = os;
+    mObject= object;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void solve(ScreenSolver solver)
+    {
+    String result;
+
+    SolverSearch.prepare(mOS);
+    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_generic_edge_twist); break;
+          case '4': result = mRes.getString(R.string.solver_cube3_error4); break;
+          case '5': result = mRes.getString(R.string.solver_generic_corner_twist); 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.displayImpossibleDialog(result);
+        }
+      else
+        {
+        solver.setSolved(result);
+        }
+      }
+    else
+      {
+      String color = mRes.getString(mColorID);
+      result = mRes.getString(R.string.solver_cube3_error1,check,color);
+      solver.displayImpossibleDialog(result);
+      }
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverMain.java b/src/main/java/org/distorted/solvers/SolverMain.java
deleted file mode 100644
index 4e7a1635..00000000
--- a/src/main/java/org/distorted/solvers/SolverMain.java
+++ /dev/null
@@ -1,160 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.main.TwistyObject;
-
-import org.distorted.main.R;
-import org.distorted.objectlib.metadata.MetadataCUBE_2;
-import org.distorted.objectlib.metadata.MetadataCUBE_3;
-import org.distorted.objectlib.metadata.MetadataCU_232;
-import org.distorted.objectlib.metadata.MetadataCU_323;
-import org.distorted.objectlib.metadata.MetadataDIAM_2;
-import org.distorted.objectlib.metadata.MetadataDIN4_3;
-import org.distorted.objectlib.metadata.MetadataDINO_3;
-import org.distorted.objectlib.metadata.MetadataIVY_2;
-import org.distorted.objectlib.metadata.MetadataJING_2;
-import org.distorted.objectlib.metadata.MetadataPDIA_3;
-import org.distorted.objectlib.metadata.MetadataPDUO_2;
-import org.distorted.objectlib.metadata.MetadataPYRA_3;
-import org.distorted.objectlib.metadata.MetadataSKEW_2;
-import org.distorted.solverui.ScreenList;
-import org.distorted.solverui.ScreenSolver;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverMain implements Runnable
-{
-  private final Resources mRes;
-  private final OperatingSystemInterface mOS;
-  private final long mSignature;
-  private final TwistyObject mObject;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverMain(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    mRes       = res;
-    mOS        = os;
-    mObject    = object;
-    long[] sig = object.getSignature().getArray();
-    mSignature = sig[sig.length-1];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// certain objects have certain cubits locked - for example, the Cube3's centers of
-// sides always have the same color.
-// If a certain cubit is locked, return the color (index into it's FACE_COLORS array) it
-// must have. Otherwise return -1.
-
-  public static int cubitIsLocked(int object, int cubit)
-    {
-    if( object == MetadataCUBE_3.INDEX )
-      {
-      if( cubit==20 ) return 0; // center of the right  face
-      if( cubit==21 ) return 1; // center of the left   face
-      if( cubit==22 ) return 2; // center of the up     face
-      if( cubit==23 ) return 3; // center of the bottom face
-      if( cubit==24 ) return 4; // center of the front  face
-      if( cubit==25 ) return 5; // center of the back   face
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void start()
-    {
-    Thread thr = new Thread(this);
-    thr.start();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void run()
-    {
-    ScreenSolver screen = (ScreenSolver) ScreenList.SVER.getScreenClass();
-
-    if( mSignature == MetadataCUBE_3.INDEX )
-      {
-      SolverCube3 solver = new SolverCube3(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataPDUO_2.INDEX )
-      {
-      SolverTablebase solver = new SolverPyraminxDuo(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataIVY_2.INDEX )
-      {
-      SolverTablebase solver = new SolverIvyCube(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataCU_232.INDEX )
-      {
-      SolverTablebase solver = new SolverCuboid232(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataCU_323.INDEX )
-      {
-      SolverTablebase solver = new SolverCuboid323(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataPYRA_3.INDEX )
-      {
-      SolverTablebase solver = new SolverPyraminx(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataDIAM_2.INDEX )
-      {
-      SolverTablebase solver = new SolverSkewbDiamond(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataCUBE_2.INDEX )
-      {
-      SolverTablebase solver = new SolverCube2(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataJING_2.INDEX )
-      {
-      SolverTablebase solver = new SolverJing(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataSKEW_2.INDEX )
-      {
-      SolverTablebase solver = new SolverSkewb(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataDINO_3.INDEX )
-      {
-      SolverTablebase solver = new SolverDino6(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataDIN4_3.INDEX )
-      {
-      SolverTablebase solver = new SolverDino4(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else if( mSignature == MetadataPDIA_3.INDEX )
-      {
-      SolverTablebase solver = new SolverPyraminxDiamond(mOS,mRes,mObject);
-      solver.solve(screen);
-      }
-    else
-      {
-      screen.displayErrorDialog(mRes.getString(R.string.solver_generic_not_implemented));
-      }
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverPyraminx.java b/src/main/java/org/distorted/solvers/SolverPyraminx.java
deleted file mode 100644
index 396ccf57..00000000
--- a/src/main/java/org/distorted/solvers/SolverPyraminx.java
+++ /dev/null
@@ -1,549 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-import org.distorted.objectlib.tablebases.TBPyraminx;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverPyraminx extends SolverTablebase
-{
-  private static final int ERROR_CORNER_GYB_MISSING = -1;
-  private static final int ERROR_CORNER_GYR_MISSING = -2;
-  private static final int ERROR_CORNER_GBR_MISSING = -3;
-  private static final int ERROR_CORNER_YBR_MISSING = -4;
-
-  private static final int ERROR_VERTEX_GYB_MISSING = -5;
-  private static final int ERROR_VERTEX_GYR_MISSING = -6;
-  private static final int ERROR_VERTEX_GBR_MISSING = -7;
-  private static final int ERROR_VERTEX_YBR_MISSING = -8;
-
-  private static final int ERROR_EDGE_RB_MISSING = -9;
-  private static final int ERROR_EDGE_RY_MISSING = -10;
-  private static final int ERROR_EDGE_RG_MISSING = -11;
-  private static final int ERROR_EDGE_YB_MISSING = -12;
-  private static final int ERROR_EDGE_GB_MISSING = -13;
-  private static final int ERROR_EDGE_GY_MISSING = -14;
-
-  private static final int ERROR_CORNERS_CANNOT   = -15;
-  private static final int ERROR_VERTICES_CANNOT  = -16;
-  private static final int ERROR_EDGE_TWISTED     = -17;
-  private static final int ERROR_C_V_DONT_MATCH   = -18;
-  private static final int ERROR_TWO_EDGES        = -19;
-
-  private TablebasesAbstract mSolver;
-  private int[] mCornerTwist;
-  private int[] mFaceColors;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean pieceEqual3(int[] piece, int c1, int c2, int c3)
-    {
-    return ( (piece[0]==c1 && piece[1]==c2 && piece[2]==c3) ||
-             (piece[0]==c1 && piece[2]==c2 && piece[1]==c3) ||
-             (piece[1]==c1 && piece[0]==c2 && piece[2]==c3) ||
-             (piece[1]==c1 && piece[2]==c2 && piece[0]==c3) ||
-             (piece[2]==c1 && piece[1]==c2 && piece[0]==c3) ||
-             (piece[2]==c1 && piece[0]==c2 && piece[1]==c3)  );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean pieceEqual2(int[] piece, int[] colors)
-    {
-    return ( (piece[0]==colors[0] && piece[1]==colors[1]) ||
-             (piece[0]==colors[1] && piece[1]==colors[0])  );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllCornersPresent(int[][] corners)
-    {
-    boolean ybr = false;
-    boolean gbr = false;
-    boolean gyr = false;
-    boolean gyb = false;
-
-    for(int i=0; i<4; i++)
-      {
-      if( pieceEqual3(corners[i],0,1,2) ) gyb = true;
-      if( pieceEqual3(corners[i],0,1,3) ) gyr = true;
-      if( pieceEqual3(corners[i],0,2,3) ) gbr = true;
-      if( pieceEqual3(corners[i],1,2,3) ) ybr = true;
-      }
-
-    if( !ybr ) return ERROR_CORNER_YBR_MISSING;
-    if( !gbr ) return ERROR_CORNER_GBR_MISSING;
-    if( !gyr ) return ERROR_CORNER_GYR_MISSING;
-    if( !gyb ) return ERROR_CORNER_GYB_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllVerticesPresent(int[][] vertex)
-    {
-    boolean ybr = false;
-    boolean gbr = false;
-    boolean gyr = false;
-    boolean gyb = false;
-
-    for(int i=0; i<4; i++)
-      {
-      if( pieceEqual3(vertex[i],0,1,2) ) gyb = true;
-      if( pieceEqual3(vertex[i],0,1,3) ) gyr = true;
-      if( pieceEqual3(vertex[i],0,2,3) ) gbr = true;
-      if( pieceEqual3(vertex[i],1,2,3) ) ybr = true;
-      }
-
-    if( !ybr ) return ERROR_VERTEX_YBR_MISSING;
-    if( !gbr ) return ERROR_VERTEX_GBR_MISSING;
-    if( !gyr ) return ERROR_VERTEX_GYR_MISSING;
-    if( !gyb ) return ERROR_VERTEX_GYB_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllEdgesPresent(int[][] edges, int[][] edgeColors)
-    {
-    boolean[] present = new boolean[6];
-    for(int i=0; i<6; i++) present[i] = false;
-
-    for(int i=0; i<6; i++)
-      for(int j=0; j<6; j++)
-        if (pieceEqual2(edges[i], edgeColors[j]))
-          {
-          present[j] = true;
-          break;
-          }
-
-    if( !present[0] ) return ERROR_EDGE_RB_MISSING;
-    if( !present[1] ) return ERROR_EDGE_RY_MISSING;
-    if( !present[2] ) return ERROR_EDGE_RG_MISSING;
-    if( !present[3] ) return ERROR_EDGE_YB_MISSING;
-    if( !present[4] ) return ERROR_EDGE_GB_MISSING;
-    if( !present[5] ) return ERROR_EDGE_GY_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] computeFaceColors(int[][] corners)
-    {
-    int[] ret = new int[4];
-
-    for(int i=0; i<4; i++)
-      for(int j=0; j<4; j++)
-        if( corners[i][0]!=j && corners[i][1]!=j && corners[i][2]!=j ) ret[i]=j;
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAgreement(int[] faces1, int[] faces2)
-    {
-    for(int i=0; i<4; i++)
-      if( faces1[i]!=faces2[i] ) return ERROR_C_V_DONT_MATCH;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computePieceTwist(int index, int[] corner, int[] faceColor)
-    {
-    int twist1=0, twist2=0, twist3=0;
-
-    switch(index)
-      {
-      case 0: if( corner[1]==faceColor[1] ) twist1=1;
-              if( corner[2]==faceColor[1] ) twist1=2;
-              if( corner[0]==faceColor[2] ) twist2=2;
-              if( corner[2]==faceColor[2] ) twist2=1;
-              if( corner[0]==faceColor[3] ) twist3=1;
-              if( corner[1]==faceColor[3] ) twist3=2;
-              break;
-      case 1: if( corner[1]==faceColor[0] ) twist1=1;
-              if( corner[2]==faceColor[0] ) twist1=2;
-              if( corner[0]==faceColor[2] ) twist2=1;
-              if( corner[1]==faceColor[2] ) twist2=2;
-              if( corner[0]==faceColor[3] ) twist3=2;
-              if( corner[2]==faceColor[3] ) twist3=1;
-              break;
-      case 2: if( corner[1]==faceColor[0] ) twist1=1;
-              if( corner[2]==faceColor[0] ) twist1=2;
-              if( corner[0]==faceColor[1] ) twist2=2;
-              if( corner[2]==faceColor[1] ) twist2=1;
-              if( corner[0]==faceColor[3] ) twist3=1;
-              if( corner[1]==faceColor[3] ) twist3=2;
-              break;
-      case 3: if( corner[1]==faceColor[0] ) twist1=1;
-              if( corner[2]==faceColor[0] ) twist1=2;
-              if( corner[0]==faceColor[1] ) twist2=1;
-              if( corner[1]==faceColor[1] ) twist2=2;
-              if( corner[0]==faceColor[2] ) twist3=2;
-              if( corner[2]==faceColor[2] ) twist3=1;
-              break;
-      }
-
-    return ( twist1!=twist2 || twist1!=twist3 ) ? ERROR_CORNERS_CANNOT : twist1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int locateEdge(int[][] edges, int[] colors)
-    {
-    for(int i=0; i<6; i++)
-      if( edges[i][0]==colors[0] && edges[i][1]==colors[1] ||
-          edges[i][0]==colors[1] && edges[i][1]==colors[0]  ) return i;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int edgeTwist(int[] edge, int[] colors)
-    {
-    return edge[0]==colors[0] ? 0:1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] computeEdgeQuats(int[][] edges, int[][] edgeColors)
-    {
-    int[] quats = new int[6];
-
-    for(int i=0; i<6; i++)
-      {
-      int pos   = locateEdge(edges,edgeColors[i]);
-      int twist = edgeTwist(edges[pos],edgeColors[i]);
-      quats[i]  = TBPyraminx.EDGE_QUATS[pos][twist];
-      }
-
-    return quats;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverPyraminx(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCorners(TwistyObject object, int[][] corners)
-    {
-    corners[0][0] = object.getCubitFaceStickerIndex(4,0);  // G
-    corners[0][1] = object.getCubitFaceStickerIndex(4,3);  // R
-    corners[0][2] = object.getCubitFaceStickerIndex(4,2);  // B
-
-    corners[1][0] = object.getCubitFaceStickerIndex(6,1);  // Y
-    corners[1][1] = object.getCubitFaceStickerIndex(6,2);  // B
-    corners[1][2] = object.getCubitFaceStickerIndex(6,3);  // R
-
-    corners[2][0] = object.getCubitFaceStickerIndex(11,1);  // Y
-    corners[2][1] = object.getCubitFaceStickerIndex(11,0);  // G
-    corners[2][2] = object.getCubitFaceStickerIndex(11,2);  // B
-
-    corners[3][0] = object.getCubitFaceStickerIndex(13,1);  // Y
-    corners[3][1] = object.getCubitFaceStickerIndex(13,3);  // R
-    corners[3][2] = object.getCubitFaceStickerIndex(13,0);  // G
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getVertices(TwistyObject object, int[][] vertex)
-    {
-    vertex[0][0] = object.getCubitFaceStickerIndex(0,0);  // G
-    vertex[0][1] = object.getCubitFaceStickerIndex(0,5);  // R
-    vertex[0][2] = object.getCubitFaceStickerIndex(0,7);  // B
-
-    vertex[1][0] = object.getCubitFaceStickerIndex(1,2);  // Y
-    vertex[1][1] = object.getCubitFaceStickerIndex(1,7);  // B
-    vertex[1][2] = object.getCubitFaceStickerIndex(1,5);  // R
-
-    vertex[2][0] = object.getCubitFaceStickerIndex(2,2);  // Y
-    vertex[2][1] = object.getCubitFaceStickerIndex(2,0);  // G
-    vertex[2][2] = object.getCubitFaceStickerIndex(2,7);  // B
-
-    vertex[3][0] = object.getCubitFaceStickerIndex(3,2);  // Y
-    vertex[3][1] = object.getCubitFaceStickerIndex(3,5);  // R
-    vertex[3][2] = object.getCubitFaceStickerIndex(3,0);  // G
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getEdges(TwistyObject object, int[][] edges)
-    {
-    edges[0][0] = object.getCubitFaceStickerIndex(5,3);  // R
-    edges[0][1] = object.getCubitFaceStickerIndex(5,2);  // B
-
-    edges[1][0] = object.getCubitFaceStickerIndex(10,1); // Y
-    edges[1][1] = object.getCubitFaceStickerIndex(10,3); // R
-
-    edges[2][0] = object.getCubitFaceStickerIndex(9,0);  // G
-    edges[2][1] = object.getCubitFaceStickerIndex(9,3);  // R
-
-    edges[3][0] = object.getCubitFaceStickerIndex(8,2);  // B
-    edges[3][1] = object.getCubitFaceStickerIndex(8,1);  // Y
-
-    edges[4][0] = object.getCubitFaceStickerIndex(7,2);  // B
-    edges[4][1] = object.getCubitFaceStickerIndex(7,0);  // G
-
-    edges[5][0] = object.getCubitFaceStickerIndex(12,1); // Y
-    edges[5][1] = object.getCubitFaceStickerIndex(12,0); // G
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[][] computeEdgeColors(int[] faceColor)
-    {
-    // The first pair being (2,3) means 'the first edge's first face is on the tetrahedron
-    // face which oppeses corner number 2, and its second face on tetra face which opposes
-    // corner number 3'
-    // Order of those pairs determines edge twist.
-
-    final int[][] edgeColorIndices = new int[][] { {2,3},{0,2},{1,2},{3,0},{3,1},{0,1}  };
-    int[][] ret = new int[6][2];
-
-    for(int i=0; i<6; i++)
-      {
-      ret[i][0] = faceColor[edgeColorIndices[i][0]];
-      ret[i][1] = faceColor[edgeColorIndices[i][1]];
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners = new int[4][3];
-    int[][] edges   = new int[6][2];
-    int[][] vertices= new int[4][3];
-    int[] vertex_twist = new int[4];
-    mCornerTwist = new int[4];
-
-    getCorners(object,corners);
-    getVertices(object,vertices);
-
-    int result1 = checkAllCornersPresent(corners);
-    if( result1<0 ) return result1;
-
-    int result2 = checkAllVerticesPresent(vertices);
-    if( result2<0 ) return result2;
-
-    int[] facesC = computeFaceColors(corners);
-    int[] facesV = computeFaceColors(vertices);
-
-    int result3 = checkAgreement(facesC,facesV);
-    if( result3<0 ) return result3;
-
-    mFaceColors = facesC;
-
-    int[][] edgeColors = computeEdgeColors(mFaceColors);
-
-    getEdges(object,edges);
-    int result4 = checkAllEdgesPresent(edges,edgeColors);
-    if( result4<0 ) return result4;
-
-    for(int i=0; i<4; i++)
-      {
-      mCornerTwist[i] = computePieceTwist(i,corners[i],mFaceColors);
-      if( mCornerTwist[i]<0 ) return ERROR_CORNERS_CANNOT;
-      }
-
-    for(int i=0; i<4; i++)
-      {
-      vertex_twist[i] = computePieceTwist(i,vertices[i],mFaceColors);
-      if( vertex_twist[i]<0 ) return ERROR_VERTICES_CANNOT;
-      }
-
-    int[] quats = computeEdgeQuats(edges,edgeColors);
-    int[] permutation = new int[6];
-    TBPyraminx.getEdgePermutation(permutation,quats,0);
-    boolean even = TablebaseHelpers.permutationIsEven(permutation);
-    if( !even ) return ERROR_TWO_EDGES;
-    int[] edge_twist = new int[6];
-    TBPyraminx.getEdgeTwist(edge_twist,quats,0);
-
-    int totalEdgeTwist=0;
-    for(int i=0; i<6; i++) totalEdgeTwist += edge_twist[i];
-    if( (totalEdgeTwist%2)!=0 ) return ERROR_EDGE_TWISTED;
-
-    int vertexTwist = vertex_twist[0]+ 3*(vertex_twist[1]+ 3*(vertex_twist[2]+ 3*vertex_twist[3]));
-    int edgeTwist = edge_twist[0]+ 2*(edge_twist[1]+ 2*(edge_twist[2]+ 2*(edge_twist[3]+ 2*edge_twist[4])));
-    int perm_num = TablebaseHelpers.computeEvenPermutationNum(permutation);
-
-    return vertexTwist + 81*(edgeTwist + 32*perm_num);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_green3;
-      case 1: return R.string.color_yellow3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_red3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex4(int color)
-    {
-    switch(color)
-      {
-      case 0: return R.string.color_green4;
-      case 1: return R.string.color_yellow4;
-      case 2: return R.string.color_blue4;
-      case 3: return R.string.color_red4;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getFaceIndex3(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_green3;
-      case 1: return R.string.color_yellow3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_red3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getFaceIndex6(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_green6;
-      case 1: return R.string.color_yellow6;
-      case 2: return R.string.color_blue6;
-      case 3: return R.string.color_red6;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerError(Resources res, int color0, int color1, int color2)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex3(color1);
-    int j2 = getColorIndex4(color2);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-    String c2 = res.getString(j2);
-
-    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String vertexError(Resources res, int color0, int color1, int color2)
-    {
-    int j0 = getColorIndex3(color0);
-    int j1 = getColorIndex3(color1);
-    int j2 = getColorIndex4(color2);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-    String c2 = res.getString(j2);
-
-    return res.getString(R.string.solver_generic_missing_vertex,c0,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String edgeError(Resources res, int color0, int color1)
-    {
-    int j0 = getFaceIndex3(color0);
-    int j1 = getFaceIndex6(color1);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-
-    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_YBR_MISSING: return cornerError(res,3,2,1);
-      case ERROR_CORNER_GBR_MISSING: return cornerError(res,3,2,0);
-      case ERROR_CORNER_GYR_MISSING: return cornerError(res,3,1,0);
-      case ERROR_CORNER_GYB_MISSING: return cornerError(res,2,1,0);
-      case ERROR_VERTEX_YBR_MISSING: return vertexError(res,3,2,1);
-      case ERROR_VERTEX_GBR_MISSING: return vertexError(res,3,2,0);
-      case ERROR_VERTEX_GYR_MISSING: return vertexError(res,3,1,0);
-      case ERROR_VERTEX_GYB_MISSING: return vertexError(res,2,1,0);
-      case ERROR_EDGE_RB_MISSING   : return edgeError(res,3,2);
-      case ERROR_EDGE_RY_MISSING   : return edgeError(res,2,0);
-      case ERROR_EDGE_RG_MISSING   : return edgeError(res,2,1);
-      case ERROR_EDGE_YB_MISSING   : return edgeError(res,3,0);
-      case ERROR_EDGE_GB_MISSING   : return edgeError(res,3,1);
-      case ERROR_EDGE_GY_MISSING   : return edgeError(res,1,0);
-      case ERROR_EDGE_TWISTED      : return res.getString(R.string.solver_generic_edge_twist);
-      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
-      case ERROR_VERTICES_CANNOT   : return res.getString(R.string.solver_generic_vertices_cannot);
-      case ERROR_C_V_DONT_MATCH    : return res.getString(R.string.solver_generic_c_v_dont_match);
-      case ERROR_TWO_EDGES         : return res.getString(R.string.solver_generic_two_edges);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.PYRA_3.name());
-      }
-
-    return mSolver!=null ? mSolver.solution(index,mCornerTwist,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverPyraminxDiamond.java b/src/main/java/org/distorted/solvers/SolverPyraminxDiamond.java
deleted file mode 100644
index c74de1a2..00000000
--- a/src/main/java/org/distorted/solvers/SolverPyraminxDiamond.java
+++ /dev/null
@@ -1,375 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverPyraminxDiamond extends SolverTablebase
-{
-  private static final int ERROR_CORNER_FR_MISSING = -1;
-  private static final int ERROR_CORNER_BR_MISSING = -2;
-  private static final int ERROR_CORNER_BL_MISSING = -3;
-  private static final int ERROR_CORNER_FL_MISSING = -4;
-  private static final int ERROR_CORNER_TO_MISSING = -5;
-  private static final int ERROR_CORNER_BO_MISSING = -6;
-
-  private static final int ERROR_CENTER_0_MISSING = -7;
-  private static final int ERROR_CENTER_1_MISSING = -8;
-  private static final int ERROR_CENTER_2_MISSING = -9;
-  private static final int ERROR_CENTER_3_MISSING = -10;
-  private static final int ERROR_CENTER_4_MISSING = -11;
-  private static final int ERROR_CENTER_5_MISSING = -12;
-  private static final int ERROR_CENTER_6_MISSING = -13;
-  private static final int ERROR_CENTER_7_MISSING = -14;
-
-  private static final int ERROR_TWO_CENTERS      = -15;
-  private static final int ERROR_CORNERS_CANNOT   = -16;
-
-  private TablebasesAbstract mSolver;
-  private final int[] mFaceColors;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverPyraminxDiamond(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    mFaceColors = new int[8];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retCenter(int color, int[] centers)
-    {
-    for(int i=0; i<8; i++ )
-      if( centers[i]==color ) return i;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] getCentersPermutation(int[] centers)
-    {
-    int[] perm = new int[8];
-
-    for(int i=0; i<8; i++ )
-      perm[i] = retCenter(mFaceColors[i],centers);
-
-    return perm;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean isTwistEven(int[] twist)
-    {
-    int total = twist[0]+twist[1]+twist[2]+twist[3]+twist[4]+twist[5];
-    return ((total%2)==0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int indexOf(int[] corner, int color )
-    {
-    if( corner[0]==color ) return 0;
-    if( corner[1]==color ) return 1;
-    if( corner[2]==color ) return 2;
-    if( corner[3]==color ) return 3;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] getCornersTwist(int[][] corners)
-    {
-    int[] twist = new int[6];
-
-    twist[0] = indexOf(corners[0],mFaceColors[0]);
-    twist[1] = indexOf(corners[1],mFaceColors[5]);
-    twist[2] = indexOf(corners[2],mFaceColors[2]);
-    twist[3] = indexOf(corners[3],mFaceColors[4]);
-    twist[4] = indexOf(corners[4],mFaceColors[2]);
-    twist[5] = indexOf(corners[5],mFaceColors[6]);
-
-
-    return twist;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllColorsDifferent()
-    {
-    for(int i=0; i<8; i++)
-      {
-      boolean present = false;
-
-      for(int j=0; j<8; j++)
-        if( mFaceColors[j]==i )
-          {
-          present=true;
-          break;
-          }
-
-      if( !present ) return ERROR_CORNERS_CANNOT;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllCentersPresent(int[] centers)
-    {
-    for(int i=0; i<8; i++)
-      {
-      boolean present = false;
-
-      for(int j=0; j<8; j++)
-        if( centers[j]==i )
-          {
-          present=true;
-          break;
-          }
-
-      if( !present )
-        {
-        switch(i)
-          {
-          case 0: return ERROR_CENTER_0_MISSING;
-          case 1: return ERROR_CENTER_1_MISSING;
-          case 2: return ERROR_CENTER_2_MISSING;
-          case 3: return ERROR_CENTER_3_MISSING;
-          case 4: return ERROR_CENTER_4_MISSING;
-          case 5: return ERROR_CENTER_5_MISSING;
-          case 6: return ERROR_CENTER_6_MISSING;
-          case 7: return ERROR_CENTER_7_MISSING;
-          }
-        }
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int commonColor(int[][] corners, int index1, int index2, int index3)
-    {
-    int[] c1 = corners[index1];
-    int[] c2 = corners[index2];
-    int[] c3 = corners[index3];
-
-    for(int i=0; i<4; i++)
-      {
-      int c = c1[i];
-
-      if( (c2[0]==c || c2[1]==c || c2[2]==c || c2[3]==c) &&
-          (c3[0]==c || c3[1]==c || c3[2]==c || c3[3]==c)  ) return c;
-      }
-
-    return ERROR_CORNERS_CANNOT;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int figureOutFaceColors(int[] output, int[][] corners)
-    {
-    int[][] commonCorners = {{0,2,4},{0,3,5},{1,2,4},{1,3,5},{0,2,3},{1,2,3},{0,4,5},{1,4,5}};
-
-    for(int i=0; i<8; i++)
-      {
-      int[] common = commonCorners[i];
-      output[i] = commonColor(corners,common[0],common[1],common[2]);
-      if( output[i]<0 ) return output[i];
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCorners(TwistyObject object, int[][] corners)
-    {
-    for(int i=0; i<6; i++)
-      for(int j=0; j<4; j++)
-         corners[i][j] = object.getCubitFaceStickerIndex(i,j);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCenters(TwistyObject object, int[] centers)
-    {
-    for(int i=0; i<8; i++)
-       centers[i] = object.getCubitFaceStickerIndex(6+i,0)-8;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners = new int[6][4];
-    int[] centers = new int[8];
-
-    getCorners(object,corners);
-    getCenters(object,centers);
-
-    int result1 = figureOutFaceColors(mFaceColors,corners);
-    if( result1<0 ) return result1;
-
-    int result2 = checkAllCentersPresent(centers);
-    if( result2<0 ) return result2;
-
-    int result3 = checkAllColorsDifferent();
-    if( result3<0 ) return result3;
-
-    int[] twist = getCornersTwist(corners);
-    boolean even1 = isTwistEven(twist);
-
-    int[] centers_perm = getCentersPermutation(centers);
-    boolean even2 = TablebaseHelpers.permutationIsEven(centers_perm);
-    if( even1^even2 ) return ERROR_TWO_CENTERS;
-
-    int centers_perm_num = TablebaseHelpers.computePermutationNum(centers_perm);
-    int total_twist = twist[0]+ 4*(twist[1]+ 4*(twist[2]+ 4*(twist[3]+ 4*(twist[4]+ 4*(twist[5]>1 ? 1:0)))));
-
-/*
-android.util.Log.e("D", "faces: "+mFaceColors[0]+" "+mFaceColors[1]+" "+mFaceColors[2]+" "
-+mFaceColors[3]+" "+mFaceColors[4]+" "+mFaceColors[5]+" "+mFaceColors[6]+" "+mFaceColors[7]);
-android.util.Log.e("D", "corn twist: "+twist[0]+" "+twist[1]+" "+twist[2]+" "+twist[3]+" "+twist[4]+" "+twist[5]);
-android.util.Log.e("D", "ret="+(total_twist + 2048*centers_perm_num) );
-*/
-    return total_twist + 2048*centers_perm_num;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex4(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_violet4;
-      case 1: return R.string.color_grey4;
-      case 2: return R.string.color_blue4;
-      case 3: return R.string.color_red4;
-      case 4: return R.string.color_orange4;
-      case 5: return R.string.color_green4;
-      case 6: return R.string.color_white4;
-      case 7: return R.string.color_yellow4;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_violet3;
-      case 1: return R.string.color_grey3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_red3;
-      case 4: return R.string.color_orange3;
-      case 5: return R.string.color_green3;
-      case 6: return R.string.color_white3;
-      case 7: return R.string.color_yellow3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex2(int face)
-    {
-    switch(face)
-      {
-      case 0: return R.string.color_violet2;
-      case 1: return R.string.color_grey2;
-      case 2: return R.string.color_blue2;
-      case 3: return R.string.color_red2;
-      case 4: return R.string.color_orange2;
-      case 5: return R.string.color_green2;
-      case 6: return R.string.color_white2;
-      case 7: return R.string.color_yellow2;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String centerError(Resources res, int face)
-    {
-    String color = res.getString(getColorIndex2(face));
-    return res.getString(R.string.solver_generic_missing_center,color);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerError(Resources res, int f1, int f2)
-    {
-    String c1 = res.getString(getColorIndex3(f1));
-    String c2 = res.getString(getColorIndex4(f2));
-    return res.getString(R.string.solver_generic_missing_corner2,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_FR_MISSING: return cornerError(res,1,4);
-      case ERROR_CORNER_BR_MISSING: return cornerError(res,1,6);
-      case ERROR_CORNER_BL_MISSING: return cornerError(res,3,6);
-      case ERROR_CORNER_FL_MISSING: return cornerError(res,3,4);
-      case ERROR_CORNER_TO_MISSING: return cornerError(res,1,3);
-      case ERROR_CORNER_BO_MISSING: return cornerError(res,4,6);
-
-      case ERROR_CENTER_0_MISSING : return centerError(res,0);
-      case ERROR_CENTER_1_MISSING : return centerError(res,1);
-      case ERROR_CENTER_2_MISSING : return centerError(res,2);
-      case ERROR_CENTER_3_MISSING : return centerError(res,3);
-      case ERROR_CENTER_4_MISSING : return centerError(res,4);
-      case ERROR_CENTER_5_MISSING : return centerError(res,5);
-      case ERROR_CENTER_6_MISSING : return centerError(res,6);
-      case ERROR_CENTER_7_MISSING : return centerError(res,7);
-
-      case ERROR_TWO_CENTERS      : 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, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.PDIA_3.name() );
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverPyraminxDuo.java b/src/main/java/org/distorted/solvers/SolverPyraminxDuo.java
deleted file mode 100644
index 39234ec8..00000000
--- a/src/main/java/org/distorted/solvers/SolverPyraminxDuo.java
+++ /dev/null
@@ -1,299 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverPyraminxDuo extends SolverTablebase
-{
-  private static final int ERROR_CORNER_GYB_MISSING = -1;
-  private static final int ERROR_CORNER_GYR_MISSING = -2;
-  private static final int ERROR_CORNER_GBR_MISSING = -3;
-  private static final int ERROR_CORNER_YBR_MISSING = -4;
-
-  private static final int ERROR_CENTER_G_MISSING = -5;
-  private static final int ERROR_CENTER_Y_MISSING = -6;
-  private static final int ERROR_CENTER_B_MISSING = -7;
-  private static final int ERROR_CENTER_R_MISSING = -8;
-
-  private static final int ERROR_TWO_CENTERS      = -9;
-  private static final int ERROR_CORNER_TWISTED   = -10;
-
-  private static final int ERROR_CORNERS_CANNOT   = -11;
-
-  TablebasesAbstract mSolver;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean cornerEqual(int[] corner, int c1, int c2, int c3)
-    {
-    return ( (corner[0]==c1 && corner[1]==c2 && corner[2]==c3) ||
-             (corner[0]==c1 && corner[2]==c2 && corner[1]==c3) ||
-             (corner[1]==c1 && corner[0]==c2 && corner[2]==c3) ||
-             (corner[1]==c1 && corner[2]==c2 && corner[0]==c3) ||
-             (corner[2]==c1 && corner[1]==c2 && corner[0]==c3) ||
-             (corner[2]==c1 && corner[0]==c2 && corner[1]==c3)  );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllCornersPresent(int[][] corners)
-    {
-    boolean ybr = false;
-    boolean gbr = false;
-    boolean gyr = false;
-    boolean gyb = false;
-
-    for(int i=0; i<4; i++)
-      {
-      if( cornerEqual(corners[i],0,1,2) ) gyb = true;
-      if( cornerEqual(corners[i],0,1,3) ) gyr = true;
-      if( cornerEqual(corners[i],0,2,3) ) gbr = true;
-      if( cornerEqual(corners[i],1,2,3) ) ybr = true;
-      }
-
-    if( !ybr ) return ERROR_CORNER_YBR_MISSING;
-    if( !gbr ) return ERROR_CORNER_GBR_MISSING;
-    if( !gyr ) return ERROR_CORNER_GYR_MISSING;
-    if( !gyb ) return ERROR_CORNER_GYB_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllCentersPresent(int[] centers)
-    {
-    if( centers[0]!=0 && centers[1]!=0 && centers[2]!=0 && centers[3]!=0 ) return ERROR_CENTER_G_MISSING;
-    if( centers[0]!=1 && centers[1]!=1 && centers[2]!=1 && centers[3]!=1 ) return ERROR_CENTER_Y_MISSING;
-    if( centers[0]!=2 && centers[1]!=2 && centers[2]!=2 && centers[3]!=2 ) return ERROR_CENTER_B_MISSING;
-    if( centers[0]!=3 && centers[1]!=3 && centers[2]!=3 && centers[3]!=3 ) return ERROR_CENTER_R_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getFaceOfCenter(int color, int[] centers)
-    {
-    if( centers[0]==color ) return 0;
-    if( centers[1]==color ) return 1;
-    if( centers[2]==color ) return 2;
-    if( centers[3]==color ) return 3;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] computeFaceColor(int[][] corners)
-    {
-    int[] ret = new int[4];
-
-    for(int i=0; i<4; i++)
-      for(int j=0; j<4; j++)
-        if( corners[i][0]!=j && corners[i][1]!=j && corners[i][2]!=j ) ret[i]=j;
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeCornerTwist(int index, int[] corner, int[] missing)
-    {
-    int twist1=0, twist2=0, twist3=0;
-
-    switch(index)
-      {
-      case 0: if( corner[1]==missing[1] ) twist1=1;
-              if( corner[2]==missing[1] ) twist1=2;
-              if( corner[0]==missing[2] ) twist2=2;
-              if( corner[2]==missing[2] ) twist2=1;
-              if( corner[0]==missing[3] ) twist3=1;
-              if( corner[1]==missing[3] ) twist3=2;
-              break;
-      case 1: if( corner[1]==missing[0] ) twist1=1;
-              if( corner[2]==missing[0] ) twist1=2;
-              if( corner[0]==missing[2] ) twist2=1;
-              if( corner[1]==missing[2] ) twist2=2;
-              if( corner[0]==missing[3] ) twist3=2;
-              if( corner[2]==missing[3] ) twist3=1;
-              break;
-      case 2: if( corner[1]==missing[0] ) twist1=1;
-              if( corner[2]==missing[0] ) twist1=2;
-              if( corner[0]==missing[1] ) twist2=2;
-              if( corner[2]==missing[1] ) twist2=1;
-              if( corner[0]==missing[3] ) twist3=1;
-              if( corner[1]==missing[3] ) twist3=2;
-              break;
-      case 3: if( corner[1]==missing[0] ) twist1=1;
-              if( corner[2]==missing[0] ) twist1=2;
-              if( corner[0]==missing[1] ) twist2=1;
-              if( corner[1]==missing[1] ) twist2=2;
-              if( corner[0]==missing[2] ) twist3=2;
-              if( corner[2]==missing[2] ) twist3=1;
-              break;
-      }
-
-    return ( twist1!=twist2 || twist1!=twist3 ) ? ERROR_CORNERS_CANNOT : twist1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeCenterTwist(int[] centers, int color1, int color2)
-    {
-    if( centers[0]==color1 )
-      {
-      if( centers[1]==color2 ) return 1;
-      if( centers[2]==color2 ) return 2;
-      if( centers[3]==color2 ) return 0;
-
-      return -1;
-      }
-    if( centers[1]==color1 )
-      {
-      if( centers[0]==color2 ) return 1;
-      if( centers[2]==color2 ) return 0;
-      if( centers[3]==color2 ) return 2;
-
-      return -1;
-      }
-    if( centers[2]==color1 )
-      {
-      if( centers[0]==color2 ) return 2;
-      if( centers[1]==color2 ) return 0;
-      if( centers[3]==color2 ) return 1;
-
-      return -1;
-      }
-    if( centers[3]==color1 )
-      {
-      if( centers[0]==color2 ) return 0;
-      if( centers[1]==color2 ) return 2;
-      if( centers[2]==color2 ) return 1;
-
-      return -1;
-      }
-
-    return -2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverPyraminxDuo(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners = new int[4][3];
-    int[] centers = new int[4];
-    int[] corner_twist = new int[4];
-
-    for(int i=0; i<4; i++)
-      {
-      centers[i] = object.getCubitFaceStickerIndex(i+4,0) - 4;
-
-      for(int j=0; j<3; j++)
-        corners[i][j] = object.getCubitFaceStickerIndex(i,j);
-      }
-
-    int result1 = checkAllCornersPresent(corners);
-    if( result1<0 ) return result1;
-
-    int result2 = checkAllCentersPresent(centers);
-    if( result2<0 ) return result2;
-
-    int[] faces = computeFaceColor(corners);
-
-    int twist_gr = computeCenterTwist(centers,faces[1],faces[2]);
-    int twist_ry = computeCenterTwist(centers,faces[2],faces[0]);
-
-    if( (twist_ry-twist_gr+1)%3 != 0 ) return ERROR_TWO_CENTERS;
-
-    int total_twist=0;
-
-    for(int i=0; i<4; i++)
-      {
-      corner_twist[i] = computeCornerTwist(i,corners[i],faces);
-      if( corner_twist[i]<0 ) return corner_twist[i];
-      total_twist += corner_twist[i];
-      }
-
-    if( (total_twist-twist_gr)%3 !=0 ) return ERROR_CORNER_TWISTED;
-
-    int green_face = getFaceOfCenter(faces[1],centers);
-
-    return green_face + 4*(corner_twist[3] + 3*(corner_twist[2] + 3*(corner_twist[1] + 3*corner_twist[0])));
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_YBR_MISSING: String c01 = res.getString(R.string.color_yellow3);
-                                     String c02 = res.getString(R.string.color_blue3);
-                                     String c03 = res.getString(R.string.color_red4);
-                                     return res.getString(R.string.solver_generic_missing_corner,c01,c02,c03);
-      case ERROR_CORNER_GBR_MISSING: String c11 = res.getString(R.string.color_green3);
-                                     String c12 = res.getString(R.string.color_blue3);
-                                     String c13 = res.getString(R.string.color_red4);
-                                     return res.getString(R.string.solver_generic_missing_corner,c11,c12,c13);
-      case ERROR_CORNER_GYR_MISSING: String c21 = res.getString(R.string.color_green3);
-                                     String c22 = res.getString(R.string.color_yellow3);
-                                     String c23 = res.getString(R.string.color_red4);
-                                     return res.getString(R.string.solver_generic_missing_corner,c21,c22,c23);
-      case ERROR_CORNER_GYB_MISSING: String c31 = res.getString(R.string.color_green3);
-                                     String c32 = res.getString(R.string.color_yellow3);
-                                     String c33 = res.getString(R.string.color_blue4);
-                                     return res.getString(R.string.solver_generic_missing_corner,c31,c32,c33);
-      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       : return res.getString(R.string.solver_generic_two_centers);
-      case ERROR_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
-      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.PDUO_2.name() );
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverSkewb.java b/src/main/java/org/distorted/solvers/SolverSkewb.java
deleted file mode 100644
index b171b922..00000000
--- a/src/main/java/org/distorted/solvers/SolverSkewb.java
+++ /dev/null
@@ -1,451 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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 static org.distorted.objectlib.tablebases.TBSkewb.FIXED;
-import static org.distorted.objectlib.tablebases.TBSkewb.FREE;
-
-import android.content.res.Resources;
-
-import org.distorted.main.R;
-import org.distorted.objectlib.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TBSkewb;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverSkewb extends SolverTablebase
-{
-  private static final int ERROR_CORNER_135_MISSING = -1;
-  private static final int ERROR_CORNER_134_MISSING = -2;
-  private static final int ERROR_CORNER_125_MISSING = -3;
-  private static final int ERROR_CORNER_124_MISSING = -4;
-  private static final int ERROR_CORNER_035_MISSING = -5;
-  private static final int ERROR_CORNER_034_MISSING = -6;
-  private static final int ERROR_CORNER_025_MISSING = -7;
-  private static final int ERROR_CORNER_024_MISSING = -8;
-
-  private static final int ERROR_CENTER_0_MISSING   = -9;
-  private static final int ERROR_CENTER_1_MISSING   = -10;
-  private static final int ERROR_CENTER_2_MISSING   = -11;
-  private static final int ERROR_CENTER_3_MISSING   = -12;
-  private static final int ERROR_CENTER_4_MISSING   = -13;
-  private static final int ERROR_CENTER_5_MISSING   = -14;
-
-  private static final int ERROR_CORNERS_CANNOT     = -15;
-  private static final int ERROR_CORNER_TWISTED     = -16;
-  private static final int ERROR_TWO_CENTERS        = -17;
-  private static final int ERROR_TWO_CORNERS        = -18;
-
-  private static final int ERROR_FREE_CORNERS_NOT_EVEN    = -19;
-  private static final int ERROR_FREE_CORNERS_ROTATED     = -20;
-
-  private TablebasesAbstract mSolver;
-  private final int[] mFaceColors;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverSkewb(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    mFaceColors = new int[6];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCorners(TwistyObject object, int[][] corners)
-    {
-    for(int i=0; i<8; i++)
-      for(int j=0; j<3; j++)
-        corners[i][j] = object.getCubitFaceStickerIndex(i,j);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createCenterPermutation(int[] output, int[] symbols, int[] perm)
-    {
-    for(int s=0; s<6; s++)
-      {
-      int symbol = symbols[s];
-
-      for(int p=0; p<6; p++)
-        if( perm[p]==symbol )
-          {
-          output[s] = p;
-          break;
-          }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getCenters(TwistyObject object, int[] out)
-    {
-    final int[] map = {4,5,2,3,0,1};
-    int[] tmp = new int[6];
-    int[] mcl = new int[6];
-    boolean[] present = new boolean[6];
-
-    for(int i=0; i<6; i++)
-      {
-      int color = object.getCubitFaceStickerIndex(i+8,0) - 6;
-      present[color] = true;
-      tmp[i] = color;
-      }
-
-    if( !present[0] ) return ERROR_CENTER_0_MISSING;
-    if( !present[1] ) return ERROR_CENTER_1_MISSING;
-    if( !present[2] ) return ERROR_CENTER_2_MISSING;
-    if( !present[3] ) return ERROR_CENTER_3_MISSING;
-    if( !present[4] ) return ERROR_CENTER_4_MISSING;
-    if( !present[5] ) return ERROR_CENTER_5_MISSING;
-
-    for(int i=0; i<6; i++) mcl[i] = map[mFaceColors[i]];
-    createCenterPermutation(out,mcl,tmp);
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int commonCornerColor(int[] c1, int[] c2)
-    {
-    int theSame = 0;
-    int index   = 0;
-
-    for(int i=0; i<3; i++)
-      for(int j=0; j<3; j++)
-        if( c1[i]==c2[j] )
-          {
-          index = i;
-          theSame++;
-          }
-
-    return theSame==1 ? c1[index] : ERROR_CORNERS_CANNOT;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeFaceColors(int[][] corners, int[] output)
-    {
-    final int[][] corner_indices = { {0,3},{5,6},{0,5},{6,3},{0,6},{3,5} };
-
-    for(int i=0; i<6; i++)
-      {
-      int c1 = corner_indices[i][0];
-      int c2 = corner_indices[i][1];
-      output[i] = commonCornerColor(corners[c1],corners[c2]);
-      if( output[i]<0 ) return ERROR_CORNERS_CANNOT;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean cornerIs(int[] corner, int f0, int f1, int f2)
-    {
-    int c0 = mFaceColors[f0];
-    int c1 = mFaceColors[f1];
-    int c2 = mFaceColors[f2];
-
-    return ( (corner[0]==c0 && corner[1]==c1 && corner[2]==c2 ) ||
-             (corner[1]==c0 && corner[2]==c1 && corner[0]==c2 ) ||
-             (corner[2]==c0 && corner[0]==c1 && corner[1]==c2 )  );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllCornersPresent(int[][] corners)
-    {
-    boolean[] present = new boolean[8];
-
-    for(int i=0; i<8; i++)
-      {
-      if( cornerIs(corners[i],4,2,0) ) present[0]=true;
-      if( cornerIs(corners[i],2,5,0) ) present[1]=true;
-      if( cornerIs(corners[i],3,4,0) ) present[2]=true;
-      if( cornerIs(corners[i],5,3,0) ) present[3]=true;
-      if( cornerIs(corners[i],1,2,4) ) present[4]=true;
-      if( cornerIs(corners[i],5,2,1) ) present[5]=true;
-      if( cornerIs(corners[i],4,3,1) ) present[6]=true;
-      if( cornerIs(corners[i],1,3,5) ) present[7]=true;
-      }
-
-    if( !present[0] ) return ERROR_CORNER_024_MISSING;
-    if( !present[1] ) return ERROR_CORNER_025_MISSING;
-    if( !present[2] ) return ERROR_CORNER_034_MISSING;
-    if( !present[3] ) return ERROR_CORNER_035_MISSING;
-    if( !present[4] ) return ERROR_CORNER_124_MISSING;
-    if( !present[5] ) return ERROR_CORNER_125_MISSING;
-    if( !present[6] ) return ERROR_CORNER_134_MISSING;
-    if( !present[7] ) return ERROR_CORNER_135_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retFreeCornerPermutation(int[] perm, int[][] corners)
-    {
-    perm[0] = -1;
-    perm[1] = -1;
-    perm[2] = -1;
-    perm[3] = -1;
-
-    for(int i=0; i<4; i++)
-      {
-      int[] cor = corners[FREE[i]];
-
-      if( cornerIs(cor,2,5,0) ) perm[0] = i;
-      if( cornerIs(cor,3,4,0) ) perm[1] = i;
-      if( cornerIs(cor,1,2,4) ) perm[2] = i;
-      if( cornerIs(cor,1,3,5) ) perm[3] = i;
-      }
-
-    if( perm[0]==-1 ) return ERROR_CORNER_025_MISSING;
-    if( perm[1]==-1 ) return ERROR_CORNER_034_MISSING;
-    if( perm[2]==-1 ) return ERROR_CORNER_124_MISSING;
-    if( perm[3]==-1 ) return ERROR_CORNER_135_MISSING;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void computeCornerQuats(int[] quats, int[][] corners, int[] perm)
-    {
-    final int[] zeroeth_face_map = { 4,2,3,5,1,5,4,1 };
-    int[] twist = new int[8];
-
-    for(int i=0; i<4; i++)
-      {
-      int fi = FIXED[i];
-      int colorFi = mFaceColors[zeroeth_face_map[fi]];
-      int[] cFi = corners[fi];
-
-           if( cFi[0]==colorFi ) twist[fi] = 0;
-      else if( cFi[1]==colorFi ) twist[fi] = 1;
-      else                       twist[fi] = 2;
-      }
-
-    int[] inv_perm = new int[4];
-    TablebaseHelpers.invertPermutation(perm,inv_perm);
-
-    int common14 = commonCornerColor(corners[1], corners[4]);
-    int common27 = commonCornerColor(corners[2], corners[7]);
-    int common47 = commonCornerColor(corners[4], corners[7]);
-    int index;
-    int[] c;
-
-    index = FREE[inv_perm[0]];
-    c = corners[FREE[0]];
-
-         if(c[0]==common14) twist[index] = 0;
-    else if(c[1]==common14) twist[index] = 2;
-    else                    twist[index] = 1;
-
-    index = FREE[inv_perm[1]];
-    c = corners[FREE[1]];
-
-         if(c[0]==common27) twist[index] = 0;
-    else if(c[1]==common27) twist[index] = 2;
-    else                    twist[index] = 1;
-
-    index = FREE[inv_perm[2]];
-    c = corners[FREE[2]];
-
-         if(c[0]==common47) twist[index] = 0;
-    else if(c[1]==common47) twist[index] = 2;
-    else                    twist[index] = 1;
-
-    index = FREE[inv_perm[3]];
-    c = corners[FREE[3]];
-
-         if(c[0]==common47) twist[index] = 0;
-    else if(c[1]==common47) twist[index] = 2;
-    else                    twist[index] = 1;
-
-    TBSkewb.fillInQuats(quats,perm,twist);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners= new int[8][3];
-    int[] centers  = new int[6];
-    int[] twist    = new int[8];
-    int[] freePerm = new int[4];
-    int[] quats    = new int[8];
-
-    getCorners(object,corners);
-
-    int result1 = computeFaceColors(corners,mFaceColors);
-    if( result1<0 ) return result1;
-
-    int result2 = checkAllCornersPresent(corners);
-    if( result2<0 ) return result2;
-
-    int result3 = getCenters(object,centers);
-    if( result3<0 ) return result3;
-
-    if( !TablebaseHelpers.permutationIsEven(centers) ) return ERROR_TWO_CENTERS;
-    int center_perm_num = TablebaseHelpers.computeEvenPermutationNum(centers);
-
-    int result4 = retFreeCornerPermutation(freePerm,corners);
-    if( result4<0 ) return result4;
-
-    computeCornerQuats(quats,corners,freePerm);
-
-    int[] freeLoc = new int[4];
-    for(int f=0; f<4; f++) freeLoc[f] = TBSkewb.computeLocation(f,quats[FREE[f]]);
-    if( !TablebaseHelpers.permutationIsEven(freeLoc) ) return ERROR_FREE_CORNERS_NOT_EVEN;
-
-    TBSkewb.computeCornerTwists(twist,quats);
-
-    int total = 0;
-    for(int i=0; i<8; i++) total += twist[i];
-    if( (total%3)!=0 ) return ERROR_CORNER_TWISTED;
-    int totalTwist = twist[0]+ 3*(twist[1]+ 3*(twist[2]+ 3*(twist[3]+ 3*(twist[4]+ 3*(twist[5]+ 3*twist[6])))));
-
-    int sumFixedTwists = twist[FIXED[0]]+twist[FIXED[1]]+twist[FIXED[2]]+twist[FIXED[3]];
-    int freeLoc1 = TBSkewb.permFree1(freeLoc[0],sumFixedTwists);
-    if( freeLoc[1] != freeLoc1 ) return ERROR_FREE_CORNERS_ROTATED;
-
-    return center_perm_num+ 360*(totalTwist + 2187*freeLoc[0]);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex2(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_yellow2;
-      case 1: return R.string.color_white2;
-      case 2: return R.string.color_blue2;
-      case 3: return R.string.color_green2;
-      case 4: return R.string.color_red2;
-      case 5: return R.string.color_orange2;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_yellow3;
-      case 1: return R.string.color_white3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_green3;
-      case 4: return R.string.color_red3;
-      case 5: return R.string.color_orange3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex4(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_yellow4;
-      case 1: return R.string.color_white4;
-      case 2: return R.string.color_blue4;
-      case 3: return R.string.color_green4;
-      case 4: return R.string.color_red4;
-      case 5: return R.string.color_orange4;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerError(Resources res, int face0, int face1, int face2)
-    {
-    int j0 = getColorIndex3(face0);
-    int j1 = getColorIndex3(face1);
-    int j2 = getColorIndex4(face2);
-
-    String c0 = res.getString(j0);
-    String c1 = res.getString(j1);
-    String c2 = res.getString(j2);
-
-    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String centerError(Resources res, int face)
-    {
-    int color = getColorIndex2(face);
-    String clr= res.getString(color);
-    return res.getString(R.string.solver_generic_missing_center,clr);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_135_MISSING   : return cornerError(res,1,3,5);
-      case ERROR_CORNER_134_MISSING   : return cornerError(res,1,3,4);
-      case ERROR_CORNER_125_MISSING   : return cornerError(res,1,2,5);
-      case ERROR_CORNER_124_MISSING   : return cornerError(res,1,2,4);
-      case ERROR_CORNER_035_MISSING   : return cornerError(res,0,3,5);
-      case ERROR_CORNER_034_MISSING   : return cornerError(res,0,3,4);
-      case ERROR_CORNER_025_MISSING   : return cornerError(res,0,2,5);
-      case ERROR_CORNER_024_MISSING   : return cornerError(res,0,2,4);
-
-      case ERROR_CENTER_0_MISSING     : return centerError(res,0);
-      case ERROR_CENTER_1_MISSING     : return centerError(res,1);
-      case ERROR_CENTER_2_MISSING     : return centerError(res,2);
-      case ERROR_CENTER_3_MISSING     : return centerError(res,3);
-      case ERROR_CENTER_4_MISSING     : return centerError(res,4);
-      case ERROR_CENTER_5_MISSING     : return centerError(res,5);
-
-      case ERROR_CORNERS_CANNOT       : return res.getString(R.string.solver_generic_corners_cannot);
-      case ERROR_CORNER_TWISTED       : return res.getString(R.string.solver_generic_corner_twist);
-      case ERROR_TWO_CENTERS          : return res.getString(R.string.solver_generic_two_centers);
-      case ERROR_TWO_CORNERS          : return res.getString(R.string.solver_generic_two_corners);
-
-      case ERROR_FREE_CORNERS_NOT_EVEN: return res.getString(R.string.solver_generic_free_corners_odd);
-      case ERROR_FREE_CORNERS_ROTATED : return res.getString(R.string.solver_generic_free_corners_rotated);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.SKEW_2.name() );
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverSkewbDiamond.java b/src/main/java/org/distorted/solvers/SolverSkewbDiamond.java
deleted file mode 100644
index 01de3b07..00000000
--- a/src/main/java/org/distorted/solvers/SolverSkewbDiamond.java
+++ /dev/null
@@ -1,512 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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.helpers.OperatingSystemInterface;
-import org.distorted.objectlib.metadata.ListObjects;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
-import org.distorted.objectlib.tablebases.TablebaseHelpers;
-import org.distorted.objectlib.tablebases.TablebasesAbstract;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class SolverSkewbDiamond extends SolverTablebase
-{
-  private static final int ERROR_CORNER_FR_MISSING = -1;
-  private static final int ERROR_CORNER_BR_MISSING = -2;
-  private static final int ERROR_CORNER_BL_MISSING = -3;
-  private static final int ERROR_CORNER_FL_MISSING = -4;
-  private static final int ERROR_CORNER_TO_MISSING = -5;
-  private static final int ERROR_CORNER_BO_MISSING = -6;
-
-  private static final int ERROR_CENTER_0_MISSING = -7;
-  private static final int ERROR_CENTER_1_MISSING = -8;
-  private static final int ERROR_CENTER_2_MISSING = -9;
-  private static final int ERROR_CENTER_3_MISSING = -10;
-  private static final int ERROR_CENTER_4_MISSING = -11;
-  private static final int ERROR_CENTER_5_MISSING = -12;
-  private static final int ERROR_CENTER_6_MISSING = -13;
-  private static final int ERROR_CENTER_7_MISSING = -14;
-
-  private static final int ERROR_TWO_CORNERS      = -15;
-  private static final int ERROR_TWO_CENTERS      = -16;
-  private static final int ERROR_CORNER_TWIST_90  = -17;
-  private static final int ERROR_CORNER_TWIST_180 = -18;
-  private static final int ERROR_CORNERS_CANNOT   = -19;
-
-  private TablebasesAbstract mSolver;
-  private final int[] mFaceColors;
-
-  private static final int[] FREE_CENTERS = {0,2,5,7};
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public SolverSkewbDiamond(OperatingSystemInterface os, Resources res, TwistyObject object)
-    {
-    super(os,res,object);
-    mFaceColors = new int[8];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retCorner(int[][] corners, int c1, int c2)
-    {
-    for(int i=0; i<6; i++)
-      {
-      int[] cor = corners[i];
-      int twist = retCornerTwist(cor,c1,c2);
-      if( twist>=0 ) return i;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] getCornersPermutation(int[][] corners)
-    {
-    int[] perm = new int[6];
-
-    perm[0] = retCorner(corners,mFaceColors[1],mFaceColors[4]);
-    perm[1] = retCorner(corners,mFaceColors[1],mFaceColors[6]);
-    perm[2] = retCorner(corners,mFaceColors[3],mFaceColors[6]);
-    perm[3] = retCorner(corners,mFaceColors[3],mFaceColors[4]);
-    perm[4] = retCorner(corners,mFaceColors[1],mFaceColors[3]);
-    perm[5] = retCorner(corners,mFaceColors[4],mFaceColors[6]);
-
-    return perm;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeCornerTwist(int[] corner, int color )
-    {
-    if( corner[3]==color ) return 0;
-    if( corner[2]==color ) return 1;
-    if( corner[1]==color ) return 2;
-    if( corner[0]==color ) return 3;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] getCornersTwist(int[] corners_perm, int[][] corners)
-    {
-    int[] twist = new int[6];
-
-    twist[0] = computeCornerTwist( corners[corners_perm[0]], mFaceColors[1] );
-    twist[1] = computeCornerTwist( corners[corners_perm[1]], mFaceColors[1] );
-    twist[2] = computeCornerTwist( corners[corners_perm[2]], mFaceColors[3] );
-    twist[3] = computeCornerTwist( corners[corners_perm[3]], mFaceColors[3] );
-    twist[4] = computeCornerTwist( corners[corners_perm[4]], mFaceColors[1] );
-    twist[5] = computeCornerTwist( corners[corners_perm[5]], mFaceColors[4] );
-
-    return twist;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retCenter(int color, int[] centers)
-    {
-    for(int i=0; i<4; i++ )
-      if( centers[FREE_CENTERS[i]]==color ) return i;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int[] getFreeCentersPermutation(int[] centers)
-    {
-    int[] perm = new int[4];
-
-    for(int i=0; i<4; i++ )
-      perm[i] = retCenter(mFaceColors[FREE_CENTERS[i]],centers);
-
-    return perm;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getTotalTwist(int[] twist)
-    {
-    int total = 0;
-    boolean even = true;
-
-    for(int i=0; i<6; i++)
-      {
-      int t= twist[i];
-
-      if( t==1 || t==3 ) return ERROR_CORNER_TWIST_90;
-      if( t==2 )
-        {
-        if( i<5 )
-          {
-          total+=(1<<i);
-          even = !even;
-          }
-        else if( even ) return ERROR_CORNER_TWIST_180;
-        }
-      }
-
-    return total;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int retCornerTwist(int[] corner, int c1, int c2)
-    {
-    if( corner[0]==c1 && corner[2]==c2 ) return 1;
-    if( corner[1]==c1 && corner[3]==c2 ) return 2;
-    if( corner[2]==c1 && corner[0]==c2 ) return 3;
-    if( corner[3]==c1 && corner[1]==c2 ) return 0;
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int figureOutColor(int[][] corners, int c1, int c2)
-    {
-    for(int i=0; i<6; i++)
-      {
-      int[] cor = corners[i];
-      int twist = retCornerTwist(cor,c1,c2);
-      if( twist>=0 ) return cor[twist];
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// We only move the UR, UR, DR & DB faces --> those centers are fixed and determine the colors of
-// the faces.
-
-  private int figureOutFaceColors(int[] output, int[] centers, int[][] corners)
-    {
-    output[1] = centers[1];
-    output[3] = centers[3];
-    output[4] = centers[4];
-    output[6] = centers[6];
-
-    int color01 = figureOutColor(corners,output[4],output[1]);
-    if( color01<0 ) return ERROR_CORNER_FR_MISSING;
-    int color02 = figureOutColor(corners,output[3],output[4]);
-    if( color02<0 ) return ERROR_CORNER_FL_MISSING;
-    int color03 = figureOutColor(corners,output[1],output[3]);
-    if( color03<0 ) return ERROR_CORNER_TO_MISSING;
-    if( color01!=color02 || color01!=color03 ) return ERROR_CORNERS_CANNOT;
-    output[0] = color01;
-
-    int color21 = figureOutColor(corners,output[1],output[6]);
-    if( color21<0 ) return ERROR_CORNER_BR_MISSING;
-    int color22 = figureOutColor(corners,output[6],output[3]);
-    if( color22<0 ) return ERROR_CORNER_BL_MISSING;
-    int color23 = figureOutColor(corners,output[3],output[1]);
-    if( color23<0 ) return ERROR_CORNER_TO_MISSING;
-    if( color21!=color22 || color21!=color23 ) return ERROR_CORNERS_CANNOT;
-    output[2] = color21;
-
-    int color51 = figureOutColor(corners,output[1],output[4]);
-    if( color51<0 ) return ERROR_CORNER_FR_MISSING;
-    int color52 = figureOutColor(corners,output[6],output[1]);
-    if( color52<0 ) return ERROR_CORNER_BR_MISSING;
-    int color53 = figureOutColor(corners,output[4],output[6]);
-    if( color53<0 ) return ERROR_CORNER_BO_MISSING;
-    if( color51!=color52 || color51!=color53 ) return ERROR_CORNERS_CANNOT;
-    output[5] = color51;
-
-    int color71 = figureOutColor(corners,output[3],output[6]);
-    if( color71<0 ) return ERROR_CORNER_BL_MISSING;
-    int color72 = figureOutColor(corners,output[4],output[3]);
-    if( color72<0 ) return ERROR_CORNER_FL_MISSING;
-    int color73 = figureOutColor(corners,output[6],output[4]);
-    if( color73<0 ) return ERROR_CORNER_BO_MISSING;
-    if( color71!=color72 || color71!=color73 ) return ERROR_CORNERS_CANNOT;
-    output[7] = color71;
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllColorsDifferent()
-    {
-    for(int i=0; i<8; i++)
-      {
-      boolean present = false;
-
-      for(int j=0; j<8; j++)
-        if( mFaceColors[j]==i )
-          {
-          present=true;
-          break;
-          }
-
-      if( !present ) return ERROR_CORNERS_CANNOT;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int checkAllCentersPresent(int[] centers)
-    {
-    for(int i=0; i<8; i++)
-      {
-      boolean present = false;
-
-      for(int j=0; j<8; j++)
-        if( centers[j]==i )
-          {
-          present=true;
-          break;
-          }
-
-      if( !present )
-        {
-        switch(i)
-          {
-          case 0: return ERROR_CENTER_0_MISSING;
-          case 1: return ERROR_CENTER_1_MISSING;
-          case 2: return ERROR_CENTER_2_MISSING;
-          case 3: return ERROR_CENTER_3_MISSING;
-          case 4: return ERROR_CENTER_4_MISSING;
-          case 5: return ERROR_CENTER_5_MISSING;
-          case 6: return ERROR_CENTER_6_MISSING;
-          case 7: return ERROR_CENTER_7_MISSING;
-          }
-        }
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCorners(TwistyObject object, int[][] corners)
-    {
-    corners[0][0] = object.getCubitFaceStickerIndex( 0,5); // FR
-    corners[0][1] = object.getCubitFaceStickerIndex( 0,4);
-    corners[0][2] = object.getCubitFaceStickerIndex( 0,0);
-    corners[0][3] = object.getCubitFaceStickerIndex( 0,1);
-
-    corners[1][0] = object.getCubitFaceStickerIndex( 1,2); // BR
-    corners[1][1] = object.getCubitFaceStickerIndex( 1,6);
-    corners[1][2] = object.getCubitFaceStickerIndex( 1,5);
-    corners[1][3] = object.getCubitFaceStickerIndex( 1,1);
-
-    corners[2][0] = object.getCubitFaceStickerIndex( 2,7); // BL
-    corners[2][1] = object.getCubitFaceStickerIndex( 2,6);
-    corners[2][2] = object.getCubitFaceStickerIndex( 2,2);
-    corners[2][3] = object.getCubitFaceStickerIndex( 2,3);
-
-    corners[3][0] = object.getCubitFaceStickerIndex( 3,0); // FL
-    corners[3][1] = object.getCubitFaceStickerIndex( 3,4);
-    corners[3][2] = object.getCubitFaceStickerIndex( 3,7);
-    corners[3][3] = object.getCubitFaceStickerIndex( 3,3);
-
-    corners[4][0] = object.getCubitFaceStickerIndex( 4,0); // U
-    corners[4][1] = object.getCubitFaceStickerIndex( 4,3);
-    corners[4][2] = object.getCubitFaceStickerIndex( 4,2);
-    corners[4][3] = object.getCubitFaceStickerIndex( 4,1);
-
-    corners[5][0] = object.getCubitFaceStickerIndex( 5,5); // D
-    corners[5][1] = object.getCubitFaceStickerIndex( 5,6);
-    corners[5][2] = object.getCubitFaceStickerIndex( 5,7);
-    corners[5][3] = object.getCubitFaceStickerIndex( 5,4);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void getCenters(TwistyObject object, int[] centers)
-    {
-    centers[0] = object.getCubitFaceStickerIndex( 9,0); // UF
-    centers[1] = object.getCubitFaceStickerIndex( 6,0); // UR
-    centers[2] = object.getCubitFaceStickerIndex( 7,0); // UB
-    centers[3] = object.getCubitFaceStickerIndex( 8,0); // UL
-    centers[4] = object.getCubitFaceStickerIndex(13,0); // DF
-    centers[5] = object.getCubitFaceStickerIndex(10,1); // DR
-    centers[6] = object.getCubitFaceStickerIndex(11,0); // DB
-    centers[7] = object.getCubitFaceStickerIndex(12,0); // DL
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int tablebaseIndex(TwistyObject object)
-    {
-    int[][] corners = new int[6][4];
-    int[] centers = new int[8];
-
-    getCorners(object,corners);
-    getCenters(object,centers);
-
-    int result1 = checkAllCentersPresent(centers);
-    if( result1<0 ) return result1;
-
-    int result2 = figureOutFaceColors(mFaceColors,centers,corners);
-    if( result2<0 ) return result2;
-
-    int result3 = checkAllColorsDifferent();
-    if( result3<0 ) return result3;
-
-    int[] corners_perm = getCornersPermutation(corners);
-    boolean even1 = TablebaseHelpers.permutationIsEven(corners_perm);
-    if( !even1 ) return ERROR_TWO_CORNERS;
-
-    int[] free_centers_perm = getFreeCentersPermutation(centers);
-    boolean even2 = TablebaseHelpers.permutationIsEven(free_centers_perm);
-    if( !even2 ) return ERROR_TWO_CENTERS;
-
-    int[] corners_twist = getCornersTwist(corners_perm, corners);
-/*
-android.util.Log.e("D", "faces: "+mFaceColors[0]+" "+mFaceColors[1]+" "+mFaceColors[2]+" "
-+mFaceColors[3]+" "+mFaceColors[4]+" "+mFaceColors[5]+" "+mFaceColors[6]+" "+mFaceColors[7]);
-
-android.util.Log.e("D", "corn perm: "+corners_perm[0]+" "+corners_perm[1]+" "+corners_perm[2]+" "
-+corners_perm[3]+" "+corners_perm[4]+" "+corners_perm[5]);
-
-android.util.Log.e("D", "corn twist: "+corners_twist[0]+" "+corners_twist[1]+" "+corners_twist[2]+" "
-+corners_twist[3]+" "+corners_twist[4]+" "+corners_twist[5]);
-*/
-    int totalTwist = getTotalTwist(corners_twist);
-    if( totalTwist<0 ) return totalTwist;
-
-    int corners_perm_num = TablebaseHelpers.computeEvenPermutationNum(corners_perm);
-    int centers_perm_num = TablebaseHelpers.computeEvenPermutationNum(free_centers_perm);
-
-    return centers_perm_num + 12*(totalTwist + 32*corners_perm_num);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex4(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_violet4;
-      case 1: return R.string.color_grey4;
-      case 2: return R.string.color_blue4;
-      case 3: return R.string.color_red4;
-      case 4: return R.string.color_orange4;
-      case 5: return R.string.color_green4;
-      case 6: return R.string.color_white4;
-      case 7: return R.string.color_yellow4;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex3(int face)
-    {
-    switch(mFaceColors[face])
-      {
-      case 0: return R.string.color_violet3;
-      case 1: return R.string.color_grey3;
-      case 2: return R.string.color_blue3;
-      case 3: return R.string.color_red3;
-      case 4: return R.string.color_orange3;
-      case 5: return R.string.color_green3;
-      case 6: return R.string.color_white3;
-      case 7: return R.string.color_yellow3;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int getColorIndex2(int face)
-    {
-    switch(face)
-      {
-      case 0: return R.string.color_violet2;
-      case 1: return R.string.color_grey2;
-      case 2: return R.string.color_blue2;
-      case 3: return R.string.color_red2;
-      case 4: return R.string.color_orange2;
-      case 5: return R.string.color_green2;
-      case 6: return R.string.color_white2;
-      case 7: return R.string.color_yellow2;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String centerError(Resources res, int face)
-    {
-    String color = res.getString(getColorIndex2(face));
-    return res.getString(R.string.solver_generic_missing_center,color);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String cornerError(Resources res, int f1, int f2)
-    {
-    String c1 = res.getString(getColorIndex3(f1));
-    String c2 = res.getString(getColorIndex4(f2));
-    return res.getString(R.string.solver_generic_missing_corner2,c1,c2);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String error(int index, Resources res)
-    {
-    switch(index)
-      {
-      case ERROR_CORNER_FR_MISSING: return cornerError(res,1,4);
-      case ERROR_CORNER_BR_MISSING: return cornerError(res,1,6);
-      case ERROR_CORNER_BL_MISSING: return cornerError(res,3,6);
-      case ERROR_CORNER_FL_MISSING: return cornerError(res,3,4);
-      case ERROR_CORNER_TO_MISSING: return cornerError(res,1,3);
-      case ERROR_CORNER_BO_MISSING: return cornerError(res,4,6);
-
-      case ERROR_CENTER_0_MISSING : return centerError(res,0);
-      case ERROR_CENTER_1_MISSING : return centerError(res,1);
-      case ERROR_CENTER_2_MISSING : return centerError(res,2);
-      case ERROR_CENTER_3_MISSING : return centerError(res,3);
-      case ERROR_CENTER_4_MISSING : return centerError(res,4);
-      case ERROR_CENTER_5_MISSING : return centerError(res,5);
-      case ERROR_CENTER_6_MISSING : return centerError(res,6);
-      case ERROR_CENTER_7_MISSING : return centerError(res,7);
-
-      case ERROR_TWO_CORNERS      : return res.getString(R.string.solver_generic_two_corners);
-      case ERROR_TWO_CENTERS      : return res.getString(R.string.solver_generic_two_centers);
-      case ERROR_CORNER_TWIST_90  : return res.getString(R.string.solver_generic_corner_twist) + " (90)";
-      case ERROR_CORNER_TWIST_180 : return res.getString(R.string.solver_generic_corner_twist) + " (180)";
-      case ERROR_CORNERS_CANNOT   : return res.getString(R.string.solver_generic_corners_cannot);
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int[][] solution(int index, OperatingSystemInterface os)
-    {
-    if( mSolver==null )
-      {
-      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.DIAM_2.name());
-      }
-
-    return mSolver!=null ? mSolver.solution(index,null,os) : null;
-    }
-}  
-
diff --git a/src/main/java/org/distorted/solvers/SolverTablebase.java b/src/main/java/org/distorted/solvers/SolverTablebase.java
index 6d2d5b9b..04483bae 100644
--- a/src/main/java/org/distorted/solvers/SolverTablebase.java
+++ b/src/main/java/org/distorted/solvers/SolverTablebase.java
@@ -17,7 +17,7 @@ import org.distorted.solverui.ScreenSolver;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public abstract class SolverTablebase
+public abstract class SolverTablebase implements SolvingInterface
 {
   private final OperatingSystemInterface mOS;
   private final Resources mRes;
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseCU232.java b/src/main/java/org/distorted/solvers/SolverTablebaseCU232.java
new file mode 100644
index 00000000..ce582dc0
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseCU232.java
@@ -0,0 +1,418 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseCU232 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_135_MISSING = -1;
+  private static final int ERROR_CORNER_134_MISSING = -2;
+  private static final int ERROR_CORNER_125_MISSING = -3;
+  private static final int ERROR_CORNER_124_MISSING = -4;
+  private static final int ERROR_CORNER_035_MISSING = -5;
+  private static final int ERROR_CORNER_034_MISSING = -6;
+  private static final int ERROR_CORNER_025_MISSING = -7;
+  private static final int ERROR_CORNER_024_MISSING = -8;
+
+  private static final int ERROR_EDGE_15_MISSING = -9;
+  private static final int ERROR_EDGE_14_MISSING = -10;
+  private static final int ERROR_EDGE_05_MISSING = -11;
+  private static final int ERROR_EDGE_04_MISSING = -12;
+
+  private static final int ERROR_CORNERS_CANNOT  = -13;
+  private static final int ERROR_EDGE_TWISTED    = -14;
+  private static final int ERROR_CORNER_TWISTED  = -15;
+
+  TablebasesAbstract mSolver;
+  private final int[] mFaceColors;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int edgeIs(int[] edge, int i0, int i1)
+    {
+    int c0 = mFaceColors[i0];
+    int c1 = mFaceColors[i1];
+
+    if( edge[0]==c0 && edge[1]==c1 ) return 0;
+    if( edge[0]==c1 && edge[1]==c0 ) return 1;
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retEdgePermutation(int[] output, int[][] edges)
+    {
+    for(int i=0; i<4; i++) output[i] = -1;
+
+    for(int i=0; i<4; i++)
+      {
+      int edge0 = edgeIs(edges[i],1,5);
+           if( edge0==0 ) output[0]=i;
+      else if( edge0==1 ) return ERROR_EDGE_TWISTED;
+
+      int edge1 = edgeIs(edges[i],1,4);
+           if( edge1==0 ) output[1]=i;
+      else if( edge1==1 ) return ERROR_EDGE_TWISTED;
+
+      int edge2 = edgeIs(edges[i],0,5);
+           if( edge2==0 ) output[2]=i;
+      else if( edge2==1 ) return ERROR_EDGE_TWISTED;
+
+      int edge3 = edgeIs(edges[i],0,4);
+           if( edge3==0 ) output[3]=i;
+      else if( edge3==1 ) return ERROR_EDGE_TWISTED;
+      }
+
+    if( output[0]==-1 ) return ERROR_EDGE_15_MISSING;
+    if( output[1]==-1 ) return ERROR_EDGE_14_MISSING;
+    if( output[2]==-1 ) return ERROR_EDGE_05_MISSING;
+    if( output[3]==-1 ) return ERROR_EDGE_04_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int cornerIs(int[] corner, int i0, int i1, int i2)
+    {
+    int c0 = mFaceColors[i0];
+    int c1 = mFaceColors[i1];
+    int c2 = mFaceColors[i2];
+
+    if( corner[0]==c0 && corner[1]==c1 && corner[2]==c2 ) return 0;
+
+    if( corner[0]==c0 && corner[1]==c2 && corner[2]==c1 ||
+        corner[0]==c1 && corner[1]==c0 && corner[2]==c2 ||
+        corner[0]==c1 && corner[1]==c2 && corner[2]==c0 ||
+        corner[0]==c2 && corner[1]==c0 && corner[2]==c1 ||
+        corner[0]==c2 && corner[1]==c1 && corner[2]==c0  ) return 1;
+
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retCornerPermutation(int[] output, int[][] corners)
+    {
+    for(int i=0; i<8; i++) output[i] = -1;
+
+    for(int i=0; i<8; i++)
+      {
+      int corner7 = cornerIs(corners[i],2,4,0);
+           if( corner7==0 ) output[7]=i;
+      else if( corner7==1 ) return ERROR_CORNER_TWISTED;
+
+      int corner6 = cornerIs(corners[i],2,0,5);
+           if( corner6==0 ) output[6]=i;
+      else if( corner6==1 ) return ERROR_CORNER_TWISTED;
+
+      int corner5 = cornerIs(corners[i],3,0,4);
+           if( corner5==0 ) output[5]=i;
+      else if( corner5==1 ) return ERROR_CORNER_TWISTED;
+
+      int corner4 = cornerIs(corners[i],3,5,0);
+           if( corner4==0 ) output[4]=i;
+      else if( corner4==1 ) return ERROR_CORNER_TWISTED;
+
+      int corner3 = cornerIs(corners[i],2,1,4);
+           if( corner3==0 ) output[3]=i;
+      else if( corner3==1 ) return ERROR_CORNER_TWISTED;
+
+      int corner2 = cornerIs(corners[i],2,5,1);
+           if( corner2==0 ) output[2]=i;
+      else if( corner2==1 ) return ERROR_CORNER_TWISTED;
+
+      int corner1 = cornerIs(corners[i],3,4,1);
+           if( corner1==0 ) output[1]=i;
+      else if( corner1==1 ) return ERROR_CORNER_TWISTED;
+
+      int corner0 = cornerIs(corners[i],3,1,5);
+           if( corner0==0 ) output[0]=i;
+      else if( corner0==1 ) return ERROR_CORNER_TWISTED;
+      }
+
+    if( output[0]==-1 ) return ERROR_CORNER_135_MISSING;
+    if( output[1]==-1 ) return ERROR_CORNER_134_MISSING;
+    if( output[2]==-1 ) return ERROR_CORNER_125_MISSING;
+    if( output[3]==-1 ) return ERROR_CORNER_124_MISSING;
+    if( output[4]==-1 ) return ERROR_CORNER_035_MISSING;
+    if( output[5]==-1 ) return ERROR_CORNER_034_MISSING;
+    if( output[6]==-1 ) return ERROR_CORNER_025_MISSING;
+    if( output[7]==-1 ) return ERROR_CORNER_024_MISSING;
+
+    return 0;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeFaceColors(int[][] corners, int[][] edges)
+    {
+    mFaceColors[1] = edges[1][0];
+    mFaceColors[4] = edges[1][1];
+
+         if( edges[0][0]==mFaceColors[1] ) mFaceColors[5] = edges[0][1];
+    else if( edges[2][0]==mFaceColors[1] ) mFaceColors[5] = edges[2][1];
+    else if( edges[3][0]==mFaceColors[1] ) mFaceColors[5] = edges[3][1];
+    else return ERROR_EDGE_TWISTED;
+
+         if( edges[0][1]==mFaceColors[4] ) mFaceColors[0] = edges[0][0];
+    else if( edges[2][1]==mFaceColors[4] ) mFaceColors[0] = edges[2][0];
+    else if( edges[3][1]==mFaceColors[4] ) mFaceColors[0] = edges[3][0];
+    else return ERROR_EDGE_TWISTED;
+
+    boolean found2 = false;
+    boolean found3 = false;
+
+    for(int c=0; c<8; c++)
+      {
+      if( !found3 && corners[c][1]==mFaceColors[4] && corners[c][2]==mFaceColors[1] )
+        {
+        found3=true;
+        mFaceColors[3] = corners[c][0];
+        }
+      if( !found2 && corners[c][1]==mFaceColors[1] && corners[c][2]==mFaceColors[4] )
+        {
+        found2=true;
+        mFaceColors[2] = corners[c][0];
+        }
+      }
+
+    if( !found2 || !found3 ) return ERROR_CORNERS_CANNOT;
+
+    for(int i=0; i<6; i++)
+      for(int j=i+1; j<6; j++)
+        if( mFaceColors[i]==mFaceColors[j] ) return ERROR_CORNERS_CANNOT;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] correctEdgePerm(int[] perm)
+    {
+    int[] ret = new int[3];
+
+    ret[0] = perm[0];
+    ret[1] = perm[2];
+    ret[2] = perm[3];
+
+    if( ret[0]>1 ) ret[0]--;
+    if( ret[1]>1 ) ret[1]--;
+    if( ret[2]>1 ) ret[2]--;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseCU232(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    mFaceColors = new int[6];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners= new int[8][3];
+    int[][] edges  = new int[4][2];
+
+    corners[0][0] = object.getCubitFaceStickerIndex(0,3);
+    corners[0][1] = object.getCubitFaceStickerIndex(0,1);
+    corners[0][2] = object.getCubitFaceStickerIndex(0,5);
+
+    corners[1][0] = object.getCubitFaceStickerIndex(1,1);
+    corners[1][1] = object.getCubitFaceStickerIndex(1,5);
+    corners[1][2] = object.getCubitFaceStickerIndex(1,3);
+
+    corners[2][0] = object.getCubitFaceStickerIndex(2,3);
+    corners[2][1] = object.getCubitFaceStickerIndex(2,1);
+    corners[2][2] = object.getCubitFaceStickerIndex(2,5);
+
+    corners[3][0] = object.getCubitFaceStickerIndex(3,3);
+    corners[3][1] = object.getCubitFaceStickerIndex(3,1);
+    corners[3][2] = object.getCubitFaceStickerIndex(3,5);
+
+    corners[4][0] = object.getCubitFaceStickerIndex(4,5);
+    corners[4][1] = object.getCubitFaceStickerIndex(4,3);
+    corners[4][2] = object.getCubitFaceStickerIndex(4,1);
+
+    corners[5][0] = object.getCubitFaceStickerIndex(5,3);
+    corners[5][1] = object.getCubitFaceStickerIndex(5,1);
+    corners[5][2] = object.getCubitFaceStickerIndex(5,5);
+
+    corners[6][0] = object.getCubitFaceStickerIndex(6,3);
+    corners[6][1] = object.getCubitFaceStickerIndex(6,1);
+    corners[6][2] = object.getCubitFaceStickerIndex(6,5);
+
+    corners[7][0] = object.getCubitFaceStickerIndex(7,5);
+    corners[7][1] = object.getCubitFaceStickerIndex(7,3);
+    corners[7][2] = object.getCubitFaceStickerIndex(7,1);
+
+    edges[0][0] = object.getCubitFaceStickerIndex(8,5);
+    edges[0][1] = object.getCubitFaceStickerIndex(8,3);
+    edges[1][0] = object.getCubitFaceStickerIndex(9,3);
+    edges[1][1] = object.getCubitFaceStickerIndex(9,5);
+    edges[2][0] = object.getCubitFaceStickerIndex(10,5);
+    edges[2][1] = object.getCubitFaceStickerIndex(10,3);
+    edges[3][0] = object.getCubitFaceStickerIndex(11,3);
+    edges[3][1] = object.getCubitFaceStickerIndex(11,5);
+
+    int result0 = computeFaceColors(corners, edges);
+    if( result0<0 ) return result0;
+
+    int[] corner_perm = new int[8];
+    int result1 = retCornerPermutation(corner_perm,corners);
+    if( result1<0 ) return result1;
+
+    int[] edge_perm = new int[4];
+    int result2 = retEdgePermutation(edge_perm,edges);
+    if( result2<0 ) return result2;
+
+    int[] edge_perm2 = correctEdgePerm(edge_perm); // edge1 is fixed!
+
+    int corner_perm_num = TablebaseHelpers.computePermutationNum(corner_perm);
+    int edge_perm_num = TablebaseHelpers.computePermutationNum(edge_perm2);
+
+    return edge_perm_num + 6*corner_perm_num;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_yellow4;
+      case 1: return R.string.color_white4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_green4;
+      case 4: return R.string.color_red4;
+      case 5: return R.string.color_orange4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_yellow3;
+      case 1: return R.string.color_white3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_green3;
+      case 4: return R.string.color_red3;
+      case 5: return R.string.color_orange3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex6(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_yellow6;
+      case 1: return R.string.color_white6;
+      case 2: return R.string.color_blue6;
+      case 3: return R.string.color_green6;
+      case 4: return R.string.color_red6;
+      case 5: return R.string.color_orange6;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeError(Resources res, int face0, int face1)
+    {
+    int j0 = getColorIndex3(face0);
+    int j1 = getColorIndex6(face1);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerError(Resources res, int face0, int face1, int face2)
+    {
+    int j0 = getColorIndex3(face0);
+    int j1 = getColorIndex3(face1);
+    int j2 = getColorIndex4(face2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_135_MISSING: return cornerError(res,1,3,5);
+      case ERROR_CORNER_134_MISSING: return cornerError(res,1,3,4);
+      case ERROR_CORNER_125_MISSING: return cornerError(res,1,2,5);
+      case ERROR_CORNER_124_MISSING: return cornerError(res,1,2,4);
+      case ERROR_CORNER_035_MISSING: return cornerError(res,0,3,5);
+      case ERROR_CORNER_034_MISSING: return cornerError(res,0,3,4);
+      case ERROR_CORNER_025_MISSING: return cornerError(res,0,2,5);
+      case ERROR_CORNER_024_MISSING: return cornerError(res,0,2,4);
+
+      case ERROR_EDGE_15_MISSING   : return edgeError(res,1,5);
+      case ERROR_EDGE_14_MISSING   : return edgeError(res,1,4);
+      case ERROR_EDGE_05_MISSING   : return edgeError(res,0,5);
+      case ERROR_EDGE_04_MISSING   : return edgeError(res,0,4);
+
+      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
+      case ERROR_EDGE_TWISTED      : return res.getString(R.string.solver_generic_edge_twist);
+      case ERROR_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.CU_232.name() );
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseCU323.java b/src/main/java/org/distorted/solvers/SolverTablebaseCU323.java
new file mode 100644
index 00000000..b29b1225
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseCU323.java
@@ -0,0 +1,456 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TBCuboid323;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseCU323 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_MISSING = -1;
+  private static final int ERROR_EDGE_MISSING   = -2;
+  private static final int ERROR_CORNERS_CANNOT = -3;
+  private static final int ERROR_EDGE_TWISTED   = -4;
+  private static final int ERROR_CORNER_TWISTED = -5;
+
+  TablebasesAbstract mSolver;
+  private final int[] mFaceColors;
+  private int mErrorColor1, mErrorColor2, mErrorColor3;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseCU323(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    mFaceColors = new int[6];
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int findCorner(int[][] corners, int c1, int c2)
+    {
+    for(int i=0; i<8; i++)
+      {
+      int[] c = corners[i];
+
+      if( c[0]==c1 && c[1]==c2 ) return c[2];
+      if( c[1]==c1 && c[2]==c2 ) return c[0];
+      if( c[2]==c1 && c[0]==c2 ) return c[1];
+      }
+
+    return ERROR_CORNERS_CANNOT;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int missingColor()
+    {
+    boolean[] present = new boolean[6];
+    for(int i=0; i<5; i++) present[mFaceColors[i]] = true;
+
+    int indexFalse = -1;
+
+    for(int i=0; i<6; i++)
+      if( !present[i] )
+        {
+        if( indexFalse<0 ) indexFalse=i;
+        else return ERROR_CORNERS_CANNOT;
+        }
+
+    return indexFalse;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int edgePresent(int[][] edges, int f0, int f1)
+    {
+    int c0 = mFaceColors[f0];
+    int c1 = mFaceColors[f1];
+
+    for(int i=0; i<8; i++ )
+      {
+      int[] edge = edges[i];
+
+      if( edge[0]==c0 && edge[1]==c1 ) return i;
+      if( edge[0]==c1 && edge[1]==c0 )
+        {
+        mErrorColor1 = c0;
+        mErrorColor2 = c1;
+        return ERROR_EDGE_TWISTED;
+        }
+      }
+
+    mErrorColor1 = c0;
+    mErrorColor2 = c1;
+    return ERROR_EDGE_MISSING;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int cornerPresent(int[][] corners, int f0, int f1, int f2)
+    {
+    int c0 = mFaceColors[f0];
+    int c1 = mFaceColors[f1];
+    int c2 = mFaceColors[f2];
+
+    for(int i=0; i<8; i++)
+      {
+      int[] corner = corners[i];
+
+      if(  corner[0]==c0 && corner[1]==c1 && corner[2]==c2 ) return i;
+      if( (corner[0]==c1 && corner[1]==c2 && corner[2]==c0 ) ||
+          (corner[0]==c2 && corner[1]==c0 && corner[2]==c1 )  )
+        {
+        mErrorColor1 = c0;
+        mErrorColor2 = c1;
+        mErrorColor3 = c2;
+        return ERROR_CORNER_TWISTED;
+        }
+      }
+
+    mErrorColor1 = c0;
+    mErrorColor2 = c1;
+    mErrorColor3 = c2;
+    return ERROR_CORNER_MISSING;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retEdgePermutation(int[] output, int[][] edges)
+    {
+    int[][] e = { {5,3}, {4,3}, {5,2}, {4,2}, {1,3}, {1,2}, {0,3}, {0,2} };
+
+    for(int i=0; i<8; i++)
+      {
+      int[] ee = e[i];
+      output[i] = edgePresent(edges,ee[0],ee[1]);
+      if( output[i]<0 ) return output[i];
+      }
+
+    return 0;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retCornerPermutation(int[] output, int[][] corners)
+    {
+    int[][] c = { {5,1,3}, {1,4,3}, {1,5,2}, {4,1,2}, {0,5,3}, {4,0,3}, {5,0,2}, {0,4,2} };
+
+    for(int i=0; i<8; i++)
+      {
+      int[] cc = c[i];
+      output[i] = cornerPresent(corners,cc[0],cc[1],cc[2]);
+      if( output[i]<0 ) return output[i];
+      }
+
+    return 0;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeFaceColors(int[][] corners, int[][] edges, int[] centers)
+    {
+    mFaceColors[4] = edges[1][0];
+    mFaceColors[3] = edges[1][1];
+
+         if( centers[0]!=mFaceColors[3] ) mFaceColors[2] = centers[0];
+    else if( centers[1]!=mFaceColors[3] ) mFaceColors[2] = centers[1];
+    else return ERROR_CORNERS_CANNOT;
+
+    mFaceColors[0] = findCorner(corners,mFaceColors[4],mFaceColors[2]);
+    if( mFaceColors[0]<0 ) return mFaceColors[0];
+
+    mFaceColors[1] = findCorner(corners,mFaceColors[2],mFaceColors[4]);
+    if( mFaceColors[1]<0 ) return mFaceColors[1];
+
+    mFaceColors[5] = missingColor();
+    if( mFaceColors[5]<0 ) return mFaceColors[5];
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCorners(TwistyObject object, int[][] corners)
+    {
+    corners[0][0] = object.getCubitFaceStickerIndex(0,5);
+    corners[0][1] = object.getCubitFaceStickerIndex(0,1);
+    corners[0][2] = object.getCubitFaceStickerIndex(0,3);
+
+    corners[1][0] = object.getCubitFaceStickerIndex(1,3);
+    corners[1][1] = object.getCubitFaceStickerIndex(1,5);
+    corners[1][2] = object.getCubitFaceStickerIndex(1,1);
+
+    corners[2][0] = object.getCubitFaceStickerIndex(2,5);
+    corners[2][1] = object.getCubitFaceStickerIndex(2,1);
+    corners[2][2] = object.getCubitFaceStickerIndex(2,3);
+
+    corners[3][0] = object.getCubitFaceStickerIndex(3,5);
+    corners[3][1] = object.getCubitFaceStickerIndex(3,1);
+    corners[3][2] = object.getCubitFaceStickerIndex(3,3);
+
+    corners[4][0] = object.getCubitFaceStickerIndex(4,1);
+    corners[4][1] = object.getCubitFaceStickerIndex(4,3);
+    corners[4][2] = object.getCubitFaceStickerIndex(4,5);
+
+    corners[5][0] = object.getCubitFaceStickerIndex(5,5);
+    corners[5][1] = object.getCubitFaceStickerIndex(5,1);
+    corners[5][2] = object.getCubitFaceStickerIndex(5,3);
+
+    corners[6][0] = object.getCubitFaceStickerIndex(6,5);
+    corners[6][1] = object.getCubitFaceStickerIndex(6,1);
+    corners[6][2] = object.getCubitFaceStickerIndex(6,3);
+
+    corners[7][0] = object.getCubitFaceStickerIndex(7,1);
+    corners[7][1] = object.getCubitFaceStickerIndex(7,3);
+    corners[7][2] = object.getCubitFaceStickerIndex(7,5);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getEdges(TwistyObject object, int[][] edges)
+    {
+    edges[0][0] = object.getCubitFaceStickerIndex(8,5);
+    edges[0][1] = object.getCubitFaceStickerIndex(8,3);
+    edges[1][0] = object.getCubitFaceStickerIndex(9,3);
+    edges[1][1] = object.getCubitFaceStickerIndex(9,5);
+    edges[2][0] = object.getCubitFaceStickerIndex(10,3);
+    edges[2][1] = object.getCubitFaceStickerIndex(10,5);
+    edges[3][0] = object.getCubitFaceStickerIndex(11,5);
+    edges[3][1] = object.getCubitFaceStickerIndex(11,3);
+    edges[4][0] = object.getCubitFaceStickerIndex(12,3);
+    edges[4][1] = object.getCubitFaceStickerIndex(12,5);
+    edges[5][0] = object.getCubitFaceStickerIndex(13,5);
+    edges[5][1] = object.getCubitFaceStickerIndex(13,3);
+    edges[6][0] = object.getCubitFaceStickerIndex(14,3);
+    edges[6][1] = object.getCubitFaceStickerIndex(14,5);
+    edges[7][0] = object.getCubitFaceStickerIndex(15,5);
+    edges[7][1] = object.getCubitFaceStickerIndex(15,3);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCenters(TwistyObject object, int[] centers)
+    {
+    centers[0] = object.getCubitFaceStickerIndex(16,4);
+    centers[1] = object.getCubitFaceStickerIndex(17,4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners= new int[8][3];
+    int[][] edges  = new int[8][2];
+    int[] centers  = new int[2];
+
+    getCorners(object,corners);
+    getEdges(object,edges);
+    getCenters(object,centers);
+
+//for(int i=0; i<8; i++) android.util.Log.e("D", "corner: "+i+" : "+corners[i][0]+" "+corners[i][1]+" "+corners[i][2]);
+//for(int i=0; i<8; i++) android.util.Log.e("D", "edge: "+i+" : "+edges[i][0]+" "+edges[i][1]);
+
+    int result0 = computeFaceColors(corners, edges, centers);
+    if( result0<0 ) return result0;
+
+    int[] corner_perm = new int[8];
+    int result1 = retCornerPermutation(corner_perm,corners);
+    if( result1<0 ) return result1;
+
+//android.util.Log.e("D", "upper: "+mUpper);
+//for(int i=0; i<6; i++) android.util.Log.e("D", "face color: "+mFaceColors[i]);
+
+    int[] edge_perm8 = new int[8];
+    int result2 = retEdgePermutation(edge_perm8,edges);
+    if( result2<0 ) return result2;
+
+    int[] edge_perm7 = TBCuboid323.edgePermTo7(edge_perm8);
+/*
+    TablebaseHelpers.displayTable(corner_perm, "CORNER PERM");
+    TablebaseHelpers.displayTable(edge_perm8, "EDGE PERM8");
+    TablebaseHelpers.displayTable(edge_perm7, "EDGE PERM7");
+*/
+    int corner_perm_num = TablebaseHelpers.computePermutationNum(corner_perm);
+    int edge_perm_num = TablebaseHelpers.computePermutationNum(edge_perm7);
+    int centersInPlace = (centers[0]==mFaceColors[2]) ? 0 : 1;
+
+//android.util.Log.e("D", "corner_perm_num: "+corner_perm_num+" edge_perm_num: "+edge_perm_num+" inPlace: "+centersInPlace);
+//android.util.Log.e("D", "index="+(corner_perm_num + 40320*( centersInPlace + 2*edge_perm_num)));
+
+    return corner_perm_num + 40320*( centersInPlace + 2*edge_perm_num);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_yellow4;
+      case 1: return R.string.color_white4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_green4;
+      case 4: return R.string.color_red4;
+      case 5: return R.string.color_orange4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_yellow3;
+      case 1: return R.string.color_white3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_green3;
+      case 4: return R.string.color_red3;
+      case 5: return R.string.color_orange3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex5(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_yellow5;
+      case 1: return R.string.color_white5;
+      case 2: return R.string.color_blue5;
+      case 3: return R.string.color_green5;
+      case 4: return R.string.color_red5;
+      case 5: return R.string.color_orange5;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex6(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_yellow6;
+      case 1: return R.string.color_white6;
+      case 2: return R.string.color_blue6;
+      case 3: return R.string.color_green6;
+      case 4: return R.string.color_red6;
+      case 5: return R.string.color_orange6;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeTwistedError(Resources res, int color0, int color1)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex6(color1);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_twisted_edge,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerTwistedError(Resources res, int color0, int color1, int color2)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex3(color1);
+    int j2 = getColorIndex5(color2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_twisted_corner,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeMissingError(Resources res, int color0, int color1)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex6(color1);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerMissingError(Resources res, int color0, int color1, int color2)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex3(color1);
+    int j2 = getColorIndex4(color2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_MISSING : return cornerMissingError(res,mErrorColor1,mErrorColor2,mErrorColor3);
+      case ERROR_EDGE_MISSING   : return edgeMissingError(res,mErrorColor1,mErrorColor2);
+      case ERROR_CORNERS_CANNOT : return res.getString(R.string.solver_generic_corners_cannot);
+      case ERROR_EDGE_TWISTED   : return edgeTwistedError(res,mErrorColor1,mErrorColor2);
+      case ERROR_CORNER_TWISTED : return cornerTwistedError(res,mErrorColor1,mErrorColor2,mErrorColor3);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// a few cu_323 max (depth 18) indices:
+// 1180633, 1180642, 1182044, 1190482, 128151851, 128190028
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.CU_323.name() );
+      }
+
+    ((TBCuboid323)mSolver).initialize();
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseCUBE2.java b/src/main/java/org/distorted/solvers/SolverTablebaseCUBE2.java
new file mode 100644
index 00000000..72758435
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseCUBE2.java
@@ -0,0 +1,297 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+import org.distorted.objectlib.tablebases.TBCube2;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseCUBE2 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_135_MISSING = -1;
+  private static final int ERROR_CORNER_134_MISSING = -2;
+  private static final int ERROR_CORNER_125_MISSING = -3;
+  private static final int ERROR_CORNER_124_MISSING = -4;
+  private static final int ERROR_CORNER_035_MISSING = -5;
+  private static final int ERROR_CORNER_034_MISSING = -6;
+  private static final int ERROR_CORNER_025_MISSING = -7;
+  private static final int ERROR_CORNER_024_MISSING = -8;
+  private static final int ERROR_CORNERS_CANNOT     = -9;
+  private static final int ERROR_CORNER_TWISTED     = -10;
+
+  TablebasesAbstract mSolver;
+  private final int[] mFaceColors;
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private void fillCornerTwists(int[] output, int[][] corners, int[] perm)
+    {
+    for(int i=0; i<8; i++)
+      {
+      int[] c = corners[perm[i]];
+
+           if( c[0]==mFaceColors[0] || c[0]==mFaceColors[1] ) output[i] = 0;
+      else if( c[1]==mFaceColors[0] || c[1]==mFaceColors[1] ) output[i] = 1;
+      else                                                    output[i] = 2;
+      }
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int cornerIndex(int[][] corners, int i1, int i2, int i3)
+    {
+    int c1 = mFaceColors[i1];
+    int c2 = mFaceColors[i2];
+    int c3 = mFaceColors[i3];
+
+    for(int i=0; i<8; i++)
+      {
+      int[] cor = corners[i];
+
+      if( (cor[0]==c1 && cor[1]==c2 && cor[2]==c3) ||
+          (cor[0]==c2 && cor[1]==c3 && cor[2]==c1) ||
+          (cor[0]==c3 && cor[1]==c1 && cor[2]==c2)  ) return i;
+      }
+
+    return -1;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retCornerPermutation(int[] output, int[][] corners)
+    {
+    for(int i=0; i<8; i++) output[i] = -1;
+
+    output[0] = cornerIndex(corners, 1,2,5);
+    output[1] = cornerIndex(corners, 1,4,2);
+    output[2] = cornerIndex(corners, 1,5,3);
+    output[3] = cornerIndex(corners, 1,3,4);
+    output[4] = cornerIndex(corners, 0,5,2);
+    output[5] = cornerIndex(corners, 0,2,4);
+    output[6] = cornerIndex(corners, 0,3,5);
+    output[7] = cornerIndex(corners, 0,4,3);
+
+    if( output[0]==-1 ) return ERROR_CORNER_125_MISSING;
+    if( output[1]==-1 ) return ERROR_CORNER_124_MISSING;
+    if( output[2]==-1 ) return ERROR_CORNER_135_MISSING;
+    if( output[3]==-1 ) return ERROR_CORNER_134_MISSING;
+    if( output[4]==-1 ) return ERROR_CORNER_025_MISSING;
+    if( output[5]==-1 ) return ERROR_CORNER_024_MISSING;
+    if( output[6]==-1 ) return ERROR_CORNER_035_MISSING;
+    if( output[7]==-1 ) return ERROR_CORNER_034_MISSING;
+
+    return 0;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int findColor(int[][] corners, int c1, int c2)
+    {
+    for(int i=0; i<8; i++)
+      {
+      int[] cor = corners[i];
+
+      if( cor[0]==c1 && cor[1]==c2 ) return cor[2];
+      if( cor[1]==c1 && cor[2]==c2 ) return cor[0];
+      if( cor[2]==c1 && cor[0]==c2 ) return cor[1];
+      }
+
+    return -1;
+    }
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeFaceColors(int[][] corners)
+    {
+    mFaceColors[1] = corners[1][0];
+    mFaceColors[2] = corners[1][2];
+    mFaceColors[4] = corners[1][1];
+
+    mFaceColors[0] = findColor(corners,mFaceColors[2],mFaceColors[4]);
+    mFaceColors[3] = findColor(corners,mFaceColors[4],mFaceColors[1]);
+    mFaceColors[5] = findColor(corners,mFaceColors[1],mFaceColors[2]);
+
+    for(int i=0; i<6; i++)
+      {
+      int color = mFaceColors[i];
+      if( color<0 ) return ERROR_CORNERS_CANNOT;
+
+      for(int j=i+1; j<6; j++)
+        if( mFaceColors[j]==color ) return ERROR_CORNERS_CANNOT;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseCUBE2(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    mFaceColors = new int[6];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCorners(TwistyObject object, int[][] corners)
+    {
+    corners[0][0] = object.getCubitFaceStickerIndex(0,1);
+    corners[0][1] = object.getCubitFaceStickerIndex(0,3);
+    corners[0][2] = object.getCubitFaceStickerIndex(0,5);
+
+    corners[1][0] = object.getCubitFaceStickerIndex(1,3);
+    corners[1][1] = object.getCubitFaceStickerIndex(1,5);
+    corners[1][2] = object.getCubitFaceStickerIndex(1,1);
+
+    corners[2][0] = object.getCubitFaceStickerIndex(2,5);
+    corners[2][1] = object.getCubitFaceStickerIndex(2,1);
+    corners[2][2] = object.getCubitFaceStickerIndex(2,3);
+
+    corners[3][0] = object.getCubitFaceStickerIndex(3,1);
+    corners[3][1] = object.getCubitFaceStickerIndex(3,3);
+    corners[3][2] = object.getCubitFaceStickerIndex(3,5);
+
+    corners[4][0] = object.getCubitFaceStickerIndex(4,1);
+    corners[4][1] = object.getCubitFaceStickerIndex(4,3);
+    corners[4][2] = object.getCubitFaceStickerIndex(4,5);
+
+    corners[5][0] = object.getCubitFaceStickerIndex(5,1);
+    corners[5][1] = object.getCubitFaceStickerIndex(5,3);
+    corners[5][2] = object.getCubitFaceStickerIndex(5,5);
+
+    corners[6][0] = object.getCubitFaceStickerIndex(6,1);
+    corners[6][1] = object.getCubitFaceStickerIndex(6,3);
+    corners[6][2] = object.getCubitFaceStickerIndex(6,5);
+
+    corners[7][0] = object.getCubitFaceStickerIndex(7,1);
+    corners[7][1] = object.getCubitFaceStickerIndex(7,3);
+    corners[7][2] = object.getCubitFaceStickerIndex(7,5);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners= new int[8][3];
+    getCorners(object,corners);
+
+    int result0 = computeFaceColors(corners);
+    if( result0<0 ) return result0;
+
+    int[] perm = new int[8];
+    int result1 = retCornerPermutation(perm,corners);
+    if( result1<0 ) return result1;
+
+    int[] perm7 = TBCube2.shrinkPerm(perm);
+    int permNum = TablebaseHelpers.computePermutationNum(perm7);
+    int[] twist = new int[8];
+    fillCornerTwists(twist,corners,perm);
+
+    int totalTwist = 0;
+    for(int i=0; i<8; i++) totalTwist += twist[i];
+    if( ((totalTwist)%3) != 0 ) return ERROR_CORNER_TWISTED;
+
+    int twistNum = twist[0] + 3*(twist[2] + 3*(twist[3] + 3*(twist[4] + 3*(twist[5] + 3*twist[6]))));
+
+    return twistNum + 729*permNum;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_yellow4;
+      case 1: return R.string.color_white4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_green4;
+      case 4: return R.string.color_red4;
+      case 5: return R.string.color_orange4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_yellow3;
+      case 1: return R.string.color_white3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_green3;
+      case 4: return R.string.color_red3;
+      case 5: return R.string.color_orange3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerError(Resources res, int face0, int face1, int face2)
+    {
+    int j0 = getColorIndex3(face0);
+    int j1 = getColorIndex3(face1);
+    int j2 = getColorIndex4(face2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_135_MISSING: return cornerError(res,1,3,5);
+      case ERROR_CORNER_134_MISSING: return cornerError(res,1,3,4);
+      case ERROR_CORNER_125_MISSING: return cornerError(res,1,2,5);
+      case ERROR_CORNER_124_MISSING: return cornerError(res,1,2,4);
+      case ERROR_CORNER_035_MISSING: return cornerError(res,0,3,5);
+      case ERROR_CORNER_034_MISSING: return cornerError(res,0,3,4);
+      case ERROR_CORNER_025_MISSING: return cornerError(res,0,2,5);
+      case ERROR_CORNER_024_MISSING: return cornerError(res,0,2,4);
+      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
+      case ERROR_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.CUBE_2.name() );
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseDIAM2.java b/src/main/java/org/distorted/solvers/SolverTablebaseDIAM2.java
new file mode 100644
index 00000000..22e779e0
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseDIAM2.java
@@ -0,0 +1,512 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseDIAM2 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_FR_MISSING = -1;
+  private static final int ERROR_CORNER_BR_MISSING = -2;
+  private static final int ERROR_CORNER_BL_MISSING = -3;
+  private static final int ERROR_CORNER_FL_MISSING = -4;
+  private static final int ERROR_CORNER_TO_MISSING = -5;
+  private static final int ERROR_CORNER_BO_MISSING = -6;
+
+  private static final int ERROR_CENTER_0_MISSING = -7;
+  private static final int ERROR_CENTER_1_MISSING = -8;
+  private static final int ERROR_CENTER_2_MISSING = -9;
+  private static final int ERROR_CENTER_3_MISSING = -10;
+  private static final int ERROR_CENTER_4_MISSING = -11;
+  private static final int ERROR_CENTER_5_MISSING = -12;
+  private static final int ERROR_CENTER_6_MISSING = -13;
+  private static final int ERROR_CENTER_7_MISSING = -14;
+
+  private static final int ERROR_TWO_CORNERS      = -15;
+  private static final int ERROR_TWO_CENTERS      = -16;
+  private static final int ERROR_CORNER_TWIST_90  = -17;
+  private static final int ERROR_CORNER_TWIST_180 = -18;
+  private static final int ERROR_CORNERS_CANNOT   = -19;
+
+  private TablebasesAbstract mSolver;
+  private final int[] mFaceColors;
+
+  private static final int[] FREE_CENTERS = {0,2,5,7};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseDIAM2(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    mFaceColors = new int[8];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retCorner(int[][] corners, int c1, int c2)
+    {
+    for(int i=0; i<6; i++)
+      {
+      int[] cor = corners[i];
+      int twist = retCornerTwist(cor,c1,c2);
+      if( twist>=0 ) return i;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] getCornersPermutation(int[][] corners)
+    {
+    int[] perm = new int[6];
+
+    perm[0] = retCorner(corners,mFaceColors[1],mFaceColors[4]);
+    perm[1] = retCorner(corners,mFaceColors[1],mFaceColors[6]);
+    perm[2] = retCorner(corners,mFaceColors[3],mFaceColors[6]);
+    perm[3] = retCorner(corners,mFaceColors[3],mFaceColors[4]);
+    perm[4] = retCorner(corners,mFaceColors[1],mFaceColors[3]);
+    perm[5] = retCorner(corners,mFaceColors[4],mFaceColors[6]);
+
+    return perm;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeCornerTwist(int[] corner, int color )
+    {
+    if( corner[3]==color ) return 0;
+    if( corner[2]==color ) return 1;
+    if( corner[1]==color ) return 2;
+    if( corner[0]==color ) return 3;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] getCornersTwist(int[] corners_perm, int[][] corners)
+    {
+    int[] twist = new int[6];
+
+    twist[0] = computeCornerTwist( corners[corners_perm[0]], mFaceColors[1] );
+    twist[1] = computeCornerTwist( corners[corners_perm[1]], mFaceColors[1] );
+    twist[2] = computeCornerTwist( corners[corners_perm[2]], mFaceColors[3] );
+    twist[3] = computeCornerTwist( corners[corners_perm[3]], mFaceColors[3] );
+    twist[4] = computeCornerTwist( corners[corners_perm[4]], mFaceColors[1] );
+    twist[5] = computeCornerTwist( corners[corners_perm[5]], mFaceColors[4] );
+
+    return twist;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retCenter(int color, int[] centers)
+    {
+    for(int i=0; i<4; i++ )
+      if( centers[FREE_CENTERS[i]]==color ) return i;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] getFreeCentersPermutation(int[] centers)
+    {
+    int[] perm = new int[4];
+
+    for(int i=0; i<4; i++ )
+      perm[i] = retCenter(mFaceColors[FREE_CENTERS[i]],centers);
+
+    return perm;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getTotalTwist(int[] twist)
+    {
+    int total = 0;
+    boolean even = true;
+
+    for(int i=0; i<6; i++)
+      {
+      int t= twist[i];
+
+      if( t==1 || t==3 ) return ERROR_CORNER_TWIST_90;
+      if( t==2 )
+        {
+        if( i<5 )
+          {
+          total+=(1<<i);
+          even = !even;
+          }
+        else if( even ) return ERROR_CORNER_TWIST_180;
+        }
+      }
+
+    return total;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retCornerTwist(int[] corner, int c1, int c2)
+    {
+    if( corner[0]==c1 && corner[2]==c2 ) return 1;
+    if( corner[1]==c1 && corner[3]==c2 ) return 2;
+    if( corner[2]==c1 && corner[0]==c2 ) return 3;
+    if( corner[3]==c1 && corner[1]==c2 ) return 0;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int figureOutColor(int[][] corners, int c1, int c2)
+    {
+    for(int i=0; i<6; i++)
+      {
+      int[] cor = corners[i];
+      int twist = retCornerTwist(cor,c1,c2);
+      if( twist>=0 ) return cor[twist];
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// We only move the UR, UR, DR & DB faces --> those centers are fixed and determine the colors of
+// the faces.
+
+  private int figureOutFaceColors(int[] output, int[] centers, int[][] corners)
+    {
+    output[1] = centers[1];
+    output[3] = centers[3];
+    output[4] = centers[4];
+    output[6] = centers[6];
+
+    int color01 = figureOutColor(corners,output[4],output[1]);
+    if( color01<0 ) return ERROR_CORNER_FR_MISSING;
+    int color02 = figureOutColor(corners,output[3],output[4]);
+    if( color02<0 ) return ERROR_CORNER_FL_MISSING;
+    int color03 = figureOutColor(corners,output[1],output[3]);
+    if( color03<0 ) return ERROR_CORNER_TO_MISSING;
+    if( color01!=color02 || color01!=color03 ) return ERROR_CORNERS_CANNOT;
+    output[0] = color01;
+
+    int color21 = figureOutColor(corners,output[1],output[6]);
+    if( color21<0 ) return ERROR_CORNER_BR_MISSING;
+    int color22 = figureOutColor(corners,output[6],output[3]);
+    if( color22<0 ) return ERROR_CORNER_BL_MISSING;
+    int color23 = figureOutColor(corners,output[3],output[1]);
+    if( color23<0 ) return ERROR_CORNER_TO_MISSING;
+    if( color21!=color22 || color21!=color23 ) return ERROR_CORNERS_CANNOT;
+    output[2] = color21;
+
+    int color51 = figureOutColor(corners,output[1],output[4]);
+    if( color51<0 ) return ERROR_CORNER_FR_MISSING;
+    int color52 = figureOutColor(corners,output[6],output[1]);
+    if( color52<0 ) return ERROR_CORNER_BR_MISSING;
+    int color53 = figureOutColor(corners,output[4],output[6]);
+    if( color53<0 ) return ERROR_CORNER_BO_MISSING;
+    if( color51!=color52 || color51!=color53 ) return ERROR_CORNERS_CANNOT;
+    output[5] = color51;
+
+    int color71 = figureOutColor(corners,output[3],output[6]);
+    if( color71<0 ) return ERROR_CORNER_BL_MISSING;
+    int color72 = figureOutColor(corners,output[4],output[3]);
+    if( color72<0 ) return ERROR_CORNER_FL_MISSING;
+    int color73 = figureOutColor(corners,output[6],output[4]);
+    if( color73<0 ) return ERROR_CORNER_BO_MISSING;
+    if( color71!=color72 || color71!=color73 ) return ERROR_CORNERS_CANNOT;
+    output[7] = color71;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllColorsDifferent()
+    {
+    for(int i=0; i<8; i++)
+      {
+      boolean present = false;
+
+      for(int j=0; j<8; j++)
+        if( mFaceColors[j]==i )
+          {
+          present=true;
+          break;
+          }
+
+      if( !present ) return ERROR_CORNERS_CANNOT;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCentersPresent(int[] centers)
+    {
+    for(int i=0; i<8; i++)
+      {
+      boolean present = false;
+
+      for(int j=0; j<8; j++)
+        if( centers[j]==i )
+          {
+          present=true;
+          break;
+          }
+
+      if( !present )
+        {
+        switch(i)
+          {
+          case 0: return ERROR_CENTER_0_MISSING;
+          case 1: return ERROR_CENTER_1_MISSING;
+          case 2: return ERROR_CENTER_2_MISSING;
+          case 3: return ERROR_CENTER_3_MISSING;
+          case 4: return ERROR_CENTER_4_MISSING;
+          case 5: return ERROR_CENTER_5_MISSING;
+          case 6: return ERROR_CENTER_6_MISSING;
+          case 7: return ERROR_CENTER_7_MISSING;
+          }
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCorners(TwistyObject object, int[][] corners)
+    {
+    corners[0][0] = object.getCubitFaceStickerIndex( 0,5); // FR
+    corners[0][1] = object.getCubitFaceStickerIndex( 0,4);
+    corners[0][2] = object.getCubitFaceStickerIndex( 0,0);
+    corners[0][3] = object.getCubitFaceStickerIndex( 0,1);
+
+    corners[1][0] = object.getCubitFaceStickerIndex( 1,2); // BR
+    corners[1][1] = object.getCubitFaceStickerIndex( 1,6);
+    corners[1][2] = object.getCubitFaceStickerIndex( 1,5);
+    corners[1][3] = object.getCubitFaceStickerIndex( 1,1);
+
+    corners[2][0] = object.getCubitFaceStickerIndex( 2,7); // BL
+    corners[2][1] = object.getCubitFaceStickerIndex( 2,6);
+    corners[2][2] = object.getCubitFaceStickerIndex( 2,2);
+    corners[2][3] = object.getCubitFaceStickerIndex( 2,3);
+
+    corners[3][0] = object.getCubitFaceStickerIndex( 3,0); // FL
+    corners[3][1] = object.getCubitFaceStickerIndex( 3,4);
+    corners[3][2] = object.getCubitFaceStickerIndex( 3,7);
+    corners[3][3] = object.getCubitFaceStickerIndex( 3,3);
+
+    corners[4][0] = object.getCubitFaceStickerIndex( 4,0); // U
+    corners[4][1] = object.getCubitFaceStickerIndex( 4,3);
+    corners[4][2] = object.getCubitFaceStickerIndex( 4,2);
+    corners[4][3] = object.getCubitFaceStickerIndex( 4,1);
+
+    corners[5][0] = object.getCubitFaceStickerIndex( 5,5); // D
+    corners[5][1] = object.getCubitFaceStickerIndex( 5,6);
+    corners[5][2] = object.getCubitFaceStickerIndex( 5,7);
+    corners[5][3] = object.getCubitFaceStickerIndex( 5,4);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCenters(TwistyObject object, int[] centers)
+    {
+    centers[0] = object.getCubitFaceStickerIndex( 9,0); // UF
+    centers[1] = object.getCubitFaceStickerIndex( 6,0); // UR
+    centers[2] = object.getCubitFaceStickerIndex( 7,0); // UB
+    centers[3] = object.getCubitFaceStickerIndex( 8,0); // UL
+    centers[4] = object.getCubitFaceStickerIndex(13,0); // DF
+    centers[5] = object.getCubitFaceStickerIndex(10,1); // DR
+    centers[6] = object.getCubitFaceStickerIndex(11,0); // DB
+    centers[7] = object.getCubitFaceStickerIndex(12,0); // DL
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners = new int[6][4];
+    int[] centers = new int[8];
+
+    getCorners(object,corners);
+    getCenters(object,centers);
+
+    int result1 = checkAllCentersPresent(centers);
+    if( result1<0 ) return result1;
+
+    int result2 = figureOutFaceColors(mFaceColors,centers,corners);
+    if( result2<0 ) return result2;
+
+    int result3 = checkAllColorsDifferent();
+    if( result3<0 ) return result3;
+
+    int[] corners_perm = getCornersPermutation(corners);
+    boolean even1 = TablebaseHelpers.permutationIsEven(corners_perm);
+    if( !even1 ) return ERROR_TWO_CORNERS;
+
+    int[] free_centers_perm = getFreeCentersPermutation(centers);
+    boolean even2 = TablebaseHelpers.permutationIsEven(free_centers_perm);
+    if( !even2 ) return ERROR_TWO_CENTERS;
+
+    int[] corners_twist = getCornersTwist(corners_perm, corners);
+/*
+android.util.Log.e("D", "faces: "+mFaceColors[0]+" "+mFaceColors[1]+" "+mFaceColors[2]+" "
++mFaceColors[3]+" "+mFaceColors[4]+" "+mFaceColors[5]+" "+mFaceColors[6]+" "+mFaceColors[7]);
+
+android.util.Log.e("D", "corn perm: "+corners_perm[0]+" "+corners_perm[1]+" "+corners_perm[2]+" "
++corners_perm[3]+" "+corners_perm[4]+" "+corners_perm[5]);
+
+android.util.Log.e("D", "corn twist: "+corners_twist[0]+" "+corners_twist[1]+" "+corners_twist[2]+" "
++corners_twist[3]+" "+corners_twist[4]+" "+corners_twist[5]);
+*/
+    int totalTwist = getTotalTwist(corners_twist);
+    if( totalTwist<0 ) return totalTwist;
+
+    int corners_perm_num = TablebaseHelpers.computeEvenPermutationNum(corners_perm);
+    int centers_perm_num = TablebaseHelpers.computeEvenPermutationNum(free_centers_perm);
+
+    return centers_perm_num + 12*(totalTwist + 32*corners_perm_num);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_violet4;
+      case 1: return R.string.color_grey4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_red4;
+      case 4: return R.string.color_orange4;
+      case 5: return R.string.color_green4;
+      case 6: return R.string.color_white4;
+      case 7: return R.string.color_yellow4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_violet3;
+      case 1: return R.string.color_grey3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_red3;
+      case 4: return R.string.color_orange3;
+      case 5: return R.string.color_green3;
+      case 6: return R.string.color_white3;
+      case 7: return R.string.color_yellow3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex2(int face)
+    {
+    switch(face)
+      {
+      case 0: return R.string.color_violet2;
+      case 1: return R.string.color_grey2;
+      case 2: return R.string.color_blue2;
+      case 3: return R.string.color_red2;
+      case 4: return R.string.color_orange2;
+      case 5: return R.string.color_green2;
+      case 6: return R.string.color_white2;
+      case 7: return R.string.color_yellow2;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String centerError(Resources res, int face)
+    {
+    String color = res.getString(getColorIndex2(face));
+    return res.getString(R.string.solver_generic_missing_center,color);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerError(Resources res, int f1, int f2)
+    {
+    String c1 = res.getString(getColorIndex3(f1));
+    String c2 = res.getString(getColorIndex4(f2));
+    return res.getString(R.string.solver_generic_missing_corner2,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_FR_MISSING: return cornerError(res,1,4);
+      case ERROR_CORNER_BR_MISSING: return cornerError(res,1,6);
+      case ERROR_CORNER_BL_MISSING: return cornerError(res,3,6);
+      case ERROR_CORNER_FL_MISSING: return cornerError(res,3,4);
+      case ERROR_CORNER_TO_MISSING: return cornerError(res,1,3);
+      case ERROR_CORNER_BO_MISSING: return cornerError(res,4,6);
+
+      case ERROR_CENTER_0_MISSING : return centerError(res,0);
+      case ERROR_CENTER_1_MISSING : return centerError(res,1);
+      case ERROR_CENTER_2_MISSING : return centerError(res,2);
+      case ERROR_CENTER_3_MISSING : return centerError(res,3);
+      case ERROR_CENTER_4_MISSING : return centerError(res,4);
+      case ERROR_CENTER_5_MISSING : return centerError(res,5);
+      case ERROR_CENTER_6_MISSING : return centerError(res,6);
+      case ERROR_CENTER_7_MISSING : return centerError(res,7);
+
+      case ERROR_TWO_CORNERS      : return res.getString(R.string.solver_generic_two_corners);
+      case ERROR_TWO_CENTERS      : return res.getString(R.string.solver_generic_two_centers);
+      case ERROR_CORNER_TWIST_90  : return res.getString(R.string.solver_generic_corner_twist) + " (90)";
+      case ERROR_CORNER_TWIST_180 : return res.getString(R.string.solver_generic_corner_twist) + " (180)";
+      case ERROR_CORNERS_CANNOT   : return res.getString(R.string.solver_generic_corners_cannot);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.DIAM_2.name());
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseDINO4.java b/src/main/java/org/distorted/solvers/SolverTablebaseDINO4.java
new file mode 100644
index 00000000..28c34893
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseDINO4.java
@@ -0,0 +1,185 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.shape.ShapeColors;
+import org.distorted.objectlib.shape.ShapeHexahedron;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TBDino4;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseDINO4 extends SolverTablebase
+{
+  private static final int ERROR_EDGE_THREE  = -1;
+  private TablebasesAbstract mSolver;
+  private int mErrorColor;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseDINO4(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os, res,object);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getEdges(TwistyObject object, int[][] edges)
+    {
+    for(int i=0; i<12; i++)
+      {
+      edges[0][i] = object.getCubitFaceStickerIndex(i,0);
+      edges[1][i] = object.getCubitFaceStickerIndex(i,1);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkEdges(int[][] edges)
+    {
+    int numB=0,numR=0, numY=0, numW=0;
+    int indB=0,indY=0, indW=0, indR=0;
+
+    int[] hexColors = ShapeHexahedron.FACE_COLORS;
+
+    for(int i=0; i<6; i++)
+      {
+      int color = hexColors[i];
+
+      if( color == ShapeColors.COLOR_WHITE) indW = i;
+      if( color == ShapeColors.COLOR_YELLOW) indY = i;
+      if( color == ShapeColors.COLOR_RED) indR = i;
+      if( color == ShapeColors.COLOR_BLUE) indB = i;
+      }
+
+    for(int i=0; i<12; i++)
+      {
+      int e = edges[0][i];
+
+      if( e==edges[1][i] )
+        {
+        if( e==indY ) numY++;
+        if( e==indW ) numW++;
+        if( e==indR ) numR++;
+        if( e==indB ) numB++;
+        }
+      }
+
+    if( numY !=3 ) { mErrorColor=0; return ERROR_EDGE_THREE; }
+    if( numW !=3 ) { mErrorColor=1; return ERROR_EDGE_THREE; }
+    if( numB !=3 ) { mErrorColor=2; return ERROR_EDGE_THREE; }
+    if( numR !=3 ) { mErrorColor=3; return ERROR_EDGE_THREE; }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void remap(int[] perm, int[] edges, int index, int section)
+    {
+    int val = edges[index];
+
+    for(int i=index;i<12; i++)
+      if( edges[i]==val )
+        {
+        edges[i]=-1;
+        perm[i] = section;
+        }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] getPermutation(int[] edges)
+    {
+    int[] perm = new int[12];
+
+    int index0 = 0;
+    remap(perm,edges,index0,0);
+
+    int index1 = index0+1;
+
+    for(;index1<12;index1++)
+      if( edges[index1]>=0 ) break;
+    remap(perm,edges,index1,1);
+
+    int index2 = index1+1;
+
+    for(;index2<12;index2++)
+      if( edges[index2]>=0 ) break;
+    remap(perm,edges,index2,2);
+
+    int index3 = index2+1;
+
+    for(;index3<12;index3++)
+      if( edges[index3]>=0 ) break;
+    remap(perm,edges,index3,3);
+
+    return perm;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] edges = new int[2][12];
+    getEdges(object,edges);
+
+    int result1 = checkEdges(edges);
+    if( result1<0 ) return result1;
+
+    int[] perm = getPermutation(edges[0]);
+    return TBDino4.indexFromPartition(perm);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex7(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_yellow7;
+      case 1: return R.string.color_white7;
+      case 2: return R.string.color_blue7;
+      case 3: return R.string.color_red7;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    int i = getColorIndex7(mErrorColor);
+    String color = res.getString(i);
+
+    return res.getString(R.string.solver_generic_edge_three,color);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.DIN4_3.name() );
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseDINO6.java b/src/main/java/org/distorted/solvers/SolverTablebaseDINO6.java
new file mode 100644
index 00000000..681e7478
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseDINO6.java
@@ -0,0 +1,451 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TBDino6;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseDINO6 extends SolverTablebase
+{
+  private static final int ERROR_EDGE_MISSING        = -1;
+  private static final int ERROR_EDGE_CANNOT         = -2;
+  private static final int ERROR_EDGE_TWISTED        = -3;
+  private static final int ERROR_EDGE_MONOCHROMATIC  = -4;
+  private static final int ERROR_EDGE_TWICE          = -5;
+  private static final int ERROR_TWO_EDGES           = -6;
+
+  int[][] EDGE_MAP = {
+                      {4,2},{0,4},{4,3},{1,4},
+                      {2,0},{3,0},{3,1},{2,1},
+                      {5,2},{0,5},{5,3},{1,5}
+                     };
+
+  private TablebasesAbstract mSolver;
+  private final int[] mFaceColors;
+  private int mErrorColor1, mErrorColor2;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseDINO6(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    mFaceColors = new int[6];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getEdges(TwistyObject object, int[][] edges)
+    {
+    for(int i=0; i<12; i++)
+      {
+      edges[i][0] = object.getCubitFaceStickerIndex(i,0);
+      edges[i][1] = object.getCubitFaceStickerIndex(i,1);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeOpposite(int color, int[][] edges)
+    {
+    int[] buffer = new int[6];
+    for(int i=0; i<6; i++) buffer[i] = 0;
+
+    for(int i=0; i<12; i++)
+      {
+      int e0 = edges[i][0];
+      int e1 = edges[i][1];
+
+      if( e0==color && e1==color)
+        {
+        mErrorColor1 = color;
+        return ERROR_EDGE_MONOCHROMATIC;
+        }
+
+      if( e0==color )
+        {
+        buffer[e1]++;
+
+        if( buffer[e1]>1 )
+          {
+          mErrorColor1 = color;
+          mErrorColor2 = e1;
+          return ERROR_EDGE_TWICE;
+          }
+        }
+
+      if( e1==color )
+        {
+        buffer[e0]++;
+
+        if( buffer[e0]>1 )
+          {
+          mErrorColor1 = color;
+          mErrorColor2 = e0;
+          return ERROR_EDGE_TWICE;
+          }
+        }
+      }
+
+    int total = 0;
+
+    for(int i=0; i<6; i++)
+      if( buffer[i]==1 )
+        {
+        if( i!=color) total++;
+        else
+          {
+          mErrorColor1 = i;
+          return ERROR_EDGE_MONOCHROMATIC;
+          }
+        }
+
+    if( total==4 )
+      for(int i=0; i<6; i++)
+        if( buffer[i]==0 && i!=color ) return i;
+
+    return ERROR_EDGE_CANNOT;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int fillUpRemainingFaceColors(boolean[] buffer)
+    {
+    for(int i=0; i<6; i++)
+      {
+      buffer[i] = false;
+      }
+    for(int i=0; i<6; i++)
+      {
+      int color = mFaceColors[i];
+      if( color>=0 ) buffer[color] = true;
+      }
+
+    int total = 0;
+    for(int i=0; i<6; i++)
+      if( buffer[i] ) total++;
+
+    if( total==4 )
+      {
+      for(int i=0; i<6; i++)
+        if( !buffer[i] )
+          {
+          buffer[i]=true;
+          mFaceColors[0] = i;
+          break;
+          }
+      for(int i=0; i<6; i++)
+        if( !buffer[i] )
+          {
+          buffer[i]=true;
+          mFaceColors[1] = i;
+          break;
+          }
+
+      return 0;
+      }
+
+    return ERROR_EDGE_CANNOT;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int figureOutFaceColors(int[][] edges)
+    {
+    boolean[] present = new boolean[6];
+
+    for(int i=0; i<6; i++) mFaceColors[i] = -1;
+
+    for(int i=0; i<12; i++)
+      {
+      int[] edge = edges[i];
+      int c0 = edge[0];
+      int c1 = edge[1];
+
+      if( c0>=0 && c0<6 ) present[c0] = true;
+      if( c1>=0 && c1<6 ) present[c1] = true;
+      }
+
+    for(int i=0; i<6; i++)
+      if( !present[i] )
+        {
+        mErrorColor1 = i;
+        mErrorColor2 = (i<4 ? i+2 : i-2 );
+        return ERROR_EDGE_MISSING;
+        }
+
+    if( edges[0][0]==edges[0][1] ) return ERROR_EDGE_CANNOT;
+
+    mFaceColors[4] = edges[0][0];
+    mFaceColors[2] = edges[0][1];
+    mFaceColors[5] = computeOpposite(mFaceColors[4], edges);
+    if( mFaceColors[5]<0 ) return mFaceColors[5];
+
+    mFaceColors[3] = computeOpposite(mFaceColors[2], edges);
+    if( mFaceColors[3]<0 ) return mFaceColors[3];
+
+    int success = fillUpRemainingFaceColors(present);
+    if( success<0 ) return success;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean notPresent(int face0, int face1, int[][] edges)
+    {
+    int c0=mFaceColors[face0];
+    int c1=mFaceColors[face1];
+
+    for(int i=0; i<12; i++)
+      {
+      int[] edge = edges[i];
+      if( edge[0]==c0 && edge[1]==c1 ) return false;
+      if( edge[0]==c1 && edge[1]==c0 ) return false;
+      }
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int checkAllPresent(int[][] edges)
+    {
+    for(int i=0; i<12; i++)
+      {
+      int[] map = EDGE_MAP[i];
+      int m1 = map[0];
+      int m2 = map[1];
+
+      if( notPresent(m1,m2,edges) )
+        {
+        mErrorColor1 = mFaceColors[m1];
+        mErrorColor2 = mFaceColors[m2];
+        return ERROR_EDGE_MISSING;
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean wrongTwist(int face0, int face1, int[][] edges)
+    {
+    int c0=mFaceColors[face0];
+    int c1=mFaceColors[face1];
+
+    for(int i=0; i<12; i++)
+      {
+      int[] edge = edges[i];
+      if( edge[1]==c0 && edge[0]==c1 ) return true;
+      }
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int checkTwist(int[][] edges)
+    {
+    for(int i=0; i<12; i++)
+      {
+      int[] map = EDGE_MAP[i];
+      int m1 = map[0];
+      int m2 = map[1];
+
+      if( wrongTwist(m1,m2,edges) )
+        {
+        mErrorColor1 = mFaceColors[m1];
+        mErrorColor2 = mFaceColors[m2];
+        return ERROR_EDGE_TWISTED;
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getPerm(int face0, int face1, int[][] edges)
+    {
+    int c0=mFaceColors[face0];
+    int c1=mFaceColors[face1];
+
+    for(int i=0; i<12; i++)
+      {
+      int[] edge = edges[i];
+      if( edge[0]==c0 && edge[1]==c1 ) return i;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computePermutation(int[][] edges, int[] perm)
+    {
+    for(int i=0; i<12; i++)
+      {
+      int[] map = EDGE_MAP[i];
+      int m1 = map[0];
+      int m2 = map[1];
+      perm[i] = getPerm(m1,m2,edges);
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] edges = new int[12][2];
+    getEdges(object,edges);
+
+    int result1 = figureOutFaceColors(edges);
+    if( result1<0 ) return result1;
+
+    int result2 = checkAllPresent(edges);
+    if( result2<0 ) return result2;
+
+    int result3 = checkTwist(edges);
+    if( result3<0 ) return result3;
+
+    int[] perm = new int[12];
+    int result4 = computePermutation(edges,perm);
+    if( result4<0 ) return result4;
+
+    if( !TablebaseHelpers.permutationIsEven(perm) ) return ERROR_TWO_EDGES;
+
+    return TBDino6.getIndexFromPerm(perm);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_yellow3;
+      case 1: return R.string.color_white3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_green3;
+      case 4: return R.string.color_red3;
+      case 5: return R.string.color_orange3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex6(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_yellow6;
+      case 1: return R.string.color_white6;
+      case 2: return R.string.color_blue6;
+      case 3: return R.string.color_green6;
+      case 4: return R.string.color_red6;
+      case 5: return R.string.color_orange6;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeMissingError(Resources res, int color0, int color1)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex6(color1);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeTwistedError(Resources res, int color0, int color1)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex6(color1);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_twisted_edge,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeMonoError(Resources res, int color)
+    {
+    int j0 = getColorIndex3(color);
+    int j1 = getColorIndex6(color);
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_edge_mono,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeTwiceError(Resources res, int color0, int color1)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex6(color1);
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_edge_twice,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_EDGE_MISSING      : return edgeMissingError(res,mErrorColor1,mErrorColor2);
+      case ERROR_EDGE_TWISTED      : return edgeTwistedError(res,mErrorColor1,mErrorColor2);
+      case ERROR_EDGE_MONOCHROMATIC: return edgeMonoError(res,mErrorColor1);
+      case ERROR_EDGE_TWICE        : return edgeTwiceError(res,mErrorColor1,mErrorColor2);
+      case ERROR_EDGE_CANNOT       : return res.getString(R.string.solver_generic_edges_cannot);
+      case ERROR_TWO_EDGES         : return res.getString(R.string.solver_generic_two_edges);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.DINO_3.name() );
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseIVY2.java b/src/main/java/org/distorted/solvers/SolverTablebaseIVY2.java
new file mode 100644
index 00000000..37ae6a09
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseIVY2.java
@@ -0,0 +1,254 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseIVY2 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 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 TablebaseHelpers.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;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseIVY2(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,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;
+
+    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 = TablebaseHelpers.computeEvenPermutationNum(center_colors);
+
+    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, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.IVY_2.name() );
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseJING2.java b/src/main/java/org/distorted/solvers/SolverTablebaseJING2.java
new file mode 100644
index 00000000..dcbfa660
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseJING2.java
@@ -0,0 +1,536 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TBPyraminx;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseJING2 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_GYB_MISSING = -1;
+  private static final int ERROR_CORNER_GYR_MISSING = -2;
+  private static final int ERROR_CORNER_GBR_MISSING = -3;
+  private static final int ERROR_CORNER_YBR_MISSING = -4;
+
+  private static final int ERROR_EDGE_RB_MISSING    = -5;
+  private static final int ERROR_EDGE_RY_MISSING    = -6;
+  private static final int ERROR_EDGE_RG_MISSING    = -7;
+  private static final int ERROR_EDGE_YB_MISSING    = -8;
+  private static final int ERROR_EDGE_GB_MISSING    = -9;
+  private static final int ERROR_EDGE_GY_MISSING    = -10;
+
+  private static final int ERROR_CENTER_G_MISSING   = -11;
+  private static final int ERROR_CENTER_Y_MISSING   = -12;
+  private static final int ERROR_CENTER_B_MISSING   = -13;
+  private static final int ERROR_CENTER_R_MISSING   = -14;
+
+  private static final int ERROR_CORNERS_CANNOT     = -15;
+  private static final int ERROR_EDGE_TWISTED       = -16;
+  private static final int ERROR_CORNER_TWISTED     = -17;
+  private static final int ERROR_TWO_EDGES          = -18;
+  private static final int ERROR_TWO_CENTERS        = -19;
+
+  private TablebasesAbstract mSolver;
+  private int[] mFaceColors;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseJING2(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean pieceEqual3(int[] piece, int c1, int c2, int c3)
+    {
+    return ( (piece[0]==c1 && piece[1]==c2 && piece[2]==c3) ||
+             (piece[0]==c1 && piece[2]==c2 && piece[1]==c3) ||
+             (piece[1]==c1 && piece[0]==c2 && piece[2]==c3) ||
+             (piece[1]==c1 && piece[2]==c2 && piece[0]==c3) ||
+             (piece[2]==c1 && piece[1]==c2 && piece[0]==c3) ||
+             (piece[2]==c1 && piece[0]==c2 && piece[1]==c3)  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean pieceEqual2(int[] piece, int[] colors)
+    {
+    return ( (piece[0]==colors[0] && piece[1]==colors[1]) ||
+             (piece[0]==colors[1] && piece[1]==colors[0])  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCornersPresent(int[][] corners)
+    {
+    boolean ybr = false;
+    boolean gbr = false;
+    boolean gyr = false;
+    boolean gyb = false;
+
+    for(int i=0; i<4; i++)
+      {
+      if( pieceEqual3(corners[i],0,1,2) ) gyb = true;
+      if( pieceEqual3(corners[i],0,1,3) ) gyr = true;
+      if( pieceEqual3(corners[i],0,2,3) ) gbr = true;
+      if( pieceEqual3(corners[i],1,2,3) ) ybr = true;
+      }
+
+    if( !ybr ) return ERROR_CORNER_YBR_MISSING;
+    if( !gbr ) return ERROR_CORNER_GBR_MISSING;
+    if( !gyr ) return ERROR_CORNER_GYR_MISSING;
+    if( !gyb ) return ERROR_CORNER_GYB_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllEdgesPresent(int[][] edges, int[][] edgeColors)
+    {
+    boolean[] present = new boolean[6];
+    for(int i=0; i<6; i++) present[i] = false;
+
+    for(int i=0; i<6; i++)
+      for(int j=0; j<6; j++)
+        if (pieceEqual2(edges[i], edgeColors[j]))
+          {
+          present[j] = true;
+          break;
+          }
+
+    if( !present[0] ) return ERROR_EDGE_RB_MISSING;
+    if( !present[1] ) return ERROR_EDGE_GB_MISSING;
+    if( !present[2] ) return ERROR_EDGE_RG_MISSING;
+    if( !present[3] ) return ERROR_EDGE_YB_MISSING;
+    if( !present[4] ) return ERROR_EDGE_RY_MISSING;
+    if( !present[5] ) return ERROR_EDGE_GY_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCentersPresent(int[] centers)
+    {
+    boolean[] present = new boolean[4];
+    for(int i=0; i<4; i++) present[i] = false;
+
+    for(int i=0; i<4; i++)
+      {
+      present[centers[i]]= true;
+      }
+
+    if( !present[0] ) return ERROR_CENTER_G_MISSING;
+    if( !present[1] ) return ERROR_CENTER_Y_MISSING;
+    if( !present[2] ) return ERROR_CENTER_B_MISSING;
+    if( !present[3] ) return ERROR_CENTER_R_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] computeFaceColors(int[][] corners)
+    {
+    int[] ret = new int[4];
+
+    for(int i=0; i<4; i++)
+      for(int j=0; j<4; j++)
+        if( corners[i][0]!=j && corners[i][1]!=j && corners[i][2]!=j ) ret[i]=j;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computePieceTwist(int index, int[] corner, int[] faceColor)
+    {
+    int twist1=0, twist2=0, twist3=0;
+
+    switch(index)
+      {
+      case 0: if( corner[1]==faceColor[1] ) twist1=1;
+              if( corner[2]==faceColor[1] ) twist1=2;
+              if( corner[0]==faceColor[2] ) twist2=2;
+              if( corner[2]==faceColor[2] ) twist2=1;
+              if( corner[0]==faceColor[3] ) twist3=1;
+              if( corner[1]==faceColor[3] ) twist3=2;
+              break;
+      case 1: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[2] ) twist2=1;
+              if( corner[1]==faceColor[2] ) twist2=2;
+              if( corner[0]==faceColor[3] ) twist3=2;
+              if( corner[2]==faceColor[3] ) twist3=1;
+              break;
+      case 2: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[1] ) twist2=2;
+              if( corner[2]==faceColor[1] ) twist2=1;
+              if( corner[0]==faceColor[3] ) twist3=1;
+              if( corner[1]==faceColor[3] ) twist3=2;
+              break;
+      case 3: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[1] ) twist2=1;
+              if( corner[1]==faceColor[1] ) twist2=2;
+              if( corner[0]==faceColor[2] ) twist3=2;
+              if( corner[2]==faceColor[2] ) twist3=1;
+              break;
+      }
+
+    return ( twist1!=twist2 || twist1!=twist3 ) ? ERROR_CORNERS_CANNOT : twist1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int locateEdge(int[][] edges, int[] colors)
+    {
+    for(int i=0; i<6; i++)
+      if( edges[i][0]==colors[0] && edges[i][1]==colors[1] ||
+          edges[i][0]==colors[1] && edges[i][1]==colors[0]  ) return i;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int edgeTwist(int[] edge, int[] colors)
+    {
+    return edge[0]==colors[0] ? 0:1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] computeEdgeQuats(int[][] edges, int[][] edgeColors)
+    {
+    int[] quats = new int[6];
+
+    for(int i=0; i<6; i++)
+      {
+      int pos   = locateEdge(edges,edgeColors[i]);
+      int twist = edgeTwist(edges[pos],edgeColors[i]);
+      quats[i]  = TBPyraminx.EDGE_QUATS[pos][twist];
+      }
+
+    return quats;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// GRB YBR YGB YRG
+
+  private void getCorners(TwistyObject object, int[][] corners)
+    {
+    for(int i=0; i<4; i++)
+      for(int j=0; j<3; j++)
+        corners[i][j] = object.getCubitFaceStickerIndex(i,j);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// RB GB RG YB RY GY
+
+  private void getEdges(TwistyObject object, int[][] edges)
+    {
+    for(int i=0; i<6; i++)
+      for(int j=0; j<2; j++)
+        edges[i][j] = object.getCubitFaceStickerIndex(i+4,j) -4;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// G Y B R
+
+  private void getCenters(TwistyObject object, int[] centers)
+    {
+    for(int i=0; i<4; i++)
+      centers[i] = object.getCubitFaceStickerIndex(i+10,0) - 8;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[][] computeEdgeColors(int[] faceColor)
+    {
+    // The first pair being (2,3) means 'the first edge's first face is on the tetrahedron
+    // face which opposes corner number 2, and its second face on tetra face which opposes
+    // corner number 3'
+    // Order of those pairs determines edge twist.
+
+    final int[][] edgeColorIndices = new int[][] { {2,3},{1,3},{2,1},{0,3},{2,0},{1,0}  };
+    int[][] ret = new int[6][2];
+
+    for(int i=0; i<6; i++)
+      {
+      ret[i][0] = faceColor[edgeColorIndices[i][0]];
+      ret[i][1] = faceColor[edgeColorIndices[i][1]];
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeCenterTwist(int[] centers, int color1, int color2)
+    {
+    if( centers[0]==color1 )
+      {
+      if( centers[1]==color2 ) return 1;
+      if( centers[2]==color2 ) return 2;
+      if( centers[3]==color2 ) return 0;
+
+      return -1;
+      }
+    if( centers[1]==color1 )
+      {
+      if( centers[0]==color2 ) return 1;
+      if( centers[2]==color2 ) return 0;
+      if( centers[3]==color2 ) return 2;
+
+      return -1;
+      }
+    if( centers[2]==color1 )
+      {
+      if( centers[0]==color2 ) return 2;
+      if( centers[1]==color2 ) return 0;
+      if( centers[3]==color2 ) return 1;
+
+      return -1;
+      }
+    if( centers[3]==color1 )
+      {
+      if( centers[0]==color2 ) return 0;
+      if( centers[1]==color2 ) return 2;
+      if( centers[2]==color2 ) return 1;
+
+      return -1;
+      }
+
+    return -2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getFaceOfCenter(int color, int[] centers)
+    {
+    if( centers[0]==color ) return 0;
+    if( centers[1]==color ) return 1;
+    if( centers[2]==color ) return 2;
+    if( centers[3]==color ) return 3;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners   = new int[4][3];
+    int[][] edges     = new int[6][2];
+    int[] centers     = new int[4];
+    int[] corner_twist= new int[4];
+
+    getCorners(object,corners);
+
+    int result1 = checkAllCornersPresent(corners);
+    if( result1<0 ) return result1;
+
+    mFaceColors = computeFaceColors(corners);
+    int[][] edgeColors = computeEdgeColors(mFaceColors);
+
+    getEdges(object,edges);
+    int result2 = checkAllEdgesPresent(edges,edgeColors);
+    if( result2<0 ) return result2;
+
+    getCenters(object,centers);
+    int result3 = checkAllCentersPresent(centers);
+    if( result3<0 ) return result3;
+
+    for(int i=0; i<4; i++)
+      {
+      corner_twist[i] = computePieceTwist(i,corners[i],mFaceColors);
+      if( corner_twist[i]<0 ) return ERROR_CORNERS_CANNOT;
+      }
+
+    int[] quats = computeEdgeQuats(edges,edgeColors);
+    int[] permutation = new int[6];
+    TBPyraminx.getEdgePermutation(permutation,quats,0);
+    boolean even = TablebaseHelpers.permutationIsEven(permutation);
+    if( !even ) return ERROR_TWO_EDGES;
+    int[] edge_twist = new int[6];
+    TBPyraminx.getEdgeTwist(edge_twist,quats,0);
+
+    int twist_gr = computeCenterTwist(centers,mFaceColors[1],mFaceColors[2]);
+    int twist_ry = computeCenterTwist(centers,mFaceColors[2],mFaceColors[0]);
+
+    if( (twist_ry-twist_gr+1)%3 != 0 ) return ERROR_TWO_CENTERS;
+
+    int total_twist=0;
+    for(int i=0; i<4; i++) total_twist += corner_twist[i];
+
+    if( (total_twist-twist_gr)%3 !=0 ) return ERROR_CORNER_TWISTED;
+
+    int green_face = getFaceOfCenter(mFaceColors[1],centers);
+
+    int totalEdgeTwist=0;
+    for(int i=0; i<6; i++) totalEdgeTwist += edge_twist[i];
+    if( (totalEdgeTwist%2)!=0 ) return ERROR_EDGE_TWISTED;
+
+    int vertexTwist = corner_twist[0]+ 3*(corner_twist[1]+ 3*(corner_twist[2]+ 3*corner_twist[3]));
+    int edgeTwist = edge_twist[0]+ 2*(edge_twist[1]+ 2*(edge_twist[2]+ 2*(edge_twist[3]+ 2*edge_twist[4])));
+    int perm_num = TablebaseHelpers.computeEvenPermutationNum(permutation);
+
+    return green_face + 4*(vertexTwist + 81*(edgeTwist + 32*perm_num));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_green3;
+      case 1: return R.string.color_yellow3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_red3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_green4;
+      case 1: return R.string.color_yellow4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_red4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getFaceIndex3(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_green3;
+      case 1: return R.string.color_yellow3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_red3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getFaceIndex6(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_green6;
+      case 1: return R.string.color_yellow6;
+      case 2: return R.string.color_blue6;
+      case 3: return R.string.color_red6;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerError(Resources res, int color0, int color1, int color2)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex3(color1);
+    int j2 = getColorIndex4(color2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeError(Resources res, int face0, int face1)
+    {
+    int j0 = getFaceIndex3(face0);
+    int j1 = getFaceIndex6(face1);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_YBR_MISSING: return cornerError(res,3,2,1);
+      case ERROR_CORNER_GBR_MISSING: return cornerError(res,3,2,0);
+      case ERROR_CORNER_GYR_MISSING: return cornerError(res,3,1,0);
+      case ERROR_CORNER_GYB_MISSING: return cornerError(res,2,1,0);
+      case ERROR_EDGE_RB_MISSING   : return edgeError(res,3,2);
+      case ERROR_EDGE_RY_MISSING   : return edgeError(res,2,0);
+      case ERROR_EDGE_RG_MISSING   : return edgeError(res,2,1);
+      case ERROR_EDGE_YB_MISSING   : return edgeError(res,3,0);
+      case ERROR_EDGE_GB_MISSING   : return edgeError(res,3,1);
+      case ERROR_EDGE_GY_MISSING   : return edgeError(res,1,0);
+      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_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
+      case ERROR_EDGE_TWISTED      : return res.getString(R.string.solver_generic_edge_twist);
+      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
+      case ERROR_TWO_EDGES         : return res.getString(R.string.solver_generic_two_edges);
+      case ERROR_TWO_CENTERS       : return res.getString(R.string.solver_generic_two_centers);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.JING_2.name());
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebasePDIA3.java b/src/main/java/org/distorted/solvers/SolverTablebasePDIA3.java
new file mode 100644
index 00000000..ab188981
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebasePDIA3.java
@@ -0,0 +1,375 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebasePDIA3 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_FR_MISSING = -1;
+  private static final int ERROR_CORNER_BR_MISSING = -2;
+  private static final int ERROR_CORNER_BL_MISSING = -3;
+  private static final int ERROR_CORNER_FL_MISSING = -4;
+  private static final int ERROR_CORNER_TO_MISSING = -5;
+  private static final int ERROR_CORNER_BO_MISSING = -6;
+
+  private static final int ERROR_CENTER_0_MISSING = -7;
+  private static final int ERROR_CENTER_1_MISSING = -8;
+  private static final int ERROR_CENTER_2_MISSING = -9;
+  private static final int ERROR_CENTER_3_MISSING = -10;
+  private static final int ERROR_CENTER_4_MISSING = -11;
+  private static final int ERROR_CENTER_5_MISSING = -12;
+  private static final int ERROR_CENTER_6_MISSING = -13;
+  private static final int ERROR_CENTER_7_MISSING = -14;
+
+  private static final int ERROR_TWO_CENTERS      = -15;
+  private static final int ERROR_CORNERS_CANNOT   = -16;
+
+  private TablebasesAbstract mSolver;
+  private final int[] mFaceColors;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebasePDIA3(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    mFaceColors = new int[8];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retCenter(int color, int[] centers)
+    {
+    for(int i=0; i<8; i++ )
+      if( centers[i]==color ) return i;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] getCentersPermutation(int[] centers)
+    {
+    int[] perm = new int[8];
+
+    for(int i=0; i<8; i++ )
+      perm[i] = retCenter(mFaceColors[i],centers);
+
+    return perm;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean isTwistEven(int[] twist)
+    {
+    int total = twist[0]+twist[1]+twist[2]+twist[3]+twist[4]+twist[5];
+    return ((total%2)==0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int indexOf(int[] corner, int color )
+    {
+    if( corner[0]==color ) return 0;
+    if( corner[1]==color ) return 1;
+    if( corner[2]==color ) return 2;
+    if( corner[3]==color ) return 3;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] getCornersTwist(int[][] corners)
+    {
+    int[] twist = new int[6];
+
+    twist[0] = indexOf(corners[0],mFaceColors[0]);
+    twist[1] = indexOf(corners[1],mFaceColors[5]);
+    twist[2] = indexOf(corners[2],mFaceColors[2]);
+    twist[3] = indexOf(corners[3],mFaceColors[4]);
+    twist[4] = indexOf(corners[4],mFaceColors[2]);
+    twist[5] = indexOf(corners[5],mFaceColors[6]);
+
+
+    return twist;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllColorsDifferent()
+    {
+    for(int i=0; i<8; i++)
+      {
+      boolean present = false;
+
+      for(int j=0; j<8; j++)
+        if( mFaceColors[j]==i )
+          {
+          present=true;
+          break;
+          }
+
+      if( !present ) return ERROR_CORNERS_CANNOT;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCentersPresent(int[] centers)
+    {
+    for(int i=0; i<8; i++)
+      {
+      boolean present = false;
+
+      for(int j=0; j<8; j++)
+        if( centers[j]==i )
+          {
+          present=true;
+          break;
+          }
+
+      if( !present )
+        {
+        switch(i)
+          {
+          case 0: return ERROR_CENTER_0_MISSING;
+          case 1: return ERROR_CENTER_1_MISSING;
+          case 2: return ERROR_CENTER_2_MISSING;
+          case 3: return ERROR_CENTER_3_MISSING;
+          case 4: return ERROR_CENTER_4_MISSING;
+          case 5: return ERROR_CENTER_5_MISSING;
+          case 6: return ERROR_CENTER_6_MISSING;
+          case 7: return ERROR_CENTER_7_MISSING;
+          }
+        }
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int commonColor(int[][] corners, int index1, int index2, int index3)
+    {
+    int[] c1 = corners[index1];
+    int[] c2 = corners[index2];
+    int[] c3 = corners[index3];
+
+    for(int i=0; i<4; i++)
+      {
+      int c = c1[i];
+
+      if( (c2[0]==c || c2[1]==c || c2[2]==c || c2[3]==c) &&
+          (c3[0]==c || c3[1]==c || c3[2]==c || c3[3]==c)  ) return c;
+      }
+
+    return ERROR_CORNERS_CANNOT;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int figureOutFaceColors(int[] output, int[][] corners)
+    {
+    int[][] commonCorners = {{0,2,4},{0,3,5},{1,2,4},{1,3,5},{0,2,3},{1,2,3},{0,4,5},{1,4,5}};
+
+    for(int i=0; i<8; i++)
+      {
+      int[] common = commonCorners[i];
+      output[i] = commonColor(corners,common[0],common[1],common[2]);
+      if( output[i]<0 ) return output[i];
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCorners(TwistyObject object, int[][] corners)
+    {
+    for(int i=0; i<6; i++)
+      for(int j=0; j<4; j++)
+         corners[i][j] = object.getCubitFaceStickerIndex(i,j);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCenters(TwistyObject object, int[] centers)
+    {
+    for(int i=0; i<8; i++)
+       centers[i] = object.getCubitFaceStickerIndex(6+i,0)-8;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners = new int[6][4];
+    int[] centers = new int[8];
+
+    getCorners(object,corners);
+    getCenters(object,centers);
+
+    int result1 = figureOutFaceColors(mFaceColors,corners);
+    if( result1<0 ) return result1;
+
+    int result2 = checkAllCentersPresent(centers);
+    if( result2<0 ) return result2;
+
+    int result3 = checkAllColorsDifferent();
+    if( result3<0 ) return result3;
+
+    int[] twist = getCornersTwist(corners);
+    boolean even1 = isTwistEven(twist);
+
+    int[] centers_perm = getCentersPermutation(centers);
+    boolean even2 = TablebaseHelpers.permutationIsEven(centers_perm);
+    if( even1^even2 ) return ERROR_TWO_CENTERS;
+
+    int centers_perm_num = TablebaseHelpers.computePermutationNum(centers_perm);
+    int total_twist = twist[0]+ 4*(twist[1]+ 4*(twist[2]+ 4*(twist[3]+ 4*(twist[4]+ 4*(twist[5]>1 ? 1:0)))));
+
+/*
+android.util.Log.e("D", "faces: "+mFaceColors[0]+" "+mFaceColors[1]+" "+mFaceColors[2]+" "
++mFaceColors[3]+" "+mFaceColors[4]+" "+mFaceColors[5]+" "+mFaceColors[6]+" "+mFaceColors[7]);
+android.util.Log.e("D", "corn twist: "+twist[0]+" "+twist[1]+" "+twist[2]+" "+twist[3]+" "+twist[4]+" "+twist[5]);
+android.util.Log.e("D", "ret="+(total_twist + 2048*centers_perm_num) );
+*/
+    return total_twist + 2048*centers_perm_num;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_violet4;
+      case 1: return R.string.color_grey4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_red4;
+      case 4: return R.string.color_orange4;
+      case 5: return R.string.color_green4;
+      case 6: return R.string.color_white4;
+      case 7: return R.string.color_yellow4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_violet3;
+      case 1: return R.string.color_grey3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_red3;
+      case 4: return R.string.color_orange3;
+      case 5: return R.string.color_green3;
+      case 6: return R.string.color_white3;
+      case 7: return R.string.color_yellow3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex2(int face)
+    {
+    switch(face)
+      {
+      case 0: return R.string.color_violet2;
+      case 1: return R.string.color_grey2;
+      case 2: return R.string.color_blue2;
+      case 3: return R.string.color_red2;
+      case 4: return R.string.color_orange2;
+      case 5: return R.string.color_green2;
+      case 6: return R.string.color_white2;
+      case 7: return R.string.color_yellow2;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String centerError(Resources res, int face)
+    {
+    String color = res.getString(getColorIndex2(face));
+    return res.getString(R.string.solver_generic_missing_center,color);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerError(Resources res, int f1, int f2)
+    {
+    String c1 = res.getString(getColorIndex3(f1));
+    String c2 = res.getString(getColorIndex4(f2));
+    return res.getString(R.string.solver_generic_missing_corner2,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_FR_MISSING: return cornerError(res,1,4);
+      case ERROR_CORNER_BR_MISSING: return cornerError(res,1,6);
+      case ERROR_CORNER_BL_MISSING: return cornerError(res,3,6);
+      case ERROR_CORNER_FL_MISSING: return cornerError(res,3,4);
+      case ERROR_CORNER_TO_MISSING: return cornerError(res,1,3);
+      case ERROR_CORNER_BO_MISSING: return cornerError(res,4,6);
+
+      case ERROR_CENTER_0_MISSING : return centerError(res,0);
+      case ERROR_CENTER_1_MISSING : return centerError(res,1);
+      case ERROR_CENTER_2_MISSING : return centerError(res,2);
+      case ERROR_CENTER_3_MISSING : return centerError(res,3);
+      case ERROR_CENTER_4_MISSING : return centerError(res,4);
+      case ERROR_CENTER_5_MISSING : return centerError(res,5);
+      case ERROR_CENTER_6_MISSING : return centerError(res,6);
+      case ERROR_CENTER_7_MISSING : return centerError(res,7);
+
+      case ERROR_TWO_CENTERS      : 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, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.PDIA_3.name() );
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebasePDUO2.java b/src/main/java/org/distorted/solvers/SolverTablebasePDUO2.java
new file mode 100644
index 00000000..2adad7d7
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebasePDUO2.java
@@ -0,0 +1,299 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebasePDUO2 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_GYB_MISSING = -1;
+  private static final int ERROR_CORNER_GYR_MISSING = -2;
+  private static final int ERROR_CORNER_GBR_MISSING = -3;
+  private static final int ERROR_CORNER_YBR_MISSING = -4;
+
+  private static final int ERROR_CENTER_G_MISSING = -5;
+  private static final int ERROR_CENTER_Y_MISSING = -6;
+  private static final int ERROR_CENTER_B_MISSING = -7;
+  private static final int ERROR_CENTER_R_MISSING = -8;
+
+  private static final int ERROR_TWO_CENTERS      = -9;
+  private static final int ERROR_CORNER_TWISTED   = -10;
+
+  private static final int ERROR_CORNERS_CANNOT   = -11;
+
+  TablebasesAbstract mSolver;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cornerEqual(int[] corner, int c1, int c2, int c3)
+    {
+    return ( (corner[0]==c1 && corner[1]==c2 && corner[2]==c3) ||
+             (corner[0]==c1 && corner[2]==c2 && corner[1]==c3) ||
+             (corner[1]==c1 && corner[0]==c2 && corner[2]==c3) ||
+             (corner[1]==c1 && corner[2]==c2 && corner[0]==c3) ||
+             (corner[2]==c1 && corner[1]==c2 && corner[0]==c3) ||
+             (corner[2]==c1 && corner[0]==c2 && corner[1]==c3)  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCornersPresent(int[][] corners)
+    {
+    boolean ybr = false;
+    boolean gbr = false;
+    boolean gyr = false;
+    boolean gyb = false;
+
+    for(int i=0; i<4; i++)
+      {
+      if( cornerEqual(corners[i],0,1,2) ) gyb = true;
+      if( cornerEqual(corners[i],0,1,3) ) gyr = true;
+      if( cornerEqual(corners[i],0,2,3) ) gbr = true;
+      if( cornerEqual(corners[i],1,2,3) ) ybr = true;
+      }
+
+    if( !ybr ) return ERROR_CORNER_YBR_MISSING;
+    if( !gbr ) return ERROR_CORNER_GBR_MISSING;
+    if( !gyr ) return ERROR_CORNER_GYR_MISSING;
+    if( !gyb ) return ERROR_CORNER_GYB_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCentersPresent(int[] centers)
+    {
+    if( centers[0]!=0 && centers[1]!=0 && centers[2]!=0 && centers[3]!=0 ) return ERROR_CENTER_G_MISSING;
+    if( centers[0]!=1 && centers[1]!=1 && centers[2]!=1 && centers[3]!=1 ) return ERROR_CENTER_Y_MISSING;
+    if( centers[0]!=2 && centers[1]!=2 && centers[2]!=2 && centers[3]!=2 ) return ERROR_CENTER_B_MISSING;
+    if( centers[0]!=3 && centers[1]!=3 && centers[2]!=3 && centers[3]!=3 ) return ERROR_CENTER_R_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getFaceOfCenter(int color, int[] centers)
+    {
+    if( centers[0]==color ) return 0;
+    if( centers[1]==color ) return 1;
+    if( centers[2]==color ) return 2;
+    if( centers[3]==color ) return 3;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] computeFaceColor(int[][] corners)
+    {
+    int[] ret = new int[4];
+
+    for(int i=0; i<4; i++)
+      for(int j=0; j<4; j++)
+        if( corners[i][0]!=j && corners[i][1]!=j && corners[i][2]!=j ) ret[i]=j;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeCornerTwist(int index, int[] corner, int[] missing)
+    {
+    int twist1=0, twist2=0, twist3=0;
+
+    switch(index)
+      {
+      case 0: if( corner[1]==missing[1] ) twist1=1;
+              if( corner[2]==missing[1] ) twist1=2;
+              if( corner[0]==missing[2] ) twist2=2;
+              if( corner[2]==missing[2] ) twist2=1;
+              if( corner[0]==missing[3] ) twist3=1;
+              if( corner[1]==missing[3] ) twist3=2;
+              break;
+      case 1: if( corner[1]==missing[0] ) twist1=1;
+              if( corner[2]==missing[0] ) twist1=2;
+              if( corner[0]==missing[2] ) twist2=1;
+              if( corner[1]==missing[2] ) twist2=2;
+              if( corner[0]==missing[3] ) twist3=2;
+              if( corner[2]==missing[3] ) twist3=1;
+              break;
+      case 2: if( corner[1]==missing[0] ) twist1=1;
+              if( corner[2]==missing[0] ) twist1=2;
+              if( corner[0]==missing[1] ) twist2=2;
+              if( corner[2]==missing[1] ) twist2=1;
+              if( corner[0]==missing[3] ) twist3=1;
+              if( corner[1]==missing[3] ) twist3=2;
+              break;
+      case 3: if( corner[1]==missing[0] ) twist1=1;
+              if( corner[2]==missing[0] ) twist1=2;
+              if( corner[0]==missing[1] ) twist2=1;
+              if( corner[1]==missing[1] ) twist2=2;
+              if( corner[0]==missing[2] ) twist3=2;
+              if( corner[2]==missing[2] ) twist3=1;
+              break;
+      }
+
+    return ( twist1!=twist2 || twist1!=twist3 ) ? ERROR_CORNERS_CANNOT : twist1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeCenterTwist(int[] centers, int color1, int color2)
+    {
+    if( centers[0]==color1 )
+      {
+      if( centers[1]==color2 ) return 1;
+      if( centers[2]==color2 ) return 2;
+      if( centers[3]==color2 ) return 0;
+
+      return -1;
+      }
+    if( centers[1]==color1 )
+      {
+      if( centers[0]==color2 ) return 1;
+      if( centers[2]==color2 ) return 0;
+      if( centers[3]==color2 ) return 2;
+
+      return -1;
+      }
+    if( centers[2]==color1 )
+      {
+      if( centers[0]==color2 ) return 2;
+      if( centers[1]==color2 ) return 0;
+      if( centers[3]==color2 ) return 1;
+
+      return -1;
+      }
+    if( centers[3]==color1 )
+      {
+      if( centers[0]==color2 ) return 0;
+      if( centers[1]==color2 ) return 2;
+      if( centers[2]==color2 ) return 1;
+
+      return -1;
+      }
+
+    return -2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebasePDUO2(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners = new int[4][3];
+    int[] centers = new int[4];
+    int[] corner_twist = new int[4];
+
+    for(int i=0; i<4; i++)
+      {
+      centers[i] = object.getCubitFaceStickerIndex(i+4,0) - 4;
+
+      for(int j=0; j<3; j++)
+        corners[i][j] = object.getCubitFaceStickerIndex(i,j);
+      }
+
+    int result1 = checkAllCornersPresent(corners);
+    if( result1<0 ) return result1;
+
+    int result2 = checkAllCentersPresent(centers);
+    if( result2<0 ) return result2;
+
+    int[] faces = computeFaceColor(corners);
+
+    int twist_gr = computeCenterTwist(centers,faces[1],faces[2]);
+    int twist_ry = computeCenterTwist(centers,faces[2],faces[0]);
+
+    if( (twist_ry-twist_gr+1)%3 != 0 ) return ERROR_TWO_CENTERS;
+
+    int total_twist=0;
+
+    for(int i=0; i<4; i++)
+      {
+      corner_twist[i] = computeCornerTwist(i,corners[i],faces);
+      if( corner_twist[i]<0 ) return corner_twist[i];
+      total_twist += corner_twist[i];
+      }
+
+    if( (total_twist-twist_gr)%3 !=0 ) return ERROR_CORNER_TWISTED;
+
+    int green_face = getFaceOfCenter(faces[1],centers);
+
+    return green_face + 4*(corner_twist[3] + 3*(corner_twist[2] + 3*(corner_twist[1] + 3*corner_twist[0])));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_YBR_MISSING: String c01 = res.getString(R.string.color_yellow3);
+                                     String c02 = res.getString(R.string.color_blue3);
+                                     String c03 = res.getString(R.string.color_red4);
+                                     return res.getString(R.string.solver_generic_missing_corner,c01,c02,c03);
+      case ERROR_CORNER_GBR_MISSING: String c11 = res.getString(R.string.color_green3);
+                                     String c12 = res.getString(R.string.color_blue3);
+                                     String c13 = res.getString(R.string.color_red4);
+                                     return res.getString(R.string.solver_generic_missing_corner,c11,c12,c13);
+      case ERROR_CORNER_GYR_MISSING: String c21 = res.getString(R.string.color_green3);
+                                     String c22 = res.getString(R.string.color_yellow3);
+                                     String c23 = res.getString(R.string.color_red4);
+                                     return res.getString(R.string.solver_generic_missing_corner,c21,c22,c23);
+      case ERROR_CORNER_GYB_MISSING: String c31 = res.getString(R.string.color_green3);
+                                     String c32 = res.getString(R.string.color_yellow3);
+                                     String c33 = res.getString(R.string.color_blue4);
+                                     return res.getString(R.string.solver_generic_missing_corner,c31,c32,c33);
+      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       : return res.getString(R.string.solver_generic_two_centers);
+      case ERROR_CORNER_TWISTED    : return res.getString(R.string.solver_generic_corner_twist);
+      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.PDUO_2.name() );
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebasePYRA3.java b/src/main/java/org/distorted/solvers/SolverTablebasePYRA3.java
new file mode 100644
index 00000000..0a89cef2
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebasePYRA3.java
@@ -0,0 +1,549 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+import org.distorted.objectlib.tablebases.TBPyraminx;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebasePYRA3 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_GYB_MISSING = -1;
+  private static final int ERROR_CORNER_GYR_MISSING = -2;
+  private static final int ERROR_CORNER_GBR_MISSING = -3;
+  private static final int ERROR_CORNER_YBR_MISSING = -4;
+
+  private static final int ERROR_VERTEX_GYB_MISSING = -5;
+  private static final int ERROR_VERTEX_GYR_MISSING = -6;
+  private static final int ERROR_VERTEX_GBR_MISSING = -7;
+  private static final int ERROR_VERTEX_YBR_MISSING = -8;
+
+  private static final int ERROR_EDGE_RB_MISSING = -9;
+  private static final int ERROR_EDGE_RY_MISSING = -10;
+  private static final int ERROR_EDGE_RG_MISSING = -11;
+  private static final int ERROR_EDGE_YB_MISSING = -12;
+  private static final int ERROR_EDGE_GB_MISSING = -13;
+  private static final int ERROR_EDGE_GY_MISSING = -14;
+
+  private static final int ERROR_CORNERS_CANNOT   = -15;
+  private static final int ERROR_VERTICES_CANNOT  = -16;
+  private static final int ERROR_EDGE_TWISTED     = -17;
+  private static final int ERROR_C_V_DONT_MATCH   = -18;
+  private static final int ERROR_TWO_EDGES        = -19;
+
+  private TablebasesAbstract mSolver;
+  private int[] mCornerTwist;
+  private int[] mFaceColors;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean pieceEqual3(int[] piece, int c1, int c2, int c3)
+    {
+    return ( (piece[0]==c1 && piece[1]==c2 && piece[2]==c3) ||
+             (piece[0]==c1 && piece[2]==c2 && piece[1]==c3) ||
+             (piece[1]==c1 && piece[0]==c2 && piece[2]==c3) ||
+             (piece[1]==c1 && piece[2]==c2 && piece[0]==c3) ||
+             (piece[2]==c1 && piece[1]==c2 && piece[0]==c3) ||
+             (piece[2]==c1 && piece[0]==c2 && piece[1]==c3)  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean pieceEqual2(int[] piece, int[] colors)
+    {
+    return ( (piece[0]==colors[0] && piece[1]==colors[1]) ||
+             (piece[0]==colors[1] && piece[1]==colors[0])  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCornersPresent(int[][] corners)
+    {
+    boolean ybr = false;
+    boolean gbr = false;
+    boolean gyr = false;
+    boolean gyb = false;
+
+    for(int i=0; i<4; i++)
+      {
+      if( pieceEqual3(corners[i],0,1,2) ) gyb = true;
+      if( pieceEqual3(corners[i],0,1,3) ) gyr = true;
+      if( pieceEqual3(corners[i],0,2,3) ) gbr = true;
+      if( pieceEqual3(corners[i],1,2,3) ) ybr = true;
+      }
+
+    if( !ybr ) return ERROR_CORNER_YBR_MISSING;
+    if( !gbr ) return ERROR_CORNER_GBR_MISSING;
+    if( !gyr ) return ERROR_CORNER_GYR_MISSING;
+    if( !gyb ) return ERROR_CORNER_GYB_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllVerticesPresent(int[][] vertex)
+    {
+    boolean ybr = false;
+    boolean gbr = false;
+    boolean gyr = false;
+    boolean gyb = false;
+
+    for(int i=0; i<4; i++)
+      {
+      if( pieceEqual3(vertex[i],0,1,2) ) gyb = true;
+      if( pieceEqual3(vertex[i],0,1,3) ) gyr = true;
+      if( pieceEqual3(vertex[i],0,2,3) ) gbr = true;
+      if( pieceEqual3(vertex[i],1,2,3) ) ybr = true;
+      }
+
+    if( !ybr ) return ERROR_VERTEX_YBR_MISSING;
+    if( !gbr ) return ERROR_VERTEX_GBR_MISSING;
+    if( !gyr ) return ERROR_VERTEX_GYR_MISSING;
+    if( !gyb ) return ERROR_VERTEX_GYB_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllEdgesPresent(int[][] edges, int[][] edgeColors)
+    {
+    boolean[] present = new boolean[6];
+    for(int i=0; i<6; i++) present[i] = false;
+
+    for(int i=0; i<6; i++)
+      for(int j=0; j<6; j++)
+        if (pieceEqual2(edges[i], edgeColors[j]))
+          {
+          present[j] = true;
+          break;
+          }
+
+    if( !present[0] ) return ERROR_EDGE_RB_MISSING;
+    if( !present[1] ) return ERROR_EDGE_RY_MISSING;
+    if( !present[2] ) return ERROR_EDGE_RG_MISSING;
+    if( !present[3] ) return ERROR_EDGE_YB_MISSING;
+    if( !present[4] ) return ERROR_EDGE_GB_MISSING;
+    if( !present[5] ) return ERROR_EDGE_GY_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] computeFaceColors(int[][] corners)
+    {
+    int[] ret = new int[4];
+
+    for(int i=0; i<4; i++)
+      for(int j=0; j<4; j++)
+        if( corners[i][0]!=j && corners[i][1]!=j && corners[i][2]!=j ) ret[i]=j;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAgreement(int[] faces1, int[] faces2)
+    {
+    for(int i=0; i<4; i++)
+      if( faces1[i]!=faces2[i] ) return ERROR_C_V_DONT_MATCH;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computePieceTwist(int index, int[] corner, int[] faceColor)
+    {
+    int twist1=0, twist2=0, twist3=0;
+
+    switch(index)
+      {
+      case 0: if( corner[1]==faceColor[1] ) twist1=1;
+              if( corner[2]==faceColor[1] ) twist1=2;
+              if( corner[0]==faceColor[2] ) twist2=2;
+              if( corner[2]==faceColor[2] ) twist2=1;
+              if( corner[0]==faceColor[3] ) twist3=1;
+              if( corner[1]==faceColor[3] ) twist3=2;
+              break;
+      case 1: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[2] ) twist2=1;
+              if( corner[1]==faceColor[2] ) twist2=2;
+              if( corner[0]==faceColor[3] ) twist3=2;
+              if( corner[2]==faceColor[3] ) twist3=1;
+              break;
+      case 2: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[1] ) twist2=2;
+              if( corner[2]==faceColor[1] ) twist2=1;
+              if( corner[0]==faceColor[3] ) twist3=1;
+              if( corner[1]==faceColor[3] ) twist3=2;
+              break;
+      case 3: if( corner[1]==faceColor[0] ) twist1=1;
+              if( corner[2]==faceColor[0] ) twist1=2;
+              if( corner[0]==faceColor[1] ) twist2=1;
+              if( corner[1]==faceColor[1] ) twist2=2;
+              if( corner[0]==faceColor[2] ) twist3=2;
+              if( corner[2]==faceColor[2] ) twist3=1;
+              break;
+      }
+
+    return ( twist1!=twist2 || twist1!=twist3 ) ? ERROR_CORNERS_CANNOT : twist1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int locateEdge(int[][] edges, int[] colors)
+    {
+    for(int i=0; i<6; i++)
+      if( edges[i][0]==colors[0] && edges[i][1]==colors[1] ||
+          edges[i][0]==colors[1] && edges[i][1]==colors[0]  ) return i;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int edgeTwist(int[] edge, int[] colors)
+    {
+    return edge[0]==colors[0] ? 0:1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] computeEdgeQuats(int[][] edges, int[][] edgeColors)
+    {
+    int[] quats = new int[6];
+
+    for(int i=0; i<6; i++)
+      {
+      int pos   = locateEdge(edges,edgeColors[i]);
+      int twist = edgeTwist(edges[pos],edgeColors[i]);
+      quats[i]  = TBPyraminx.EDGE_QUATS[pos][twist];
+      }
+
+    return quats;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebasePYRA3(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCorners(TwistyObject object, int[][] corners)
+    {
+    corners[0][0] = object.getCubitFaceStickerIndex(4,0);  // G
+    corners[0][1] = object.getCubitFaceStickerIndex(4,3);  // R
+    corners[0][2] = object.getCubitFaceStickerIndex(4,2);  // B
+
+    corners[1][0] = object.getCubitFaceStickerIndex(6,1);  // Y
+    corners[1][1] = object.getCubitFaceStickerIndex(6,2);  // B
+    corners[1][2] = object.getCubitFaceStickerIndex(6,3);  // R
+
+    corners[2][0] = object.getCubitFaceStickerIndex(11,1);  // Y
+    corners[2][1] = object.getCubitFaceStickerIndex(11,0);  // G
+    corners[2][2] = object.getCubitFaceStickerIndex(11,2);  // B
+
+    corners[3][0] = object.getCubitFaceStickerIndex(13,1);  // Y
+    corners[3][1] = object.getCubitFaceStickerIndex(13,3);  // R
+    corners[3][2] = object.getCubitFaceStickerIndex(13,0);  // G
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getVertices(TwistyObject object, int[][] vertex)
+    {
+    vertex[0][0] = object.getCubitFaceStickerIndex(0,0);  // G
+    vertex[0][1] = object.getCubitFaceStickerIndex(0,5);  // R
+    vertex[0][2] = object.getCubitFaceStickerIndex(0,7);  // B
+
+    vertex[1][0] = object.getCubitFaceStickerIndex(1,2);  // Y
+    vertex[1][1] = object.getCubitFaceStickerIndex(1,7);  // B
+    vertex[1][2] = object.getCubitFaceStickerIndex(1,5);  // R
+
+    vertex[2][0] = object.getCubitFaceStickerIndex(2,2);  // Y
+    vertex[2][1] = object.getCubitFaceStickerIndex(2,0);  // G
+    vertex[2][2] = object.getCubitFaceStickerIndex(2,7);  // B
+
+    vertex[3][0] = object.getCubitFaceStickerIndex(3,2);  // Y
+    vertex[3][1] = object.getCubitFaceStickerIndex(3,5);  // R
+    vertex[3][2] = object.getCubitFaceStickerIndex(3,0);  // G
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getEdges(TwistyObject object, int[][] edges)
+    {
+    edges[0][0] = object.getCubitFaceStickerIndex(5,3);  // R
+    edges[0][1] = object.getCubitFaceStickerIndex(5,2);  // B
+
+    edges[1][0] = object.getCubitFaceStickerIndex(10,1); // Y
+    edges[1][1] = object.getCubitFaceStickerIndex(10,3); // R
+
+    edges[2][0] = object.getCubitFaceStickerIndex(9,0);  // G
+    edges[2][1] = object.getCubitFaceStickerIndex(9,3);  // R
+
+    edges[3][0] = object.getCubitFaceStickerIndex(8,2);  // B
+    edges[3][1] = object.getCubitFaceStickerIndex(8,1);  // Y
+
+    edges[4][0] = object.getCubitFaceStickerIndex(7,2);  // B
+    edges[4][1] = object.getCubitFaceStickerIndex(7,0);  // G
+
+    edges[5][0] = object.getCubitFaceStickerIndex(12,1); // Y
+    edges[5][1] = object.getCubitFaceStickerIndex(12,0); // G
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[][] computeEdgeColors(int[] faceColor)
+    {
+    // The first pair being (2,3) means 'the first edge's first face is on the tetrahedron
+    // face which oppeses corner number 2, and its second face on tetra face which opposes
+    // corner number 3'
+    // Order of those pairs determines edge twist.
+
+    final int[][] edgeColorIndices = new int[][] { {2,3},{0,2},{1,2},{3,0},{3,1},{0,1}  };
+    int[][] ret = new int[6][2];
+
+    for(int i=0; i<6; i++)
+      {
+      ret[i][0] = faceColor[edgeColorIndices[i][0]];
+      ret[i][1] = faceColor[edgeColorIndices[i][1]];
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners = new int[4][3];
+    int[][] edges   = new int[6][2];
+    int[][] vertices= new int[4][3];
+    int[] vertex_twist = new int[4];
+    mCornerTwist = new int[4];
+
+    getCorners(object,corners);
+    getVertices(object,vertices);
+
+    int result1 = checkAllCornersPresent(corners);
+    if( result1<0 ) return result1;
+
+    int result2 = checkAllVerticesPresent(vertices);
+    if( result2<0 ) return result2;
+
+    int[] facesC = computeFaceColors(corners);
+    int[] facesV = computeFaceColors(vertices);
+
+    int result3 = checkAgreement(facesC,facesV);
+    if( result3<0 ) return result3;
+
+    mFaceColors = facesC;
+
+    int[][] edgeColors = computeEdgeColors(mFaceColors);
+
+    getEdges(object,edges);
+    int result4 = checkAllEdgesPresent(edges,edgeColors);
+    if( result4<0 ) return result4;
+
+    for(int i=0; i<4; i++)
+      {
+      mCornerTwist[i] = computePieceTwist(i,corners[i],mFaceColors);
+      if( mCornerTwist[i]<0 ) return ERROR_CORNERS_CANNOT;
+      }
+
+    for(int i=0; i<4; i++)
+      {
+      vertex_twist[i] = computePieceTwist(i,vertices[i],mFaceColors);
+      if( vertex_twist[i]<0 ) return ERROR_VERTICES_CANNOT;
+      }
+
+    int[] quats = computeEdgeQuats(edges,edgeColors);
+    int[] permutation = new int[6];
+    TBPyraminx.getEdgePermutation(permutation,quats,0);
+    boolean even = TablebaseHelpers.permutationIsEven(permutation);
+    if( !even ) return ERROR_TWO_EDGES;
+    int[] edge_twist = new int[6];
+    TBPyraminx.getEdgeTwist(edge_twist,quats,0);
+
+    int totalEdgeTwist=0;
+    for(int i=0; i<6; i++) totalEdgeTwist += edge_twist[i];
+    if( (totalEdgeTwist%2)!=0 ) return ERROR_EDGE_TWISTED;
+
+    int vertexTwist = vertex_twist[0]+ 3*(vertex_twist[1]+ 3*(vertex_twist[2]+ 3*vertex_twist[3]));
+    int edgeTwist = edge_twist[0]+ 2*(edge_twist[1]+ 2*(edge_twist[2]+ 2*(edge_twist[3]+ 2*edge_twist[4])));
+    int perm_num = TablebaseHelpers.computeEvenPermutationNum(permutation);
+
+    return vertexTwist + 81*(edgeTwist + 32*perm_num);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_green3;
+      case 1: return R.string.color_yellow3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_red3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int color)
+    {
+    switch(color)
+      {
+      case 0: return R.string.color_green4;
+      case 1: return R.string.color_yellow4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_red4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getFaceIndex3(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_green3;
+      case 1: return R.string.color_yellow3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_red3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getFaceIndex6(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_green6;
+      case 1: return R.string.color_yellow6;
+      case 2: return R.string.color_blue6;
+      case 3: return R.string.color_red6;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerError(Resources res, int color0, int color1, int color2)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex3(color1);
+    int j2 = getColorIndex4(color2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String vertexError(Resources res, int color0, int color1, int color2)
+    {
+    int j0 = getColorIndex3(color0);
+    int j1 = getColorIndex3(color1);
+    int j2 = getColorIndex4(color2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_missing_vertex,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String edgeError(Resources res, int color0, int color1)
+    {
+    int j0 = getFaceIndex3(color0);
+    int j1 = getFaceIndex6(color1);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+
+    return res.getString(R.string.solver_generic_missing_edge,c0,c1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_YBR_MISSING: return cornerError(res,3,2,1);
+      case ERROR_CORNER_GBR_MISSING: return cornerError(res,3,2,0);
+      case ERROR_CORNER_GYR_MISSING: return cornerError(res,3,1,0);
+      case ERROR_CORNER_GYB_MISSING: return cornerError(res,2,1,0);
+      case ERROR_VERTEX_YBR_MISSING: return vertexError(res,3,2,1);
+      case ERROR_VERTEX_GBR_MISSING: return vertexError(res,3,2,0);
+      case ERROR_VERTEX_GYR_MISSING: return vertexError(res,3,1,0);
+      case ERROR_VERTEX_GYB_MISSING: return vertexError(res,2,1,0);
+      case ERROR_EDGE_RB_MISSING   : return edgeError(res,3,2);
+      case ERROR_EDGE_RY_MISSING   : return edgeError(res,2,0);
+      case ERROR_EDGE_RG_MISSING   : return edgeError(res,2,1);
+      case ERROR_EDGE_YB_MISSING   : return edgeError(res,3,0);
+      case ERROR_EDGE_GB_MISSING   : return edgeError(res,3,1);
+      case ERROR_EDGE_GY_MISSING   : return edgeError(res,1,0);
+      case ERROR_EDGE_TWISTED      : return res.getString(R.string.solver_generic_edge_twist);
+      case ERROR_CORNERS_CANNOT    : return res.getString(R.string.solver_generic_corners_cannot);
+      case ERROR_VERTICES_CANNOT   : return res.getString(R.string.solver_generic_vertices_cannot);
+      case ERROR_C_V_DONT_MATCH    : return res.getString(R.string.solver_generic_c_v_dont_match);
+      case ERROR_TWO_EDGES         : return res.getString(R.string.solver_generic_two_edges);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.PYRA_3.name());
+      }
+
+    return mSolver!=null ? mSolver.solution(index,mCornerTwist,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverTablebaseSKEW2.java b/src/main/java/org/distorted/solvers/SolverTablebaseSKEW2.java
new file mode 100644
index 00000000..3cda0fcf
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverTablebaseSKEW2.java
@@ -0,0 +1,451 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 static org.distorted.objectlib.tablebases.TBSkewb.FIXED;
+import static org.distorted.objectlib.tablebases.TBSkewb.FREE;
+
+import android.content.res.Resources;
+
+import org.distorted.main.R;
+import org.distorted.objectlib.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.metadata.ListObjects;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.tablebases.ImplementedTablebasesList;
+import org.distorted.objectlib.tablebases.TBSkewb;
+import org.distorted.objectlib.tablebases.TablebaseHelpers;
+import org.distorted.objectlib.tablebases.TablebasesAbstract;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverTablebaseSKEW2 extends SolverTablebase
+{
+  private static final int ERROR_CORNER_135_MISSING = -1;
+  private static final int ERROR_CORNER_134_MISSING = -2;
+  private static final int ERROR_CORNER_125_MISSING = -3;
+  private static final int ERROR_CORNER_124_MISSING = -4;
+  private static final int ERROR_CORNER_035_MISSING = -5;
+  private static final int ERROR_CORNER_034_MISSING = -6;
+  private static final int ERROR_CORNER_025_MISSING = -7;
+  private static final int ERROR_CORNER_024_MISSING = -8;
+
+  private static final int ERROR_CENTER_0_MISSING   = -9;
+  private static final int ERROR_CENTER_1_MISSING   = -10;
+  private static final int ERROR_CENTER_2_MISSING   = -11;
+  private static final int ERROR_CENTER_3_MISSING   = -12;
+  private static final int ERROR_CENTER_4_MISSING   = -13;
+  private static final int ERROR_CENTER_5_MISSING   = -14;
+
+  private static final int ERROR_CORNERS_CANNOT     = -15;
+  private static final int ERROR_CORNER_TWISTED     = -16;
+  private static final int ERROR_TWO_CENTERS        = -17;
+  private static final int ERROR_TWO_CORNERS        = -18;
+
+  private static final int ERROR_FREE_CORNERS_NOT_EVEN    = -19;
+  private static final int ERROR_FREE_CORNERS_ROTATED     = -20;
+
+  private TablebasesAbstract mSolver;
+  private final int[] mFaceColors;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverTablebaseSKEW2(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    mFaceColors = new int[6];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void getCorners(TwistyObject object, int[][] corners)
+    {
+    for(int i=0; i<8; i++)
+      for(int j=0; j<3; j++)
+        corners[i][j] = object.getCubitFaceStickerIndex(i,j);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createCenterPermutation(int[] output, int[] symbols, int[] perm)
+    {
+    for(int s=0; s<6; s++)
+      {
+      int symbol = symbols[s];
+
+      for(int p=0; p<6; p++)
+        if( perm[p]==symbol )
+          {
+          output[s] = p;
+          break;
+          }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getCenters(TwistyObject object, int[] out)
+    {
+    final int[] map = {4,5,2,3,0,1};
+    int[] tmp = new int[6];
+    int[] mcl = new int[6];
+    boolean[] present = new boolean[6];
+
+    for(int i=0; i<6; i++)
+      {
+      int color = object.getCubitFaceStickerIndex(i+8,0) - 6;
+      present[color] = true;
+      tmp[i] = color;
+      }
+
+    if( !present[0] ) return ERROR_CENTER_0_MISSING;
+    if( !present[1] ) return ERROR_CENTER_1_MISSING;
+    if( !present[2] ) return ERROR_CENTER_2_MISSING;
+    if( !present[3] ) return ERROR_CENTER_3_MISSING;
+    if( !present[4] ) return ERROR_CENTER_4_MISSING;
+    if( !present[5] ) return ERROR_CENTER_5_MISSING;
+
+    for(int i=0; i<6; i++) mcl[i] = map[mFaceColors[i]];
+    createCenterPermutation(out,mcl,tmp);
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int commonCornerColor(int[] c1, int[] c2)
+    {
+    int theSame = 0;
+    int index   = 0;
+
+    for(int i=0; i<3; i++)
+      for(int j=0; j<3; j++)
+        if( c1[i]==c2[j] )
+          {
+          index = i;
+          theSame++;
+          }
+
+    return theSame==1 ? c1[index] : ERROR_CORNERS_CANNOT;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeFaceColors(int[][] corners, int[] output)
+    {
+    final int[][] corner_indices = { {0,3},{5,6},{0,5},{6,3},{0,6},{3,5} };
+
+    for(int i=0; i<6; i++)
+      {
+      int c1 = corner_indices[i][0];
+      int c2 = corner_indices[i][1];
+      output[i] = commonCornerColor(corners[c1],corners[c2]);
+      if( output[i]<0 ) return ERROR_CORNERS_CANNOT;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean cornerIs(int[] corner, int f0, int f1, int f2)
+    {
+    int c0 = mFaceColors[f0];
+    int c1 = mFaceColors[f1];
+    int c2 = mFaceColors[f2];
+
+    return ( (corner[0]==c0 && corner[1]==c1 && corner[2]==c2 ) ||
+             (corner[1]==c0 && corner[2]==c1 && corner[0]==c2 ) ||
+             (corner[2]==c0 && corner[0]==c1 && corner[1]==c2 )  );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int checkAllCornersPresent(int[][] corners)
+    {
+    boolean[] present = new boolean[8];
+
+    for(int i=0; i<8; i++)
+      {
+      if( cornerIs(corners[i],4,2,0) ) present[0]=true;
+      if( cornerIs(corners[i],2,5,0) ) present[1]=true;
+      if( cornerIs(corners[i],3,4,0) ) present[2]=true;
+      if( cornerIs(corners[i],5,3,0) ) present[3]=true;
+      if( cornerIs(corners[i],1,2,4) ) present[4]=true;
+      if( cornerIs(corners[i],5,2,1) ) present[5]=true;
+      if( cornerIs(corners[i],4,3,1) ) present[6]=true;
+      if( cornerIs(corners[i],1,3,5) ) present[7]=true;
+      }
+
+    if( !present[0] ) return ERROR_CORNER_024_MISSING;
+    if( !present[1] ) return ERROR_CORNER_025_MISSING;
+    if( !present[2] ) return ERROR_CORNER_034_MISSING;
+    if( !present[3] ) return ERROR_CORNER_035_MISSING;
+    if( !present[4] ) return ERROR_CORNER_124_MISSING;
+    if( !present[5] ) return ERROR_CORNER_125_MISSING;
+    if( !present[6] ) return ERROR_CORNER_134_MISSING;
+    if( !present[7] ) return ERROR_CORNER_135_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int retFreeCornerPermutation(int[] perm, int[][] corners)
+    {
+    perm[0] = -1;
+    perm[1] = -1;
+    perm[2] = -1;
+    perm[3] = -1;
+
+    for(int i=0; i<4; i++)
+      {
+      int[] cor = corners[FREE[i]];
+
+      if( cornerIs(cor,2,5,0) ) perm[0] = i;
+      if( cornerIs(cor,3,4,0) ) perm[1] = i;
+      if( cornerIs(cor,1,2,4) ) perm[2] = i;
+      if( cornerIs(cor,1,3,5) ) perm[3] = i;
+      }
+
+    if( perm[0]==-1 ) return ERROR_CORNER_025_MISSING;
+    if( perm[1]==-1 ) return ERROR_CORNER_034_MISSING;
+    if( perm[2]==-1 ) return ERROR_CORNER_124_MISSING;
+    if( perm[3]==-1 ) return ERROR_CORNER_135_MISSING;
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void computeCornerQuats(int[] quats, int[][] corners, int[] perm)
+    {
+    final int[] zeroeth_face_map = { 4,2,3,5,1,5,4,1 };
+    int[] twist = new int[8];
+
+    for(int i=0; i<4; i++)
+      {
+      int fi = FIXED[i];
+      int colorFi = mFaceColors[zeroeth_face_map[fi]];
+      int[] cFi = corners[fi];
+
+           if( cFi[0]==colorFi ) twist[fi] = 0;
+      else if( cFi[1]==colorFi ) twist[fi] = 1;
+      else                       twist[fi] = 2;
+      }
+
+    int[] inv_perm = new int[4];
+    TablebaseHelpers.invertPermutation(perm,inv_perm);
+
+    int common14 = commonCornerColor(corners[1], corners[4]);
+    int common27 = commonCornerColor(corners[2], corners[7]);
+    int common47 = commonCornerColor(corners[4], corners[7]);
+    int index;
+    int[] c;
+
+    index = FREE[inv_perm[0]];
+    c = corners[FREE[0]];
+
+         if(c[0]==common14) twist[index] = 0;
+    else if(c[1]==common14) twist[index] = 2;
+    else                    twist[index] = 1;
+
+    index = FREE[inv_perm[1]];
+    c = corners[FREE[1]];
+
+         if(c[0]==common27) twist[index] = 0;
+    else if(c[1]==common27) twist[index] = 2;
+    else                    twist[index] = 1;
+
+    index = FREE[inv_perm[2]];
+    c = corners[FREE[2]];
+
+         if(c[0]==common47) twist[index] = 0;
+    else if(c[1]==common47) twist[index] = 2;
+    else                    twist[index] = 1;
+
+    index = FREE[inv_perm[3]];
+    c = corners[FREE[3]];
+
+         if(c[0]==common47) twist[index] = 0;
+    else if(c[1]==common47) twist[index] = 2;
+    else                    twist[index] = 1;
+
+    TBSkewb.fillInQuats(quats,perm,twist);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int tablebaseIndex(TwistyObject object)
+    {
+    int[][] corners= new int[8][3];
+    int[] centers  = new int[6];
+    int[] twist    = new int[8];
+    int[] freePerm = new int[4];
+    int[] quats    = new int[8];
+
+    getCorners(object,corners);
+
+    int result1 = computeFaceColors(corners,mFaceColors);
+    if( result1<0 ) return result1;
+
+    int result2 = checkAllCornersPresent(corners);
+    if( result2<0 ) return result2;
+
+    int result3 = getCenters(object,centers);
+    if( result3<0 ) return result3;
+
+    if( !TablebaseHelpers.permutationIsEven(centers) ) return ERROR_TWO_CENTERS;
+    int center_perm_num = TablebaseHelpers.computeEvenPermutationNum(centers);
+
+    int result4 = retFreeCornerPermutation(freePerm,corners);
+    if( result4<0 ) return result4;
+
+    computeCornerQuats(quats,corners,freePerm);
+
+    int[] freeLoc = new int[4];
+    for(int f=0; f<4; f++) freeLoc[f] = TBSkewb.computeLocation(f,quats[FREE[f]]);
+    if( !TablebaseHelpers.permutationIsEven(freeLoc) ) return ERROR_FREE_CORNERS_NOT_EVEN;
+
+    TBSkewb.computeCornerTwists(twist,quats);
+
+    int total = 0;
+    for(int i=0; i<8; i++) total += twist[i];
+    if( (total%3)!=0 ) return ERROR_CORNER_TWISTED;
+    int totalTwist = twist[0]+ 3*(twist[1]+ 3*(twist[2]+ 3*(twist[3]+ 3*(twist[4]+ 3*(twist[5]+ 3*twist[6])))));
+
+    int sumFixedTwists = twist[FIXED[0]]+twist[FIXED[1]]+twist[FIXED[2]]+twist[FIXED[3]];
+    int freeLoc1 = TBSkewb.permFree1(freeLoc[0],sumFixedTwists);
+    if( freeLoc[1] != freeLoc1 ) return ERROR_FREE_CORNERS_ROTATED;
+
+    return center_perm_num+ 360*(totalTwist + 2187*freeLoc[0]);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex2(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_yellow2;
+      case 1: return R.string.color_white2;
+      case 2: return R.string.color_blue2;
+      case 3: return R.string.color_green2;
+      case 4: return R.string.color_red2;
+      case 5: return R.string.color_orange2;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex3(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_yellow3;
+      case 1: return R.string.color_white3;
+      case 2: return R.string.color_blue3;
+      case 3: return R.string.color_green3;
+      case 4: return R.string.color_red3;
+      case 5: return R.string.color_orange3;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int getColorIndex4(int face)
+    {
+    switch(mFaceColors[face])
+      {
+      case 0: return R.string.color_yellow4;
+      case 1: return R.string.color_white4;
+      case 2: return R.string.color_blue4;
+      case 3: return R.string.color_green4;
+      case 4: return R.string.color_red4;
+      case 5: return R.string.color_orange4;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String cornerError(Resources res, int face0, int face1, int face2)
+    {
+    int j0 = getColorIndex3(face0);
+    int j1 = getColorIndex3(face1);
+    int j2 = getColorIndex4(face2);
+
+    String c0 = res.getString(j0);
+    String c1 = res.getString(j1);
+    String c2 = res.getString(j2);
+
+    return res.getString(R.string.solver_generic_missing_corner,c0,c1,c2);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String centerError(Resources res, int face)
+    {
+    int color = getColorIndex2(face);
+    String clr= res.getString(color);
+    return res.getString(R.string.solver_generic_missing_center,clr);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String error(int index, Resources res)
+    {
+    switch(index)
+      {
+      case ERROR_CORNER_135_MISSING   : return cornerError(res,1,3,5);
+      case ERROR_CORNER_134_MISSING   : return cornerError(res,1,3,4);
+      case ERROR_CORNER_125_MISSING   : return cornerError(res,1,2,5);
+      case ERROR_CORNER_124_MISSING   : return cornerError(res,1,2,4);
+      case ERROR_CORNER_035_MISSING   : return cornerError(res,0,3,5);
+      case ERROR_CORNER_034_MISSING   : return cornerError(res,0,3,4);
+      case ERROR_CORNER_025_MISSING   : return cornerError(res,0,2,5);
+      case ERROR_CORNER_024_MISSING   : return cornerError(res,0,2,4);
+
+      case ERROR_CENTER_0_MISSING     : return centerError(res,0);
+      case ERROR_CENTER_1_MISSING     : return centerError(res,1);
+      case ERROR_CENTER_2_MISSING     : return centerError(res,2);
+      case ERROR_CENTER_3_MISSING     : return centerError(res,3);
+      case ERROR_CENTER_4_MISSING     : return centerError(res,4);
+      case ERROR_CENTER_5_MISSING     : return centerError(res,5);
+
+      case ERROR_CORNERS_CANNOT       : return res.getString(R.string.solver_generic_corners_cannot);
+      case ERROR_CORNER_TWISTED       : return res.getString(R.string.solver_generic_corner_twist);
+      case ERROR_TWO_CENTERS          : return res.getString(R.string.solver_generic_two_centers);
+      case ERROR_TWO_CORNERS          : return res.getString(R.string.solver_generic_two_corners);
+
+      case ERROR_FREE_CORNERS_NOT_EVEN: return res.getString(R.string.solver_generic_free_corners_odd);
+      case ERROR_FREE_CORNERS_ROTATED : return res.getString(R.string.solver_generic_free_corners_rotated);
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int[][] solution(int index, OperatingSystemInterface os)
+    {
+    if( mSolver==null )
+      {
+      mSolver = ImplementedTablebasesList.createPacked(os, ListObjects.SKEW_2.name() );
+      }
+
+    return mSolver!=null ? mSolver.solution(index,null,os) : null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolvingInterface.java b/src/main/java/org/distorted/solvers/SolvingInterface.java
new file mode 100644
index 00000000..71034ba5
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolvingInterface.java
@@ -0,0 +1,19 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2024 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 org.distorted.solverui.ScreenSolver;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public interface SolvingInterface
+{
+  public void solve(ScreenSolver screen);
+}
diff --git a/src/main/java/org/distorted/solvers/SolvingList.java b/src/main/java/org/distorted/solvers/SolvingList.java
new file mode 100644
index 00000000..b8bca0f7
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolvingList.java
@@ -0,0 +1,151 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objectlib.metadata.*;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public enum SolvingList
+{
+  CUBE2          (MetadataCUBE_2.INDEX, SolverTablebaseCUBE2.class,  R.string.solver_cube2_title, R.string.solver_cube2_description, true),
+  CUBE3_KOCIEMBA (MetadataCUBE_3.INDEX, SolverKociembaCUBE3.class , R.string.solver_cube3_title, R.string.solver_cube3_description, true),
+  CU_232         (MetadataCU_232.INDEX, SolverTablebaseCU232.class, R.string.solver_cu232_title, R.string.solver_cu232_description, true),
+  CU_323         (MetadataCU_323.INDEX, SolverTablebaseCU323.class, R.string.solver_cu323_title, R.string.solver_cu323_description, true),
+  PYRAMINX       (MetadataPYRA_3.INDEX, SolverTablebasePYRA3.class, R.string.solver_pyra3_title, R.string.solver_pyra3_description, true),
+  SKEWB          (MetadataSKEW_2.INDEX, SolverTablebaseSKEW2.class, R.string.solver_skew2_title, R.string.solver_skew2_description, true),
+  PYRAMINX_DUO   (MetadataPDUO_2.INDEX, SolverTablebasePDUO2.class, R.string.solver_pduo2_title, R.string.solver_pduo2_description, true),
+  IVY            (MetadataIVY_2.INDEX , SolverTablebaseIVY2.class , R.string.solver_ivy_title, R.string.solver_ivy_description, true),
+  DIAMOND        (MetadataDIAM_2.INDEX, SolverTablebasePDIA3.class, R.string.solver_diam2_title, R.string.solver_diam2_description, true),
+  JING2          (MetadataJING_2.INDEX, SolverTablebaseJING2.class, R.string.solver_jing2_title, R.string.solver_jing2_description, true),
+  DINO6          (MetadataDINO_3.INDEX, SolverTablebaseDINO6.class, R.string.solver_dino6_title, R.string.solver_dino6_description, true),
+  DINO4          (MetadataDIN4_3.INDEX, SolverTablebaseDINO4.class, R.string.solver_dino4_title, R.string.solver_dino4_description, true),
+  PDIA           (MetadataPDIA_3.INDEX, SolverTablebasePDUO2.class, R.string.solver_pdia_title, R.string.solver_pdia_description, true),
+  ;
+
+  public static final int NUM_OBJECTS = values().length;
+
+  private final int mObject;
+  private final Class<? extends SolvingInterface> mClass;
+  private final int mTitle;
+  private final int mDescription;
+  private final boolean mImplemented;
+
+  private static final SolvingList[] objects;
+
+  static
+    {
+    objects = new SolvingList[NUM_OBJECTS];
+    int i=0;
+
+    for( SolvingList object: SolvingList.values())
+      {
+      objects[i++] = object;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static SolvingList getSolver(int solverOrdinal)
+    {
+    return objects[solverOrdinal];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static int getSolverOrdinal(int objectOrdinal)
+    {
+    for(int o=0; o<NUM_OBJECTS; o++)
+      if( objects[o].mObject==objectOrdinal ) return o;
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  SolvingList(int object, final Class<? extends SolvingInterface> clazz, int title, int descripton, boolean implemented)
+    {
+    mObject      = object;
+    mClass       = clazz;
+    mTitle       = title;
+    mDescription = descripton;
+    mImplemented = implemented;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// certain objects have certain cubits locked - for example, the Cube3's centers of
+// sides always have the same color.
+// If a certain cubit is locked, return the color (index into it's FACE_COLORS array) it
+// must have. Otherwise return -1.
+
+  public static int cubitIsLocked(int object, int cubit)
+    {
+    if( object == MetadataCUBE_3.INDEX )
+      {
+      if( cubit==20 ) return 0; // center of the right  face
+      if( cubit==21 ) return 1; // center of the left   face
+      if( cubit==22 ) return 2; // center of the up     face
+      if( cubit==23 ) return 3; // center of the bottom face
+      if( cubit==24 ) return 4; // center of the front  face
+      if( cubit==25 ) return 5; // center of the back   face
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getObject() { return mObject; }
+  public int getTitle() { return mTitle; }
+  public int getDescription() { return mDescription; }
+  public boolean isImplemented() { return mImplemented; }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolvingInterface create(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    try
+      {
+      Constructor<?>[] cons = mClass.getConstructors();
+
+      if( cons.length==1 )
+        {
+        Object[] parameters = new Object[] { os,res,object };
+        return (SolvingInterface)cons[0].newInstance(parameters);
+        }
+      else
+        {
+        android.util.Log.e("ImplementedSolversList", "ERROR! number of SolverInterface constructors="+cons.length);
+        }
+      }
+    catch(IllegalAccessException iae)
+      {
+      android.util.Log.e("ImplementedSolversList", "Illegal Access Exception: "+iae.getMessage());
+      }
+    catch(InstantiationException ie)
+      {
+      android.util.Log.e("ImplementedSolversList", "Instantiation Exception: "+ie.getMessage());
+      }
+    catch(InvocationTargetException ite)
+      {
+      android.util.Log.e("ImplementedSolversList", "Invocation Target Exception: "+ite.getMessage());
+      }
+
+    return null;
+    }
+}
diff --git a/src/main/java/org/distorted/solvers/SolvingThread.java b/src/main/java/org/distorted/solvers/SolvingThread.java
new file mode 100644
index 00000000..6b1e71e9
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolvingThread.java
@@ -0,0 +1,58 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 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.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.main.TwistyObject;
+
+import org.distorted.main.R;
+import org.distorted.solverui.ScreenList;
+import org.distorted.solverui.ScreenSolver;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolvingThread implements Runnable
+{
+  private final Resources mRes;
+  private final OperatingSystemInterface mOS;
+  private final TwistyObject mObject;
+  private final SolvingList mList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolvingThread(OperatingSystemInterface os, Resources res, TwistyObject object, SolvingList list)
+    {
+    mRes   = res;
+    mOS    = os;
+    mObject= object;
+    mList  = list;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void start()
+    {
+    Thread thr = new Thread(this);
+    thr.start();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void run()
+    {
+    ScreenSolver screen=(ScreenSolver) ScreenList.SVER.getScreenClass();
+    SolvingInterface solver = mList.create(mOS,mRes,mObject);
+    if( solver!=null ) solver.solve(screen);
+    else screen.displayErrorDialog(mRes.getString(R.string.solver_generic_not_implemented));
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solverui/ScreenSolver.java b/src/main/java/org/distorted/solverui/ScreenSolver.java
index 5528eec3..36101bcd 100644
--- a/src/main/java/org/distorted/solverui/ScreenSolver.java
+++ b/src/main/java/org/distorted/solverui/ScreenSolver.java
@@ -60,8 +60,8 @@ import org.distorted.objectlib.shape.ShapeHexahedron;
 import org.distorted.objectlib.shape.ShapeIcosahedron;
 import org.distorted.objectlib.shape.ShapeOctahedron;
 import org.distorted.objectlib.shape.ShapeTetrahedron;
-import org.distorted.solvers.ImplementedSolversList;
-import org.distorted.solvers.SolverMain;
+import org.distorted.solvers.SolvingList;
+import org.distorted.solvers.SolvingThread;
 
 import java.lang.ref.WeakReference;
 
@@ -83,6 +83,7 @@ public class ScreenSolver extends ScreenAbstract
   private int mNumColors;
   private float mBitmapSize;
   private WeakReference<SolverActivity> mWeakAct;
+  private SolvingList mList;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -106,8 +107,8 @@ public class ScreenSolver extends ScreenAbstract
     mSolving = false;
 
     int solverIndex= act.getSolverOrdinal();
-    ImplementedSolversList currentSolver = ImplementedSolversList.getSolver(solverIndex);
-    int currentObject = currentSolver.getObject();
+    mList = SolvingList.getSolver(solverIndex);
+    int currentObject = mList.getObject();
     control.solveOnly();
     generateFaceColors(currentObject);
 
@@ -335,7 +336,7 @@ public class ScreenSolver extends ScreenAbstract
           {
           mSolving = true;
           TwistyObject object = act.getObject();
-          SolverMain solver = new SolverMain( act.getInterface(), act.getResources(), object );
+          SolvingThread solver = new SolvingThread( act.getInterface(), act.getResources(), object, mList );
           solver.start();
           }
         }
@@ -419,7 +420,7 @@ public class ScreenSolver extends ScreenAbstract
           ScreenList.switchScreen(act, ScreenList.SOLU);
           ScreenSolution solution = (ScreenSolution) ScreenList.SOLU.getScreenClass();
           solution.setupMoves(act, moves);
-          if( moves.length()>0 ) act.doNotShowDialogAnymore();
+          if( !moves.isEmpty() ) act.doNotShowDialogAnymore();
           }
         });
       }
diff --git a/src/main/java/org/distorted/solverui/SolverActivity.java b/src/main/java/org/distorted/solverui/SolverActivity.java
index 922d8a92..14342de9 100644
--- a/src/main/java/org/distorted/solverui/SolverActivity.java
+++ b/src/main/java/org/distorted/solverui/SolverActivity.java
@@ -29,7 +29,7 @@ import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 import org.distorted.os.OSInterface;
-import org.distorted.solvers.ImplementedSolversList;
+import org.distorted.solvers.SolvingList;
 
 import java.io.InputStream;
 
@@ -63,7 +63,7 @@ public class SolverActivity extends BaseActivity
       mObjectOrdinal = b!=null ? b.getInt("obj") : 0;
       RubikObject object = RubikObjectList.getObject(mObjectOrdinal);
       int objectIndex = object==null ? -1 : object.getIndex();
-      mSolverOrdinal = ImplementedSolversList.getSolverOrdinal(objectIndex);
+      mSolverOrdinal = SolvingList.getSolverOrdinal(objectIndex);
       mDisplayMessageDialog = true;
 
       computeScreenDimensions();
diff --git a/src/main/java/org/distorted/solverui/SolverObjectLibInterface.java b/src/main/java/org/distorted/solverui/SolverObjectLibInterface.java
index c1674708..22d5fe1d 100644
--- a/src/main/java/org/distorted/solverui/SolverObjectLibInterface.java
+++ b/src/main/java/org/distorted/solverui/SolverObjectLibInterface.java
@@ -18,7 +18,7 @@ import org.distorted.objectlib.helpers.ObjectLibInterface;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
-import org.distorted.solvers.SolverMain;
+import org.distorted.solvers.SolvingList;
 
 import java.lang.ref.WeakReference;
 
@@ -173,7 +173,7 @@ public class SolverObjectLibInterface implements ObjectLibInterface
     ScreenSolver solver = (ScreenSolver) ScreenList.SVER.getScreenClass();
     int color = solver.getCurrentColor();
     int currObject = act.getObjectOrdinal();
-    mLastCubitColor = SolverMain.cubitIsLocked(currObject,cubit);
+    mLastCubitColor = SolvingList.cubitIsLocked(currObject,cubit);
     mLastCubit = cubit;
     mLastCubitFace = face;
     ObjectControl control = act.getControl();
