DeepCube: A Generalized 3D Cube Puzzle

DeepCube, version 0.861;  June 19, 2015;  Louis J. Palumbo

© 2015, Louis J. Palumbo  (loupalumbo@comcast.net)

Notes

User’s Notes

DeepCube is a computer simulation of a generalized 3D cube puzzle.   Inspired by the popular and time-tested Rubik’s Cube, it extends the original 3x3x3 cube concept to cube lengths ranging from 1 through 9.   Additionally, it models the motions and orientations of interior as well as exterior subcubes, each with all six of their faces viewable and appropriately colored.  With a cube length of 5, for example, you are challenged to solve the puzzle by aligning simultaneously 125 subcubes.

DeepCube also supplies several tools to help you see inside the cube and to keep track of your moves as you develop useful layer-rotation sequences.  You can see many of these capabilities by running the sequences in the “demos” pop-up menu in the “sequences & patterns” panel.  Of course, you can customize colors and styles to your liking.

User documentation is contained mainly in Tooltips, which are available for nearly all controls by hovering over them with the mouse.  The best way to start using DeepCube is to jump on in and experiment.

DeepCube requires a screen size of at least 1280 by 1024 pixels.

Programmer’s Notes

Code Structure, Style & Documentation

The code and documentation are all contained in this single notebook.  The code consists of an initialization part containing definitions of global constants, variables and functions followed a single Manipulate function, which handles all controls and animation.  These definitions are grouped, organized and annotated using various text styles (section, subsection, plain text, etc.) available in Mathematica notebooks along with copious comments.  Detailed programming documentation is provided for individual functions and variables.

I have tried to use a consistent programming style that emphasizes readability and maintainability.  Hopefully most parts of the code will be easy for other Mathematica programmers to follow and, possibly, modify and enhance.

Animation

The programming approach for animation of layer turns is based on:  Roman E. Maeder, “Rubik’s Cube”,   http://demonstrations.wolfram.com/RubiksCube/,  Wolfram Demonstrations Project ,  Published: July 26, 2007.

The frame rendering rate is affected predominantly by the length (number of bytes) of the Graphics3D objects (created by function displayGraphics) that define the graphic to be drawn in the Manipulate output cell.  Since the Graphics3D objects are mainly GraphicComplex’s, that render the subcubes, this length increases as the cube becomes more scrambled.   And, of course, this length also increases approximately as the third power of the cube length.  The user has several options to speed up rendering, for example: reducing the number of animation steps or turning sequence animation off altogether.

Buffers for Move Sequences

All executed moves (layer rotations) are captured in a linear history buffer, which is large enough to hold the most recent ~6000 moves.  When the buffer gets full, the oldest ~1000 moves are discarded to make room for new ones.  Similarly, the recording buffer and each of the 50 user-defined-sequences buffers are also implemented as linear buffers .

Sequences & Patterns

Several system-supplied move sequences can be executed by choosing pop-up menu items in the “sequences & patterns” panel.  This set of move sequences is by no means complete.  It is intended as a “starter set” that will enable the user to see the effect of these sequences and to produce interesting patterns.

Prefix Conventions for Symbol Names

Many variable names and function names are decorated with prefixes of a few lower case characters which
indicate their purpose and usage.  Most of these prefixes and their meanings are listed here:

	ctl: Implementation of a group of controls
	cv: Control variable
	debug:  Global variable used to hold testing and debugging information
	gb: Global variable that can be very big and is NOT saved to a cube-state file.
	gc:  Global Constant
	gs:  Global variable that’s saved to and loaded from a cube-state file
	hist: Functions for move history
	ma:  Module argument that usually corresponds to a control variable with a similar name.
	mf:  Functions for various move formats and their interconversions (Text, List, Packed integer)
	ml: Local Module variable that usually corresponds to a control variable with a similar name.
	rec: Functions for recording and playback of user-defined sequences
	sa:  Functions and constants for Special Actions
	seq:   A function that creates a move sequence
	ssq:  A move-sequence support function called by one or more seq functions.
	testF: Function use for unit and component testing
	xsa: Index values for Special Actions (These are global constants)

Initialization:  Everything but the Manipulate

© 2015, Louis J. Palumbo  (loupalumbo@comcast.net)

Global Initialization and Clearing

a=1;  ClearAll["Global`*"];   Remove["Global`*"];  
On[Assert];    (*  Do I need to turn this off for CDF Demo???   <<<<<<------ *)(*<<<<*)
$HistoryLength=0;  
 (* {NotebookDirectory[],NotebookFileName[]}//Column  *)

Functions that Compute Global Constant Tables

computeUnitScVertexCoords -- Vertex coordinates for a unit half-length subcube

Compute the coordinates of the 8 vertices of a subcube of unit half-length centered at the origin
for 24 possible orientations.  These coordinates are used as part of a GraphicsComplex
to render the subcubes.
DIMENSIONS:  24 orientations, 8 vertices, 3 spatial dimensions (x, y, z).  
The actual subcube coordinates are computed by multiplying the resulting array by the
subcube size (cvSubcubeSize), which typically ranges from 40 to 100.
CALLS:  NONE

computeUnitScVertexCoords[]:=Module[{ix,ax1,
allaxes=Tuples[{-1,0,1},3],
c0=Tuples[{-1,1},3],
coords={}
},
For[ix=1,ix≤Length@allaxes,ix++,
ax1=allaxes[[ix]];
Switch[Total[Abs[ax1]],
0, (* -------- Identity *)
AppendTo[coords,c0],
1, (* -------- Coordinate Axes: 90 & 180 degrees *)
AppendTo[coords,RotationTransform[Pi/2,ax1][c0]];
AppendTo[coords,RotationTransform[Pi,ax1][c0]],
2,(* -------- Centers of opposite edges: 180 degrees *)
AppendTo[coords,RotationTransform[Pi,ax1][c0]],
3,(* -------- Opposite corners: 120 degrees *)
AppendTo[coords,RotationTransform[2Pi/3,ax1][c0]]
];
];
Union@coords  (* Delete duplicates, sort and return *)
];

computeInPlaceRotTable  --  In-place subcube rotation look up table (global constant)

Create a look up table that specifies how each of the 24 possible subcube 
 orientations changes due to rotations of 0, 90, 180, -90, and -180 degrees about the
 x, y and z axes. 
 DIMENSIONS:  3 axes, 24 orientations, 5 rotation angles.
USES GLOBALS:  gcUnitScVertexCoords

computeInPlaceRotTable[]:=Module[{angles, result},
angles={0,Pi/2,Pi,-Pi/2,-Pi};               (*  5 rotation angles *)
result=Table[First@First@Position[gcUnitScVertexCoords,
RotationTransform[angles[[k]],UnitVector[3,i]][gcUnitScVertexCoords[[j]]]],
{i,3},{j,24},{k,5}];
result  (*  Returned value: A table of integers representing subcube orientations  *)
];

computeViewPtAngles -- (for Slider2D controlling cvViewPtAngle)

Compute angles needed for the Slider2D that controls cvViewPtAngle.
o  13 steps in both horizontal and vertical directions.
o  Step size == 5 degrees.   Range: ~15 to ~75 degrees
o  A small adjustment value of  ArcTan[Sqrt[2]]-55.0 = -0.2643897 degrees
	is added to each element in the list of vertical viewing angles.
o  This adjustment produces a list of angles such that one of the vertical angles
	 in the list (54.7361 degrees), combined with the horizontal angle of 
	45 degrees, results in a viewpoint lying exactly on the cube diagonal. 
o  For convenience, computations are done in degrees.  Results are converted 
 	to radians for output.
 CALL AS: {gcViewtAngle1,gcViewPtAngle2,gcViewPtAngleDelta,
		gcViewPtAngleValuesHoriz,,gcViewPtAngleValuesVert,
		gcViewPtAngleInit} = computeViewPtAngles[];

computeViewPtAngles[]:=Module[{
adjust,ang1,ang2,angDelta,avHoriz,avVert,angInit
},
adjust=ArcTan[Sqrt[2]]/Degree-55.0;
ang1={15.0,75.0+adjust};
ang2={75.0,15.0+adjust};
angDelta={5.0, -5.0};
avHoriz=Range[ang1[[1]],ang2[[1]],angDelta[[1]]];
avVert=Range[ang1[[2]],ang2[[2]],angDelta[[2]]];
angInit={avHoriz[[7]],avVert[[5]]};
{ang1,ang2,angDelta,avHoriz,avVert,angInit} Degree  (*  in radians  *)
];

histPlaybackIconTest  -- { |<<,  <<,  <|,   |>,  >>,  >>| }

Render one of the six icons that control move-history playback.  With testopt = True, this 
function draws large icons with grid lines for easy inspection of the art work.  Otherwise,
it renders small icons, which appear in the History control panel.

histPlaybackIconTest[ix_,testopt_:False]:= Module[{pltrules,geom},
pltrules=If[testopt, 
{GridLines->Automatic,PlotRange->{{-1,13},{-1,9}}, Frame->True,ImageSize->{250,200}},
{ImageSize->{24,16}}
];
geom=Which[
ix==1, (* -------- Go back to beginning, " |<< "  *)
{Rectangle[{0,0},{2,8}],Polygon[{{{0,4},{7,0},{7,8}},{{5,4},{12,0},{12,8}}}]},
ix==2, (* -------- Go back N moves, " << "  *)
{Polygon[{{{0,4},{7,0},{7,8}},{{5,4},{12,0},{12,8}}}]},
ix==3, (* -------- Go back 1 move, " <| "  *)
{Polygon[{{0,4},{7,0},{7,8}}],Rectangle[{8,0},{10,8}]},
ix==4, (* -------- Go forward 1 move, " |> "  *)
{Rectangle[{0,0},{2,8}],Polygon[{{3,0},{3,8},{10,4}}]},
ix==5, (* -------- Go forward N moves, " >> "  *)
{Polygon[{{{0,0},{0,8},{7,4}},{{5,0},{5,8},{12,4}}}]},
ix==6, (* -------- Go forward to End, " >>| "  *)
{Polygon[{{{0,0},{0,8},{7,4}},{{5,0},{5,8},{12,4}}}],Rectangle[{10,0},{12,8}]},
True, (* -------- Dummy, " > | " *)
{Polygon[{{{0,0},{0,8},{7,4}}}],Rectangle[{10,0},{12,8}]}
];
Graphics[{Lighter[Blue,0.2], geom},pltrules]   (*  Returned value  *)
];

histPlaybackIcon

histPlaybackIcon[ix_Integer/;1≤ix≤6]:=
histPlaybackIcon[ix]=                  (*  Memoization: 6 values  *)
histPlaybackIconTest[ix,False];

Global Constants (prefix = “gc”)

Global constants used in controls need to be defined before the Manipulate Initialization option.
So we define them here.

initGlobalConstants[ ]

initGlobalConstants[]:=Module[{},

ctNone=ControlType->None;   (*  Shorthand for frequently used option  *)
gcInitCubeLength=5;        (* Starting value for cube length *)  
gcMaxCubeLength = 9;          (* Largest cube length allowed  *)

gcInitSubcubeSize=75;   (* Starting value for subcube size *)  
gcMinSubcubeSize=40;          (* Smallest allowable subcube width *)
gcMaxSubcubeSize = 100;     (* Largest allowable subcube width *)

(*  Layer Rotations:  Directions and Amounts  *)
gcCCW  = 2;        (*  Counterclockwise one quarter turn  *)
gcCCW2 = 3;      (*  Counterclockwise one half turn  *)
gcCW  = 4;         (*  Clockwise one quarter turn  *)
gcCW2 = 5;       (*  Clockwise one half turn  *)
gcCW8020=Flatten@{                                          (*  Used by oneRandomMove  *)
 ConstantArray[{gcCCW,gcCW},4],       (* 80% quarter turns *)
 ConstantArray[{gcCCW2,gcCW2},1]      (* 20% half turns *)
};

gcMaxFaceIx=9;                     (* Maximum face/axis index *)     

(*  Global constants for Move History, UDS Recording, etc.  *)
gcHistMaxLen=6200;                        (* Maximum number of moves saved in history buffer *)
gcHistStixMaxLen=1550;             (* Maximum number of lines of history text in window *)
gcHistMaxDisplayWidth=80;      (* Maximum number of characters per line in the move history display *)
gcHistMaxDisplayLines=4;      (*  Maybe get this from a Control ***LATER*** *)

gcRecMaxLen = 6200;           (* Maximum number of moves saved in recording buffer *)   
gcNumUserSeqs = 50;            (* Number of user-defined sequences  *)
gcMaxUdsMoves=6200;        (*  Maximum number of moves in a one User-Defined Sequence  *)

(* ---------------------- Colors & Styles ---------------------- *)
(*  Defaults for edges and colors  (subcubes, rings, etc.)  *)
gcDfltEdgeTh=4; 
gcDfltRingTh=3; 
gcDfltEdgeColor=Black;
gcDfltRingColor=LightPurple;
gcDfltBkgdColor=GrayLevel[0.85];
 gcLogoColor=ColorData["HTML","DodgerBlue"];

(*  Background colors for controls  *)
gcBkgdBST=Lighter[ColorData["HTML","DodgerBlue"],0.8];  (*  Basic Moves *)
gcBkgdFSA=Lighter[ColorData["HTML","DodgerBlue"],0.6];  (*  Face, Layers, Angle  *)
gcBkgdWC=Lighter[Brown,0.7];                                                       (*  Whole-Cube Grid  *)
gcBkgdManip=Lighter[Blue,0.9];                    (*  Manipulate BaseStyle  *)
gcBkgdOpenerView=Lighter[Blue,0.9];         (*  OpenerView's  *)
gcBkgdRecOn=RGBColor[1.0,0.1,0.1];           (*  Actively recording UDS  *)
gcBkgdHistControls =Lighter[Blue,0.8];    (*  History control panel  *)

(*  Bright colors frequently used for commercial Rubik's cubes.  *)
gcDefaultFaceColors=RGBColor@@@{   
{1,1,0},                    (* +x, Front, Yellow *)
{1,0,0},                    (* +y, Right, Red *)
{0,0,1},                   (* +z, Up,    Blue *)
{0.2,0.55,0.2},     (* -x, Back,  Dark Green *)
{0.8,0.6,0},           (* -y, Left,  Orange *)
{1,1,1}                       (* -z, Down,  White *)
};
gcFenwayGreen=RGBColor[0.29,0.47,0.48];  (*  ~RGBColor@@({74,119,122}/255.)  *)
gcDefaultFaceColors[[4]]=gcFenwayGreen;  (*  Use Fenway green instead of dark green  *)

gcGrayTable=GrayLevel/@((Range[6]-1)/5.0)[[{1,3,5,2,4,6}]];

(*  Hand-picked color sets easily distingushed (maybe) by color blind persons  *)
gcAltFaceColorsData={
{{0,10,10},{10,10,10},{0,0,5},{0,8,5},{7,8,0},{5,5,10}},
{{0,0,10},{10,6,10},{10,10,10},{0,10,10},{0,10,0},{0,0,0}},
{{10,0,0},{0,0,0},{0,0,10},{0,10,10},{10,0,10},{10,10,0}},
{{2,10,10},{2,8,10},{2,6,2},{2,3,6},{2,10,2},{2,0,10}},
{{5,0,0},{5,5,10},{5,0,10},{5,5,0},{5,10,10},{5,10,0}},
{{10,0,0},{10,0,8},{10,5,0},{10,10,0},{10,10,10},{2,4,6}},
{{0,8,0},{10,10,0},{10,0,0},{0,10,10},{0,0,5},{0,0,10}},
{{10,0,0},{0,10,0},{0,0,10},{0,10,10},{10,6,10},{10,10,10}},
{{10,2,2},{10,5,0},{10,10,0},{6,9,6},{7,7,10},{8,0,8}},
{{10,0,0},{0,10,0},{0,0,10},{10,8,8},{8,10,8},{8,8,10}},
{{0,9,9},{10,0,10},{10,10,0},{9,10,10},{10,6,10},{10,10,8}},
{{8,0,0},{0,8,0},{0,0,8},{7,5,2},{7,10,7},{6,6,8}}
};

(* ---------- Icons ---------- *)
(*  gcPausePlayIcon -- ( || > )  *)
gcPausePlayIcon=Graphics[{
Darker[Green,0.5],
Rectangle[{0,0},{2,8}],Rectangle[{3,0},{5,8}], (*  Pause   || *)
Polygon[{{8,0},{8,8},{15,4}}]                                           (*  Play     > *)
},ImageSize->{25,14}];

gcStopSeqIcon = Graphics[{Red,Rectangle[]},ImageSize->14]; (* Red square *)

(*
Assign values of 101, 102, etc. to the global constants with
prefixes = "xsa" used for special action indices.
*)

gcRangeXsa=Range[101,130];
{xsaAltFaceColors,xsaBkgdColor,xsaChangeFaceColors,xsaClearForceRings,
xsaClearInvis,xsaClearLayers,xsaEdgeColor,xsaEdgeTh,xsaForceRings,
xsaInvis,xsaNumHist,xsaNumSteps,xsaPause,xsaPermuteColors,xsaResetThColors,
xsaRingColor,xsaRingTh,xsaSelectFace,xsaSelectLayers,xsaSeqAnim,
xsaShowCores,xsaShowMisAl,xsaShowRings,xsaSolvedCube,xsaSubcubeSize,
xsaViewOpt,xsaViewPtAngle,xsaViewPtMult,
xsaViewPtReset,xsaWireFrame}=gcRangeXsa;

(* ------------ Lookup tables ------------ *)
gcUnitScVertexCoords=computeUnitScVertexCoords[];

gcInPlaceRotTable=computeInPlaceRotTable[];

(* ---- Parameters used by displayGraphics for cube display ---- *)
gcBoxedDisplay=False;
gcImageRatio=4/3;
gcDrawAxes=False;
gcMoveTextFormat="RCN";
(*
gcMoveTextFormat="FRU"];  (*  Probably not supported any longer.  *)
*)
(* ----------------------------------------------------------------- *)
(* -------- Lookup Tables for Text Representations of Moves -------- *)
(* ----------------------------------------------------------------- *)
(*  These tables specify text-format moves for RCN and FRU notations.  
"N" indicates padding (i.e., the element is not used).  The 
padding is used to improve proofreadibility by aligning columns.  *)

(*  Front,Right,Up (FRU) Notation (Examples:F134++,U-,D2++)  *)
gcFRUTextFace={"X","Y","Z","F","R","U","B","L","D"};
gcFRUTextDir={"N","-","--","+","++"};
gcFRUTextWholeCube={
{"N","XW-","XW--","XW+","XW++"},{"N","YW-","YW--","YW+","YW++"},{"N","ZW-","ZW--","ZW+","ZW++"},{"N","FW-","FW--","FW+","FW++"},{"N","RW-","RW--","RW+","RW++"},{"N","UW-","UW--","UW+","UW++"},
{"N","BW-","BW--","BW+","BW++"},{"N","LW-","LW--","LW+","LW++"},{"N","DW-","DW--","DW+","DW++"}
};

(*  Modified Rubik's Cube Notation (RCN)  (Examples:  134F2, U', 2D2)  *)
(*  These notations are preceeded by layer numbers (unless layers = {1}.  *)
gcRCNTextFaceDir={
{"N","NN","NNN","N","NN"},{"N","NN","NNN","N","NN"},{"N","NN","NNN","N","NN"},
{"N","F'","F2'","F","F2"},{"N","R'","R2'","R","R2"},{"N","U'","U2'","U","U2"},
{"N","B'","B2'","B","B2"},{"N","L'","L2'","L","L2"},{"N","D'","D2'","D","D2"}
};
(*  For whole-cube rotations,we use a MODIFIED Rubik's cube notation where the 
layers field contains "wc" indicating "whole cube" (all layers).  
Whole-cube rotation directions are reckoned from the "Front View" 
faces: F, R, & U  *)
gcRCNTextWholeCube={    (*  This is MODIFIED Rubik's Cube Notation  *)
{"N","wcF'","wcF2'","wcF","wcF2"},    (*  Front-Back axis  *)
{"N","wcR'","wcR2'","wcR","wcR2"},    (*  Right-Left axis  *)
{"N","wcU'","wcU2'","wcU","wcU2"}         (*  Up-Down axis  *)
};

(* -------- Constants Needed for view angle & view range controls -------- *)

{gcViewPtAngle1,gcViewPtAngle2,gcViewPtAngleDelta,
gcViewPtAngleValuesHoriz,gcViewPtAngleValuesVert,
gcViewPtAngleInit} = computeViewPtAngles[];

gcViewPtMultValues={2.5,2.6,2.8,3.0,4.0,5.5,8.0,12.0};
gcViewPtMultInit=gcViewPtMultValues[[4]];

Assert[Length@gcRangeXsa==Length@Names["`xsa*"]];

];(* -------- End of function initGlobalConstants  *)

Call initGlobalConstants[ ] -- Important to call it here.

initGlobalConstants[];

Initialization of Global Variables

InitGlobalVariables[ ]

initGlobalVariables[]:=Module[{},

(*  Arrays and variables for storing debug information  *)
debugCubeAndCores={};
debugBigDisplay={};
debugBytesBigDisplay=0;      (* ByteCount for most recent debugBigDisplay structure. *)
debugCumBytesBigDisplay=0;   (* Cumulative ByteCount for all debugBigDisplay structures. *)
debugBuildCubeCores={};

(*  Variables for user defined sequences and the recording buffer  *)
gbRecIntBuffer=ConstantArray[0,gcRecMaxLen];
gsRecIntBuffer={};
gsRecBuffLen=0; 
gsUserSeq = ConstantArray[{},gcNumUserSeqs];
gsUserSeqName=ConstantArray["",gcNumUserSeqs];
gsLastDeletedUds={};
gsLastDeletedUdsName="";

];  (*  End function initGlobalVariables  *)

Call initGlobalVariables -- Important:  We have to call this here.

initGlobalVariables[];   (*  You have to call it here  *)

Global Variables for Move History

Programming note:  The most recent several thousand moves in the move history are maintained in a
linear buffer.  When the buffer is full, the oldest ~15% of the moves are discarded to make room for 
new moves.  It would be cooler to use a circular buffer for this purpose; but, the linear buffer works 
OK and perhaps a circular buffer can be implemented in a later version of DeepCube.
CALLS: NONE
SETS GLOBALS: gcHist*, gnHist*, gsHist*

(*
gsHistBufferLength=0;
gsHistPtr=0;
gbHistIntBuffer=ConstantArray[0,gcHistMaxLen];
gsHistIntBuffer={};
gbHistTextBufferFRU=ConstantArray["",gcHistMaxLen];
gbHistTextBufferRCN=ConstantArray["",gcHistMaxLen];
*)
(*
gsHistStixLen=1;
gbHistStixBuffer=ConstantArray[0,gcHistStixMaxLen];

gsHistStixBuffer={1};
gbHistStixBuffer[[1]]=1;
gsHistTraversing=0;
gsHistTotalMoves=0;

*)

histInit

Initialize all history values.
CALLS: NONE
SETS GLOBALS: gcHist*, gnHist*, gsHist*

histInit[]:=(

gsHistBufferLength=0;
gsHistPtr=0;
gbHistIntBuffer=ConstantArray[0,gcHistMaxLen];
gsHistIntBuffer={};
gbHistTextBufferFRU=ConstantArray["",gcHistMaxLen];
gbHistTextBufferRCN=ConstantArray["",gcHistMaxLen];

(*  Global Variables  *)
gsHistStixLen=1;
gbHistStixBuffer=ConstantArray[0,gcHistStixMaxLen];

gsHistStixBuffer={1};
gbHistStixBuffer[[1]]=1;
gsHistTraversing=0;
gsHistTotalMoves=0;
);

Call histInit -- Important:  We have to call this here.

histInit[];

Move Format (mf*) Functions

General Discussion

These functions, prefixed by ”mf”, support various formats in which moves are expressed and stored.
Types of formats and notations they support:.
	Integer (aka: Packed Integer Format):  Same for XYZ,  FRU or RCN
	Move (a list of face/axis, layer(s), direction):  Same format for XYZ,  FRU or RCN  (e.g., {4,{2,3},3})
	Text:   XYZ,  FRU,  RCN (Text only)
	
In (hopefully) all cases, we follow a convention in which layer lists are Lists, even if they contain only 
one element.  For example:  {9, {5}, 4}.

Conversion functions:
	Conversion between moves in XYZ and FRU notation
		mfMoveXYZ2FRU		mfMoveFRU2XYZ
		
	Conversion between move-list representation and packed-integer representation:
		mfMove2Int			mfInt2Move
															
	Conversion to  Text:
		mfLayers2TextFRU		mfMove2Text		
		mfLayers2TextRCN		mfInt2Text
		
	Conversion functions from Text representation to other representations:
		(These are used for testing and debugging purposes only.  They are not
		included in this notebook; but are kept and maintained in a test notebook)
		 mfText2Layers  		mfText2Move 	 mfText2Int 
		 moveFRUtoXYZ 		moveXYZtoFRU

Supporting Utilities
		mfCleanLayers		mfCleanLLQ		mfOppositeMoveFRU
		mfLayersAndCompl		mfOppositeDir
		mfOppositeFace		mfReduceFRU

Supporting utilities for moves

mfCleanLayers

Given a List of layers, return it sorted numerically with duplicates and any values outside the 
interval [1,nx] deleted.  If the input value is a single integer (i.e., not a list), it is returned 
as a one-element List: e.g., 4 --->  {4}.

mfCleanLayers[nx_,layerlist_]:=Select[Union@Flatten@{layerlist},(1≤#≤nx)&];

mfCleanLLQ

Check if the given layer list is “clean” (see mfCleanLayers).  This function is useful, for example, in
an Assert expression to verify that an input layer list is in expected form.

mfCleanLLQ[nx_,layerlist_]:=SameQ[layerlist,mfCleanLayers[nx,layerlist]];

mfOppositeDir

mfOppositeDir[d_Integer/;1≤d≤5]:=mfOppositeDir[d]={1,4,5,2,3}[[d]];

mfOppositeFace

mfOppositeFace[f_Integer/;1≤f≤gcMaxFaceIx]:=mfOppositeFace[f]={1,2,3,7,8,9,4,5,6}[[f]];

mfLayersAndCompl

Return a cleaned-up layer list and it’s complement.  By complement, we mean a layer list as
viewed from the opposite face.   For example, with nx = 5,  layer list = {1, 3, 4} and   its 
complement is {2, 3, 5}

mfLayersAndCompl[nx_,layerlist_]:=Module[{laylist,laylistc},
laylist=mfCleanLayers[nx,layerlist];  (*  Sorted, unique and a list of one or more integers *)
laylistc=Union@Flatten[(nx+1-#)&/@laylist];  (*  Sorted complement of laylist  *)
{laylist,laylistc}
];

mfMoveFRU2XYZ

Convert a move in FRU notation into a move in XYZ notation.

mfMoveFRU2XYZ[nx_,moveFRU_]:=Module[
{
face = moveFRU[[1]],layersFRU= moveFRU[[2]],dirFRU= moveFRU[[3]],
laylist, laylistc,moveXYZ
},
Assert[mfCleanLLQ[nx,layersFRU],"layersFRU in mfMoveFRU2XYZ"];

{laylist,laylistc}=mfLayersAndCompl[nx,layersFRU];
moveXYZ = If[face≤6,
{face-3,laylistc,dirFRU},
{face-6,laylist,mfOppositeDir@dirFRU}
];
moveXYZ       (*  Returned value: a single move in XYZ notation  *)
];

mfOppositeMoveFRU

Convert a move in FRU notation into an equivalent move as viewed from the opposite face.

mfOppositeMoveFRU[nx_,moveFRU_]:=Module[
{
face = moveFRU[[1]],
layersFRU= moveFRU[[2]],
dirFRU= moveFRU[[3]],
laylist, laylistc,moveOppFRU
},
Assert[mfCleanLLQ[nx,layersFRU],"layersFRU in mfOppositeMoveFRU"];
{laylist,laylistc}=mfLayersAndCompl[nx,layersFRU];
moveOppFRU={mfOppositeFace@face,laylistc,mfOppositeDir@dirFRU};
moveOppFRU     (*  Returned value: a single move in FRU notation  *)
];

mfGetXYZParts

Extract and return the axis, layer list and rotation direction from a move.
The move may be in XYZ or FRU notation.

mfGetXYZParts[nx_,move_]:=Module[
{mvx,facein=move[[1]],layersin=move[[2]]}
,
Assert[1≤facein≤gcMaxFaceIx, "facein in mfGetXYZParts"];
Assert[mfCleanLLQ[nx,layersin],"layersin in mfGetXYZParts"];

If[facein>9,
Print["mfGetXYZParts: nx = ",nx, "  move = ",move, "  facein = ", facein];
];
mvx=If[1≤facein≤3,move, mfMoveFRU2XYZ[nx,move]];
{mvx[[1]],mfCleanLayers[nx,mvx[[2]]],mvx[[3]]}
];

mfReduceFRU

Rewrite (if necessary) the given FRU move so that it is reckoned from the face
to which the layers are closest.

mfReduceFRU[nx_Integer,moveFRU1_]:=Module[
{laylist,laylistc},
{laylist,laylistc}=mfLayersAndCompl[nx,moveFRU1[[2]]];
(*  Choose the face with the LOWER layer score. If there is a tie, choose
the lower face number. (i.e., FRU takes precedence over BLD.) *)
Which[
Total[laylist]<Total[laylistc],moveFRU1,
Total[laylist]>Total[laylistc],mfOppositeMoveFRU[nx,moveFRU1],
moveFRU1[[1]]≤6, moveFRU1,                        (*  Tie goes to FRU face  *)
True,mfOppositeMoveFRU[nx,moveFRU1]
]             (*  Returned value  *)
];

Conversion to and from packed-integer move format

mfMove2Int

Convert a move in move-list notation into its packed-integer representation.    << XYZ or FRU >>

mfMove2Int[move_]:=Module[
{face,layerlist,dir,layerint},
{face,layerlist,dir}=move;
layerint=Total[2^(Union[Flatten[{layerlist}]-1])];
100 layerint+10 face+dir  (*  Reutrned value  *)
];

mfInt2Move

Convert a move in packed-integer representation into move-list notation.    << XYZ or FRU >>

mfInt2Move[moveint_]:=Module[
{
digits=IntegerDigits[moveint],
layerint=Quotient[moveint,100],                            (*  integer-encoded layer list  *)
face,dir,binarydigs,onespos,layerlist
},
face=digits[[-2]];              (*  May be face (4, 5, ... 9) or axis (1, 2, 3)  *)
dir=digits[[-1]];

binarydigs=IntegerDigits[layerint,2];             (*  layerint in binary representation  *)
onespos = Position[Reverse@binarydigs,1];    (*  Positions of the binary ones  *)
layerlist=Flatten@onespos;                                           (*  Remove inner braces returned by Position  *)

{face,layerlist,dir}       (*  Returned value:  Standard move format  *)
];

Conversion between XYZ and FRU notations

mfMoveXYZ2FRU

Convert a move in XYZ notation into a move in FRU notation.

mfMoveXYZ2FRU[nx_,moveXYZ_]:=Module[
{
axis = moveXYZ[[1]],
layersXYZ= moveXYZ[[2]],
dirXYZ= moveXYZ[[3]],
laylist, laylistc
},
{laylist,laylistc}=mfLayersAndCompl[nx,layersXYZ];
(*  Determine whether to use FRU or BLD using a "nearness" algorithm. *)
(*  NOTE:  This is not always reversible.  *)
If[Total[laylist]<Total[laylistc],    (*  less-than sign indicates tie goes to FRU faces.  *)
{axis+6,laylist,mfOppositeDir@dirXYZ},
{axis+3,laylistc,dirXYZ}
]     (*  Returned value: a single move in FRU notation  *)
];

Conversion to text formats

mfLayers2TextFRU

Convert a layer list into a character string.  << XYZ or FRU >>

mfLayers2TextFRU[nx_,layerlist_]:=Module[{laylist},
laylist=mfCleanLayers[nx,layerlist];
Which[
Length@laylist==nx,"W",            (* "W" indicates all layers, i.e., whole-cube rotation *)
True,StringJoin[ToString/@laylist]  
]
];

mfLayers2TextRCN

Convert a layer list into c character string using RCN.     << RCN >>

mfLayers2TextRCN[nx_,layerlist_]:=Module[{laylist},
laylist=mfCleanLayers[nx,layerlist];
Which[
SameQ[laylist,{1}],"",                     (*  Don't show layer if it's only the first one.  *)
Length@laylist==nx,"wc",            (*  "wc" indicates whole-cube rotation.  *)
True,StringJoin[ToString/@laylist]    (*   *)
]
];

mfMove2Text

Convert a move in List format to text in either FRU or RCN notation.  The input move may
be in XYZ or FRU notation.  For output, RCN text is the default.

mfMove2Text[nx_,move_,optRCN_:True]:=Module[
{
face,layerlist,dir,mv,laylist,mvtxt,
facein=move[[1]]
},
mv=If[4≤facein≤gcMaxFaceIx,
move,                                                     (*  an FRU move  *)
mfMoveXYZ2FRU[nx,move]      (*  an XYZ move.  Convert to FRU  *)
];
{face,layerlist,dir}=mv;
laylist=mfCleanLayers[nx,layerlist];

mvtxt=If[optRCN,
(*  RCN notation  *)
(*  Special treatment for whole-cube rotation.  Use lower case x, y, z  *)
(*  NOTE:  If nx == 1, all moves are whole-cube: use RFU instead of xyz.  *)
If[nx>1&&Length@laylist==nx,
(*  Whole-Cube rotation  *)
If[face≥7,dir=mfOppositeDir@dir];
gcRCNTextWholeCube[[Mod[face,3,1],dir]],
(*  Fewer than all layers  *)
StringJoin[ mfLayers2TextRCN[nx,layerlist],gcRCNTextFaceDir[[face,dir]]]
]
,  (*  ELSE:  FRU notation  *)
If[Length@laylist==nx,
gcFRUTextWholeCube[[face,dir]],
StringJoin[gcFRUTextFace[[face]],mfLayers2TextFRU[nx,layerlist],gcFRUTextDir[[dir]]]
]
];
mvtxt
];

mfInt2Text

Convert a move in packed-integer format to text in RCN or FRU format.  The input integer may
be in XYZ or FRU notation.  The output text will be in RCN or FRU notation, depending on the
value of optRCN.  (RCN output is the default.)

mfInt2Text[nx_,moveint_,optRCN_:True]:=mfMove2Text[nx,mfInt2Move@moveint,optRCN];

Utilities

Utilities are usually simple, self-contained functions which don’t call other functions and
don’t use or modify any global variables.

solvedCube -- Create a pristine cube state

solvedCube[nx_Integer,ori_Integer:1]:= ConstantArray[ori,{nx,nx,nx}];

normalMoveQ[move_] -- Check if a move is normal or special

normalMoveQ[move_]:=(1≤First@move≤gcMaxFaceIx);

rotMatrix2x2[angleix_] -- 2-by-2 rotation matrix

Calculate a 2-by-2 matrix specifying a rotation of a layer by a given angle (in radians).  
The parameter, angleix, is the index into the rotation-angle list: {0, +Pi/2, +Pi, -Pi/2,  -Pi}.
Uses memoization to store the five possible results.

rotMatrix2x2[angleix_Integer/;1≤angleix≤5]:=
rotMatrix2x2[angleix]=  (*  Memoization: 5 matrices  *)
Module[{angle,s,c},
angle={0,Pi/2,Pi,-Pi/2,-Pi}[[angleix]];
s=Sin[angle];c=Cos[angle];
{{c,s},{-s,c}}
];

downUpRange

Create a list of integers that form the pattern: start ...,2, 1, 2, ... len, len-1 ... start.  This utility
is used for animated demos.

downUpRange[len_,start_]:=Module[{
r1=Table[i,{i,start,1,-1}],
r2=Table[i,{i,2,len}],
r3=Table[i,{i,len-1,start,-1}]
},
Join[r1,r2,r3]
];

calcViewpoints

Convert a view angle and a distance to front and back viewpoints in a Cartesian system.
NOTE:  phi is the POLAR ANGLE, i.e., the angle between the Z axis and the line of sight.

calcViewpoints[viewangle_,vptmult_]:=Module[{
theta=viewangle[[1]],
phi=viewangle[[2]],
vptf}
,
(*Print["calcViewpoints:  theta = ",theta, "  phi = ", phi];*)
vptf={Cos[theta] Sin[phi],Sin[theta]Sin[phi],Cos[phi]}*vptmult;
{vptf,-vptf}
];

subcubeCenters[nx_Integer] --  Calculate locations of subcube centers

Compute the x, y, & z coordinates of the centers of each subcube for an nx-by-nx-by-nx 
cube. The centers are spaced by 200 units in each direction and the overall cube center
is at the origin. The result is a 4-dimensional array with dimensions {nx, nx, nx, 3}. 
Uses memoization to store (as needed) the computed 4-D arrays for each cube length, nx = 1...9.

subcubeCenters[nx_Integer]:=
subcubeCenters[nx]=                   (*  Memoization: nx = 1...9  *)
Module[{cnxmax,cnx},
Assert[1≤nx≤gcMaxCubeLength,"nx in subcubeCenters"];
cnxmax=100(nx-1);
cnx=Range[-cnxmax,cnxmax,200];
Partition[Partition[Tuples[cnx,3],nx],nx]
];

getAxisLayersXYZ

Given a face and a layer list in FRUBLD notation, compute and return  the corresponding
axis and  layer list in XYZ notation.
NOTE:  For L, B & D faces, layersfru may be in decending order; so, we don’t
check (Assert) that layersfru is “clean” (see mfCleanLLQ).

getAxisLayersXYZ[nx_,face_,layersfru_,debug_:False]:=
Module[{axis,layersxyz},

Assert[1≤nx≤gcMaxCubeLength,"nx in getAxisLayersXYZ"];
Assert[4≤face≤gcMaxFaceIx,"face in getAxisLayersXYZ"];

axis=Mod[face,3,1];    (* {4,5,6,7,8,9} maps to {1,2,3,1,2,3}  *)
layersxyz=Which[
4≤face≤6,(nx+1-#)&/@layersfru,
7≤face≤gcMaxFaceIx,layersfru,
True,{}
];
layersxyz=mfCleanLayers[nx,layersxyz];  (*  Fix reversed order if nec'y.  *)
If[debug,
Print["getAxisLayersXYZ:  nx = ", nx , ", face = ", face, ", 
			layersfru = ", layersfru, ", axis = " ,axis,  ", layersxyz =", layersxyz]];
{axis,layersxyz}   (*  Returned value  *)
];

Functions for Generating Global Lookup Tables

layerTranslationMatrix[nx_, dirix_] -- Calculate a subcube translation matrix

Compute a matrix that specifies how subcubes are translated when a layer is rotated about an axis.
The resulting matrix applies to all coordinate axes (x, y, & z).
Uses memoization to store matrices from computations where nx = 1...gcMaxCubeLength & dirix = 1 ... 5.
CALLED BY:  rotateOneLayer.
CALLS:  rotMatrix2x2

layerTranslationMatrix[nx_,dirix_]:=
layerTranslationMatrix[nx,dirix]=  (*  Memoization:  nx = 1...9; dirix = 1...5  *)
Module[{shift,b,c},
Assert[1≤nx≤gcMaxCubeLength,"nx in layerTranslationMatrix"];
Assert[1≤dirix≤5,"dirix in layerTranslationMatrix"];
shift = ConstantArray[{(nx+1)/2, (nx+1)/2},{nx,nx}];
b=Array[List,{nx,nx}]-shift;
c=Table[rotMatrix2x2[dirix].b[[i,j]],{i,nx},{j,nx}];
c+shift
];

Initializing and Updating Global Constants and Variables

getSixAltColors[ix_Integer:1]

Get a list of 6 subcube face colors from a set of colors that might be easy for a 
color blind person to distinguish.
CALLS:  NONE

getSixAltColors[ix_Integer:1]:=
getSixAltColors[ix]=   (*  Memoization:  12 tables of 6 colors each  *)
RGBColor@@@(gcAltFaceColorsData[[ix]]/10.0);

changeFaceColors --- Define Colors for Subcube Faces

Define six colors to be used for subcube faces.  Also updates the current polygons (gsCurrPolys).
USES GLOBAL CONSTANTS: gcDefaultFaceColors, gcGrayTable
CALLS:  getSixAltColors.
CALLED BY:   Manipulate

changeFaceColors[opt_,newScfc_:{}]:=Module[{fc,h},
Assert[-9≤opt≤-1||61≤opt≤72,"opt in changeFaceColors"];
h=If[Length@newScfc==6,True,False,False];
fc=Which[
opt==-1,          gcDefaultFaceColors,(*  Default face colors  *)
opt==-2,          RGBColor@@@Round[RandomReal[1,{6,3}],0.001],  (*  See Note 1 *)
opt==-3,          gcGrayTable,
h&&opt==-4,  newScfc,
h&&opt==-5,  RandomSample[newScfc,6],          (*  Random permutation  *)
h&&opt==-6, newScfc[[{4,1,2,5,6,3}]],    (*  Cyclic permutation clockwise *)
h&&opt==-7, newScfc[[{2,3,6,1,4,5}]],    (*  Cyclic permutation counterclockwise  *)
h&&opt==-8, newScfc[[{4,5,6,1,2,3}]],    (*  Swap top & bottom rows  *)
h&&opt==-9, newScfc[[{3,1,2,6,4,5}]],    (*  Cycle columns  *)
61≤opt≤72,  getSixAltColors[opt-60],           (*  Select an alternate color scheme  *)
True,                gcDefaultFaceColors
];
(*  
Note 1:  Random colors can be made lighter (to yield a "pastel" scheme)
by taking the square root (or any power less than 1) of the randomly 
generated colors: e.g., RGBColor@@@Sqrt@Round[RandomReal[1,{6,3}],0.001]
  *)
fc    (*  Returns {cvFaceC1,cvFaceC2,... ,cvFaceC6} = changeFaceColors[...]  *)
];

gcAltColorMenu -- Popup menu for alternate colors schemes

gcAltColorMenu=Table[With[{i=i,j=i+60},
Row[getSixAltColors[i]] :>
({cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[j])],{i,12}];

Functions for Updating the Cube State due to Layer Rotations

rotateOneLayer[nx_, axis_, layerix_, angleix_, oldCubeState_]

Update the cube state as affected by the rotation of a single layer by either a quarter
turn or a half turn about the given axis (X, Y, or Z).  Returns newCubeState.
USES GLOBAL CONSTANT:  gcInPlaceRotTable
CALLS: mfOppositeDir, layerTranslationMatrix.
CALLED BY:  rotateLayers.

rotateOneLayer[nx_,axis_, layerix_, angleix_,oldCubeState_,skiprot_:False]:=
Module[
{
newCubeState,
angIx,oldLayer, intermediateLayer, newLayer,
transArray,i1,j1, ix1,ix2,ix3
},
Assert[1≤layerix≤nx,"layerix in rotateOneLayer"];

oldLayer = Switch[axis,
1,oldCubeState[[layerix,All,All]],
2,oldCubeState[[All,layerix,All]],
3,oldCubeState[[All,All,layerix]]
];

(*  Because of the way the 2x2 rotation matricies are defined, we 
need to negate the rotation angle for the Y axis *)
angIx=If[axis==2, mfOppositeDir@angleix, angleix];

(* First translate the subcubes ... *)
transArray=layerTranslationMatrix[nx,angIx];
intermediateLayer = Table[
({i1,j1}=transArray[[i,j]];oldLayer[[i1,j1]]),
{i,nx},{j,nx}];

(* ...and then rotate all the intermediate-layer subcubes in place *)
(*
   The parameter, skiprot, controls whether the subcubes are 
  rotated in place after translation.  It is used for unit 
  testing and debugging purposes.
*)
If[skiprot,newLayer=intermediateLayer,  (*  No rotation, for debugging. *)
(* ------ ELSE, !skiprot ------  (normal usage) *)
ix1=axis;
ix3=angleix;  (*  {1,2,3,4,5} -> angles = {0, Pi/2, Pi, -Pi/2, -Pi  *)
newLayer=Table[ix2=intermediateLayer[[i,j]];
gcInPlaceRotTable[[ix1,ix2,ix3]],{i,nx},{j,nx}]
];

(* Now copy the new layer into the new cube state... *)
newCubeState=oldCubeState;
Switch[axis,
1,newCubeState[[layerix,All,All]] = newLayer,
2,newCubeState[[All,layerix,All]] = newLayer,
3,newCubeState[[All,All,layerix]] = newLayer];

newCubeState                                            (* ... and return it *)
]    (*  ------ End function rotateOneLayer ------ *);

rotateLayers[nx_, move_, oldCubeState_]

Update the cube state by applying a given move, which may be either in XYZ or
FRU notation and which represents the rotation of one or more parallel layers.
Returns newCubeState.
CALLS:   mfMoveFRU2XYZ, rotateOneLayer.
CALLED BY:  Manipulate

rotateLayers[nx_,move_,oldCubeState_]:=Module[
{
facein=move[[1]],layersin=move[[2]],
moveXYZ,axis,layers,laylist,dir,newCubeState,ix
},

(*  For special moves, return the cube state unchanged.  *)
If[!(1≤facein≤gcMaxFaceIx),Return[oldCubeState]];

Assert[mfCleanLLQ[nx,layersin],"layersin in rotateLayers"];

newCubeState=oldCubeState;
moveXYZ=If[4≤facein≤gcMaxFaceIx,mfMoveFRU2XYZ[nx,move],move];

(*  Unpack the move and check the components for validity.  *)
{axis,layers,dir}=moveXYZ;
Assert[1≤axis≤3,"axis in rotateLayers"];
Assert[2≤dir≤5,"dir in rotateLayers"];
Assert[mfCleanLLQ[nx,layers],"layers in rotateLayers"];

laylist=mfCleanLayers[nx,layers];
For[ix=1,ix≤Length@laylist,ix++,
newCubeState=rotateOneLayer[nx,axis,laylist[[ix]],dir,newCubeState]
];
newCubeState      (*  Returned value -- a 3D array of integers  *)
];

rotateLayersSeq

Update the cube state by applying a given sequence of moves.   Returns newCubeState.
Works with moves in XYZ or FRU notation.
NOTE:  This is called by Manipulate only for the case of no animation.
CALLS:  rotateLayers.
CALLED BY:  Manipulate

rotateLayersSeq[nx_,moveSeq_,oldCubeState_]:=Module[{newCubeState,ix},
newCubeState=oldCubeState;
For[ix=1,ix≤Length@moveSeq,ix++,
newCubeState=rotateLayers[nx,moveSeq[[ix]],newCubeState]
];
newCubeState  (*  Returned value: a 3D array of integers  *)
];

Generating Sequences of Moves

revSeq & reverseSequence

Generate a move sequence that undoes a given move sequence.  Works with both
XYZ and FRU conventions.  revSeq is a short-name version for code compactness.
The equivalent function, reverseSequence, is sometimes used where we want to
express what we’re doing with more readability.
CALLS:  NONE

revSeq[moves_]:=Module[{revmoves= Reverse[moves]},
revmoves[[All,3]]=mfOppositeDir/@revmoves[[All,3]];
revmoves
]

reverseSequence[moves_]:=revSeq[moves]         (*  Long name for readability.  *)

oneRandomMove

Generate a single random move. This function supports all types of moves including  single-layer,
multi-layer and whole-cube rotations.  It can generate moves consisting of CW & CCW quarter 
turns and half turns about any of three axes or six faces. 
The move is returned in XYZ (axis = 1, 2, 3) or FRU (face = 4, 5, 6, 7, 8, 9) notation, depending
upon the value of the notation parameter.  For FRU notation, choose the face closest to the randomly
generated layer list with ties going to the F, R or U faces.
CALLS:   mfReduceFRU

oneRandomMove[nx_Integer,reduceOpt_:True,notation_:"FRU"]:=Module[
{axis, layerlist, angleix,numlayers,randnx,r1,move1},
randnx = RandomInteger[{1,nx}];
r1 = RandomReal[];  (*  Roll of the dice  *)

layerlist = Which[
r1 <0.1, Range@nx,    (*  Whole-cube rotations (10% of the time) *)
r1 <0.7,{randnx},      (*  Single-layer rotations (60%) *)
nx≤2,    {randnx},       (*  Use single-layer rotations for small nx (90%)  *)
True,                                 (*  Multi-layer rotations (30%)  *)
		(numlayers=RandomInteger[{2,nx-1}];
		Sort@RandomSample[Range@nx,numlayers])
];
axis=Which[
notation=="FRU", RandomInteger[{4,9}],
notation=="XYZ", RandomInteger[{1,3}],
True,                        RandomInteger[{4,9}]
];
angleix=RandomChoice@gcCW8020;  (*  80% quarter turns, 20% half turns  *)

move1={axis,layerlist,angleix};     (*  layerlist is enclosed in curly braces  *)
If[4≤axis≤9&&reduceOpt,move1=mfReduceFRU[nx,move1]];
move1   (*  Returned value  *)
];

randomMoves

Generate a sequence of random moves by calling the function oneRandomMove multiple times. 
CALLS:  oneRandomMove[nx, True, ”FRU”]
CALLED BY:  namedMoveSeq, randomDemo

randomMoves[nx_Integer, nummoves_Integer] :=
   Table[oneRandomMove[nx,True,"FRU"],{nummoves}];

randomDemo

Generate a sequence of random moves in FRU notation that scrambles the cube
and then unscrambles it.
CALLS:  randomMoves,  reverseSequence

randomDemo[nx_Integer, nummoves_Integer] :=   Module[{m1},
m1 = randomMoves[nx,nummoves];
Join[m1,reverseSequence@m1]
];

buildBasicMove

Return a List containing  a single move (in FRU notation) built from the given 
face, layerlist, and direction index.  The input parameters, face, layerlist, and dir
are in the FRU convention. The returned move is a one-element list containing
a single move:  e.g.,{{8, {1, 2, 5} ,4}}.  The face can be {4, 5, 6, 7, 8, 9}
representing Front, Right, Up, Back, Left, Down, respectively.

buildBasicMove[nx_,face_,layerlist_,dir_]:=Module[{laylist, move},

Assert[1≤nx≤gcMaxCubeLength, "nx in buildBasicMove"];
Assert[4≤face≤gcMaxFaceIx, "face in buildBasicMove"]; (*  {4,5,6,7,8,9} --> {F,R,U,B,L,D}  *)
Assert[2≤dir≤5,"dir in buildBasicMove"];
Assert[mfCleanLLQ[nx,layerlist],"layerlist in buildBasicMove"];

laylist=If[Length@layerlist≤0,
{1},  (* -------- Default to first layer if layerlist is empty  *)
mfCleanLayers[nx,layerlist]
];
move={{face,laylist,dir}};  (*   **** IMPORTANT:  Must have double curly braces.  *)
move     (*  Returned value is in FRU notation, e.g., {{8, (1,2,4}, 4}}  *)
];

System-Supplied Move Sequences (Sequences & Patterns)

General Information

Most Sequences & Patterns are affected by the current choice of Face and Layers in
the Basic Moves panel.

Canned sequences involving a variable number of outer and inner layers, such as
swapping three edges, use the selected Layers as follows:
    o  No Layers selected (default):  one outer layer, all interior layers
    o  Otherwise:  {s1, s2} = unpackLayers[nx, layerlist] (q.v.).

Supporting Utilities for Sequences & Patterns

facePerm -- Face permutations supporting canned move sequences

Given a permutation number and a face number, return an alternate face number
as required by a move sequence.  The permutation number, face number and
alternate face number all lie in the range 4 through 9.
CALLED BY:  Various functions that generate canned move sequences,
                          e.g seqSwap3Corners.

SetAttributes[facePerm,Listable];
facePerm[permnum_Integer/;4≤permnum≤9,face_Integer/;4≤face≤9]:=
(*facePerm[permnum,face]=*)
Module[{fm3=face-3},
Switch[permnum,
4,{4,5,6,7,8,9}[[fm3]],  (*  identity  *)
5,{5,6,4,9,7,8}[[fm3]],
6,{6,4,5,8,9,7}[[fm3]],
7,{7,8,9,4,5,6}[[fm3]],  (*  opposite face  *)
8,{8,9,7,6,4,5}[[fm3]],
9,{9,7,8,5,6,4}[[fm3]]
]
];

countOuterLayers

Count the number of adjacent outer layers in the given layer list. Return the count from each
end along with  the maximum of the two counts.  The counts can range from 0 to nx.
Usually used for canned move sequences involving flipping or swapping edge subcubes.
layers is a “clean” (sorted with no duplicates) layer list.
CALLED BY:  unpackLayers

countOuterLayers[nx_,layers_]:=Module[
{len=Length@layers,rng,lay2, ct1,ct2}
,
Assert[mfCleanLLQ[nx,layers],"layers in countOuterLayers"];
rng=Range[len];
ct1=Length@Select[layers-rng,(#==0&)];
lay2=Union[nx+1-layers];
ct2=Length@Select[lay2-rng,(#==0&)];
{ct1,ct2,Max[ct1,ct2]}
];

getLayerPart -- Support function for edge sequences

getLayerPart[layers_,ix1_,ix2_]:=Module[{newlayers},
newlayers=Select[layers,(ix1≤#≤ix2)&];
If[Length@newlayers==0, 
Range[ix1,ix2],  (*  Empty list defaults to full  *)
newlayers
]
];

unpackLayers -- Support function for edge sequences

Separate the given layer list into two NON-OVERLAPPING lists:  an outer list and
an inner list.  
--- The outer list comprises the larger of the two clusters of adjacent outer layers
starting at either end; but it never extends beyond nor includes the middle layer (odd nx)
or the two middle layers (even nx).  The maxouter argument can be used to limit further
the size of the outer list.
--- The inner list contains the remainder of the specified middle layers.  The option,
symmet, forces the inner layer list to be symmetrical by adding layers on either side
of the middle.

unpackLayers[nx_,layers_,symmet_:True,maxouter_:Infinity]:=
Module[{
out1,out2,out3,
numout, (*  Number of layers in the outer list  *)
s1,s2     (*  Returned values  *)
},
Assert[mfCleanLLQ[nx,layers],"layers in unpackLayers"];

(*  Special values for small nx (not dependent on layers)  *)
If[nx≤3,Return[{{{1},{1}},{{1},{2}},{{1},{2}}} [[nx]] ]];

(*  Default for layers == {}  *)
If[Length@layers≤0,Return[{{1},Range[2,nx-1]}]];

{out1,out2,out3}=countOuterLayers[nx,layers];
numout=Min[out3,Floor[(nx-1)/2],maxouter];
numout=Max[numout,1];
{s1,s2}={
Range[numout],
getLayerPart[layers,numout+1,nx-numout]
};

If[symmet,s2=Union[Join[s2,nx+1-s2]]];  (*  Force symmetry  *)
{s1,s2}
];

unpackLayersEvenEdges -- Support function for edge sequences

Separate the given layer list into an outer list and an inner list appropriate for move
sequences that involve swapping of flipping an even number of edges.
CALLS: unpackLayers (q.v.).

unpackLayersEvenEdges[nx_,layers_]:=
Module[{s1,s2,ix2a,ix2b,s2c,
len1max=Floor[(nx-2)/2],
},
Assert[mfCleanLLQ[nx,layers],"layers in unpackLayersEvenEdges"];
{s1,s2}=unpackLayers[nx,layers,True];
If[Length@s1>len1max,s1=Range@len1max];
ix2a=Length@s1+1;
ix2b=Floor[nx/2];
s2c=Select[layers,(ix2a≤#≤nx+1-ix2a )&];
s2c=Union[Join[s2c,nx+1-s2c]];  (*  Always force symmetry  *)
s2=Select[s2c,(ix2a≤#≤ix2b)&];
If[Length@s2≤0,s2=Range[ix2a,ix2b]];
{s1,s2}
];

seqWholeCube

Return a one-move sequence that rotates the entire cube about a given axis in a given direction.

seqWholeCube[nx_,face_, angleix_]:={{face,Range@nx,angleix}};

Move Sequences that Swap and/or Flip Corners

ssqFlip2Corners -- Flip two corners in place and in opposite directions

If the F, R or U face is chosen, one of the corners is the FRU corner; if the B, L or D
face is chosen, one of the corners is the BLD corner.  The other corner depends on the
value of variant as follows
    o  variant == 1:  Adjacent: {FUL, RFD, URB, BDR, LBU, DLF} for face = {F, R, U, B, L, D}
    o  variant == 2:  Face diagonal: {FDL, RBD, ULB, BRU, LUF, DFR} for face = {F, R, U, B, L, D}
    o  variant == 3:  Cube diagonal: BLD for face = {F, R or U}; FRU for face = {B, L or D}
For variant == 3, The final effect is the same for  face = {F, R or U};
For variant == 3, The final effect is the same for  face = {B, L or D};
The FRU or BLD corner is rotated clockwise; the other corner is rotated counterclockwise.

ssqFlip2Corners[nx_,face_,layers_,variant_]:=
Module[
{co,out3,s1,f1,f2,f3,f4,m1,m2,m3,m4,m5},

If[nx≤1,Return[{}]];   (*  Need at least a 2x2x2 cube  *)

Assert[mfCleanLLQ[nx,layers],"layers in ssqFlip2Corners"];

{f1,f2,f3,f4}=facePerm[{5,9,6,8},face];
out3=countOuterLayers[nx,layers][[3]];
s1=Which[
out3≤0,{1},
True,Range[Min[out3,Floor[nx/2]]]
];

m1={{f1,s1,gcCCW},{f2,s1,gcCW},{f1,s1,gcCW}};
m2={{f2,s1,gcCCW}};
m3=Join[m1,m2,m1];
m4={{f3,s1,gcCCW}};
m5=Join[m3,m4,revSeq@m3,revSeq@m4];

Switch[variant,
1, m5,(*  Adjacent corners on face *)
2, Join[{{f4,s1,gcCCW}},m5,{{f4,s1,gcCW}}],(*  Opposite corners on face  *)
3, Join[{{f4,s1,gcCCW2}},m5,{{f4,s1,gcCW2}}](*  Opposite corners along cube diagonal  *)
]
];

seqFlip2AdjacentCorners[nx_,face_,layers_] :=ssqFlip2Corners[nx,face,layers,1];
seqFlip2FaceDiagCorners[nx_,face_,layers_] :=ssqFlip2Corners[nx,face,layers,2];
seqFlip2CubeDiagCorners[nx_,face_,layers_] :=ssqFlip2Corners[nx,face,layers,3];

seqSwap3Corners

Swap positions of three corner cubes all in the same face using the face/layers selected
in the Basic Moves panel..

seqSwap3Corners[nx_,face_,layers_]:=Module[
{s1,out3,f1,f2,f3,m1,m2},
If[nx≤1,Return[{}]];   (*  Need at least a 2x2x2 cube  *)

Assert[mfCleanLLQ[nx,layers],"layers in seqSwap3Corners"];

{f1,f2,f3}=facePerm[{5,9,8},face];
out3=countOuterLayers[nx,layers][[3]];
s1=Which[
out3≤0,{1},
True,Range[Min[out3,Floor[nx/2]]]
];
m1={{f1,s1,gcCCW}};
m2={{f2,s1,gcCCW},{f3,s1,gcCW},{f2,s1,gcCW}};
Join[m1,m2,revSeq@m1,revSeq@m2]
];

Move Sequences that Swap and/or Flip Edges

ssqSwap3EdgesBasic

Swap subcubes in three edges that lie in  layers parallel to the given face.  Variants
allow cancelling unwanted middle-face rotations.  Does not affect corner subcubes.
Forces symmetry for inner layers.

ssqSwap3EdgesBasic[nx_,face_,layers_,variant_:2]:=Module[
{s1,s2,f1,f2,m1,m2,m3,m4},
(*
Action Menu Items:,
"Swap3Edges8Moves", Swaps the 3 edges but cancels the middle-face rotations,
"RotateMidFaces180Deg",  Cancels the edge swap but rotates 2 middle faces by 180 degrees
*)
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)
Assert[mfCleanLLQ[nx,layers],"layers in ssqSwap3EdgesBasic"];

{f1,f2}=facePerm[{4,6},face];
{s1,s2}=unpackLayers[nx,layers,True];

m1={{f1,s2,gcCCW}};
m2={{f2,s1,gcCW2}};
m3=Join[m1,m2,revSeq@m1,revSeq@m2];
m4=revSeq@m3;
Switch[variant,
1,m3,                                       (*  Not used *)
2,Join[m4,m4],             (* "Swap3Edges8Moves" *)
3,Join[m3,m3,m3],   (* "RotateMidFaces180Deg" (180 degreesin place) *)
_,Join[m4,m4]                 (* "Swap3Edges8Moves" *)
]
];

seqSwap3Edges8Moves[nx_,face_,layers_  ]:=ssqSwap3EdgesBasic[nx,face,layers,2];
seqRotateMidFaces180Deg[nx_,face_,layers_  ]:=ssqSwap3EdgesBasic[nx,face,layers,3];

seqFlip3EdgesSameFaceNoFlip

Swap three edges in the same face without flipping (i.e., keep the face color
in the selected  face).  Forces symmetry for inner layers.

seqSwap3EdgesSameFaceNoFlip[nx_,face_,layers_]:=Module[
{f1,f2,f3,f4,s1,s2,m1,m2},
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)

{f1,f2,f3,f4}=facePerm[{9,7,4,6},face];
{s1,s2}=unpackLayers[nx,layers,True];
m1={{f1,s1,gcCW2},{f2,s1,gcCW}};
m2={{f3,s1,gcCW2},{f4,s2,gcCW},{f3,s1,gcCCW2},{f4,s2,gcCCW}};
Join[m1,m2,m2,revSeq@m1] 
];

seqSwap3EdgesSameFaceFlip2

Swap three edges in the same face, flipping two of the three as they are swapped. (8 Moves) 
We do not enforce symmetry for this move sequence.

seqSwap3EdgesSameFaceFlip2[nx_,face_,layers_,symmet_:False]:=Module[
{s1,s2,f1,f2,f3,m1,m2},
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)

{f1,f2,f3}=facePerm[{8,9,6},face];
{s1,s2}=unpackLayers[nx,layers,symmet];
m1={{f1,s1,gcCW},{f2,s1,gcCCW},{f1,s1,gcCCW}};
m2={{f3,s2,gcCW}};
Join[m1,m2,revSeq@m1,revSeq@m2]
];

seqSwap3EdgesSameCorner

Swap three adjacent and mutually perpendicular edges that share a common corner. (12 Moves).
Forces symmetry for inner layers.

seqSwap3EdgesSameCorner[nx_,face_,layers_]:=Module[
{s1,s2,f1,f2,f3,f4,m1,m2},
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)

{f1,f2,f3,f4}=facePerm[{8,7,9,5},face];
{s1,s2}=unpackLayers[nx,layers,True];
m1={{f1,s1,gcCCW},{f2,s1,gcCCW},{f1,s1,gcCW2 },{f3, s1,gcCW},{f2,s1,gcCW2}};
m2={{f4,s2,gcCCW},{f3,s1,gcCCW2},{f4,s2,gcCW},{f3,s1,gcCW2}};

Join[m1,m2,m2,revSeq@m1]
];

seqSwap3EdgesSameCornerAlt

Swap three adjacent and mutually perpendicular edges that share a common corner, which is 
either the FRU corner or the BLD corner.  (12 Moves). 
Forces symmetry for inner layers.

seqSwap3EdgesSameCornerAlt[nx_,face_,layers_]:=Module[
{s1,s2,f1,f2,f3,f4,m1,m2},
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)

{f1,f2,f3,f4}=facePerm[{5,7,9,5},face];
{s1,s2}=unpackLayers[nx,layers,True];
m1={{f1,s1,gcCW},{f2,s1,gcCW},{f1,s1,gcCCW2},{f3,s1,gcCCW},{f2,s1,gcCW2}};
m2={{f4,s2,gcCCW},{f3,s1,gcCCW2},{f4,s2,gcCW},{f3,s1,gcCW2}};

revSeq@Join[m1,m2,m2,revSeq@m1]  (*  revSeq need for CW swap  *)
];

seqFlip2EdgesInPlace

Flip in place two edges that lie on opposite sides of the given face.  This sequence is
its own inverse.

seqFlip2EdgesInPlace[nx_,face_,layers_]:=Module[
{m1,m2},
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)
m1=seqSwap3EdgesSameFaceFlip2[nx,face,layers,True];
m2=seqSwap3EdgesSameFaceNoFlip[nx,face,layers];

Join[m1,m1,m2]
];

seqFlip4EdgesInPlace

Flip in place four edges, two that lie on opposite sides of the given face and the two that lie on
opposite sides of the given face.  This sequence is its own inverse.

seqFlip4EdgesInPlace[nx_,face_,layers_]:=Module[
{f1,m1,m2},
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)

f1=facePerm[6,face];
m1=seqFlip2EdgesInPlace[nx,face,layers];
m2=seqWholeCube[nx,f1,gcCW2];

Join[m1,m2,revSeq@m1,revSeq@m2]
];

seqFlipAllEdgesInPlace

Flip in place all twelve edges of the cube.  The final effect is the same regardless of the selected
face; but, the order in which the moves are executed will vary.  This sequence is its 
own inverse.  It is more pretty than practical.

seqFlipAllEdgesInPlace[nx_,face_,layers_]:=Module[{f1,f2,f3},

{f1,f2,f3}=facePerm[{4,5,6},face];
Join[
seqFlip4EdgesInPlace[nx,f1,layers],
seqFlip4EdgesInPlace[nx,f2,layers],
seqFlip4EdgesInPlace[nx,f3,layers]
]
];

Functions for Swapping Positions of an even number of Edges (Cube length >= 4)

ssqCoreFlip

This sequence swaps cyclicly positions of 4 edge subcubes and rotates an inner (core) layer by 180 
degrees.  It affects only the edge subcubes in the layers next to the outer layers (i.e., layer 2 
and layer nx - 1).  It is not affected by Layers selected in the Basic Moves panel.  
Repeating this sequence 4 times get back to the original state.

ssqCoreFlip[nx_,face_,layers_]:=Module[
{m1,m2,s1,s2,f1,f2},
If[nx≤3,Return[{}]];   (*  Need at least a 4x4x4 cube  *)

{s1,s2}=unpackLayers[nx,layers,True];
(*  Select only the "near" half of the inner layers.  *)
s2=Select[s2,(Length[s1]+1≤#≤Floor[nx/2])&];

{f1,f2}=facePerm[{5,4},face];
m1={{f1,s2,gcCW}};
m2={{f2,s1,gcCW2}};

Join[m1,m2,m1,m2,m1,m2,m1,m2,m1]
];

seqRotateCore180Deg

Rotate the first (largest) inner core by 180 degrees. For odd cube length, we do not
rotate the center layer of the first core. 
CALLS:  ssqCoreFlip, seqWholeCube, revSeq.

seqRotateCore180Deg[nx_,face_,layers_]:=Module[
{m1,m2,f1,f2},
If[nx≤3,Return[{}]];   (*  Need at least a 4x4x4 cube  *)

{f1,f2}=facePerm[{6,5},face];
m1=ssqCoreFlip[nx,f1,layers];
m2=seqWholeCube[nx,f2,gcCW2];

Join[m1,m1,m2,revSeq@m1,revSeq@m1,revSeq@m2]
];

seqSwap2Edges

Swap two pairs of opposite edges in the same face.  For odd cubes, the center layer
is not affected. The results of this function depend on the selected face and on 
selected **outer** layers.  This sequence is  its own inverse.

seqSwap2Edges[nx_,face_,layers_]:=Module[
{f1,f2,s1,s2,s2a,s2b,
maxlens1=Floor[(nx-2)/2],
mid=Floor[5],
m1,m2,m3,m4,m5
},
If[nx≤3,Return[{}]];   (*  Need at least a 4x4x4 cube  *)

{f1,f2}=facePerm[{6,4},face];
{s1,s2}=unpackLayersEvenEdges[nx,layers];
m1={{f1,s2,gcCCW}};
m2={{f2,s1,gcCW2}};
m3=Join[m2,{{f1,s2,gcCW}}];
m4=Join[m3,m3,m3,m2];
m5={{f1,s2,gcCW2}};

Join[m1,m4,m5,m4,m1]
];

seqSwap4Edges

Swap two pairs of opposite edges.  The swapped edges are in the layers perpendicular
to the selected face

seqSwap4Edges[nx_,face_,layers_]:=Module[
{f1,f2,f3,f4,s1,s2,m1,m2,m3,m4,m5,m6},
If[nx≤3,Return[{}]];   (*  Need at least a 44x4x4 cube.  *)

{f1,f2,f3}=facePerm[{9,6,4},face];
{s1,s2}=unpackLayersEvenEdges[nx,layers];
(*  Select only the "near" half of the inner layers.  *)
s2=Select[s2,(Length[s1]+1≤#≤Floor[nx/2])&];

m1={{f1,s2,gcCW2}};
m2={{f2,s2,gcCW},{f3,s1,gcCW2}};
m3={{f2,s2,gcCW}};
m4=Join[m2,m2,m2,m2,m3];
m5=Join[m1,m4,m4,revSeq@m1] ;
(*m6={{f2,Range@nx,gcCW2}};*)
m6=seqWholeCube[nx,f2,gcCW2];

Join[m5,m6,m5,revSeq@m6]
];

Pattern-Generating Sequences

seqDots

Sequence of moves to create a “Dots” pattern .  The selected outer and inner layers
determine the “size” and “shape” of the dot.
Dependence on selected face:  For any selected face, the mid-face subcubes in the F, R & U faces are 
swapped among themselves as are  those in the B, L & D faces. Selecting F, R or U produces identical
results; Selecting B, L or D produce identical results which are not quite the reverse of selecting F, R or U.
Selecting different Layers in the Basic Moves panel produces interesting results.

seqDots[nx_,face_,layers_,variant_:1]:=Module[
{f1,f2, s1,s2,m1,m2,m3},
If[nx≤2,Return[{}]];  (*  Need 3x3x3 cube or larger *)

{f1,f2}=facePerm[{4,6},face];
{s1,s2}=unpackLayers[nx,layers,True];
m1={{f1,s2,gcCW}};
m2={{f2,s2,gcCCW}};
m3=Join[m1,m2,revSeq@m1,revSeq@m2];

Switch[variant,
1,m3,
2,Join[revSeq@m3,revSeq@m3],
_,m3
]
]

seqCheckers

Sequence of moves to create a “Checkers” pattern in which each face ends up with a
checker-board pattern in its own color and the color of its opposite face.
The order in which the turns are executed differs depending on the selected face; but,
because of the symmetry of this sequence, the final result is the same for all selected
faces.  Currently, there is no dependence on selected layers.
This sequence is its own inverse.

seqCheckers[nx_,face_,layers_]:=Module[
{s1,f1,f2,f3},
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)

{f1,f2,f3}=facePerm[{4,5,6},face];
s1=If[OddQ@nx,
Range[2,nx-1,2],
Table[{i,nx+1-i},{i,2,nx/2,2}]
];
s1=Sort@Flatten[s1];

{{f1,s1,gcCW2},{f2,s1,gcCW2},{f3,s1,gcCW2}}
];

ssqBigCornerFlip

Flips two cube-diagonal-opposite “giant” subcubes in place in opposite directions.  
The giant subcubes are FRU and BLD and have length = nx - 1. 
This pattern is not affected by choice of Face or Layers.

ssqBigCornerFlip[nx_,face_,layers_]:=Module[
{s1,m1,m2,m3,m4},
If[nx≤2,Return[{}]];   (*  Need at least a 3x3x3 cube  *)

s1={};
m1=seqSwap3EdgesSameCornerAlt[nx,4,s1];
m2=revSeq@seqSwap3EdgesSameCornerAlt[nx,7,s1];
m3=seqDots[nx,4,s1];
m4=ssqFlip2Corners[nx,4,s1,3];

Join[m1,m2,m3,m4]
];

seqFlip2BigCorners

Flip two “big” corners in place and in opposite directions.  The “bigness” of the corner
is determined by the selected layers.  The two corners are the FRU and the BLD corners
and the FRU corner is rotated clockwise.	The argument “face” is ignored.
CALLS:  countOuterLayers, ssqBigCornerFlip

seqFlip2BigCorners[nx_,face_,layers_]:=
Module[
{bcSize,s1={},s2=Range@nx,f1=4,f2=5}
,
bcSize=countOuterLayers[nx,layers][[3]];

Which[
nx==1||bcSize==nx,  (*  Rotate the whole cube CW about the FRU-BLD diagonal  *)
	{{f1,s2,gcCW},{f2,s2,gcCW}},
nx==2, 
	seqFlip2FaceDiagCorners[nx,f1,s1],  (*  Flip two cube-diagonally opposite corners  *)
bcSize==0,
	ssqBigCornerFlip[nx,f1,s1],    (*  Default for nx ≥ 3: Flip corners of length nx-1 *)
bcSize≤Floor[nx/2],
	seqFlip2CubeDiagCorners[nx,f1,Range[bcSize]], (*  Normal cluster flip  *)
True,
	ssqBigCornerFlip[nx,f1,s1]
] 
];

seqRipple & seqMultiRipple

seqRipple effectively implements a whole-cube rotation in which the layers are turned individually
to create a neat effect.  seqMultipRipple executes seqRipple for all six faces.  The ripple effect is,
of course, only seen when sequence animation is turned on.  No dependence on selected layers

seqRipple[nx_,face_,dir_:gcCW]:={face,{#},dir}&/@Range[nx];

seqMultiRipple[nx_]:=Flatten[seqRipple[nx,#]&/@Range[4,9],1];

Special Actions -- Used for Demo Sequences

Overview

Special Actions are implemented in the form of “special moves”  used during the execution of a move sequence to perform a given action such as, pausing for a few seconds or modifying  selected control variables.  They are indicated in a move sequence by moves in which the face/axis value is outside the normal-move range (1 through 9) .  Presently, we support special actions using face/axis values greater than 100.  Other parts of a special move (i.e., layers and direction) specify any needed parameters (for example,  Pause duration) pertaining to the action.  Special moves are ignored by routines that change the cube state, such as rotateLayers.  Also, they are not logged to the history buffer or the recording buffer.

These special moves are used only in canned demonstrations; the user has no way to insert them into his/her user-defined sequences.  Maybe in later versions, we might include a user interface to create special moves.

Functions  for special actions  (Supports Demo Sequences)

NOTE:  The returned result of all sa* functions must be a List of standard-format moves,
even if  the List contains only one move.
Examples:   {{123,{4}, 2}, {106,{2,5}, 4}, . . . }     (Multiple move list)
                  {{117,{300}, 2}}                                  (Single move list)

saPause[msec_]:={{xsaPause,{msec},2}};
saViewOpt[opt_]:=saViewOpt[opt]={{xsaViewOpt,{opt},2}};
saShowCores[tf_]:=saShowCores[tf]={{xsaShowCores,{If[tf,1,0]},2}};
saSolvedCube[]:={{xsaSolvedCube,{1},2}};
saSubcubeSize[sz_]:={{xsaSubcubeSize,{sz},2}};
(*
NOTE:  Changing cvNx automatically aborts the executing sequence;
therefore, we do not support changing cvNx.
(* NOT SUPPORTED:  saNewNx[nx_]:=saNewNx[nx]={{xsaNewNx,{nx},2}};*) 
*)
saSeqAnim[tf_]:=saSeqAnim[tf]={{xsaSeqAnim,{If[tf,1,0]},2}};
saInvis[xyz_,layers_]:={{xsaInvis,layers,xyz}};
saClearInvis[]:={{xsaClearInvis,{1},2}};
saWireFrame[tf_]:=saWireFrame[tf]={{xsaWireFrame,{If[tf,1,0]},2}};

(*  ---- Colors and Styles ---- *)
saChangeFaceColors[ ix_]:={{xsaChangeFaceColors,{ix},2}};  (*  1: Default;  2: Random  *)
saAltFaceColors[ix_]:={{xsaAltFaceColors,{ix},2}};
saPermuteColors[ix_]:={{xsaPermuteColors,{ix},2}};  (*  Permute face colors  *)
saEdgeColor[rgb_]:={{xsaEdgeColor,rgb,2}};
saRingColor[rgb_]:={{xsaRingColor,rgb,2}};
saEdgeTh[ix_]:={{xsaEdgeTh,{ix},2}};
saRingTh[ix_]:={{xsaRingTh,{ix},2}};
saBkgdColor[rgb_]:={{xsaBkgdColor,rgb,2}};
saResetThColors[ ]:={{xsaResetThColors,{1},2}};

(* ---- History Support (has no effect) ---- *)
saNumHist[ix_]:={{xsaNumHist,{ix},2}};  (*  has no effect  *)

saSelectFace[f_]:={{xsaSelectFace,{f},2}};
saSelectLayers[laylist_]:={{xsaSelectLayers,laylist,2}};
saClearLayers[]:={{xsaClearLayers,{},2}};
saShowRings[tf_]:={{xsaShowRings,{If[tf,1,0]},2}};
saForceRings[tf_]:={{xsaForceRings,{If[tf,1,0]},2}};
saClearForceRings[]:={{xsaClearForceRings,{},2}};
saShowMisAl[opt_]:=saShowMisAl[opt]={{xsaShowMisAl,{opt},2}};
saViewPtAngle[ix1_,ix2_]:={{xsaViewPtAngle,{ix1,ix2},2}};   (*  ix1, ix2 = 1...13  *)
saViewPtMult[ix_]:={{xsaViewPtMult,{ix},2}};    (*  ix = 1...6  *)
saViewPtReset[]:={{xsaViewPtReset,{1},2}};
saNumSteps[n_]:={{xsaNumSteps,{n},2}};

specialAction[nx_, move_]

Handle moves encoded as special actions.  A special action is indicated by a value >= 100 in the face/axis part of the move.  
This function may alter one or more of the control (cv*)  variables.
 
Problems arise when trying to support modification of cvNx ; so, we don’t support special actions with this control variable.
CALLS:  solvedCube mfCleanLayers
CALLED BY:  Manipulate
RETURNS: Null

specialAction[nx_,move_]:=Module[
{action,param2,param3,p21,param2list},

{action,param2,param3}= move;

p21=If[Length@param2>0,First@param2,0];
If[action<100,Return[]];  (*  Not a special-action move  *)
(*
Print["specialAction 1:  move = ",move];
Print["specialAction 2:  action = ", action,
", param2 = ",   param2 ,", param3 = ",param3,", p21 = ",p21];
*)

Switch[action,

xsaPause,Pause[Clip[p21,{10,5000}]/1000.0],
xsaViewOpt,cvViewOpt=Clip[p21,{1,3}],
xsaShowCores,cvShowCores=p21≠0,
xsaSolvedCube,cvCubeState=solvedCube[nx,1],
xsaSubcubeSize,cvSubcubeSize=Clip[p21,{gcMinSubcubeSize,gcMaxSubcubeSize}],

xsaSelectFace,  cvSelectedFace=p21,
xsaSelectLayers, cvSelectedLayersFRU=param2,
xsaClearLayers, cvSelectedLayersFRU={},

xsaChangeFaceColors, {cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[-p21,{cvFaceC1, cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}],
xsaAltFaceColors,{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[p21+60],
xsaPermuteColors, {cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[-p21,{cvFaceC1, cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}],

xsaBkgdColor, cvBkgdColor=RGBColor@@(param2/100.0),
xsaEdgeColor, cvEdgeColor=RGBColor@@(param2/100.0),
xsaRingColor,  cvRingColor=RGBColor@@(param2/100.0),
xsaEdgeTh, cvEdgeTh=p21,
xsaRingTh, cvRingTh=p21,
xsaResetThColors, {cvEdgeTh,cvRingTh, cvEdgeColor,cvRingColor,cvBkgdColor }=
				{gcDfltEdgeTh,gcDfltRingTh, gcDfltEdgeColor,
				gcDfltRingColor,gcDfltBkgdColor},

xsaShowRings, cvShowRings=cvShowRingsTemp=p21≠0,
xsaForceRings, cvForceRings=p21≠0,
xsaClearForceRings,cvForceRings=cvShowRingsTemp=False,

xsaInvis,(param2list=mfCleanLayers[nx,param2];
		param3=Clip[move[[3]],{1,3}];
		Which[
		param3==1,cvXInvis=param2list,
		param3==2,cvYInvis=param2list,
		param3==3,cvZInvis=param2list
		]),
xsaClearInvis,cvXInvis = cvYInvis = cvZInvis={},
xsaWireFrame,cvWireFrame=p21≠0,

xsaShowMisAl, cvShowMisAl3=p21,
xsaViewPtAngle, cvViewPtAngle={
gcViewPtAngleValuesHoriz[[param2[[1]]]],
gcViewPtAngleValuesVert[[param2[[2]]]]
},
xsaViewPtMult, cvViewPtMult=gcViewPtMultValues[[p21]],
xsaViewPtReset, {cvViewPtAngle,cvViewPtMult}={gcViewPtAngleInit,gcViewPtMultInit},

xsaNumSteps,cvNumStepsNext=Clip[p21,{1,8}],
xsaNumHist, Null,(*  saNumHist -- Has no effect  ???  *)
_,    Null
];
];

Sequences Used by Demos

demoInit -- Standard starting conditions for demo

demoInit[nx_Integer]:=(
Join[ 
saShowMisAl[1],saWireFrame[False],saClearInvis[],
saSolvedCube[],saChangeFaceColors[1],saViewOpt[1],
saShowCores[False],saSubcubeSize[gcInitSubcubeSize]
]
);

demoTransistion -- Ripple moves for transitions between demos

demoTransition[nx_Integer,face_Integer,dir_Integer:gcCW]:=
Module[{m1},
m1=seqRipple[nx,face,dir];
Join[saNumSteps[1],m1, reverseSequence@m1,saNumSteps[3]] 
];

demoViewOptions -- Front, Back & Both views, with & w/o cores

demoViewOptions[nx_Integer]:=Module[{viewOpts={1,2,3}},
Join[
demoTransition[nx,4,gcCCW],
saShowCores[False], saPause[200],
Flatten[Join[saViewOpt[#],saPause[1000]]&/@viewOpts,1],
saViewOpt[1],saPause[1000],
saShowCores[True],
Flatten[Join[saViewOpt[#],saPause[1000]]&/@viewOpts,1],
saViewOpt[1],saPause[1000],
saShowCores[False]
]
];

demoSubcubeSizes -- Run through all subcube sizes from 40 to 100

demoSubcubeSizes[nx_Integer]:=Module[ 
{
scSizes=5(downUpRange[13,8]+7)   (*  40 ... 100  *)
},
Join[
demoTransition[nx,5,gcCCW],
saShowCores[False],saViewOpt[1],
Flatten[saSubcubeSize[#]&/@scSizes,1],
saSubcubeSize[gcInitSubcubeSize]
]
];

demoFaceColors -- Show random & alternate subcube face colors

demoFaceColors[nx_Integer]:=Module[
{mCheckers,mRand,mAlts,mPerm,mJoin,mRiff,pt=300},

mCheckers=seqCheckers[nx,4,{}];
mRand=saChangeFaceColors[2]&/@Range[2];    (*  Random colors *)
mAlts=saAltFaceColors[#]&/@{3,9};         (*  Alternate color schemes *)
mPerm=saChangeFaceColors[6]&/@Range[2];    (*  Permute CW *)
mJoin=Join[mRand,mAlts,mPerm];
mRiff=Riffle[Flatten[mJoin,1],saPause[pt]];  (*  Pause 1/2 secs between each  *)

Join[
demoTransition[nx,4],
saChangeFaceColors[1], saPause[pt],
saSubcubeSize[100],  (*  Show colors with full subcube size  *)
 saPause[pt],mCheckers, saPause[pt],
mRiff, saPause[pt],
saChangeFaceColors[1], saPause[pt],
mCheckers, saPause[pt],
saSubcubeSize[gcInitSubcubeSize]
]
];

demoEdgeColors -- Show various edge thicknesses and colors

(*  Show different edge thicknesses and colors  *)(*  Happy with this!!!  *)
demoEdgeColors[nx_Integer]:=Module[{
edgeThicks=downUpRange[11,5]-1, 
mThks,mRiffThks,
edgeColors=Tuples[{0,1},3]*100,
mCols,mRiffCols
},
mCols=saEdgeColor[#]&/@edgeColors;
mThks=saEdgeTh[#]&/@edgeThicks;
mRiffThks=Riffle[Flatten[mThks,1],saPause[100]];
mRiffCols=Riffle[Flatten[mCols,1],saPause[300]];

Join[
demoTransition[nx,6],
mRiffThks, saPause[1000],saResetThColors[],
mRiffCols,saPause[1000],saResetThColors[]
]
];

demoRingColors -- Show various thicknesses and colors of layer-indicator rings

demoRingColors[nx_Integer]:=Module[
{
ringThicks=Range[0,7], mThks,mRiffThks,
ringColors=Tuples[{0,1},3]*100,
mCols,mRiffCols,
faces={4,5,6,7,8,9,4},
mFace,mRiffFace,
pt=350
},
mThks=saRingTh[#]&/@ringThicks;
mCols=saRingColor[#]&/@ringColors[[1;;4]];
mFace=saSelectFace[#]&/@faces;
mRiffThks=Riffle[Flatten[mThks,1],saPause[pt]];
mRiffCols=Riffle[Flatten[mCols,1],saPause[pt]];
mRiffFace=Riffle[Flatten[mFace,1],saPause[pt]];

Join[
demoTransition[nx,7],
saClearLayers[],
saRingTh[0], saSelectFace[6],saSelectLayers[{1,3}],
saForceRings[True],saShowRings[True],
mRiffThks,saPause[pt],
mRiffCols,saPause[pt],
mRiffFace,saPause[pt],
saClearLayers[],
saForceRings[True],
seqRipple[nx,6],saForceRings[False],
seqCheckers[nx,4,{}],seqCheckers[nx,6,{}],
seqRipple[nx,9],
saClearForceRings[],saRingTh[gcDfltRingTh]
]
];

demoBkgdColors -- Show different background colors

demoBkgdColors[nx_Integer]:=Module[{colorPcts={50,100}},
Join[
demoTransition[nx,8],
Flatten[Join[saBkgdColor[{#,80,80}],saPause[200]]&/@colorPcts,1],
Flatten[Join[saBkgdColor[{80,#,80}],saPause[200]]&/@colorPcts,1],Flatten[Join[saBkgdColor[{80,80,#}],saPause[200]]&/@colorPcts,1],
saResetThColors[]
]
];

demoInvis -- Invisible layers and wireframes

demoInvis[nx_Integer]:=Module[{mCheckers}, 
mCheckers=seqCheckers[nx,4,{}];

Join[
demoTransition[nx,5],
saClearInvis[],
(*saSelectLayers[{2,3}],*)
saWireFrame[False],
saInvis[1,{2}],saPause[500],
saInvis[2,{2}],saPause[500],
saInvis[3,{2}],saPause[500],
mCheckers, saPause[500],saWireFrame[True],
mCheckers, saPause[500],saWireFrame[False],
saClearInvis[],saClearLayers[],saPause[500]
]
];

demoShowMisAl -- Demonstrate display of misaligned subcubes

demoShowMisAl[nx_Integer]:=Module[
{rot360Up=Flatten[Table[{{6,Range@nx,gcCW},First@saPause[200]},{4}],1],
pt=500
},
Join[
demoTransition[nx,6,gcCCW],
saShowMisAl[1],saWireFrame[False],saClearInvis[],
saSubcubeSize[100],saPause[pt],
seqRotateMidFaces180Deg[nx,5,{}],saPause[4 pt],
Flatten[saSubcubeSize[#]&/@{90,80, 70, 60,60},1],saPause[pt],
Flatten[saSubcubeSize[#]&/@{60, 70,gcInitSubcubeSize},1],saPause[pt],
saShowMisAl[3],saPause[pt],
saWireFrame[True],saPause[pt],
rot360Up,
saShowMisAl[2],saPause[pt],
rot360Up,
seqRotateMidFaces180Deg[nx,5,{}],
saWireFrame[False],saPause[pt],
saShowMisAl[1],saPause[pt]
]
];

demoPerspective -- Viewing angles and perspectives

demoPerspective[nx_Integer] := (
Join[
demoTransition[nx,9],
saViewPtReset[],
Flatten[Riffle[saViewPtMult[#]&/@downUpRange[8,4],{saPause[30]}],1],
Flatten[saViewPtAngle[7,#]&/@downUpRange[13,5],1],
Flatten[saViewPtAngle[#,5]&/@downUpRange[13,7],1],
saViewPtReset[]
]
);

demoNumSteps -- Animation with various numbers of steps using Ripple moves

demoNumSteps[nx_Integer]:=Module[
{mSteps1,mSteps2,mSteps3,mRippl1,mRippl2,mRippl3,
tbl1,tbl2,tbl3}
,
mSteps1=saNumSteps[#]&/@{2,5,8};
mSteps2=saNumSteps[1];
mSteps3=saNumSteps[#]&/@{7,4,3};
mRippl1=seqRipple[nx,#]&/@{4,5,6};
mRippl2=seqRipple[nx,#]&/@{7,8,9,4,5,6};
mRippl3=seqRipple[nx,#]&/@{7,8,9};
tbl1=Table[{mSteps1[[i]],mRippl1[[i]]},{i,3}];
tbl2=Table[{mRippl2[[i]]},{i,6}];
tbl3=Table[{mSteps3[[i]],mRippl3[[i]]},{i,3}];

Flatten[Join[tbl1,{{mSteps2}},tbl2,tbl3],2]
];

demoAll  --  Demonstration of DeepCube Functionality

Build a list of moves that comprise an interesting demo with all kinds of special actions.  
This is also useful for testing.

demoAll[nx_Integer]:=Join[
demoInit[nx],
demoViewOptions[nx],demoSubcubeSizes[nx],demoFaceColors[nx],
demoEdgeColors[nx],demoRingColors[nx],demoBkgdColors[nx],
demoInvis[nx],demoShowMisAl[nx],demoPerspective[nx],
demoNumSteps[nx]
];

Sequences & Patterns

namedMoveSeq

Given a sequence name, generate a sequence of moves (quarter and half turns).
Most of these sequences are affected by the choices of Face and Layers in the Basic Moves panel.

namedMoveSeq[seqname_, nx_,selface_,sellayers_]:= Module[
{movesFRU={},
layers=mfCleanLayers[nx,sellayers]
},
movesFRU=Switch[seqname,

 (* ============ CORNERS ============ *)
"Flip2AdjacentCorners",       seqFlip2AdjacentCorners[nx,selface,layers],
"Flip2FaceDiagCorners",       seqFlip2FaceDiagCorners[nx,selface,layers],
"Flip2CubeDiagCorners",       seqFlip2CubeDiagCorners[nx,selface,layers],
"Swap3Corners",                         seqSwap3Corners[nx,selface,layers],

(* ============ EDGES ============ *)
"Swap3Edges8Moves",                       seqSwap3Edges8Moves[nx,selface,layers],
"Swap3EdgesSameFaceNoFlip",  seqSwap3EdgesSameFaceNoFlip[nx,selface,layers],
"Swap3EdgesSameFaceFlip2",    seqSwap3EdgesSameFaceFlip2[nx,selface,layers],
"Swap3EdgesSameCorner",            seqSwap3EdgesSameCorner[nx,selface,layers],
"Swap3EdgesSameCornerAlt",    seqSwap3EdgesSameCornerAlt[nx,selface,layers],
"Flip2EdgesInPlace",                    seqFlip2EdgesInPlace[nx,selface,layers],
"Flip4EdgesInPlace",                    seqFlip4EdgesInPlace[nx,selface,layers],
"FlipAllEdgesInPlace",                seqFlipAllEdgesInPlace[nx,selface,layers],
"Swap2Edges",                                    seqSwap2Edges[nx,selface,layers],
"Swap4Edges",                           	seqSwap4Edges[nx,selface,layers],

(*  ======== PRETTY PATTERNS ======== *)
"Dots",                                           seqDots[nx,selface,layers,1],
"Checkers",                                  seqCheckers[nx,selface,layers],
"Ripple",                                      seqRipple[nx,selface],
"MultiRipple",                           seqMultiRipple[nx],
"RotateMidFaces180Deg",      seqRotateMidFaces180Deg[nx,selface,layers],
"RotateCore180Deg",               seqRotateCore180Deg[nx,selface,layers],
"Flip2BigCorners",                  seqFlip2BigCorners[nx,selface,layers],

(* ======== RANDOM SCRAMBLES ======== *)
"Scramble 10",                    randomMoves[nx,10],
"Scramble 20",                   randomMoves[nx,20],
"Scramble 100",                 randomMoves[nx,100],
"Scramble/Solve 10",      randomDemo[nx,5],
"Scramble/Solve 20",     randomDemo[nx,10],
"Scramble/Solve 100",   randomDemo[nx,50],

(* ======== Demos ======== *)
"View Options",                    demoViewOptions[nx],
"Subcube Sizes",                  demoSubcubeSizes[nx],
"Face Colors",                      demoFaceColors[nx],
"Edges Colors/Styles",    demoEdgeColors[nx],
"Rings Colors/Styles",    demoRingColors[nx],
"Background Colors",        demoBkgdColors[nx],
"Invisible Layers",           demoInvis[nx],
"Show Misaligned",            demoShowMisAl[nx],
"Perspective",                     demoPerspective[nx],
"Animation Steps",            demoNumSteps[nx],
"All Demos",                          demoAll[nx],

(* ======== Demos for testing and debugging ======== *)
"Demo 10K",                   randomDemo[nx,5000],     (*  Test & debug only  *)
"Demo 40K",                   randomDemo[nx,20000],  (*  Test & debug only  *)

(* ======== NOT RECOGNIZED ======== *)
_, {}     (*  None of the above:  Return an empty list  *)

]; (* End of  Switch[seqname, ...  *)

movesFRU        (*  Returned value  *)

]; (* -------- End of namedMoveSeq -------- *)

Display Functions for Rendering the Cube and its Cores

makeRings -- Draw “rings” to indicate layers selected for rotation

Build a Graphics3D object for drawing a set of “rings” to indicate layers selected for rotation.
CALLS:  subcubeCenters

makeRings[nx_,maSubcubeSize_,axis_,ringlayers_,ringcolor_:LightPurple,ringthick_:5]:=
Module[{
ringspac=30,
s1,s2,margin,p1,p2,vertexcoords0,ix,
offset, transvecs,
norings=Length@ringlayers≤0||ringthick≤0
},

If[norings,Return[{}]];      (**  Return an empty list.  **)

Assert[1≤nx≤gcMaxCubeLength,"nx in makeRings"];
Assert[1≤axis≤3,"axis in makeRings"];
Assert[1≤Min@ringlayers≤Max@ringlayers≤nx,"ringlayers in makeRings"];
Assert[mfCleanLLQ[nx,ringlayers],"ringlayers in makeRings"];

(**********  Build the rings  **********)

margin=1.20 maSubcubeSize;  (*  offset of ring corners from nearest subcube center *)

p1=subcubeCenters[nx][[1,1,1,1]]-margin;
p2=subcubeCenters[nx][[nx,nx,nx,1]]+margin;
vertexcoords0=Switch[axis,
1,{{0,p1,p1},{0,p2,p1},{0,p2,p2},{0,p1,p2}},
2,{{p1,0,p1},{p2,0,p1},{p2,0,p2},{p1,0,p2}},
3,{{p1,p1,0},{p2,p1,0},{p2,p2,0},{p1,p2,0}}
];
(*  Build a list of translation vectors for placing rings along the axis *)
transvecs = Reap[
Do[                                     (*  Loop over all ringlayers  *)
offset=subcubeCenters[nx][[ringlayers[[ix]],1,1,1]];  (*  Use X coord  *)
Sow[UnitVector[3,axis] (offset-ringspac)];
Sow[UnitVector[3,axis] (offset+ringspac)],
{ix,Length@ringlayers}
];
];
Translate[{
Directive[ringcolor,AbsoluteThickness[ringthick]],
GraphicsComplex[vertexcoords0,Line[{1,2,3,4,1}]]
},
transvecs[[2,1]]
]    (*  Returned value: A Graphics3D Object *)
];  (*  End function makeRings  *)

mostCommontOri[cubestate_]

Determine the most common subcube orientation in the given cube state. 
Break ties by favoring smaller orientation numbers.  
SPECIAL CASE:  If all subcubes have the same orientation, return a negative value,
which indicates to the calling program that the cube is solved.
CALLED BY:  flagRotInvis

mostCommonOri[cubestate_]:=Module[{
csTally=Tally@Flatten@cubestate
},
If[Length@csTally==1,
-csTally[[1,1]],       (* ---- Special case for solved cube  *)
SortBy[csTally,(100 #[[2]]+(25-#[[1]]))&][[-1,1]]
]
];

flagRotInvis[nx_, cubestate_, axis_ . . . ] --  Flag subcubes in rotated and invisible layers.

Modify the cube state by flagging subcube orientations in rotated layers and invisible  layers .
The subcube orientation values have the following meanings:
	1 ... 24: 	Visible subcubes in non-rotated  layers
	-24..-1: 	Visible subcubes in rotated  layers
	25:  		Wireframe subcubes in non-rotated  layers
	-25:		Wireframe subcubes in rotated  layers
	0: 		Invisible subcubes in rotated or non-rotated  layers
CALLS:  NONE

flagRotInvis[nx_,cubestate_,axis_,rotlayers_,angle_,
invis_:{{},{},{}},wireframesQ_:False,showMisAl3_:1]:=
Module[{
cs1=cubestate,
wf0,csTally, alignedOri,aopos
},
Assert[1≤axis≤3,"axis in flagRotInvis"];
Assert[MemberQ[{True,False},wireframesQ],"wireframesQ in flagRotInvis"];
Assert[1≤showMisAl3≤3,"showMisAl3 in flagRotInvis"];
Assert[mfCleanLLQ[nx,rotlayers],"rotlayers in flagRotInvis"];

wf0=If[wireframesQ,25,0];(*  25 -> wireframe; 0 -> invisible  *)

(* ------------------------------------------------------------ *)
(* Handle option showMisAl3 to show only misaligned, aligned 
  or all subcubes.  If showMisAl3 == 1 ("All") or if the cube
 is solved, do nothing.  *)
(*  Control@{cvShowMisAl3,{1->"All",2->"Aligned",3->"Misaligned"},  *)

If[showMisAl3 > 1,      (*  No change if showMisAl3 == 1  ("All") *)
alignedOri=mostCommonOri[cs1];
If[alignedOri>0,                                  (*  Cube not pristine  *)
aopos=Switch[showMisAl3,
2, Position[cs1,x_/;x≠alignedOri],  (*  Mask misaligned subcubes  *)
3, Position[cs1,alignedOri]                         (*  Mask aligned subcubes  *)
];
cs1=ReplacePart[cs1,aopos->wf0];                (*  Apply the mask  *)
]
];
(* ----------------------------------------------------------- *)

(*  ------ Flag subcubes in invisible layers. ------  *)
(*
  We need nx + 1 - invis[[i]] here to convert from the FRU reckoning
represented by invis[[i]] to XYZ reckoning needed in this function.
*)
If[Length@invis[[1]]>0,cs1[[nx+1-invis[[1]],All,All]]=wf0];
If[Length@invis[[2]]>0,cs1[[All,nx+1-invis[[2]],All]]=wf0];
If[Length@invis[[3]]>0,cs1[[All,All,nx+1-invis[[3]]]]=wf0];

(*  Subcubes in rotated layers are indicated by negative orientations.  *)
If[angle≠0&& Length@rotlayers>0,
Switch[axis,
1,cs1[[rotlayers,All,All]]*=-1,
2,cs1[[All,rotlayers,All]]*=-1,
3,cs1[[All,All,rotlayers]]*=-1
]];

cs1         (* ------ Returned value,  modified cube state *)

];(*  End function flagRotInvis  *)

getCore[nx, modcubestate_, coreNo_]

Return the modified state of the full cube or one of its cores.  The returned value is a three-dimensional cubical
matrix that is a submatrix of the input value, modcubestate.   If coreNo equals zero, the input argument, 
modcubestate (i.e., the whole 3D matrix), is returned.  modcubestate is the cube state modified by function 
flagRotInvis to flag subcubes in rotated  layer and invisible  layers.
coreNo = 0 indicates the full cube; coreNo = 1 indicates the next inner core, etc.
CALLED BY:  buildCubeAndCores
CALLS:  NONE

getCore[nx_,modcubestate_,coreNo_]:=Module[
{
layerlist,maxcores
},
Assert[1≤nx≤gcMaxCubeLength, "nx in getCore"];
(*
Print["getCore: nx = ",nx, ", coreNo = ", coreNo, ", 
           maxcores = ",Floor[(nx+1)/2]];
*)
If[coreNo==0,modcubestate,        (*  Returned value for coreNo == 0  *)
(*  ------ ELSE ------  *)
maxcores = Floor[(nx+1)/2]; (*  1, 1, 2, 2, 3, 3, ....  *)
Assert[0≤coreNo ≤ maxcores-1,"coreNo in getCore"];
layerlist=Range[coreNo+1,nx-coreNo];
modcubestate[[layerlist,layerlist,layerlist]]  (*  ----  Returned value  *)
]
];

buildCubeAndCores[corelistin_,  maNx_,  maCubeState,  . . . ]

Build and return a list of Graphics3D objects (mostly GraphicComplex’s) that can be used to  render a given cube
and all of its cores (if any).
Most all input arguments are prefixed by “ma.”  They correspond to the control variables (prefix “cv”) with the same
name, other than the prefix.
CALLS:  flagRotInvis, getCore, subcubeCenters, makeRings
CALLED BY:  displayGraphics

buildCubeAndCores[corelistin_,
maNx_,maCubeState_,maRotAngle_,
maViewOpt_,maShowCores_,
maWindowWidth_,maSubcubeSize_,
maCurrAxisXYZ_,maCurrLayerListXYZ_,
maShowRings_,maShowRingsTemp_,maForceRings_,
maXInvis_,maYInvis_,maZInvis_,maWireFrame_,maShowMisAl3_,
maRecOn_,maIxUds_,
maFaceC1_,maFaceC2_,maFaceC3_,maFaceC4_,maFaceC5_,maFaceC6_,
maEdgeTh_,maRingTh_, maEdgeColor_,maRingColor_,maBkgdColor_,
maViewPtAngle_, maViewPtMult_,
maShowFrames_,debugInfo_:False]:=
Module[
{
numOris=25,          (*  24 possible orientations plus the wireframe rendering  *)
corelist=Union@Flatten@{corelistin},

grCplxSubcubes,     (*  A list of GraphicComplex's for all 24 possible
					subcube orientations and the wire frame  *)
grCplxRings,          (*  List of GraphicsComplex's for rotated-layer rings  *)
modCubeState,        (*  "modified" cube state  *)
ixCore,                        (*  corelist index  *)
maxcores,coreNo,currCore,currgr3d,coretable,ringlayers,drawRings,

posNonRot,              (*  Positions of each positive orientation in the currCore array *)
posRot,                    (*  Positions of each negative orientation in the currCore array *)
transNonRot,          (*  Translation array for subcubes in non-rotated layers  *)
transRot,                (*  Translation array for subcubes in rotated layers  *)
grCplxNonRot,        (*  List of GraphicsComplex's for non-rotated layers  *)
grCplxRot,              (*  List of GraphicsComplex's for rotated layers  *)

ixOri,                      (*  subcube orientation index  (1 .. 25)  *)
rotlayerlist,
scCoords,edgeform,grCplxWireFrame,
currPolys,
nxfac, wwfac,edgeAbsTh, ringAbsTh,ringOption
},

rotlayerlist=mfCleanLayers[maNx,maCurrLayerListXYZ];

Assert[1≤maNx≤gcMaxCubeLength,"maNx in buildCubeAndCores"];
Assert[mfCleanLLQ[maNx,maCurrLayerListXYZ],"maCurrLayerListXYZ in buildCubeAndCores"];

(*
This function is always called with corelistin = { }.  It was never tested 
with a non-empty corelist.
*)
maxcores = Floor[(maNx+1)/2]; (*  1, 1, 2, 2, 3, 3, ....  *)
If[Length@corelist==0,corelist=Range[0,maxcores-1]];  (*  Default: Build all cores  *)

modCubeState=flagRotInvis[maNx,maCubeState,maCurrAxisXYZ,maCurrLayerListXYZ,maRotAngle,
{maXInvis, maYInvis,maZInvis},maWireFrame,maShowMisAl3 ];

(*  Compute absolute thicknesses of edges and rings.  *)
nxfac=Min[Sqrt[maNx/4.0],1.0]/maNx;
wwfac=maWindowWidth/320.0;
edgeAbsTh=Round[3.0 nxfac wwfac maEdgeTh,0.0001];
ringAbsTh=Round[2.4nxfac wwfac maRingTh,0.0001];

edgeform=If[maEdgeTh≤0,
EdgeForm[],   (*  No edges  *)
EdgeForm[{maEdgeColor,AbsoluteThickness[edgeAbsTh]}]
];

currPolys={    (*  Part of GraphicsComplex for rendering subcubes  *)
edgeform,
maFaceC1,Polygon[{5,7,8,6}],maFaceC2,Polygon[{7,3,4,8}],
maFaceC3,Polygon[{8,4,2,6}],maFaceC4,Polygon[{1,3,4,2}],
maFaceC5,Polygon[{1,2,6,5}],maFaceC6,Polygon[{7,3,1,5}]
};

scCoords=maSubcubeSize * gcUnitScVertexCoords;

grCplxWireFrame=GraphicsComplex[scCoords[[1]],
{GrayLevel[0.6],AbsoluteThickness[0.25],
Line[{{1,5,6,2},{5,7,8,6},{7,3,4,8},{3,1,2,4}}]}];

grCplxSubcubes=Append[
Table[GraphicsComplex[scCoords[[i]],currPolys],{i,24}],
grCplxWireFrame
];

coretable = Reap[

For[ixCore=1,ixCore≤Length@corelist,ixCore++,(*  Loop over specified cores  *)

coreNo = corelist[[ixCore]];
If[Not[0≤coreNo≤maxcores-1],Continue[]];  (*  Ignore non-applicable cores  *)

currCore=getCore[maNx,modCubeState,coreNo];

(* -------- Non-rotated layers -- Positive orientation numbers -------- *)
posNonRot=Table[Position[currCore,i],{i,numOris}];
transNonRot=Table[Extract[subcubeCenters[maNx-2coreNo],posNonRot[[i]]],{i,numOris}];
grCplxNonRot= 
If[Count[Flatten[currCore],ori_/;ori>0]≤0,
{},        (*  There are no non-rotated subcubes in this core *)
Reap[
For[ixOri=1,ixOri≤numOris,ixOri++,  (*  Loop over all 25 orientations  *)
If[Length@posNonRot[[ixOri]]>0,
Sow[Translate[grCplxSubcubes[[ixOri]],transNonRot[[ixOri]]]]
]
]
][[2,1]]
];

(* -------- Rotated layers -- Negative orientation numbers -------- *)
posRot=Table[Position[currCore,-i],{i,numOris}];
transRot=Table[Extract[subcubeCenters[maNx-2coreNo],posRot[[i]]],{i,numOris}];
grCplxRot= 
If[Count[Flatten[currCore],ori_/;ori<0]≤0,
{},    (*  There are no rotated subcubes in this core *)
Reap[
For[ixOri=1,ixOri≤numOris,ixOri++,       (*  Loop over all 25 orientations  *)
If[Length@posRot[[ixOri]]>0,
Sow[Translate[grCplxSubcubes[[ixOri]],transRot[[ixOri]]]]
]
]
][[2,1]]
];

(* -------- Rings around layers selected for rotation -------- *)
ringOption=maForceRings||(maShowRingsTemp&&maShowRings);
ringlayers=Select[maCurrLayerListXYZ-coreNo,1≤#≤(maNx-2coreNo)&];
drawRings=
Length@ringlayers>0          (*  Are there any rings to draw for this core?  *)
&&maRingTh>0                       (*  Are they thick enough to be visible?  *)
&&ringOption;                       (*  Does the calling program want them?  *)

If[drawRings,
grCplxRings=makeRings[maNx-2coreNo,maSubcubeSize,maCurrAxisXYZ,ringlayers,maRingColor,ringAbsTh];
];
(*  ------------------------------------------------  *)
(*    Collect everything for the current core         *)
(*  ------------------------------------------------  *)
currgr3d={
grCplxNonRot,
Rotate[grCplxRot,maRotAngle,UnitVector[3,maCurrAxisXYZ]],
If[drawRings,Rotate[grCplxRings,maRotAngle,UnitVector[3,maCurrAxisXYZ]],{}]
};

Sow[currgr3d]             (*  Building a list of cube and cores    *)

];          (*  End Loop over Cores:   For[ixCore = 1, ...  *)

][[2,1]];   (*  End of coretable = Reap[ .....  *)

(*  Store debug information in a global variable for later inspection.  *)
If[debugInfo,
debugBuildCubeCores={maCurrAxisXYZ,,maCurrLayerListXYZ,grCplxWireFrame,edgeform,
{maFaceC1,maFaceC2,maFaceC3,maFaceC4,maFaceC5,maFaceC6},
currPolys,grCplxSubcubes,coretable},
debugBuildCubeCores={}
];
coretable             (* ------  Returned value  *)
];  
(*  End of function buildCubeAndCores  *)

calcWindowDims

Compute the width and height of the display window in units of subcubes.  The results returned
by this function are used to compute window and frame sizes. 
Possible parameter values:  nx = 1...gcMaxCubeLength; showcores = True or False;  viewopt = 1, 2, 3
CALLED BY:  computeDisplayWidths
CALLS:  NONE

calcWindowDims[nx_,showcores_,viewopt_]:=
calcWindowDims[nx,showcores,viewopt]=     (*  Memoization:  54 possible combinations  *)
(
Assert[1≤nx≤gcMaxCubeLength,"nx in calcWindowDims"];
Assert[showcores==True|| showcores==False,"showcores in calcWindowDims"];
Assert[1≤viewopt≤3,"viewopt in calcWindowDims"];

Which[
!showcores, {nx,nx},
viewopt≤2, {Max[nx,2nx-2,3nx-10],Max[nx,2nx-6]},
True,                {Max[nx,2nx-6],Max[nx,2nx-2]}
]
);

calcRotAngle[dirix_, numsteps_, turnix_, exact_]

Calculate a rotation angle (in radians) needed to rotate a layer during animation.  
Uses memoization.
Arguments:
	dirix: Values (1,2,3,4,5);   indicates if the rotation is part of a quarter turn or a half turn and
	      whether it is CCW or CW.  {1, 2, 3, 4, 5}  -->  {0, Pi/2, Pi, -Pi/2, -Pi}.
	numsteps: Values(1 thru 9); the number of animation steps per quarter turn.
	turnix: Values(1 thru 2 numsteps - 1); indicates the current step of the animation.
	exact:  If True, return the exact value (useful for debugging), otherwise return a machine-precision real number.
CALLED BY:   Manipulate
CALLS:  NONE

calcRotAngle[dirix_Integer,numsteps_Integer,turnix_Integer,exact_:False]:=
 calcRotAngle[dirix,numsteps,turnix,exact]=   (*  Memoization:  5*8*17 = 680 values  *)
Module[{temp,angleRad},
Assert[1≤dirix≤5,"dirix in calcRotAngle"];
temp=Switch[dirix,
1,     0,                                                   (* No turn, probably never happens *)
2,    (2 numsteps-turnix+1)/2,    (* quarter turn CCW, Pi/2 *)
3,      2 numsteps-turnix,                (* half turn CCW, Pi *)
4, -(2 numsteps-turnix+1)/2,     (* quarter turn CW, -Pi/2 *)
5, -(2 numsteps-turnix)                  (* half turn CW, -Pi *)
];
angleRad= temp Pi/(2 numsteps);
If[exact,angleRad,N[angleRad]]      (*  Return the angle in radians  *)
];

dispCubeNoCores

Display one view (front or back) of the full cube without cores.  This functions can be used for either the
left, right or only panel.     Uses the Framed function to organize and clip the images.
CALLS:  NONE

dispCubeNoCores[cubx_,frrules_,frwidth_,frstyle_]:=
Framed[cubx[[1]],frrules,
ImageSize->{frwidth,frwidth},
FrameMargins->0,FrameStyle->frstyle
];

dispCubeCores1-- One view:  Front or Back

Display one view (either the front or back)  of the cube along with all cores.   Uses the
Framed function to organize and clip the images.
CALLS:  NONE

dispCubeCores1[cubx_,nx_,frrules_,frwidth_,frstyle_]:=
Module[{frimgsize,frfunc1,frdisp},

frimgsize=(frwidth/nx) Table[nx-i,{i,0,gcMaxCubeLength,2}]; 

(*frimgsize=(frwidth/nx) Range[nx,nx-gcMaxCubeLength+1,-2]; *)  (* TO DO SOON:  Use this instead.  *)

frfunc1[i_]:=Framed[cubx[[i]],frrules,ImageSize->{frimgsize[[i]],frimgsize[[i]]},
FrameMargins->0,FrameStyle->frstyle];

frdisp=Which[
nx==1||nx==2,frfunc1[1],
nx==3||nx==4,Row[{frfunc1[2],frfunc1[1]}],
nx==5||nx==6,Row[{Column[{frfunc1[3],frfunc1[2]},Center,0],frfunc1[1]}],
nx==7||nx==8,Row[{
Column[{Row[{frfunc1[4],frfunc1[3]}],frfunc1[2]},Center,0],
frfunc1[1]
}],
nx==9||nx==10,Row[{
Column[{
Row[{Column[{frfunc1[5],frfunc1[4]},Center,0],frfunc1[3]}],
frfunc1[2]
},Center,0],
frfunc1[1]
}]
];
frdisp
(*{frdisp,Prolog->{Text[Style,"HELLO!",Bold,16,{0.7,0.2}]}}(*  Doesn't work  *)*)
];

dispCubeCores2 -- Two views:  Front and Back

Display both front and back views of the cube along with all cores.  Uses the
Framed function to organize and clip the images
CALLS:  NONE

dispCubeCores2[cubx_,nx_,frrules_,frwidth_,frstyle_]:=
Module[{frimgsize,frfunct2,frblank},

frimgsize=(frwidth/nx) Table[nx-i,{i,0,gcMaxCubeLength,2}]; 
(*frimgsize=(frwidth/nx) Range[nx,nx-gcMaxCubeLength+1,-2]; *)  (*  TO DO SOON:  Use this instead.  *)

frfunct2[i_]:=Framed[cubx[[i]],frrules,ImageSize->{frimgsize[[i]],frimgsize[[i]]},
FrameMargins->0,FrameStyle->frstyle];
frblank[i_,j_]:=Framed["",frrules,ImageSize->{frimgsize[[i]],frimgsize[[j]]},
FrameMargins->0,FrameStyle->frstyle];

Which[
nx==1||nx==2,frfunct2[1],
nx==3||nx==4,Column[{frfunct2[1],frfunct2[2]},Center,0],
nx==5||nx==6,Column[{frfunct2[1],Row[{frfunct2[3],frfunct2[2]}]},Center,0],
nx==7||nx==8,Column[{
frfunct2[1],
Row[{Column[{frfunct2[4],frfunct2[3]},Center,0],frfunct2[2]}]
},Center,0],
nx==9||nx==10,Column[{
Row[{Column[{frblank[5,nx-5],frfunct2[5],frblank[5,5],frfunct2[4]},Center,0],frfunct2[1]}],
Row[{frfunct2[3],frfunct2[2]}]
},Center,0]
]
];

dispCubes

Call the appropriate cube/core display function depending on the given options showcores and numViews.
CALLS: dispNoCores, dispCubeCores1, dispCubeCores2.

dispCubes[cubx_,nx_,frrules_,frwidth_,showcores_,showframes_,numViews_:2]:=
Module[{frstyle},
frstyle=If[showframes,Automatic,None];
Which[
!showcores,        dispCubeNoCores[cubx,frrules,frwidth,frstyle],
numViews==1,     dispCubeCores1[cubx,nx,frrules,frwidth,frstyle],
True,                      dispCubeCores2[cubx,nx,frrules,frwidth,frstyle]
]
];

Functions for Move History

histTrunc

Truncate the history list at histPtr.
CALLS:  NONE

histTrunc[]:=Module[{},
gsHistBufferLength=gsHistPtr; (* Truncate the history buffers at gsHistPtr *)
(*  Update gbHistStixBuffer & gsHistStixLen  *)
If[gsHistBufferLength≤0,
gbHistStixBuffer[[1]]=1;gsHistStixLen=1,
(*  ELSE  *)
While[gbHistStixBuffer[[gsHistStixLen]] > gsHistBufferLength,
gbHistStixBuffer[[gsHistStixLen]]=0;gsHistStixLen--]
];
];

histGetIcon

Return one of five icons used in the move-history display.
CALLS:  NONE

histGetIcon[ix_]:=histGetIcon[ix]=Module[{diamond, octagon,iconrules,icon},
(*
ix = 1: Diamond (History pointer),
ix = 2: Red octagon (Begining of history list),
ix = 3: Square (End of history list),
ix = 4: Left-pointing triangle (more earlier),
ix = 5; Right-pointing triangle (more later)
*)
iconrules={ImageSize->12,BaselinePosition->Scaled[0.2]};

icon=Switch[ix,
1,diamond={{1,0},{0,1},{-1,0},{0,-1},{1,0}};
Graphics[{ColorData["Legacy"]["MidnightBlue"],Polygon[diamond]},iconrules],
2,(*octagon=Table[{Cos[t],Sin[t]},{t,Pi/8,17Pi/8,Pi/4}];*)
(*Graphics[{Red,Polygon[octagon]},iconrules],*)
octagon={Cos[#],Sin[#]}&/@(Range[1,17,2]Pi/8);
Graphics[{Red,Polygon[{octagon}]},iconrules],
3, Graphics[{Darker[Green,0.3],Rectangle[]},iconrules],
4,Graphics[{Orange,Polygon[{{0,1},{2,2},{2,0},{0,1}}]},iconrules],
5,Graphics[{Orange,Polygon[{{0,1},{-2,2},{-2,0},{0,1}}]},iconrules]
];
icon
];

histTextLine

Generate a single line of text to display the given list of text-formatted moves.  The input parameter
must be a List of Strings.
CALLS:  None

histTextLine[textmoveList_]:=Module[{ix,txtline},
(* Call this as:  str1 = histTextLine[textmoves[[i1;;i2]]]; *)
txtline="";
For[ix=1,ix≤Length@textmoveList,ix++,
txtline=StringJoin[txtline,textmoveList[[ix]],"  "]
];
txtline
];

histDropEarlyMoves

Drop several lines of moves at the beginning of the history buffers to make room for more moves to be 
added to the end.  This function is called by histAddOneMove when it detects the next move will cause the
buffers to overflow.
Variables affected:  gbHistStixBuffer, gsHistStixLen, gbHistIntBuffer, gbHistTextBufferFRU,
			gbHistTextBufferRCN, gsHistBufferLength, gsHistPtr.
CALLS:  NONE

histDropEarlyMoves[]:=
Module[{hs,movesgoal,ixstix,linestodrop,movestodrop},

hs=gbHistStixBuffer[[1;;gsHistStixLen]];
movesgoal=Round[ 0.15 gcHistMaxLen]; (* ~15% of capacity *)
ixstix=Position[hs,First@Select[hs,#≥movesgoal&,1]][[1,1]];
linestodrop=ixstix-1;
movestodrop=hs[[ixstix]]-1;  (* last move on previous line *)

(* Adjust gbHistStixBuffer array *)
gbHistStixBuffer=PadRight[Drop[gbHistStixBuffer,linestodrop],gcHistStixMaxLen];
gsHistStixLen-=linestodrop;
gbHistStixBuffer[[1;;gsHistStixLen]]-=movestodrop;

(* Adjust history list *)
gbHistIntBuffer=PadRight[Drop[gbHistIntBuffer,movestodrop],gcHistMaxLen];
gbHistTextBufferFRU=PadRight[Drop[gbHistTextBufferFRU,movestodrop],gcHistMaxLen,""];
gbHistTextBufferRCN=PadRight[Drop[gbHistTextBufferRCN,movestodrop],gcHistMaxLen,""];
gsHistPtr-=movestodrop;
gsHistBufferLength-=movestodrop;
];

histAddOneMove

Add a single move to the history buffer.

histAddOneMove[nx_,move_]:=Module[{ix1, textline},

(*  Log only normal moves  *)
If[!(1≤move[[1]]≤gcMaxFaceIx),Return[]];

gsHistPtr=gsHistPtr+1;
gsHistTotalMoves=gsHistTotalMoves+1;  (*  Counts how many times this function is called.  Used for general debugging.*)
If[gsHistPtr<gsHistBufferLength+1,histTrunc[]];  (*  We don't support insertion  *)

 (*  If necessary, drop some early moves to make room for the new move.  *)
If[gsHistPtr>gcHistMaxLen, histDropEarlyMoves[];];

gbHistIntBuffer[[gsHistPtr]]=mfMove2Int@move; 
gbHistTextBufferFRU[[gsHistPtr]]=mfMove2Text[nx,move,False];
gbHistTextBufferRCN[[gsHistPtr]]=mfMove2Text[nx,move,True];
gsHistBufferLength=gsHistPtr;

(* --------------------------------------------------------------- *)
(*  Update the starting indices that determine the display lines.  *)
(* --------------------------------------------------------------- *)
ix1=gbHistStixBuffer[[gsHistStixLen]];

textline=Switch[gcMoveTextFormat,
"FRU",histTextLine[gbHistTextBufferFRU[[ix1;;gsHistPtr]]],
"RCN",histTextLine[gbHistTextBufferRCN[[ix1;;gsHistPtr]]]
];

If[StringLength@textline >gcHistMaxDisplayWidth,
(* Text won't fit on this line; we need to start a new line.  *)
gsHistStixLen=gsHistStixLen+1;
gbHistStixBuffer[[gsHistStixLen]] = gsHistPtr;
];
];

histPtrLine

Calculate which text line the history pointer is in. Call as histPtrLine[gsHistPtr].
Bizzare Note:  Tried to use BinCounts here and it did not work (see the comments)
CALLS: NONE

histPtrLine[ptr_]:=Module[{ptrline,sl1,ix},
ptrline=Which[
ptr<0 || ptr>gsHistBufferLength || gsHistStixLen≤0,0, (* Indicates an error  *)
gsHistStixLen==1,1,                                                          (*  There's only one line  *)
True,-1
];
If[ptrline<0,
sl1=Flatten@{0,gbHistStixBuffer[[2;;gsHistStixLen]],Infinity};
ix=1;While[sl1[[ix]]≤ptr,ix++];
ptrline=ix-1;
];
ptrline
];

histPtrCol

Calculate the location of the history pointer in the given display line.
CALLS: NONE

histPtrCol[ptr_,lineno_]:=(

If[lineno <1,Print["1: histPtrCol: lineno = ",lineno, "  ptr = ",ptr]];
If[lineno >gsHistStixLen,Print["2: histPtrCol: lineno = ",lineno]];
If[ptr <0,Print["3: histPtrCol: lineno = ",lineno, "  ptr = ",ptr]];
If[ptr >gsHistBufferLength,Print["4: histPtrCol: lineno = ",lineno, "  ptr = ",ptr]];

Assert[1≤lineno≤gsHistStixLen&&0≤ptr≤gsHistBufferLength,"lineno and ptr in histPtrCol"];

Which[
lineno≤0 || lineno>gsHistStixLen,0,  (*  Indicates an error *)
ptr==0,1,          (*  ptr is at beginning of the history buffers *)
True,ptr-gbHistStixBuffer[[lineno]]+2
]
);

histPtrLineCol

Calculate the line and column of the history pointer. Call as {hline,hcol}=histPtrLineCol[gsHistPtr].

histPtrLineCol[ptr_]:=Module[{lineno, colno},
lineno=histPtrLine[ptr];
colno=histPtrCol[ptr,lineno];
{lineno,colno}
];

histCalcDispLines

Calculate the first and last history text lines to be displayed in the history panel so that the line 
containing the history pointer is approximately centered and as many lines as possible are shown 
on either side of it.  Call as: histCalcDispLines[gvHistMaxDispLines, gsHistStixLen, gsHistPtrLine[gsHistPtr]].
CALLS: NONE

histCalcDispLines[maxdisplines_,numhistlines_,hptrline_]:=
Module[{halfWindow,line1,line2},

(*halfWindow=Floor[maxdisplines/2];*)
halfWindow=Floor[(maxdisplines-1)/2];

(* If all history lines fit in the display window .... *)
If[numhistlines ≤maxdisplines,Return[{1,numhistlines}]];

line1=Clip[hptrline-halfWindow,{1,numhistlines-(maxdisplines-1)}];
line2=line1+(maxdisplines-1);
{line1,line2}
];

histDisplayLines

Build and return a table of strings showing the move history around the history pointer.
Required globals:  gsHistPtr, gbHistIntBuffer, gbHistTextBufferFRU, gbHistTextBufferRCN, 
gsHistBufferLength, gbHistStixBuffer, gsHistStixLen, gcHistMaxDisplayLines

histDisplayLines[nx_]:=
Module[{ptrline,ptrcol,line1,line2,linex,ix1,ix2,currline, txtlines,
nummoves1, nummoves2,tticons},

{ptrline,ptrcol}=histPtrLineCol[gsHistPtr];
{line1,line2}=histCalcDispLines[gcHistMaxDisplayLines,gsHistStixLen,ptrline];

txtlines={};
(*  How many moves lie before the first line and after the last line?  *)
nummoves1=gbHistStixBuffer[[line1]]-1;
nummoves2=If[line2==gsHistStixLen,0,gsHistBufferLength-gbHistStixBuffer[[line2+1]]+1];

tticons={  (*  Icons with tooltips  *)
Tooltip[histGetIcon[1],StringJoin["History pointer: ", ToString[gsHistPtr]]],
Tooltip[histGetIcon[2],"Start of history"],
Tooltip[histGetIcon[3],StringJoin["End of history: ",ToString[gsHistBufferLength]," moves"]],
Tooltip[histGetIcon[4],StringJoin[ ToString[nummoves1]," moves"]],
Tooltip[histGetIcon[5],StringJoin[ ToString[nummoves2]," moves"]]
};

For[linex=line1,linex≤line2,linex++,
ix1=gbHistStixBuffer[[linex]];
ix2=If[linex<gsHistStixLen,gbHistStixBuffer[[linex+1]]-1,gsHistBufferLength];

currline=Switch[gcMoveTextFormat,
"FRU",gbHistTextBufferFRU[[ix1;;ix2]],
"RCN",gbHistTextBufferRCN[[ix1;;ix2]]
];

(* Inset the required icons into the curren line (currline)  *)
If[linex==ptrline,currline = Insert[currline,tticons[[1]],ptrcol]];
If[linex==1,currline = Insert[currline,tticons[[2]],1]];
If[linex≠ 1 && linex==line1,currline = Insert[currline,tticons[[4]],1]];
If[linex==gsHistStixLen, currline =Insert[currline,tticons[[3]],-1]];
If[linex≠gsHistStixLen&&linex==line2, currline =Insert[currline,tticons[[5]],-1]];

AppendTo[txtlines,currline];
];
txtlines
];

histMovePtr

Move the history pointer and execute the indicated number of moves either forward or
backward.  Don’t go beyond either end of the history list.
CALLED BY:  Manipulate/ctlHistoryControls

histMovePtr[nummoves_]:=Module[{oldptr,newptr,moves},

(*  If a sequence or move is currently executing, do 
nothing; i.e., ignore the button press.  *)
If[cvNumMoves>0, Return[]];

oldptr=gsHistPtr;
newptr=Clip[gsHistPtr+nummoves,{0,gsHistBufferLength}];
gsHistTraversing=Sign[nummoves];
(*gsHistPtr=newptr;*)

(* Forward moves:  oldptr+1 ---> newptr  *)
(* Backward moves:  oldptr ---> newptr+1 (in reverse) *)

moves=Which[
nummoves>0,Table[mfInt2Move@gbHistIntBuffer[[i]],{i,oldptr+1,newptr}],
nummoves<0,reverseSequence@Table[mfInt2Move@gbHistIntBuffer[[i]],{i,newptr+1,oldptr}],
True,{}
];
cvMoves=moves; 
cvMovesIx=1;  cvNumMoves=Length@cvMoves;
cvSeqDesc=StringJoin["History ",
If[nummoves>0,"<forward>","<back>"]," (",ToString@Length@moves,")"];

cvShowRingsTemp=False; (*  No rings while traversing the move history  *)
(*  Returns Null  *)
];

restoreBuffers -- Support for save/load

Regenerate the strings in gbHistTextBufferFRU and gbHistTextBufferRCN from gbHistIntBuffer.  This is done after
restoring the cube state from a file.  Text buffers are not stored in the cube-state file since they can be easily regenerated.

restoreBuffers[nx_Integer]:=
(
gbRecIntBuffer=PadRight[gsRecIntBuffer,gcRecMaxLen];
gbHistStixBuffer = PadRight[gsHistStixBuffer, gcHistStixMaxLen];
gbHistIntBuffer=PadRight[gsHistIntBuffer,gcHistMaxLen];

gbHistTextBufferFRU=ConstantArray["",gcHistMaxLen];
gbHistTextBufferRCN=ConstantArray["",gcHistMaxLen];
Do[gbHistTextBufferFRU[[ix]]=mfInt2Text[nx,gbHistIntBuffer[[ix]],False],{ix,gsHistBufferLength}];
Do[gbHistTextBufferRCN[[ix]]=mfInt2Text[nx,gbHistIntBuffer[[ix]],True],{ix,gsHistBufferLength}];
);

Recording Buffer Functions -- (Part of User-Defined Sequences)

recClear[ ] -- Reinitialize the Recording Buffer

recClear[]:=(
gbRecIntBuffer=ConstantArray[0,gcRecMaxLen];
gsRecIntBuffer={};
gsRecBuffLen=0; 
);

recStatusBuffSize -- Builds trailing text for recording status info

recStatusBuffSize[len_,nx_:0, buf_:{},fmt_:"RCN"]:=Module[
{txt,lastmove,
 bfmt="buffer empty", 
bfeq="buffer size = "
},
txt= If[nx≤0
,    (*  THEN:  Text when recording is inactive (OFF)  *)
Which[
len≤0,bfmt,
len==1,bfeq<>"1 move",
len>1,bfeq<>ToString@len<>" moves"
]
,    (*  ELSE:  Text when recording is active (ON)  *)
If[len>0,
lastmove=Switch[fmt,
"FRU",mfInt2Text[nx,buf[[len]],False],
"RCN",mfInt2Text[nx,buf[[len]],True]
]
];
Which[
len≤0,bfmt,
len==1,bfeq<>"1 move ("<>lastmove<>")",
len>1,bfeq<>ToString@len<>" moves  ("<>lastmove<>")"
]
];
txt  (*  Returned value  *)
];

recDisplayOff -- Display status when recording is OFF

recDisplayOff[len_]:=Module[
{
offTtip="Recording buffer collects moves for User-Defined Sequences.",
txt=recStatusBuffSize[len]
},
Row[{
Tooltip[Style[" recording:",Black,12,Bold,FontFamily->"Arial"],offTtip],
Tooltip[Style["OFF",Black,12,Italic,Bold,FontFamily->"Times"],offTtip],
Tooltip[Style[txt,Black,12,FontFamily->"Arial"],offTtip]
},Spacer[2]]    (*  Returned value  *)
];

recDisplayOn -- Display status when recording is ON

recDisplayOn[nx_,len_,buf_,fmt_]:=Module[
{
onTtip="Recording buffer collects moves for user-defined sequences.",
txt
},
(*  Call as:  recDisplayOn[nx, gsRecBuffLen, gbRecIntBuffer, gcMoveTextFormat] *)
txt=recStatusBuffSize[len,nx,buf,fmt];
Row[{
Tooltip[Style[" recording:",Black,12,Bold,FontFamily->"Arial"],onTtip],
Framed[
Tooltip[Style["ON",White,12,Bold,Italic,FontFamily->"Arial"],onTtip],
FrameMargins->{{2,3},{0,0}},Background->gcBkgdRecOn
],
Tooltip[Style[txt,Black,12,FontFamily->"Arial"],onTtip]
},Spacer[2]]    (*  Returned value  *)
];

recBackspace[ ] -- Delete the last move from the recording buffer

recBackspace[]:=(
If[gsRecBuffLen>0,
gbRecIntBuffer[[gsRecBuffLen]]=0;
gsRecBuffLen--;
];
);

recDropEarlyMoves

Drop several moves at the beginning of gbRecIntBuffer to make room for more moves to be 
added to the end.  This function is called by recAddOneMove when it detects the next move will cause the
buffer (gbRecIntBuffer) to overflow.
GLOBAL VARIABLES AFFECTED:   gbRecIntBuffer, gvRecBufferLen
CALLS:  NONE

recDropEarlyMoves[]:=
Module[{movestodrop},
movestodrop=Round[ 0.15 gcRecMaxLen]; (* ~15% of capacity *)
gbRecIntBuffer=PadRight[Drop[gbRecIntBuffer,movestodrop],gcRecMaxLen];
gsRecBuffLen-=movestodrop;
];

recAddOneMove

Append a single move to the recording buffer.  This function supports a finite buffer size that retains the most
recent few thousand moves.
CALLED BY:  logOneMove,  logMoveList
CALLS: recDropEarlyMoves, mfMove2Int

recAddOneMove[move_]:=
(
If[!(1≤move[[1]]≤gcMaxFaceIx),Return[]];  (*  Log only normal moves  *)
gsRecBuffLen++;
If[gsRecBuffLen>gcRecMaxLen,   recDropEarlyMoves[]];
gbRecIntBuffer[[gsRecBuffLen]]=mfMove2Int@move; 
);

User-Defined Sequences

These functions support the User-Defined Sequence (UDS) feature.

udsClearAll

Delete the contents of all user-defined sequences and also the record buffer.  This must
be done when changing the cube length (cvNx) because any recorded moves won’t
be meaningful for the new cvNx.  (See also histInit[...].)

udsClearAll[]:=(
gsUserSeq = ConstantArray[{},gcNumUserSeqs];
gsUserSeqName=ConstantArray["",gcNumUserSeqs];
gsLastDeletedUds={};
gsLastDeletedUdsName="";
);

updateUdsOK

Update flags for enabled options in User-Defined Sequence (UDS) buttons.  
CALL AS: {cvUdsDeleteOK, cvUdsExecuteOK,cvUdsOverwriteOK,cvUdsSaveOK} = 
            updateUdsOK[gsUserSeq[[cvIxUds]],gsRecBuffLen,cvReady];

updateUdsOK[useq_,recbufflen_,rdy_]:=
Module[{udslen,del,exec,over,save},
udslen=Length@useq;
del=udslen>0;
exec=rdy&&udslen>0;
over=recbufflen>0;
save=recbufflen>0&&udslen≤0;
{del,exec,over,save}
];

recUdsName

Generate a name for a User-Defined Sequence.  The name is a short string intended to fit on one line in a
popup-menu item.  It comprises the sequence length in parentheses followed by its first few moves in FRU 
or RCN notation. Example:  “(18)2F’ 123D2, B2’...”
CALLED BY:  recUdsOverwriteMoves
CALLS:  mfMove2Text

recUdsName[nx_,moveseq_,fmt_,maxmoves_:5,maxchars_:8]:=
Module[{len=Length@moveseq,firstmoves, text, chars},

Assert[1≤nx≤gcMaxCubeLength, "nx in recUdsName"]; 
Assert[(fmt=="FRU"||fmt=="RCN"),"fmt in recUdsName"];

If[len≤0,Return[""]];  (*  Special case: empty move sequence  *)

(*  Display at most maxmoves moves but no more than maxchars characters  *)
firstmoves= Take[moveseq,Min[maxmoves,len]];
text=Switch[gcMoveTextFormat,
"FRU",StringJoin@Riffle[mfMove2Text[nx,#,False]&/@firstmoves," "],
"RCN",StringJoin@Riffle[mfMove2Text[nx,#,True]&/@firstmoves," "]
];
chars=If[StringLength@text<=maxchars,  
text,
StringTake[text,maxchars-3]<>"..."
];
"("<>ToString@len<>")"<>chars        (*  Returned value  *)
]

recUdsOverwriteMoves

Save moves as a user-defined sequence indexed by ix.
CALLED BY: recUdsSave
CALLS:  recUdsName

recUdsOverwriteMoves[nx_,ix_,fmt_,moves_]:=(
gsUserSeq[[ix]]=If[Length@moves≤gcMaxUdsMoves,
moves,
Take[moves,gcMaxUdsMoves]   (*  Limit the size of a UDS  *)
];
gsUserSeqName[[ix]]=recUdsName[nx,moves,fmt,10,18];
)

recUdsSave

Save, Overwrite or Append (according to saveopt) the contents of the recording buffer to a user-defined 
sequence indexed by  ix.  This function returns the generated name of the newly saved UDS or the input 
value, origname, if nothing is saved.
CALLED BY:  Manipulate (Buttons “Save”, “Ov’write” and “Append” in User-Defined Sequences)
CALLS:  mfInt2Move, recUdsOverwriteMoves

recUdsSave[nx_,ix_,fmt_,saveopt_]:=Module[
{
recbuffempty=gsRecBuffLen≤0,
currudsempty=Length@gsUserSeq[[ix]]≤0,
recbuffmoves, currudsmoves,seqname
},
Assert[1≤nx≤gcMaxCubeLength,"nx in recUdsSave"];
Assert[1≤ix≤gcNumUserSeqs,"ix in recUdsSave"];
Assert[saveopt=="Save"||
saveopt=="Overwrite"||
saveopt=="Append","saveopt in recUdsSave"];
(* 
---------------------------------------------------,
LOGIC  (Whether to Save, Overwrite, or Append),
---------------------------------------------------,
If the record buffer is empty, 
		Do nothing (Don't want Save, Overwrite, or Append to act as Delete),
Else (record buffer is not empty)
	If current User-Defined Sequence (currUDS) is empty, 
		Save the record buffer to currUDS (Identical action for Save, Overwrite, and Append)
	Else (current UDS is not empty and record buffer is not empty),
		Save Button ---> Do nothing (Don't overwrite),
		Overwrite Button ---> Save record buffer to current UDS, overwriting the previous UDS,
		Append Button ---> Join currUDS and record buffer and overwrite to current UDS,
	 EndIf,
EndIf,
---------------------------------------------------------------------------------------,
NOTE:  Some (or, perhaps, all) of this logic may be redundant because it is already 
implemented by the Enabled->... options of the Button's that call this function.
 See Buttons "Save", "Ov'write" and "Append" in ctlUserDefinedSeqs.,
---------------------------------------------------------------------------------------,
*)

If[recbuffempty, Return[]];

recbuffmoves=mfInt2Move/@Take[gbRecIntBuffer,gsRecBuffLen]; (*  Convert format: Integer to Move  *)

If[currudsempty,    (*  Same action for Save, Overwrite, and Append  *)
recUdsOverwriteMoves[nx,ix,fmt,recbuffmoves];
Return[]
];
(*
  At this point, both the recording buffer and currUDS have moves. 
We don't allow "Save":  the user has to explicitly 
 choose "Ov'write" or "Append"
 *)
If[saveopt=="Overwrite" ||saveopt=="Append",
(*  currUDS will be replaced.  Save it for Undelete.  *)
gsLastDeletedUds=gsUserSeq[[ix]];
gsLastDeletedUdsName=gsUserSeqName[[ix]]
];

Which[
saveopt=="Save",   Null,  (*  No change, "Save" does not overwrite.  <<< Maybe add a Beep. >>>  *)
saveopt=="Overwrite",    (*  Overwrite currUDS  *)
	recUdsOverwriteMoves[nx,ix,fmt,recbuffmoves],
saveopt=="Append",   (*  Append: Join currUDS and record buffer.  *)
	recUdsOverwriteMoves[nx,ix,fmt,Join[gsUserSeq[[ix]],recbuffmoves]],
True,                    (*  Should never get here  *)
	"Error in recUdsSave: bad saveopt"
];
]

recUdsDelete

Delete the contents of the user-defined sequence indicated by index, ix.
CALLED BY: Manipulate (Button “Delete” in User-Defined Sequences)
CALLS: NONE

recUdsDelete[ix_]:=(
Assert[1≤ix≤gcNumUserSeqs,"ix in recUdsDelete"];
(*  CALLED BY Manipulate as: recUdsDelete[cvIxUds]  *)
(*
Save this sequence in case you want to restore it; but, 
don't overwrite the last deleted sequence with an empty sequence.
*)
If[Length@gsUserSeq[[ix]]≤0,Return[]]; (*  It's empty; do nothing.  *)

(*  If the UDS is not empty, save it for later undelete and then clear it.  *)
gsLastDeletedUds=gsUserSeq[[ix]];
gsLastDeletedUdsName=gsUserSeqName[[ix]];
gsUserSeq[[ix]]={};
gsUserSeqName[[ix]]=  "";
)

recUdsRestore

Copy the contents of the last-deleted user-defined sequence into an empty UDS indicated by the given
index number, ix.
NOTE:  This function will only restore to an empty user-defined sequence and only if there is something
to restore; otherwise it does nothing.
MODIFES GLOBALS: gsUserSeq, gsUserSeqsName
USES GLOBALS: gsLastDeletedUds,  gsLastDeletedUdsName
CALLED BY:  Manipulate (Button “Undel” in User-Defined Sequences)
CALLS: NONE

recUdsRestore [ix_]:=(
Assert[1≤ix≤gcNumUserSeqs,"ix in recUdsRestore"];
(*  CALLED BY Manipulate as: recUdsRestore[cvIxUds],  *)

If[Length@gsUserSeq[[ix]]≤0               (*  Only restore to an empty UDS ...  *)
&&Length@gsLastDeletedUds>0,     (*  .. and only if there's something to restore.  *)
gsUserSeq[[ix]]=gsLastDeletedUds;
gsUserSeqName[[ix]]=gsLastDeletedUdsName;
]
)

Logging Moves

These functions add one or more moves to the history buffer and/or the UDS recording buffer.

logOneMove

Copy the given move into the history buffer (if gsHistTraversing == 0) and to the  recording 
buffer (if cvRecOn == True). 
The parameter, moves,must be  a single move: e.g., {2, {1, 3, 5}, 4} or {1, 5 ,4}
CALLED BY:  Manipulate
CALLS:  hsitAddOneMove, recAddOneMove
USES GLOBALS:  gsHistTraversing;  MODIFIES GLOBALS:  gsHistPtr

logOneMove[nx_,move_]:=(
If[gsHistTraversing==0 
, (* THEN *)  histAddOneMove[nx,move]
, (* ELSE *)  gsHistPtr+=gsHistTraversing
];
If[cvRecOn, recAddOneMove[move]];
)

logMoveList

Copy a list of moves into the history buffer and/or the  recording buffer. The parameter,movelist, must
be a ** List** of one or more moves:  e.g., {{2, 3, 4}, {1, {3, 5}, 2}, ...} or {{1,{2,3,4}, 3}}
CALLED BY:  Manipulate
CALLS:  histAddOneMove,  recAddOneMove
USES GLOBALS:  gsHistTraversing;  MODIFIES GLOBALS:  gsHistPtr

logMoveList[nx_,movelist_]:=(
If[gsHistTraversing==0 
, (* THEN *)  histAddOneMove[nx,#]&/@movelist
, (* ELSE *)  gsHistPtr+=gsHistTraversing *Length@movelist
];
If[cvRecOn, recAddOneMove[#]&/@movelist];
)

Main Display Functions

getFaceLabels

Create a set of three labels to be used as a background Prolog to identify the faces in the front 
view (Front, Right, Up) or the back view (Back, Left, Down).  These labels are only used for the
full cube, not the cores.
CALLED BY: displayGraphics.

getFaceLabels[fb_,vopt_]:=getFaceLabels[fb,vopt]=Module[{fontsize,labelstyle},
fontsize=If[vopt==3,18,24];
labelstyle={Lighter[Blue,0.2],Italic,  FontFamily->"Times",fontsize, Bold};

Which[
fb==1,   (*  Front, Right, Up labels *)
{
Text[Style["Front",labelstyle],{0.3,0.2}],
Text[Style["Right",labelstyle],{0.7,0.2}],
Text[Style["Up",      labelstyle],{0.3,0.8}]
},
True,   (*  Back, Left, Down labels *)
{
Text[Style["Back",   labelstyle],{0.3,0.8}],
Text[Style["Left",   labelstyle],{0.7,0.8}],
Text[Style["Down",   labelstyle],{0.7,0.2}]
}
]
];

computeDisplayWidths -- Called by displayGraphics

Compute the widths and heights of the items that appear in the Manipulate output cell.
CALLED by displayGraphics.

computeDisplayWidths[nx_,viewOpt_, showCores_, winWid_]:=
Module[
{winw, ttlw,fnw,frs,frw,sepw,seph,dispw,disph,fcsz},

{dispw,disph}=calcWindowDims[nx,showCores,viewOpt];
sepw=4;

Which[
viewOpt≤2&&!showCores,    (*  Back OR Front view, NO cores  *)
	winw=1.206 winWid;
	ttlw=(5 winw)/3;
	fnw=ttlw;
	frs=1.0;
	frw=frs winw
,
viewOpt≤2&&showCores,       (*  Back OR Front view, ALL cores  *)
	winw=1.206 winWid;
	ttlw=(5 winw)/3;
	fnw=ttlw;
	frs=If[nx≤6,1.0,(1.0nx)/disph];
	frw=frs winw
,
viewOpt>2&&!showCores,    (*  BOTH Back & Front views, NO cores  *)
	winw=1.0winWid;
	ttlw=winw;
	fnw=2ttlw + sepw;
	frs=1.0;
	frw=(frs winw nx)/dispw
,
viewOpt>2&&showCores,        (*  BOTH Back & Front views,  ALL cores  *)
	winw=1.0winWid;
	ttlw=winw;
	fnw=2ttlw + sepw;
	frs=If[nx≤2,(1.0 dispw)/disph,(1.25 dispw)/disph];
	frw=(frs winw 1.0nx)/dispw
];

seph=(frs winw disph)/dispw;
(*
Increase the footnote width by a little bit.  This keeps the display 
width nearly constant for all view options and cube lengths.
*)
fnw=fnw+4;

(*
The expression, Min[Sqrt[nx/4],1], yields values {(1/2),(1/(Sqrt[2])),((Sqrt[3])/2),1,1,1,1,1,1}, for 
nx = 1 through gcMaxCubeLength.  This scaling prevents cubes with lengths of 1, 2 and 3
from filling up the panel and, thus, appearing too large.
*)
fcsz=Min[Sqrt[nx/4],1]frw gcImageRatio;

(*  Return a List of widths and heights.  *)
{winw, ttlw, fnw, frs, frw, sepw,seph,dispw,disph,fcsz}
];    (*  End function computeDisplayWidths  *)

buildInfoPanel -- Called by displayGraphics

Build the information panel that shows the number of misaligned subcubes, status of the currently
executing sequence, etc.  There is also the capability to display optionally debug information in this panel.
CALLED by displayGraphics.

buildInfoPanel[nx_,cubestate_,recOn_, fnotewid_]:=Module[
{
showDebugInfo=False,
nxCubed=nx^3,maxaligned,
recStatusRow,misaTtip, seqdTtip, misaFrac,
seqdText,seqStatusRow,seqStatusFrPane,
debugStatusRow1,debugStatusRow2,histStatusRow,testlines,
infoStyle1, infoStyle2, infoStyle3
},
infoStyle1={Lighter[Blue,0.6],12,FontFamily->"Courier"};
infoStyle2={Black,12,FontFamily->"Arial"};
infoStyle3={Black,12,Bold,FontFamily->"Arial"};

maxaligned =Max[Tally[Flatten[cubestate]][[All,2]]];

recStatusRow=If[recOn,
recDisplayOn[nx, gsRecBuffLen, gbRecIntBuffer, gcMoveTextFormat],
recDisplayOff[gsRecBuffLen]
];

misaTtip="Fraction of misaligned subcubes";
seqdTtip="Name & status of currently executing sequence";
misaFrac=If[nxCubed>maxaligned,
StringJoin[ToString@(nxCubed-maxaligned),"/",ToString@(nxCubed)],
Style["** SOLVED **",Red,Bold,12]
];

seqdText=StringJoin[
cvSeqDesc,
"  <",
If[cvReady || cvMovesIx>cvNumMoves,"done",ToString[cvMovesIx]],
">"
];

seqStatusRow=Row[{
Grid[{{                 (*  Using 1-by-1 Grids to effect a fixed ItemSize  *)
Row[{
Tooltip[Style["misaligned:   ",infoStyle3],misaTtip],
Tooltip[Style[misaFrac,infoStyle2],misaTtip],
}]
}}, Alignment->Left,ItemSize->fnotewid*0.019]
,
Grid[{{
Row[{
Tooltip[Style["sequence:   ",infoStyle3],seqdTtip],
Tooltip[Style[seqdText,infoStyle2],seqdTtip],
}]
}}, Alignment->Left,ItemSize->fnotewid*0.04]
},Spacer[10]];

seqStatusFrPane=Framed[Pane[seqStatusRow,ImageSize->{Automatic,14}],FrameStyle->None];

If[showDebugInfo,
debugStatusRow1=Row[{
Style["cvReady = "<>If[cvReady,"-True -","-False-"],infoStyle1],
Style["     ET: "~~StringTake[DateString@debugElapsedTime,-8],infoStyle1]
}];
debugStatusRow2=Style["Memory Usage (MB): miufe = "~~ToString[MemoryInUse[$FrontEnd]/2.0^20]~~
"  miu = "~~ToString[MemoryInUse[]/2.0^20]~~
"  maxmu = "~~ToString[MaxMemoryUsed[]/2.0^20],infoStyle1];
histStatusRow=Style["History Info:  "~~
ToString[gsHistPtr]~~
"  "~~ToString[gsHistBufferLength]~~
"  "~~ToString[gsHistTotalMoves]~~
"  "~~ToString[gcHistMaxLen]~~
"  "~~ToString[gcHistMaxDisplayWidth]~~
"  "~~ToString[gcHistMaxDisplayLines]~~
"  "~~ToString[gsHistStixLen]~~
 "  bcbd = "~~ToString[Round[debugBytesBigDisplay/2.0^10]]~~" KB"~~
"  cum = "~~ToString[Round[debugCumBytesBigDisplay/2.0^20]]~~" MB"~~
" ",infoStyle1];
];

testlines=If[showDebugInfo,
Column[{seqStatusRow,debugStatusRow1,debugStatusRow2,histStatusRow,recStatusRow},Left],
Column[{seqStatusRow,recStatusRow},Left]
];

testlines
];(*  End function buildInfoPanel  *)

buildHistoryLines -- Build the move history window (Called by displayGraphics)

buildHistoryLines[nx_]:=Module[
{hStyle1,hStyle2,ptrline,ptrcol,hlines},

hStyle1={Black,Bold,16,FontFamily->"Courier"};
hStyle2={Black,Bold,14,FontFamily->"Courier"};
{ptrline,ptrcol}= histPtrLineCol[gsHistPtr];

hlines=Style[With[{hdl=histDisplayLines[nx]},
Table[Row[hdl[[i]],"   "],{i,Length@hdl}]]//Column,hStyle2];
hlines
];

displayGraphics

This is the main workhorse function that constructs all the graphics to be displayed in the Manipulate
ouput panel.  It includes the status window, panel titles,  the cube (with optional cores),  and a lower
panel containing recent move history.  

(2015-06-02)  This version is called with only control-variable arguments.

displayGraphics[maNx_,maOldNx_,maCubeState_,maSolvedOri_,
maNumSteps_,maNumStepsNext_,maSeqAnimOn_,maRotAngle_,
maRevSeqs_,maAbortFlag_,maFreeze_,
maViewOpt_,maShowCores_,
maWindowWidth_,maSubcubeSize_,
maCurrAxisXYZ_,maCurrLayerListXYZ_,
maShowRings_,maShowRingsTemp_,maForceRings_,
maXInvis_,maYInvis_,maZInvis_,maWireFrame_,maShowMisAl3_,
maRecOn_,maIxUds_,maUdsDeleteOK_,maUdsExecuteOK_,maUdsOverwriteOK_,maUdsSaveOK_,
maFaceC1_,maFaceC2_,maFaceC3_,maFaceC4_,maFaceC5_,maFaceC6_,
maEdgeTh_,maRingTh_, maEdgeColor_,maRingColor_,maBkgdColor_,
maViewPtAngle_, maViewPtMult_,
maNumHist_,maClearHist_,
maShowFrames_]:=
Module[{
bkgdCol1,bkgdCol2,
winw, ttlw, fnw, frs, frw, sepw,seph,dispw,disph,fcsz,
viewptFront,viewptBack,coresizes,
cubePlotRules,cubeFrameRules,
viewLabelRules,histFrameRules,vertDiv,infoFrameRules,
testlines,hlines,
cubeAndCores,cubeFront,cubeBack,graphicsOut
},

debugElapsedTime=AbsoluteTime[]-debugStartTime;

(*  Cumulative ByteCount for the display  *)
(*  Works OK if TrackedSymbols :> Full.  Causes infinite looping when TrackedSymbols:>All   *)
(*  debugCumBytesBigDisplay+=debugBytesBigDisplay;  *)

{viewptFront,viewptBack} = calcViewpoints[maViewPtAngle,maViewPtMult];

bkgdCol1=maBkgdColor;
bkgdCol2=Blend[{bkgdCol1,Blue},0.05];

infoFrameRules={Alignment->{Left,Center},BaseStyle->{Background->bkgdCol2}};
cubeFrameRules={Alignment->{Center,Center},BaseStyle->{Background->bkgdCol1}};
viewLabelRules={Alignment->{Center,Center},BaseStyle->{Background->bkgdCol2}};
vertDiv=  {Alignment->{Center,Center},BaseStyle->{Background->Blue}};  

histFrameRules={Alignment->{Left,Center},FrameMargins->{{5,5},{0,5}},
BaseStyle->{Background->bkgdCol2}};

cubePlotRules={ViewVertical->{0,0,1},Lighting->{{"Ambient",White}},
Boxed->gcBoxedDisplay,Axes->gcDrawAxes,SphericalRegion->True,Background->bkgdCol1};

(* -------- Sizes and widths for windows, panels, cubes and cores -------- *)

coresizes = Range[maNx,1,-2];    (*  {9, 7, 5, 3, 1}, {8, 6, 4, 2}, etc.  *)

(*  Compute widths and heights for various windows and frames.  *)
{winw, ttlw, fnw, frs, frw, sepw,seph,dispw,disph,fcsz}=
computeDisplayWidths[maNx, maViewOpt,maShowCores,maWindowWidth];
(*
Print["displayGraphics:  maWindowWidth = ",maWindowWidth,", winw = ",winw,", ttlw = ",ttlw,
", fnw = ",fnw, ", frs = ", frs,", frw = ",frw,", fcsz = ",fcsz];
*)

testlines=buildInfoPanel[maNx, maCubeState,maRecOn,fnw];

hlines=buildHistoryLines[maNx];

cubeAndCores=buildCubeAndCores[{},
maNx,maCubeState,maRotAngle,
maViewOpt,maShowCores,
maWindowWidth,maSubcubeSize,
maCurrAxisXYZ,maCurrLayerListXYZ,
maShowRings,maShowRingsTemp,maForceRings,
maXInvis,maYInvis,maZInvis,maWireFrame,maShowMisAl3,
maRecOn,maIxUds,
maFaceC1,maFaceC2,maFaceC3,maFaceC4,maFaceC5,maFaceC6,
maEdgeTh,maRingTh, maEdgeColor,maRingColor,maBkgdColor,
maViewPtAngle, maViewPtMult,
maShowFrames,False];

(*  debugCubeAndCores = cubeAndCores;  *)

(* ---------------------------------------------------------------- *)
(* -------- Display cubes and cores in frames and windows. -------- *)
(* ---------------------------------------------------------------- *)

graphicsOut=Switch[maViewOpt,        (*  maViewOpt may be 1, 2, or 3  *)

1,                           (* -------- Front View Only -------- *)
cubeFront=Table[
Graphics3D[{cubeAndCores[[(maNx-s)/2+1]]},cubePlotRules,ViewPoint->viewptFront,
PlotRange->100 Sqrt[2]s,ImageSize->(s/maNx) fcsz,
Prolog->If[s==maNx,getFaceLabels[1,maViewOpt],{}]]
,{s,coresizes}];
Framed[Column[{
Framed[testlines,infoFrameRules,ImageSize->fnw,FrameMargins->10,FrameStyle->None],
Framed[Style["Front View",Bold,16,FontFamily->"Arial"],viewLabelRules,ImageSize->ttlw,FrameMargins->0],
dispCubes[cubeFront,maNx,cubeFrameRules,frw,maShowCores,maShowFrames,1],
Framed[hlines,histFrameRules,ImageSize->{fnw,20gcHistMaxDisplayLines}]
},Center,0],Background->bkgdCol1],

2,                           (* -------- Back View Only -------- *)
cubeBack=Table[
Graphics3D[{cubeAndCores[[(maNx-s)/2+1]]},cubePlotRules,ViewPoint->viewptBack,
PlotRange->100 Sqrt[2]s,ImageSize->(s/maNx) fcsz,
Prolog->If[s==maNx,getFaceLabels[2,maViewOpt],{}]]
,{s,coresizes}];
Framed[Column[{
Framed[testlines,infoFrameRules,ImageSize->fnw,FrameMargins->10,FrameStyle->None],
Framed[Style["Back View",Bold,16,FontFamily->"Arial"],viewLabelRules,ImageSize->ttlw,FrameMargins->0],
dispCubes[cubeBack,maNx,cubeFrameRules,frw,maShowCores,maShowFrames,1],
Framed[hlines,histFrameRules,ImageSize->{fnw,20gcHistMaxDisplayLines}]
},Center,0],Background->bkgdCol1],

3,                           (* -------- Front and Back Views -------- *)
cubeFront=Table[
Graphics3D[{cubeAndCores[[(maNx-s)/2+1]]},cubePlotRules,ViewPoint->viewptFront,
PlotRange->100 Sqrt[2]s,ImageSize->(s/maNx) fcsz,
Prolog->If[s==maNx,getFaceLabels[1,maViewOpt],{}]]
,{s,coresizes}];
cubeBack=Table[
Graphics3D[{cubeAndCores[[(maNx-s)/2+1]]},cubePlotRules,ViewPoint->viewptBack,
PlotRange->100 Sqrt[2]s,ImageSize->(s/maNx) fcsz,
Prolog->If[s==maNx,getFaceLabels[2,maViewOpt],{}]]
,{s,coresizes}];
Framed[Column[{
Framed[testlines,infoFrameRules,ImageSize->fnw,FrameMargins->10,FrameStyle->None],
Grid[{
{
Framed[Style["Back View",Bold,16,FontFamily->"Arial"],viewLabelRules,ImageSize->ttlw,FrameMargins->0],
Framed["",cubeFrameRules,ImageSize->{sepw,sepw},FrameMargins->0,FrameStyle->None],
Framed[Style["Front View",Bold,16,FontFamily->"Arial"],viewLabelRules,ImageSize->ttlw,FrameMargins->0]
},{
dispCubes[cubeBack,maNx,cubeFrameRules,frw,maShowCores,maShowFrames,2],
Framed["",vertDiv,ImageSize->{sepw,seph-sepw},FrameMargins->0,FrameStyle->None],
dispCubes[cubeFront,maNx,cubeFrameRules,frw,maShowCores,maShowFrames,2]
}
},
Alignment->{Center,Center},Spacings->0,Frame->None],   (*  End of Grid  *)

Framed[hlines,histFrameRules,ImageSize->{fnw,20gcHistMaxDisplayLines}]
},Center,0],(*  End of Column  *)
Background->bkgdCol1]  (*  End of Framed for front and back views.  *)

];(*  End of  graphicsOut=Switch[maViewOpt, ... *)

debugBytesBigDisplay=ByteCount[debugBigDisplay];    

(* Print["Time for displayGraphics  ",64(AbsoluteTime[]-t1)]; *)
(* Print["Byte count from displayGraphics = ", ByteCount[debugBigDisplay]]; *)

graphicsOut   (* ------ Returned value  *)
];
(*  End  displayGraphics  *)

displayInfoGrid  --  Debugging Function (Leave this one in)

This function is used for debugging and testing.  It produces, in a Grid format, a
simple display, which is used to replace the usual 3D graphical display in the
Manipulate output panel.
NOTE:  This needs updating and expansion.

displayInfoGrid[nx_,cubestate_,rotangle_]:= Module[{moveText},
moveText = If[
Length@cvMoves>0 && cvMovesIx≤Length@cvMoves,
mfMove2Text[nx,cvMoves[[cvMovesIx]],True],
"---"
];
Grid[{
{
Row[{"Bytes = ",ByteCount[debugBigDisplay]}],
Row[{"Aligned = ",Max[Tally[Flatten[cubestate]][[All,2]]]}], 
Row[{"nx = ",nx}],
Row[{"move = ",moveText}]
},{
Row[{"move ix = ",cvMovesIx}],
Row[{"num moves = ",cvNumMoves}],
Row[{"Angle = ",rotangle * 180/Pi}],
Row[{"Time = ",StringTake[DateString@debugElapsedTime,-8] }]
},{
Row[{"hBuffLen = ",gsHistBufferLength}],
Row[{"histPrt = ",gsHistPtr}],
Row[{"hStixlen = ",gsHistStixLen}],
Row[{"Ori[1,1,1] = ",cubestate[[1,1,1]]}]
}
},Frame->All,ItemSize->{10,2},Alignment->Left]
];

Executing Named Sequences of Moves

setMoveSeq

Supporting function for the doSeqNamed, etc. ... functions.  Assigns a sequence of moves
to the global variable, cvMoves.
CALL AS: setMoveSeq[moves, rev], where moves is a list of one or more moves, and rev
		is a flag, which specifies forward or reverse.,
CALLED BY:  doSeqNamed, doSeqUser, doSeqFaceLayerlist,
CALLS:  reverseSequence  (Conditionally),  histTrunc (Conditionally)
USES GLOBALS:  gsHistPtr, gsHistBufferLength
MODIFIES GLOBALS:  gsHistTraversing
MODIFIES CONTROL VARIABLES:  cvMoves, cvMovesIx, cvNumMoves.

setMoveSeq[moves_,rev_:False]:=( 
If[gsHistPtr≠gsHistBufferLength,histTrunc[]];
gsHistTraversing=0;
cvMoves=If[rev,reverseSequence@moves,moves];
cvMovesIx=1;  cvNumMoves=Length@cvMoves;
);

doSeqNamed

Execute a sequence of moves that may be affected by the selected face and selected
layers in the Basic Moves area.  If rev is True, return the inverse of the resulting sequence.
CALLING EXAMPLE:  “Flip2AdjacentCorners” :-> 
	doSeqNamed[“Flip2AdjacentCorners”, cvNx, cvSelectedFace, 
				cvSelectedLayersFRU, cvRevSeqs]
CALLS: namedMoveSeq, setMoveSeq
RETURNS: Null

doSeqNamed[name_,nx_,selface_,sellayers_,rev_:False]:=
Module[{moves,layers,m1,pos,sname},
(*
Print["doSeqNamed:  name = ",name, "  nx = ",nx, 
"  selface = ", selface, "  sellayers = ",sellayers, "  rev = ",rev];
*)
layers=mfCleanLayers[nx,sellayers];    (*  We really need this here.  *)
moves = namedMoveSeq[name,nx,selface,layers];

(*  Build the description for the sequence status line.  *)
cvSeqDesc=
If[StringStartsQ[name,"Scramble"],
pos=StringPosition[name," "][[1,1]];
sname=StringTake[name,pos-1];
StringJoin[sname," (",ToString[Length@moves],")"]
,
StringJoin[name,If[rev," <rev>",""]," (",ToString[Length@moves],")"]
];
(*
cvSeqDesc=name<>If[rev," <rev>",""]<>" ("<>ToString[Length@moves]<>")";
*)
setMoveSeq[moves,rev];
cvShowRingsTemp=False;  (*  No rings for a named sequence  *)
];

doSeqUser

Execute a user-defined sequence specified by the given index, ix.
CALLED BY: Manipulate
CALL AS:  doSeqUser[cvIxUds, cvNx,  False | True);
CALLS:  setMoveSeq, clearMoveSeq
MODIFIES:  cvSeqDesc  (See also setMoveSeq)
RETURNS: Null

doSeqUser[ix_,nx_, rev_:False]:=   Module[{moves,len}, 

Assert[1≤ix≤gcNumUserSeqs,"ix in doSeqUser"];   (*  Typically 1...50  *)

moves=gsUserSeq[[ix]];
len=Length@moves;
(*  If there are no moves, clear cvMoves and do nothing.  *)
If[len≤0,cvMoves={}; cvMovesIx=1;  cvNumMoves=0; Return[]];

cvSeqDesc=StringJoin["User-Defined Seq ",ToString[ix],
If[rev," <rev>",""]," (",ToString[len],")"];
setMoveSeq[moves,rev];
cvShowRingsTemp=False;  (*  No rings for a user-defined sequence  *)
];

doSeqFaceLayerList

Execute a single move by rotating one or more parallel layers about an axis indicated by the given face.
CALLED BY:  Manipulate (Basic Moves controls)
CALLS:  buildBasicMove, setMoveSeq
CALL AS:   doSeqFaceLayerList[cvNx,  cvSelectedFace, cvSelectedLayersFRU,  angleix];
RETURNS: Null

doSeqFaceLayerList[nx_,selface_,sellayers_,angleix_]:=Module[{move,layers},
 (*  
NOTE:  This function does NOT use reverseSequence.,
NOTE: We need to call mfCleanLayers here because cvSelectedLayersFRU,
which is specified by a ListPicker and a TogglerBar, might not
be sorted.
*)
layers=mfCleanLayers[nx,sellayers];    (*  We really need this here.  *)
move=buildBasicMove[nx,selface,layers,angleix];
cvSeqDesc="Basic Move "<>mfMove2Text[nx,First@move];
setMoveSeq[move,False];
cvShowRingsTemp=True;   (*  Show rings for a Basic Move  *)
];

doSeqWholeCube -- Rotate the whole cube about a given face

Execute a sequence of moves that rotates the entire cube about a given face in a given direction.
NOTE:  This function does NOT use reverseSequence.
CALLS:  seqWholeCube,  mfMove2Text,  setMoveSeq
CALL Example:   doSeqWholeCube[cvNx,  4,  gcCCW];   (RCN:  wcF’)
RETURNS: Null

doSeqWholeCube[nx_,face_, angleix_]:=(
cvSeqDesc= StringJoin[
"Whole-Cube ",
{"Front ","Right ","Up "}[[face-3]],
{"CCW","CCW2","CW","CCW"}[[angleix-1]],
" (1)"
];
setMoveSeq[seqWholeCube[nx,face,angleix],False];
cvShowRingsTemp=False;  (*  No rings for a whole-cube rotation  *)
);

Miscellaneous Functions

finishMove

Reset relevant control variables when a single move is completed.

finishMove[maNx_,maSelectedFace_,maSelectedLayersFRU_]:=Module[
{mlCurrAxisXYZ,mlCurrLayerListXYZ, mlRotAngle},
(*
CALL AS:  {cvCurrAxisXYZ,cvCurrLayerListXYZ, cvRotAngle} =
finishMove[cvNx,cvSelectedFace,cvSelectedLayersFRU]; 
*)
{mlCurrAxisXYZ,mlCurrLayerListXYZ}=
getAxisLayersXYZ[maNx,maSelectedFace,maSelectedLayersFRU];
mlRotAngle = 0.0;
{mlCurrAxisXYZ,mlCurrLayerListXYZ, mlRotAngle}
];

finishSequence

Reset relevant control variables when a sequence of moves is completed.

finishSequence[maNx_,maSelectedFace_,maSelectedLayersFRU_]:=Module[
{mlCurrAxisXYZ,mlCurrLayerListXYZ, mlRotAngle,
mlShowRingsTemp,mlForceRings,mlReady},
(*
CALL AS: {cvCurrAxisXYZ,cvCurrLayerListXYZ, cvRotAngle,
cvShowRingsTemp,cvForceRings,cvReady} =
finishSequence[cvNx,cvSelectedFace,cvSelectedLayersFRU]; 
*)
(*clearMoveSeq[];*)
{mlCurrAxisXYZ,mlCurrLayerListXYZ}=
getAxisLayersXYZ[maNx,maSelectedFace,maSelectedLayersFRU];
mlRotAngle = 0.0;
mlShowRingsTemp= True;
mlForceRings= False;
mlReady=True;
{mlCurrAxisXYZ,mlCurrLayerListXYZ, mlRotAngle,
mlShowRingsTemp,mlForceRings,mlReady}
];

handleAbort

Reset relevant control variables when a sequence of moves is aborted.

handleAbort[maMoves_, maMovesIx_]:=Module[
{mlMoves,mlNumMoves,mlAbortFlag, mlFreeze, mlReady,mlForceRings},
(*  CALL AS:
{cvMoves, cvNumMoves, cvAbortFlag, cvFreeze, 
cvReady, cvForceRings} = handleAbort[cvMoves, cvMovesIx];
*)
(*  Truncate the move sequence to contain only those *)
(*  moves that have already been executed.  *)
mlMoves=Take[maMoves,Max[0,maMovesIx-1]];
mlNumMoves=Length@mlMoves;  (*  i.e., mlMovesIx - 1 (or zero)  *)
mlAbortFlag=False;
mlFreeze=False; 
mlReady=True;
mlForceRings=False;
{mlMoves,mlNumMoves,mlAbortFlag, mlFreeze, mlReady,mlForceRings}
];

Controls for Manipulate

framelLabel & openerLabel  -- Labels for Controls (Support functions)

frameLabel[name_,ttip_,fontsize_:14,color_:Black]:=
Tooltip[Style[name,fontsize,Bold,color],ttip];

openerLabel[name_,ttip_,fontsize_:14,color_:Blue]:=
Tooltip[Style[name,fontsize,Bold,color],ttip];

Buttons for Abort, Pause/Play

buttonsAbortPausePlay=Row[{
Button[
Tooltip[gcStopSeqIcon,"Abort the currently executing sequence."],
(cvAbortFlag=True)
,FrameMargins->2,Enabled->True]
,
Button[
Tooltip[gcPausePlayIcon,"Pause/resume the currently executing sequence."],
(cvFreeze=!cvFreeze)
,FrameMargins->2,Enabled->True]
},Spacer[1]];

ctlCubeLength  -- Cube-Length PopupMenu and Solved-Cube button

Control variables affected:  cvNx
CALLS:  solvedCube, clearMoveSeq

ctlCubeLength=Framed[ Grid[{
{
Button[
Tooltip[
"  solved cube  ",
"Reset the cube to a pristine state.\n(Does not erase move history.)"
],
(
cvCubeState=solvedCube[cvNx,cvSolvedOri];
cvMoves={}; cvMovesIx=1;  cvNumMoves=0;
),
Enabled->cvReady,ImageSize->Automatic],
SpanFromLeft
},{
frameLabel["cube length:", "Number of subcubes per side"]
,
Tooltip[Control@{cvNx,1,gcMaxCubeLength,1, ControlType->PopupMenu,Enabled->cvReady},
StringJoin[
"           **** WARNING ****\n",
"  Changing the cube length resets the cube to  \n",
"  a solved state, erases your move history and  \n",
"  deletes all your user-defined sequences."
]]
}
}],Background->Lighter[Brown,0.80],FrameMargins->{{10,10},{6,6}}];
(*  End ctlCubeLength  *)

ctlAnimation -- Animation Controls

Control variables affected:  cvNumStepsNext, cvSeqAnimOn, cvAbortFlag, cvFreeze,

ctlAnimation=Framed[
Column[{ 
frameLabel["animation","Configure animation of rotating layers."],
Row[{
Column[{
frameLabel["steps","Choose the number of animation steps per quarter turn.",12],
Control@{cvNumStepsNext,Range@8, 
 ControlType->PopupMenu,ImageMargins->{{4,6},{0,0}}}
},Center],
Column[{
frameLabel["seq anim","Enable/disable sequence animation.",12],
Control@{cvSeqAnimOn,{
True->Tooltip[Style["on",14],"Turn sequence animation ON."],
False->Tooltip[Style["off",14],"Turn sequence animation OFF."]
},ControlType->SetterBar}
},Center]
}Spacer[3]]
},Center]
,Background->LightOrange,FrameMargins->{{5,5},{5,5}}];(*  End ctlAnimation  *)

ctlSequencesAndPatterns --  Controls for canned sequences of moves

Control variables affected:  cvRevSeqs
CALLS:  doSeqNamed

dsn[seqname]  and dsnDemo[seqname] -- Helper functions for ctlSequencesAndPatterns

dsn[seqname_String]:=seqname:>
doSeqNamed[seqname, cvNx,cvSelectedFace,cvSelectedLayersFRU,cvRevSeqs];

dsnDemo[seqname_String]:=seqname:>(
cvSeqAnimOn=True;                           (*  For demos, force seq anim'n ON.  *)
doSeqNamed[seqname, cvNx,cvSelectedFace,cvSelectedLayersFRU,cvRevSeqs]
);

ctlSequencesAndPatterns=Framed[Column[
{
Row[{   (*  Row 1 of 2:  Title & Forward/Reverse SetterBar  *)

frameLabel["sequences & patterns",
StringJoin[
"Useful and interesting move sequences.  The resulting\n",
"patterns of most of these may be affected by choices of\n",
"Face and Layers in the Basic Moves panel."
]],
Control@{cvRevSeqs,{
False->Tooltip[Style["forward",12],"Execute sequences forward."],
True->Tooltip[Style["reverse",12],"Execute sequences in reverse."]
},ControlType->SetterBar,Enabled->cvReady}
,
 buttonsAbortPausePlay
},Spacer[10]]
,
Row[{ (*  Row 2 of 2:  Five Action Menus  *)
ActionMenu[Tooltip["corners","Sequences affecting only corner subcubes."], 
{
dsn["Flip2AdjacentCorners"],
dsn["Flip2FaceDiagCorners"],
dsn["Flip2CubeDiagCorners"],
Delimiter,
dsn["Swap3Corners"]
},Enabled->cvReady]
,
ActionMenu[Tooltip["edges","Sequences affecting only edge subcubes."],
{
dsn["Swap3Edges8Moves"],
dsn["Swap3EdgesSameFaceNoFlip"],
dsn["Swap3EdgesSameFaceFlip2"],
dsn["Swap3EdgesSameCorner"],
dsn["Swap3EdgesSameCornerAlt"],
Delimiter,
dsn["Flip2EdgesInPlace"],
dsn["Flip4EdgesInPlace"],
dsn["FlipAllEdgesInPlace"],
Delimiter,
dsn["Swap2Edges"],
dsn["Swap4Edges"]
},Enabled->cvReady]
,
ActionMenu[Tooltip["patterns","Interesting patterns."],
{
dsn["Dots"],
dsn["Checkers"],
dsn["Ripple"],
dsn["MultiRipple"],
Delimiter,
dsn["RotateMidFaces180Deg"],
dsn["RotateCore180Deg"],
(*dsn["BigCornerFlip"],*)
dsn["Flip2BigCorners"]
},Enabled->cvReady]
,
ActionMenu[Tooltip["random","Random sequences for scrambling the cube."],
 {
dsn["Scramble 10"],
dsn["Scramble 20"],
dsn["Scramble 100"],
Delimiter,
dsn["Scramble/Solve 10"],
dsn["Scramble/Solve 20"],
dsn["Scramble/Solve 100"]
},Enabled->cvReady]
,
ActionMenu[
Tooltip["demos",
StringJoin[
"  Demos illustrating some DeepCube features.  \n",
"  (Best results with cube length = 4, 5, or 6.)  "]
],
 {
dsnDemo["View Options"],
dsnDemo["Subcube Sizes"],
Delimiter,
dsnDemo["Face Colors"],
dsnDemo["Edges Colors/Styles"],
dsnDemo["Rings Colors/Styles"],
dsnDemo["Background Colors"],
Delimiter,
dsnDemo["Invisible Layers"],
dsnDemo["Show Misaligned"],
dsnDemo["Perspective"],
dsnDemo["Animation Steps"],
Delimiter,
dsnDemo["All Demos"]
},Enabled->cvReady]
},Spacer[5]]  (*  End of row 2  *)

},Center],
Background->RGBColor[1,1,0.95],FrameMargins->{{10,10},{6,6}}

]; (*  End ctlSequencesAndPatterns  *)

ctlViewOptions --  View Options: Front, Back, Both, Show Cores

Control variables affected:  cvViewOpt, cvShowCores

ctlViewOptions=Framed[
Column[{
Tooltip[Style["view options",14,Black,Bold],
"Choose front/back views and visibility of inner cores."]
,
Row[{
Control@{cvViewOpt,{1->"front view",2->"back view",3->"both"},
ControlType->RadioButtonBar,Appearance->"Vertical"},
Column[{
Style["show\ncores",LineSpacing->{.8,0}],
Control@{cvShowCores,{True,False}}
},Center]
},Spacer[20]]
},Center],
Background->Lighter[Orange,0.85],FrameMargins->{{10,10},{6,6}}
];(*  End ctlViewOptions  *)

ctlSliders -- Sliders for window width and subcube size

Control variables affected:  cvWindowWidth, cvSubcubeSize

ctlSliders=Row[{
Tooltip["    display width  ","Adjust the width of the cube display window."],
 Control@{cvWindowWidth,200,500,10,ControlType->Slider,labl, imsz1}
,
Tooltip["  subcube size  ",
StringJoin["Adjust the subecube size.\n",
"(Smaller sizes let you see past the exterior subcubes into the interior.)"]
],
Control@{cvSubcubeSize,gcMinSubcubeSize,gcMaxSubcubeSize,1,
ControlType->Slider,labl,ImageMargins->{{0,0},{0,0}},imsz1}
}]   /.{labl->Appearance->"Labeled",imsz1->ImageSize->150};
(*  End ctlSliders  *)

ctlBasicMoves  --  The Basic Moves Frame

Control variables affected:  cvSelectedFace, cvSelectedLayersFRU, cvShowRings
CALLS:  doSeqFaceLayerList, doSeqWholeCube.
This implements the FRUBLD convention.

doFS1[dir_]:=doSeqFaceLayerList[cvNx, cvSelectedFace, cvSelectedLayersFRU, dir];

ctlBasicMoves=Framed[
Column[{
frameLabel["basic moves",
"Choose a face and layer(s).\nThen press an action button.",16]
,
Framed[
Row[{         (*  FACE ROW  *)
frameLabel["face:  ","Choose a Face:\n    Front, Right, Up,\n    Back, Left, Down."],
Control@{cvSelectedFace,
{4->Style[" F ",12],5->Style[" R ",12],6->Style[" U ",12],
 7->Style[" B ",12],8->Style[" L" ,12],9->Style[" D ",12] }, 
ControlType->SetterBar}
}]
,Background->gcBkgdFSA,FrameMargins->{{9.5,9.5},{5,5}}]       (*  End Framed Face Row  *)
,
Grid[{{                     (*  This Grid acts as a Row with Alignment->Top.  *)

Framed[Grid[{   (*  LAYERS  GRID   *)
{
frameLabel["layers","Choose one or more Layers to be rotated."],
SpanFromLeft
},{
(*  We provide both ListPicker and TogglerBar for user convenience.  *)
Control@{cvSelectedLayersFRU,Range@cvNx,ControlType->ListPicker,Alignment->Top},
Control@{cvSelectedLayersFRU,Range@cvNx,ControlType->TogglerBar,Appearance->"Vertical"}
},{
Button[Tooltip["clear","Unselect all layers."],
(cvSelectedLayersFRU={}  (* ; cvShowRingsTemp=True  *)  )
],
SpanFromLeft
},{
Column[{
Tooltip[
Grid[{{"show"},{"rings"}},Spacings->{1,0.1}],
"Show \"Rings\" indicating selected layer(s)."
],
Control@{cvShowRings,{True,False}}
},Center],
SpanFromLeft
}
}] ,Background->gcBkgdFSA,FrameMargins->5]    (*  END LAYERS  GRID   *)
,
Column[{
Framed[
Grid[{   (*  ACTION GRID  *)
{
frameLabel["action","Perform the rotation for the selected Face and Layer(s)."],
SpanFromLeft
},{
Style["cw",Bold,12],Style["ccw",Bold,12]
},{
Button["quarter",doFS1[gcCW],rdy1],
Button["quarter",doFS1[gcCCW],rdy1]
},{
Button["half",        doFS1[gcCW2],rdy1],
Button["half",        doFS1[gcCCW2],rdy1]
}
}] /.rdy1->Enabled->cvReady    
,Background->gcBkgdFSA,FrameMargins->5]     (*  END  ACTION GRID  *)
,
Framed[
Grid[{  (*  GRID FOR WHOLE-CUBE ROTATION  *)
{
frameLabel["whole-cube",
"Rotate the whole cube one quarter\nturn about the indicated face."],
SpanFromLeft
},{
Button["F",   doSeqWholeCube[cvNx,4,gcCW],rdy1,imsz1],
Button["F'",doSeqWholeCube[cvNx,4,gcCCW],rdy1,imsz1]
},{
Button["R",   doSeqWholeCube[cvNx,5,gcCW],rdy1,imsz1],
Button["R'",doSeqWholeCube[cvNx,5,gcCCW],rdy1,imsz1]
},{
Button["U" ,   doSeqWholeCube[cvNx,6,gcCW],rdy1,imsz1],
Button["U'", doSeqWholeCube[cvNx,6,gcCCW],rdy1,imsz1]
}
},Spacings->{1.,0.3}
]    /.{rdy1->Enabled->cvReady,imsz1->ImageSize->35}                   (*  END WHOLE-CUBE GRID  *)
,Background->gcBkgdWC,FrameMargins->{{10.5,10.5},{5,5}}]
},Center,0.6]
}},Alignment->Top]
},Center,0.6]
,Background->gcBkgdBST,FrameMargins->8
];  
(*  End ctlBasicMoves  *)

ctlDeepView -- Tools to See Inside the Cube

Control variables affected:  cvXInvis, cvYInvis, cvZInvis, cvWireFrame, cvShowMisAl3

ctlDeepView=Framed[Column[{
Grid[{
{"F",Control@{cvXInvis,Range@cvNx,  tbar},"B"},
{"R",Control@{cvYInvis,Range@cvNx,  tbar},"L"},
{"U",Control@{cvZInvis,Range@cvNx,  tbar},"D"}
},Spacings->{0.7,0.1}]/.tbar->ControlType->TogglerBar
,
Row[{
Grid[{{"wire"},{"frames"}},Spacings->{1,0.2}],"  ",
Control@{cvWireFrame,{True,False}},"     ",
Button["clear all",(cvXInvis = cvYInvis = cvZInvis={} ), ImageSize->60]
}],
"____________________",
Tooltip["show selected ", "Display all subcubes or only aligned or misaligned."],
Control@{cvShowMisAl3,{1->"all",2->"aligned",3->"misaligned"},
ControlType->RadioButtonBar,Appearance->"Vertical"}
},Center],
Background->Lighter[Magenta,0.95]]; (*  End ctlDeepView  *)

ctlUserDefinedSeqs -- User-Defined Sequences

Control variables affected: cvRecOn, cvIxUds.
USES:  cvRecOn, cvNx, cvIxuds, cvNumSteps, cvReady, cvUdsSaveOK, cvUdsOverwriteOK, cvUdsDeleteOK.

ctlUserDefinedSeqs=Framed[Column[{  (*  Start of Column for User-Defined Sequences (UDS's) *)

Row[      (*  SetterBar and Buttons for recording buffer  *)
{
Control[{cvRecOn,{                                            (*  SetterBar to turn recording ON or OFF  *)
True->Tooltip[Style["on",12,Bold,Darker[Green,0.4]],"Start recording moves."],
False->Tooltip[Style["off",12,Bold,Red],"Stop recording moves."]
},ControlType->SetterBar,Enabled->True}]
,
Button[Tooltip["clear","Delete all recorded moves and turn off recording."],
 (cvRecOn=False;recClear[]),Enabled->True]
,
Button[Tooltip["bkspc","Delete the last recorded move (if any)."], (recBackspace[])]
},Spacer[10]
]
,
Grid[{     (*  Buttons and a PopupMenu for UDS's, 4 Rows by 3 columns  *)
{          
Button[Tooltip["save","Save the contents of the recording buffer as a UDS."],
recUdsSave[cvNx,cvIxUds,gcMoveTextFormat,"Save"],Enabled->cvUdsSaveOK]
,
Button[Tooltip["ov'write",
"Replace the selected UDS with the contents of the recording buffer."],
 recUdsSave[cvNx,cvIxUds,gcMoveTextFormat,"Overwrite"],
Enabled->cvUdsOverwriteOK]
,
Button[Tooltip["append",
"Append the contents of the recording buffer to the selected UDS."],
 recUdsSave[cvNx,cvIxUds,gcMoveTextFormat,"Append"],
Enabled->cvUdsOverwriteOK]
},{
Tooltip[
Control@{cvIxUds,
Table[i->Style[StringJoin[ToString[i],gsUserSeqName[[i]]],12,FontFamily->"Courier"],
{i,gcNumUserSeqs}],
 ControlType->PopupMenu,
FrameMargins->0,ImageMargins->{{4,4},{2,2}},ImageSize->170},
"Menu of user-defined sequences.\n(Select one on which to apply an action button.)"],
SpanFromLeft,
SpanFromLeft
},{
Button[Tooltip[Style["delete",White,Bold,12],"Delete the selected UDS."],
recUdsDelete[cvIxUds],
Background->Blend[{Red,Orange}],Enabled->cvUdsDeleteOK]
,
Button[Tooltip[Style["undel",White,Bold,12],"Restore the last-deleted UDS."],
 recUdsRestore[cvIxUds],
Background->Lighter[Blue,0.4],Enabled->True]
,
Null
},{
Style["execute:",Bold,12]
,
Button[Tooltip["fwd","Execute the selected UDS."],
doSeqUser[cvIxUds, cvNx, False],Enabled->cvUdsExecuteOK]
,
Button[Tooltip["rev","Execute the selected UDS in reverse."],
doSeqUser[cvIxUds,cvNx,True],Enabled->cvUdsExecuteOK]
}
},ItemSize->5.5,Spacings->{0.2,0.4}]
,
 buttonsAbortPausePlay
},Left]  (*  End of Column for User-Defined Seqs *)
,Background->Lighter[Magenta,0.85],FrameMargins->4];(*  End of Framed for User-Defined Seqs *)
(*  End ctlUserDefinedSeqs  *)

ctlSubcubeFaceColors -- Color setters for subcube faces  (*  Changed  *)

Control variables affected:  cvFaceC1,  cvFaceC2,  cvFaceC3,  cvFaceC4,  cvFaceC5,  cvFaceC6
CALLS: changeFaceColors

ctlSubcubeFaceColors=Module[
{ct1,im1,im2,fm1,bg1,bg2,fclrs},
Column[{
Framed[Grid[{
{
frameLabel["color schemes","Choose random or specific color schemes."],
SpanFromLeft,
SpanFromLeft
},{
Button[Tooltip["def","Reset to default colors."],{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=changeFaceColors[-1],im2],
Button[Tooltip["rnd","Color subcube faces randomly."],{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=changeFaceColors[-2],im2],
ActionMenu[Tooltip["alt","Choose a color scheme from a list."], gcAltColorMenu,im2]
}
},Spacings->{0,0.2}],bg2,FrameMargins->4]
,
Framed[Grid[{          (*  Grid of six ColorSetter's for choosing subcube face colors *)
{
frameLabel["face colors",
StringJoin[
"Choose colors for subcube faces.\n",
"(Click on a color to change it.)\n",
"Note:  F, R,U, etc. refer to faces\n",
"in the \"solved cube\" orientation."
]]
,SpanFromLeft,SpanFromLeft
},{
"F","R","U"
},{
(*
NOTE:  Tried using an array of face colors here.
Apparently, the ColorSetter Control wants a simple variable, and
won't work with an array element.
*)
Framed[Control@{cvFaceC1,Red,ct1,im1},fm1],
Framed[Control@{cvFaceC2,Red,ct1,im1},fm1],
Framed[Control@{cvFaceC3,Red,ct1,im1},fm1]
},{
Framed[Control@{cvFaceC4,Red,ct1,im1},fm1],
Framed[Control@{cvFaceC5,Red,ct1,im1},fm1],
Framed[Control@{cvFaceC6,Red,ct1,im1},fm1]
},{
"B","L","D"
}
}
,Alignment->{Center,Center},bg1,Spacings->{1.0,0.3}]
,bg1,FrameMargins->{{5,5},{4,4}}]
,
Framed[Grid[{
{
frameLabel["permute colors","Rearrange current colors."],
SpanFromLeft
},{
Button[Tooltip["rnd","Randomly permute current colors."],
{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[-5,fclrs],im2],
Button[Tooltip["cw","Cycle clockwise."],
{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[-6,fclrs],im2],
Button[Tooltip["ccw","Cycle counterclockwise."],
{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[-7,fclrs],im2],
Button[Tooltip["↕","Swap top & bottom rows."],
{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[-8,fclrs],im2],
Button[Tooltip["→","Swap columns cyclically."],
{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6}=
changeFaceColors[-9,fclrs],im2]
}
},Alignment->{Center,Center},Spacings->{0.0,0.3}]
,bg2,FrameMargins->{{2,2},{2,2}}](*  End of Framed[Grid[{....  *)
},Center]   (*  End of Column  *)
/.
{ct1->ControlType->ColorSetter,im1->ImageSize->{28,28},
fclrs->{cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6},
fm1->FrameMargins->0,im2->ImageMargins->1,
bg1->Background->GrayLevel[0.9],bg2->Background->Lighter[Blue,0.85]}
]; (*  End ctlSubcubeFaceColors  *)

ctlOtherColors -- Edge, Ring & Background Colors; Edge & Ring Thicknesses

Control variables affected:  cvEdgeColor, cvRingColor, cvBkgdColor,  cvEdgeThi, cvRingTh

ctlOtherColors= Module[{ct1,im1,fm1,bg1,bg2,ct2},
Column[{
Framed[Grid[{        (*  Grid of ColorSetter's for Choosing "Other" Colors *)
{
Tooltip[Style["edges",11],"Choose a color for the subcube edges."],
Tooltip[Style["rings",11],"Choose a color for the layer-indicator rings."],
Tooltip[Style["bkgd",11],  "Choose a background color for the cube display."]
},{
Framed[Control@{cvEdgeColor,Red,ct1,im1},fm1],
Framed[Control@{cvRingColor,Red,ct1,im1},fm1],
Framed[Control@{cvBkgdColor,Red,ct1,im1},fm1]
}
},Alignment->{Center,Center},bg1,Spacings->{0.6,0.4}]
,bg1,FrameMargins->{{10,10},{4,4}}]  (*  End of Framed[Grid[{....  *)
,
Framed[Grid[{       
{
"thicknesses",SpanFromLeft,
},{
Control@{cvEdgeTh,0,10,1, ct2},
Control@{cvRingTh,0,7,1, ct2}
},{
Tooltip[Style["edges",11],"Choose a thickness for subcube edges."],
Tooltip[Style["rings",11],"Choose a thickness for layer-indicator rings"],
}
},Alignment->{Center,Center},Spacings->{0.5,0.3}]
,bg2,FrameMargins->{{10,0},{4,4}}]  (*  End of Framed[Grid[{....  *)
,
Button[Tooltip["defaults","Reset thicknesses & colors to default values."],
(
{cvEdgeTh,cvRingTh, cvEdgeColor,cvRingColor,cvBkgdColor }=
{gcDfltEdgeTh,gcDfltRingTh, gcDfltEdgeColor,gcDfltRingColor,gcDfltBkgdColor};
),ImageSize->{80,Automatic}]
},Center]
/.
{ct1->ControlType->ColorSetter,ct2->ControlType->PopupMenu,
im1->ImageSize->{25,25},fm1->FrameMargins->0,
bg1->Background->GrayLevel[0.9],bg2->Background->Lighter[Blue,0.85]}
];
(*  End ctlOtherColors  *)

ctlPerspective -- Controls for View Angle and View Range

Control variables affected:  cvViewPtAngle, cvViewPtMult

ctlPerspective=Grid[{
{
Tooltip["view angle","Change viewing angle."],
Tooltip["dist","Change viewing distance."]
},{
Control[{cvViewPtAngle,gcViewPtAngle1,gcViewPtAngle2,gcViewPtAngleDelta}],
Control@{cvViewPtMult,gcViewPtMultValues, ControlType->VerticalSlider,ImageSize->Tiny}
},{
Button[Tooltip["reset","Reset perspective values to defaults."],
(cvViewPtAngle=gcViewPtAngleInit; cvViewPtMult=gcViewPtMultInit)],
SpanFromLeft
}
}]; (*  End ctlPerspective  *)

ctlLogo

ctlLogo=Column[{         
Style["DeepCube",18,Bold,gcLogoColor],
Style["© 2015, Louis J. Palumbo             ",12,
Italic,FontFamily->"Times New Roman",gcLogoColor],
},Left];
(*
Style["   DeepCube    ",24,Bold,gcLogoColor],
*)

ctlHistoryControls -- Controls for Navigation through Move History

Control variables affected:   cvAbortFlag, cvFreeze, cvNumHist
CALLS:  histMovePtr

histPlaybackTtip={    (*  Tooltip text  *)
"Go back to the beginning of saved history.",
"Go back N moves.",
"Go back one move.",
"Go forward one move.",
"Go forward N moves.",
"Go forward to the end of saved history."
};

ctlHistoryControls=Framed[  
Row[{ 
frameLabel["history controls:","Execute moves in the saved history."],
Spacer[8],
ButtonBar[
{
Tooltip[histPlaybackIcon[1], histPlaybackTtip[[1]]]:>histMovePtr[-Infinity],
Tooltip[histPlaybackIcon[2], histPlaybackTtip[[2]]]:>histMovePtr[-cvNumHist],
Tooltip[histPlaybackIcon[3], histPlaybackTtip[[3]]]:>histMovePtr[-1],
Tooltip[histPlaybackIcon[4], histPlaybackTtip[[4]]]:>histMovePtr[1],
Tooltip[histPlaybackIcon[5], histPlaybackTtip[[5]]]:>histMovePtr[cvNumHist],
Tooltip[histPlaybackIcon[6], histPlaybackTtip[[6]]]:>histMovePtr[Infinity]
}
,ImageMargins->1.5,Enabled->cvReady]
,Spacer[8]
,
Style["N = ",14],
Tooltip[Control@{cvNumHist,{2,3,4,5,7,10,15,20,30,50,70,100,200}, 
ControlType->PopupMenu,ImageMargins->0,Enabled->cvReady},
Row[{
"Choose the number of moves executed by the ",
histPlaybackIcon[2]," and ",histPlaybackIcon[5]," buttons."
}]
],
Spacer[20], buttonsAbortPausePlay,Spacer[16],
Button[
Tooltip[Style["clear history",White,Bold,14],
"Erase all moves in the history buffer."
],
(If[gsHistBufferLength>0,cvClearHist=True]),
ImageSize->Automatic,Background->Blend[{Red,Orange}],
Enabled->cvReady
]
}]  (*  End of Row  *)
,Background->gcBkgdHistControls,FrameMargins->{{10,10},{4,4}}
]; (*  End ctlHistoryControls  *)

DeepCube -- The Manipulate Function

Manipulate[
(* ----------------------------------------------------------------,
 NOTE: The following multiline "If" expression, which pops up a 
ChoiceDialog asking the user if he/she really wants to proceed
with the cube-length change, doesn't work with the Free CDF 
Player; so, I have commented it out.  Instead, a similar warning
is attached to the cube length button as a Tooltip.
*)
(*
If[!gnFirstTime && cvNx≠cvOldNx,
If[ChoiceDialog[     (*  CDF Player does not like this.  *)
StringJoin[
"**** WARNING ****\n",
"Changing the cube length resets the cube to a\n",
"solved state, erases the move history and\n",
"deletes your User-Defined Sequences."
]],
cvOldNx=-2,        (*  Accept new cube length  *)
cvNx=cvOldNx       (*  Keep the current cube length *)
]
];
--------------------------------------------------------------- *)

If[cvNx≠cvOldNx,  (*  The cube length has changed -- reinitialize everything.  *)
{cvTurnix,cvSelectedFace,cvSelectedLayersFRU} = {0,4,{}};
{cvCurrAxisXYZ,cvCurrLayerListXYZ, cvRotAngle,
cvShowRingsTemp,cvForceRings,cvReady} =
finishSequence[cvNx,cvSelectedFace,cvSelectedLayersFRU]; 
cvMoves={}; cvMovesIx=1;  cvNumMoves=0;
cvXInvis = cvYInvis = cvZInvis={};
cvCubeState=solvedCube[cvNx,cvSolvedOri];
histInit[];
cvRecOn=False;recClear[];udsClearAll[];
cvIxUds=1;
cvOldNx=cvNx;
cvRotating=False;
];

While[cvFreeze && !cvAbortFlag, Pause[0.25]];  (*  Implements Play/Pause Buttons  *)

(* **************************************************** *)
(*   Determine which branch of the big Which statement  *)
(*   we need to execute at this point.                  *)
(* **************************************************** *)
If[cvRotating,
cvAnimOpt = 3    (*  Currently rotating; continue until it's done  *)
,
(* -------- ELSE:  Not rotating; update cvNumSteps, cvAnimOpt and cvTurnix  *)
cvNumSteps=cvNumStepsNext;
cvAnimOpt=Which[
!cvSeqAnimOn || cvNumSteps<1, 1,  (*  No sequence animation  *)
cvNumSteps==1,2,   (*  Single step: quarter turn or half turn  *)
cvNumSteps≥2,3       (*  Show layer rotation with possibly different # of steps. *)
];
cvTurnix=2 cvNumSteps-1;(*  Indicates start of layer rotation.  *)
(* -------- END of ELSE:  Not rotating ..... *)
];

{cvUdsDeleteOK, cvUdsExecuteOK,cvUdsOverwriteOK,cvUdsSaveOK}=
updateUdsOK[gsUserSeq[[cvIxUds]],gsRecBuffLen,cvReady];
{cvCurrAxisXYZ,cvCurrLayerListXYZ}=
getAxisLayersXYZ[cvNx,cvSelectedFace,cvSelectedLayersFRU];
cvMoveIsNormal=True;

(* *************************************************************************** *)
(*  Start of the Big Which Statement:  Branch according to Animation choice.   *)
(* --------------------------------------------------------------------------- *)
Which[ 
(* ****************  Which ... cvAnimOpt == 1  **************** *)
(* --------------------------------------------------------- *)
(*  Sequence animation is turned off.  Jump ahead to a cube  *)
(*  state that results from the execution of an entire       *)
(*  sequence with no animation.  Special action moves are    *)
(*  skipped over.                                            *)
(* --------------------------------------------------------- *)
cvAnimOpt==1,                (* <<<< No animation >>>> *)
(
(*  This choice ignores the cvAbortFlag and executes all moves without animation.  *)
If[cvMovesIx ≤ cvNumMoves,
cvReady=False;        (*  Has to be False here or it will create problems.  *)
logMoveList[cvNx, cvMoves [[cvMovesIx;;]]];
cvCubeState=rotateLayersSeq[cvNx,cvMoves [[cvMovesIx;;]],cvCubeState];
];
(*  We're done with the sequence: reset everything. *)
cvAbortFlag = False;  (*  Probably needed here in case the Stop (Abort) button 
is pushed during the execution of the sequence.  *)

{cvCurrAxisXYZ,cvCurrLayerListXYZ, cvRotAngle,
cvShowRingsTemp,cvForceRings,cvReady} =
finishSequence[cvNx,cvSelectedFace,cvSelectedLayersFRU]; 
cvMoves={}; cvMovesIx=1;  cvNumMoves=0;
),
(*  ------ End of Which cvAnimOpt == 1 ------  *)

(* ****************  Which ... cvAnimOpt == 2  **************** *)
(* ---------------------------------------------------------- *)
(*   cvNumSteps equals 1: Execute a single move (quarter      *)
(*   or half turn) with no animation, Or, execute a           *)
(*   special-action move.                                     *)
(* ---------------------------------------------------------- *)
cvAnimOpt==2,                         (* <<<< Single-Step Animation >>>> *)
(
If[cvAbortFlag,      (*  ------ Abort the sequence ------  *)
{cvMoves, cvNumMoves, cvAbortFlag, cvFreeze, 
cvReady, cvForceRings} = handleAbort[cvMoves, cvMovesIx];
];
(*
If the move sequence was aborted, then at this point, cvMovesIx = cvNumMoves + 1, 
indicating there are no more moves in cvMoves .  So, the following "If"
expression does not get executed. 
*)
If[cvMovesIx≤cvNumMoves,
(* -------- THEN 1:  No ELSE 1 Clause  *)
cvReady=False;
cvCurrMove=cvMoves [[cvMovesIx]];
If[normalMoveQ[cvCurrMove],
(* -------- THEN 2:  Handle the normal move  *)
logOneMove[cvNx,cvCurrMove];
cvCubeState=rotateLayers[cvNx,cvCurrMove, cvCubeState];
, 
(* -------- ELSE 2:  Handle special actions  *)
(*  logOneMove[cvNx,cvCurrMove];   Don't log special actions.  *)
specialAction[cvNx,cvCurrMove];
];
cvMovesIx++;
];

If[cvMovesIx>cvNumMoves,(*  No more moves left in this sequence.  *)
{cvCurrAxisXYZ,cvCurrLayerListXYZ, cvRotAngle,
cvShowRingsTemp,cvForceRings,cvReady} =
finishSequence[cvNx,cvSelectedFace,cvSelectedLayersFRU]; 
cvMoves={}; cvMovesIx=1;  cvNumMoves=0;
];
{cvCurrAxisXYZ,cvCurrLayerListXYZ, cvRotAngle} =
finishMove[cvNx,cvSelectedFace,cvSelectedLayersFRU]; 
),
(*  ------ End Which cvAnimOpt == 2 ------  *)

(* ****************  Which ... cvAnimOpt == 3  **************** *)
(* ------------------------------------------------------------------ *)
(*  cvNumSteps > 1:  Execute a single move (quarter or half turn)     *)
(*  with animation at cvNumSteps per quarter turn. Or, execute a      *)
(*  special-action move                                               *)
(* ------------------------------------------------------------------ *)
cvAnimOpt==3,                  (* <<<< Multi-Step Animation >>>> *)
(
(* ------------------------------------------------------------ *)
(*  When cvTurnix == 2 cvNumSteps - 1, we are at the beginning 
  of the turn animation.  This is where we can abort the 
sequence or handle special moves.  I refer to this 
part as "Which 3a."  *)
(* ------------------------------------------------------------ *)
If[cvTurnix==2cvNumSteps-1  (*  IF 1  *)
,  (* -------- THEN 1 *)  (*  NO ELSE 1 CLAUSE  *)
If[cvAbortFlag,      (*  ------ Abort the sequence ------  *)
{cvMoves, cvNumMoves, cvAbortFlag, cvFreeze, 
cvReady, cvForceRings} = handleAbort[cvMoves, cvMovesIx];
];

cvMoveIsNormal=True;
If[cvMovesIx≤cvNumMoves,   (*  There's a move to be processed  *)
cvCurrMove=cvMoves [[cvMovesIx]];
cvMoveIsNormal=normalMoveQ[cvCurrMove];
If[!cvMoveIsNormal
,(* THEN: Perform special action  *)
specialAction[cvNx,cvCurrMove];
cvMovesIx++;
,(* ELSE: It's a normal move; Initialize stuff and log it  *)
cvReady=False;
cvRotating=True;
logOneMove[cvNx, cvCurrMove];
];
];

If[cvMovesIx>cvNumMoves,
(* -------- THEN: We're done with this sequence.  *)  
(*
  We can get here if the last move in the sequence was a special 
action move, *OR* if cvMovesIx ≤ cvNumMoves, above, was already False.  
*)
{cvCurrAxisXYZ,cvCurrLayerListXYZ, cvRotAngle,
cvShowRingsTemp,cvForceRings,cvReady} =
finishSequence[cvNx,cvSelectedFace,cvSelectedLayersFRU]; 
cvMoves={}; cvMovesIx=1;  cvNumMoves=0;
];
];   (*  If[cvTurnix == 2 cvNumSteps - 1  *)

(* ------------------------------------------------------ *)
(*    <<<< This part executes an animation step. >>>>     *)
(* ------------------------------------------------------ *)

If[cvMoveIsNormal&&(cvMovesIx ≤cvNumMoves ),  (*  Special move has already been handled  *)
(* -------- THEN 1 *)  (*  NO ELSE CLAUSE  *)
{cvCurrAxisXYZ,cvCurrLayerListXYZ,
cvAngleix}=mfGetXYZParts[cvNx,cvMoves [[cvMovesIx]]];

If[cvTurnix>{0,1,0,1,0}[[cvAngleix]]  (*  Are we still rotating the layer(s)?  *)
,
(* -------- THEN 2:  Stepping through the turn animation *)
cvRotAngle=calcRotAngle[cvAngleix,cvNumSteps,cvTurnix] ; 
cvTurnix =cvTurnix-{0,2,1,2,1}[[cvAngleix]]; 
,
(* -------- ELSE 2:  We're done rotating the layer(s)  *)
(* Update the cube state and get ready for the next move in cvMoves   *)
cvCubeState=rotateLayers[cvNx,cvMoves [[cvMovesIx]],cvCubeState];
cvRotating=False;
cvMovesIx++;
{cvCurrAxisXYZ,cvCurrLayerListXYZ, cvRotAngle} =
finishMove[cvNx,cvSelectedFace,cvSelectedLayersFRU]; 
]
];   (*  If[cvMoveIsNormal && (cvMovesIx ≤ cvNumMoves)...  *)

)   (*  ------ End Which cvAnimOpt == 3 ------  *)

];  (*  **********  End of Big Which Expression *********  *)

If[cvDbgPause,Pause[1/2]];  (*  Pause for half a second  *)

If[cvClearHist,histInit[];cvClearHist=False];   (*  Clears the history list before displaying it.  *)

(* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% *)
(*        Here's where we construct and display the graphics.        *)
(* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% *)

(*  ------ Debugging Output:  Simple Text, No Graphics ----- *)
(*  
This tests everything but the final graphics rendering.,
Replace the call to displayGraphics with the following call
to displayInfoGrid.
  displayInfoGrid[cvNx, cvCubeState,cvRotAngle]
*)
(* ------ displayGraphics generates the Manipulate output cell. ------ *)
displayGraphics[cvNx,cvOldNx,cvCubeState,cvSolvedOri,
cvNumSteps,cvNumStepsNext,cvSeqAnimOn,cvRotAngle,
cvRevSeqs,cvAbortFlag,cvFreeze,
cvViewOpt,cvShowCores,
cvWindowWidth,cvSubcubeSize,
cvCurrAxisXYZ,cvCurrLayerListXYZ,
cvShowRings,cvShowRingsTemp, cvForceRings ,
cvXInvis,cvYInvis,cvZInvis,cvWireFrame,cvShowMisAl3,
cvRecOn,cvIxUds,cvUdsDeleteOK,cvUdsExecuteOK,cvUdsOverwriteOK,cvUdsSaveOK,
cvFaceC1,cvFaceC2,cvFaceC3,cvFaceC4,cvFaceC5,cvFaceC6,
cvEdgeTh,cvRingTh, cvEdgeColor,cvRingColor,cvBkgdColor,
cvViewPtAngle, cvViewPtMult,
cvNumHist,cvClearHist,
cvShowFrames]     
,  
(* %%%%%%%%%%%% End of First Manipulate Argument  %%%%%%%%%%%% *)

(* ========================================================================== *)

(* Control variables with ControlType->None 
  and their initial values *)

{{cvAngleix,1,""},ctNone},  
{{cvAnimOpt,1,""},ctNone},  
{{cvAbortFlag,False,""},ctNone},     
{{cvBkgdColor,gcDfltBkgdColor,""},ctNone},
{{cvClearHist,False,""},ctNone},
{{cvCubeState,solvedCube[gcInitCubeLength,1],""},ctNone},
{{cvCurrAxisXYZ,1,""},ctNone},  
{{cvCurrLayerListXYZ,{},""},ctNone},
{{cvCurrMove,{},""},ctNone},  
{{cvDbgPause,False,""},ctNone},
{{cvEdgeColor,gcDfltEdgeColor,""},ctNone},
{{cvEdgeTh,gcDfltEdgeTh,""},ctNone},
{{cvFaceC1,gcDefaultFaceColors[[1]],""},ctNone},
{{cvFaceC2,gcDefaultFaceColors[[2]],""},ctNone},
{{cvFaceC3,gcDefaultFaceColors[[3]],""},ctNone},
{{cvFaceC4,gcDefaultFaceColors[[4]],""},ctNone},
{{cvFaceC5,gcDefaultFaceColors[[5]],""},ctNone},
{{cvFaceC6,gcDefaultFaceColors[[6]],""},ctNone},
{{cvFreeze,False,""},ctNone},
{{cvForceRings,False,""},ctNone},
{{cvIxUds,1,""},ctNone},
{{cvMoveIsNormal,True,""},ctNone},
{{cvMoves ,{},""},ctNone},
{{cvMovesIx,1,""},ctNone},
{{cvNumHist,5,""},ctNone},
{{cvNumMoves,0,""},ctNone},
{{cvNumSteps,3,""},ctNone},
{{cvNumStepsNext,3,""},ctNone},
{{cvNx,gcInitCubeLength,""},ctNone},
{{cvOldNx,-1,""},ctNone},
{{cvReady,True,""},ctNone},
{{cvRecOn,False,""},ctNone},
{{cvRevSeqs,False,""},ctNone},
{{cvRingColor,gcDfltRingColor,""},ctNone},
{{cvRingTh,gcDfltRingTh,""},ctNone},
{{cvRotAngle,0.0,""},ctNone},
{{cvRotating,False,""},ctNone},
{{cvSelectedFace,4,""},ctNone},
{{cvSelectedLayersFRU,{},""},ctNone},
{{cvSeqAnimOn,True,""},ctNone},
{{cvSeqDesc,"None",""},ctNone},
{{cvShowCores,False,""},ctNone},
{{cvShowFrames,False,""},ctNone},
{{cvShowMisAl3,1,""},ctNone},
{{cvShowRings,True,""},ctNone},
{{cvShowRingsTemp,True,""},ctNone},
{{cvSolvedOri,1,""},ctNone},
{{cvSubcubeSize,gcInitSubcubeSize,""},ctNone},
{{cvTurnix,0,""},ctNone},
{{cvUdsDeleteOK,False,""},ctNone},
{{cvUdsExecuteOK,False,""},ctNone},
{{cvUdsOverwriteOK,False,""},ctNone},
{{cvUdsSaveOK,False,""},ctNone},
{{cvViewPtAngle,gcViewPtAngleInit,""},ctNone},
{{cvViewOpt,1,""},ctNone},
{{cvViewPtMult,gcViewPtMultInit,""},ctNone},
{{cvWindowWidth,320,""},ctNone},
{{cvWireFrame,False,""},ctNone},
{{cvXInvis,{},""},ctNone},
{{cvYInvis,{},""},ctNone},
{{cvZInvis,{},""},ctNone},

(* %%%%%%%%%%%%%%%%   CONTROLS LAYOUT   %%%%%%%%%%%%%%%%%% *)

(*  Controls along the TOP of the Display  *)
Item[
Row[{
ctlCubeLength,
ctlAnimation,
ctlSequencesAndPatterns,
ctlViewOptions
},Spacer[8],Alignment->{Center,Center}]
,ControlPlacement->Top],

(* Sliders for Display Width & Subcube Size *)
Item[Row[{ctlSliders}],ControlPlacement->Top], 

(*  Controls along the LEFT SIDE of the Display  *)

Item[
OpenerView[{openerLabel["subcube colors           ",
"Modify subcube face colors."],ctlSubcubeFaceColors},
Background->gcBkgdOpenerView,ImageMargins->0],ControlPlacement->Left],

Item[
OpenerView[{openerLabel["other colors",
"Modify colors/styles for rings, background, etc."], ctlOtherColors},
Background->gcBkgdOpenerView,ImageMargins->0],ControlPlacement->Left],

Item[
OpenerView[{openerLabel["perspective",
"Adjust viewpoint angles and perspective."], ctlPerspective},
Background->gcBkgdOpenerView,ImageMargins->0],ControlPlacement->Left],

(*  Controls along the RIGHT SIDE of the Display  *)

Item[ctlBasicMoves,ControlPlacement->Right],     (* Basic Moves *)  

Item[OpenerView[{openerLabel["deep view controls",
"Render selected subcube groups invisible."],ctlDeepView}
,Background->gcBkgdOpenerView],ControlPlacement->Right],

Item[OpenerView[{openerLabel["user-defined sequences",
"Manage your own move sequences."],ctlUserDefinedSeqs}
,Background->gcBkgdOpenerView],ControlPlacement->Right],

(*  Controls along the BOTTOM of the Display  *)

Item[Row[{ 
ctlLogo,                           (* Logo *)  
ctlHistoryControls     (* History Controls  *)  
}],ControlPlacement->Bottom],

(* ------ Manipulate Initialization ------ *)
Initialization:>(
$HistoryLength=0;
debugStartTime=AbsoluteTime[];
debugElapsedTime=0.0;
initGlobalConstants[];
initGlobalVariables[];
histInit[];
recClear[];            (*  Initialize the recording buffer  *)
udsClearAll[];      (*  Initialize user-defined sequences  *)
)
,
(* ------ Other Manipulate Options ------ *)

(*  Seems like we need TrackedSymbols :-> Full here.  Other TrackedSymbols options cause problems.  *)
(*  Possible TrackedSymbols values: All, Full, True, Automatic, {sym1, sym2, ...}.  *)
(*  However, it appears that Full is the only choice that works.  *)
TrackedSymbols:>Full,   (*  Trying Full instead of All.  Seems to work OK with debugCumBytesBigDisplay *)

PreserveImageOptions->False, (*  Keeps 3D viewpoint under program control  *)
SaveDefinitions->True,         (*  Need for CDF file and for Wolfram Demonstrations Project  *)
BaseStyle->{Black,Background->gcBkgdManip,Framed->True},
Paneled->False,ImageMargins->0,FrameMargins->0,Frame->True

]     (*  ------------ End of Manipulate ------------  *)