In this notebook we show a brief demonstration of how to create and manipulate a virtual 4x4 Rubik's Cube (a Rubik's Revenge puzzle).
We use the rbuikscubennnsolver library from Github.
from rubikscubennnsolver.RubiksCube444 import RubiksCube444, solved_4x4x4
from pprint import pprint
def get_cube():
"""
Get a 4x4 Rubiks Cube.
"""
order = 'URFDLB'
cube = RubiksCube444(solved_4x4x4, order)
return cube
rr = get_cube()
rr.print_cube()
The cube starts in the solved state, and from there we can apply a sequence of moves (more on the move notation in the next notebook). For example, the notation for turning the upper face clockwise, then left face clockwise, then upper face counter-clockwise, then left face counter-clockwise, would be "U L U' L'". To apply this to the virtual cube, we use the cube's rotate(move)
method to apply each move.
We will use this cube and this sequence to demonstrate a curious property that any move sequence will fulfill: if we repeatedly apply a move sequence to a solved cube, the cube will eventually return to a solved state. The number of times we need to apply the sequence depends on the sequence itself (and can be as small as 1 or as high as nearly 100,000).
In between these cycles, the center four squares or the 8 squares comprising the cross will occasionally all go through a solved state (that is, the cube will reach a "lower entropy" state). We can check that with the crosses_solved()
and center_solved()
methods.
For the sequence we picked, ULU'L, the cross pattern will reappear after the squence is applied 3 times, and the cube will return to the solved state after the sequence is applied 6 times.
# Apply this sequence 3 times to a solved cube, and the cross pattern re-appears on each face.
# Apply this sequence 6 times to a solved cube, and the entire cube returns to solved state.
sequence = ["U","L","U'","L'"]
for move in sequence:
rr.rotate(move)
for move in sequence:
rr.rotate(move)
for move in sequence:
rr.rotate(move)
rr.print_cube()
rr.crosses_solved()
for move in sequence:
rr.rotate(move)
for move in sequence:
rr.rotate(move)
for move in sequence:
rr.rotate(move)
rr.print_cube()
rr.crosses_solved()
rr.solved()
It's important to note that (as far as I can tell) this library will not check if a cube position is valid. For example, on a Rubik's Revenge cube, it is impossible to have a double edge with a left piece oriented correctly and a right piece oriented incorrectly, if all other parts of the cube are solved. The double edges must be oriented correctly or incorrectly, together. The following position is impossible:
impossible_4x4x4="UUUUUUUUUUUUUFUURRRRRRRRRRRRRRRRFUFFFFFFFFFFFFFFDDDDDDDDDDDDDDDDLLLLLLLLLLLLLLLLBBBBBBBBBBBBBBBB"
order = 'URFDLB'
i = RubiksCube444(impossible_4x4x4,order)
i.print_cube()
This means that if we want, at some point, to generate random Rubik's Cube permutations, we have to be careful to only generate valid cubes (or else implement a method for RubiksCube444 objects to check if a cube configuration is valid!).
There are a few moves that the cube will execute, but none implement a second-layer turn.
2U, for example, implements a rotation of the upper two layers, rather than only the second upper layer. But that's also what Uw does, so this is redundant.
U2 implements two rotations of the U layer, so this doesn't do what we want either.
rr = get_cube()
rr.print_cube()
print("\n"*2)
rr.rotate("2U")
rr.print_cube()
The kludge solution is to implement the clockwise rotation of the second layer by rotating the top two layers clockwise, then the top layer counterclockwise:
Uw U'
Note that this portion of the code (rotate()
method in __init__.py
of rubiksnnnsolver library) is non-trivial to modify, so instead we just replace any 2U
in the sequence with Uw U'
.
rr = get_cube()
rr.print_cube()
print("\n"*2)
rr.rotate("Uw")
rr.rotate("U'")
rr.print_cube()