If you were developing software to solve a Rubik\'s Cube, how would you represent the cube?
The short answer is that it depends on how you're going to solve the cube. If your solver is going to use a human method like the layer-by-layer approach or the Fridrich method then the underlying data structure won't make much of a difference. A computer can solve a cube using a human method in negligible time (well under a second) even in the slowest of programming languages. But if you are going to solve the cube using a more computationally intensive method such as Thistlethwaite's 52-move algorithm, Reid's 29-move algorithm, or Korf's 20-move algorithm, then the data structure and programming language are of utmost importance.
I implemented a Rubik's Cube program that renders the cube using OpenGL, and it has two different types of solvers built in (Thistlethwaite and Korf). The solver has to generate billions of moves and compare each cube state billions of times, so the underlying structure has to be fast. I tried the following structures:
Expanding on method (3) above, each face of the cube is made up of 9 stickers, but the center is stationary so only 8 need to be stored. And there are 6 colors, so each color fits in a byte. Given these color definitions:
enum class COLOR : uchar {WHITE, GREEN, RED, BLUE, ORANGE, YELLOW};
A face might look like this, stored in a single 64-bit integer:
00000000 00000001 00000010 00000011 00000100 00000101 00000000 00000001
Which is decoded as:
WGR
G B
WYO
An advantage of using this structure is that the rolq
and rorq
bit-wise operators can be used to move a face. Rolling by 16 bits effects a 90-degree rotation; rolling by 32 bits gives a 180-degree turn. The adjacent pieces need to be up-kept manually--i.e. after rotating the top face, the top layer of the front, left, back, and right faces need to be moved, too. Turning faces in this manner is really fast. For example, rolling
00000000 00000001 00000010 00000011 00000100 00000101 00000000 00000001
by 16 bits yields
00000000 00000001 00000000 00000001 00000010 00000011 00000100 00000101
Decoded, that looks like this:
WGW
Y G
OBR
Another advantage is that comparing cube states can in some instances be done using some clever bit masks and standard integer comparisons. That can be a pretty big speed-up for a solver.
Anyway, my implementation is on github: https://github.com/benbotto/rubiks-cube-cracker/tree/2.2.0 See Model/RubiksCubeModel.{h,cpp}
.
Expanding on method (4) above, some of the algorithms for programmatically solving the Rubik's Cube use an iterative deepening depth-first search with A*, using pattern databases as a heuristic. For example, Korf's algorithm utilizes three pattern databases: one stores the index and orientation of the 8 corner cubies; one stores the index and orientation of 6 of the 12 edge pieces; the last stores the index and orientation of the other 6 edges. When using pattern databases, a fast approach is to store the cube as a set of indexes and orientations.
Arbitrarily defining a convention, the edge cubies could be indexed as follows.
0 1 2 3 4 5 6 7 8 9 10 11 // Index.
UB UR UF UL FR FL BL BR DF DL DB DR // Position (up-back, ..., down-right).
RY RG RW RB WG WB YB YG OW OB OY OG // Colors (red-yellow, ..., orange-green).
So the red-yellow edge cubie is at index 0, and the white-green edge cubie is at index 4. Likewise, the corner cubies might be indexed like so:
0 1 2 3 4 5 6 7
ULB URB URF ULF DLF DLB DRB DRF
RBY RGY RGW RBW OBW OBY OGY OGW
So the red-blue-yellow corner cubie is at index 0, and the orange-green-yellow corner cubie is at index 6.
The orientation of each cubie needs to be kept as well. An edge piece can be in one of two orientations (oriented or flipped), while a corner piece can be in three different orientations (oriented, rotated once, or rotated twice). More details about the orientation of pieces can be found here: http://cube.crider.co.uk/zz.php?p=eoline#eo_detection With this model, rotating a face means updating indexes and orientations. This representation is the most difficult because it's hard for a human (for me at least) to look at a big blob of index and orientation numbers and verify their correctness. That being said, this model is significantly faster than dynamically calculating indexes and orientations using one of the other models described above, and so it's the best choice when using pattern databases. You can seen an implementation of this model here: https://github.com/benbotto/rubiks-cube-cracker/tree/2.2.0/Model (see RubiksCubeIndexModel.{h,cpp}
).
As mentioned, the program also renders the cube. I used a different structure for that part. I defined a "cubie" class, which is a six squares with 1, 2, or 3 colored faces for center, edge, and corner pieces, respectively. The Rubik's Cube is then composed of 26 cubies. The faces are rotated using quaternions. The code for the cubies and cube is here: https://github.com/benbotto/rubiks-cube-cracker/tree/2.2.0/Model/WorldObject
If you're interested in my Rubik's Cube solver program, there's a high-level overview video on YouTube: https://www.youtube.com/watch?v=ZtlMkzix7Bw&feature=youtu.be I also have a more extensive write-up on solving the Rubik's Cube programmatically on Medium.
Eschew optimisation; make it object-oriented. A pseudocode class outline I've used is:
class Square
+ name : string
+ accronym : string
class Row
+ left_square : square
+ center_square : square
+ right_square : square
class Face
+ top_row : list of 3 square
+ center_row : list of 3 square
+ bottom_row : list of 3 square
+ rotate(counter_clockwise : boolean) : nothing
class Cube
+ back_face : face
+ left_face : face
+ top_face : face
+ right_face : face
+ front_face : face
+ bottom_face : face
- rotate_face(cube_face : face, counter_clockwise : boolean) : nothing
The amount of memory used is so small and processing so minimal that optimisation is totally unnecessary, especially when you sacrifice code usability.
You could imagine the cube as three vertical circular linked lists, which intersect three horizontal linked lists.
Whenever a certain row of the cube is rotated you would just rotate the corresponding pointers.
It would look like this:
struct cubeLinkedListNode {
cubedLinkedListNode* nextVertical;
cubedLinkedListNode* lastVertical;
cubedLinkedListNode* nextHorizontal;
cubedLinkedListNode* lastHorizontal;
enum color;
}
You might not actually need the 2 'last'-pointers.
[ I did this with C, but it could be done in Java or C# just using a simple class for cubeLinkedListNode, with each class holding references to other nodes. ]
Remember there are six interlocking circular linked lists. 3 vertical 3 horizontal.
For each rotation you would just loop through the corresponding circular linked list sequentially shifting the links of the rotating circle, as well as the connecting circles.
Something like that, at least...
The others well addressed describing the physical cube, but regarding the state of the cube... I would try using an array of vector transformations to describe the changes of the cube. That way you could keep the history of the rubiks cube as changes are made. And I wonder if you could multiply the vectors into a transformation matrix to find the simplest solution?
An interesting method to represent the cube is used by the software "Cube Explorer". Using a lot of clever maths that method can represent the cube using only 5 integers. The author explains the maths behind his program on his website. According to the author the representation is suited to implement fast solvers.