commit adbd16d93a8020a191dfc24d1e8dabbbb54f8cca
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Mon Jul 4 15:08:00 2022 +0200

    New 'Free Play' screen.

diff --git a/src/main/java/org/distorted/dialogs/RubikDialogFreePlaySettings.java b/src/main/java/org/distorted/dialogs/RubikDialogFreePlaySettings.java
new file mode 100644
index 00000000..a5896215
--- /dev/null
+++ b/src/main/java/org/distorted/dialogs/RubikDialogFreePlaySettings.java
@@ -0,0 +1,199 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.dialogs;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatDialogFragment;
+import androidx.fragment.app.FragmentActivity;
+
+import org.distorted.main.RubikActivity;
+import org.distorted.screens.RubikScreenFreePlay;
+import org.distorted.main.R;
+import org.distorted.screens.ScreenList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikDialogFreePlaySettings extends AppCompatDialogFragment implements AdapterView.OnItemSelectedListener
+  {
+  private int mAnimPos, mScraPos;
+  private float mTextSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String[] createScrambleModes()
+    {
+    int len = RubikScreenFreePlay.DEPTHS.length;
+    String[] ret = new String[len];
+
+    for(int i=0; i<len; i++)
+      {
+      ret[i] = String.valueOf(RubikScreenFreePlay.DEPTHS[i]);
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void configureView(FragmentActivity act, View view, float textSize, int scraPos, int animPos)
+    {
+    TextView text1 = view.findViewById(R.id.bandaged_text_depth);
+    text1.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+    TextView text2 = view.findViewById(R.id.bandaged_text_animation);
+    text2.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+
+    mScraPos = scraPos;
+    mAnimPos = animPos;
+
+    Spinner scramble = view.findViewById(R.id.bandaged_spinner_scramble);
+    scramble.setOnItemSelectedListener(this);
+    String[] scrambleModes = createScrambleModes();
+
+    ArrayAdapter<String> scrambleAdapter = new ArrayAdapter<>(act, R.layout.spinner_item, scrambleModes );
+    scrambleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+    scramble.setAdapter(scrambleAdapter);
+    scramble.setSelection(mScraPos);
+
+    Spinner animation = view.findViewById(R.id.bandaged_spinner_animation);
+    animation.setOnItemSelectedListener(this);
+    String[] animationModes = { "ON" , "OFF" };
+
+    ArrayAdapter<String> animationAdapter = new ArrayAdapter<>(act, R.layout.spinner_item, animationModes );
+    animationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+    animation.setAdapter(animationAdapter);
+    animation.setSelection(mAnimPos);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
+    {
+    int spinnerID = parent.getId();
+
+    ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX,mTextSize);
+
+    if( spinnerID == R.id.bandaged_spinner_scramble )
+      {
+      mScraPos = pos;
+      }
+    else if( spinnerID == R.id.bandaged_spinner_animation )
+      {
+      mAnimPos = pos;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void onNothingSelected(AdapterView<?> parent) { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @NonNull
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState)
+    {
+    FragmentActivity act = getActivity();
+    LayoutInflater inflater = act.getLayoutInflater();
+    AlertDialog.Builder builder = new AlertDialog.Builder(act);
+
+    DisplayMetrics displaymetrics = new DisplayMetrics();
+    act.getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
+    final float titleSize= displaymetrics.widthPixels * RubikActivity.MENU_BIG_TEXT_SIZE;
+    final float okSize   = displaymetrics.widthPixels * RubikActivity.DIALOG_BUTTON_SIZE;
+    mTextSize = displaymetrics.widthPixels * RubikActivity.MENU_BIG_TEXT_SIZE;
+
+    TextView tv = (TextView) inflater.inflate(R.layout.dialog_title, null);
+    tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize);
+    tv.setText(R.string.settings);
+    builder.setCustomTitle(tv);
+
+    Bundle args = getArguments();
+    int scraPos, animPos;
+
+    try
+      {
+      scraPos = args.getInt("scraPos");
+      animPos = args.getInt("animPos");
+      }
+    catch(Exception e)
+      {
+      scraPos = 0;
+      animPos = 0;
+      }
+
+    builder.setCancelable(true);
+    builder.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener()
+      {
+      @Override
+      public void onClick(DialogInterface dialog, int which)
+        {
+        RubikScreenFreePlay free = (RubikScreenFreePlay) ScreenList.FREE.getScreenClass();
+
+        if( free!=null )
+          {
+          free.setScrambleDepth(mScraPos);
+          free.setAnimationMode(mAnimPos);
+          }
+        }
+      });
+
+    final View view = inflater.inflate(R.layout.dialog_settings, null);
+    configureView(act,view,mTextSize,scraPos,animPos);
+    builder.setView(view);
+
+    Dialog dialog = builder.create();
+    dialog.setCanceledOnTouchOutside(false);
+    Window window = dialog.getWindow();
+
+    if( window!=null )
+      {
+      window.getDecorView().setSystemUiVisibility(RubikActivity.FLAGS);
+      }
+
+    dialog.setOnShowListener(new DialogInterface.OnShowListener()
+      {
+      @Override
+      public void onShow(DialogInterface dialog)
+        {
+        Button btnPositive = ((AlertDialog)dialog).getButton(Dialog.BUTTON_POSITIVE);
+        btnPositive.setTextSize(TypedValue.COMPLEX_UNIT_PX, okSize);
+        }
+      });
+
+    return dialog;
+    }
+  }
diff --git a/src/main/java/org/distorted/main/RubikObjectLibInterface.java b/src/main/java/org/distorted/main/RubikObjectLibInterface.java
index 3ca4eb1d..59424f94 100644
--- a/src/main/java/org/distorted/main/RubikObjectLibInterface.java
+++ b/src/main/java/org/distorted/main/RubikObjectLibInterface.java
@@ -45,6 +45,7 @@ import org.distorted.dialogs.RubikDialogSolved;
 import org.distorted.external.RubikScores;
 import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
+import org.distorted.screens.RubikScreenFreePlay;
 import org.distorted.screens.RubikScreenPlay;
 import org.distorted.screens.RubikScreenReady;
 import org.distorted.screens.RubikScreenSolver;
@@ -264,6 +265,11 @@ public class RubikObjectLibInterface implements ObjectLibInterface
       RubikScreenPlay play = (RubikScreenPlay) ScreenList.PLAY.getScreenClass();
       play.addMove(mAct.get(), axis, row, angle);
       }
+    if( ScreenList.getCurrentScreen()== ScreenList.FREE )
+      {
+      RubikScreenFreePlay free = (RubikScreenFreePlay) ScreenList.FREE.getScreenClass();
+      free.addMove(mAct.get(), axis, row, angle);
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -308,6 +314,11 @@ public class RubikObjectLibInterface implements ObjectLibInterface
       RubikScreenSolving solv = (RubikScreenSolving) ScreenList.SOLV.getScreenClass();
       solv.reddenLock(mAct.get());
       }
+    else if( curr==ScreenList.FREE )
+      {
+      RubikScreenFreePlay free = (RubikScreenFreePlay) ScreenList.FREE.getScreenClass();
+      free.reddenLock(mAct.get());
+      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/screens/RubikScreenFreePlay.java b/src/main/java/org/distorted/screens/RubikScreenFreePlay.java
new file mode 100644
index 00000000..0422f3b7
--- /dev/null
+++ b/src/main/java/org/distorted/screens/RubikScreenFreePlay.java
@@ -0,0 +1,233 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.screens;
+
+import java.util.Random;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import org.distorted.dialogs.RubikDialogFreePlaySettings;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.main.RubikActivity;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObject;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikScreenFreePlay extends RubikScreenBase
+{
+  public static final int[] DEPTHS = new int[] {20,50,100,200,500,1000};
+  public static final int ANIMATION_ON  = 0;
+  public static final int ANIMATION_OFF = 1;
+
+  private TransparentImageButton mBackButton, mScrambleButton, mSolveButton, mSettingsButton;
+  private int[][] mMoves;
+  private Random mRnd;
+  private int mAnimationMode;
+  private int mScrambleDepth;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void leaveScreen(final RubikActivity act)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void enterScreen(final RubikActivity act)
+    {
+    int width = act.getScreenWidthInPixels();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams(width/2, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    // UPPER PANE /////////////
+
+    setupSolveButton(act);
+    setupSettingsButton(act);
+    setupScrambleButton(act);
+
+    LinearLayout layoutUpper = act.findViewById(R.id.upperBar);
+
+    LinearLayout layoutLeftU = new LinearLayout(act);
+    layoutLeftU.setLayoutParams(paramsL);
+    LinearLayout layoutMidU  = new LinearLayout(act);
+    layoutMidU.setLayoutParams(paramsM);
+    LinearLayout layoutRightU= new LinearLayout(act);
+    layoutRightU.setLayoutParams(paramsR);
+
+    layoutLeftU.addView(mSolveButton);
+    layoutMidU.addView(mSettingsButton);
+    layoutRightU.addView(mScrambleButton);
+
+    layoutUpper.removeAllViews();
+    layoutUpper.addView(layoutLeftU);
+    layoutUpper.addView(layoutMidU);
+    layoutUpper.addView(layoutRightU);
+
+    // LOWER PANE /////////////
+
+    setupBackButton(act);
+    createBottomPane(act,mBackButton,null);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final RubikActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_smallback,R.drawable.ui_medium_smallback, R.drawable.ui_big_smallback, R.drawable.ui_huge_smallback);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mBackButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ScreenList.goBack(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupSettingsButton(final RubikActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_settings,R.drawable.ui_medium_settings, R.drawable.ui_big_settings, R.drawable.ui_huge_settings);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mSettingsButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mSettingsButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        Bundle bundle = new Bundle();
+        bundle.putInt("scraPos", mScrambleDepth );
+        bundle.putInt("animPos", mAnimationMode );
+
+        RubikDialogFreePlaySettings setDiag = new RubikDialogFreePlaySettings();
+        setDiag.setArguments(bundle);
+        setDiag.show(act.getSupportFragmentManager(), null);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupSolveButton(final RubikActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_solve,R.drawable.ui_medium_cube_solve, R.drawable.ui_big_cube_solve, R.drawable.ui_huge_cube_solve);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mSolveButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE,params);
+
+    mSolveButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        if( mAnimationMode==ANIMATION_OFF ) control.solveOnly();
+        if( mAnimationMode==ANIMATION_ON  ) control.solveObject();
+        mMovesController.clearMoves(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupScrambleButton(final RubikActivity act)
+    {
+    int icon = RubikActivity.getDrawable(R.drawable.ui_small_cube_scramble,R.drawable.ui_medium_cube_scramble, R.drawable.ui_big_cube_scramble, R.drawable.ui_huge_cube_scramble);
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mScrambleButton = new TransparentImageButton(act, icon, TransparentImageButton.GRAVITY_MIDDLE, params);
+
+    mScrambleButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        TwistyObject object = control.getObject();
+
+        int depth = DEPTHS[mScrambleDepth];
+
+        if( mAnimationMode==ANIMATION_OFF )
+          {
+          if( mMoves==null || mMoves.length<depth ) mMoves = new int[depth][3];
+          if( mRnd==null ) mRnd = new Random();
+
+          for(int move=0; move<depth; move++)
+            {
+            object.randomizeNewScramble(mMoves, mRnd, move, depth);
+            }
+          for(int move=0; move<depth; move++)
+            {
+            int row = mMoves[move][1];
+            mMoves[move][1] = (1<<row);
+            }
+
+          control.initializeObject(mMoves);
+          }
+
+        if( mAnimationMode==ANIMATION_ON )
+          {
+          control.scrambleObject(depth);
+          }
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(SharedPreferences.Editor editor)
+    {
+    editor.putInt("freePlayScreen_scramble" , mScrambleDepth);
+    editor.putInt("freePlayScreen_animation", mAnimationMode);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(SharedPreferences preferences)
+    {
+    mScrambleDepth = preferences.getInt("freePlayScreen_scramble" ,3);
+    mAnimationMode = preferences.getInt("freePlayScreen_animation",ANIMATION_OFF);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setAnimationMode(int mode)
+    {
+    mAnimationMode = mode;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setScrambleDepth(int depth)
+    {
+    mScrambleDepth = depth;
+    }
+}
diff --git a/src/main/java/org/distorted/screens/RubikScreenPlay.java b/src/main/java/org/distorted/screens/RubikScreenPlay.java
index 8a89b179..9be24589 100644
--- a/src/main/java/org/distorted/screens/RubikScreenPlay.java
+++ b/src/main/java/org/distorted/screens/RubikScreenPlay.java
@@ -64,7 +64,7 @@ import static android.view.View.inflate;
 public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Updatee
   {
   public static final int NUM_COLUMNS  = 5;
-  public static final int LEVELS_SHOWN = 10;
+  public static final int LEVELS_SHOWN = 8;
 
   private static final int[] BUTTON_LABELS = { R.string.scores,
                                                R.string.patterns,
@@ -74,7 +74,7 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
                                                R.string.about };
   private static final int NUM_BUTTONS = BUTTON_LABELS.length;
 
-  private static final float LAST_BUTTON = 1.5f;
+  private static final float BIG_BUTTON = 1.5f;
   private static final int[] mLocation = new int[2];
 
   private TransparentImageButton mObjButton, mMenuButton, mSolveButton, mScrambleButton;
@@ -194,8 +194,8 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
           popupView.setSystemUiVisibility(RubikActivity.FLAGS);
           final int object  = RubikObjectList.getCurrObject();
           final int dbLevel = RubikObjectList.getDBLevel(object);
-          final int levelsShown = Math.min(dbLevel,LEVELS_SHOWN);
-          final int popupHeight = (int)(levelsShown*(mMenuItemSize+margin)+3*margin+mMenuItemSize*(LAST_BUTTON-1.0f));
+          final int levelsShown = Math.min(dbLevel,LEVELS_SHOWN)+1;
+          final int popupHeight = (int)(levelsShown*(mMenuItemSize+margin)+4*margin+2*mMenuItemSize*(BIG_BUTTON-1.0f));
           final int realHeight = Math.min(popupHeight,maxHeight);
           displayPopup(act,view,mPlayPopup,mPlayLayoutWidth,realHeight,margin,margin);
           }
@@ -237,9 +237,9 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
 
   private void setupObjectWindow(final RubikActivity act, final float width, final float height)
     {
-    int cubeWidth = (int)(width/9);
-    int margin = (int)(width*RubikActivity.LARGE_MARGIN);
-    mObjectSize = (int)(cubeWidth + 2*margin + 0.5f);
+    int cubeSize = (int)(width/9);
+    int margin   = (int)(width*RubikActivity.LARGE_MARGIN);
+    mObjectSize  = (int)(cubeSize + 2*margin + 0.5f);
     mMaxRowCount = (int)((height-1.8f*mUpperBarHeight)/mObjectSize);
 
     LinearLayout view = (LinearLayout)inflate( act, R.layout.popup_object, null);
@@ -303,8 +303,8 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
       params.leftMargin   = margin;
       params.rightMargin  = margin;
 
-      params.width = cubeWidth;
-      params.height= cubeWidth;
+      params.width = cubeSize;
+      params.height= cubeSize;
 
       nextInRow[row]++;
 
@@ -603,12 +603,12 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
       {
       int currObject = RubikObjectList.getCurrObject();
       int dbLevel = RubikObjectList.getDBLevel(currObject);
-      int numLevel= Math.min(dbLevel, LEVELS_SHOWN);
+      int numLevel= Math.min(dbLevel, LEVELS_SHOWN)+1;
       RubikScores scores = RubikScores.getInstance();
 
       for(int i=0; i<numLevel; i++)
         {
-        int level = i<numLevel-1 ? i+1 : dbLevel;
+        int level = i<numLevel-1 ? i : dbLevel;
         Button button = (Button)mPlayLayout.getChildAt(i);
         int icon = scores.isSolved(currObject, level-1) ? R.drawable.ui_solved : R.drawable.ui_notsolved;
         button.setCompoundDrawablesWithIntrinsicBounds(icon,0,0,0);
@@ -624,55 +624,39 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
     int dbLevel = RubikObjectList.getDBLevel(currObject);
     RubikObject object = RubikObjectList.getObject(currObject);
     int numScrambles = object==null ? 0 : object.getNumScramble();
-    int numLevel = Math.min(dbLevel, LEVELS_SHOWN);
+    int numLevel = Math.min(dbLevel, LEVELS_SHOWN)+1;
     String[] levels = new String[numLevel];
 
-    for(int i=0; i<numLevel-1; i++)
-      {
-      levels[i] = act.getString(R.string.lv_placeholder,i+1);
-      }
-
-    if( numLevel>0 )
-      {
-      levels[numLevel-1] = act.getString(R.string.level_full);
-      }
+    levels[0] = act.getString(R.string.free_play);
+    for(int i=1; i<numLevel-1; i++) levels[i] = act.getString(R.string.lv_placeholder,i);
+    levels[numLevel-1] = act.getString(R.string.level_full);
 
     if( mLevelValue>dbLevel || mLevelValue<1 ||
-       (mLevelValue<dbLevel || mLevelValue>LEVELS_SHOWN ) )
-      {
-      mLevelValue=1;
-      }
+       (mLevelValue<dbLevel || mLevelValue>LEVELS_SHOWN ) ) mLevelValue=1;
 
     float width  = act.getScreenWidthInPixels();
     int margin   = (int)(width*RubikActivity.SMALL_MARGIN);
     int padding  = (int)(width*RubikActivity.PADDING);
     int butWidth = mPlayLayoutWidth - 2*padding;
     int butHeight= (int)mMenuItemSize;
-    int lastButH = (int)(mMenuItemSize*LAST_BUTTON) ;
+    int bigButH  = (int)(mMenuItemSize*BIG_BUTTON) ;
 
     LinearLayout.LayoutParams pM = new LinearLayout.LayoutParams( butWidth, butHeight );
     pM.setMargins(margin, 0, margin, margin);
-    LinearLayout.LayoutParams pT = new LinearLayout.LayoutParams( butWidth, butHeight );
-    pT.setMargins(margin, margin, margin, margin);
-    LinearLayout.LayoutParams pB = new LinearLayout.LayoutParams( butWidth, lastButH  );
+    LinearLayout.LayoutParams pB = new LinearLayout.LayoutParams( butWidth, bigButH   );
     pB.setMargins(margin, margin, margin, 2*margin);
 
     mPlayLayout.removeAllViews();
 
-    RubikScores scores = RubikScores.getInstance();
-
     for(int i=0; i<numLevel; i++)
       {
-      final int level     = i<numLevel-1 ? i+1 : dbLevel;
-      final int scrambles = i<numLevel-1 ? i+1 : numScrambles;
+      final int level     = i<numLevel-1 ? i : dbLevel;
+      final int scrambles = i<numLevel-1 ? i : numScrambles;
       Button button = new Button(act);
-      button.setLayoutParams(i==0 ? pT : (i==numLevel-1 ? pB : pM));
+      button.setLayoutParams(i==0 ? pB : (i==numLevel-1 ? pB : pM));
       button.setText(levels[i]);
       button.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
 
-      int icon = scores.isSolved(currObject, level-1) ? R.drawable.ui_solved : R.drawable.ui_notsolved;
-      button.setCompoundDrawablesWithIntrinsicBounds(icon,0,0,0);
-
       button.setOnClickListener( new View.OnClickListener()
         {
         @Override
@@ -683,9 +667,17 @@ public class RubikScreenPlay extends RubikScreenBase implements RubikNetwork.Upd
           if(control.isUINotBlocked())
             {
             if( mPlayPopup!=null ) mPlayPopup.dismiss();
-            mLevelValue = level;
-            mShouldReactToEndOfScrambling = true;
-            control.scrambleObject(scrambles);
+
+            if( level>0 )
+              {
+              mLevelValue = level;
+              mShouldReactToEndOfScrambling = true;
+              control.scrambleObject(scrambles);
+              }
+             else
+              {
+              ScreenList.switchScreen(act, ScreenList.FREE);
+              }
             }
           }
         });
diff --git a/src/main/java/org/distorted/screens/ScreenList.java b/src/main/java/org/distorted/screens/ScreenList.java
index 61b2a7c5..4c873c77 100644
--- a/src/main/java/org/distorted/screens/ScreenList.java
+++ b/src/main/java/org/distorted/screens/ScreenList.java
@@ -38,6 +38,7 @@ public enum ScreenList
   SOLU ( SVER , MODE_DRAG   , new RubikScreenSolution() ),
   READ ( PLAY , MODE_ROTATE , new RubikScreenReady()    ),
   DONE ( PLAY , MODE_DRAG   , new RubikScreenDone()     ),
+  FREE ( PLAY , MODE_ROTATE , new RubikScreenFreePlay() ),
   ;
 
   public static final int LENGTH = values().length;
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index a1e5d38a..8d37be2d 100755
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">Netzwerkfehler</string>
     <string name="success">Erfolg</string>
     <string name="view">Sehen</string>
+    <string name="free_play">Freies Spiel</string>
     <string name="level_full">Volles Scramble</string>
     <string name="save_object">Speichern</string>
     <string name="save_object_really">Möchten Sie diesen Cube speichern?</string>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index 0fc960aa..49c0016b 100755
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">Error de red</string>
     <string name="success">Éxito</string>
     <string name="view">Ver</string>
+    <string name="free_play">Juego libre</string>
     <string name="level_full">Revuelto Completo</string>
     <string name="save_object">Guardar</string>
     <string name="save_object_really">¿Quieres guardar este Cubo?</string>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index 7eb774c4..fba6f4b1 100755
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">Erreur réseau</string>
     <string name="success">Succès</string>
     <string name="view">Regarder</string>
+    <string name="free_play">Jeu libre</string>
     <string name="level_full">Brouillage Complet</string>
     <string name="save_object">Enregistrer</string>
     <string name="save_object_really">Voulez-vous enregistrer ce Cube?</string>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index 6355ad88..2a5c8a25 100755
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">ネットワークエラー</string>
     <string name="success">成功</string>
     <string name="view">見る</string>
+    <string name="free_play">無料プレイ</string>
     <string name="level_full">フルスクランブル</string>
     <string name="save_object">保存</string>
     <string name="save_object_really">このキューブを保存しますか?</string>
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index a4972d05..499da576 100755
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">네트워크 오류</string>
     <string name="success">성공</string>
     <string name="view">보다</string>
+    <string name="free_play">게임</string>
     <string name="level_full">풀 스크램블</string>
     <string name="save_object">저장</string>
     <string name="save_object_really">이 큐브를 저장하시겠습니까?</string>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 81718d09..30171aed 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">Błąd sieci</string>
     <string name="success">Sukces</string>
     <string name="view">Zobacz</string>
+    <string name="free_play">Zabawa z Kostką</string>
     <string name="level_full">Pełne Pomieszanie</string>
     <string name="save_object">Zapisz</string>
     <string name="save_object_really">Chcesz zapisać tą kostkę?</string>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index fa8ed10e..059bd690 100755
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">Ошибка сети</string>
     <string name="success">Успех</string>
     <string name="view">Смотри</string>
+    <string name="free_play">Игра с кубом</string>
     <string name="level_full">Полная Схватка</string>
     <string name="save_object">Сохранять</string>
     <string name="save_object_really">Вы хотите сохранить этот куб?</string>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index f52e1928..0949b4ab 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">網絡錯誤</string>
     <string name="success">成功</string>
     <string name="view">看</string>
+    <string name="free_play">玩立方体</string>
     <string name="level_full">级满</string>
     <string name="save_object">保存</string>
     <string name="save_object_really">你想保存这个多维数据集吗?</string>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index fccabaf1..178b9cb6 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -36,6 +36,7 @@
     <string name="networkError">網絡錯誤</string>
     <string name="success">成功</string>
     <string name="view">看</string>
+    <string name="free_play">玩立方體</string>
     <string name="level_full">級滿</string>
     <string name="save_object">保存</string>
     <string name="save_object_really">你想保存這個多維數據集嗎?</string>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 155ee4b3..c23e7337 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -37,6 +37,7 @@
     <string name="networkError">Network Error</string>
     <string name="success">Success</string>
     <string name="view">View</string>
+    <string name="free_play">Free Play</string>
     <string name="level_full">Full Scramble</string>
     <string name="save_object">Save</string>
     <string name="save_object_really">Do you want to save this Cube?</string>
