Download data/mock_env.py from tchbcb/qwenjev: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/tchbcb/qwenjev/resolve/main/data/mock_env.py
- Command line
-
hf download hf://tchbcb/qwenjev/data/mock_env.py
-
curl -L -o mock_env.py https://huggingface.co/tchbcb/qwenjev/resolve/main/data/mock_env.py
11.5 kB
| """Built-in mock ARC-AGI-3 game: multi-level push-box ("sokoban-lite"). | |
| Purpose: the FULL QwenJev pipeline (collect -> build-data -> distill -> | |
| train-s1 -> eval) must run on any machine, including CPU-only laptops with | |
| no `arc-agi` package and no game downloads. `MockGame` reproduces the parts | |
| of the official environment contract that the pipeline touches: | |
| reset() / step(action) / observe() | |
| FrameData-like fields: grids (<=64x64, values 0..15), | |
| levels_completed, win_levels, available_actions, state | |
| actions: RESET, ACTION1..ACTION5, ACTION6(x,y), ACTION7 (undo) | |
| Mechanics (sparse-reward, locality-sensitive - good training signal): | |
| * grid values: 0 floor, 1 wall, 2 player, 3 box, 4 goal; | |
| * ACTION1..4 move the player (up/down/left/right); walking into a box | |
| pushes it one cell if the far side is free; | |
| * a level is complete when every box sits on a goal -> levels_completed+1 | |
| and a NEW (larger, more boxes) level is generated; | |
| * ACTION5 waits (no-op), ACTION6(x,y) steps onto an adjacent walkable cell, | |
| ACTION7 undoes up to 8 recent frames; | |
| * running out of a per-level action budget -> GAME_OVER; | |
| * completing `win_levels` levels -> WIN. | |
| Deterministic under `seed`; solvability approximated with BFS reachability | |
| from player to each box and from each box to a goal (walls only). | |
| """ | |
| from __future__ import annotations | |
| import random | |
| from dataclasses import dataclass, field | |
| from typing import Sequence | |
| GAME_ID = "mock-pushbox" | |
| # cell values (ARC palette indices; 0..15 legal, we use 0,1,2,3,4) | |
| FLOOR, WALL, PLAYER, BOX, GOAL = 0, 1, 2, 3, 4 | |
| DIRS = { | |
| "ACTION1": (-1, 0), # up | |
| "ACTION2": (1, 0), # down | |
| "ACTION3": (0, -1), # left | |
| "ACTION4": (0, 1), # right | |
| } | |
| ALL_ACTIONS = ( | |
| "RESET", "ACTION1", "ACTION2", "ACTION3", "ACTION4", | |
| "ACTION5", "ACTION6", "ACTION7", | |
| ) | |
| UNDO_DEPTH = 8 | |
| class MockFrame: | |
| """FrameData-compatible observation.""" | |
| grids: list[list[list[int]]] | |
| levels_completed: int = 0 | |
| win_levels: int = 3 | |
| available_actions: list[str] = field(default_factory=lambda: list(ALL_ACTIONS)) | |
| state: str = "NOT_PLAYED" # NOT_PLAYED | NOT_FINISHED | WIN | GAME_OVER | |
| class _Level: | |
| grid: list[list[int]] | |
| boxes: list[tuple[int, int]] | |
| goals: list[tuple[int, int]] | |
| player: tuple[int, int] | |
| budget: int | |
| steps_used: int = 0 | |
| class MockGame: | |
| """One game instance. `step` accepts an action NAME (str) - the mock has | |
| no dependency on the official `arcengine` enums.""" | |
| def __init__(self, seed: int = 0, win_levels: int = 3, base_size: int = 10): | |
| self.rng = random.Random(seed) | |
| self.win_levels = win_levels | |
| self.base_size = base_size | |
| self.levels_completed = 0 | |
| self.state = "NOT_PLAYED" | |
| self._level: _Level | None = None | |
| self._undo: list[list[list[int]]] = [] | |
| self._last_frame: MockFrame | None = None | |
| # ------------------------------------------------------------------ # | |
| # level generation # | |
| # ------------------------------------------------------------------ # | |
| def _gen_level(self, index: int) -> _Level: | |
| rng = self.rng | |
| size = min(64, self.base_size + 2 * index) # grow with levels | |
| n_boxes = min(3, 1 + index // 2) | |
| wall_ratio = 0.10 | |
| grid = [[FLOOR] * size for _ in range(size)] | |
| for i in range(size): # border walls | |
| grid[0][i] = grid[size - 1][i] = WALL | |
| grid[i][0] = grid[i][size - 1] = WALL | |
| inner = [(y, x) for y in range(1, size - 1) | |
| for x in range(1, size - 1)] | |
| for (y, x) in rng.sample(inner, int(len(inner) * wall_ratio)): | |
| grid[y][x] = WALL | |
| free = [(y, x) for (y, x) in inner if grid[y][x] == FLOOR] | |
| rng.shuffle(free) | |
| def far(cell, others, min_dist): | |
| for (y, x) in others: | |
| if abs(y - cell[0]) + abs(x - cell[1]) < min_dist: | |
| return False | |
| return True | |
| player = free.pop() | |
| boxes, goals = [], [] | |
| tries = 0 | |
| while len(boxes) < n_boxes and free and tries < 200: | |
| tries += 1 | |
| b = free.pop() | |
| if not far(b, [player], 3): | |
| continue | |
| if not far(b, boxes + goals, 2): | |
| continue | |
| g = free.pop() | |
| if not far(g, boxes + goals, 2) or g == b: | |
| continue | |
| if not (self._reachable(grid, player, b) | |
| and self._reachable(grid, b, g)): | |
| continue | |
| boxes.append(b) | |
| goals.append(g) | |
| if not boxes: # degenerate: retry | |
| return self._gen_level(index) | |
| for (y, x) in boxes: | |
| grid[y][x] = BOX | |
| for (y, x) in goals: | |
| if grid[y][x] == FLOOR: | |
| grid[y][x] = GOAL | |
| grid[player[0]][player[1]] = PLAYER | |
| return _Level( | |
| grid=grid, boxes=boxes, goals=goals, player=player, | |
| budget=6 * size * size // max(1, n_boxes), | |
| ) | |
| def _reachable(grid, src, dst) -> bool: | |
| """BFS on floor cells (walls only) - walls-only solvability proxy.""" | |
| size = len(grid) | |
| seen = {src} | |
| queue = [src] | |
| while queue: | |
| y, x = queue.pop() | |
| if (y, x) == dst: | |
| return True | |
| for dy, dx in ((-1, 0), (1, 0), (0, -1), (0, 1)): | |
| ny, nx = y + dy, x + dx | |
| if (0 <= ny < size and 0 <= nx < size | |
| and grid[ny][nx] != WALL and (ny, nx) not in seen): | |
| seen.add((ny, nx)) | |
| queue.append((ny, nx)) | |
| return False | |
| # ------------------------------------------------------------------ # | |
| # environment contract # | |
| # ------------------------------------------------------------------ # | |
| def observe(self) -> MockFrame: | |
| if self._level is None: | |
| return MockFrame(grids=[[[FLOOR]]], | |
| levels_completed=self.levels_completed, | |
| win_levels=self.win_levels, | |
| state=self.state) | |
| return MockFrame( | |
| grids=[[list(row) for row in self._level.grid]], | |
| levels_completed=self.levels_completed, | |
| win_levels=self.win_levels, | |
| available_actions=list(ALL_ACTIONS), | |
| state=self.state, | |
| ) | |
| def reset(self) -> MockFrame: | |
| self.levels_completed = 0 | |
| self.state = "NOT_FINISHED" | |
| self._level = self._gen_level(0) | |
| self._undo = [] | |
| self._last_frame = self.observe() | |
| return self._last_frame | |
| def step(self, action: str, x: int | None = None, y: int | None = None) -> MockFrame: | |
| if self.state in ("WIN", "GAME_OVER"): | |
| return self.observe() | |
| if action == "RESET": | |
| return self.reset() | |
| lvl = self._level | |
| self._undo.append([list(row) for row in lvl.grid]) | |
| if len(self._undo) > UNDO_DEPTH: | |
| self._undo.pop(0) | |
| moved = False | |
| if action in DIRS: | |
| moved = self._move(lvl, *DIRS[action]) | |
| elif action == "ACTION5": | |
| moved = False # explicit no-op | |
| elif action == "ACTION6": | |
| moved = self._click(lvl, x, y) | |
| elif action == "ACTION7": | |
| if self._undo: | |
| lvl.grid = self._undo.pop() # restore state | |
| self._sync_entities(lvl) | |
| moved = True | |
| # unknown action names: treated as no-op (frame unchanged) | |
| lvl.steps_used += 1 | |
| if self._all_boxes_on_goals(lvl): | |
| self.levels_completed += 1 | |
| if self.levels_completed >= self.win_levels: | |
| self.state = "WIN" | |
| else: | |
| self._level = self._gen_level(self.levels_completed) | |
| self._undo = [] | |
| elif lvl.steps_used >= lvl.budget: | |
| self.state = "GAME_OVER" | |
| self._last_frame = self.observe() | |
| return self._last_frame | |
| # ------------------------------------------------------------------ # | |
| # mechanics # | |
| # ------------------------------------------------------------------ # | |
| def _move(self, lvl: _Level, dy: int, dx: int) -> bool: | |
| size = len(lvl.grid) | |
| py, px = lvl.player | |
| ny, nx = py + dy, px + dx | |
| if not (0 <= ny < size and 0 <= nx < size) or lvl.grid[ny][nx] == WALL: | |
| return False | |
| if lvl.grid[ny][nx] == BOX: | |
| by, bx = ny + dy, nx + dx | |
| if not (0 <= by < size and 0 <= bx < size): | |
| return False | |
| if lvl.grid[by][bx] in (WALL, BOX): | |
| return False | |
| lvl.grid[by][bx] = BOX | |
| lvl.boxes[lvl.boxes.index((ny, nx))] = (by, bx) | |
| lvl.grid[py][px] = FLOOR | |
| lvl.grid[ny][nx] = PLAYER | |
| lvl.player = (ny, nx) | |
| return True | |
| def _click(self, lvl: _Level, x: int | None, y: int | None) -> bool: | |
| """ACTION6(x, y): one step toward an adjacent walkable cell.""" | |
| if x is None or y is None: | |
| return False | |
| size = len(lvl.grid) | |
| if not (0 <= y < size and 0 <= x < size): | |
| return False | |
| py, px = lvl.player | |
| if abs(y - py) + abs(x - px) != 1: | |
| return False | |
| if lvl.grid[y][x] in (WALL, BOX): | |
| return False | |
| lvl.grid[py][px] = FLOOR | |
| lvl.grid[y][x] = PLAYER | |
| lvl.player = (y, x) | |
| return True | |
| def _sync_entities(lvl: _Level) -> None: | |
| """Rebuild player/box coordinates after an undo (grid is truth).""" | |
| lvl.boxes = [] | |
| for y, row in enumerate(lvl.grid): | |
| for x, v in enumerate(row): | |
| if v == PLAYER: | |
| lvl.player = (y, x) | |
| lvl.grid[y][x] = FLOOR # re-stamp below | |
| elif v == BOX: | |
| lvl.boxes.append((y, x)) | |
| y, x = lvl.player | |
| lvl.grid[y][x] = PLAYER | |
| def _all_boxes_on_goals(lvl: _Level) -> bool: | |
| goal_set = set(lvl.goals) | |
| return all(b in goal_set for b in lvl.boxes) | |
| # --------------------------------------------------------------------- # | |
| # adapter used by collect / eval # | |
| # --------------------------------------------------------------------- # | |
| def make_mock_env(seed: int = 0, win_levels: int = 3) -> MockGame: | |
| return MockGame(seed=seed, win_levels=win_levels) | |
| def step_env(env, action_name: str, x: int | None = None, y: int | None = None): | |
| """Dispatch one action to either a MockGame (name-based) or the official | |
| offline engine (GameAction enum). Keeps callers engine-agnostic.""" | |
| if isinstance(env, MockGame): | |
| return env.step(action_name, x=x, y=y) | |
| from arcengine import GameAction # official toolkit | |
| action = GameAction.from_name(action_name) | |
| if x is not None and y is not None and action.is_complex(): | |
| # official signature: step(action, data={"x": .., "y": ..}) | |
| return env.step(action, data={"x": int(x), "y": int(y)}) | |
| return env.step(action) | |