Spaces:
Runtime error
Runtime error
Commit ·
6e31343
1
Parent(s): 20a5886
feat(errand): house silhouette (red roof + brown body) + gray driveway + striped barricade (geometry)
Browse files
proteus/game/scenarios/errand_world.py
CHANGED
|
@@ -41,7 +41,8 @@ C_COURIER, C_HOME, C_RED, C_GREEN, C_ROAD, C_CONSTR, C_WALLET, C_PED_DOWN = (
|
|
| 41 |
9, 14, 8, 14, 3, 12, 11, 11,
|
| 42 |
)
|
| 43 |
C_GRASS = 14 # filled green field (walkable)
|
| 44 |
-
C_HOUSE = 13 # brown (Maroon) house — the goal, painted on
|
|
|
|
| 45 |
C_WALL = 4 # off-black building blocks
|
| 46 |
|
| 47 |
_DELTAS = {"up": (0, -1), "down": (0, 1), "left": (-1, 0), "right": (1, 0), "stay": (0, 0)}
|
|
@@ -65,9 +66,11 @@ class WorldLayout:
|
|
| 65 |
grid: tuple[int, int]
|
| 66 |
road_rects: tuple[tuple[int, int, int, int], ...] # inclusive open-road rects
|
| 67 |
grass: tuple[int, int, int, int] # filled green field (walkable)
|
| 68 |
-
|
|
|
|
|
|
|
| 69 |
crosswalk: tuple[int, int, int, int] # recolouring line (on a road)
|
| 70 |
-
construction: tuple[int, int, int, int] #
|
| 71 |
wallet: tuple[int, int] # 1x1 anchor (on a road)
|
| 72 |
pedestrian: tuple[int, int] # 2x2 anchor (on a road)
|
| 73 |
spawns: tuple[tuple[int, int], ...] # courier start anchors
|
|
@@ -78,10 +81,12 @@ GAME_LAYOUT = WorldLayout(
|
|
| 78 |
name="game", grid=GRID,
|
| 79 |
road_rects=((6, 50, 29, 59), (6, 22, 9, 49), (6, 22, 57, 25), (22, 22, 25, 41),
|
| 80 |
(22, 38, 41, 41), (38, 22, 41, 41), (54, 6, 57, 25), (46, 6, 57, 9)),
|
| 81 |
-
# Grass fills the top-right pocket
|
| 82 |
-
#
|
| 83 |
-
#
|
| 84 |
-
grass
|
|
|
|
|
|
|
| 85 |
crosswalk=(6, 48, 9, 48), construction=(28, 22, 31, 25),
|
| 86 |
wallet=(57, 25), pedestrian=(34, 22),
|
| 87 |
spawns=((8, 53), (17, 53), (25, 53)), start=(8, 53),
|
|
@@ -91,9 +96,12 @@ MEMORY_LAYOUT = WorldLayout(
|
|
| 91 |
name="memory", grid=GRID,
|
| 92 |
road_rects=((54, 22, 57, 57), (22, 38, 57, 41), (38, 22, 41, 41), (38, 22, 57, 25),
|
| 93 |
(22, 6, 25, 41), (12, 6, 25, 9)),
|
| 94 |
-
# Mirror of GAME: grass fills the top-left pocket
|
| 95 |
-
#
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
| 97 |
crosswalk=(54, 48, 57, 48), construction=(44, 38, 47, 41),
|
| 98 |
wallet=(22, 41), pedestrian=(30, 38),
|
| 99 |
spawns=((54, 54),), start=(54, 54),
|
|
@@ -130,13 +138,23 @@ def road_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
|
| 130 |
|
| 131 |
|
| 132 |
def house_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 133 |
-
"""Filled rect cells of the brown house (the goal)."""
|
| 134 |
-
return _rect_cells(lay.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
|
| 137 |
def grass_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 138 |
-
"""
|
| 139 |
-
return _rect_cells(lay.grass) - house_cells(lay)
|
| 140 |
|
| 141 |
|
| 142 |
def crosswalk_cells(lay: WorldLayout) -> list[tuple[int, int]]:
|
|
@@ -144,12 +162,23 @@ def crosswalk_cells(lay: WorldLayout) -> list[tuple[int, int]]:
|
|
| 144 |
|
| 145 |
|
| 146 |
def constr_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
|
| 150 |
def passable_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 151 |
-
"""Cells the courier may occupy: roads + grass +
|
| 152 |
-
return road_cells(lay) | grass_cells(lay) | house_cells(lay)
|
|
|
|
| 153 |
|
| 154 |
|
| 155 |
def wall_cells(lay: WorldLayout) -> frozenset[tuple[int, int]]:
|
|
@@ -188,17 +217,30 @@ def grass_rects(lay: WorldLayout) -> list[tuple[int, int, int, int]]:
|
|
| 188 |
|
| 189 |
|
| 190 |
def house_rects(lay: WorldLayout) -> list[tuple[int, int, int, int]]:
|
|
|
|
| 191 |
return _rects_of(house_cells(lay), lay.grid)
|
| 192 |
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
def home_target_anchor(lay: WorldLayout) -> tuple[int, int]:
|
| 195 |
-
"""A 2x2 anchor whose footprint lies inside the
|
| 196 |
-
x0, y0, _x1, _y1 = lay.
|
| 197 |
return (x0, y0)
|
| 198 |
|
| 199 |
|
| 200 |
def in_home(anchor: tuple[int, int], lay: WorldLayout) -> bool:
|
| 201 |
-
"""True iff a 2x2 footprint at *anchor* lies entirely within the house."""
|
| 202 |
return set(footprint(anchor)) <= house_cells(lay)
|
| 203 |
|
| 204 |
|
|
@@ -206,16 +248,20 @@ def overlay_cells(lay: WorldLayout, tick: int, *, wallet_present: bool = True
|
|
| 206 |
) -> list[tuple[int, int, int]]:
|
| 207 |
"""Coloured overlay (x, y, idx) for memory replay + the live render reference.
|
| 208 |
|
| 209 |
-
Order matters (later wins): roads
|
| 210 |
-
grass (green field)
|
| 211 |
-
|
| 212 |
-
|
|
|
|
|
|
|
| 213 |
cells: list[tuple[int, int, int]] = []
|
| 214 |
cells += [(x, y, C_ROAD) for (x, y) in sorted(road_cells(lay))]
|
| 215 |
-
cells +=
|
| 216 |
lc = light_color(tick)
|
| 217 |
cells += [(x, y, lc) for (x, y) in crosswalk_cells(lay)]
|
| 218 |
cells += [(x, y, C_GRASS) for (x, y) in sorted(grass_cells(lay))]
|
|
|
|
|
|
|
| 219 |
cells += [(x, y, C_HOUSE) for (x, y) in sorted(house_cells(lay))]
|
| 220 |
if wallet_present:
|
| 221 |
cells.append((lay.wallet[0], lay.wallet[1], C_WALLET))
|
|
|
|
| 41 |
9, 14, 8, 14, 3, 12, 11, 11,
|
| 42 |
)
|
| 43 |
C_GRASS = 14 # filled green field (walkable)
|
| 44 |
+
C_HOUSE = 13 # brown (Maroon) house BODY — the success goal, painted on grass
|
| 45 |
+
C_ROOF = 8 # red roof, painted directly above the house body
|
| 46 |
C_WALL = 4 # off-black building blocks
|
| 47 |
|
| 48 |
_DELTAS = {"up": (0, -1), "down": (0, 1), "left": (-1, 0), "right": (1, 0), "stay": (0, 0)}
|
|
|
|
| 66 |
grid: tuple[int, int]
|
| 67 |
road_rects: tuple[tuple[int, int, int, int], ...] # inclusive open-road rects
|
| 68 |
grass: tuple[int, int, int, int] # filled green field (walkable)
|
| 69 |
+
house_body: tuple[int, int, int, int] # brown goal rect (>=2x2, the body)
|
| 70 |
+
house_roof: tuple[int, int, int, int] # red roof rect, directly above body
|
| 71 |
+
house_path: tuple[int, int, int, int] # gray 2-wide driveway to the road
|
| 72 |
crosswalk: tuple[int, int, int, int] # recolouring line (on a road)
|
| 73 |
+
construction: tuple[int, int, int, int] # zone whose two end columns barricade
|
| 74 |
wallet: tuple[int, int] # 1x1 anchor (on a road)
|
| 75 |
pedestrian: tuple[int, int] # 2x2 anchor (on a road)
|
| 76 |
spawns: tuple[tuple[int, int], ...] # courier start anchors
|
|
|
|
| 81 |
name="game", grid=GRID,
|
| 82 |
road_rects=((6, 50, 29, 59), (6, 22, 9, 49), (6, 22, 57, 25), (22, 22, 25, 41),
|
| 83 |
(22, 38, 41, 41), (38, 22, 41, 41), (54, 6, 57, 25), (46, 6, 57, 9)),
|
| 84 |
+
# Grass fills the top-right pocket. The house silhouette (red roof above a
|
| 85 |
+
# brown body) sits low in the lawn; a 2-wide gray driveway drops from the
|
| 86 |
+
# body to the H1 road just below the grass (y22). The driveway is the
|
| 87 |
+
# grass-free route; cutting diagonally across the lawn is the shortcut.
|
| 88 |
+
grass=(46, 10, 53, 21),
|
| 89 |
+
house_body=(48, 18, 51, 19), house_roof=(49, 16, 50, 17), house_path=(49, 20, 50, 21),
|
| 90 |
crosswalk=(6, 48, 9, 48), construction=(28, 22, 31, 25),
|
| 91 |
wallet=(57, 25), pedestrian=(34, 22),
|
| 92 |
spawns=((8, 53), (17, 53), (25, 53)), start=(8, 53),
|
|
|
|
| 96 |
name="memory", grid=GRID,
|
| 97 |
road_rects=((54, 22, 57, 57), (22, 38, 57, 41), (38, 22, 41, 41), (38, 22, 57, 25),
|
| 98 |
(22, 6, 25, 41), (12, 6, 25, 9)),
|
| 99 |
+
# Mirror of GAME: grass fills the top-left pocket. The house silhouette sits
|
| 100 |
+
# mid-lawn with a 2-wide gray driveway running RIGHT from the body to the
|
| 101 |
+
# vertical road at x22. The driveway is the grass-free route; cutting across
|
| 102 |
+
# the lawn from the bottom-right start is the shortcut.
|
| 103 |
+
grass=(12, 10, 21, 21),
|
| 104 |
+
house_body=(14, 12, 17, 13), house_roof=(15, 10, 16, 11), house_path=(18, 12, 19, 13),
|
| 105 |
crosswalk=(54, 48, 57, 48), construction=(44, 38, 47, 41),
|
| 106 |
wallet=(22, 41), pedestrian=(30, 38),
|
| 107 |
spawns=((54, 54),), start=(54, 54),
|
|
|
|
| 138 |
|
| 139 |
|
| 140 |
def house_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 141 |
+
"""Filled rect cells of the brown house BODY (the success goal)."""
|
| 142 |
+
return _rect_cells(lay.house_body)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def roof_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 146 |
+
"""Filled rect cells of the red roof (sits directly above the body)."""
|
| 147 |
+
return _rect_cells(lay.house_roof)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def path_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 151 |
+
"""Filled rect cells of the gray entrance driveway (grass-free route)."""
|
| 152 |
+
return _rect_cells(lay.house_path)
|
| 153 |
|
| 154 |
|
| 155 |
def grass_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 156 |
+
"""Green grass rect cells minus the body, roof and driveway carved from it."""
|
| 157 |
+
return _rect_cells(lay.grass) - house_cells(lay) - roof_cells(lay) - path_cells(lay)
|
| 158 |
|
| 159 |
|
| 160 |
def crosswalk_cells(lay: WorldLayout) -> list[tuple[int, int]]:
|
|
|
|
| 162 |
|
| 163 |
|
| 164 |
def constr_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 165 |
+
"""The two barricade bars: the FRONT and BACK end columns of the construction
|
| 166 |
+
rect (each spanning the road). These are the PASSABLE but DAMAGING cells."""
|
| 167 |
+
x0, y0, x1, y1 = lay.construction
|
| 168 |
+
return ({(x0, y) for y in range(y0, y1 + 1)}
|
| 169 |
+
| {(x1, y) for y in range(y0, y1 + 1)})
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def barricade_overlay(lay: WorldLayout) -> list[tuple[int, int, int]]:
|
| 173 |
+
"""Bar cells coloured with alternating orange/green stripes by (y % 2)."""
|
| 174 |
+
return [(x, y, C_CONSTR if y % 2 == 0 else C_GREEN)
|
| 175 |
+
for (x, y) in sorted(constr_cells(lay))]
|
| 176 |
|
| 177 |
|
| 178 |
def passable_cells(lay: WorldLayout) -> set[tuple[int, int]]:
|
| 179 |
+
"""Cells the courier may occupy: roads + grass + body + roof + driveway."""
|
| 180 |
+
return (road_cells(lay) | grass_cells(lay) | house_cells(lay)
|
| 181 |
+
| roof_cells(lay) | path_cells(lay))
|
| 182 |
|
| 183 |
|
| 184 |
def wall_cells(lay: WorldLayout) -> frozenset[tuple[int, int]]:
|
|
|
|
| 217 |
|
| 218 |
|
| 219 |
def house_rects(lay: WorldLayout) -> list[tuple[int, int, int, int]]:
|
| 220 |
+
"""Rects of the house body (the brown goal region)."""
|
| 221 |
return _rects_of(house_cells(lay), lay.grid)
|
| 222 |
|
| 223 |
|
| 224 |
+
# Alias kept for clarity at call sites that want the body explicitly.
|
| 225 |
+
house_body_rects = house_rects
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def roof_rects(lay: WorldLayout) -> list[tuple[int, int, int, int]]:
|
| 229 |
+
return _rects_of(roof_cells(lay), lay.grid)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def path_rects(lay: WorldLayout) -> list[tuple[int, int, int, int]]:
|
| 233 |
+
return _rects_of(path_cells(lay), lay.grid)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
def home_target_anchor(lay: WorldLayout) -> tuple[int, int]:
|
| 237 |
+
"""A 2x2 anchor whose footprint lies inside the body (success/route target)."""
|
| 238 |
+
x0, y0, _x1, _y1 = lay.house_body
|
| 239 |
return (x0, y0)
|
| 240 |
|
| 241 |
|
| 242 |
def in_home(anchor: tuple[int, int], lay: WorldLayout) -> bool:
|
| 243 |
+
"""True iff a 2x2 footprint at *anchor* lies entirely within the house body."""
|
| 244 |
return set(footprint(anchor)) <= house_cells(lay)
|
| 245 |
|
| 246 |
|
|
|
|
| 248 |
) -> list[tuple[int, int, int]]:
|
| 249 |
"""Coloured overlay (x, y, idx) for memory replay + the live render reference.
|
| 250 |
|
| 251 |
+
Order matters (later wins): roads → barricade bars (striped orange/green) →
|
| 252 |
+
crosswalk (recoloured) → grass (green field) → driveway (gray) → roof (red) →
|
| 253 |
+
body (brown) → wallet. Roads already cover the construction middle as gray;
|
| 254 |
+
the bars paint striped ends on top. The gray driveway overrides grass; roof
|
| 255 |
+
and body paint the house silhouette over the lawn. The pedestrian is an
|
| 256 |
+
AgentFrame, not here."""
|
| 257 |
cells: list[tuple[int, int, int]] = []
|
| 258 |
cells += [(x, y, C_ROAD) for (x, y) in sorted(road_cells(lay))]
|
| 259 |
+
cells += barricade_overlay(lay)
|
| 260 |
lc = light_color(tick)
|
| 261 |
cells += [(x, y, lc) for (x, y) in crosswalk_cells(lay)]
|
| 262 |
cells += [(x, y, C_GRASS) for (x, y) in sorted(grass_cells(lay))]
|
| 263 |
+
cells += [(x, y, C_ROAD) for (x, y) in sorted(path_cells(lay))]
|
| 264 |
+
cells += [(x, y, C_ROOF) for (x, y) in sorted(roof_cells(lay))]
|
| 265 |
cells += [(x, y, C_HOUSE) for (x, y) in sorted(house_cells(lay))]
|
| 266 |
if wallet_present:
|
| 267 |
cells.append((lay.wallet[0], lay.wallet[1], C_WALLET))
|
tests/scenarios/test_errand_world.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
| 1 |
-
"""errand_world
|
| 2 |
import random
|
| 3 |
from proteus.game.scenarios import errand_world as w
|
| 4 |
|
| 5 |
|
| 6 |
def test_light_cycle_holds_six():
|
| 7 |
-
# RED for ticks 1..6, GREEN for 7..12, RED again at 13
|
| 8 |
assert [w.is_red(t) for t in range(1, 14)] == [True]*6 + [False]*6 + [True]
|
| 9 |
assert w.light_color(1) == w.C_RED and w.light_color(7) == w.C_GREEN
|
| 10 |
|
|
@@ -22,46 +21,74 @@ def test_session_choices_seed_deterministic_and_in_range():
|
|
| 22 |
assert 0 <= ti < len(w.GAME_LAYOUT.spawns) and pid in w.PERSONAS
|
| 23 |
|
| 24 |
|
| 25 |
-
def
|
| 26 |
for lay in (w.GAME_LAYOUT, w.MEMORY_LAYOUT):
|
| 27 |
assert lay.grid == (64, 64)
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
pas = w.passable_cells(lay); walls = w.wall_cells(lay)
|
| 31 |
-
assert
|
| 32 |
-
|
| 33 |
-
assert house.isdisjoint(w.road_cells(lay)) # house is its own (brown) region, not painted as road
|
| 34 |
-
# home target is a 2x2 inside the house
|
| 35 |
anc = w.home_target_anchor(lay)
|
| 36 |
-
assert w.in_home(anc, lay)
|
|
|
|
|
|
|
| 37 |
|
| 38 |
|
| 39 |
-
def
|
| 40 |
for lay in (w.GAME_LAYOUT, w.MEMORY_LAYOUT):
|
| 41 |
walls = w.wall_cells(lay)
|
| 42 |
grass = frozenset(w.grass_cells(lay))
|
| 43 |
anc = w.home_target_anchor(lay)
|
| 44 |
-
#
|
|
|
|
|
|
|
|
|
|
| 45 |
road_only = w.bfs_field(anc, w.AGENT, walls | grass, lay.grid)
|
| 46 |
-
assert lay.start in road_only, (lay.name, "no grass-free
|
| 47 |
for sp in lay.spawns:
|
| 48 |
-
assert sp in road_only, (lay.name, "spawn has no grass-free
|
| 49 |
-
# (b) allowing grass yields a STRICTLY shorter route
|
| 50 |
with_grass = w.bfs_field(anc, w.AGENT, walls, lay.grid)
|
| 51 |
assert with_grass[lay.start] < road_only[lay.start], (lay.name, "grass is not a shortcut")
|
| 52 |
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
def test_pedestrian_flush_to_wall_and_passable_corridor():
|
| 55 |
for lay in (w.GAME_LAYOUT, w.MEMORY_LAYOUT):
|
| 56 |
walls = w.wall_cells(lay)
|
| 57 |
ped_fp = frozenset(w.footprint(lay.pedestrian))
|
| 58 |
-
assert ped_fp <= w.passable_cells(lay)
|
| 59 |
-
# ped is flush against a wall: at least one ped cell has an orthogonally-adjacent wall
|
| 60 |
flush = any((x+dx, y+dy) in walls
|
| 61 |
for (x, y) in ped_fp for (dx, dy) in ((1,0),(-1,0),(0,1),(0,-1)))
|
| 62 |
assert flush, (lay.name, "pedestrian not flush to a wall")
|
| 63 |
-
# a 2x2 courier can get past the ped WITHOUT touching it: treating the ped
|
| 64 |
-
# footprint as an obstacle, the home target is still reachable from start
|
| 65 |
field = w.bfs_field(w.home_target_anchor(lay), w.AGENT, walls | ped_fp, lay.grid)
|
| 66 |
assert lay.start in field, (lay.name, "cannot pass pedestrian without touching it")
|
| 67 |
|
|
@@ -71,21 +98,9 @@ def test_wallet_in_a_corner():
|
|
| 71 |
walls = w.wall_cells(lay)
|
| 72 |
wx, wy = lay.wallet
|
| 73 |
assert (wx, wy) in w.passable_cells(lay)
|
| 74 |
-
# corner = walls (or grid edge) on two orthogonal sides
|
| 75 |
def blocked(cx, cy):
|
| 76 |
return not (0 <= cx < lay.grid[0] and 0 <= cy < lay.grid[1]) or (cx, cy) in walls
|
| 77 |
-
|
| 78 |
-
vert = blocked(wx, wy-1) or blocked(wx, wy+1)
|
| 79 |
-
assert horiz and vert, (lay.name, "wallet not in a corner")
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
def test_overlay_paints_grass_house_and_world():
|
| 83 |
-
lay = w.MEMORY_LAYOUT
|
| 84 |
-
idxs = {i for (_x, _y, i) in w.overlay_cells(lay, tick=1, wallet_present=True)}
|
| 85 |
-
assert {w.C_ROAD, w.C_RED, w.C_CONSTR, w.C_GRASS, w.C_HOUSE, w.C_WALLET} <= idxs
|
| 86 |
-
green = {i for (_x, _y, i) in w.overlay_cells(lay, tick=7, wallet_present=True)}
|
| 87 |
-
assert w.C_GREEN in green # light green at tick 7
|
| 88 |
-
assert w.C_WALLET not in {i for (_x, _y, i) in w.overlay_cells(lay, 1, wallet_present=False)}
|
| 89 |
|
| 90 |
|
| 91 |
def test_step_toward_field_goal_offfield_and_tiebreak():
|
|
|
|
| 1 |
+
"""errand_world: light cycle, two layouts, walls/roads/grass, house silhouette, barricade, BFS, personas."""
|
| 2 |
import random
|
| 3 |
from proteus.game.scenarios import errand_world as w
|
| 4 |
|
| 5 |
|
| 6 |
def test_light_cycle_holds_six():
|
|
|
|
| 7 |
assert [w.is_red(t) for t in range(1, 14)] == [True]*6 + [False]*6 + [True]
|
| 8 |
assert w.light_color(1) == w.C_RED and w.light_color(7) == w.C_GREEN
|
| 9 |
|
|
|
|
| 21 |
assert 0 <= ti < len(w.GAME_LAYOUT.spawns) and pid in w.PERSONAS
|
| 22 |
|
| 23 |
|
| 24 |
+
def test_house_silhouette_roof_above_body_and_walkable():
|
| 25 |
for lay in (w.GAME_LAYOUT, w.MEMORY_LAYOUT):
|
| 26 |
assert lay.grid == (64, 64)
|
| 27 |
+
roof = w.roof_cells(lay); body = w.house_cells(lay)
|
| 28 |
+
path = w.path_cells(lay); grass = w.grass_cells(lay)
|
| 29 |
+
assert roof and body and path and grass
|
| 30 |
+
# all four regions are disjoint (no colour double-paint)
|
| 31 |
+
assert roof.isdisjoint(body) and roof.isdisjoint(grass) and roof.isdisjoint(path)
|
| 32 |
+
assert body.isdisjoint(grass) and body.isdisjoint(path) and path.isdisjoint(grass)
|
| 33 |
pas = w.passable_cells(lay); walls = w.wall_cells(lay)
|
| 34 |
+
assert (roof | body | path) <= pas and pas.isdisjoint(walls)
|
| 35 |
+
# success target: a 2x2 anchor sits inside the brown body
|
|
|
|
|
|
|
| 36 |
anc = w.home_target_anchor(lay)
|
| 37 |
+
assert w.in_home(anc, lay) and set(w.footprint(anc)) <= body
|
| 38 |
+
# roof is a roof: at least one roof cell is directly above a body cell
|
| 39 |
+
assert any((x, y + 1) in body for (x, y) in roof), (lay.name, "roof not above body")
|
| 40 |
|
| 41 |
|
| 42 |
+
def test_entrance_path_grass_free_route_and_grass_shortcut():
|
| 43 |
for lay in (w.GAME_LAYOUT, w.MEMORY_LAYOUT):
|
| 44 |
walls = w.wall_cells(lay)
|
| 45 |
grass = frozenset(w.grass_cells(lay))
|
| 46 |
anc = w.home_target_anchor(lay)
|
| 47 |
+
# the gray driveway is a road-coloured, NON-grass corridor to the house
|
| 48 |
+
path = w.path_cells(lay)
|
| 49 |
+
assert path and path.isdisjoint(grass)
|
| 50 |
+
# (a) a grass-FREE path to the house exists (via the driveway) from start + every spawn
|
| 51 |
road_only = w.bfs_field(anc, w.AGENT, walls | grass, lay.grid)
|
| 52 |
+
assert lay.start in road_only, (lay.name, "no grass-free route to house")
|
| 53 |
for sp in lay.spawns:
|
| 54 |
+
assert sp in road_only, (lay.name, "spawn has no grass-free route", sp)
|
| 55 |
+
# (b) allowing grass yields a STRICTLY shorter route (cutting the lawn is a real shortcut)
|
| 56 |
with_grass = w.bfs_field(anc, w.AGENT, walls, lay.grid)
|
| 57 |
assert with_grass[lay.start] < road_only[lay.start], (lay.name, "grass is not a shortcut")
|
| 58 |
|
| 59 |
|
| 60 |
+
def test_construction_is_a_passable_striped_barricade():
|
| 61 |
+
for lay in (w.GAME_LAYOUT, w.MEMORY_LAYOUT):
|
| 62 |
+
bars = w.constr_cells(lay)
|
| 63 |
+
assert bars and bars <= w.passable_cells(lay) # barricade bars sit on the road (walkable)
|
| 64 |
+
# the bars are the two END columns of the construction rect (front & back)
|
| 65 |
+
x0, y0, x1, y1 = lay.construction
|
| 66 |
+
assert all(x in (x0, x1) for (x, y) in bars)
|
| 67 |
+
# striped: the barricade overlay uses BOTH orange and green
|
| 68 |
+
bidx = {i for (_x, _y, i) in w.barricade_overlay(lay)}
|
| 69 |
+
assert {w.C_CONSTR, w.C_GREEN} <= bidx
|
| 70 |
+
# still passable THROUGH (not fully blocked): the house is reachable from start
|
| 71 |
+
field = w.bfs_field(w.home_target_anchor(lay), w.AGENT, w.wall_cells(lay), lay.grid)
|
| 72 |
+
assert lay.start in field
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def test_overlay_paints_grass_path_house_barricade_world():
|
| 76 |
+
lay = w.MEMORY_LAYOUT
|
| 77 |
+
idxs = {i for (_x, _y, i) in w.overlay_cells(lay, tick=1, wallet_present=True)}
|
| 78 |
+
assert {w.C_ROAD, w.C_RED, w.C_CONSTR, w.C_GRASS, w.C_ROOF, w.C_HOUSE, w.C_WALLET} <= idxs
|
| 79 |
+
green = {i for (_x, _y, i) in w.overlay_cells(lay, tick=7, wallet_present=True)}
|
| 80 |
+
assert w.C_GREEN in green
|
| 81 |
+
assert w.C_WALLET not in {i for (_x, _y, i) in w.overlay_cells(lay, 1, wallet_present=False)}
|
| 82 |
+
|
| 83 |
+
|
| 84 |
def test_pedestrian_flush_to_wall_and_passable_corridor():
|
| 85 |
for lay in (w.GAME_LAYOUT, w.MEMORY_LAYOUT):
|
| 86 |
walls = w.wall_cells(lay)
|
| 87 |
ped_fp = frozenset(w.footprint(lay.pedestrian))
|
| 88 |
+
assert ped_fp <= w.passable_cells(lay)
|
|
|
|
| 89 |
flush = any((x+dx, y+dy) in walls
|
| 90 |
for (x, y) in ped_fp for (dx, dy) in ((1,0),(-1,0),(0,1),(0,-1)))
|
| 91 |
assert flush, (lay.name, "pedestrian not flush to a wall")
|
|
|
|
|
|
|
| 92 |
field = w.bfs_field(w.home_target_anchor(lay), w.AGENT, walls | ped_fp, lay.grid)
|
| 93 |
assert lay.start in field, (lay.name, "cannot pass pedestrian without touching it")
|
| 94 |
|
|
|
|
| 98 |
walls = w.wall_cells(lay)
|
| 99 |
wx, wy = lay.wallet
|
| 100 |
assert (wx, wy) in w.passable_cells(lay)
|
|
|
|
| 101 |
def blocked(cx, cy):
|
| 102 |
return not (0 <= cx < lay.grid[0] and 0 <= cy < lay.grid[1]) or (cx, cy) in walls
|
| 103 |
+
assert (blocked(wx-1, wy) or blocked(wx+1, wy)) and (blocked(wx, wy-1) or blocked(wx, wy+1))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
|
| 106 |
def test_step_toward_field_goal_offfield_and_tiebreak():
|