0% found this document useful (0 votes)
142 views17 pages

Rubik's Cube Solver by Ben Botto

The document describes implementing two algorithms for solving a Rubik's Cube: Thistlethwaite's algorithm, which can solve any cube in 52 moves or fewer, and Korf's optimal algorithm, which can solve any cube in 20 or fewer moves. It discusses the data structures and terminology used to represent the cube state in the algorithms.

Uploaded by

Huseyn Huseynli
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
142 views17 pages

Rubik's Cube Solver by Ben Botto

The document describes implementing two algorithms for solving a Rubik's Cube: Thistlethwaite's algorithm, which can solve any cube in 52 moves or fewer, and Korf's optimal algorithm, which can solve any cube in 20 or fewer moves. It discusses the data structures and terminology used to represent the cube state in the algorithms.

Uploaded by

Huseyn Huseynli
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

Implementing an Optimal Rubik’s Cube Solver

using Korf’s Algorithm


And a Quick Solver Using Thistlethwaite’s Algorithm

Ben Botto
May 9, 2020

The Rubik’s Cube is a fascinating, timeless puzzle with quintillions of possible


states. As a cube-geek turned AI-enthusiast, and armed with a handful of human-
centric algorithms for solving the cube, I decided to try my hands at programming a
Rubik’s Cube solver. Perhaps a bit naively, I didn’t outright realize that solving a cube
optimally or proving that any cube is solvable in 20 or fewer moves would involve a
decent understanding of mathematics, combinatorics, optimization, and group
theory. Fun stuff. So I waded through a bunch of papers, brushed off some old math
and AI books, and went to work coding up two algorithms. First I implemented
Thistlethwaite’s algorithm, which can solve any cube in 52 moves or fewer, and then
Korf’s optimal algorithm, a.k.a. “God’s algorithm,” which can solve any cube in 20 or
fewer moves.
In this article I’ll give an overview of some of the data structures I used, and a high-
level description of each algorithm. My code is available on GitHub, and at the time
of writing version 4.0.0 is the latest release. I also have a video on YouTube that
showcases the program (note that version 2.2.0 is shown in the video, and the
program has been enhanced since then).

While this might be obvious, I do want to get it out of the way early on. It is not
possible on a modern computer to solve a cube with a simple brute force algorithm.
A 3x3 Rubik’s Cube has 43,252,003,274,489,856,000 permutations with a branching
factor of about 13, and that’s far too many states to iterate through. Cooking up a
programmatic solver involves a pinch of efficient data structures, a sprinkle of
group theory, and a splash of clever algorithms.

Terminology and Notation


For those unfamiliar with the Rubik’s Cube, I’ll be using the following terms and
notation.

Cubie: One of the 26 mini cubes that make up the entire Rubik’s Cube.

Center cubie: A cubie with one sticker on it at the center of a face. Note that the
center cubies are static. They cannot be moved relative to each other (the red
center cubie is always opposite the orange), nor can they be moved by twisting
the faces.

Edge cubie: A cubie with two stickers, like the red-yellow cubie.

Corner cubie: A cubie with three stickers, such as the red-blue-yellow cubie.

Face: A side of a Rubik’s Cube made up of 9 cubies: 1 center, 4 edges, and 4


corners. There are six faces: front, back, left, right, up, and down.

Twist: A 90- or 180-degree turn of one of the six faces. Given that each face can
be turned 90 degrees clockwise, 90 degrees counterclockwise (“prime”), or 180
degrees, there are 18 possible twists.

Twists generally follow the notation that the first letter of the twisting face is a 90-
degree clockwise turn. So F, B, U, D, L, and R designate 90-degree turns of the front,
back, up, down, left, and right faces, respectively. An apostrophe (’) suffix
designates a 90-degree counterclockwise “prime” twist, so R’ means to turn the right
face 90 degrees counterclockwise. And a “2” suffix indicates a 180-degree twist. L2
means the left face is turned 180-degrees.

A corner, edge, and center cubie on the front face. F would move the red-yellow edge cubie to the front-right
position, F’ to the front-left, and F2 would move it to the front-down position.

You should also be up to speed on the A* search and iterative-deepening depth-first


search (IDDFS) algorithms, at least at a high level, although this article contains a
brief overview of each.

Some O.K. Data Structures for the Rubik’s Cube


Writing a program to solve a Rubik’s cube using a human method like the layer-by-
layer approach — the one that comes in the pamphlet with a new Rubik’s Cube — is
trivial and 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 when solving the cube
using a more computationally intensive method like Korf’s or Thistlethwaite’s, the
data structure and programming language are of utmost importance. The
algorithms described in this article involve twisting the cube billions of times and
making billions of comparisons, and the data structure used to represent the cube
therefore has a large impact on the time it takes to solve. Early on in my
implementation, I tested the following structures:
1. A three-dimensional array of chars, 6x3x3. The color of a face is indexed like
cube[SIDE][ROW][COL] . This is intuitive, but slow.

2. A single array of 54 chars. This is faster than the first structure, and the row and
stride are calculated manually (trivial). It’s still slow.

3. 6 64-bit integers. This method is essentially a bitboard, for those familiar with
the chess domain, and is significantly faster than methods one and two.
Twisting can be done using bitwise 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).

I used the third structure for my original implementation of the Thistlethwaite


algorithm, which was done using decision trees. Later I enhanced the algorithm to
use pattern databases, which are described in depth below. Structure four is much
faster when using pattern databases, and in the end I used that structure for both
the Thistlethwaite and Korf algorithms. Structures three and four are described in
detail in the following two sections.

Bitboard Structure
Expanding on structure three, 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 a color can
be stored in a single byte. The 8 stickers on a face of a Rubik’s Cube can therefore be
stored in 64 bits.

Given these (arbitrary) 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 bitwise assembly
instructions can be used to move a face. Rolling by 16 bits effects a 90-degree twist,
and rolling by 32 bits gives a 180-degree twist. The adjacent pieces need to be up-
kept manually — e.g. after rotating the top face, the top-most colors 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

Here’s how that looks with inline assembly in C++, using g++ with AT&T assembly
syntax. (Here is the model on GitHub.)

/* Where face is an array of 8 characters cast to a


64-bit number. */
asm volatile ("rolq $16, %[face]" : [face] "+r" (face) : );
Another advantage of this structure 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. Also, creating copies of the cube when
searching for a solution only requires copying 8 integers.

Index-Orientation Structure
Moving on to structure four from the list above, Korf’s algorithm uses an A* search
with pattern databases as a heuristic. Pattern databases are described in more detail
below, but in a nutshell a pattern database can be thought of as a value function that
accepts the state of a cube and returns a lower-bounds estimate of the number of
moves it takes to get from that state to the solved state. For Korf’s algorithm, “state”
is represented as the permutation and orientation of the cubies. For example, the
indexes and orientations of the 8 corner cubies could make up part of the cube’s
state. That representation given, it makes sense to store an ordered array of corner
cubie indexes i ∈ {0, ..., 7} and orientations o ∈ {0, 1, 2} .

Arbitrarily defining a convention, 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

The rows represent the index (0, …, 7), the position in the solved state (up-left-back,
…, down-right-front), and the colors (red-blue-yellow, …, orange-green-white). So
the red-blue-yellow (RBY) cubie is indexed as 0 and resides in the up-left-back (ULB)
corner when the cube is in a solved state. The orange-green-yellow (OGY) cubie is
indexed as 6 and sits in the down-right-back (DRB) corner.

Likewise, the edge cubies could be indexed as follows (again, arbitrarily).

0 1 2 3 4 5 6 7 8 9 10 11
UB UR UF UL FR FL BL BR DF DL DB DR
RY RG RW RB WG WB YB YG OW OB OY OG

The red-yellow (RY) cubie is indexed as 0 and sits at the intersection of the up and
back (UB) faces when the cube is in a solved state. The white-green (WG) cubie is
indexed as 4 and occupies the intersection of the front and right (FR) faces.
With this structure the orientation of each cubie needs to be kept as well. An edge
piece can be in one of two orientations, oriented or flipped ( o∈{0,1} ), while a
corner piece can be in three different orientations, oriented, rotated once, or
rotated twice ( o∈{0,1,2} ). More details about the orientation of pieces can be found
in Conrad Rider’s Edge Orientation Detection article.

In this model, rotating a face means updating indexes and orientations. This
representation is difficult to program 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
works well when using pattern databases.

You can see an implementation of this model on my GitHub.


A Graphical Structure
My program also renders the cube, and I used a different structure for that part.
Since it’s not part of the solving algorithm, I’ll only touch on this structure briefly.

I defined a “cubie” class, which is made up of 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 and spherical linear
interpolation (SLERP), and the whole thing is rendered in OpenGL.

The code for the cubies and cube is also on my GitHub.

Korf’s Optimal Algorithm


Richard Korf’s algorithm can solve any scrambled cube in 20 moves or fewer. It
works by searching for solutions using an iterative-deepening depth-first search
combined with A* (IDA*).

Iterative-deepening depth-first search (IDDFS) is a tree-traversal algorithm. Like a


breadth-first search (BFS), IDDFS is guaranteed to find an optimal path from the
root node of a tree to a goal node, but it uses less memory than a BFS. The lower
memory footprint makes it applicable to searching trees with a large branching
factor like the Rubik’s Cube. Consider a cube as the root of a tree, i.e. depth 0.
Applying each possible twist of the cube (L, F, R, etc.) brings the cube to a new node
at depth 1, any of which may be the solved state. If not, applying each combination
of two moves (LF, LR, LU, etc.) may solve the cube. That continues until a solution is
found.

IDDFS alone would take far too much time to solve most scrambled cubes. There are
18 possible face twists of the cube, a large branching factor. After the first set of
twists, some of the moves can be pruned. For example, turning the same face twice
is redundant: FF is the same as F2; FF’ is the same as no move; FF2 is the same as F’;
and so on. Also, some moves are commutative: FB is the same as BF; U2D is the
same as DU2; etc. But even after pruning, the branching factor is over 13, so
searching for a solution with raw IDDFS would take thousands of years on a modern
computer! Here’s where A* comes in.

A* is a graph-traversal algorithm that’s used to find the optimal path from one node
of a graph to another, and, given that a tree is just a graph, it can be combined with
IDDFS. A* uses a heuristic to guide the search. A heuristic provides an estimated
distance (number of twists) from one node (a scrambled state) to another (the
solved state). For A* to operate correctly — specifically, to guarantee an optimal path
— the heuristic must never overestimate the distance. IDA* works the same way as
IDDFS, but rather than starting the search at depth 0 it queries a heuristic for an
estimated distance to the goal state and starts at that depth. During the search, if the
estimated distance through a node to the solved state exceeds the search depth, then
the node is pruned from the tree. In other words, the heuristic is used to see if
moving from one state of the cube to another brings the cube closer to or farther
away from the solved state. If the twist moves the cube farther away from the solved
state, then that branch of the search can be pruned.

Putting this in lay terms, a permissible heuristic for the Rubik’s Cube is a function
that accepts a scrambled cube and estimates or underestimates the number of
moves to solve the cube. One way to create such a heuristic function is to create a
database of every possible scramble of a subset of the cubies. For example, a
database that takes in the indexes and orientations of the 8 corner cubies and
returns the number of moves to solve just the corners. Given such a database,
provided a scrambled cube will take two moves to solve the corner cubies, it will
take at least two moves to solve the entire cube. It will likely take more than two
moves because of the edge cubies. In the image below, the corners are all solved, so
a corner-only pattern database would return an estimate of 0 moves to the solved
state. That’s obviously an underestimate because the edges are not solved, but it’s a
permissible heuristic because it never overestimates the distance to the solved state.
(This particular scramble is 6 moves away from solved.)

Pattern Databases
Indeed, Richard Korf proposes using a series of pattern databases as a heuristic for
the Rubik’s Cube, and one of the databases stores the number of moves required to
solve the corner pieces of any scramble. There are 8 corner cubies, and each can
occupy any of the 8 positions, so there are 8! possible permutations. Each of the
corner pieces can be oriented in 3 different ways — any of the three stickers can face
up — but the orientations of 7 of the cubies dictate the orientation of the 8th, by the
laws of the cube. (For the math folks out there, this has to do with parity of the cube,
which is even for the entire cube, but even or odd when considering the corners or
edges alone.) Therefore, there are 3⁷ possible ways the corners can be orientated.
Altogether then, there are 8! * 3⁷ possible ways for the corners of the cube to be
scrambled, and these 88,179,840 states can be iterated in a reasonable amount of
time (30 minutes or so). All corner states can be reached in 11 moves or fewer, so
each entry in the corner pattern database can be stored in a nibble (4 bits). On disk,
the corner pattern database occupies about 42MB.

Korf suggests two additional databases: one for 6 of the 12 edges, and another for
the other 6 edges. Given that modern developer boxes have plenty of RAM, I opted
to use two databases with 7 edges each. 7 edges can occupy 12 positions, so there are
12P7 (12! / (12–7)!) permutations. Each corner can be oriented in 2 ways, so there are
2⁷ possible orientations of 7 edges. Again, this is a small enough number of cube
states to iterate over, and all states can be reached in 11 moves or fewer. Storing
each entry in a nibble, each of the 7-edge databases occupies about 244MB (12P7 * 2⁷
/ 2 bytes).

Lastly, I used one additional database that holds the permutations of the 12 edges. It
takes about 228MB (12! / 2 bytes).
Using larger edge databases and the additional edge permutation database results in
a huge speed increase. Larger databases would result in an even bigger performance
increase, but it’s easy to use an enormous amount of memory. Adding just one more
edge piece to the 7-edge database, for example, increases the size of each database
to roughly 2.4GB.

An implementation detail that Korf glazes over in his algorithm is how to create
indexes into these pattern databases. That is, given a scrambled cube, how to create
a perfect hash out of the indexes and orientations of the edges or corners. That topic
is complex enough that I decided to dedicate another article to it, so if you’re
interested in the mathematical details, read my Sequentially Indexing Permutations:
A Linear Algorithm for Computing Lexicographic Rank article.

In a nutshell, the indexing works by first generating the Lehmer code of the cubie
index permutation, then converting the Lehmer code to a base-10 “rank.” (The
Lehmer code is a way of numbering permutations lexicographically). For the 8
corners, the ranks would be.

Permutation Rank
(0 1 2 3 4 5 6 7) 0
(0 1 2 3 4 5 7 6) 1
(0 1 2 3 4 6 5 7) 2
(0 1 2 3 4 6 7 5) 3
...
(4 0 1 2 3 5 6 7) 20160
...
(7 6 5 4 3 2 1 0) 40319

Then the permutation of the orientations of 7 of the corner cubies is ranked in a


similar manner. (Recall from above that the orientation of 7 of the corners dictate
the orientation of the 8th.)

Permutation Rank
(0 0 0 0 0 0 0) 0
(0 0 0 0 0 0 1) 1
(0 0 0 0 0 0 2) 2
(0 0 0 0 0 1 0) 3
...
(1 0 0 0 0 0 0) 729
...
(2 0 0 0 0 0 0) 1458
...
(2 2 2 2 2 2 2) 2186

Lastly, those two ranks are combined together to form an index into the database.

index = rankWithoutReplacement(cornerIndexes) * 3^7 +


rankWithReplacement(cornerOrientations)

It’s worth pointing out that indexing into the edge databases is more complicated
because it involves generating ranks over partial permutations (i.e. 7 of the 12
edges). Ranking partial permutations is also covered in my other article.

Since the Korf algorithm uses multiple pattern databases, the estimated number of
moves to the solved state, the A* heuristic, is the maximum value returned from all
of the pattern databases. More concretely, given a cube state where the 8 corner
cubies are 4 moves away from solved, 7 edge cubies are 6 moves away from solved,
and the other 7 edge cubies 3 moves away from solved, the lower-bounds estimated
number of moves to the solved state is max(4, 6, 3) = 6 .

IDA*
With the Rubik’s Cube structure and pattern databases in place, the IDA* algorithm
can be coded. I opted to use a non-recursive implementation, which, in my testing,
performed better than the typical recursive version. It also uses an optimization,
similar to best-first search, where leaf nodes are sorted based on their estimated
distance to the solved state. That way cube states that appear to be closest to the
solved state are visited first.

Here’s the gist of it in pseudocode, which is loosely C++.


1 /**
2 * Non-recursive IDA* search (pseudocode).
3 * @param cube A scrambled cube to solve.
4 */
5 vector<uint8_t> idaStar(RubiksCube& cube)
6 {
7 // Holds a copy of the cube, a move (L, R, etc.), and the search depth.
8 struct Node
9 {
10 RubiksCube cube;
11 uint8_t move;
12 uint8_t depth;
13 };
14
15 // A stack of the visited nodes.
16 stack<Node> nodeStack;
17 // The current node in the search.
18 Node curNode;
19 // A list of the moves to get to the current node. 0xFF is used as a marker
20 // to indicate the end of the list.
21 array<uint8_t, 21> moves = {0xFF};
22 // Flag that indicates when the cube is solved.
23 bool solved = false;
24 // The current search bound, which is a lower-bounds estimate of the number
25 // of moves required to solve the cube.
26 uint8_t bound;
27 // The next bound if the cube is not solved at the current bound. It will
28 // hold the minimum estimated moves of all successor nodes greater than
29 // bound.
30 uint8_t nextBound = heuristic(cube);
31
32 while (!solved)
33 {
34 // When there are no nodes in the stack, either this is the first iteration
35 // of the search, or the current search bound is exhausted (i.e. it takes
36 // more moves than "bound" to solve the cube). Start a new search from the
37 // scrambled cube (root) and move on to the next bound.
38 if (nodeStack.empty())
39 {
40 // Start with the scrambled (root) node. It's at depth 0, and no move is
41 // required to get to it.
42 nodeStack.push({cube, 0xFF, 0});
43
44 // Set the new search bound, and nextBound to "infinity."
45 bound = nextBound;
46 nextBound = 0xFF;
47 }
48
48
49 curNode = nodeStack.top();
50 nodeStack.pop();
51
52 // The move required to get to the new current node is appended to the
53 // list, and the end-of-move marker is set.
54 if (curNode.depth != 0)
55 moves[curNode.depth - 1] = curNode.move;
56 moves[curNode.depth] = 0xFF;
57
58 // At the leaves of the search tree, check if the current node is the
59 // solved cube.
60 if (curNode.depth == bound)
61 {
62 if (curNode.cube.isSolved())
63 solved = true;
64 }
65 else
66 {
67 // A priority queue is used to sort the successor nodes by estimated
68 // moves. That way the states that appear to be closest to solved are
69 // visited first.
70 struct PrioritizedMove
71 {
72 RubiksCube cube;
73 uint8_t move;
74 uint8_t estMoves; // Priority. Least number of moves to most.
75 bool operator>(const PrioritizedMove& rhs) const
76 {
77 return this->estMoves > rhs.estMoves;
78 }
79 };
80 priority_queue<PrioritizedMove> successors;
81
82 // Iterate over all 18 possible twists.
83 for (uint8_t i = 0; i < 18; ++i)
84 {
85 // Some moves (redundant/commutative) can be pruned.
86 if (curNode.depth == 0 || !prune(i, curNode.move))
87 {
88 // Copy the cube and move it to get to a successor state.
89 RubiksCube cubeCopy(curNode.cube);
90 cubeCopy.move(i);
91
92 // Estimated moves to the solved state from the scrambled cube (root)
93 // through this successor.
94 uint8_t estSuccMoves = curNode.depth + 1 + heuristic(cubeCopy);
95
96 if (estSuccMoves <= bound)
97 {
98 // If this successor state is estimated to take fewer moves than
99 // the current bound, push it; otherwise, it's pruned (moving away
100 // from the solved state).
101 successors.push({cubeCopy, i, estSuccMoves});
102 }
103 else if (estSuccMoves < nextBound)
104 {
105 // The next search bound is the minimum of all successor node move
106 // estimates that's greater than the current bound.
107 nextBound = estSuccMoves;
108 }
109 }
110 }
111
112 while (!successors.empty())
113 {
114 // Push the nodes in sorted order.
115 nodeStack.push({
116 successors.top().cube,
117 successors.top().move,
118 curNode.depth + 1
119 });
120
121 successors.pop();
122 }
123 }
124 }
125
126 return moves;
127 }

The actual implementation is available on my GitHub.

Thistlethwaite’s Algorithm
The optimal solver can take a long time to solve a cube, especially for scrambles that
take 18+ moves to solve. The Thistlethwaite algorithm is markedly faster, solving
cubes effectively instantaneously. It can also be implemented without pattern
databases using a decision tree, which is slower but easier to implement. In version
2.2.0 of my program I used a decision tree to implement Thistlethwaite’s algorithm,
but I’ve since enhanced the program to use pattern databases. A word of warning:
Implementing Thistlethwaite’s algorithm with pattern databases is quite difficult.

Like Korf’s, Thistlethwaite’s algorithm uses IDDFS. Above it was mentioned that the
branching factor of a Rubik’s Cube is a little above 13. There are 18 twists, but some
pairs of moves are redundant or commutative and can be pruned. Thistlethwaite’s
algorithm works by moving the cube from one “group” to another. Each successive
group can be solved with only a subset of the 18 twists, thereby reducing the
branching factor and the number of possible cube states, and making it
computationally easier to solve.

The initial group, group 0, is any scrambled cube. Remember from earlier that each
edge cubie can be in one of two orientations. Well, it turns out that edge pieces
cannot be flipped if quarter turns of the front and back faces are not used. (The
front and back faces are arbitrary — the same holds true for any two opposing faces,
e.g. left and right, front and back, or up and down.) By moving the cube to a state
wherein all 12 edge pieces are correctly oriented, group 1, the cube can be solved
without using quarter turns of the front or back faces. Put another way, an edge
piece is correctly oriented if it can be moved to its solved position without using
quarter turns of the front or back faces. From group 1 the branching factor is
reduced by 4 because the cube can be solved without F, F’, B, or B’ twists.

Next, the cube is moved to a state such that all corners are correctly oriented. That
is, if the cube is oriented such that the red center cubie is pointed up, then all 8
corner cubies have red or orange stickers on the up or down faces. Also, four of the
edges are moved to the correct slice: The front-left, front-right, back-left, and back-
right edges are placed in the E slice (the layer between the front and back faces).
This is group 2. Once group 2 is reached, the cube can be solved without quarter
turns of the left or right faces. This again reduces the branching factor by 4 because
L, L’, R, and R’ are no longer needed.

For group 3, all corners are moved to the correct orbit, meaning that each corner
can be moved to its home position with only 180-degree twists. Also, each edge is
moved to its home slice, M, E, or S, which are the layers between the left and right
faces, the up and down faces, and the front and back faces, respectively. From group
3 the cube can be solved with only 180-degree twists. Once again, the branching
factor is reduced by 4 (U, U’, D, and D’ are no longer needed).

Finally, only the 6 180-degree twists are needed to move from group 3 to the solved
state: U2, F2, L2, R2, B2, and D2. With that, we’ll stick a fork in it an call it done.

Again, this algorithm can be implemented using decision trees. In this case IDDFS is
used to move from one group to the next. Once a new group is reached, a subset of
the 18 twists are excluded from the search. This approach takes about a minute on
average to solve a cube. Alternatively, a series of pattern databases can be used, with
one pattern database per group, where each pattern database provides the exact
number of moves to get from an arbitrary cube state to the next group. That
approach can solve any scramble in mere milliseconds, but the math involved is
complicated.

Final Notes
There is a table on my GitHub that shows how long it takes to solve various
scrambles using my implementation of Korf’s algorithm. My program is
significantly faster than others I found on the internet, specifically because of the
linear ranking algorithm I use. It still takes quite a while to run through a solution,
though.

There’s also a table that shows the size and maximum distances in each group used
in the Thistlethwaite algorithm. Note that my implementation differs slightly from
Thistlethwaite’s in the third phase, and as such it solves cubes in a maximum of 46
twists instead of 52.

A great enhancement would be to utilize symmetry. Abstractly speaking, each


scrambled cube can be mirrored in multiple ways. Using symmetry would in effect
compress the pattern databases and allow for the usage of databases with larger
subsets of the cubies.

But those are topics for another day. I hope you have enjoyed the article. As always,
if you have any questions or comments about the article or code, please drop me a
line below.

References and Helpful Links


Botto, Ben. Sequentially Indexing Permutations: A Linear Algorithm for Computing
Lexicographic Rank. An article describing permutation ranking in further detail.

Brown, Andrew. Rubik’s Cube Solver. Used for speed comparison. Also used to verify
optimal solutions.

Coppin, Ben. Artificial Intelligence Illuminated. The sections on searching, game


playing, and dynamic programming are particularly pertinent. This is a great book
on AI in general.
Heise, Ryan. Rubik’s Cube Theory. This has some advanced discussion about
reachable permutations and orientations of the cubies.

Korf, Richard E. Finding Optimal Solutions to Rubik’s Cube Using Pattern Databases.
This describes Korf’s algorithm for solving the cube.

Korf, Richard E. et. al. Large-Scale Parallel Breadth-First Search. This contains a
terse linear algorithm for generating sequential indexes into a pattern database
using a factorial number system.

MIT. The Mathematics of the Rubik’s Cube. A paper that goes into some of the
mathematics and group theory surrounding the cube.

Rider, Conrad. Edge Orientation Detection. This page presents a simple algorithm
for detecting the orientation of the edge pieces.

Rubiks.com. How to solve the Rubik’s Cube. A simple layer-by-layer approach for
solving a cube as a human.

Scherphuis, Jaap. Thistlethwaite, Morwen. Thistlethwaite’s 52-move algorithm. Jaap


has an overview of the algorithm, as well as scans of a letter from Thistlethwaite.

Taylor, Peter. Indexing Edge Permutations for the Rubik’s Cube. Peter was kind
enough to help me with creating indexes for the 7-edge pattern databases, which is a
variation of the Lehmer code using partial permutations.

Wikipedia. A* search algorithm.

Wikipedia. Best-first search.

Wikipedia. Iterative deepening A*. This has a recursive implementation of IDA*.

Wikipedia. Iterative deepening depth-first.

Wikipedia. Lehmer code.

You might also like