Download code/src/stackcraft/engine.py from nima1/stackcraft-clef-flash-lora: direct link, hf CLI and curl.
- Browser
- Download file 3.32 kB
-
https://huggingface.co/nima1/stackcraft-clef-flash-lora/resolve/main/code/src/stackcraft/engine.py
- Command line
-
hf download hf://nima1/stackcraft-clef-flash-lora/code/src/stackcraft/engine.py
-
curl -L -o engine.py https://huggingface.co/nima1/stackcraft-clef-flash-lora/resolve/main/code/src/stackcraft/engine.py
3.32 kB
| """Pure, deterministic vertical-placement falling-block game rules.""" | |
| from dataclasses import replace | |
| from stackcraft.pieces import piece_at, rotations | |
| from stackcraft.schema import COLORS, HEIGHT, WIDTH, Board, Cells, GameState, Placement, Transition | |
| _SCORES = (0, 100, 300, 500, 800) | |
| def new_game(seed: int) -> GameState: | |
| return GameState( | |
| board=tuple((0,) * WIDTH for _ in range(HEIGHT)), | |
| seed=seed, | |
| piece_index=0, | |
| current=piece_at(seed, 0), | |
| next_piece=piece_at(seed, 1), | |
| ) | |
| def _fits(state: GameState, cells: Cells, x: int, y: int) -> bool: | |
| return all( | |
| 0 <= x + dx < WIDTH and 0 <= y + dy < HEIGHT and state.board[y + dy][x + dx] == 0 | |
| for dx, dy in cells | |
| ) | |
| def legal_actions(state: GameState) -> tuple[Placement, ...]: | |
| """Enumerate rotation then column; each move starts fully inside row zero.""" | |
| if state.terminal: | |
| return () | |
| result = [] | |
| for rotation, cells in enumerate(rotations(state.current)): | |
| width = max(x for x, _ in cells) + 1 | |
| for x in range(WIDTH - width + 1): | |
| if not _fits(state, cells, x, 0): | |
| continue | |
| y = 0 | |
| while _fits(state, cells, x, y + 1): | |
| y += 1 | |
| absolute = tuple((x + dx, y + dy) for dx, dy in cells) | |
| result.append(Placement(f"r{rotation}x{x}", rotation, x, y, absolute)) | |
| return tuple(result) | |
| def place(board: Board, piece: str, action: Placement) -> tuple[Board, int]: | |
| """Place a previously validated action without reading any future pieces. | |
| Callers must supply an action from legal_actions for this board and piece. | |
| This low-level helper checks occupied/boundary cells but not hard-drop paths; | |
| user-controlled IDs must go through step instead. | |
| """ | |
| if piece not in COLORS: | |
| raise ValueError(f"unknown piece: {piece!r}") | |
| if len(set(action.cells)) != 4 or any( | |
| not (0 <= x < WIDTH and 0 <= y < HEIGHT) or board[y][x] for x, y in action.cells | |
| ): | |
| raise ValueError("placement cells must be four distinct empty in-bounds cells") | |
| rows = [list(row) for row in board] | |
| for x, y in action.cells: | |
| rows[y][x] = COLORS[piece] | |
| remaining = [tuple(row) for row in rows if not all(row)] | |
| cleared = HEIGHT - len(remaining) | |
| if cleared > 4: | |
| raise ValueError("invalid starting board: more than four completed rows") | |
| return tuple([(0,) * WIDTH] * cleared + remaining), cleared | |
| def step(state: GameState, action_id: str) -> Transition: | |
| """Validate, place, clear simultaneously, advance, and detect next top-out.""" | |
| action = next((move for move in legal_actions(state) if move.id == action_id), None) | |
| if action is None: | |
| raise ValueError(f"illegal action {action_id!r}") | |
| board, cleared = place(state.board, state.current, action) | |
| next_state = GameState( | |
| board=board, | |
| seed=state.seed, | |
| piece_index=state.piece_index + 1, | |
| current=state.next_piece, | |
| next_piece=piece_at(state.seed, state.piece_index + 2), | |
| score=state.score + _SCORES[cleared], | |
| lines=state.lines + cleared, | |
| ) | |
| if not legal_actions(next_state): | |
| next_state = replace(next_state, terminal=True) | |
| return Transition(next_state, action, cleared) | |