| """ |
| Grid world transition system. |
| Each cell is labeled with a set of atomic propositions. |
| Robots move N/S/E/W; obstacles block movement. |
| """ |
|
|
| from dataclasses import dataclass, field |
| from typing import Dict, FrozenSet, List, Set, Tuple |
|
|
| MOVES = {"N": (-1, 0), "S": (1, 0), "E": (0, 1), "W": (0, -1)} |
|
|
| |
| CELL_PROPS = { |
| "free": frozenset(), |
| "obstacle": frozenset({"obstacle"}), |
| "zone_a": frozenset({"zone_a"}), |
| "zone_b": frozenset({"zone_b"}), |
| "zone_c": frozenset({"zone_c"}), |
| "danger": frozenset({"danger"}), |
| "goal": frozenset({"goal"}), |
| "start": frozenset(), |
| } |
|
|
|
|
| @dataclass |
| class GridWorld: |
| n: int |
| grid: List[List[str]] = field(default_factory=list) |
| start: Tuple[int, int] = (0, 0) |
|
|
| def __post_init__(self): |
| if not self.grid: |
| self.grid = [["free"] * self.n for _ in range(self.n)] |
|
|
| def label(self, pos: Tuple[int, int]) -> FrozenSet[str]: |
| r, c = pos |
| return CELL_PROPS.get(self.grid[r][c], frozenset()) |
|
|
| def successors(self, pos: Tuple[int, int]) -> List[Tuple[str, Tuple[int, int]]]: |
| r, c = pos |
| result = [] |
| for action, (dr, dc) in MOVES.items(): |
| nr, nc = r + dr, c + dc |
| if 0 <= nr < self.n and 0 <= nc < self.n: |
| if self.grid[nr][nc] != "obstacle": |
| result.append((action, (nr, nc))) |
| |
| result.append(("stay", pos)) |
| return result |
|
|
| def all_positions(self) -> List[Tuple[int, int]]: |
| return [ |
| (r, c) |
| for r in range(self.n) |
| for c in range(self.n) |
| if self.grid[r][c] != "obstacle" |
| ] |
|
|
|
|
| def make_scenario(name: str) -> GridWorld: |
| """Built-in demo scenarios.""" |
| if name == "road": |
| |
| |
| n = 8 |
| g = GridWorld(n=n) |
| g.start = (0, 0) |
| |
| for r in range(n): |
| g.grid[r][3] = "danger" |
| |
| for r in range(2, 5): |
| g.grid[r][5] = "obstacle" |
| g.grid[1][6] = "zone_a" |
| g.grid[6][1] = "zone_b" |
| g.grid[7][7] = "goal" |
| return g |
|
|
| if name == "patrol": |
| |
| n = 6 |
| g = GridWorld(n=n) |
| g.start = (0, 0) |
| g.grid[0][5] = "zone_a" |
| g.grid[5][0] = "zone_b" |
| g.grid[2][2] = "danger" |
| g.grid[2][3] = "danger" |
| g.grid[3][2] = "danger" |
| g.grid[3][3] = "danger" |
| return g |
|
|
| if name == "rescue": |
| |
| n = 7 |
| g = GridWorld(n=n) |
| g.start = (3, 0) |
| for c in range(1, 6): |
| g.grid[3][c] = "obstacle" |
| g.grid[3][3] = "free" |
| g.grid[0][6] = "zone_a" |
| g.grid[6][6] = "zone_b" |
| for r in [1, 2, 4, 5]: |
| g.grid[r][2] = "danger" |
| g.grid[0][0] = "zone_c" |
| return g |
|
|
| raise ValueError(f"Unknown scenario: {name}") |
|
|