Spaces:
Runtime error
Runtime error
Commit ·
cc37031
1
Parent(s): 6b5cb90
feat(cp6): Scenario.build_level takes difficulty + record_focal_move/safety_distance defaults
Browse files
proteus/grid/game.py
CHANGED
|
@@ -100,7 +100,7 @@ class MotiveGridGame(ARCBaseGame):
|
|
| 100 |
difficulty: Session difficulty (passed through to the scenario).
|
| 101 |
max_steps: Survival budget; surviving this many played turns wins.
|
| 102 |
"""
|
| 103 |
-
level = scenario.build_level(rng)
|
| 104 |
width, height = scenario.grid_size
|
| 105 |
camera = Camera(width=width, height=height)
|
| 106 |
|
|
|
|
| 100 |
difficulty: Session difficulty (passed through to the scenario).
|
| 101 |
max_steps: Survival budget; surviving this many played turns wins.
|
| 102 |
"""
|
| 103 |
+
level = scenario.build_level(rng, difficulty)
|
| 104 |
width, height = scenario.grid_size
|
| 105 |
camera = Camera(width=width, height=height)
|
| 106 |
|
proteus/grid/scenario.py
CHANGED
|
@@ -39,6 +39,7 @@ if TYPE_CHECKING:
|
|
| 39 |
# is injected into MotiveGridGame, and the game reads it back here only
|
| 40 |
# for annotations.
|
| 41 |
from .game import MotiveGridGame
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
class Scenario(ABC):
|
|
@@ -63,19 +64,18 @@ class Scenario(ABC):
|
|
| 63 |
rules_text: str
|
| 64 |
|
| 65 |
@abstractmethod
|
| 66 |
-
def build_level(self, rng: random.Random) -> Level:
|
| 67 |
-
"""Build the arc_grid level: walls
|
| 68 |
|
| 69 |
-
All randomness
|
| 70 |
-
|
| 71 |
|
| 72 |
Args:
|
| 73 |
rng: Seeded RNG owned by the game/module.
|
|
|
|
| 74 |
|
| 75 |
Returns:
|
| 76 |
-
A freshly built :class:`~proteus.arc_grid.Level`
|
| 77 |
-
focal sprite (``name="focal"``), the predator sprite
|
| 78 |
-
(``name="predator"``), and any walls / NPCs.
|
| 79 |
"""
|
| 80 |
raise NotImplementedError
|
| 81 |
|
|
@@ -175,6 +175,23 @@ class Scenario(ABC):
|
|
| 175 |
"""
|
| 176 |
raise NotImplementedError
|
| 177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
# --------------------------------------------------------------------------- #
|
| 180 |
# Scenario registry
|
|
|
|
| 39 |
# is injected into MotiveGridGame, and the game reads it back here only
|
| 40 |
# for annotations.
|
| 41 |
from .game import MotiveGridGame
|
| 42 |
+
from .difficulty import Difficulty
|
| 43 |
|
| 44 |
|
| 45 |
class Scenario(ABC):
|
|
|
|
| 64 |
rules_text: str
|
| 65 |
|
| 66 |
@abstractmethod
|
| 67 |
+
def build_level(self, rng: random.Random, difficulty: Difficulty) -> Level:
|
| 68 |
+
"""Build the arc_grid level for *difficulty*: walls + initial sprites.
|
| 69 |
|
| 70 |
+
All randomness must flow from *rng* so that a given
|
| 71 |
+
``(scenario, seed, difficulty)`` is fully deterministic.
|
| 72 |
|
| 73 |
Args:
|
| 74 |
rng: Seeded RNG owned by the game/module.
|
| 75 |
+
difficulty: The session difficulty band (a ``Difficulty``).
|
| 76 |
|
| 77 |
Returns:
|
| 78 |
+
A freshly built :class:`~proteus.arc_grid.Level`.
|
|
|
|
|
|
|
| 79 |
"""
|
| 80 |
raise NotImplementedError
|
| 81 |
|
|
|
|
| 175 |
"""
|
| 176 |
raise NotImplementedError
|
| 177 |
|
| 178 |
+
def record_focal_move(self, action: str) -> None:
|
| 179 |
+
"""Record the focal's last committed move (no-op by default).
|
| 180 |
+
|
| 181 |
+
Scenarios whose ``habit_action`` tracks the live trajectory override
|
| 182 |
+
this; others (and future scenarios) inherit a safe no-op so callers
|
| 183 |
+
like ``SessionRunner`` and ``viz.reconstruct`` never hit AttributeError.
|
| 184 |
+
"""
|
| 185 |
+
|
| 186 |
+
def safety_distance(self, game: MotiveGridGame) -> int | None:
|
| 187 |
+
"""Return the focal's 'safety' distance for this motive (None by default).
|
| 188 |
+
|
| 189 |
+
Survival-style scenarios return the BFS distance from the focal to the
|
| 190 |
+
threat (larger = safer). Non-survival scenarios leave it ``None`` so the
|
| 191 |
+
trajectory final-distance metric is simply skipped for them.
|
| 192 |
+
"""
|
| 193 |
+
return None
|
| 194 |
+
|
| 195 |
|
| 196 |
# --------------------------------------------------------------------------- #
|
| 197 |
# Scenario registry
|
proteus/grid/scenarios/predator_evade.py
CHANGED
|
@@ -158,23 +158,27 @@ class PredatorEvade(Scenario):
|
|
| 158 |
# ------------------------------------------------------------------ #
|
| 159 |
# World construction
|
| 160 |
# ------------------------------------------------------------------ #
|
| 161 |
-
def build_level(self, rng: random.Random) -> Level:
|
| 162 |
-
"""Build the
|
| 163 |
|
| 164 |
The layout is deterministic (fixed coordinates), so *rng* is accepted
|
| 165 |
for interface conformance and forward compatibility (harder difficulties
|
| 166 |
may randomise wall placement) but is not consumed here. ``_wall_cells``
|
| 167 |
is refreshed so the BFS helpers stay in sync with the built level.
|
|
|
|
|
|
|
| 168 |
|
| 169 |
Args:
|
| 170 |
rng: Seeded RNG owned by the game/module (unused at EASY).
|
|
|
|
| 171 |
|
| 172 |
Returns:
|
| 173 |
-
A :class:`~
|
| 174 |
(``name="focal"``), predator (``name="predator"``), and the wall
|
| 175 |
sprites (tagged ``sys_static`` so the engine merges them).
|
| 176 |
"""
|
| 177 |
-
del rng # deterministic
|
|
|
|
| 178 |
|
| 179 |
self._wall_cells = frozenset(_WALL_CELLS)
|
| 180 |
|
|
|
|
| 158 |
# ------------------------------------------------------------------ #
|
| 159 |
# World construction
|
| 160 |
# ------------------------------------------------------------------ #
|
| 161 |
+
def build_level(self, rng: random.Random, difficulty: Difficulty) -> Level:
|
| 162 |
+
"""Build the level for *difficulty*: focal, predator, walls.
|
| 163 |
|
| 164 |
The layout is deterministic (fixed coordinates), so *rng* is accepted
|
| 165 |
for interface conformance and forward compatibility (harder difficulties
|
| 166 |
may randomise wall placement) but is not consumed here. ``_wall_cells``
|
| 167 |
is refreshed so the BFS helpers stay in sync with the built level.
|
| 168 |
+
Per-difficulty dispatch is added in Task 2; currently all difficulties
|
| 169 |
+
produce the EASY layout unchanged.
|
| 170 |
|
| 171 |
Args:
|
| 172 |
rng: Seeded RNG owned by the game/module (unused at EASY).
|
| 173 |
+
difficulty: The session difficulty band (a ``Difficulty``).
|
| 174 |
|
| 175 |
Returns:
|
| 176 |
+
A :class:`~proteus.arc_grid.Level` with the focal
|
| 177 |
(``name="focal"``), predator (``name="predator"``), and the wall
|
| 178 |
sprites (tagged ``sys_static`` so the engine merges them).
|
| 179 |
"""
|
| 180 |
+
del rng # deterministic layout; rng reserved for tie-breaks
|
| 181 |
+
del difficulty # per-difficulty dispatch added in Task 2
|
| 182 |
|
| 183 |
self._wall_cells = frozenset(_WALL_CELLS)
|
| 184 |
|
tests/grid/test_difficulty_layouts.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
|
| 3 |
+
from proteus.grid.difficulty import Difficulty
|
| 4 |
+
from proteus.grid.scenario import Scenario, get_scenario
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def _scenario():
|
| 8 |
+
return get_scenario("predator_evade")()
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_build_level_accepts_difficulty():
|
| 12 |
+
s = _scenario()
|
| 13 |
+
level = s.build_level(random.Random(42), Difficulty.EASY)
|
| 14 |
+
names = {sp.name for sp in level.get_sprites()}
|
| 15 |
+
assert "focal" in names and "predator" in names
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_record_focal_move_default_is_noop_on_base():
|
| 19 |
+
# hasattr proves the no-op default is now inherited from the Scenario ABC
|
| 20 |
+
# (so non-tracking scenarios never AttributeError). PredatorEvade overrides
|
| 21 |
+
# it to track the live trajectory, which habit_action reads back.
|
| 22 |
+
assert hasattr(Scenario, "record_focal_move")
|
| 23 |
+
# PredatorEvade still tracks it (used by habit_action).
|
| 24 |
+
s = _scenario()
|
| 25 |
+
s.record_focal_move("up")
|
| 26 |
+
assert s.habit_action(None) == "up"
|