def test_triple_is_never_0_for_an_unsolved_cube(): for _ in range(TEST_SIZE): cube = Cube() while cube.is_solved: cube.scramble() assert cube.triple != (0, 0, 0)
def __init__(self): print("Initializing...", end="", flush=True) self.__cube = Cube() self.__simple = Simple(self.__cube) self.__kociemba = Kociemba(self.__cube) self.__korf = Korf(self.__cube) print(" Done.\n")
def test_CHECKERBOARD_pattern_with_single_twists(): cube = Cube() for turn in CHECKERBOARD_TURNS: cube.twist(turn) assert cube.cube_string == CHECKERBOARD
def test_triple_is_never_0_for_a_cube_not_in_G1(): for _ in range(TEST_SIZE): cube = Cube() while cube.is_domino: cube.scramble() assert cube.triple != (0, 0, 0)
def test_random_scramble_has_one_of_each_edge(): for _ in range(10): cube = Cube() cube.scramble() edges = cube.edges for edge in Cube.edge_order: assert edge in edges
def test_random_scramble_has_one_of_each_corner(): for _ in range(10): cube = Cube() cube.scramble() corners = cube.corners for corner in Cube.corner_order: assert corner in corners
def test_all_phase1_coordinates_of_cube_in_G1_are_always_0(): for _ in range(TEST_SIZE): cube = Cube() cube.scramble_g1() assert cube.coordinate_corner_orientation == 0 assert cube.coordinate_edge_orientation == 0 assert cube.coordinate_ud_slice == 0
def test_cube_is_solved_after_reset(): cube = Cube() cube.scramble() cube.twist(2) cube.twist_by_notation("U2 B D'") cube.reset() assert cube.is_solved
def test_all_phase1_coordinates_of_cube_not_in_G1_are_never_0(): for _ in range(TEST_SIZE): cube = Cube() while cube.is_domino: cube.scramble() x1 = cube.coordinate_corner_orientation x2 = cube.coordinate_edge_orientation x3 = cube.coordinate_ud_slice assert (x1, x2, x3) != (0, 0, 0)
def test_all_coordinates_of_a_not_solved_cube_are_never_0(): for _ in range(TEST_SIZE): cube = Cube() while cube.is_solved: cube.scramble() x1 = cube.coordinate_corner_orientation x2 = cube.coordinate_edge_orientation x3 = cube.coordinate_ud_slice x4 = cube.coordinate_corner_permutation x5 = cube.coordinate_edge_permutation x6 = cube.coordinate_ud_slice_phase2 assert (x1, x2, x3, x4, x5, x6) != (0, 0, 0, 0, 0, 0)
def __search(self, phase, notes: List[str], cube: Cube, depth: int) -> Tuple[int, str]: """A function to actually perform the search to the current search depth by using recursion. TODO: Also contains the pruning if the minimum distance increases. Return a tuple with the amount of moves as the first item and the solution as a notation str as the second item. In case a valid solution was not found the amount of moves is negative. (currently always -1)""" if self.__checked % 10000 == 0: print(f"Depth: {depth:2d}, checked: {self.__checked:,}+ " + f"(pruned: {self.__skipped:,}+)", end="\r") if depth <= len(notes): if phase == 1 and cube.is_domino or cube.is_solved: return (len(notes), " ".join(notes)) return (-1, "") for move in Cube.moves if phase == 1 else self.phase2_moves: if Cube.skip_move(notes, move): self.__skipped += 1 continue new_cube = copy.deepcopy(cube) new_cube.twist_by_notation(move) self.__checked += 1 result = self.__search(phase, notes + [move], new_cube, depth) if result[0] > 0: return result return (-1, "")
def __search(self, notes: List[str], cube: Cube, depth: int, estimate: int) -> Tuple[int, str]: """A function to actually perform the search to the current search depth by using recursion. Also contains the pruning if the minimum distance increases""" if self.__checked % 10000 == 0: print(f"Depth: {depth:2d}, checked: {self.__checked:,}+, " + f"(pruned: {self.__skipped:,}+)", end="\r") if len(notes) >= depth or estimate <= 0: if cube.is_solved: return (len(notes), " ".join(notes)) return (-1, "") for move in cube.moves: if Cube.skip_move(notes, move): self.__skipped += 1 continue new_cube = copy.deepcopy(cube) new_cube.twist_by_notation(move) new_estimate = self.__tables.get_distance(self.coordinate(cube)) if new_estimate != -1: if estimate < new_estimate: self.__skipped += 1 continue estimate = new_estimate + 1 self.__checked += 1 result = self.__search(notes + [move], new_cube, depth, estimate - 1) if result[0] > 0: return result return (-1, "")
def test_all_coordinates_of_a_solved_cube_are_0(): cube = Cube() assert cube.coordinate_corner_orientation == 0 assert cube.coordinate_edge_orientation == 0 assert cube.coordinate_ud_slice == 0 assert cube.coordinate_corner_permutation == 0 assert cube.coordinate_edge_permutation == 0 assert cube.coordinate_ud_slice_phase2 == 0
def generate_tables(self) -> None: """A function to generate the pruning tables for Korf's algorithm by iterating through search depths from 0 to 20.""" cube = Cube() for depth in range(0, 8): # FIXME: Should be 0..20 range(0, 21) print(f"Generation Depth: {depth}") self.generation_search([], cube, depth, 0) if self.__tables.is_complete: break self.__tables.print_completeness()
def test_any_cube_with_just_one_move_is_solved(): for move in Cube.moves: cube = Cube() cube.twist_by_notation(move) result = Simple(cube).solve() cube.twist_by_notation(result[1]) assert cube.is_solved
def test_any_random_cube_is_solved(): for _ in range(TEST_SIZE): cube = Cube() cube.scramble() result = Simple(cube).solve() cube.twist_by_notation(result[1]) assert cube.is_solved
def test_a_cube_in_G1_with_4_moves_is_solved(): cube = Cube() cube.twist_by_notation("U R2 B2 D'") solution = Kociemba(copy.deepcopy(cube)).solve() assert solution[0] > 0 and len(solution[1]) > 0 cube.twist_by_notation(solution[1]) assert cube.is_solved
def test_checkerboard_is_solved_with_6_moves(): cube = Cube() cube.twist_by_notation("U2 D2 F2 B2 L2 R2") solution = Kociemba(copy.deepcopy(cube)).solve() assert solution[0] == 6 cube.twist_by_notation(solution[1]) assert cube.is_solved
def edge_pattern_second(cls, cube: Cube) -> int: """A function to calculate the edge pattern index for the last six edges of a cube for Korf's algorithm. The pattern is a combination of the orientation (0..63) and the permutation (0..665,219) of the last six edges of the cube. Which means that the value of index of the pattern is always between 0 and 42,577,919.""" order = Cube().edge_order[6:][::-1] edges = [ edge if edge in order or edge[::-1] in order else "-" for edge in cube.unoriented_edges ][::-1] return cls.__edge_pattern(order, edges)
def test_cubes_in_G1_with_4_random_moves_are_solved(): for _ in range(5): cube = Cube() cube.scramble_g1(4) solution = Kociemba(copy.deepcopy(cube)).solve() assert solution[0] >= 0 and len(solution[1]) >= 0 cube.twist_by_notation(solution[1]) assert cube.is_solved
def test_all_cubes_with_one_move_are_solved(): for move in Cube.moves: cube = Cube() cube.twist_by_notation(move) solution = Kociemba(copy.deepcopy(cube)).solve() assert solution[0] >= 1 cube.twist_by_notation(solution[1]) assert cube.is_solved
def generation_search(self, notes: List[str], cube: Cube, depth: int, distance: int) -> None: """A function to search all of the patterns indexes to generate the pruning tables required by the Korf's algorithm to solve any cube in less than about 10^12 years. Unfortunately this function currently takes about 10^13 years with depth 20 to finish on a modern desktop computer.""" # FIXME: Optimize to run in less than a week. # FIXME: Pruning while generating doesn't seem to work correctly. if distance >= depth: self.__tables.set_distance(self.coordinate(cube), distance) return for move in cube.moves: if Cube.skip_move(notes, move): continue new_cube = copy.deepcopy(cube) new_cube.twist_by_notation(move) estimate = self.__tables.get_distance(self.coordinate(new_cube)) if estimate != -1 and estimate - depth < distance: # print(f"Depth: {depth}, distance: {distance}, move: {move}") continue self.generation_search(notes + [move], new_cube, depth, distance + 1)
def test_solved_cube_is_domino(): assert Cube().is_domino
def test_the_str_of_a_solved_cube_is_correct(): assert str(Cube()) == SOLVED_STR
def test_CUBE_IN_THE_CUBE_pattern(): cube = Cube() cube.twist_by_notation(CUBE_IN_THE_CUBE_NOTATION) assert cube.cube_string == CUBE_IN_THE_CUBE
def test_CHECKERBOARD_is_domino(): cube = Cube() cube.twist_by_notation(CHECKERBOARD_NOTATION) assert cube.is_domino
def test_empty_notation_doesnt_affect_the_cube(): cube = Cube() cube.twist_by_notation("") assert cube.is_solved
def test_cube_string_of_a_solved_cube_is_correct(): SOLVED_STRing = "UUUUUUUUULLLLLLLLLFFFFFFFFFRRRRRRRRRBBBBBBBBBDDDDDDDDD" cube_string = Cube().cube_string assert SOLVED_STRing == cube_string
def test_random_cubes_in_G1_are_domino(): for _ in range(5): cube = Cube() cube.scramble_g1() assert cube.is_domino
def test_CUBE_IN_THE_CUBE_is_not_domino(): cube = Cube() cube.twist_by_notation(CUBE_IN_THE_CUBE_NOTATION) assert not cube.is_domino