Spaces:
Runtime error
Runtime error
Commit ·
2aee482
1
Parent(s): 12a796d
feat(engine): footprint-aware focal bounds (1x1 unchanged)
Browse files- proteus/grid/game.py +15 -1
- tests/grid/test_footprint_bounds.py +34 -0
proteus/grid/game.py
CHANGED
|
@@ -158,7 +158,7 @@ class MotiveGridGame(ARCBaseGame):
|
|
| 158 |
if (
|
| 159 |
focal is not None
|
| 160 |
and (dx != 0 or dy != 0)
|
| 161 |
-
and self.
|
| 162 |
):
|
| 163 |
# arc_grid has no implicit world border: try_move_sprite reverts
|
| 164 |
# only on sprite collision, so the framework guards the grid edge
|
|
@@ -196,6 +196,20 @@ class MotiveGridGame(ARCBaseGame):
|
|
| 196 |
width, height = self.scenario.grid_size
|
| 197 |
return 0 <= x < width and 0 <= y < height
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
def get_sprite(self, name: str) -> Sprite | None:
|
| 200 |
"""Return the first sprite with *name*, or ``None`` if absent.
|
| 201 |
|
|
|
|
| 158 |
if (
|
| 159 |
focal is not None
|
| 160 |
and (dx != 0 or dy != 0)
|
| 161 |
+
and self._footprint_in_bounds(focal, dx, dy)
|
| 162 |
):
|
| 163 |
# arc_grid has no implicit world border: try_move_sprite reverts
|
| 164 |
# only on sprite collision, so the framework guards the grid edge
|
|
|
|
| 196 |
width, height = self.scenario.grid_size
|
| 197 |
return 0 <= x < width and 0 <= y < height
|
| 198 |
|
| 199 |
+
def _footprint_in_bounds(self, sprite, dx: int, dy: int) -> bool:
|
| 200 |
+
"""Return True if *sprite*'s full footprint stays on-grid after (dx, dy).
|
| 201 |
+
|
| 202 |
+
For a 1x1 sprite this is exactly ``within_bounds(x+dx, y+dy)`` (so the
|
| 203 |
+
single-cell scenarios are unaffected); for a multi-cell sprite it checks
|
| 204 |
+
every footprint cell.
|
| 205 |
+
"""
|
| 206 |
+
nx, ny = sprite.x + dx, sprite.y + dy
|
| 207 |
+
for j in range(sprite.height):
|
| 208 |
+
for i in range(sprite.width):
|
| 209 |
+
if not self.within_bounds(nx + i, ny + j):
|
| 210 |
+
return False
|
| 211 |
+
return True
|
| 212 |
+
|
| 213 |
def get_sprite(self, name: str) -> Sprite | None:
|
| 214 |
"""Return the first sprite with *name*, or ``None`` if absent.
|
| 215 |
|
tests/grid/test_footprint_bounds.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The multi-cell focal must keep its full footprint on the grid; a 1x1 sprite
|
| 2 |
+
(predator_evade) behaves exactly as before (regression guard)."""
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import random
|
| 6 |
+
|
| 7 |
+
import proteus.grid # noqa: F401
|
| 8 |
+
from proteus.grid.difficulty import Difficulty
|
| 9 |
+
from proteus.grid.game import MotiveGridGame
|
| 10 |
+
from proteus.grid.scenario import get_scenario
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def test_3x3_focal_cannot_leave_grid():
|
| 14 |
+
scenario = get_scenario("pack_evade")()
|
| 15 |
+
game = MotiveGridGame(scenario, random.Random(1), Difficulty.EASY, max_steps=99)
|
| 16 |
+
focal = game.focal_sprite
|
| 17 |
+
focal.move(-focal.x, -focal.y) # anchor (0,0); footprint x,y in [0,3)
|
| 18 |
+
game.apply_motive_action("left") # would push footprint to x=-1 -> rejected
|
| 19 |
+
assert game.focal_sprite.x == 0
|
| 20 |
+
game.apply_motive_action("up") # would push footprint to y=-1 -> rejected
|
| 21 |
+
assert game.focal_sprite.y == 0
|
| 22 |
+
# A legal move still works.
|
| 23 |
+
game.apply_motive_action("right")
|
| 24 |
+
assert game.focal_sprite.x == 1
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def test_1x1_focal_regression():
|
| 28 |
+
scenario = get_scenario("predator_evade")()
|
| 29 |
+
game = MotiveGridGame(scenario, random.Random(42), Difficulty.EASY, max_steps=99)
|
| 30 |
+
focal = game.focal_sprite
|
| 31 |
+
x0 = focal.x
|
| 32 |
+
game.apply_motive_action("up") # legal in the 8x8 layout
|
| 33 |
+
assert game.focal_sprite.y == focal.y # moved or not, no crash; 1x1 path unchanged
|
| 34 |
+
assert isinstance(x0, int)
|