commit 64975793022290e677db76343c229c9fba88d22d
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Wed Jan 22 00:44:45 2020 +0000

    RubikCube: renamed RubikSettingsEnum to BaseEffect and moved it to the 'effect' package.

diff --git a/src/main/java/org/distorted/effect/BaseEffect.java b/src/main/java/org/distorted/effect/BaseEffect.java
new file mode 100644
index 00000000..c6bf7fc2
--- /dev/null
+++ b/src/main/java/org/distorted/effect/BaseEffect.java
@@ -0,0 +1,259 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect;
+
+import java.lang.reflect.Method;
+import android.content.SharedPreferences;
+
+import org.distorted.effect.scramble.ScrambleEffect;
+import org.distorted.effect.sizechange.SizeChangeEffect;
+import org.distorted.effect.solve.SolveEffect;
+import org.distorted.magic.R;
+import org.distorted.magic.RubikRenderer;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BaseEffect
+  {
+  public enum Type
+    {
+    SIZECHANGE  ( 20, 1, R.string.sizechange_effect , SizeChangeEffect.class),
+    SOLVE       ( 20, 1, R.string.solve_effect      , SolveEffect.class     ),
+    SCRAMBLE    ( 20, 1, R.string.scramble_effect   , ScrambleEffect.class  ),
+    ;
+
+    private final int mDefaultPos, mDefaultType;
+    private final Class mClass;
+    private int mCurrentPos, mCurrentType;
+    private int mText;
+
+    Type(int dPos, int dType, int text, Class clazz )
+      {
+      mDefaultPos  = mCurrentPos = dPos;
+      mDefaultType = mCurrentType= dType;
+      mText        = text;
+      mClass       = clazz;
+      }
+
+    public static final int LENGTH = Type.values().length;
+    private static final Type[] types;  // copy the values() to a local variable so that we
+                                        // don't have to keep recreating the array every time
+    static
+      {
+      int i=0;
+
+      types= new Type[LENGTH];
+
+      for(Type type: Type.values())
+        {
+        types[i] = type;
+        i++;
+        }
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public int getText()
+      {
+      return mText;
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public int getCurrentPos()
+      {
+      return mCurrentPos;
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public int getCurrentType()
+      {
+      return mCurrentType;
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public void setCurrentPos(int pos)
+      {
+      mCurrentPos = pos;
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public void setCurrentType(int type)
+      {
+      mCurrentType = type;
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public void savePreferences(SharedPreferences.Editor editor)
+      {
+      String name = name();
+
+      editor.putInt(name+"_Pos" , mCurrentPos );
+      editor.putInt(name+"_Type", mCurrentType);
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public void restorePreferences(SharedPreferences preferences)
+      {
+      String name = name();
+
+      mCurrentPos  = preferences.getInt(name+"_Pos" , mDefaultPos );
+      mCurrentType = preferences.getInt(name+"_Type", mDefaultType);
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public String[] getNames()
+      {
+      Method method;
+
+      try
+        {
+        method = mClass.getDeclaredMethod("getNames");
+        }
+      catch(NoSuchMethodException ex)
+        {
+        android.util.Log.e("BaseEffect", mClass.getSimpleName()+": exception getting method: "+ex.getMessage());
+        method = null;
+        }
+
+      try
+        {
+        if( method!=null )
+          {
+          Object value = method.invoke(null);
+          return (String[]) value;
+          }
+        }
+      catch(Exception ex)
+        {
+        android.util.Log.e("BaseEffect", mClass.getSimpleName()+": exception invoking method: "+ex.getMessage());
+        }
+
+      return null;
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public static Type getType(int ordinal)
+      {
+      return types[ordinal];
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public static void enableEffects()
+      {
+      Method method;
+
+      for(Type type: values())
+        {
+        try
+          {
+          method = type.mClass.getDeclaredMethod("enableEffects");
+          }
+        catch(NoSuchMethodException ex)
+          {
+          android.util.Log.e("BaseEffect", type.mClass.getSimpleName()+": exception getting method: "+ex.getMessage());
+          method = null;
+          }
+
+        try
+          {
+          if( method!=null ) method.invoke(null);
+          }
+        catch(Exception ex)
+          {
+          android.util.Log.e("BaseEffect", type.mClass.getSimpleName()+": exception invoking method: "+ex.getMessage());
+          }
+        }
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public long startEffect(RubikRenderer renderer)
+      {
+      Method method1, method2;
+      BaseEffect baseEffect=null;
+
+      try
+        {
+        method1 = mClass.getDeclaredMethod("create", int.class);
+        }
+      catch(NoSuchMethodException ex)
+        {
+        android.util.Log.e("BaseEffect", mClass.getSimpleName()+": 1 exception getting method: "+ex.getMessage());
+        return -1;
+        }
+
+      try
+        {
+        if( method1!=null )
+          {
+          Object value = method1.invoke(null,mCurrentType);
+          baseEffect = (BaseEffect)value;
+          }
+        }
+      catch(Exception ex)
+        {
+        android.util.Log.e("BaseEffect", mClass.getSimpleName()+": 1 exception invoking method: "+ex.getMessage());
+        return -2;
+        }
+
+      try
+        {
+        method2 = mClass.getDeclaredMethod("start", int.class, RubikRenderer.class);
+        }
+      catch(NoSuchMethodException ex)
+        {
+        android.util.Log.e("BaseEffect", mClass.getSimpleName()+": 2 exception getting method: "+ex.getMessage());
+        return -3;
+        }
+
+      try
+        {
+        if( method2!=null )
+          {
+          Integer translated = translatePos(mCurrentPos)+1;
+          Object value = method2.invoke(baseEffect,translated,renderer);
+          return (Long)value;
+          }
+        }
+      catch(Exception ex)
+        {
+        android.util.Log.e("BaseEffect", mClass.getSimpleName()+": 2 exception invoking method: "+ex.getMessage());
+        }
+
+      return -4;
+      }
+
+  ////////////////////////////////////////////////////////////////////////////////
+
+    public static int translatePos(int pos)
+      {
+      return (pos/2)*100;
+      }
+    }
+  }
diff --git a/src/main/java/org/distorted/effect/ScrambleEffect.java b/src/main/java/org/distorted/effect/ScrambleEffect.java
deleted file mode 100644
index 59e3fd2b..00000000
--- a/src/main/java/org/distorted/effect/ScrambleEffect.java
+++ /dev/null
@@ -1,341 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.message.EffectListener;
-import org.distorted.magic.RubikCube;
-
-import java.lang.reflect.Method;
-import java.util.Random;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public abstract class ScrambleEffect implements EffectListener
-{
-  public enum Type
-    {
-    NONE         (ScrambleEffectNone.class        ),
-    ROTATIONS    (ScrambleEffectRotations.class   ),
-    ;
-
-    final Class<? extends ScrambleEffect> effect;
-
-    Type(Class<? extends ScrambleEffect> effect)
-      {
-      this.effect= effect;
-      }
-    }
-
-  private static int NUM_EFFECTS = Type.values().length;
-  private static final int FAKE_EFFECT_ID  = -3;
-  private static final Type[] types;
-
-  static
-    {
-    int i=0;
-    types = new Type[NUM_EFFECTS];
-
-    for(Type type: Type.values())
-      {
-      types[i++] = type;
-      }
-    }
-
-  private EffectListener mListener;
-  private int mEffectReturned;
-  private long mCurrentBaseEffectID;
-  private int mNumDoubleScramblesLeft, mNumScramblesLeft;
-  private int mLastVector;
-  private long mDurationSingleTurn;
-  private Random mRnd;
-  private RubikCube mCube;
-
-  Effect[] mNodeEffects;
-  int[] mNodeEffectPosition;
-  Effect[] mCubeEffects;
-  int[] mCubeEffectPosition;
-  int mCubeEffectNumber, mNodeEffectNumber;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  ScrambleEffect()
-    {
-    mRnd = new Random( System.currentTimeMillis() );
-    mLastVector = -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static String[] getNames()
-    {
-    String[] names = new String[NUM_EFFECTS];
-
-    for( int i=0; i<NUM_EFFECTS; i++)
-      {
-      names[i] = types[i].name();
-      }
-
-    return names;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static ScrambleEffect create(int ordinal) throws InstantiationException, IllegalAccessException
-    {
-    return types[ordinal].effect.newInstance();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  abstract void createEffects(int duration);
-  abstract void effectFinishedPlugin(final long effectID);
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void createBaseEffects(int duration, int numScrambles)
-    {
-    mNumScramblesLeft = numScrambles;
-
-    // compute how many out of 'numScrambles' are double turns.
-    mNumDoubleScramblesLeft=0;
-
-    for(int i=0; i<numScrambles; i++)
-      {
-      if( (mRnd.nextInt() % 3) == 0 )
-        {
-        mNumDoubleScramblesLeft++;
-        }
-      }
-
-    mDurationSingleTurn = duration/(mNumScramblesLeft+mNumDoubleScramblesLeft);
-
-    addNewScramble();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void addNewScramble()
-    {
-    if( mNumScramblesLeft>0 )
-      {
-      if( mLastVector == -1 )
-        {
-        switch(mRnd.nextInt(3))
-          {
-          case 0: mLastVector = RubikCube.VECTX; break;
-          case 1: mLastVector = RubikCube.VECTY; break;
-          case 2: mLastVector = RubikCube.VECTZ; break;
-          }
-        }
-      else
-        {
-        int newVector = mRnd.nextInt(2);
-
-        switch(mLastVector)
-          {
-          case RubikCube.VECTX: mLastVector = (newVector==0 ? RubikCube.VECTY: RubikCube.VECTZ); break;
-          case RubikCube.VECTY: mLastVector = (newVector==0 ? RubikCube.VECTX: RubikCube.VECTZ); break;
-          case RubikCube.VECTZ: mLastVector = (newVector==0 ? RubikCube.VECTX: RubikCube.VECTY); break;
-          }
-        }
-
-      int row  = mRnd.nextInt(mCube.getSize());
-      int angle= randomizeAngle();
-      int absAngle = (angle<0 ? -angle : angle);
-      long durationMillis =  absAngle*mDurationSingleTurn;
-
-      mNumScramblesLeft--;
-      if( absAngle==2 ) mNumDoubleScramblesLeft--;
-
-      if( mNumScramblesLeft==0 && mNumDoubleScramblesLeft!=0 )
-        {
-        android.util.Log.e("effect", "ERROR: "+mNumDoubleScramblesLeft);
-        }
-
-      mCurrentBaseEffectID = mCube.addNewRotation(mLastVector, row, angle*90, durationMillis, this );
-      }
-    else
-      {
-      mLastVector = -1;
-
-      if( mEffectReturned == mCubeEffectNumber+mNodeEffectNumber )
-        {
-        mListener.effectFinished(FAKE_EFFECT_ID);
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int randomizeAngle()
-    {
-    int random = mRnd.nextInt(mNumScramblesLeft);
-    int result = random<mNumDoubleScramblesLeft ? 2:1;
-    int sign   = mRnd.nextInt(2);
-
-    return sign==0 ? result : -result;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void effectFinished(final long effectID)
-    {
-    if( effectID == mCurrentBaseEffectID )
-      {
-      mCube.removeRotationNow();
-      addNewScramble();
-      return;
-      }
-
-    for(int i=0; i<mCubeEffectNumber; i++)
-      {
-      long id = mCubeEffects[i].getID();
-
-      if( effectID == id )
-        {
-        mEffectReturned++;
-        effectFinishedPlugin(effectID);
-
-        if( mEffectReturned == mCubeEffectNumber+mNodeEffectNumber )
-          {
-          disassignEffects();
-
-          if( mNumScramblesLeft==0 )
-            {
-            mListener.effectFinished(FAKE_EFFECT_ID);
-            }
-          }
-
-        return;
-        }
-      }
-
-    for(int i=0; i<mNodeEffectNumber; i++)
-      {
-      long id = mNodeEffects[i].getID();
-
-      if( effectID == id )
-        {
-        mEffectReturned++;
-        effectFinishedPlugin(effectID);
-
-        if( mEffectReturned == mCubeEffectNumber+mNodeEffectNumber )
-          {
-          disassignEffects();
-
-          if( mNumScramblesLeft==0 )
-            {
-            mListener.effectFinished(FAKE_EFFECT_ID);
-            }
-          }
-
-        return;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public long start(int duration, RubikCube cube, int numScrambles, EffectListener listener)
-    {
-    mCube     = cube;
-    mListener = listener;
-
-    createBaseEffects(duration, numScrambles);
-    createEffects(duration);
-
-    if( mCubeEffectNumber==0 && mNodeEffectNumber==0 )
-      {
-      throw new RuntimeException("Cube and Node Plugin Effects not created!");
-      }
-
-    assignEffects();
-
-    return FAKE_EFFECT_ID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void assignEffects()
-    {
-    for(int i=0; i<mCubeEffectNumber; i++)
-      {
-      mCube.apply(mCubeEffects[i],mCubeEffectPosition[i]);
-      mCubeEffects[i].notifyWhenFinished(this);
-      }
-
-    DistortedEffects nodeEffects = mCube.getEffects();
-
-    for(int i=0; i<mNodeEffectNumber; i++)
-      {
-      nodeEffects.apply(mNodeEffects[i],mNodeEffectPosition[i]);
-      mNodeEffects[i].notifyWhenFinished(this);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void disassignEffects()
-    {
-    for(int i=0; i<mCubeEffectNumber; i++)
-      {
-      mCube.remove(mCubeEffects[i].getID());
-      }
-
-    DistortedEffects nodeEffects = mCube.getEffects();
-
-    for(int i=0; i<mNodeEffectNumber; i++)
-      {
-      nodeEffects.abortById(mNodeEffects[i].getID());
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @SuppressWarnings("unused")
-  public static void enableEffects()
-    {
-    Method method;
-
-    for(Type type: Type.values())
-      {
-      try
-        {
-        method = type.effect.getDeclaredMethod("enable"); // enable not public, thus getDeclaredMethod
-        }
-      catch(NoSuchMethodException ex)
-        {
-        android.util.Log.e("ScrambleEffect", type.effect.getSimpleName()+": exception getting method: "+ex.getMessage());
-        method = null;
-        }
-
-      try
-        {
-        if( method!=null ) method.invoke(null);
-        }
-      catch(Exception ex)
-        {
-        android.util.Log.e("ScrambleEffect", type.effect.getSimpleName()+": exception invoking method: "+ex.getMessage());
-        }
-      }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/effect/ScrambleEffectNone.java b/src/main/java/org/distorted/effect/ScrambleEffectNone.java
deleted file mode 100644
index b8d5bf4f..00000000
--- a/src/main/java/org/distorted/effect/ScrambleEffectNone.java
+++ /dev/null
@@ -1,59 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Static3D;
-
-public class ScrambleEffectNone extends ScrambleEffect
-  {
-  public void createEffects(int duration)
-    {
-    Dynamic3D d0 = new Dynamic3D(1,0.5f);
-    d0.add(new Static3D(0,0,0));
-
-    mCubeEffectNumber   = 1;
-    mNodeEffectNumber   = 0;
-
-    mCubeEffectPosition = new int[] {-1};
-    mCubeEffects        = new Effect[mCubeEffectPosition.length];
-    mCubeEffects[0]     = new MatrixEffectMove(d0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void effectFinishedPlugin(final long effectID)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Enable all effects used in this Effect. Called by reflection from the parent class.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-
-    }
-  }
diff --git a/src/main/java/org/distorted/effect/ScrambleEffectRotations.java b/src/main/java/org/distorted/effect/ScrambleEffectRotations.java
deleted file mode 100644
index 381a93b3..00000000
--- a/src/main/java/org/distorted/effect/ScrambleEffectRotations.java
+++ /dev/null
@@ -1,101 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.effect.MatrixEffectQuaternion;
-import org.distorted.library.type.Dynamic;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.DynamicQuat;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-
-import java.util.Random;
-
-import static org.distorted.magic.RubikRenderer.TEXTURE_SIZE;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class ScrambleEffectRotations extends ScrambleEffect
-  {
-  private Random mRnd = new Random(0);
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void createEffects(int duration)
-    {
-    mCubeEffectNumber   = 2;
-    mNodeEffectNumber   = 0;
-    mCubeEffectPosition = new int[] {6,7};
-    mCubeEffects        = new Effect[mCubeEffectPosition.length];
-
-    mRnd.setSeed(System.currentTimeMillis());
-
-    DynamicQuat dq = new DynamicQuat(duration, 0.5f);
-    dq.setMode(Dynamic.MODE_PATH);
-    dq.add(new Static4D(0,0,0,1));
-    dq.add(generateNewRandomPoint());
-    dq.add(generateNewRandomPoint());
-    dq.add(generateNewRandomPoint());
-    dq.add(new Static4D(0,0,0,1));
-
-    mCubeEffects[0] = new MatrixEffectQuaternion(dq, new Static3D(0,0,0));
-
-    float Z = TEXTURE_SIZE/3;
-
-    Dynamic3D d0 = new Dynamic3D(duration, 0.5f);
-    d0.setMode(Dynamic.MODE_PATH);
-    d0.add(new Static3D( 0, 0, 0));
-    d0.add(new Static3D( 0, 0,-Z));
-    d0.add(new Static3D( 0, 0, 0));
-    mCubeEffects[1] = new MatrixEffectMove(d0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private Static4D generateNewRandomPoint()
-    {
-    float x = mRnd.nextFloat();
-    float y = mRnd.nextFloat();
-    float z = mRnd.nextFloat();
-    float w = mRnd.nextFloat();
-
-    float len = (float)Math.sqrt(x*x + y*y + z*z + w*w);
-
-    return new Static4D( x/len, y/len, z/len, w/len);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void effectFinishedPlugin(final long effectID)
-    {
-
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Enable all effects used in this Effect. Called by reflection from the parent class.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-
-    }
-  }
diff --git a/src/main/java/org/distorted/effect/SizeChangeEffect.java b/src/main/java/org/distorted/effect/SizeChangeEffect.java
deleted file mode 100644
index 81984751..00000000
--- a/src/main/java/org/distorted/effect/SizeChangeEffect.java
+++ /dev/null
@@ -1,273 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.library.message.EffectListener;
-import org.distorted.magic.RubikCube;
-
-import java.lang.reflect.Method;
-
-public abstract class SizeChangeEffect implements EffectListener
-{
-  public enum Type
-    {
-    NONE         (SizeChangeEffectNone.class        ),
-    TRANSPARENCY (SizeChangeEffectTransparency.class),
-    MOVE         (SizeChangeEffectMove.class        ),
-    ROUND        (SizeChangeEffectRound.class       ),
-    SCALE        (SizeChangeEffectScale.class       ),
-    ;
-
-    final Class<? extends SizeChangeEffect> effect;
-
-    Type(Class<? extends SizeChangeEffect> effect)
-      {
-      this.effect= effect;
-      }
-    }
-
-  private static int NUM_EFFECTS = Type.values().length;
-  private static final int NUM_PHASES  = 2;
-  private static final int FAKE_EFFECT_ID  = -1;
-  private static final Type[] types;
-
-  static
-    {
-    int i=0;
-    types = new Type[NUM_EFFECTS];
-
-    for(Type type: Type.values())
-      {
-      types[i++] = type;
-      }
-    }
-
-  private EffectListener mListener;
-  private int mDuration;
-  private int[] mEffectReturned;
-  private int[] mCubeEffectNumber, mNodeEffectNumber;
-  private int[] mEffectFinished;
-  private boolean[] mPhaseActive;
-  private RubikCube[] mCube;
-
-  DistortedScreen mScreen;
-  Effect[][] mCubeEffects;
-  int[][] mCubeEffectPosition;
-  Effect[][] mNodeEffects;
-  int[][] mNodeEffectPosition;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  SizeChangeEffect()
-    {
-    mPhaseActive        = new boolean[NUM_PHASES];
-    mEffectReturned     = new int[NUM_PHASES];
-    mCubeEffectNumber   = new int[NUM_PHASES];
-    mNodeEffectNumber   = new int[NUM_PHASES];
-    mEffectFinished     = new int[NUM_PHASES];
-    mCubeEffectPosition = new int[NUM_PHASES][];
-    mNodeEffectPosition = new int[NUM_PHASES][];
-    mCubeEffects        = new Effect[NUM_PHASES][];
-    mNodeEffects        = new Effect[NUM_PHASES][];
-    mCube               = new RubikCube[NUM_PHASES];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static String[] getNames()
-    {
-    String[] names = new String[NUM_EFFECTS];
-
-    for( int i=0; i<NUM_EFFECTS; i++)
-      {
-      names[i] = types[i].name();
-      }
-
-    return names;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static SizeChangeEffect create(int ordinal) throws InstantiationException, IllegalAccessException
-    {
-    return types[ordinal].effect.newInstance();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  abstract int createEffectsPhase0(int duration);
-  abstract int createEffectsPhase1(int duration);
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void effectFinished(final long effectID)
-    {
-    if( mPhaseActive[0] ) effectFinishedPhase(effectID,0);
-    if( mPhaseActive[1] ) effectFinishedPhase(effectID,1);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void effectFinishedPhase(final long effectID, int phase)
-    {
-    for(int i=0; i<mCubeEffectNumber[phase]; i++)
-      {
-      long id = mCubeEffects[phase][i].getID();
-
-      if( effectID == id )
-        {
-        effectReturned(phase);
-        mCube[phase].remove(id);
-        return;
-        }
-      }
-    for(int i=0; i<mNodeEffectNumber[phase]; i++)
-      {
-      long id = mNodeEffects[phase][i].getID();
-
-      if( effectID == id )
-        {
-        effectReturned(phase);
-        mCube[phase].getEffects().abortById(id);
-        return;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void effectReturned(int phase)
-    {
-    mEffectReturned[phase]++;
-
-    if( mEffectReturned[phase] == mEffectFinished[phase] )
-      {
-      switch(phase)
-        {
-        case 0: mPhaseActive[1] = true;
-                mEffectFinished[1] = createEffectsPhase1(mDuration);
-                assignEffects(1);
-                mScreen.attach(mCube[1]);
-                break;
-        case 1: mListener.effectFinished(FAKE_EFFECT_ID);
-                break;
-        }
-      }
-    if( mEffectReturned[phase] == mCubeEffectNumber[phase]+mNodeEffectNumber[phase] )
-      {
-      switch(phase)
-        {
-        case 0: mPhaseActive[0] = false;
-                mScreen.detach(mCube[0]);
-                break;
-        case 1: mPhaseActive[1] = false;
-                break;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public long start(int duration, DistortedScreen screen, RubikCube oldcube, RubikCube newcube, EffectListener listener)
-    {
-    mScreen   = screen;
-    mCube[0]  = oldcube;
-    mCube[1]  = newcube;
-    mListener = listener;
-    mDuration = duration;
-
-    if( oldcube!=null )
-      {
-      mPhaseActive[0] = true;
-      mEffectFinished[0] = createEffectsPhase0(mDuration);
-      assignEffects(0);
-      }
-    else
-      {
-      mPhaseActive[1] = true;
-      mEffectFinished[1] = createEffectsPhase1(mDuration);
-      assignEffects(1);
-      mScreen.attach(mCube[1]);
-      }
-
-    return FAKE_EFFECT_ID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void assignEffects(int phase)
-    {
-    mCubeEffectNumber[phase] = ( mCubeEffects[phase]!=null ) ? mCubeEffects[phase].length : 0;
-    mNodeEffectNumber[phase] = ( mNodeEffects[phase]!=null ) ? mNodeEffects[phase].length : 0;
-
-    if( mCubeEffectNumber[phase]==0 && mNodeEffectNumber[phase]==0 )
-      {
-      throw new RuntimeException("Cube and Node Effects ("+phase+" phase) both not created!");
-      }
-
-    for(int i=0; i<mCubeEffectNumber[phase]; i++)
-      {
-      mCube[phase].apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
-      mCubeEffects[phase][i].notifyWhenFinished(this);
-      }
-
-    DistortedEffects nodeEffects = mCube[phase].getEffects();
-
-    for(int i=0; i<mNodeEffectNumber[phase]; i++)
-      {
-      nodeEffects.apply(mNodeEffects[phase][i],mNodeEffectPosition[phase][i]);
-      mNodeEffects[phase][i].notifyWhenFinished(this);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static void enableEffects()
-    {
-    Method method;
-
-    for(Type type: Type.values())
-      {
-      try
-        {
-        method = type.effect.getDeclaredMethod("enable");  // enable not public, thus getDeclaredMethod
-        }
-      catch(NoSuchMethodException ex)
-        {
-        android.util.Log.e("SizeChangeEffect", type.effect.getSimpleName()+": exception getting method: "+ex.getMessage());
-        method = null;
-        }
-
-      try
-        {
-        if( method!=null ) method.invoke(null);
-        }
-      catch(Exception ex)
-        {
-        android.util.Log.e("SizeChangeEffect", type.effect.getSimpleName()+": exception invoking method: "+ex.getMessage());
-        }
-      }
-    }
-}
diff --git a/src/main/java/org/distorted/effect/SizeChangeEffectMove.java b/src/main/java/org/distorted/effect/SizeChangeEffectMove.java
deleted file mode 100644
index 2d8cbbec..00000000
--- a/src/main/java/org/distorted/effect/SizeChangeEffectMove.java
+++ /dev/null
@@ -1,77 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Static3D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class SizeChangeEffectMove extends SizeChangeEffect
-  {
-  public int createEffectsPhase0(int duration)
-    {
-    int w = mScreen.getWidth();
-    int h = mScreen.getHeight();
-    int xmove = w/2 + (w<h?w:h)/2;
-
-    mNodeEffectPosition[0] = new int[] {1};
-    mNodeEffects[0]        = new Effect[mNodeEffectPosition[0].length];
-
-    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
-    d0.add(new Static3D(    0,0,0));
-    d0.add(new Static3D(xmove,0,0));
-    mNodeEffects[0][0] = new MatrixEffectMove(d0);
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int createEffectsPhase1(int duration)
-    {
-    int w = mScreen.getWidth();
-    int h = mScreen.getHeight();
-    int xmove = w/2 + (w<h?w:h)/2;
-
-    mNodeEffectPosition[1] = new int[] {1};
-    mNodeEffects[1]        = new Effect[mNodeEffectPosition[1].length];
-
-    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
-    d0.add(new Static3D(-xmove,0,0));
-    d0.add(new Static3D(     0,0,0));
-    mNodeEffects[1][0] = new MatrixEffectMove(d0);
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Enable all effects used in this Effect. Called by reflection from the parent class.
-// Matrix Effects do not have to be enabled.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-
-    }
-  }
-
diff --git a/src/main/java/org/distorted/effect/SizeChangeEffectNone.java b/src/main/java/org/distorted/effect/SizeChangeEffectNone.java
deleted file mode 100644
index 635b17c8..00000000
--- a/src/main/java/org/distorted/effect/SizeChangeEffectNone.java
+++ /dev/null
@@ -1,65 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Static3D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class SizeChangeEffectNone extends SizeChangeEffect
-  {
-  public int createEffectsPhase0(int duration)
-    {
-    mCubeEffectPosition[0] = new int[] {-1};
-    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
-
-    Dynamic3D d0 = new Dynamic3D(1,0.5f);
-    d0.add(new Static3D(0,0,0));
-    mCubeEffects[0][0] = new MatrixEffectMove(d0);
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int createEffectsPhase1(int duration)
-    {
-    mCubeEffectPosition[1] = new int[] {-1};
-    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
-
-    Dynamic3D d0 = new Dynamic3D(1,0.5f);
-    d0.add(new Static3D(0,0,0));
-    mCubeEffects[1][0] = new MatrixEffectMove(d0);
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// enable all effects used in this Effect (here: none).  Called by reflection from the parent class.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-
-    }
-  }
diff --git a/src/main/java/org/distorted/effect/SizeChangeEffectRound.java b/src/main/java/org/distorted/effect/SizeChangeEffectRound.java
deleted file mode 100644
index b5f1d5a5..00000000
--- a/src/main/java/org/distorted/effect/SizeChangeEffectRound.java
+++ /dev/null
@@ -1,91 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.effect.MatrixEffectScale;
-import org.distorted.library.type.Dynamic;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Static3D;
-
-import static org.distorted.magic.RubikRenderer.TEXTURE_SIZE;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class SizeChangeEffectRound extends SizeChangeEffect
-  {
-  public int createEffectsPhase0(int duration)
-    {
-    float X = TEXTURE_SIZE/3;
-
-    mCubeEffectPosition[0] = new int[] {6,7};
-    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
-
-    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
-    d0.add(new Static3D( 1.00f, 1.00f, 1.00f));
-    d0.add(new Static3D( 0.01f, 0.01f, 0.01f));
-    mCubeEffects[0][0] = new MatrixEffectScale(d0);
-
-    Dynamic3D d1 = new Dynamic3D(duration/2, 0.5f);
-    d1.setMode(Dynamic.MODE_PATH);
-    d1.add(new Static3D( 0, 0, 0));
-    d1.add(new Static3D(+X, 0, 0));
-    d1.add(new Static3D( 0, 0, 0));
-    mCubeEffects[0][1] = new MatrixEffectMove(d1);
-
-    return 2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int createEffectsPhase1(int duration)
-    {
-    float X = TEXTURE_SIZE/3;
-
-    mCubeEffectPosition[1] = new int[] {6,7};
-    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
-
-    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
-    d0.add(new Static3D( 0.01f, 0.01f, 0.01f));
-    d0.add(new Static3D( 1.00f, 1.00f, 1.00f));
-    mCubeEffects[1][0] = new MatrixEffectScale(d0);
-
-    Dynamic3D d1 = new Dynamic3D(duration/2, 0.5f);
-    d1.setMode(Dynamic.MODE_PATH);
-    d1.add(new Static3D( 0, 0, 0));
-    d1.add(new Static3D(-X, 0, 0));
-    d1.add(new Static3D( 0, 0, 0));
-    mCubeEffects[1][1] = new MatrixEffectMove(d1);
-
-    return 2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Enable all effects used in this Effect. Called by reflection from the parent class.
-// Matrix Effects do not have to be enabled.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-
-    }
-  }
-
diff --git a/src/main/java/org/distorted/effect/SizeChangeEffectScale.java b/src/main/java/org/distorted/effect/SizeChangeEffectScale.java
deleted file mode 100644
index a734955a..00000000
--- a/src/main/java/org/distorted/effect/SizeChangeEffectScale.java
+++ /dev/null
@@ -1,69 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectScale;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Static3D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class SizeChangeEffectScale extends SizeChangeEffect
-  {
-  public int createEffectsPhase0(int duration)
-    {
-    mCubeEffectPosition[0] = new int[] {6};
-    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
-
-    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
-    d0.add(new Static3D(1.00f, 1.00f, 1.00f));
-    d0.add(new Static3D(0.01f, 0.01f, 0.01f));
-    mCubeEffects[0][0] = new MatrixEffectScale(d0);
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int createEffectsPhase1(int duration)
-    {
-    mCubeEffectPosition[1] = new int[] {6};
-    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
-
-    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
-    d0.add(new Static3D(0.01f, 0.01f, 0.01f));
-    d0.add(new Static3D(1.00f, 1.00f, 1.00f));
-    mCubeEffects[1][0] = new MatrixEffectScale(d0);
-
-    return 1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Enable all effects used in this Effect. Called by reflection from the parent class.
-// Matrix Effects do not have to be enabled.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-
-    }
-  }
-
diff --git a/src/main/java/org/distorted/effect/SizeChangeEffectTransparency.java b/src/main/java/org/distorted/effect/SizeChangeEffectTransparency.java
deleted file mode 100644
index e5c3710e..00000000
--- a/src/main/java/org/distorted/effect/SizeChangeEffectTransparency.java
+++ /dev/null
@@ -1,114 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.FragmentEffectAlpha;
-import org.distorted.library.effect.VertexEffectWave;
-import org.distorted.library.type.Dynamic1D;
-import org.distorted.library.type.Dynamic5D;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static5D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class SizeChangeEffectTransparency extends SizeChangeEffect
-  {
-  public int createEffectsPhase0(int duration)
-    {
-    mCubeEffectPosition[0] = new int[] {-1};
-    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
-
-    Dynamic1D d0 = new Dynamic1D(duration/2, 0.5f);
-    d0.add(new Static1D(1.0f));
-    d0.add(new Static1D(0.0f));
-    mCubeEffects[0][0] = new FragmentEffectAlpha(d0);
-
-    mNodeEffectPosition[0] = new int[] {-1};
-    mNodeEffects[0]        = new Effect[mNodeEffectPosition[0].length];
-
-    int w = mScreen.getWidth();
-    int h = mScreen.getHeight();
-    int min = w<h ? w:h;
-
-    float init_amplitude = 0.0f;
-    float end_amplitude  = min/15.0f;
-    float length         = min/15.0f;
-    float init_phase     = 360.0f;
-    float end_phase      = 0.0f;
-    float alpha          = 30.0f;
-    float beta           = 90.0f;
-
-    Dynamic5D d1 = new Dynamic5D(duration/2, 0.5f);
-    d1.add(new Static5D( init_amplitude, length, init_phase, alpha, beta) );
-    d1.add(new Static5D( end_amplitude , length, end_phase , alpha, beta) );
-    Static3D center = new Static3D(min*0.5f,min*0.5f,0);
-    mNodeEffects[0][0] = new VertexEffectWave(d1, center, null);
-
-    return 2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int createEffectsPhase1(int duration)
-    {
-    mCubeEffectPosition[1] = new int[] {-1};
-    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
-
-    Dynamic1D d0 = new Dynamic1D(duration/2, 0.5f);
-    d0.add(new Static1D(0.0f));
-    d0.add(new Static1D(1.0f));
-    mCubeEffects[1][0] = new FragmentEffectAlpha(d0);
-
-    mNodeEffectPosition[1] = new int[] {-1};
-    mNodeEffects[1]        = new Effect[mNodeEffectPosition[1].length];
-
-    int w = mScreen.getWidth();
-    int h = mScreen.getHeight();
-    int min = w<h ? w:h;
-
-    float init_amplitude = min/15.0f;
-    float end_amplitude  = 0.0f;
-    float length         = min/15.0f;
-    float init_phase     = 0.0f;
-    float end_phase      = 360.0f;
-    float alpha          = 30.0f;
-    float beta           = 90.0f;
-
-    Dynamic5D d1 = new Dynamic5D(duration/2, 0.5f);
-    d1.add(new Static5D( init_amplitude, length, init_phase, alpha, beta) );
-    d1.add(new Static5D( end_amplitude , length, end_phase , alpha, beta) );
-    Static3D center = new Static3D(min*0.5f,min*0.5f,0);
-    mNodeEffects[1][0] = new VertexEffectWave(d1, center, null);
-
-    return 2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Enable all effects used in this Effect. Called by reflection from the parent class.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-    FragmentEffectAlpha.enable();
-    VertexEffectWave.enable();
-    }
-  }
diff --git a/src/main/java/org/distorted/effect/SolveEffect.java b/src/main/java/org/distorted/effect/SolveEffect.java
deleted file mode 100644
index 1ebe4bac..00000000
--- a/src/main/java/org/distorted/effect/SolveEffect.java
+++ /dev/null
@@ -1,235 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.library.message.EffectListener;
-import org.distorted.magic.RubikCube;
-
-import java.lang.reflect.Method;
-
-public abstract class SolveEffect implements EffectListener
-{
-  public enum Type
-    {
-    NONE   (SolveEffectNone.class),
-    SPIN   (SolveEffectSpin.class),
-    ;
-
-    final Class<? extends SolveEffect> effect;
-
-    Type(Class<? extends SolveEffect> effect)
-      {
-      this.effect = effect;
-      }
-    }
-
-  private static final int NUM_EFFECTS = Type.values().length;
-  private static final int NUM_PHASES  = 2;
-  private static final int FAKE_EFFECT_ID = -2;
-  private static final Type[] types;
-
-  static
-    {
-    int i=0;
-    types = new Type[NUM_EFFECTS];
-
-    for(Type type: Type.values())
-      {
-      types[i++] = type;
-      }
-    }
-
-  private EffectListener mListener;
-  private int mDuration;
-  private int mEffectReturned;
-  private int[] mCubeEffectNumber, mNodeEffectNumber;
-  private int mPhase;
-
-  RubikCube mCube;
-  DistortedScreen mScreen;
-  Effect[][] mCubeEffects;
-  int[][] mCubeEffectPosition;
-  Effect[][] mNodeEffects;
-  int[][] mNodeEffectPosition;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  SolveEffect()
-    {
-    mPhase        =  0;
-
-    mCubeEffectNumber   = new int[NUM_PHASES];
-    mNodeEffectNumber   = new int[NUM_PHASES];
-    mCubeEffectPosition = new int[NUM_PHASES][];
-    mNodeEffectPosition = new int[NUM_PHASES][];
-    mCubeEffects        = new Effect[NUM_PHASES][];
-    mNodeEffects        = new Effect[NUM_PHASES][];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static String[] getNames()
-    {
-    String[] names = new String[NUM_EFFECTS];
-
-    for( int i=0; i<NUM_EFFECTS; i++)
-      {
-      names[i] = types[i].name();
-      }
-
-    return names;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public static SolveEffect create(int ordinal) throws InstantiationException, IllegalAccessException
-    {
-    return types[ordinal].effect.newInstance();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  abstract void createEffectsPhase0(int duration);
-  abstract void createEffectsPhase1(int duration);
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void effectFinished(final long effectID)
-    {
-    int total = mCubeEffectNumber[mPhase]+mNodeEffectNumber[mPhase];
-
-    for(int i=0; i<mCubeEffectNumber[mPhase]; i++)
-      {
-      long id = mCubeEffects[mPhase][i].getID();
-
-      if( effectID == id )
-        {
-        if( ++mEffectReturned == total ) effectAction(mPhase);
-        mCube.remove(id);
-        return;
-        }
-      }
-    for(int i=0; i<mNodeEffectNumber[mPhase]; i++)
-      {
-      long id = mNodeEffects[mPhase][i].getID();
-
-      if( effectID == id )
-        {
-        if( ++mEffectReturned == total ) effectAction(mPhase);
-        mCube.getEffects().abortById(id);
-        return;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void effectAction(int phase)
-    {
-    switch(phase)
-      {
-      case 0: mEffectReturned = 0;
-              mPhase          = 1;
-              mCube.solve();
-              createEffectsPhase1(mDuration);
-              assignEffects(mPhase);
-              break;
-      case 1: mListener.effectFinished(FAKE_EFFECT_ID);
-              break;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public long start(int duration, DistortedScreen screen, RubikCube cube, EffectListener listener)
-    {
-    mScreen   = screen;
-    mCube     = cube;
-    mListener = listener;
-    mDuration = duration;
-
-    createEffectsPhase0(mDuration);
-    assignEffects(mPhase);
-
-    return FAKE_EFFECT_ID;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void assignEffects(int phase)
-    {
-    mCubeEffectNumber[phase] = ( mCubeEffects[phase]!=null ) ? mCubeEffects[phase].length : 0;
-    mNodeEffectNumber[phase] = ( mNodeEffects[phase]!=null ) ? mNodeEffects[phase].length : 0;
-
-    if( mCubeEffectNumber[phase]==0 && mNodeEffectNumber[phase]==0 )
-      {
-      throw new RuntimeException("Cube and Node Effects ("+phase+" phase) both not created!");
-      }
-
-    for(int i=0; i<mCubeEffectNumber[phase]; i++)
-      {
-      mCube.apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
-      mCubeEffects[phase][i].notifyWhenFinished(this);
-      }
-
-    DistortedEffects nodeEffects = mCube.getEffects();
-
-    for(int i=0; i<mNodeEffectNumber[phase]; i++)
-      {
-      nodeEffects.apply(mNodeEffects[phase][i],mNodeEffectPosition[phase][i]);
-      mNodeEffects[phase][i].notifyWhenFinished(this);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  @SuppressWarnings("unused")
-  public static void enableEffects()
-    {
-    Method method;
-
-    for(Type type: Type.values())
-      {
-      try
-        {
-        method = type.effect.getDeclaredMethod("enable"); // enable not public, thus getDeclaredMethod
-        }
-      catch(NoSuchMethodException ex)
-        {
-        android.util.Log.e("SolveEffect", type.effect.getSimpleName()+": exception getting method: "+ex.getMessage());
-        method = null;
-        }
-
-      try
-        {
-        if( method!=null ) method.invoke(null);
-        }
-      catch(Exception ex)
-        {
-        android.util.Log.e("SolveEffect", type.effect.getSimpleName()+": exception invoking method: "+ex.getMessage());
-        }
-      }
-    }
-}
diff --git a/src/main/java/org/distorted/effect/SolveEffectNone.java b/src/main/java/org/distorted/effect/SolveEffectNone.java
deleted file mode 100644
index 2f09aaea..00000000
--- a/src/main/java/org/distorted/effect/SolveEffectNone.java
+++ /dev/null
@@ -1,61 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectMove;
-import org.distorted.library.type.Dynamic3D;
-import org.distorted.library.type.Static3D;
-
-public class SolveEffectNone extends SolveEffect
-  {
-  public void createEffectsPhase0(int duration)
-    {
-    Dynamic3D d0 = new Dynamic3D(1,0.5f);
-    d0.add(new Static3D(0,0,0));
-
-    mCubeEffectPosition[0] = new int[] {-1};
-    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
-    mCubeEffects[0][0]     = new MatrixEffectMove(d0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void createEffectsPhase1(int duration)
-    {
-    Dynamic3D d0 = new Dynamic3D(1,0.5f);
-    d0.add(new Static3D(0,0,0));
-
-    mCubeEffectPosition[1]  = new int[] {-1};
-    mCubeEffects[1]         = new Effect[mCubeEffectPosition[1].length];
-    mCubeEffects[1][0]      = new MatrixEffectMove(d0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Enable all effects used in this Effect. Called by reflection from the parent class.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-
-    }
-  }
diff --git a/src/main/java/org/distorted/effect/SolveEffectSpin.java b/src/main/java/org/distorted/effect/SolveEffectSpin.java
deleted file mode 100644
index 3ae430c9..00000000
--- a/src/main/java/org/distorted/effect/SolveEffectSpin.java
+++ /dev/null
@@ -1,81 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2019 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.effect;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-import org.distorted.library.effect.Effect;
-import org.distorted.library.effect.MatrixEffectRotate;
-import org.distorted.library.type.Dynamic;
-import org.distorted.library.type.Dynamic1D;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static3D;
-
-public class SolveEffectSpin extends SolveEffect
-  {
-  public void createEffectsPhase0(int duration)
-    {
-    mCubeEffectPosition[0] = new int[] {3};
-    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
-
-    Static3D axis  = new Static3D(1,0,0);
-    Static3D center= new Static3D(0,0,0);
-
-    Dynamic1D d0 = new Dynamic1D(duration/2, 1.0f);
-    d0.setMode(Dynamic.MODE_JUMP);
-    d0.setConvexity(0.0f);          // otherwise speed of the rotation would be strangely uneven
-    d0.add(new Static1D( 0*36));
-    d0.add(new Static1D( 1*36));
-    d0.add(new Static1D( 3*36));
-    d0.add(new Static1D( 6*36));
-    d0.add(new Static1D(10*36));
-    mCubeEffects[0][0] = new MatrixEffectRotate(d0,axis,center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void createEffectsPhase1(int duration)
-    {
-    mCubeEffectPosition[1] = new int[] {3};
-    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
-
-    Static3D axis  = new Static3D(1,0,0);
-    Static3D center= new Static3D(0,0,0);
-
-    Dynamic1D d1 = new Dynamic1D(duration/2, 1.0f);
-    d1.setMode(Dynamic.MODE_JUMP);
-    d1.setConvexity(0.0f);
-    d1.add(new Static1D( 0*36));
-    d1.add(new Static1D( 4*36));
-    d1.add(new Static1D( 7*36));
-    d1.add(new Static1D( 9*36));
-    d1.add(new Static1D(10*36));
-    mCubeEffects[1][0] = new MatrixEffectRotate(d1,axis,center);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Enable all effects used in this Effect. Called by reflection from the parent class.
-
-  @SuppressWarnings("unused")
-  static void enable()
-    {
-
-    }
-  }
diff --git a/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java b/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
new file mode 100644
index 00000000..a4f63f06
--- /dev/null
+++ b/src/main/java/org/distorted/effect/scramble/ScrambleEffect.java
@@ -0,0 +1,345 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.scramble;
+
+import org.distorted.effect.BaseEffect;
+import org.distorted.library.effect.Effect;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.message.EffectListener;
+import org.distorted.magic.RubikCube;
+import org.distorted.magic.RubikRenderer;
+
+import java.lang.reflect.Method;
+import java.util.Random;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class ScrambleEffect extends BaseEffect implements EffectListener
+{
+  public enum Type
+    {
+    NONE         (ScrambleEffectNone.class        ),
+    ROTATIONS    (ScrambleEffectRotations.class   ),
+    ;
+
+    final Class<? extends ScrambleEffect> effect;
+
+    Type(Class<? extends ScrambleEffect> effect)
+      {
+      this.effect= effect;
+      }
+    }
+
+  private static int NUM_EFFECTS = Type.values().length;
+  private static final int FAKE_EFFECT_ID  = -3;
+  private static final Type[] types;
+
+  static
+    {
+    int i=0;
+    types = new Type[NUM_EFFECTS];
+
+    for(Type type: Type.values())
+      {
+      types[i++] = type;
+      }
+    }
+
+  private EffectListener mListener;
+  private int mEffectReturned;
+  private long mCurrentBaseEffectID;
+  private int mNumDoubleScramblesLeft, mNumScramblesLeft;
+  private int mLastVector;
+  private long mDurationSingleTurn;
+  private Random mRnd;
+  private RubikCube mCube;
+
+  Effect[] mNodeEffects;
+  int[] mNodeEffectPosition;
+  Effect[] mCubeEffects;
+  int[] mCubeEffectPosition;
+  int mCubeEffectNumber, mNodeEffectNumber;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  ScrambleEffect()
+    {
+    mRnd = new Random( System.currentTimeMillis() );
+    mLastVector = -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static String[] getNames()
+    {
+    String[] names = new String[NUM_EFFECTS];
+
+    for( int i=0; i<NUM_EFFECTS; i++)
+      {
+      names[i] = types[i].name();
+      }
+
+    return names;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static ScrambleEffect create(int ordinal) throws InstantiationException, IllegalAccessException
+    {
+    return types[ordinal].effect.newInstance();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract void createEffects(int duration);
+  abstract void effectFinishedPlugin(final long effectID);
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void createBaseEffects(int duration, int numScrambles)
+    {
+    mNumScramblesLeft = numScrambles;
+
+    // compute how many out of 'numScrambles' are double turns.
+    mNumDoubleScramblesLeft=0;
+
+    for(int i=0; i<numScrambles; i++)
+      {
+      if( (mRnd.nextInt() % 3) == 0 )
+        {
+        mNumDoubleScramblesLeft++;
+        }
+      }
+
+    mDurationSingleTurn = duration/(mNumScramblesLeft+mNumDoubleScramblesLeft);
+
+    addNewScramble();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void addNewScramble()
+    {
+    if( mNumScramblesLeft>0 )
+      {
+      if( mLastVector == -1 )
+        {
+        switch(mRnd.nextInt(3))
+          {
+          case 0: mLastVector = RubikCube.VECTX; break;
+          case 1: mLastVector = RubikCube.VECTY; break;
+          case 2: mLastVector = RubikCube.VECTZ; break;
+          }
+        }
+      else
+        {
+        int newVector = mRnd.nextInt(2);
+
+        switch(mLastVector)
+          {
+          case RubikCube.VECTX: mLastVector = (newVector==0 ? RubikCube.VECTY: RubikCube.VECTZ); break;
+          case RubikCube.VECTY: mLastVector = (newVector==0 ? RubikCube.VECTX: RubikCube.VECTZ); break;
+          case RubikCube.VECTZ: mLastVector = (newVector==0 ? RubikCube.VECTX: RubikCube.VECTY); break;
+          }
+        }
+
+      int row  = mRnd.nextInt(mCube.getSize());
+      int angle= randomizeAngle();
+      int absAngle = (angle<0 ? -angle : angle);
+      long durationMillis =  absAngle*mDurationSingleTurn;
+
+      mNumScramblesLeft--;
+      if( absAngle==2 ) mNumDoubleScramblesLeft--;
+
+      if( mNumScramblesLeft==0 && mNumDoubleScramblesLeft!=0 )
+        {
+        android.util.Log.e("effect", "ERROR: "+mNumDoubleScramblesLeft);
+        }
+
+      mCurrentBaseEffectID = mCube.addNewRotation(mLastVector, row, angle*90, durationMillis, this );
+      }
+    else
+      {
+      mLastVector = -1;
+
+      if( mEffectReturned == mCubeEffectNumber+mNodeEffectNumber )
+        {
+        mListener.effectFinished(FAKE_EFFECT_ID);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int randomizeAngle()
+    {
+    int random = mRnd.nextInt(mNumScramblesLeft);
+    int result = random<mNumDoubleScramblesLeft ? 2:1;
+    int sign   = mRnd.nextInt(2);
+
+    return sign==0 ? result : -result;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinished(final long effectID)
+    {
+    if( effectID == mCurrentBaseEffectID )
+      {
+      mCube.removeRotationNow();
+      addNewScramble();
+      return;
+      }
+
+    for(int i=0; i<mCubeEffectNumber; i++)
+      {
+      long id = mCubeEffects[i].getID();
+
+      if( effectID == id )
+        {
+        mEffectReturned++;
+        effectFinishedPlugin(effectID);
+
+        if( mEffectReturned == mCubeEffectNumber+mNodeEffectNumber )
+          {
+          disassignEffects();
+
+          if( mNumScramblesLeft==0 )
+            {
+            mListener.effectFinished(FAKE_EFFECT_ID);
+            }
+          }
+
+        return;
+        }
+      }
+
+    for(int i=0; i<mNodeEffectNumber; i++)
+      {
+      long id = mNodeEffects[i].getID();
+
+      if( effectID == id )
+        {
+        mEffectReturned++;
+        effectFinishedPlugin(effectID);
+
+        if( mEffectReturned == mCubeEffectNumber+mNodeEffectNumber )
+          {
+          disassignEffects();
+
+          if( mNumScramblesLeft==0 )
+            {
+            mListener.effectFinished(FAKE_EFFECT_ID);
+            }
+          }
+
+        return;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long start(int duration, RubikRenderer renderer)
+    {
+    mCube     = renderer.getCube();
+    mListener = renderer;
+
+    int numScrambles = renderer.getNumScrambles();
+
+    createBaseEffects(numScrambles*duration, numScrambles);
+    createEffects(numScrambles*duration);
+
+    if( mCubeEffectNumber==0 && mNodeEffectNumber==0 )
+      {
+      throw new RuntimeException("Cube and Node Plugin Effects not created!");
+      }
+
+    assignEffects();
+
+    return FAKE_EFFECT_ID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void assignEffects()
+    {
+    for(int i=0; i<mCubeEffectNumber; i++)
+      {
+      mCube.apply(mCubeEffects[i],mCubeEffectPosition[i]);
+      mCubeEffects[i].notifyWhenFinished(this);
+      }
+
+    DistortedEffects nodeEffects = mCube.getEffects();
+
+    for(int i=0; i<mNodeEffectNumber; i++)
+      {
+      nodeEffects.apply(mNodeEffects[i],mNodeEffectPosition[i]);
+      mNodeEffects[i].notifyWhenFinished(this);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void disassignEffects()
+    {
+    for(int i=0; i<mCubeEffectNumber; i++)
+      {
+      mCube.remove(mCubeEffects[i].getID());
+      }
+
+    DistortedEffects nodeEffects = mCube.getEffects();
+
+    for(int i=0; i<mNodeEffectNumber; i++)
+      {
+      nodeEffects.abortById(mNodeEffects[i].getID());
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @SuppressWarnings("unused")
+  public static void enableEffects()
+    {
+    Method method;
+
+    for(Type type: Type.values())
+      {
+      try
+        {
+        method = type.effect.getDeclaredMethod("enable"); // enable not public, thus getDeclaredMethod
+        }
+      catch(NoSuchMethodException ex)
+        {
+        android.util.Log.e("ScrambleEffect", type.effect.getSimpleName()+": exception getting method: "+ex.getMessage());
+        method = null;
+        }
+
+      try
+        {
+        if( method!=null ) method.invoke(null);
+        }
+      catch(Exception ex)
+        {
+        android.util.Log.e("ScrambleEffect", type.effect.getSimpleName()+": exception invoking method: "+ex.getMessage());
+        }
+      }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/distorted/effect/scramble/ScrambleEffectNone.java b/src/main/java/org/distorted/effect/scramble/ScrambleEffectNone.java
new file mode 100644
index 00000000..6e5fd295
--- /dev/null
+++ b/src/main/java/org/distorted/effect/scramble/ScrambleEffectNone.java
@@ -0,0 +1,59 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.scramble;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static3D;
+
+public class ScrambleEffectNone extends ScrambleEffect
+  {
+  public void createEffects(int duration)
+    {
+    Dynamic3D d0 = new Dynamic3D(1,0.5f);
+    d0.add(new Static3D(0,0,0));
+
+    mCubeEffectNumber   = 1;
+    mNodeEffectNumber   = 0;
+
+    mCubeEffectPosition = new int[] {-1};
+    mCubeEffects        = new Effect[mCubeEffectPosition.length];
+    mCubeEffects[0]     = new MatrixEffectMove(d0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinishedPlugin(final long effectID)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/effect/scramble/ScrambleEffectRotations.java b/src/main/java/org/distorted/effect/scramble/ScrambleEffectRotations.java
new file mode 100644
index 00000000..7d7d9303
--- /dev/null
+++ b/src/main/java/org/distorted/effect/scramble/ScrambleEffectRotations.java
@@ -0,0 +1,101 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.scramble;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.type.Dynamic;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.DynamicQuat;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+
+import java.util.Random;
+
+import static org.distorted.magic.RubikRenderer.TEXTURE_SIZE;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ScrambleEffectRotations extends ScrambleEffect
+  {
+  private Random mRnd = new Random(0);
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void createEffects(int duration)
+    {
+    mCubeEffectNumber   = 2;
+    mNodeEffectNumber   = 0;
+    mCubeEffectPosition = new int[] {6,7};
+    mCubeEffects        = new Effect[mCubeEffectPosition.length];
+
+    mRnd.setSeed(System.currentTimeMillis());
+
+    DynamicQuat dq = new DynamicQuat(duration, 0.5f);
+    dq.setMode(Dynamic.MODE_PATH);
+    dq.add(new Static4D(0,0,0,1));
+    dq.add(generateNewRandomPoint());
+    dq.add(generateNewRandomPoint());
+    dq.add(generateNewRandomPoint());
+    dq.add(new Static4D(0,0,0,1));
+
+    mCubeEffects[0] = new MatrixEffectQuaternion(dq, new Static3D(0,0,0));
+
+    float Z = TEXTURE_SIZE/3;
+
+    Dynamic3D d0 = new Dynamic3D(duration, 0.5f);
+    d0.setMode(Dynamic.MODE_PATH);
+    d0.add(new Static3D( 0, 0, 0));
+    d0.add(new Static3D( 0, 0,-Z));
+    d0.add(new Static3D( 0, 0, 0));
+    mCubeEffects[1] = new MatrixEffectMove(d0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private Static4D generateNewRandomPoint()
+    {
+    float x = mRnd.nextFloat();
+    float y = mRnd.nextFloat();
+    float z = mRnd.nextFloat();
+    float w = mRnd.nextFloat();
+
+    float len = (float)Math.sqrt(x*x + y*y + z*z + w*w);
+
+    return new Static4D( x/len, y/len, z/len, w/len);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinishedPlugin(final long effectID)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java
new file mode 100644
index 00000000..0d6c1621
--- /dev/null
+++ b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffect.java
@@ -0,0 +1,275 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.sizechange;
+
+import org.distorted.effect.BaseEffect;
+import org.distorted.library.effect.Effect;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.message.EffectListener;
+import org.distorted.magic.RubikCube;
+import org.distorted.magic.RubikRenderer;
+
+import java.lang.reflect.Method;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class SizeChangeEffect extends BaseEffect implements EffectListener
+{
+  public enum Type
+    {
+    NONE         (SizeChangeEffectNone.class        ),
+    TRANSPARENCY (SizeChangeEffectTransparency.class),
+    MOVE         (SizeChangeEffectMove.class        ),
+    ROUND        (SizeChangeEffectRound.class       ),
+    SCALE        (SizeChangeEffectScale.class       ),
+    ;
+
+    final Class<? extends SizeChangeEffect> effect;
+
+    Type(Class<? extends SizeChangeEffect> effect)
+      {
+      this.effect= effect;
+      }
+    }
+
+  private static int NUM_EFFECTS = Type.values().length;
+  private static final int NUM_PHASES  = 2;
+  private static final int FAKE_EFFECT_ID  = -1;
+  private static final Type[] types;
+
+  static
+    {
+    int i=0;
+    types = new Type[NUM_EFFECTS];
+
+    for(Type type: Type.values())
+      {
+      types[i++] = type;
+      }
+    }
+
+  private EffectListener mListener;
+  private int mDuration;
+  private int[] mEffectReturned;
+  private int[] mCubeEffectNumber, mNodeEffectNumber;
+  private int[] mEffectFinished;
+  private boolean[] mPhaseActive;
+  private RubikCube[] mCube;
+
+  DistortedScreen mScreen;
+  Effect[][] mCubeEffects;
+  int[][] mCubeEffectPosition;
+  Effect[][] mNodeEffects;
+  int[][] mNodeEffectPosition;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  SizeChangeEffect()
+    {
+    mPhaseActive        = new boolean[NUM_PHASES];
+    mEffectReturned     = new int[NUM_PHASES];
+    mCubeEffectNumber   = new int[NUM_PHASES];
+    mNodeEffectNumber   = new int[NUM_PHASES];
+    mEffectFinished     = new int[NUM_PHASES];
+    mCubeEffectPosition = new int[NUM_PHASES][];
+    mNodeEffectPosition = new int[NUM_PHASES][];
+    mCubeEffects        = new Effect[NUM_PHASES][];
+    mNodeEffects        = new Effect[NUM_PHASES][];
+    mCube               = new RubikCube[NUM_PHASES];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static String[] getNames()
+    {
+    String[] names = new String[NUM_EFFECTS];
+
+    for( int i=0; i<NUM_EFFECTS; i++)
+      {
+      names[i] = types[i].name();
+      }
+
+    return names;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static SizeChangeEffect create(int ordinal) throws InstantiationException, IllegalAccessException
+    {
+    return types[ordinal].effect.newInstance();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract int createEffectsPhase0(int duration);
+  abstract int createEffectsPhase1(int duration);
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinished(final long effectID)
+    {
+    if( mPhaseActive[0] ) effectFinishedPhase(effectID,0);
+    if( mPhaseActive[1] ) effectFinishedPhase(effectID,1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void effectFinishedPhase(final long effectID, int phase)
+    {
+    for(int i=0; i<mCubeEffectNumber[phase]; i++)
+      {
+      long id = mCubeEffects[phase][i].getID();
+
+      if( effectID == id )
+        {
+        effectReturned(phase);
+        mCube[phase].remove(id);
+        return;
+        }
+      }
+    for(int i=0; i<mNodeEffectNumber[phase]; i++)
+      {
+      long id = mNodeEffects[phase][i].getID();
+
+      if( effectID == id )
+        {
+        effectReturned(phase);
+        mCube[phase].getEffects().abortById(id);
+        return;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void effectReturned(int phase)
+    {
+    mEffectReturned[phase]++;
+
+    if( mEffectReturned[phase] == mEffectFinished[phase] )
+      {
+      switch(phase)
+        {
+        case 0: mPhaseActive[1] = true;
+                mEffectFinished[1] = createEffectsPhase1(mDuration);
+                assignEffects(1);
+                mScreen.attach(mCube[1]);
+                break;
+        case 1: mListener.effectFinished(FAKE_EFFECT_ID);
+                break;
+        }
+      }
+    if( mEffectReturned[phase] == mCubeEffectNumber[phase]+mNodeEffectNumber[phase] )
+      {
+      switch(phase)
+        {
+        case 0: mPhaseActive[0] = false;
+                mScreen.detach(mCube[0]);
+                break;
+        case 1: mPhaseActive[1] = false;
+                break;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long start(int duration, RubikRenderer renderer)
+    {
+    mScreen   = renderer.getScreen();
+    mCube[0]  = renderer.getOldCube();
+    mCube[1]  = renderer.getCube();
+    mListener = renderer;
+    mDuration = duration;
+
+    if( mCube[0]!=null )
+      {
+      mPhaseActive[0] = true;
+      mEffectFinished[0] = createEffectsPhase0(mDuration);
+      assignEffects(0);
+      }
+    else
+      {
+      mPhaseActive[1] = true;
+      mEffectFinished[1] = createEffectsPhase1(mDuration);
+      assignEffects(1);
+      mScreen.attach(mCube[1]);
+      }
+
+    return FAKE_EFFECT_ID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void assignEffects(int phase)
+    {
+    mCubeEffectNumber[phase] = ( mCubeEffects[phase]!=null ) ? mCubeEffects[phase].length : 0;
+    mNodeEffectNumber[phase] = ( mNodeEffects[phase]!=null ) ? mNodeEffects[phase].length : 0;
+
+    if( mCubeEffectNumber[phase]==0 && mNodeEffectNumber[phase]==0 )
+      {
+      throw new RuntimeException("Cube and Node Effects ("+phase+" phase) both not created!");
+      }
+
+    for(int i=0; i<mCubeEffectNumber[phase]; i++)
+      {
+      mCube[phase].apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
+      mCubeEffects[phase][i].notifyWhenFinished(this);
+      }
+
+    DistortedEffects nodeEffects = mCube[phase].getEffects();
+
+    for(int i=0; i<mNodeEffectNumber[phase]; i++)
+      {
+      nodeEffects.apply(mNodeEffects[phase][i],mNodeEffectPosition[phase][i]);
+      mNodeEffects[phase][i].notifyWhenFinished(this);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static void enableEffects()
+    {
+    Method method;
+
+    for(Type type: Type.values())
+      {
+      try
+        {
+        method = type.effect.getDeclaredMethod("enable");  // enable not public, thus getDeclaredMethod
+        }
+      catch(NoSuchMethodException ex)
+        {
+        android.util.Log.e("SizeChangeEffect", type.effect.getSimpleName()+": exception getting method: "+ex.getMessage());
+        method = null;
+        }
+
+      try
+        {
+        if( method!=null ) method.invoke(null);
+        }
+      catch(Exception ex)
+        {
+        android.util.Log.e("SizeChangeEffect", type.effect.getSimpleName()+": exception invoking method: "+ex.getMessage());
+        }
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectMove.java b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectMove.java
new file mode 100644
index 00000000..0216b2c7
--- /dev/null
+++ b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectMove.java
@@ -0,0 +1,77 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.sizechange;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static3D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class SizeChangeEffectMove extends SizeChangeEffect
+  {
+  public int createEffectsPhase0(int duration)
+    {
+    int w = mScreen.getWidth();
+    int h = mScreen.getHeight();
+    int xmove = w/2 + (w<h?w:h)/2;
+
+    mNodeEffectPosition[0] = new int[] {1};
+    mNodeEffects[0]        = new Effect[mNodeEffectPosition[0].length];
+
+    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
+    d0.add(new Static3D(    0,0,0));
+    d0.add(new Static3D(xmove,0,0));
+    mNodeEffects[0][0] = new MatrixEffectMove(d0);
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int createEffectsPhase1(int duration)
+    {
+    int w = mScreen.getWidth();
+    int h = mScreen.getHeight();
+    int xmove = w/2 + (w<h?w:h)/2;
+
+    mNodeEffectPosition[1] = new int[] {1};
+    mNodeEffects[1]        = new Effect[mNodeEffectPosition[1].length];
+
+    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
+    d0.add(new Static3D(-xmove,0,0));
+    d0.add(new Static3D(     0,0,0));
+    mNodeEffects[1][0] = new MatrixEffectMove(d0);
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+// Matrix Effects do not have to be enabled.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
+
diff --git a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectNone.java b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectNone.java
new file mode 100644
index 00000000..775f9b64
--- /dev/null
+++ b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectNone.java
@@ -0,0 +1,65 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.sizechange;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static3D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class SizeChangeEffectNone extends SizeChangeEffect
+  {
+  public int createEffectsPhase0(int duration)
+    {
+    mCubeEffectPosition[0] = new int[] {-1};
+    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
+
+    Dynamic3D d0 = new Dynamic3D(1,0.5f);
+    d0.add(new Static3D(0,0,0));
+    mCubeEffects[0][0] = new MatrixEffectMove(d0);
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int createEffectsPhase1(int duration)
+    {
+    mCubeEffectPosition[1] = new int[] {-1};
+    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
+
+    Dynamic3D d0 = new Dynamic3D(1,0.5f);
+    d0.add(new Static3D(0,0,0));
+    mCubeEffects[1][0] = new MatrixEffectMove(d0);
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// enable all effects used in this Effect (here: none).  Called by reflection from the parent class.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectRound.java b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectRound.java
new file mode 100644
index 00000000..51127211
--- /dev/null
+++ b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectRound.java
@@ -0,0 +1,91 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.sizechange;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.type.Dynamic;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static3D;
+
+import static org.distorted.magic.RubikRenderer.TEXTURE_SIZE;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class SizeChangeEffectRound extends SizeChangeEffect
+  {
+  public int createEffectsPhase0(int duration)
+    {
+    float X = TEXTURE_SIZE/3;
+
+    mCubeEffectPosition[0] = new int[] {6,7};
+    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
+
+    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
+    d0.add(new Static3D( 1.00f, 1.00f, 1.00f));
+    d0.add(new Static3D( 0.01f, 0.01f, 0.01f));
+    mCubeEffects[0][0] = new MatrixEffectScale(d0);
+
+    Dynamic3D d1 = new Dynamic3D(duration/2, 0.5f);
+    d1.setMode(Dynamic.MODE_PATH);
+    d1.add(new Static3D( 0, 0, 0));
+    d1.add(new Static3D(+X, 0, 0));
+    d1.add(new Static3D( 0, 0, 0));
+    mCubeEffects[0][1] = new MatrixEffectMove(d1);
+
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int createEffectsPhase1(int duration)
+    {
+    float X = TEXTURE_SIZE/3;
+
+    mCubeEffectPosition[1] = new int[] {6,7};
+    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
+
+    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
+    d0.add(new Static3D( 0.01f, 0.01f, 0.01f));
+    d0.add(new Static3D( 1.00f, 1.00f, 1.00f));
+    mCubeEffects[1][0] = new MatrixEffectScale(d0);
+
+    Dynamic3D d1 = new Dynamic3D(duration/2, 0.5f);
+    d1.setMode(Dynamic.MODE_PATH);
+    d1.add(new Static3D( 0, 0, 0));
+    d1.add(new Static3D(-X, 0, 0));
+    d1.add(new Static3D( 0, 0, 0));
+    mCubeEffects[1][1] = new MatrixEffectMove(d1);
+
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+// Matrix Effects do not have to be enabled.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
+
diff --git a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectScale.java b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectScale.java
new file mode 100644
index 00000000..45c1754a
--- /dev/null
+++ b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectScale.java
@@ -0,0 +1,69 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.sizechange;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectScale;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static3D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class SizeChangeEffectScale extends SizeChangeEffect
+  {
+  public int createEffectsPhase0(int duration)
+    {
+    mCubeEffectPosition[0] = new int[] {6};
+    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
+
+    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
+    d0.add(new Static3D(1.00f, 1.00f, 1.00f));
+    d0.add(new Static3D(0.01f, 0.01f, 0.01f));
+    mCubeEffects[0][0] = new MatrixEffectScale(d0);
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int createEffectsPhase1(int duration)
+    {
+    mCubeEffectPosition[1] = new int[] {6};
+    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
+
+    Dynamic3D d0 = new Dynamic3D(duration/2, 0.5f);
+    d0.add(new Static3D(0.01f, 0.01f, 0.01f));
+    d0.add(new Static3D(1.00f, 1.00f, 1.00f));
+    mCubeEffects[1][0] = new MatrixEffectScale(d0);
+
+    return 1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+// Matrix Effects do not have to be enabled.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
+
diff --git a/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectTransparency.java b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectTransparency.java
new file mode 100644
index 00000000..86af60bc
--- /dev/null
+++ b/src/main/java/org/distorted/effect/sizechange/SizeChangeEffectTransparency.java
@@ -0,0 +1,114 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.sizechange;
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.FragmentEffectAlpha;
+import org.distorted.library.effect.VertexEffectWave;
+import org.distorted.library.type.Dynamic1D;
+import org.distorted.library.type.Dynamic5D;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static5D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class SizeChangeEffectTransparency extends SizeChangeEffect
+  {
+  public int createEffectsPhase0(int duration)
+    {
+    mCubeEffectPosition[0] = new int[] {-1};
+    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
+
+    Dynamic1D d0 = new Dynamic1D(duration/2, 0.5f);
+    d0.add(new Static1D(1.0f));
+    d0.add(new Static1D(0.0f));
+    mCubeEffects[0][0] = new FragmentEffectAlpha(d0);
+
+    mNodeEffectPosition[0] = new int[] {-1};
+    mNodeEffects[0]        = new Effect[mNodeEffectPosition[0].length];
+
+    int w = mScreen.getWidth();
+    int h = mScreen.getHeight();
+    int min = w<h ? w:h;
+
+    float init_amplitude = 0.0f;
+    float end_amplitude  = min/15.0f;
+    float length         = min/15.0f;
+    float init_phase     = 360.0f;
+    float end_phase      = 0.0f;
+    float alpha          = 30.0f;
+    float beta           = 90.0f;
+
+    Dynamic5D d1 = new Dynamic5D(duration/2, 0.5f);
+    d1.add(new Static5D( init_amplitude, length, init_phase, alpha, beta) );
+    d1.add(new Static5D( end_amplitude , length, end_phase , alpha, beta) );
+    Static3D center = new Static3D(min*0.5f,min*0.5f,0);
+    mNodeEffects[0][0] = new VertexEffectWave(d1, center, null);
+
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int createEffectsPhase1(int duration)
+    {
+    mCubeEffectPosition[1] = new int[] {-1};
+    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
+
+    Dynamic1D d0 = new Dynamic1D(duration/2, 0.5f);
+    d0.add(new Static1D(0.0f));
+    d0.add(new Static1D(1.0f));
+    mCubeEffects[1][0] = new FragmentEffectAlpha(d0);
+
+    mNodeEffectPosition[1] = new int[] {-1};
+    mNodeEffects[1]        = new Effect[mNodeEffectPosition[1].length];
+
+    int w = mScreen.getWidth();
+    int h = mScreen.getHeight();
+    int min = w<h ? w:h;
+
+    float init_amplitude = min/15.0f;
+    float end_amplitude  = 0.0f;
+    float length         = min/15.0f;
+    float init_phase     = 0.0f;
+    float end_phase      = 360.0f;
+    float alpha          = 30.0f;
+    float beta           = 90.0f;
+
+    Dynamic5D d1 = new Dynamic5D(duration/2, 0.5f);
+    d1.add(new Static5D( init_amplitude, length, init_phase, alpha, beta) );
+    d1.add(new Static5D( end_amplitude , length, end_phase , alpha, beta) );
+    Static3D center = new Static3D(min*0.5f,min*0.5f,0);
+    mNodeEffects[1][0] = new VertexEffectWave(d1, center, null);
+
+    return 2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+    FragmentEffectAlpha.enable();
+    VertexEffectWave.enable();
+    }
+  }
diff --git a/src/main/java/org/distorted/effect/solve/SolveEffect.java b/src/main/java/org/distorted/effect/solve/SolveEffect.java
new file mode 100644
index 00000000..02da2fa5
--- /dev/null
+++ b/src/main/java/org/distorted/effect/solve/SolveEffect.java
@@ -0,0 +1,237 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.solve;
+
+import org.distorted.effect.BaseEffect;
+import org.distorted.library.effect.Effect;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.message.EffectListener;
+import org.distorted.magic.RubikCube;
+import org.distorted.magic.RubikRenderer;
+
+import java.lang.reflect.Method;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public abstract class SolveEffect extends BaseEffect implements EffectListener
+{
+  public enum Type
+    {
+    NONE   (SolveEffectNone.class),
+    SPIN   (SolveEffectSpin.class),
+    ;
+
+    final Class<? extends SolveEffect> effect;
+
+    Type(Class<? extends SolveEffect> effect)
+      {
+      this.effect = effect;
+      }
+    }
+
+  private static final int NUM_EFFECTS = Type.values().length;
+  private static final int NUM_PHASES  = 2;
+  private static final int FAKE_EFFECT_ID = -2;
+  private static final Type[] types;
+
+  static
+    {
+    int i=0;
+    types = new Type[NUM_EFFECTS];
+
+    for(Type type: Type.values())
+      {
+      types[i++] = type;
+      }
+    }
+
+  private EffectListener mListener;
+  private int mDuration;
+  private int mEffectReturned;
+  private int[] mCubeEffectNumber, mNodeEffectNumber;
+  private int mPhase;
+
+  RubikCube mCube;
+  DistortedScreen mScreen;
+  Effect[][] mCubeEffects;
+  int[][] mCubeEffectPosition;
+  Effect[][] mNodeEffects;
+  int[][] mNodeEffectPosition;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  SolveEffect()
+    {
+    mPhase        =  0;
+
+    mCubeEffectNumber   = new int[NUM_PHASES];
+    mNodeEffectNumber   = new int[NUM_PHASES];
+    mCubeEffectPosition = new int[NUM_PHASES][];
+    mNodeEffectPosition = new int[NUM_PHASES][];
+    mCubeEffects        = new Effect[NUM_PHASES][];
+    mNodeEffects        = new Effect[NUM_PHASES][];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static String[] getNames()
+    {
+    String[] names = new String[NUM_EFFECTS];
+
+    for( int i=0; i<NUM_EFFECTS; i++)
+      {
+      names[i] = types[i].name();
+      }
+
+    return names;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public static SolveEffect create(int ordinal) throws InstantiationException, IllegalAccessException
+    {
+    return types[ordinal].effect.newInstance();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  abstract void createEffectsPhase0(int duration);
+  abstract void createEffectsPhase1(int duration);
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void effectFinished(final long effectID)
+    {
+    int total = mCubeEffectNumber[mPhase]+mNodeEffectNumber[mPhase];
+
+    for(int i=0; i<mCubeEffectNumber[mPhase]; i++)
+      {
+      long id = mCubeEffects[mPhase][i].getID();
+
+      if( effectID == id )
+        {
+        if( ++mEffectReturned == total ) effectAction(mPhase);
+        mCube.remove(id);
+        return;
+        }
+      }
+    for(int i=0; i<mNodeEffectNumber[mPhase]; i++)
+      {
+      long id = mNodeEffects[mPhase][i].getID();
+
+      if( effectID == id )
+        {
+        if( ++mEffectReturned == total ) effectAction(mPhase);
+        mCube.getEffects().abortById(id);
+        return;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void effectAction(int phase)
+    {
+    switch(phase)
+      {
+      case 0: mEffectReturned = 0;
+              mPhase          = 1;
+              mCube.solve();
+              createEffectsPhase1(mDuration);
+              assignEffects(mPhase);
+              break;
+      case 1: mListener.effectFinished(FAKE_EFFECT_ID);
+              break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long start(int duration, RubikRenderer renderer)
+    {
+    mScreen   = renderer.getScreen();
+    mCube     = renderer.getCube();
+    mListener = renderer;
+    mDuration = duration;
+
+    createEffectsPhase0(mDuration);
+    assignEffects(mPhase);
+
+    return FAKE_EFFECT_ID;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void assignEffects(int phase)
+    {
+    mCubeEffectNumber[phase] = ( mCubeEffects[phase]!=null ) ? mCubeEffects[phase].length : 0;
+    mNodeEffectNumber[phase] = ( mNodeEffects[phase]!=null ) ? mNodeEffects[phase].length : 0;
+
+    if( mCubeEffectNumber[phase]==0 && mNodeEffectNumber[phase]==0 )
+      {
+      throw new RuntimeException("Cube and Node Effects ("+phase+" phase) both not created!");
+      }
+
+    for(int i=0; i<mCubeEffectNumber[phase]; i++)
+      {
+      mCube.apply(mCubeEffects[phase][i],mCubeEffectPosition[phase][i]);
+      mCubeEffects[phase][i].notifyWhenFinished(this);
+      }
+
+    DistortedEffects nodeEffects = mCube.getEffects();
+
+    for(int i=0; i<mNodeEffectNumber[phase]; i++)
+      {
+      nodeEffects.apply(mNodeEffects[phase][i],mNodeEffectPosition[phase][i]);
+      mNodeEffects[phase][i].notifyWhenFinished(this);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @SuppressWarnings("unused")
+  public static void enableEffects()
+    {
+    Method method;
+
+    for(Type type: Type.values())
+      {
+      try
+        {
+        method = type.effect.getDeclaredMethod("enable"); // enable not public, thus getDeclaredMethod
+        }
+      catch(NoSuchMethodException ex)
+        {
+        android.util.Log.e("SolveEffect", type.effect.getSimpleName()+": exception getting method: "+ex.getMessage());
+        method = null;
+        }
+
+      try
+        {
+        if( method!=null ) method.invoke(null);
+        }
+      catch(Exception ex)
+        {
+        android.util.Log.e("SolveEffect", type.effect.getSimpleName()+": exception invoking method: "+ex.getMessage());
+        }
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/effect/solve/SolveEffectNone.java b/src/main/java/org/distorted/effect/solve/SolveEffectNone.java
new file mode 100644
index 00000000..ca2bd2c5
--- /dev/null
+++ b/src/main/java/org/distorted/effect/solve/SolveEffectNone.java
@@ -0,0 +1,61 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.solve;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectMove;
+import org.distorted.library.type.Dynamic3D;
+import org.distorted.library.type.Static3D;
+
+public class SolveEffectNone extends SolveEffect
+  {
+  public void createEffectsPhase0(int duration)
+    {
+    Dynamic3D d0 = new Dynamic3D(1,0.5f);
+    d0.add(new Static3D(0,0,0));
+
+    mCubeEffectPosition[0] = new int[] {-1};
+    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
+    mCubeEffects[0][0]     = new MatrixEffectMove(d0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void createEffectsPhase1(int duration)
+    {
+    Dynamic3D d0 = new Dynamic3D(1,0.5f);
+    d0.add(new Static3D(0,0,0));
+
+    mCubeEffectPosition[1]  = new int[] {-1};
+    mCubeEffects[1]         = new Effect[mCubeEffectPosition[1].length];
+    mCubeEffects[1][0]      = new MatrixEffectMove(d0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/effect/solve/SolveEffectSpin.java b/src/main/java/org/distorted/effect/solve/SolveEffectSpin.java
new file mode 100644
index 00000000..b650887d
--- /dev/null
+++ b/src/main/java/org/distorted/effect/solve/SolveEffectSpin.java
@@ -0,0 +1,81 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// Distorted is free software: you can redistribute it and/or modify                             //
+// it under the terms of the GNU General Public License as published by                          //
+// the Free Software Foundation, either version 2 of the License, or                             //
+// (at your option) any later version.                                                           //
+//                                                                                               //
+// Distorted is distributed in the hope that it will be useful,                                  //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
+// GNU General Public License for more details.                                                  //
+//                                                                                               //
+// You should have received a copy of the GNU General Public License                             //
+// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.effect.solve;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+import org.distorted.library.effect.Effect;
+import org.distorted.library.effect.MatrixEffectRotate;
+import org.distorted.library.type.Dynamic;
+import org.distorted.library.type.Dynamic1D;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+
+public class SolveEffectSpin extends SolveEffect
+  {
+  public void createEffectsPhase0(int duration)
+    {
+    mCubeEffectPosition[0] = new int[] {3};
+    mCubeEffects[0]        = new Effect[mCubeEffectPosition[0].length];
+
+    Static3D axis  = new Static3D(1,0,0);
+    Static3D center= new Static3D(0,0,0);
+
+    Dynamic1D d0 = new Dynamic1D(duration/2, 1.0f);
+    d0.setMode(Dynamic.MODE_JUMP);
+    d0.setConvexity(0.0f);          // otherwise speed of the rotation would be strangely uneven
+    d0.add(new Static1D( 0*36));
+    d0.add(new Static1D( 1*36));
+    d0.add(new Static1D( 3*36));
+    d0.add(new Static1D( 6*36));
+    d0.add(new Static1D(10*36));
+    mCubeEffects[0][0] = new MatrixEffectRotate(d0,axis,center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void createEffectsPhase1(int duration)
+    {
+    mCubeEffectPosition[1] = new int[] {3};
+    mCubeEffects[1]        = new Effect[mCubeEffectPosition[1].length];
+
+    Static3D axis  = new Static3D(1,0,0);
+    Static3D center= new Static3D(0,0,0);
+
+    Dynamic1D d1 = new Dynamic1D(duration/2, 1.0f);
+    d1.setMode(Dynamic.MODE_JUMP);
+    d1.setConvexity(0.0f);
+    d1.add(new Static1D( 0*36));
+    d1.add(new Static1D( 4*36));
+    d1.add(new Static1D( 7*36));
+    d1.add(new Static1D( 9*36));
+    d1.add(new Static1D(10*36));
+    mCubeEffects[1][0] = new MatrixEffectRotate(d1,axis,center);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Enable all effects used in this Effect. Called by reflection from the parent class.
+
+  @SuppressWarnings("unused")
+  static void enable()
+    {
+
+    }
+  }
diff --git a/src/main/java/org/distorted/magic/RubikActivity.java b/src/main/java/org/distorted/magic/RubikActivity.java
index 5a157b62..405e6e80 100644
--- a/src/main/java/org/distorted/magic/RubikActivity.java
+++ b/src/main/java/org/distorted/magic/RubikActivity.java
@@ -31,6 +31,7 @@ import android.view.View;
 
 import org.distorted.component.HorizontalNumberPicker;
 import org.distorted.library.main.DistortedLibrary;
+import org.distorted.effect.BaseEffect;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -185,9 +186,9 @@ public class RubikActivity extends AppCompatActivity
      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
      SharedPreferences.Editor editor = preferences.edit();
 
-     for (int i=0; i<RubikSettingsEnum.LENGTH; i++)
+     for (int i=0; i< BaseEffect.Type.LENGTH; i++)
        {
-       RubikSettingsEnum.getEnum(i).savePreferences(editor);
+       BaseEffect.Type.getType(i).savePreferences(editor);
        }
 
      editor.putInt("scramble", mPicker.getValue() );
@@ -201,9 +202,9 @@ public class RubikActivity extends AppCompatActivity
      {
      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
 
-     for (int i=0; i<RubikSettingsEnum.LENGTH; i++)
+     for (int i=0; i< BaseEffect.Type.LENGTH; i++)
        {
-       RubikSettingsEnum.getEnum(i).restorePreferences(preferences);
+       BaseEffect.Type.getType(i).restorePreferences(preferences);
        }
 
      int scramble= preferences.getInt("scramble", MIN_SCRAMBLE);
diff --git a/src/main/java/org/distorted/magic/RubikRenderer.java b/src/main/java/org/distorted/magic/RubikRenderer.java
index a5c224b1..1c250bb8 100644
--- a/src/main/java/org/distorted/magic/RubikRenderer.java
+++ b/src/main/java/org/distorted/magic/RubikRenderer.java
@@ -21,9 +21,7 @@ package org.distorted.magic;
 
 import android.opengl.GLSurfaceView;
 
-import org.distorted.effect.SizeChangeEffect;
-import org.distorted.effect.SolveEffect;
-import org.distorted.effect.ScrambleEffect;
+import org.distorted.effect.BaseEffect;
 import org.distorted.library.effect.VertexEffectSink;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedLibrary;
@@ -50,7 +48,8 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
     private Static4D mTempCurrent, mTempAccumulated;
     private float mCubeSizeInScreenSpace;
     private int mNextCubeSize, mScrambleCubeNum;
-    private long mRotationFinishedID, mSizeChangeEffectID, mSolveEffectID, mScrambleEffectID;
+    private long mRotationFinishedID;
+    private long[] mEffectID;
     private boolean mFinishRotation, mRemoveRotation, mFinishDragCurrent, mFinishDragAccumulated, mSolveCube, mScrambleCube;
     private boolean mCanRotate, mCanDrag, mCanScramble, mCanSolve;
     private RubikCube mOldCube, mNewCube;
@@ -88,6 +87,8 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
       mCanScramble = true;
       mCanSolve    = true;
 
+      mEffectID = new long[BaseEffect.Type.LENGTH];
+
       mMesh= new MeshFlat(20,20);
       mNextCubeSize =RubikActivity.getSize();
       }
@@ -130,11 +131,10 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
       if( mNextCubeSize!=0 )
         {
         createCubeNow(mNextCubeSize);
-
         mCanDrag   = false;
         mCanRotate = false;
         mNextCubeSize = 0;
-        changeSizeNow();
+        doEffectNow(0);
         }
 
       if( mSolveCube )
@@ -144,7 +144,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
         mCanRotate   = false;
         mCanScramble = false;
         mCanSolve    = false;
-        solveCubeNow();
+        doEffectNow(1);
         }
 
       if( mScrambleCube )
@@ -154,7 +154,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
         mCanRotate    = false;
         mCanScramble  = false;
         mCanSolve     = false;
-        scrambleCubeNow();
+        doEffectNow(2);
         }
       }
 
@@ -185,9 +185,7 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
       {
       VertexEffectSink.enable();
-      SizeChangeEffect.enableEffects();
-      ScrambleEffect.enableEffects();
-      SolveEffect.enableEffects();
+      BaseEffect.Type.enableEffects();
 
       try
         {
@@ -207,19 +205,19 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
        {
        mRemoveRotation = true;
        }
-     else if( effectID == mSizeChangeEffectID )
+     else if( effectID == mEffectID[0] )
        {
        mCanRotate   = true;
        mCanDrag     = true;
        }
-     else if( effectID == mSolveEffectID )
+     else if( effectID == mEffectID[1] )
        {
        mCanRotate   = true;
        mCanDrag     = true;
        mCanSolve    = true;
        mCanScramble = true;
        }
-     else if( effectID == mScrambleEffectID   )
+     else if( effectID == mEffectID[2] )
        {
        mCanRotate   = true;
        mCanDrag     = true;
@@ -311,22 +309,12 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   private void changeSizeNow()
+   private void doEffectNow(int index)
      {
-     try
-       {
-       int pos0 = RubikSettingsEnum.getEnum(0).getCurrentPos();
-       int typ0 = RubikSettingsEnum.getEnum(0).getCurrentType();
-       int pos = RubikSettingsEnum.translatePos(pos0)+1;
-       SizeChangeEffect effect = SizeChangeEffect.create(typ0);
-       mSizeChangeEffectID = effect.start(pos,mScreen,mOldCube,mNewCube,this);
-       }
-     catch(Exception ex)
-       {
-       android.util.Log.e("Renderer", "failed to create SizeChangeEffect, exception: "+ex.getMessage());
+     mEffectID[index] = BaseEffect.Type.getType(index).startEffect(this);
 
-       if( mOldCube!=null ) mScreen.detach(mOldCube);
-       mScreen.attach(mNewCube);
+     if( mEffectID[index] == -1 )
+       {
        mCanRotate = true;
        mCanDrag   = true;
        }
@@ -334,73 +322,51 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   private void solveCubeNow()
+   float returnCubeSizeInScreenSpace()
      {
-     try
-       {
-       int pos1 = RubikSettingsEnum.getEnum(1).getCurrentPos();
-       int typ1 = RubikSettingsEnum.getEnum(1).getCurrentType();
-       int pos = RubikSettingsEnum.translatePos(pos1)+1;
-       SolveEffect effect = SolveEffect.create(typ1);
-       mSolveEffectID = effect.start(pos,mScreen,mNewCube,this);
-       }
-     catch(Exception ex)
-       {
-       android.util.Log.e("Renderer", "failed to create SolveEffect, exception: "+ex.getMessage());
-
-       mNewCube.solve();    //
-       mCanRotate = true;   // just solve the cube
-       mCanDrag   = true;   //
-       }
+     return mCubeSizeInScreenSpace;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   private void scrambleCubeNow()
+   boolean canRotate()
      {
-     try
-       {
-       int pos2 = RubikSettingsEnum.getEnum(2).getCurrentPos();
-       int typ2 = RubikSettingsEnum.getEnum(2).getCurrentType();
-       int pos = RubikSettingsEnum.translatePos(pos2)+1;
-       ScrambleEffect effect = ScrambleEffect.create(typ2);
-       mScrambleEffectID = effect.start(pos,mNewCube,mScrambleCubeNum,this);
-       }
-     catch(Exception ex)
-       {
-       android.util.Log.e("Renderer", "failed to create ScrambleEffect, exception: "+ex.getMessage());
+     return mCanRotate;
+     }
 
-       mCanRotate = true;
-       mCanDrag   = true;
-       }
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   boolean canDrag()
+     {
+     return mCanDrag;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   float returnCubeSizeInScreenSpace()
+   public RubikCube getCube()
      {
-     return mCubeSizeInScreenSpace;
+     return mNewCube;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   boolean canRotate()
+   public RubikCube getOldCube()
      {
-     return mCanRotate;
+     return mOldCube;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   boolean canDrag()
+   public DistortedScreen getScreen()
      {
-     return mCanDrag;
+     return mScreen;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-   RubikCube getCube()
+   public int getNumScrambles()
      {
-     return mNewCube;
+     return mScrambleCubeNum;
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/magic/RubikSettings.java b/src/main/java/org/distorted/magic/RubikSettings.java
index 95d3b535..21f0aea9 100644
--- a/src/main/java/org/distorted/magic/RubikSettings.java
+++ b/src/main/java/org/distorted/magic/RubikSettings.java
@@ -35,6 +35,8 @@ import android.widget.SeekBar;
 import android.widget.Spinner;
 import android.widget.TextView;
 
+import org.distorted.effect.BaseEffect;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class RubikSettings extends AppCompatDialogFragment implements SeekBar.OnSeekBarChangeListener, AdapterView.OnItemSelectedListener
@@ -45,7 +47,7 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
 
   public RubikSettings()
     {
-    mDurationText = new TextView[RubikSettingsEnum.LENGTH];
+    mDurationText = new TextView[BaseEffect.Type.LENGTH];
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -66,7 +68,7 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
 
     if( linearLayout!=null )
       {
-      for (int i=0; i<RubikSettingsEnum.LENGTH; i++)
+      for (int i=0; i< BaseEffect.Type.LENGTH; i++)
         {
         createSettingsSection(act,linearLayout,i);
         }
@@ -81,159 +83,160 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    private void createSettingsSection(FragmentActivity act, LinearLayout layout, int index)
-      {
-      RubikSettingsEnum rsEnum = RubikSettingsEnum.getEnum(index);
-      float scale = act.getResources().getDisplayMetrics().density;
-
-      ///// TEXT ///////////////////////////////////////////////////////////////////////////
-
-      int layoutHeight = (int)(scale*48 + 0.5f);
-      int padding      = (int)(scale*10 + 0.5f);
-
-      LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,layoutHeight);
-
-      TextView textView = new TextView(act);
-      textView.setText(rsEnum.getText());
-      textView.setLayoutParams(textParams);
-      textView.setGravity(Gravity.START|Gravity.CENTER);
-      textView.setPadding(padding,0,padding,0);
-      textView.setTextAppearance(android.R.style.TextAppearance_Medium);
-      layout.addView(textView);
-
-      ///// OUTER LAYOUT ///////////////////////////////////////////////////////////////////
-
-      int margin = (int)(scale*10 + 0.5f);
-      int color  = act.getResources().getColor(R.color.grey);
-      LinearLayout outerLayout = new LinearLayout(act);
-      LinearLayout.LayoutParams outerLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT, 0.5f);
-      outerLayoutParams.bottomMargin = margin;
-      outerLayoutParams.leftMargin   = margin;
-      outerLayoutParams.rightMargin  = margin;
-
-      outerLayout.setLayoutParams(outerLayoutParams);
-      outerLayout.setGravity(Gravity.CENTER|Gravity.FILL_HORIZONTAL);
-      outerLayout.setBackgroundColor(color);
-      outerLayout.setOrientation(LinearLayout.VERTICAL);
-      layout.addView(outerLayout);
-
-      ///// INNER LAYOUT1 //////////////////////////////////////////////////////////////////
-
-      int innerLayout1Height = (int)(scale*36 + 0.5f);
-      LinearLayout innerLayout1 = new LinearLayout(act);
-      LinearLayout.LayoutParams innerLayout1Params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,innerLayout1Height);
-
-      innerLayout1.setLayoutParams(innerLayout1Params);
-      innerLayout1.setGravity(Gravity.CENTER|Gravity.FILL_HORIZONTAL);
-      innerLayout1.setOrientation(LinearLayout.HORIZONTAL);
-      outerLayout.addView(innerLayout1);
-
-      ///// STUFF INSIDE INNER LAYOUT1 /////////////////////////////////////////////////////
-
-      int text1Padding = (int)(scale*5 + 0.5f);
-      LinearLayout.LayoutParams text1LayoutParams = new LinearLayout.LayoutParams(0,layoutHeight,0.2f);
-
-      TextView text1View = new TextView(act);
-      text1View.setText(R.string.duration);
-      text1View.setLayoutParams(text1LayoutParams);
-      text1View.setGravity(Gravity.START|Gravity.CENTER);
-      text1View.setPadding(text1Padding,0,text1Padding,0);
-      text1View.setTextAppearance(android.R.style.TextAppearance_Small);
-      innerLayout1.addView(text1View);
-      //////////////////////////////////////////////////////////////////
-      int text2Padding = (int)(scale*5 + 0.5f);
-      LinearLayout.LayoutParams text2LayoutParams = new LinearLayout.LayoutParams(0,layoutHeight,0.2f);
-
-      mDurationText[index] = new TextView(act);
-      mDurationText[index].setLayoutParams(text2LayoutParams);
-      mDurationText[index].setGravity(Gravity.END|Gravity.CENTER);
-      mDurationText[index].setPadding(text2Padding,0,text2Padding,0);
-      mDurationText[index].setTextAppearance(android.R.style.TextAppearance_Small);
-      innerLayout1.addView(mDurationText[index]);
-      //////////////////////////////////////////////////////////////////
-      int seekPadding = (int)(scale*10 + 0.5f);
-      LinearLayout.LayoutParams seekLayoutParams = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,0.6f);
-
-      SeekBar seekBar = new SeekBar(act);
-      seekBar.setLayoutParams(seekLayoutParams);
-      seekBar.setPadding(seekPadding,0,seekPadding,0);
-      seekBar.setId(index);
-      innerLayout1.addView(seekBar);
-
-      seekBar.setOnSeekBarChangeListener(this);
-      seekBar.setProgress(rsEnum.getCurrentPos());
-
-      ///// INNER LAYOUT2 //////////////////////////////////////////////////////////////////
-
-      int innerLayout2Height = (int)(scale*36 + 0.5f);
-      LinearLayout innerLayout2 = new LinearLayout(act);
-      LinearLayout.LayoutParams innerLayout2Params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,innerLayout2Height);
-
-      innerLayout2.setLayoutParams(innerLayout2Params);
-      innerLayout2.setGravity(Gravity.CENTER|Gravity.FILL_HORIZONTAL);
-      innerLayout2.setOrientation(LinearLayout.HORIZONTAL);
-      outerLayout.addView(innerLayout2);
-
-      ///// STUFF INSIDE INNER LAYOUT2 /////////////////////////////////////////////////////
-
-      int text3Padding = (int)(scale*5 + 0.5f);
-      LinearLayout.LayoutParams text3LayoutParams = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,0.4f);
-
-      TextView text3View = new TextView(act);
-      text3View.setText(R.string.type);
-      text3View.setLayoutParams(text3LayoutParams);
-      text3View.setGravity(Gravity.START|Gravity.CENTER);
-      text3View.setPadding(text3Padding,0,text3Padding,0);
-      text3View.setTextAppearance(android.R.style.TextAppearance_Small);
-      innerLayout2.addView(text3View);
-      //////////////////////////////////////////////////////////////////
-      int spinnerPadding = (int)(scale*10 + 0.5f);
-      LinearLayout.LayoutParams spinnerLayoutParams = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,0.6f);
-
-      Spinner spinner = new Spinner(act);
-      spinner.setLayoutParams(spinnerLayoutParams);
-      spinner.setPadding(spinnerPadding,0,spinnerPadding,0);
-      spinner.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
-      spinner.setId(index);
-      innerLayout2.addView(spinner);
-
-      spinner.setOnItemSelectedListener(this);
-      String[] appear = RubikSettingsEnum.getNames(index);
-      ArrayAdapter<String> adapterType = new ArrayAdapter<>(act,android.R.layout.simple_spinner_item, appear);
-      adapterType.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-      spinner.setAdapter(adapterType);
-      spinner.setSelection(rsEnum.getCurrentType());
-      }
+  private void createSettingsSection(FragmentActivity act, LinearLayout layout, int index)
+    {
+    BaseEffect.Type beType = BaseEffect.Type.getType(index);
+    float scale = act.getResources().getDisplayMetrics().density;
+
+    ///// TEXT ///////////////////////////////////////////////////////////////////////////
+
+    int layoutHeight = (int)(scale*48 + 0.5f);
+    int padding      = (int)(scale*10 + 0.5f);
+
+    LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,layoutHeight);
+
+    TextView textView = new TextView(act);
+    textView.setText(beType.getText());
+    textView.setLayoutParams(textParams);
+    textView.setGravity(Gravity.START|Gravity.CENTER);
+    textView.setPadding(padding,0,padding,0);
+    textView.setTextAppearance(android.R.style.TextAppearance_Medium);
+    layout.addView(textView);
+
+    ///// OUTER LAYOUT ///////////////////////////////////////////////////////////////////
+
+    int margin = (int)(scale*10 + 0.5f);
+    int color  = act.getResources().getColor(R.color.grey);
+    LinearLayout outerLayout = new LinearLayout(act);
+    LinearLayout.LayoutParams outerLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT, 0.5f);
+    outerLayoutParams.bottomMargin = margin;
+    outerLayoutParams.leftMargin   = margin;
+    outerLayoutParams.rightMargin  = margin;
+
+    outerLayout.setLayoutParams(outerLayoutParams);
+    outerLayout.setGravity(Gravity.CENTER|Gravity.FILL_HORIZONTAL);
+    outerLayout.setBackgroundColor(color);
+    outerLayout.setOrientation(LinearLayout.VERTICAL);
+    layout.addView(outerLayout);
+
+    ///// INNER LAYOUT1 //////////////////////////////////////////////////////////////////
+
+    int innerLayout1Height = (int)(scale*36 + 0.5f);
+    LinearLayout innerLayout1 = new LinearLayout(act);
+    LinearLayout.LayoutParams innerLayout1Params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,innerLayout1Height);
+
+    innerLayout1.setLayoutParams(innerLayout1Params);
+    innerLayout1.setGravity(Gravity.CENTER|Gravity.FILL_HORIZONTAL);
+    innerLayout1.setOrientation(LinearLayout.HORIZONTAL);
+    outerLayout.addView(innerLayout1);
+
+    ///// STUFF INSIDE INNER LAYOUT1 /////////////////////////////////////////////////////
+
+    int text1Padding = (int)(scale*5 + 0.5f);
+    LinearLayout.LayoutParams text1LayoutParams = new LinearLayout.LayoutParams(0,layoutHeight,0.2f);
+
+    TextView text1View = new TextView(act);
+    text1View.setText(R.string.duration);
+    text1View.setLayoutParams(text1LayoutParams);
+    text1View.setGravity(Gravity.START|Gravity.CENTER);
+    text1View.setPadding(text1Padding,0,text1Padding,0);
+    text1View.setTextAppearance(android.R.style.TextAppearance_Small);
+    innerLayout1.addView(text1View);
+    //////////////////////////////////////////////////////////////////
+    int text2Padding = (int)(scale*5 + 0.5f);
+    LinearLayout.LayoutParams text2LayoutParams = new LinearLayout.LayoutParams(0,layoutHeight,0.2f);
+
+    mDurationText[index] = new TextView(act);
+    mDurationText[index].setLayoutParams(text2LayoutParams);
+    mDurationText[index].setGravity(Gravity.END|Gravity.CENTER);
+    mDurationText[index].setPadding(text2Padding,0,text2Padding,0);
+    mDurationText[index].setTextAppearance(android.R.style.TextAppearance_Small);
+    innerLayout1.addView(mDurationText[index]);
+    //////////////////////////////////////////////////////////////////
+    int seekPadding = (int)(scale*10 + 0.5f);
+    LinearLayout.LayoutParams seekLayoutParams = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,0.6f);
+
+    SeekBar seekBar = new SeekBar(act);
+    seekBar.setLayoutParams(seekLayoutParams);
+    seekBar.setPadding(seekPadding,0,seekPadding,0);
+    seekBar.setId(index);
+    innerLayout1.addView(seekBar);
+
+    seekBar.setOnSeekBarChangeListener(this);
+    seekBar.setProgress(beType.getCurrentPos());
+
+    ///// INNER LAYOUT2 //////////////////////////////////////////////////////////////////
+
+    int innerLayout2Height = (int)(scale*36 + 0.5f);
+    LinearLayout innerLayout2 = new LinearLayout(act);
+    LinearLayout.LayoutParams innerLayout2Params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,innerLayout2Height);
+
+    innerLayout2.setLayoutParams(innerLayout2Params);
+    innerLayout2.setGravity(Gravity.CENTER|Gravity.FILL_HORIZONTAL);
+    innerLayout2.setOrientation(LinearLayout.HORIZONTAL);
+    outerLayout.addView(innerLayout2);
+
+    ///// STUFF INSIDE INNER LAYOUT2 /////////////////////////////////////////////////////
+
+    int text3Padding = (int)(scale*5 + 0.5f);
+    LinearLayout.LayoutParams text3LayoutParams = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,0.4f);
+
+    TextView text3View = new TextView(act);
+    text3View.setText(R.string.type);
+    text3View.setLayoutParams(text3LayoutParams);
+    text3View.setGravity(Gravity.START|Gravity.CENTER);
+    text3View.setPadding(text3Padding,0,text3Padding,0);
+    text3View.setTextAppearance(android.R.style.TextAppearance_Small);
+    innerLayout2.addView(text3View);
+    //////////////////////////////////////////////////////////////////
+    int spinnerPadding = (int)(scale*10 + 0.5f);
+    LinearLayout.LayoutParams spinnerLayoutParams = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,0.6f);
+
+    Spinner spinner = new Spinner(act);
+    spinner.setLayoutParams(spinnerLayoutParams);
+    spinner.setPadding(spinnerPadding,0,spinnerPadding,0);
+    spinner.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
+    spinner.setId(index);
+    innerLayout2.addView(spinner);
+
+    spinner.setOnItemSelectedListener(this);
+    String[] appear = BaseEffect.Type.getType(index).getNames();
+
+    ArrayAdapter<String> adapterType = new ArrayAdapter<>(act,android.R.layout.simple_spinner_item, appear);
+    adapterType.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+    spinner.setAdapter(adapterType);
+    spinner.setSelection(beType.getCurrentType());
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
-      {
-      int parentID = parent.getId();
+  public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
+    {
+    int parentID = parent.getId();
 
-      if( parentID>=0 && parentID< RubikSettingsEnum.LENGTH) // ith spinner's ID is equal to i (see createSettingSection)
-        {
-        RubikSettingsEnum.getEnum(parentID).setCurrentType(pos);
-        }
+    if( parentID>=0 && parentID< BaseEffect.Type.LENGTH) // ith spinner's ID is equal to i (see createSettingSection)
+      {
+      BaseEffect.Type.getType(parentID).setCurrentType(pos);
       }
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void onProgressChanged(SeekBar bar, int progress, boolean fromUser)
-      {
-      int barID = bar.getId();
+  public void onProgressChanged(SeekBar bar, int progress, boolean fromUser)
+    {
+    int barID = bar.getId();
 
-      if( barID>=0 && barID< RubikSettingsEnum.LENGTH) // ith seekbar's ID is equal to i (see createSettingSection)
-        {
-        RubikSettingsEnum.getEnum(barID).setCurrentPos(progress);
-        int ms = RubikSettingsEnum.translatePos(progress);
-        mDurationText[barID].setText(getString(R.string.ms_placeholder,ms));
-        }
+    if( barID>=0 && barID< BaseEffect.Type.LENGTH) // ith seekbar's ID is equal to i (see createSettingSection)
+      {
+      BaseEffect.Type.getType(barID).setCurrentPos(progress);
+      int ms = BaseEffect.Type.translatePos(progress);
+      mDurationText[barID].setText(getString(R.string.ms_placeholder,ms));
       }
+    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void onNothingSelected(AdapterView<?> parent) { }
-    public void onStartTrackingTouch(SeekBar bar) { }
-    public void onStopTrackingTouch(SeekBar bar)  { }
+  public void onNothingSelected(AdapterView<?> parent) { }
+  public void onStartTrackingTouch(SeekBar bar) { }
+  public void onStopTrackingTouch(SeekBar bar)  { }
   }
diff --git a/src/main/java/org/distorted/magic/RubikSettingsEnum.java b/src/main/java/org/distorted/magic/RubikSettingsEnum.java
deleted file mode 100644
index 223efb1d..00000000
--- a/src/main/java/org/distorted/magic/RubikSettingsEnum.java
+++ /dev/null
@@ -1,147 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2020 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// Distorted is free software: you can redistribute it and/or modify                             //
-// it under the terms of the GNU General Public License as published by                          //
-// the Free Software Foundation, either version 2 of the License, or                             //
-// (at your option) any later version.                                                           //
-//                                                                                               //
-// Distorted is distributed in the hope that it will be useful,                                  //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of                                //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                 //
-// GNU General Public License for more details.                                                  //
-//                                                                                               //
-// You should have received a copy of the GNU General Public License                             //
-// along with Distorted.  If not, see <http://www.gnu.org/licenses/>.                            //
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-package org.distorted.magic;
-
-import android.content.SharedPreferences;
-
-import org.distorted.effect.ScrambleEffect;
-import org.distorted.effect.SizeChangeEffect;
-import org.distorted.effect.SolveEffect;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-enum RubikSettingsEnum
-  {
-  SIZECHANGE  (  20, 1, R.string.sizechange_effect),
-  SOLVE       (  20, 1, R.string.solve_effect      ),
-  SCRAMBLE    ( 100, 1, R.string.scramble_effect   );
-
-  public static final int LENGTH = values().length;
-  private final int mDefaultPos, mDefaultType;
-  private int mCurrentPos, mCurrentType;
-  private int mText;
-
-  private static final RubikSettingsEnum[] enums;  // copy the values() to a local variable so that we
-                                                   // don't have to keep recreating the array every time
-  static
-    {
-    int i=0;
-
-    enums= new RubikSettingsEnum[LENGTH];
-
-    for(RubikSettingsEnum name: RubikSettingsEnum.values())
-      {
-      enums[i] = name;
-      i++;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  RubikSettingsEnum( int dPos, int dType, int text )
-    {
-    mDefaultPos  = mCurrentPos = dPos;
-    mDefaultType = mCurrentType= dType;
-    mText        = text;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getText()
-    {
-    return mText;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getCurrentPos()
-    {
-    return mCurrentPos;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  int getCurrentType()
-    {
-    return mCurrentType;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setCurrentPos(int pos)
-    {
-    mCurrentPos = pos;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void setCurrentType(int type)
-    {
-    mCurrentType = type;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void savePreferences(SharedPreferences.Editor editor)
-    {
-    String name = name();
-
-    editor.putInt(name+"_Pos" , mCurrentPos );
-    editor.putInt(name+"_Type", mCurrentType);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void restorePreferences(SharedPreferences preferences)
-    {
-    String name = name();
-
-    mCurrentPos  = preferences.getInt(name+"_Pos" , mDefaultPos );
-    mCurrentType = preferences.getInt(name+"_Type", mDefaultType);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static RubikSettingsEnum getEnum(int ordinal)
-    {
-    return enums[ordinal];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static String[] getNames(int ordinal)
-    {
-    switch(ordinal)
-      {
-      case 0: return SizeChangeEffect.getNames();
-      case 1: return SolveEffect.getNames();
-      case 2: return ScrambleEffect.getNames();
-      }
-
-    return null;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static int translatePos(int pos)
-     {
-     return (pos/2)*100;
-     }
-  }
