commit 3b12e6411aabcbd6956568afb0fe6ce09d60b814
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Tue Dec 10 15:12:21 2019 +0000

    Add a skeleton of ScrambleEffects

diff --git a/src/main/java/org/distorted/effect/ScrambleEffect.java b/src/main/java/org/distorted/effect/ScrambleEffect.java
new file mode 100644
index 00000000..5ab79ee1
--- /dev/null
+++ b/src/main/java/org/distorted/effect/ScrambleEffect.java
@@ -0,0 +1,241 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 ScrambleEffect implements EffectListener
+{
+  public enum Type
+    {
+    NONE         (ScrambleEffectNone.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 NUM_PHASES  = 2;
+  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 mDuration;
+  private int mEffectReturned;
+  private int[] mCubeEffectNumber, mNodeEffectNumber;
+  private int mPhase;
+  private RubikCube mCube;
+  private DistortedScreen mScreen;
+
+  Effect[][] mNodeEffects;
+  int[][] mNodeEffectPosition;
+  Effect[][] mCubeEffects;
+  int[][] mCubeEffectPosition;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  ScrambleEffect()
+    {
+    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 Type getType(int ordinal)
+    {
+    return types[ordinal];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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(Type type) throws InstantiationException, IllegalAccessException
+    {
+    return type.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.unscramble();
+              createEffectsPhase1(mDuration);
+              assignEffects(mPhase);
+              break;
+      case 1: mListener.effectFinished(FAKE_EFFECT_ID);
+              break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public long start(int duration, DistortedScreen screen, RubikCube cube, int numScrambles, 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("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
new file mode 100644
index 00000000..ab8e101b
--- /dev/null
+++ b/src/main/java/org/distorted/effect/ScrambleEffectNone.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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+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 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/magic/RubikActivity.java b/src/main/java/org/distorted/magic/RubikActivity.java
index 1f96ef47..7778a659 100644
--- a/src/main/java/org/distorted/magic/RubikActivity.java
+++ b/src/main/java/org/distorted/magic/RubikActivity.java
@@ -30,6 +30,7 @@ import android.support.v7.app.AppCompatActivity;
 import android.view.View;
 
 import org.distorted.component.HorizontalNumberPicker;
+import org.distorted.effect.ScrambleEffect;
 import org.distorted.effect.SizeChangeEffect;
 import org.distorted.effect.UnscrambleEffect;
 import org.distorted.library.main.DistortedLibrary;
@@ -44,16 +45,18 @@ public class RubikActivity extends AppCompatActivity implements RubikSettings.On
 
     public static final int DEFAULT_SIZECHANGE_POS = 20;
     public static final int DEFAULT_UNSCRAMBLE_POS = 20;
+    public static final int DEFAULT_SCRAMBLE_POS   = 20;
     public static final int DEFAULT_SIZECHANGE_TYPE= 1;
     public static final int DEFAULT_UNSCRAMBLE_TYPE= 1;
+    public static final int DEFAULT_SCRAMBLE_TYPE  = 0;
 
     public static final int MIN_SCRAMBLE =  1;
     public static final int MAX_SCRAMBLE = 10;
 
     private static int mSize = DEFAULT_SIZE;
 
-    private int mSizeChangePos, mUnscramblePos;
-    private int mSizeChangeType, mUnscrambleType;
+    private int mSizeChangePos, mUnscramblePos, mScramblePos;
+    private int mSizeChangeType, mUnscrambleType, mScrambleType;
 
     private HorizontalNumberPicker mPicker;
 
@@ -121,8 +124,10 @@ public class RubikActivity extends AppCompatActivity implements RubikSettings.On
 
       args.putInt("sizechangePos" , mSizeChangePos );
       args.putInt("unscramblePos" , mUnscramblePos );
+      args.putInt("scramblePos"   , mScramblePos   );
       args.putInt("sizechangeType", mSizeChangeType);
       args.putInt("unscrambleType", mUnscrambleType);
+      args.putInt("scrambleType"  , mScrambleType  );
 
       RubikSettings settings = new RubikSettings();
       settings.setArguments(args);
@@ -157,12 +162,14 @@ public class RubikActivity extends AppCompatActivity implements RubikSettings.On
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void onComplete(int sP, int uP, int sT, int uT )
+    public void onComplete(int sizeP, int unscP, int scraP, int sizeT, int unscT, int scraT )
       {
-      mSizeChangePos = sP;
-      mUnscramblePos = uP;
-      mSizeChangeType= sT;
-      mUnscrambleType= uT;
+      mSizeChangePos = sizeP;
+      mUnscramblePos = unscP;
+      mScramblePos   = scraP;
+      mSizeChangeType= sizeT;
+      mUnscrambleType= unscT;
+      mScrambleType  = scraT;
 
       applyPreferences();
       }
@@ -219,8 +226,10 @@ public class RubikActivity extends AppCompatActivity implements RubikSettings.On
 
      editor.putInt("sizechangePos" , mSizeChangePos );
      editor.putInt("unscramblePos" , mUnscramblePos );
+     editor.putInt("scramblePos"   , mScramblePos   );
      editor.putInt("sizechangeType", mSizeChangeType);
      editor.putInt("unscrambleType", mUnscrambleType);
+     editor.putInt("scrambleType"  , mScrambleType  );
      editor.putInt("scramble"      , mPicker.getValue() );
 
      editor.apply();
@@ -234,8 +243,10 @@ public class RubikActivity extends AppCompatActivity implements RubikSettings.On
 
      mSizeChangePos = preferences.getInt("sizechangePos" , DEFAULT_SIZECHANGE_POS );
      mUnscramblePos = preferences.getInt("unscramblePos" , DEFAULT_UNSCRAMBLE_POS );
+     mScramblePos   = preferences.getInt("scramblePos"   , DEFAULT_SCRAMBLE_POS   );
      mSizeChangeType= preferences.getInt("sizechangeType", DEFAULT_SIZECHANGE_TYPE);
      mUnscrambleType= preferences.getInt("unscrambleType", DEFAULT_UNSCRAMBLE_TYPE);
+     mScrambleType  = preferences.getInt("scrambleType"  , DEFAULT_SCRAMBLE_TYPE  );
      int scramble   = preferences.getInt("scramble"      , MIN_SCRAMBLE           );
 
      mPicker.setValue(scramble);
@@ -250,8 +261,10 @@ public class RubikActivity extends AppCompatActivity implements RubikSettings.On
 
      renderer.setSizeChangeDuration(translateDuration(mSizeChangePos)+1);
      renderer.setUnscrambleDuration(translateDuration(mUnscramblePos)+1);
+     renderer.setScrambleDuration(translateDuration(mScramblePos)+1);
      renderer.setSizeChangeType(SizeChangeEffect.getType(mSizeChangeType));
      renderer.setUnscrambleType(UnscrambleEffect.getType(mUnscrambleType));
+     renderer.setScrambleType(ScrambleEffect.getType(mScrambleType));
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/magic/RubikRenderer.java b/src/main/java/org/distorted/magic/RubikRenderer.java
index 5ead8672..a440450e 100644
--- a/src/main/java/org/distorted/magic/RubikRenderer.java
+++ b/src/main/java/org/distorted/magic/RubikRenderer.java
@@ -23,6 +23,7 @@ import android.opengl.GLSurfaceView;
 
 import org.distorted.effect.SizeChangeEffect;
 import org.distorted.effect.UnscrambleEffect;
+import org.distorted.effect.ScrambleEffect;
 import org.distorted.library.effect.VertexEffectSink;
 import org.distorted.library.main.DistortedEffects;
 import org.distorted.library.main.DistortedLibrary;
@@ -48,16 +49,17 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
     private Static4D mQuatCurrent, mQuatAccumulated;
     private Static4D mTempCurrent, mTempAccumulated;
     private float mCubeSizeInScreenSpace;
-    private int mNextCubeSize;
-    private long mRotationFinishedID, mSizeChangeEffectID, mUnscrambleEffectID;
-    private boolean mFinishRotation, mRemoveRotation, mFinishDragCurrent, mFinishDragAccumulated, mSolveCube;
+    private int mNextCubeSize, mScrambleCubeNum;
+    private long mRotationFinishedID, mSizeChangeEffectID, mUnscrambleEffectID, mScrambleEffectID;
+    private boolean mFinishRotation, mRemoveRotation, mFinishDragCurrent, mFinishDragAccumulated, mSolveCube, mScrambleCube;
     private boolean mCanRotate, mCanDrag;
     private RubikCube mOldCube, mNewCube;
     private int mScreenWidth, mScreenHeight;
     private MeshFlat mMesh;
     private SizeChangeEffect.Type mSizeChangeType;
     private UnscrambleEffect.Type mUnscrambleType;
-    private int mSizeChangeDuration, mUnscrambleDuration;
+    private ScrambleEffect.Type mScrambleType;
+    private int mSizeChangeDuration, mUnscrambleDuration, mScrambleDuration;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -76,20 +78,25 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
       mQuatAccumulated = new Static4D(0,0,0,1);
 
       mScreenWidth = mScreenHeight = 0;
+      mScrambleCubeNum = 0;
 
       mFinishRotation        = false;
       mRemoveRotation        = false;
       mFinishDragCurrent     = false;
       mFinishDragAccumulated = false;
       mSolveCube             = false;
+      mScrambleCube          = false;
 
       mCanRotate = true;
       mCanDrag   = true;
 
       mSizeChangeType    = SizeChangeEffect.Type.TRANSPARENCY;
       mUnscrambleType    = UnscrambleEffect.Type.SPIN;
+      mScrambleType      = ScrambleEffect.Type.NONE;
+
       mSizeChangeDuration= 1000;
       mUnscrambleDuration= 1000;
+      mScrambleDuration  = 1000;
 
       mMesh= new MeshFlat(20,20);
       mNextCubeSize =RubikActivity.getSize();
@@ -148,6 +155,14 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
         mCanRotate = false;
         unscrambleCubeNow();
         }
+
+      if( mScrambleCube )
+        {
+        mScrambleCube = false;
+        mCanDrag      = false;
+        mCanRotate    = false;
+        scrambleCubeNow();
+        }
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -178,6 +193,8 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
       {
       VertexEffectSink.enable();
       SizeChangeEffect.enableEffects();
+      ScrambleEffect.enableEffects();
+      UnscrambleEffect.enableEffects();
 
       try
         {
@@ -207,6 +224,11 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
        mCanRotate = true;
        mCanDrag   = true;
        }
+     else if( effectID == mScrambleEffectID    )
+       {
+       mCanRotate = true;
+       mCanDrag   = true;
+       }
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -259,6 +281,13 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
      mUnscrambleDuration = duration;
      }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void setScrambleDuration(int duration)
+     {
+     mScrambleDuration = duration;
+     }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
    void setSizeChangeType(SizeChangeEffect.Type type)
@@ -273,6 +302,13 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
      mUnscrambleType = type;
      }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void setScrambleType(ScrambleEffect.Type type)
+     {
+     mScrambleType = type;
+     }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
    boolean createCube(int newSize)
@@ -321,7 +357,26 @@ public class RubikRenderer implements GLSurfaceView.Renderer, EffectListener
 
    void scrambleCube(int num)
      {
-     android.util.Log.e("renderer","scrambling "+num+" times");
+     mScrambleCube = true;
+     mScrambleCubeNum = num;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void scrambleCubeNow()
+     {
+     try
+       {
+       ScrambleEffect effect = ScrambleEffect.create(mScrambleType);
+       mScrambleEffectID = effect.start(mScrambleDuration,mScreen,mNewCube,mScrambleCubeNum,this);
+       }
+     catch(Exception ex)
+       {
+       android.util.Log.e("Renderer", "failed to create ScrambleEffect, exception: "+ex.getMessage());
+
+       mCanRotate = true;
+       mCanDrag   = true;
+       }
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/magic/RubikSettings.java b/src/main/java/org/distorted/magic/RubikSettings.java
index 10372d7a..0e9f5879 100644
--- a/src/main/java/org/distorted/magic/RubikSettings.java
+++ b/src/main/java/org/distorted/magic/RubikSettings.java
@@ -38,6 +38,7 @@ import android.widget.TextView;
 
 import org.distorted.effect.SizeChangeEffect;
 import org.distorted.effect.UnscrambleEffect;
+import org.distorted.effect.ScrambleEffect;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -45,13 +46,13 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
   {
   public interface OnCompleteListener
     {
-    void onComplete(int sP, int uP, int sT, int uT);
+    void onComplete(int sizeP, int uscrP, int scraP, int sizeT, int unscT, int scraT);
     }
 
   private OnCompleteListener mListener;
-  private int mSizeChangePos, mUnscramblePos;
-  private int mSizeChangeType, mUnscrambleType;
-  private TextView mSizeChangeDuration, mUnscrambleDuration;
+  private int mSizeChangePos, mUnscramblePos, mScramblePos;
+  private int mSizeChangeType, mUnscrambleType, mScrambleType;
+  private TextView mSizeChangeDuration, mUnscrambleDuration, mScrambleDuration;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -83,15 +84,19 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
       {
       mSizeChangePos = args.getInt("sizechangePos");
       mUnscramblePos = args.getInt("unscramblePos");
+      mScramblePos   = args.getInt("scramblePos");
       mSizeChangeType= args.getInt("sizechangeType");
       mUnscrambleType= args.getInt("unscrambleType");
+      mScrambleType  = args.getInt("scrambleType");
       }
     catch(NullPointerException ex)
       {
       mSizeChangePos = RubikActivity.DEFAULT_SIZECHANGE_POS;
       mUnscramblePos = RubikActivity.DEFAULT_UNSCRAMBLE_POS;
+      mScramblePos   = RubikActivity.DEFAULT_SCRAMBLE_POS;
       mSizeChangeType= RubikActivity.DEFAULT_SIZECHANGE_TYPE;
       mUnscrambleType= RubikActivity.DEFAULT_UNSCRAMBLE_TYPE;
+      mScrambleType  = RubikActivity.DEFAULT_SCRAMBLE_TYPE;
       }
     }
 
@@ -118,9 +123,11 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
     final View view = inflater.inflate(R.layout.settings, null);
     builder.setView(view);
 
-    mSizeChangeDuration= view.findViewById(R.id.sizechangeDurationText);
+    mSizeChangeDuration = view.findViewById(R.id.sizechangeDurationText);
     mUnscrambleDuration = view.findViewById(R.id.unscrambleDurationText);
+    mScrambleDuration   = view.findViewById(R.id.scrambleDurationText);
 
+    /// SIZE CHANGE ///////////////////////////////////////////////////////
     Spinner sizechangeTypeSpinner  = view.findViewById(R.id.sizechangeType);
 
     if( sizechangeTypeSpinner!=null )
@@ -145,6 +152,7 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
     sizechangeBar.setOnSeekBarChangeListener(this);
     sizechangeBar.setProgress(mSizeChangePos);
 
+    /// UNSCRAMBLE ////////////////////////////////////////////////////////
     Spinner unscrambleTypeSpinner  = view.findViewById(R.id.unscrambleType);
 
     if( unscrambleTypeSpinner!=null )
@@ -169,6 +177,31 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
     unscrambleBar.setOnSeekBarChangeListener(this);
     unscrambleBar.setProgress(mUnscramblePos);
 
+    /// SCRAMBLE //////////////////////////////////////////////////////////
+    Spinner scrambleTypeSpinner  = view.findViewById(R.id.scrambleType);
+
+    if( scrambleTypeSpinner!=null )
+      {
+      scrambleTypeSpinner.setOnItemSelectedListener(this);
+      String[] scramble = ScrambleEffect.getNames();
+      ArrayAdapter<String> adapterType = new ArrayAdapter<>(act,android.R.layout.simple_spinner_item, scramble);
+      adapterType.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+      scrambleTypeSpinner.setAdapter(adapterType);
+
+      if(mScrambleType>=0 && mScrambleType<scramble.length)
+        {
+        scrambleTypeSpinner.setSelection(mScrambleType);
+        }
+      }
+    else
+      {
+      android.util.Log.e("dialog", "SCRAMBLE TYPE SPINNER NULL!!");
+      }
+
+    SeekBar scrambleBar = view.findViewById(R.id.scrambleDuration);
+    scrambleBar.setOnSeekBarChangeListener(this);
+    scrambleBar.setProgress(mScramblePos);
+
     return builder.create();
     }
 
@@ -176,7 +209,7 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
 
     private void saveOptions()
       {
-      mListener.onComplete(mSizeChangePos, mUnscramblePos, mSizeChangeType, mUnscrambleType);
+      mListener.onComplete(mSizeChangePos, mUnscramblePos, mScramblePos, mSizeChangeType, mUnscrambleType, mScrambleType);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -187,6 +220,7 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
         {
         case R.id.sizechangeType: mSizeChangeType= pos; break;
         case R.id.unscrambleType: mUnscrambleType= pos; break;
+        case R.id.scrambleType  : mScrambleType  = pos; break;
         }
       }
 
@@ -204,6 +238,10 @@ public class RubikSettings extends AppCompatDialogFragment implements SeekBar.On
                                       int unscramble_ms= RubikActivity.translateDuration(mUnscramblePos);
                                       mUnscrambleDuration.setText(getString(R.string.ms_placeholder,unscramble_ms));
                                       break;
+        case R.id.scrambleDuration  : mScramblePos= progress;
+                                      int scramble_ms= RubikActivity.translateDuration(mScramblePos);
+                                      mScrambleDuration.setText(getString(R.string.ms_placeholder,scramble_ms));
+                                      break;
         }
       }
 
diff --git a/src/main/res/layout/settings.xml b/src/main/res/layout/settings.xml
index 9e1fb2b3..f1944e88 100644
--- a/src/main/res/layout/settings.xml
+++ b/src/main/res/layout/settings.xml
@@ -172,4 +172,86 @@
         </LinearLayout>
     </LinearLayout>
 
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="48dp"
+        android:paddingStart="15dp"
+        android:paddingEnd="15dp"
+        android:gravity="start|center"
+        android:text="@string/scramble_effect"
+        android:textAppearance="?android:attr/textAppearanceMedium" />
+
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_weight="0.5"
+        android:gravity="center|fill_horizontal"
+        android:layout_marginLeft="10dp"
+        android:layout_marginRight="10dp"
+        android:background="@color/grey"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="36dp"
+            android:gravity="center|fill_horizontal"
+            android:orientation="horizontal">
+
+            <TextView
+                android:layout_weight="0.2"
+                android:layout_width="0dp"
+                android:layout_height="fill_parent"
+                android:paddingStart="5dp"
+                android:paddingEnd="5dp"
+                android:gravity="start|center"
+                android:text="@string/duration"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <TextView
+                android:id="@+id/scrambleDurationText"
+                android:layout_weight="0.2"
+                android:layout_width="0dp"
+                android:layout_height="fill_parent"
+                android:paddingStart="5dp"
+                android:paddingEnd="5dp"
+                android:gravity="end|center"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <SeekBar
+                android:id="@+id/scrambleDuration"
+                android:layout_weight="0.6"
+                android:layout_width="0dp"
+                android:layout_height="fill_parent"
+                android:paddingLeft="10dp"
+                android:paddingRight="10dp" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="36dp"
+            android:gravity="center|fill_horizontal"
+            android:orientation="horizontal">
+
+            <TextView
+                android:layout_weight="0.4"
+                android:layout_width="0dp"
+                android:layout_height="fill_parent"
+                android:paddingStart="5dp"
+                android:paddingEnd="5dp"
+                android:gravity="start|center"
+                android:text="@string/type"
+                android:textAppearance="?android:attr/textAppearanceSmall" />
+
+            <Spinner
+                android:id="@+id/scrambleType"
+                android:layout_weight="0.6"
+                android:layout_width="0dp"
+                android:layout_height="fill_parent"
+                android:textAlignment="center"
+                android:paddingLeft="10dp"
+                android:paddingRight="10dp" />
+
+        </LinearLayout>
+    </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 4b9b7fda..e5c658cd 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -10,6 +10,7 @@
     <string name="ok">OK</string>
     <string name="sizechange_effect">Cube Size Change Effect:</string>
     <string name="unscramble_effect">Cube Unscramble Effect:</string>
+    <string name="scramble_effect">Cube Scramble Effect:</string>
     <string name="duration">Duration:</string>
     <string name="type">Type:</string>
     <string name="credits1">Open Source app developed using the Distorted graphics library. </string>
