Short answer: it depends on how you are going to solve the cube. If your solver uses a human method, such as a layered approach or the Friedrich method, then the basic data structure will not matter much. A computer can solve a cube using the human method in a short time (much less than a second) even in the slowest programming languages. But if you intend to solve the cube using a more complex computational method, such as the Thistlethwaite 52-step algorithm, the 29 Reid step algorithm, or the 20 Korf step algorithm, then the data structure and programming language are of utmost importance.
I implemented the Rubik Cube program, which renders a cube using OpenGL, and two different types of solvers (Thistlethwaite and Korf) are built into it. The solver must generate billions of moves and compare each state of the cube billions of times, so the basic structure must be fast. I tried the following structures:
- Three-dimensional array of characters, 6x3x3. The complexion is indexed as a cube [SIDE] [ROW] [COL]. It was intuitive, but slow.
- A single array of 54 characters. This is faster than (1), and the line and step are calculated manually (trivially).
- 6 64-bit integers. This method, in fact, is a bitboard and is much faster than methods (1) and (2). Curl can be performed using bitwise operations, and face comparison can be performed using masks and 64-bit integer comparisons.
- An array of corner cubes and a separate array of edge cubes. Elements of each array contain the Kubi index (0-11 for edges; 0-7 for angles) and orientation (0 or 1 for edges; 0, 1 or 2 for angles). This is ideal when your solver uses pattern databases.
Extending the method described above (3), each face of the cube consists of 9 stickers, but the center is fixed, so only 8 should be stored. And there are 6 colors, so each color is placed in a byte. Given these color definitions:
enum class COLOR : uchar {WHITE, GREEN, RED, BLUE, ORANGE, YELLOW};
A person may look like this, stored in a single 64-bit integer:
00000000 00000001 00000010 00000011 00000100 00000101 00000000 00000001
Which is decoded as:
WGR GB WYO
The advantage of using this structure is that rolq operators rolq and rorq can be used to move the face. 16-bit rolling results in a 90-degree rotation; rolling 32 bits gives a 180 degree turn. Neighboring items must be stored manually - i.e. After the rotation of the upper face, the upper layer of the front, left, back and right sides must also be moved. Turning your face in this way is really quick. For example, car
00000000 00000001 00000010 00000011 00000100 00000101 00000000 00000001
16 bit gives
00000000 00000001 00000000 00000001 00000010 00000011 00000100 00000101
It is decoded that looks like this:
WGW YG OBR
Another advantage is that cube state comparisons in some cases can be performed using some smart bit masks and standard integer comparisons. This can be quite a big acceleration for the solver.
Anyway, my implementation is on github: https://github.com/benbotto/rubiks-cube-cracker/tree/2.2.0 See Model/RubiksCubeModel.{h,cpp} .
Extending the method described above (4), some of the algorithms for the software solution of the Rubik's cube use iterative deepening of the depth search with A *, using template databases as heuristics. For example, Korfβs algorithm uses three template databases: one stores the index and orientation of 8 corner cubes; one stores a pointer and orientation of 6 out of 12 edges; the latter stores the index and orientation of the remaining 6 edges. When using template databases, a quick approach is to save the cube as a set of indices and orientations.
By arbitrarily defining an agreement, edge cubes can 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).
Thus, the red-yellow edge cube has an index of 0, and the white-green edge cube has an index of 4. Similarly, corner cubes can be indexed as follows:
0 1 2 3 4 5 6 7 ULB URB URF ULF DLF DLB DRB DRF RBY RGY RGW RBW OBW OBY OGY OGW
Thus, the red-blue-yellow corner cube has an index of 0, and the orange-green-yellow corner cube has an index of 6.
The orientation of each cube must also be maintained. The edge element can be in one of two orientations (oriented or inverted), while the corner element can be in three different orientations (oriented, rotated once or rotated twice). More information on the orientation of the figures can be found here: http://cube.crider.co.uk/zz.php?p=eoline#eo_detection In this model, the rotation of the face means updating the indices and orientations. This representation is the most difficult because it is difficult for a person (at least for me) to look at a large block of index and approximate numbers and check their correctness. Moreover, this model is much faster than the dynamic calculation of indices and orientations using one of the other models described above, and therefore it is the best choice when using template databases. You can see the implementation of this model here: https://github.com/benbotto/rubiks-cube-cracker/tree/2.2.0/Model (see RubiksCubeIndexModel.{h,cpp} ).
As already mentioned, the program also displays a cube. I used a different structure for this part. I defined the βcubieβ class, which is six squares with 1, 2, or 3 colored faces for the center, edge, and corner elements, respectively. The Rubik's Cube consists of 26 dice. Faces rotate using quaternions. The code for cubes and cubes is here: https://github.com/benbotto/rubiks-cube-cracker/tree/2.2.0/Model/WorldObject
If you're interested in my Rubik Cube solution program, there is a high-level video on YouTube: https://www.youtube.com/watch?v=ZtlMkzix7Bw&feature=youtu.be