commit 2f53a016974721b39a2bf7e8e4abc7b942dd6a42
Author: leszek <leszek@koltunski.pl>
Date:   Mon Dec 23 17:20:15 2024 +0100

    Integration of the Algorithmic solvers into the App.

diff --git a/src/main/java/org/distorted/dialogs/RubikDialogSolverView.java b/src/main/java/org/distorted/dialogs/RubikDialogSolverView.java
index 3906b643..c80e9717 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogSolverView.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogSolverView.java
@@ -9,6 +9,7 @@
 
 package org.distorted.dialogs;
 
+import android.content.res.Resources;
 import android.util.TypedValue;
 import android.view.View;
 import android.widget.Button;
@@ -16,6 +17,7 @@ import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import org.distorted.main.R;
+import org.distorted.objectlib.helpers.OperatingSystemInterface;
 import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.solvers.SolvingList;
 import org.distorted.solvers.SolvingThread;
@@ -53,8 +55,10 @@ public class RubikDialogSolverView
         {
         dialog.dismiss();
         SolvingList list = SolvingList.getSolver(solverOrdinal);
+        OperatingSystemInterface os = act.getInterface();
+        Resources res = act.getResources();
         TwistyObject object = act.getObject();
-        SolvingThread solver = new SolvingThread( act.getInterface(), act.getResources(), object, list );
+        SolvingThread solver = new SolvingThread( os,res,object,list );
         solver.start();
         }
       });
diff --git a/src/main/java/org/distorted/solvers/SolverAlgorithmic.java b/src/main/java/org/distorted/solvers/SolverAlgorithmic.java
new file mode 100644
index 00000000..b5f2eb24
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverAlgorithmic.java
@@ -0,0 +1,107 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 android.content.res.Resources;
+
+import org.distorted.objectlib.algsolvers.SolutionListener;
+import org.distorted.objectlib.algsolvers.SolvedObject;
+import org.distorted.objectlib.algsolvers.implemented.PhasedSolver3x3Beginner;
+import org.distorted.objectlib.algsolvers.implemented.PhasedSolverAbstract;
+import org.distorted.objectlib.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.solverui.ScreenList;
+import org.distorted.solverui.ScreenPhasedSolution;
+import org.distorted.solverui.ScreenSolver;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class SolverAlgorithmic implements SolvingInterface, SolutionListener
+{
+  private final SolvedObject mSolvedObject;
+  private final PhasedSolverAbstract mSolver;
+  private final OperatingSystemInterface mOS;
+  private final Resources mRes;
+  private final TwistyObject mObject;
+  private long mTime;
+  private ScreenSolver mScreen;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public SolverAlgorithmic(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    mOS     = os;
+    mRes    = res;
+    mObject = object;
+    mSolver = new PhasedSolver3x3Beginner();
+    mSolvedObject = mSolver.getObject();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract int[] validatePosition(TwistyObject object);
+  abstract String getError(Resources res);
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void solve(ScreenSolver screen)
+    {
+    mScreen = screen;
+    int[] quats = validatePosition(mObject);
+
+    if( quats!=null )
+      {
+      int numPhases = mSolver.getNumPhases();
+      String[] names = new String[numPhases];
+      for(int p=0; p<numPhases; p++) names[p] = mSolver.getPhaseName(p);
+      ScreenPhasedSolution solScreen = (ScreenPhasedSolution) ScreenList.PHAS.getScreenClass();
+      solScreen.updateNames(names);
+      mTime = System.currentTimeMillis();
+      mSolver.solution(this,quats);
+      }
+    else
+      {
+      String error = getError(mRes);
+      screen.displayImpossibleDialog(error);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void receiveSolution(int[] solution, int phaseNumber)
+    {
+    if( solution==null )
+      {
+      String message = "Phase "+phaseNumber+": FAIL";
+      System.out.println(message);
+      }
+    else
+      {
+      int numMoves = solution[0];
+      int[][] moves = new int[numMoves][];
+      int index = 0;
+
+      for(int m=1; m<=numMoves; m++)
+        moves[index++] = mSolvedObject.findMove(solution[m]);
+
+      int[][] subphases = mSolver.getSubPhases(phaseNumber);
+      mScreen.setSolved(moves,phaseNumber,subphases);
+      }
+
+    long time = System.currentTimeMillis();
+    long diff = time - mTime;
+    mTime = time;
+
+    System.out.println("Phase "+phaseNumber+" solved in "+diff+"ms. Moves: "+(solution==null ? 0:solution[0]));
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolverAlgorithmicCUBE3.java b/src/main/java/org/distorted/solvers/SolverAlgorithmicCUBE3.java
new file mode 100644
index 00000000..5fa5ffd4
--- /dev/null
+++ b/src/main/java/org/distorted/solvers/SolverAlgorithmicCUBE3.java
@@ -0,0 +1,43 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 android.content.res.Resources;
+
+import org.distorted.objectlib.helpers.OperatingSystemInterface;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.solverui.SolverActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class SolverAlgorithmicCUBE3 extends SolverAlgorithmic
+{
+  public SolverAlgorithmicCUBE3(OperatingSystemInterface os, Resources res, TwistyObject object)
+    {
+    super(os,res,object);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  int[] validatePosition(TwistyObject object)
+    {
+    int numCubits = object.getNumCubits();
+    int[] ret = new int[numCubits];   // mockup
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  String getError(Resources res)
+    {
+    return null;
+    }
+}  
+
diff --git a/src/main/java/org/distorted/solvers/SolvingList.java b/src/main/java/org/distorted/solvers/SolvingList.java
index 84fa5cc6..e3de870a 100644
--- a/src/main/java/org/distorted/solvers/SolvingList.java
+++ b/src/main/java/org/distorted/solvers/SolvingList.java
@@ -25,21 +25,20 @@ import java.lang.reflect.InvocationTargetException;
 
 public enum SolvingList
 {
-  CUBE2          (CUBE_2.ordinal(), SolverTablebaseCUBE2.class, R.string.solver_cube2_title, R.string.solver_cube2_description, true),
-  CUBE3_KOCIEMBA (CUBE_3.ordinal(), SolverKociembaCUBE3.class , R.string.solver_cube3_title, R.string.solver_cube3_description, true),
-  CUBE3_FAKE     (CUBE_3.ordinal(), SolverKociembaCUBE3.class , R.string.solver_cube3_title, R.string.solver_cube3_description, true),
-
-  CU232          (CU_232.ordinal(), SolverTablebaseCU232.class, R.string.solver_cu232_title, R.string.solver_cu232_description, true),
-  CU323          (CU_323.ordinal(), SolverTablebaseCU323.class, R.string.solver_cu323_title, R.string.solver_cu323_description, true),
-  PYRAMINX       (PYRA_3.ordinal(), SolverTablebasePYRA3.class, R.string.solver_pyra3_title, R.string.solver_pyra3_description, true),
-  SKEWB          (SKEW_2.ordinal(), SolverTablebaseSKEW2.class, R.string.solver_skew2_title, R.string.solver_skew2_description, true),
-  PYRAMINX_DUO   (PDUO_2.ordinal(), SolverTablebasePDUO2.class, R.string.solver_pduo2_title, R.string.solver_pduo2_description, true),
-  IVY            (IVY_2.ordinal() , SolverTablebaseIVY2.class , R.string.solver_ivy_title, R.string.solver_ivy_description, true),
-  DIAMOND        (DIAM_2.ordinal(), SolverTablebaseDIAM2.class, R.string.solver_diam2_title, R.string.solver_diam2_description, true),
-  JING2          (JING_2.ordinal(), SolverTablebaseJING2.class, R.string.solver_jing2_title, R.string.solver_jing2_description, true),
-  DINO6          (DINO_3.ordinal(), SolverTablebaseDINO6.class, R.string.solver_dino6_title, R.string.solver_dino6_description, true),
-  DINO4          (DIN4_3.ordinal(), SolverTablebaseDINO4.class, R.string.solver_dino4_title, R.string.solver_dino4_description, true),
-  PDIA           (PDIA_3.ordinal(), SolverTablebasePDIA3.class, R.string.solver_pdia_title, R.string.solver_pdia_description, true),
+  CUBE2          (CUBE_2.ordinal(), SolverTablebaseCUBE2.class  , R.string.solver_cube2_title, R.string.solver_cube2_description, true),
+  CUBE3_KOCIEMBA (CUBE_3.ordinal(), SolverKociembaCUBE3.class   , R.string.solver_cube3_title, R.string.solver_cube3_description, true),
+  CUBE3_ALGO     (CUBE_3.ordinal(), SolverAlgorithmicCUBE3.class, R.string.solver_3algo_title, R.string.solver_3algo_description, true),
+  CU232          (CU_232.ordinal(), SolverTablebaseCU232.class  , R.string.solver_cu232_title, R.string.solver_cu232_description, true),
+  CU323          (CU_323.ordinal(), SolverTablebaseCU323.class  , R.string.solver_cu323_title, R.string.solver_cu323_description, true),
+  PYRAMINX       (PYRA_3.ordinal(), SolverTablebasePYRA3.class  , R.string.solver_pyra3_title, R.string.solver_pyra3_description, true),
+  SKEWB          (SKEW_2.ordinal(), SolverTablebaseSKEW2.class  , R.string.solver_skew2_title, R.string.solver_skew2_description, true),
+  PYRAMINX_DUO   (PDUO_2.ordinal(), SolverTablebasePDUO2.class  , R.string.solver_pduo2_title, R.string.solver_pduo2_description, true),
+  IVY            (IVY_2.ordinal() , SolverTablebaseIVY2.class   , R.string.solver_ivy_title, R.string.solver_ivy_description, true),
+  DIAMOND        (DIAM_2.ordinal(), SolverTablebaseDIAM2.class  , R.string.solver_diam2_title, R.string.solver_diam2_description, true),
+  JING2          (JING_2.ordinal(), SolverTablebaseJING2.class  , R.string.solver_jing2_title, R.string.solver_jing2_description, true),
+  DINO6          (DINO_3.ordinal(), SolverTablebaseDINO6.class  , R.string.solver_dino6_title, R.string.solver_dino6_description, true),
+  DINO4          (DIN4_3.ordinal(), SolverTablebaseDINO4.class  , R.string.solver_dino4_title, R.string.solver_dino4_description, true),
+  PDIA           (PDIA_3.ordinal(), SolverTablebasePDIA3.class  , R.string.solver_pdia_title, R.string.solver_pdia_description, true),
   ;
 
   public static final int NUM_OBJECTS = values().length;
diff --git a/src/main/java/org/distorted/solverui/ScreenList.java b/src/main/java/org/distorted/solverui/ScreenList.java
index ef409dca..73fed0e2 100644
--- a/src/main/java/org/distorted/solverui/ScreenList.java
+++ b/src/main/java/org/distorted/solverui/ScreenList.java
@@ -18,8 +18,9 @@ import android.content.SharedPreferences;
 
 public enum ScreenList
   {
-  SVER ( null , MODE_REPLACE, new ScreenSolver()   ),
-  SOLU ( SVER , MODE_DRAG   , new ScreenSolution() ),
+  SVER ( null , MODE_REPLACE, new ScreenSolver()         ),
+  SOLU ( SVER , MODE_DRAG   , new ScreenSolution()       ),
+  PHAS ( SVER , MODE_DRAG   , new ScreenPhasedSolution() ),
   ;
 
   public static final int LENGTH = values().length;
diff --git a/src/main/java/org/distorted/solverui/ScreenPhasedSolution.java b/src/main/java/org/distorted/solverui/ScreenPhasedSolution.java
new file mode 100644
index 00000000..59f17312
--- /dev/null
+++ b/src/main/java/org/distorted/solverui/ScreenPhasedSolution.java
@@ -0,0 +1,578 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.solverui;
+
+import android.content.SharedPreferences;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.library.effect.PostprocessEffectGlow;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.message.EffectListener;
+import org.distorted.library.type.Dynamic2D;
+import org.distorted.library.type.Dynamic4D;
+import org.distorted.library.type.Static2D;
+import org.distorted.library.type.Static4D;
+import org.distorted.main.R;
+import org.distorted.objectlib.helpers.MovesFinished;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObject;
+
+import java.lang.ref.WeakReference;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScreenPhasedSolution extends ScreenAbstract implements MovesFinished, EffectListener
+  {
+  private static final int MOVES_PLACE_0 = 100;
+  private static final int MOVES_PLACE_1 = 101;
+  private static final int FLASH_TIME = 1200;
+  private static final int MILLIS_PER_DEGREE = 6;
+
+  private WeakReference<SolverActivity> mAct;
+  private TransparentImageButton mPrevButton, mNextButton, mBackButton, mPrevPhase, mNextPhase;
+  private float mButtonSize;
+
+  private TextView mMovesText, mMovesPhase;
+  private String[] mPhaseNames;
+  private int mNumPhases;
+  private int[][][] mMoves;
+  private int[][] mCubitsNotInvolved;
+  private int mNumMoves,mCurrMove,mCurrPhase;
+  private boolean mCanMove;
+
+  private Dynamic2D mHaloAndRadiusDyn;
+  private Dynamic4D mColorDyn;
+  private PostprocessEffectGlow mGlow;
+  private boolean mEffectWorking;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(SolverActivity act)
+    {
+    ObjectControl control = act.getControl();
+    control.solveOnly();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final SolverActivity act)
+    {
+    mAct = new WeakReference<>(act);
+
+    mHaloAndRadiusDyn = new Dynamic2D(FLASH_TIME,1.0f);
+    mHaloAndRadiusDyn.add(new Static2D( 0,0));
+    mHaloAndRadiusDyn.add(new Static2D(10,5));
+
+    mColorDyn = new Dynamic4D(FLASH_TIME,1.0f);
+
+    final int[] colors  = new int[] {1,1,1}; // white
+
+    Static4D P1 = new Static4D(colors[0],colors[1],colors[2], 0.0f);
+    Static4D P2 = new Static4D(colors[0],colors[1],colors[2], 1.0f);
+    mColorDyn.add(P1);
+    mColorDyn.add(P2);
+
+    mGlow = new PostprocessEffectGlow(mHaloAndRadiusDyn,mColorDyn);
+
+    float width = act.getScreenWidthInPixels();
+    mButtonSize = width*SolverActivity.BUTTON_TEXT_SIZE;
+
+    // TOP ////////////////////////////
+    LinearLayout layoutTop = act.findViewById(R.id.upperBar);
+    layoutTop.removeAllViews();
+
+    setupPrevPhase(act);
+    setupNextPhase(act);
+    setupTextPhase(act,width);
+
+    layoutTop.addView(mPrevPhase);
+    layoutTop.addView(mMovesPhase);
+    layoutTop.addView(mNextPhase);
+
+    // BOT ////////////////////////////
+    LinearLayout layoutBot = act.findViewById(R.id.lowerBar);
+    layoutBot.removeAllViews();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams((int)(width/2),LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams((int)(width/6),LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams((int)(width/3),LinearLayout.LayoutParams.MATCH_PARENT);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutMid = new LinearLayout(act);
+    layoutMid.setLayoutParams(paramsM);
+    LinearLayout layoutRight = new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
+
+    setupPrevButton(act);
+    setupNextButton(act);
+    setupTextView(act,width);
+
+    layoutLeft.addView(mPrevButton);
+    layoutLeft.addView(mMovesText);
+    layoutLeft.addView(mNextButton);
+
+    setupBackButton(act);
+
+    layoutRight.addView(mBackButton);
+
+    layoutBot.addView(layoutLeft);
+    layoutBot.addView(layoutMid);
+    layoutBot.addView(layoutRight);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPrevButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mPrevButton = new TransparentImageButton(act,R.drawable.ui_left,params);
+
+    mPrevButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override public void onClick(View v) { backMove(); }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupNextButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mNextButton = new TransparentImageButton(act,R.drawable.ui_right,params);
+
+    mNextButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override public void onClick(View v) { nextMove(); }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupTextView(final SolverActivity act, final float width)
+    {
+    int padding = (int)(width*SolverActivity.PADDING);
+    int margin  = (int)(width*SolverActivity.SMALL_MARGIN);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,2.0f);
+    params.topMargin    = margin;
+    params.bottomMargin = margin;
+    params.leftMargin   = margin;
+    params.rightMargin  = margin;
+
+    mMovesText = new TextView(act);
+    mMovesText.setTextSize(20);
+    mMovesText.setLayoutParams(params);
+    mMovesText.setPadding(padding,0,padding,0);
+    mMovesText.setGravity(Gravity.CENTER);
+    mMovesText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
+    mMovesText.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPrevPhase(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mPrevPhase = new TransparentImageButton(act,R.drawable.ui_left,params);
+
+    mPrevPhase.setOnClickListener( new View.OnClickListener()
+      {
+      @Override public void onClick(View v) { backPhase(); }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupNextPhase(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mNextPhase = new TransparentImageButton(act,R.drawable.ui_right,params);
+
+    mNextPhase.setOnClickListener( new View.OnClickListener()
+      {
+      @Override public void onClick(View v) { nextPhase(); }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupTextPhase(final SolverActivity act, final float width)
+    {
+    int padding = (int)(width*SolverActivity.PADDING);
+    int margin  = (int)(width*SolverActivity.SMALL_MARGIN);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,2.0f);
+    params.topMargin    = margin;
+    params.bottomMargin = margin;
+    params.leftMargin   = margin;
+    params.rightMargin  = margin;
+
+    mMovesPhase = new TextView(act);
+    mMovesPhase.setTextSize(20);
+    mMovesPhase.setLayoutParams(params);
+    mMovesPhase.setPadding(padding,0,padding,0);
+    mMovesPhase.setGravity(Gravity.CENTER);
+    mMovesPhase.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
+    mMovesPhase.setText(act.getString(R.string.mo_placeholder,mCurrMove,mNumMoves));
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final SolverActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mBackButton = new TransparentImageButton(act,R.drawable.ui_back,params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ScreenList.goBack(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor) { }
+  public void restorePreferences(SharedPreferences preferences) { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void backPhase()
+    {
+    SolverActivity act = mAct.get();
+    ObjectControl control = act.getControl();
+
+    if( mCurrMove>0 )
+      {
+      int[][] moves = transformMoves(mMoves[mCurrPhase],0,mCurrMove, false);
+      control.applyScrambles(moves);
+      mCurrMove = 0;
+      }
+    else if( mCurrPhase>0 )
+      {
+      mCurrPhase--;
+      mNumMoves = mMoves[mCurrPhase]==null ? 0 : mMoves[mCurrPhase].length;
+      mCurrMove = 0;
+      int[][] moves = transformMoves(mMoves[mCurrPhase],0,mNumMoves, false);
+      control.applyScrambles(moves);
+      }
+    else
+      {
+      mCurrPhase = mNumPhases-1;
+      mNumMoves = mMoves[mCurrPhase]==null ? 0 : mMoves[mCurrPhase].length;
+      mCurrMove = mNumMoves;
+      int[][] moves = transformMoves(mMoves, true);
+      control.applyScrambles(moves);
+      }
+
+    setText(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void nextPhase()
+    {
+    SolverActivity act = mAct.get();
+    ObjectControl control = act.getControl();
+
+    if( mCurrPhase<mNumPhases-1 )
+      {
+      glowCubits(mCubitsNotInvolved[mCurrPhase]);
+      int[][] moves = transformMoves(mMoves[mCurrPhase],mCurrMove,mNumMoves, true);
+      control.applyScrambles(moves);
+      mCurrPhase++;
+      mNumMoves = mMoves[mCurrPhase]==null ? 0 : mMoves[mCurrPhase].length;
+      mCurrMove = 0;
+      }
+    else if( mCurrMove<mNumMoves )
+      {
+      glowCubits(mCubitsNotInvolved[mCurrPhase]);
+      int[][] moves = transformMoves(mMoves[mCurrPhase],mCurrMove,mNumMoves, true);
+      control.applyScrambles(moves);
+      mCurrMove = mNumMoves;
+      }
+    else
+      {
+      mCurrPhase = 0;
+      mNumMoves = mMoves[mCurrPhase]==null ? 0 : mMoves[mCurrPhase].length;
+      mCurrMove = 0;
+      int[][] moves = transformMoves(mMoves, false);
+      control.applyScrambles(moves);
+      }
+
+    setText(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setText(SolverActivity act)
+    {
+    int currMove = 0;
+    int totalMove = 0;
+
+    if( mMoves!=null )
+      {
+      currMove = mCurrMove;
+      for(int p=0; p<mCurrPhase; p++) currMove  += (mMoves[p]==null ? 0: mMoves[p].length);
+      for(int p=0; p<mNumPhases; p++) totalMove += (mMoves[p]==null ? 0: mMoves[p].length);
+      }
+
+    final int cMove = currMove;
+    final int tMove = totalMove;
+
+    act.runOnUiThread(new Runnable()
+      {
+      @Override
+      public void run()
+        {
+        mMovesPhase.setText(mPhaseNames[mCurrPhase]+" "+mCurrMove+"/"+mNumMoves);
+        mMovesText.setText(cMove+"/"+tMove);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[][] transformMoves(int[][] moves, int start, int end, boolean front)
+    {
+    int mult = front ? 1:-1;
+    int len = end-start;
+    int[][] ret = new int[len][];
+
+    for(int m=0; m<len; m++)
+      {
+      int[] mv = moves[front ? start+m : end-1-m];
+      int[] rt = new int[3];
+      rt[0] = mv[0];
+      rt[1] = (1<<mv[1]);
+      rt[2] = mult*mv[2];
+      ret[m] = rt;
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[][] transformMoves(int[][][] moves, boolean front)
+    {
+    int len = moves.length;
+    int totalLen = 0;
+    for (int[][] move : moves) totalLen += (move==null ? 0 : move.length);
+
+    int[][] ret = new int[totalLen][];
+    int mult = front ? 1:-1;
+    int index = 0;
+
+    for(int m=0; m<len; m++)
+      {
+      int[][] mv = moves[front ? m : len-1-m];
+      int l = (mv==null ? 0 : mv.length);
+
+      for(int p=0; p<l; p++)
+        {
+        int[] mve = mv[front ? p : l-1-p];
+        int[] rt = new int[3];
+        rt[0] = mve[0];
+        rt[1] = (1<<mve[1]);
+        rt[2] = mult*mve[2];
+        ret[index++] = rt;
+        }
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void glowCubits(int[] cubits)
+    {
+    if( !mEffectWorking )
+      {
+      mEffectWorking = true;
+      SolverActivity act=mAct.get();
+      ObjectControl control = act.getControl();
+      TwistyObject object=control.getObject();
+      DistortedEffects effects=object.getObjectEffects();
+      effects.apply(mGlow);
+
+      MeshBase mesh=object.getObjectMesh();
+      mesh.setComponentsNotAffectedByPostprocessing(cubits);
+
+      mHaloAndRadiusDyn.resetToBeginning();
+      mColorDyn.resetToBeginning();
+      mGlow.notifyWhenFinished(this);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int[] computeCubitsNotInvolved(int[][] subphases, int numCubits)
+    {
+    int numCubitsInvolved = 0;
+    boolean[] involved = new boolean[numCubits];
+
+    for(int[] sub : subphases)
+      if( sub!=null )
+        for(int s : sub)
+          {
+          numCubitsInvolved++;
+          involved[s] = true;
+          }
+
+    int[] ret = new int[numCubits-numCubitsInvolved];
+    int index = 0;
+
+    for(int c=0; c<numCubits; c++)
+      if( !involved[c] ) ret[index++] = c;
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void backMove()
+    {
+    if( mMoves!=null && mCanMove )
+      {
+      SolverActivity act=mAct.get();
+
+      if( mCurrMove>0 )
+        {
+        mCanMove = false;
+        int[] move = mMoves[mCurrPhase][--mCurrMove];
+        ObjectControl control = act.getControl();
+        control.blockTouch(MOVES_PLACE_0);
+        control.addRotation(this, move[0], (1<<move[1]), -move[2], MILLIS_PER_DEGREE);
+        }
+      else if( mCurrPhase>0 )
+        {
+        mCurrPhase--;
+        mNumMoves = mMoves[mCurrPhase]==null ? 0 : mMoves[mCurrPhase].length;
+        mCurrMove = mNumMoves;
+        glowCubits(mCubitsNotInvolved[mCurrPhase]);
+        }
+      else
+        {
+        mCurrPhase = mNumPhases-1;
+        mNumMoves = mMoves[mCurrPhase]==null ? 0 : mMoves[mCurrPhase].length;
+        mCurrMove = mNumMoves;
+        int[][] moves = transformMoves(mMoves, true);
+        ObjectControl control = act.getControl();
+        control.applyScrambles(moves);
+        glowCubits(mCubitsNotInvolved[mCurrPhase]);
+        }
+
+      setText(act);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void nextMove()
+    {
+    if( mMoves!=null && mCanMove )
+      {
+      SolverActivity act=mAct.get();
+
+      if( mCurrMove<mNumMoves )
+        {
+        mCanMove = false;
+        int[] move = mMoves[mCurrPhase][mCurrMove++];
+        ObjectControl control = act.getControl();
+        control.blockTouch(MOVES_PLACE_1);
+        control.addRotation(this, move[0], (1<<move[1]), move[2], MILLIS_PER_DEGREE);
+        if( mCurrMove==mNumMoves && mCurrPhase==mNumPhases-1 ) glowCubits(mCubitsNotInvolved[mCurrPhase]);
+        }
+      else if( mCurrPhase<mNumPhases-1 )
+        {
+        mCurrPhase++;
+        mNumMoves = mMoves[mCurrPhase]==null ? 0 : mMoves[mCurrPhase].length;
+        mCurrMove = 0;
+        glowCubits(mCubitsNotInvolved[mCurrPhase-1]);
+        }
+      else
+        {
+        mCurrPhase = 0;
+        mNumMoves = mMoves[mCurrPhase]==null ? 0 : mMoves[mCurrPhase].length;
+        mCurrMove = 0;
+        int[][] moves = transformMoves(mMoves, false);
+        ObjectControl control = act.getControl();
+        control.applyScrambles(moves);
+        }
+
+      setText(act);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void updateNames(String[] names)
+    {
+    mPhaseNames = names;
+    mNumPhases = names.length;
+    mMoves = new int[mNumPhases][][];
+    mCubitsNotInvolved = new int[mNumPhases][];
+    mCanMove = true;
+    if( mAct!=null ) setSolution(null,0,null);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setSolution(int[][] moves, int phase, int[][] subphases)
+    {
+    SolverActivity act=mAct.get();
+
+    if( subphases!=null )
+      {
+      ObjectControl control=act.getControl();
+      TwistyObject object=control.getObject();
+      int numCubits=object.getNumCubits();
+      mCubitsNotInvolved[phase]= computeCubitsNotInvolved(subphases, numCubits);
+      }
+
+    mMoves[phase] = moves;
+    if( phase==0 ) mNumMoves = (moves==null ? 0 : moves.length);
+    mCurrPhase = 0;
+    mCurrMove = 0;
+
+    setText(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onActionFinished(final long effectID)
+    {
+    mCanMove = true;
+    SolverActivity act=mAct.get();
+    ObjectControl control = act.getControl();
+    control.unblockRotation();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinished(long id)
+    {
+    SolverActivity act=mAct.get();
+    ObjectControl control = act.getControl();
+    TwistyObject object=control.getObject();
+    DistortedEffects effects=object.getObjectEffects();
+    effects.abortById(id);
+    mEffectWorking = false;
+    }
+  }
diff --git a/src/main/java/org/distorted/solverui/ScreenSolver.java b/src/main/java/org/distorted/solverui/ScreenSolver.java
index ed77b52d..96934d5e 100644
--- a/src/main/java/org/distorted/solverui/ScreenSolver.java
+++ b/src/main/java/org/distorted/solverui/ScreenSolver.java
@@ -12,6 +12,7 @@ package org.distorted.solverui;
 import static org.distorted.objectlib.metadata.ListObjects.*;
 
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -30,6 +31,7 @@ import org.distorted.dialogs.RubikDialogSolvers;
 import org.distorted.helpers.TransparentImageButton;
 import org.distorted.main.MainActivity;
 import org.distorted.main.R;
+import org.distorted.objectlib.helpers.OperatingSystemInterface;
 import org.distorted.objectlib.main.ObjectControl;
 import org.distorted.objectlib.main.TwistyObject;
 import org.distorted.objectlib.metadata.ListObjects;
@@ -252,8 +254,10 @@ public class ScreenSolver extends ScreenAbstract
       if( len==1 ) // just one solver - simply launch it and start finding the solution
         {
         SolvingList list = SolvingList.getSolver(solverOrdinals[0]);
+        OperatingSystemInterface os = act.getInterface();
+        Resources res = act.getResources();
         TwistyObject object = act.getObject();
-        SolvingThread solver = new SolvingThread( act.getInterface(), act.getResources(), object, list );
+        SolvingThread solver = new SolvingThread( os,res,object,list );
         solver.start();
         return true;
         }
@@ -452,6 +456,29 @@ public class ScreenSolver extends ScreenAbstract
       }
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setSolved(final int[][] moves, final int phaseNumber, final int[][] subphases)
+    {
+    mSolving = false;
+    final SolverActivity act = mWeakAct.get();
+
+    if( act!=null )
+      {
+      act.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          if( phaseNumber==0 ) ScreenList.switchScreen(act, ScreenList.PHAS);
+          ScreenPhasedSolution screen = (ScreenPhasedSolution) ScreenList.PHAS.getScreenClass();
+          screen.setSolution(moves, phaseNumber,subphases);
+          if( moves!=null && moves.length>0 ) act.doNotShowDialogAnymore();
+          }
+        });
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void displayErrorDialog(String message)
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 606d213a..12f956fa 100755
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -208,7 +208,8 @@
     <string name="color_violet7">violette</string>
     <string name="color_grey7">graue</string>
 
-    <string name="solver_cube3_description">Ein nahezu perfekter, sofortiger, zweiphasiger 3x3x3-Löser.\nAutor: Herbert Kociemba.</string>
+    <string name="solver_cube3_description">Ein nahezu perfekter, sofortiger, zweiphasiger 3x3-Löser.\nAutor: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">Implementierung des Anfängeralgorithmus. 7 Phasen. Erzeugt Lösungen mit etwa 100 Zügen.\nAutor: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">Ein perfekter, sofortiger Löser.\nAutor: Leszek Koltunski.</string>
     <string name="solver_ivy_description">Ein perfekter, sofortiger Löser.\nAutor: Leszek Koltunski.</string>
     <string name="solver_cu232_description">Ein perfekter, sofortiger Löser.\nAutor: Leszek Koltunski.</string>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index 1c923b50..a5c974f7 100755
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -209,6 +209,7 @@
     <string name="color_grey7">grises</string>
 
     <string name="solver_cube3_description">Un 3x3x3 solucionador casi perfecto, instantáneo, de dos fases.\nAutor: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">Implementación del algoritmo para principiantes. 7 fases. Produce soluciones de aproximadamente 100 movimientos.\nAutor: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">Un solucionador perfecto e instantáneo.\nAutor: Leszek Koltunski.</string>
     <string name="solver_ivy_description">Un solucionador perfecto e instantáneo.\nAutor: Leszek Koltunski.</string>
     <string name="solver_cu232_description">Un solucionador perfecto e instantáneo.\nAutor: Leszek Koltunski.</string>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index 847d3131..77767a9b 100755
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -208,7 +208,8 @@
     <string name="color_violet7">violets</string>
     <string name="color_grey7">gris</string>
 
-    <string name="solver_cube3_description">Un solveur 3x3x3 biphasé presque parfait, instantané.\nAuteur: Herbert Kociemba.</string>
+    <string name="solver_cube3_description">Un solveur 3x3 biphasé presque parfait, instantané.\nAuteur: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">Mise en œuvre de l\'algorithme pour débutants. 7 phases. Produit des solutions longues d\'environ 100 coups.\nAuteur: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">Un solveur parfait et instantané.\nAuteur: Leszek Koltunski.</string>
     <string name="solver_ivy_description">Un solveur parfait et instantané.\nAuteur: Leszek Koltunski.</string>
     <string name="solver_cu232_description">Un solveur parfait et instantané.\nAuteur: Leszek Koltunski.</string>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index 6f936cbb..f4843976 100755
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -209,6 +209,7 @@
     <string name="color_grey7">グレー</string>
 
     <string name="solver_cube3_description">ほぼ完璧で瞬間的な 2 フェーズ 3x3x3 ソルバー。\n著者: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">初心者向けアルゴリズムの実装。7 フェーズ。約 100 手の長さのソリューションを生成します。\n著者: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">完璧で瞬時のソルバー。\n著者: Leszek Koltunski.</string>
     <string name="solver_ivy_description">完璧で瞬時のソルバー。\n著者: Leszek Koltunski.</string>
     <string name="solver_cu232_description">完璧で瞬時のソルバー。\n著者: Leszek Koltunski.</string>
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index ac57a47d..bd8eb68c 100755
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -209,6 +209,7 @@
     <string name="color_grey7">회색</string>
 
     <string name="solver_cube3_description">거의 완벽하고 즉각적인 2상 3x3x3 솔버입니다.\n작가: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">초보자 알고리즘 구현. 7단계. 약 100-이동 길이의 솔루션을 생성합니다.\n작가: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">완벽하고 즉각적인 솔버.\n작가: Leszek Koltunski.</string>
     <string name="solver_ivy_description">완벽하고 즉각적인 솔버.\n작가: Leszek Koltunski.</string>
     <string name="solver_cu232_description">완벽하고 즉각적인 솔버.\n작가: Leszek Koltunski.</string>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index e862f31b..83c9b4d8 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -209,6 +209,7 @@
     <string name="color_grey7">szare</string>
 
     <string name="solver_cube3_description">Natychmiastowy, prawie optymalny rozwiązywacz kostki 3x3x3.\nAutor: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">Implementacja algorytmu dla początkujących. 7 faz. Tworzy rozwiązania o długości około 100 ruchów.\nAutor: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">Optymalny, natychmiastowy rozwiązywacz.\nAutor: Leszek Koltunski.</string>
     <string name="solver_ivy_description">Optymalny, natychmiastowy rozwiązywacz.\nAutor: Leszek Koltunski.</string>
     <string name="solver_cu232_description">Optymalny, natychmiastowy rozwiązywacz.\nAutor: Leszek Koltunski.</string>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index 05c66319..52f9f07a 100755
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -209,6 +209,7 @@
     <string name="color_grey7">серых</string>
 
     <string name="solver_cube3_description">Практически идеальный, мгновенный, двухфазный решатель 3x3x3.\nАвтор: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">Реализация алгоритма для начинающих. 7 фаз. Выдает решения длиной около 100 ходов.\nАвтор: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">Идеальный, мгновенный решатель.\nАвтор: Leszek Koltunski.</string>
     <string name="solver_ivy_description">Идеальный, мгновенный решатель.\nАвтор: Leszek Koltunski.</string>
     <string name="solver_cu232_description">Идеальный, мгновенный решатель.\nАвтор: Leszek Koltunski.</string>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index d49b6d4f..8d34590c 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -215,6 +215,7 @@
     <string name="color_grey7">灰色的</string>
 
     <string name="solver_cube3_description">一个几乎完美的、瞬时的、两相的3x3x3解算器。\n作者: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">初学者算法的实现。7 个阶段。产生大约 100 步长的解决方案。\n作者: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">完美的即时求解器。\n作者: Leszek Koltunski.</string>
     <string name="solver_ivy_description">完美的即时求解器。\n作者: Leszek Koltunski.</string>
     <string name="solver_cu232_description">完美的即时求解器。\n作者: Leszek Koltunski.</string>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index 72eac3bc..a73acc00 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -209,6 +209,7 @@
     <string name="color_grey7">灰色的</string>
 
     <string name="solver_cube3_description">一個近乎完美的瞬時兩相 3x3x3 求解器。\n作者: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">初學者演算法的實作。 7 個階段。產生大約 100 個動作的長解。\n作者: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">完美的即時求解器。\n作者: Leszek Koltunski.</string>
     <string name="solver_ivy_description">完美的即時求解器。\n作者: Leszek Koltunski.</string>
     <string name="solver_cu232_description">完美的即時求解器。\n作者: Leszek Koltunski.</string>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 98a851a6..915c116c 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -232,6 +232,7 @@
     <string name="color_grey7">grey</string>
 
     <string name="solver_cube3_title" translatable="false">Kociemba Solver</string>
+    <string name="solver_3algo_title" translatable="false">3x3 Beginner</string>
     <string name="solver_pduo2_title" translatable="false">Pyraminx Duo Solver</string>
     <string name="solver_pyra3_title" translatable="false">Pyraminx Solver</string>
     <string name="solver_ivy_title" translatable="false">Ivy Solver</string>
@@ -246,6 +247,7 @@
     <string name="solver_pdia_title" translatable="false">Pyraminx Diamond Solver</string>
 
     <string name="solver_cube3_description">A near-perfect, instantaneous, two-phase 3x3x3 solver.\nAuthor: Herbert Kociemba.</string>
+    <string name="solver_3algo_description">Implementation of the beginner algorithm. 7 phases. Produces about 100 move long solutions.\nAuthor: Leszek Koltunski.</string>
     <string name="solver_pduo2_description">A perfect, instantaneous solver.\nAuthor: Leszek Koltunski.</string>
     <string name="solver_ivy_description">A perfect, instantaneous solver.\nAuthor: Leszek Koltunski.</string>
     <string name="solver_cu232_description">A perfect, instantaneous solver.\nAuthor: Leszek Koltunski.</string>
