"""ZeroGPU neural-Q-learning demo for solving 2x2x2 Rubik's Cube states.""" from __future__ import annotations import random from typing import Iterable, List, Tuple import gradio as gr Vec = Tuple[int, int, int] State = Tuple[int, ...] FACE_NAMES = ("U", "R", "F", "D", "L", "B") FACE_COLORS = ("#f5f5f5", "#dc2626", "#16a34a", "#facc15", "#f97316", "#2563eb") MOVES = tuple(move for face in FACE_NAMES for move in (face, f"{face}'")) SOLVED: State = tuple(color for color in range(6) for _ in range(4)) # Each face defines its outward normal plus its screen-right and screen-down axes. FACE_GEOMETRY: Tuple[Tuple[Vec, Vec, Vec], ...] = ( ((0, 1, 0), (1, 0, 0), (0, 0, 1)), # U ((1, 0, 0), (0, 0, -1), (0, -1, 0)), # R ((0, 0, 1), (1, 0, 0), (0, -1, 0)), # F ((0, -1, 0), (1, 0, 0), (0, 0, -1)), # D ((-1, 0, 0), (0, 0, 1), (0, -1, 0)), # L ((0, 0, -1), (-1, 0, 0), (0, -1, 0)), # B ) def add(*vectors: Vec) -> Vec: return tuple(sum(vector[i] for vector in vectors) for i in range(3)) # type: ignore[return-value] def scale(vector: Vec, amount: int) -> Vec: return tuple(amount * part for part in vector) # type: ignore[return-value] def dot(left: Vec, right: Vec) -> int: return sum(a * b for a, b in zip(left, right)) def cross(left: Vec, right: Vec) -> Vec: return ( left[1] * right[2] - left[2] * right[1], left[2] * right[0] - left[0] * right[2], left[0] * right[1] - left[1] * right[0], ) def rotate_quarter(vector: Vec, axis: Vec, direction: int) -> Vec: """Rotate a grid vector 90 degrees about `axis` using right-hand direction.""" parallel = scale(axis, dot(axis, vector)) perpendicular = cross(axis, vector) if direction == 1 else cross(vector, axis) return add(parallel, perpendicular) def sticker_keys() -> List[Tuple[Vec, Vec]]: keys = [] for normal, right, down in FACE_GEOMETRY: for row in range(2): for col in range(2): position = add( normal, scale(right, -1 if col == 0 else 1), scale(down, -1 if row == 0 else 1), ) keys.append((normal, position)) return keys STICKER_KEYS = sticker_keys() KEY_TO_INDEX = {key: index for index, key in enumerate(STICKER_KEYS)} def build_permutation(face_index: int, direction: int) -> Tuple[int, ...]: axis = FACE_GEOMETRY[face_index][0] destination = list(range(24)) for source, (normal, position) in enumerate(STICKER_KEYS): if dot(position, axis) == 1: rotated = (rotate_quarter(normal, axis, direction), rotate_quarter(position, axis, direction)) destination[source] = KEY_TO_INDEX[rotated] return tuple(destination) PERMUTATIONS = {face: build_permutation(index, 1) for index, face in enumerate(FACE_NAMES)} PERMUTATIONS.update({f"{face}'": build_permutation(index, -1) for index, face in enumerate(FACE_NAMES)}) def apply_move(state: State, move: str) -> State: """Apply one legal face turn to a sticker-state representation.""" output = [0] * 24 for source, destination in enumerate(PERMUTATIONS[move]): output[destination] = state[source] return tuple(output) def apply_moves(state: State, moves: Iterable[str]) -> State: for move in moves: state = apply_move(state, move) return state def random_scramble(length: int, rng: random.Random) -> List[str]: scramble: List[str] = [] last_face = "" for _ in range(length): move = rng.choice([candidate for candidate in MOVES if candidate[0] != last_face]) scramble.append(move) last_face = move[0] return scramble def cube_preview(state: State) -> str: """Render a CSS-only, rotatable 3D cube preview for the supplied sequence.""" faces = [] for face_index, face in enumerate(FACE_NAMES): stickers = "".join( f"" for sticker in range(4) ) faces.append(f"