Grid System Guide¶
Overview¶
The MAID Grid System provides a coordinate-based spatial indexing system for organizing rooms in a navigable 3D space. It enables efficient O(1) coordinate lookups, spatial queries like radius searches, and A* pathfinding between rooms.
Key features: - 3D Coordinate System: X (east/west), Y (north/south), Z (up/down) - O(1) Lookups: Fast room lookup by coordinate or room ID - Spatial Queries: Find rooms within radius, rectangle, or nearest neighbor - A* Pathfinding: Efficient pathfinding with terrain cost support - Auto Exit Creation: Automatic bidirectional exit creation between adjacent rooms - Blocked Coordinates: Reserve coordinates to prevent room placement - Serialization: Export/import grid state for persistence
Quick Start¶
from maid_engine.core.grid import GridManager, GridCoord, GridRoom
from uuid import uuid4
# Create a grid manager with auto-exit creation
grid = GridManager(auto_create_exits=True, allow_diagonals=True)
# Register rooms at coordinates
room1_id = uuid4()
room2_id = uuid4()
grid.register_room(room1_id, GridCoord(0, 0), terrain_type="plains")
grid.register_room(room2_id, GridCoord(1, 0), terrain_type="forest")
# Auto-created exits: room1 has exit east to room2, room2 has exit west to room1
# Look up rooms
room = grid.get_room_at(GridCoord(0, 0))
coord = grid.get_coord(room1_id)
# Find path between rooms
result = grid.find_path(room1_id, room2_id)
if result.found:
print(f"Path: {result.directions}") # ['e']
Core Classes¶
GridCoord¶
An immutable 3D coordinate in the grid.
from maid_engine.core.grid import GridCoord
# Create coordinates
origin = GridCoord(0, 0, 0)
east = GridCoord(1, 0, 0)
up = GridCoord(0, 0, 1)
# Arithmetic operations
delta = east - origin # GridCoord(1, 0, 0)
combined = origin + GridCoord(5, 5, 0) # GridCoord(5, 5, 0)
# Distance calculations
origin.distance_to(GridCoord(3, 4, 0)) # 5.0 (Euclidean)
origin.manhattan_distance(GridCoord(3, 4, 0)) # 7
origin.chebyshev_distance(GridCoord(3, 4, 0)) # 4
# Get neighbors
origin.neighbors() # [n, s, e, w] neighbors
origin.neighbors(diagonals=True) # [n, s, e, w, ne, nw, se, sw]
origin.neighbors(diagonals=True, vertical=True) # All 10 neighbors
# Direction utilities
origin.direction_to(GridCoord(1, 1, 0)) # "ne"
GridCoord.from_direction("ne", origin) # GridCoord(1, 1, 0)
GridRoom¶
Metadata for a room at a specific grid coordinate.
from maid_engine.core.grid import GridRoom, GridCoord
from uuid import uuid4
room = GridRoom(
room_id=uuid4(),
coord=GridCoord(5, 10),
movement_cost=1.5, # Higher = harder to traverse
terrain_type="forest",
blocked=False,
metadata={"zone": "wilderness"},
)
# Serialize
data = room.to_dict()
restored = GridRoom.from_dict(data)
GridManager¶
The main interface for managing the grid.
from maid_engine.core.grid import GridManager, GridCoord
from maid_engine.core.world import World
# Create with World reference for event integration
world = World()
grid = GridManager(
world=world,
auto_create_exits=True,
allow_diagonals=True,
allow_vertical=False,
)
# Or create standalone
grid = GridManager()
Configuration Options¶
| Option | Type | Default | Description |
|---|---|---|---|
world |
World \| None |
None |
World reference for events and entity lookups |
auto_create_exits |
bool |
False |
Automatically create exits between adjacent rooms |
allow_diagonals |
bool |
False |
Include diagonal movement (ne, nw, se, sw) |
allow_vertical |
bool |
False |
Include vertical movement (u, d) |
Room Registration¶
Basic Registration¶
from uuid import uuid4
from maid_engine.core.grid import GridManager, GridCoord
grid = GridManager(auto_create_exits=True)
# Register with room ID and coordinate
room_id = uuid4()
grid.register_room(
room_id,
GridCoord(5, 10),
movement_cost=1.0,
terrain_type="plains",
metadata={"zone": "starting_area"},
)
# Or register a pre-built GridRoom
from maid_engine.core.grid import GridRoom
grid_room = GridRoom(
room_id=uuid4(),
coord=GridCoord(6, 10),
terrain_type="forest",
)
grid.register_room(grid_room)
Bulk Registration¶
# Create a 10x10 grid of rooms
def make_room(coord):
room_id = uuid4()
# Create actual room entity in world...
return room_id
rooms = grid.create_grid_area(
GridCoord(0, 0),
GridCoord(9, 9),
room_factory=make_room,
)
print(f"Created {len(rooms)} rooms")
Blocked Coordinates¶
# Block coordinates to prevent room placement
grid.block_coord(GridCoord(5, 5))
# Check if blocked
grid.is_blocked(GridCoord(5, 5)) # True
# Unblock
grid.unblock_coord(GridCoord(5, 5))
Spatial Queries¶
Point Lookups¶
# O(1) coordinate lookup
room = grid.get_room_at(GridCoord(5, 10))
room = grid.get_room_at_xyz(5, 10, 0)
# Reverse lookup: get coordinate from room ID
coord = grid.get_coord(room_id)
# Check existence
if grid.has_room_at(GridCoord(5, 10)):
print("Room exists")
Range Queries¶
# Find rooms within Euclidean radius
nearby = grid.rooms_in_radius(GridCoord(5, 10), radius=5.0)
# Find rooms in rectangular region
region = grid.rooms_in_rect(
GridCoord(0, 0),
GridCoord(10, 10),
)
# Find nearest room to a coordinate
nearest = grid.nearest_room(GridCoord(100, 100), max_distance=50.0)
Iteration¶
# Iterate all rooms
for room in grid:
print(f"{room.coord}: {room.terrain_type}")
# Get count
print(f"Total rooms: {len(grid)}")
# Check containment
if GridCoord(5, 10) in grid:
print("Coordinate has a room")
Pathfinding¶
The GridManager provides A* pathfinding with terrain cost support.
from maid_engine.core.grid import GridManager
grid = GridManager(allow_diagonals=True)
# ... register rooms ...
# Find path between two rooms
result = grid.find_path(start_room_id, end_room_id)
if result.found:
print(f"Path length: {result.length}")
print(f"Total cost: {result.total_cost}")
print(f"Directions: {result.directions}") # ['n', 'ne', 'e', ...]
print(f"Rooms: {result.rooms}") # List of room UUIDs
print(f"Path: {result.path}") # List of GridCoords
# With constraints
result = grid.find_path(
start_room_id,
end_room_id,
max_length=20, # Maximum path length
can_pass=lambda room: not room.blocked and room.terrain_type != "water",
)
PathResult Fields¶
| Field | Type | Description |
|---|---|---|
found |
bool |
Whether a path was found |
path |
list[GridCoord] |
Coordinates from start to end |
rooms |
list[UUID] |
Room IDs along the path |
directions |
list[str] |
Direction strings to follow |
total_cost |
float |
Total movement cost |
rooms_searched |
int |
Number of rooms evaluated |
length |
int |
Number of steps (property) |
Movement Costs¶
Pathfinding considers terrain movement costs:
# Higher movement_cost = less desirable path
grid.register_room(room_id, coord, movement_cost=2.0) # Difficult terrain
# Impassable terrain (infinite cost, or use blocked)
grid.register_room(room_id, coord, blocked=True)
Exit Management¶
When auto_create_exits is enabled, the grid automatically creates bidirectional exits between adjacent rooms.
# Get all exits from a room
exits = grid.get_exits(room_id) # {"n": UUID(...), "e": UUID(...)}
# Get specific exit
destination = grid.get_exit(room_id, "north")
# Remove an exit
grid.remove_exit(room_id, "north")
Direction Constants¶
The grid system provides direction constants:
from maid_engine.core.grid import (
DIRECTION_OFFSETS, # {"n": (0, 1, 0), "s": (0, -1, 0), ...}
DIRECTION_NAMES, # {(0, 1, 0): "n", (0, -1, 0): "s", ...}
CARDINAL_DIRECTIONS, # ("n", "s", "e", "w")
DIAGONAL_DIRECTIONS, # ("ne", "nw", "se", "sw")
VERTICAL_DIRECTIONS, # ("u", "d")
)
Serialization¶
Export and import grid state for persistence:
import json
# Export grid state
data = grid.export_grid()
with open("grid.json", "w") as f:
json.dump(data, f)
# Import grid state
with open("grid.json", "r") as f:
data = json.load(f)
grid.import_grid(data)
# Merge with existing data (no clear)
grid.import_grid(data, clear_existing=False)
# Custom room ID resolver
id_map = {"old-uuid-str": UUID("new-uuid")}
grid.import_grid(data, room_resolver=lambda s: id_map.get(s, UUID(s)))
Integration with World¶
When a World reference is provided, the GridManager emits events on room changes:
from maid_engine.core.world import World
from maid_engine.core.events import GridRoomAddedEvent, GridRoomRemovedEvent
world = World()
grid = GridManager(world=world)
# Listen for grid events
@world.events.on(GridRoomAddedEvent)
async def on_room_added(event):
print(f"Room {event.room_id} added at ({event.coord_x}, {event.coord_y})")
@world.events.on(GridRoomRemovedEvent)
async def on_room_removed(event):
print(f"Room {event.room_id} removed")
Builder Commands¶
MAID provides in-game commands for grid management (BUILDER access level):
@grid create <min_x> <min_y> <max_x> <max_y> - Create grid area
@grid info - Show grid statistics
@grid block <x> <y> [z] - Block coordinate
@grid unblock <x> <y> [z] - Unblock coordinate
@grid room <x> <y> [z] - Show room at coordinate
@grid path <room1> <room2> - Find path between rooms
@grid export <filename> - Export grid to file
@grid import <filename> - Import grid from file
Error Handling¶
The grid system provides specific exceptions:
from maid_engine.core.grid import (
GridError, # Base exception
CoordinateOccupiedError, # Room already at coordinate
CoordinateBlockedError, # Coordinate is blocked
RoomNotFoundError, # Room not in grid
)
try:
grid.register_room(room_id, GridCoord(0, 0))
except CoordinateOccupiedError as e:
print(f"Cannot place room: {e.coord} is occupied")
except CoordinateBlockedError as e:
print(f"Cannot place room: {e.coord} is blocked")
try:
result = grid.find_path(unknown_id, room2_id)
except RoomNotFoundError as e:
print(f"Room not found: {e.identifier}")
Performance Considerations¶
- Registration: O(1) for individual rooms, O(n) for bulk with auto-exits
- Coordinate Lookup: O(1) via hash map
- Radius Query: O(n) where n is total rooms (iterates all)
- Rectangle Query: O(n) where n is total rooms
- Pathfinding: O(n log n) typical case with A*
For very large grids (10,000+ rooms), consider:
- Using rectangular queries to limit search space
- Setting max_length on pathfinding to bound search
- Disabling auto_create_exits for bulk operations
Example: Creating a Town¶
from maid_engine.core.grid import GridManager, GridCoord
from uuid import uuid4
grid = GridManager(auto_create_exits=True, allow_diagonals=False)
# Town layout:
# [Inn]---[Square]---[Shop]
# |
# [Gate]
def create_room(name, coord, terrain="stone"):
room_id = uuid4()
grid.register_room(room_id, coord, terrain_type=terrain)
return room_id
inn = create_room("The Rusty Mug Inn", GridCoord(0, 0))
square = create_room("Town Square", GridCoord(1, 0))
shop = create_room("General Store", GridCoord(2, 0))
gate = create_room("Town Gate", GridCoord(1, -1), terrain="road")
# All exits are created automatically!
# inn <-> square <-> shop
# square <-> gate
# Verify connectivity
result = grid.find_path(inn, gate)
print(f"Inn to Gate: {result.directions}") # ['e', 's']