How would you represent a Rubik's Cube in code?

前端 未结 11 1635
清酒与你
清酒与你 2020-12-07 09:52

If you were developing software to solve a Rubik\'s Cube, how would you represent the cube?

相关标签:
11条回答
  • 2020-12-07 10:15

    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:

    1. A three-dimensional array of chars, 6x3x3. The color of a face is indexed like cube[SIDE][ROW][COL]. This was intuitive, but slow.
    2. A single array of 54 chars. This is faster than (1), and the row and stride are calculated manually (trivial).
    3. 6 64-bit integers. This method is essentially a bitboard, and is significantly faster than methods (1) and (2). Twisting can be done using bit-wise operations, and face comparisons can be done using masks and 64-bit integer comparison.
    4. An array of corner cubies and a separate array of edge cubies. The elements of each array contain a cubie index (0-11 for edges; 0-7 for corners) and an orientation (0 or 1 for edges; 0, 1, or 2 for corners). This is ideal when your solver involves pattern databases.

    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.

    0 讨论(0)
  • 2020-12-07 10:16

    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.

    0 讨论(0)
  • 2020-12-07 10:16

    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...

    0 讨论(0)
  • 2020-12-07 10:19

    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?

    0 讨论(0)
  • 2020-12-07 10:28

    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.

    0 讨论(0)
提交回复
热议问题