Last active
February 19, 2026 05:46
-
-
Save dsetzer/50b226332d0efd93f97883e6c58670f5 to your computer and use it in GitHub Desktop.
Prototype for solving numbered cube by converting it's state into a standard rubik's revenge state (kociemba notation)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from enum import Enum | |
| from typing import List, Dict | |
| from cube_state import CubeState | |
| from collections import Counter | |
| import subprocess | |
| class TileType(Enum): | |
| CORNER = 'corner' | |
| EDGE_A = 'edge_a' | |
| EDGE_B = 'edge_b' | |
| CENTER = 'center' | |
| class CubeTile: | |
| TILE_TYPES = { | |
| TileType.CORNER: [1, 4, 13, 16], | |
| TileType.EDGE_A: [2, 8, 9, 15], | |
| TileType.EDGE_B: [3, 5, 12, 14], | |
| TileType.CENTER: [6, 7, 10, 11] | |
| } | |
| def __init__(self, number: int, current_position: int): | |
| self.number = number | |
| self.color = None | |
| self.tile_type = self._detect_tile_type(current_position) | |
| self.current_face = None | |
| self.current_position = current_position | |
| self.expected_face = None | |
| self.expected_position = number | |
| def _detect_tile_type(self, current_position: int) -> TileType: | |
| for tile_type, positions in self.TILE_TYPES.items(): | |
| if current_position in positions: | |
| return tile_type | |
| valid_positions = [pos for positions in self.TILE_TYPES.values() for pos in positions] | |
| raise ValueError(f"Invalid face position: {current_position}. Valid positions are: {valid_positions}") | |
| def __repr__(self): | |
| return f"Tile(Label {self.number}, Type {self.tile_type}, Color {self.color}, Face {self.current_face}, Pos {self.current_position}, Solved Face {self.expected_face}, Solved Pos {self.expected_position})" | |
| class CubeFace: | |
| COLOR_MAP = {'U': 'W', 'R': 'R', 'F': 'G', 'D': 'Y', 'L': 'O', 'B': 'B'} | |
| def __init__(self, side: str, numbered_state: List[int]): | |
| self.side = side | |
| self.color = self.COLOR_MAP[side] | |
| self.current_tiles: List[CubeTile] = [None] * 16 | |
| self.solved_tiles = [] | |
| self._initialize_tiles(numbered_state) | |
| def _initialize_tiles(self, numbered_state: List[int]): | |
| for current_position, number in enumerate(numbered_state, 1): | |
| tile = CubeTile(number, current_position) | |
| self.current_tiles[current_position - 1] = tile | |
| tile.current_face = self.side | |
| if number == current_position: | |
| tile.color = self.color | |
| tile.expected_face = self.side | |
| self.solved_tiles.append(tile) | |
| def reset_colors(self): | |
| self.solved_tiles = [ | |
| tile for tile in self.current_tiles | |
| if tile.number == tile.current_position | |
| ] | |
| for tile in self.current_tiles: | |
| tile.color = self.color if tile in self.solved_tiles else None | |
| def next_unsolved_number(self): | |
| solved_numbers = {tile.number for tile in self.solved_tiles} | |
| for number in range(1, 17): | |
| if number not in solved_numbers: | |
| return number | |
| def __repr__(self): | |
| return f"Face({self.side}, {self.color})" | |
| class Cube: | |
| KOCIEMBA_ORDER = ['U', 'R', 'F', 'D', 'L', 'B'] | |
| FACE_MAP = {'U': 1, 'R': 1, 'F': 2, 'D': 3, 'L': 4, 'B': 5} | |
| COLOR_MAP = {'U': 'W', 'R': 'R', 'F': 'G', 'D': 'Y', 'L': 'O', 'B': 'B'} | |
| def __init__(self, numbered_state: List[List[int]]): | |
| self.faces: Dict[str, CubeFace] = { | |
| side: CubeFace(side, numbered_state[face_index]) | |
| for face_index, side in enumerate(self.FACE_MAP.keys()) | |
| } | |
| def generate_standard_state(self) -> str: | |
| # Reset tile colors | |
| for face in self.faces.values(): | |
| face.reset_colors() | |
| solved_tile_count = sum(len(face.solved_tiles) for face in self.faces.values()) | |
| while solved_tile_count < 96: | |
| # Assign colors to the remaining tiles | |
| for face in self.faces.values(): | |
| next_unsolved_number = face.next_unsolved_number() | |
| if next_unsolved_number is None: | |
| continue | |
| elif next_unsolved_number <= len(face.current_tiles): | |
| other_tile = self.locate_tile_for_face(face, next_unsolved_number) | |
| if other_tile: | |
| # Check neighboring faces for color consistency | |
| neighboring_faces = self.get_neighboring_faces(face.side) | |
| for neighbor in neighboring_faces: | |
| neighbor_tile = self.locate_tile_for_face(neighbor, next_unsolved_number) | |
| if neighbor_tile and neighbor_tile.color: | |
| other_tile.color = neighbor_tile.color | |
| break | |
| else: | |
| other_tile.color = face.color | |
| other_tile.expected_face = face.side | |
| face.solved_tiles.append(other_tile) | |
| solved_tile_count += 1 | |
| print(f"Solved {solved_tile_count} tiles") | |
| # Generate the Kociemba state string | |
| kociemba_state = [] | |
| for face in self.KOCIEMBA_ORDER: | |
| solved_tiles = self.faces[face].solved_tiles | |
| solved_tiles.sort(key=lambda x: x.number) | |
| for tile in solved_tiles: | |
| kociemba_state.append(tile.current_face) | |
| return "".join(kociemba_state) | |
| def get_neighboring_faces(self, side: str) -> List[CubeFace]: | |
| neighbors = { | |
| 'U': ['F', 'R', 'B', 'L'], | |
| 'D': ['F', 'R', 'B', 'L'], | |
| 'F': ['U', 'R', 'D', 'L'], | |
| 'B': ['U', 'R', 'D', 'L'], | |
| 'L': ['U', 'F', 'D', 'B'], | |
| 'R': ['U', 'F', 'D', 'B'] | |
| } | |
| return [self.faces[neighbor] for neighbor in neighbors[side]] | |
| def locate_tile_for_face(self, face: CubeFace, current_position: int) -> CubeTile: | |
| for other_face in self.faces.values(): | |
| for other_tile in other_face.current_tiles: | |
| if other_tile.expected_face is not None: | |
| continue | |
| if other_tile.number == current_position and other_tile not in face.solved_tiles: | |
| return other_tile | |
| def locate_tiles_by_color(self, color: str): | |
| tile_faces = [] | |
| for face in self.faces.values(): | |
| for tile in face.current_tiles: | |
| if tile.color == color: | |
| tile_faces.append((tile.number, face.side)) | |
| return sorted(tile_faces, key=lambda x: x[0]) | |
| def print_cube_state(self): | |
| for side in self.KOCIEMBA_ORDER: | |
| face = self.faces[side] | |
| print(f"Face {face.side}:") | |
| for i, tile in enumerate(face.current_tiles, 1): | |
| print(tile.color, end=" ") | |
| if i % 4 == 0: | |
| print() | |
| print() | |
| cubeState = CubeState() | |
| print("\nScrambling the cube...") | |
| cubeState.scramble() | |
| cubeState.debug_info() | |
| # Example usage and testing | |
| example_cube_state = cubeState.get_state() | |
| cube = Cube(example_cube_state) | |
| result = cube.generate_standard_state() | |
| print("\nKociemba state:", result) | |
| # Print color distribution | |
| color_counts = Counter(result) | |
| print("\nColor counts:\n" + "\n".join([f"{color}: {count}" for color, count in color_counts.items()])) | |
| print("\nCube state visualization:") | |
| cube.print_cube_state() | |
| try: | |
| command = f'./rubiks-cube-solver.py --state {result}' | |
| process = subprocess.run(command, shell=True, capture_output=True, text=True) | |
| # Print the output | |
| print("\nCommand Output:") | |
| print(process.stdout) | |
| print("Command Error (if any):") | |
| print(process.stderr) | |
| except Exception as e: | |
| print("Error running command:", e) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment