Skip to content

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']