def yellow_corners(state_str): side_faces = ["F", "R", "B", "L"] aim_corners = [{"D", side_faces[i], side_faces[(i + 1) % 4]} for i in range(4)] corners = [] for f in side_faces: n = get_normal(f) m = np.cross(Y, n) pos = -Y + n + m corners.append({ get_color_from_state_str(state_str, pos, -Y), get_color_from_state_str(state_str, pos, n), get_color_from_state_str(state_str, pos, m) }) is_rightly_positioned = [c == cc for c, cc in zip(aim_corners, corners)] if sum(is_rightly_positioned) == 1: i = is_rightly_positioned.index(True) # Belgium # print("One belgium found", i) to_right = aim_corners[(i + 1) % 4] == corners[(i + 3) % 4] # print(f"{to_right=}") moves = BELGIUM if not to_right else flip_left_right(BELGIUM) return rotate_moves_about_y(moves, get_normal(side_faces[(i + to_right) % 4])) # print("Not belgium found") return BELGIUM
def white_cross(state_str): # Start with 2nd crown edges for x in (-1, 1): for z in (-1, 1): pos = np.array([x, 0, z]) cols = [ get_color_from_state_str(state_str, pos, x * X), get_color_from_state_str(state_str, pos, z * Z) ] if "U" in cols: # print("2nd crown") i = [c != "U" for c in cols].index(True) col = cols[i] n = [x * X, z * Z][i] v = get_normal(col) # print(n, v) u = [x * X, z * Z][1 - i] m1, m3 = _get_up_turn(n, v) m2 = get_face_for_normal(n) if np.cross(n, u)[1] > 0: m2 += "'" # print(m1, m2, m3) return m1 + [m2] + m3 # Then down edges for x, z, n in [(-1, 0, -X), (1, 0, X), (0, -1, -Z), (0, 1, Z)]: pos = np.array([x, -1, z]) c_side = get_color_from_state_str(state_str, pos, n) c_down = get_color_from_state_str(state_str, pos, -Y) if c_down == "U": # print("Down - down") m1, m3 = _get_up_turn(get_normal(c_side), n) m2 = get_face_for_normal(n) + "2" # print(m1, m2, m3) return m1 + [m2] + m3 elif c_side == "U": # print("Down - side") m1, m3 = _get_up_turn(get_normal(c_side), n) m = get_face_for_normal(n) mm = get_face_for_normal(np.cross(n, Y)) m2 = [m, "U", mm, "U'"] return m1 + m2 + m3 # Then up edges (if misplaced) for x, z, n in [(-1, 0, -X), (1, 0, X), (0, -1, -Z), (0, 1, Z)]: pos = np.array([x, 1, z]) # print(pos, get_color_from_state_str(state_str, pos, n)) if get_color_from_state_str(state_str, pos, n) != get_face_for_normal(n): # print("Misplaced", pos, n) return [get_face_for_normal(n)] # Simply get it out raise ValueError("No move found, it cross done?")
def white_corners(state_str): # Down corners for x in (-1, 1): for z in (-1, 1): pos = np.array([x, -1, z]) csx = get_color_from_state_str(state_str, pos, x * X) csz = get_color_from_state_str(state_str, pos, z * Z) c_down = get_color_from_state_str(state_str, pos, -Y) if csx == "U" or csz == "U": # White on side # print("Down - side", csx, csz, c_down) n = x * X if csx == "U" else z * Z v = z * Z if csx == "U" else x * X col = csz if csx == "U" else csx m1, m3 = _get_up_turn(get_normal(col), v) to_right = np.cross(v, n)[1] > 0 m2 = [get_face_for_normal(n), "D"] if to_right: m2[0] += "'" m2[1] += "'" m2.append(neg_move(m2[0])) return m1 + m2 + m3 elif c_down == "U": # White down -> just move it on side csx = get_color_from_state_str(state_str, pos, x * X) csz = get_color_from_state_str(state_str, pos, z * Z) # print("Down - down", csx, csz) m1, m3 = _get_up_turn(get_normal(csx), z * Z) # Rotate around x f = get_face_for_normal(x * X) m2 = [f, "D'", neg_move(f)] if x * z > 0: m2 = [neg_move(m) for m in m2] # print(m1 + m2 + m3) return m1 + m2 + m3 # Up corners -> move it down for x in (-1, 1): for z in (-1, 1): pos = np.array([x, 1, z]) cu = get_color_from_state_str(state_str, pos, Y) csx = get_color_from_state_str(state_str, pos, x * X) csz = get_color_from_state_str(state_str, pos, z * Z) if cu != "U" or csx != get_face_for_normal( x * X) or csz != get_face_for_normal(z * Z): # print("Corner wrongly oriented") n = x * X if csx == "U" else z * Z v = z * Z if csx == "U" else x * X m2 = [ get_face_for_normal(n), "D", neg_move(get_face_for_normal(n)) ] to_right = np.cross(v, n)[1] > 0 if to_right: m2 = [neg_move(m) for m in m2] return m2 raise ValueError("No move found: is white face finished?")
def second_crown(state_str): for x, z, n in [(-1, 0, -X), (1, 0, X), (0, -1, -Z), (0, 1, Z)]: pos = np.array([x, -1, z]) c_side = get_color_from_state_str(state_str, pos, n) c_down = get_color_from_state_str(state_str, pos, -Y) if "D" not in [c_side, c_down]: # Down edges # print("Down edge", c_down, c_side) # Move in front of c_side color theta = angle(n, get_normal(c_side), ignore_axis=1) theta = (np.round(theta / np.pi * 2) * 90) % 360 m1 = [] if theta == 90: m1 = ["D'"] elif theta == 180: m1 = ["D2"] if theta == 270: m1 = ["D"] # is_right if c_down is to its right to_left = np.cross(get_normal(c_side), get_normal(c_down))[1] > 0 moves = BASE_SECOND_CROWN_MOVE if not to_left: # print("to_right") moves = flip_left_right(moves) moves = rotate_moves_about_y(moves, get_normal(c_side)) return m1 + moves for x in (-1, 1): for z in (-1, 1): pos = np.array([x, 0, z]) cs1 = get_color_from_state_str(state_str, pos, x * X) cs2 = get_color_from_state_str(state_str, pos, z * Z) if cs1 != get_face_for_normal(x * X) or cs2 != get_face_for_normal( z * Z): # Wrong place / orientation # print("Edge misplaced", cs1, cs2, x, z) if x * z == -1: rotate_about = x * X else: rotate_about = z * Z moves = rotate_moves_about_y(BASE_SECOND_CROWN_MOVE, rotate_about) return moves raise ValueError("No edge for second crown, is second crown done?")
def test_get_normal(): assert np.array_equal(get_normal("F"), Z) assert np.array_equal(get_normal("B"), -Z) assert np.array_equal(get_normal("R"), X) assert np.array_equal(get_normal("L"), -X) assert np.array_equal(get_normal("U"), Y) assert np.array_equal(get_normal("D"), -Y)
def is_yellow_corners_positioned(state_str): side_faces = ["F", "R", "B", "L"] for i, f in enumerate(side_faces): n = get_normal(f) m = np.cross(Y, n) pos = -Y + n + m corner = { get_color_from_state_str(state_str, pos, -Y), get_color_from_state_str(state_str, pos, n), get_color_from_state_str(state_str, pos, m) } aim_corner = {"D", side_faces[i], side_faces[(i + 1) % 4]} if aim_corner != corner: return False return True
def _animate(self, dt): # Needs to be called for each frame to run animations if self._animation.empty(): return anim = self._animation.peek() # Get current face animation if anim.current_angle == 0: # If first frame cube_ids = get_cube_ids_on_face(anim.face) anim.cubes = self.cubes[cube_ids] self.state.move(anim.move) speed_deg = dt * anim.DEG_PER_SEC / 1000 if anim.current_angle >= anim.target_angle - speed_deg: self._finish_animation() return speed_rad = np.radians(speed_deg) delta = -speed_rad if not anim.reverse else speed_rad rot_vec = delta * get_normal(anim.face) rot = Rotation.from_rotvec(rot_vec) for cube in anim.cubes: cube.rotate(rot) anim.current_angle += speed_deg
def generate_cubes_from_state_str(state_str, check=False): if check: try: kociemba.solve(state_str) except ValueError as e: raise ValueError(f"Invalid state string {state_str}") base = np.eye(3) cubes = [None] * 26 i = 0 for x in (-1, 0, 1): for y in (-1, 0, 1): for z in (-1, 0, 1): if x == y == z == 0: # Skip center cube continue pos = np.array([x, y, z]) colors = [False] * 6 basis = [None] * 3 for axis in np.where(pos != 0)[0]: color = get_color_from_state_str(state_str, pos, pos[axis] * base[axis]) norm = get_normal(color) basis[axis] = norm * pos[axis] colors[FACE_ORDER.index(color)] = True rot = get_rot_from_basis(basis) original_pos = rot.apply(pos) ox, oy, oz = original_pos id = 9 * (ox + 1) + 3 * (oy + 1) + (oz + 1) index = 9 * (x + 1) + 3 * (y + 1) + (z + 1) if index > 13: index = index - 1 if id > 13: id = id - 1 cube = Cube(initial_rotation=rot.inv(), colors=colors, id=id) cubes[index] = cube i += 1 return np.array(cubes)
def get_color_on_face(self, face): normal = get_normal(face) from_normal = self.rotation.inv().apply(normal) return get_face_for_normal(from_normal)