scripts/room_templates.pyΒΆ

Part of Dungeon Explorer.

  1"""Pre-designed room shapes, decoration rules, and visual room decorations."""
  2
  3import random
  4from dataclasses import dataclass
  5
  6from .dungeon_generator import TILE_SIZE, WALL, Room
  7
  8# Pre-designed room shapes (relative offsets from room origin)
  9# Each template is a set of (dx, dy) offsets that should be floor tiles
 10
 11
 12def apply_pillars(grid: list[list[int]], room: Room, rng: random.Random) -> None:
 13    """Add corner pillars to a room (cosmetic wall tiles inside the room)."""
 14    if room.w < 7 or room.h < 7:
 15        return
 16    corners = [
 17        (room.x + 2, room.y + 2),
 18        (room.x + room.w - 3, room.y + 2),
 19        (room.x + 2, room.y + room.h - 3),
 20        (room.x + room.w - 3, room.y + room.h - 3),
 21    ]
 22    for cx, cy in corners:
 23        if rng.random() < 0.6:
 24            grid[cy][cx] = WALL
 25
 26
 27def apply_cross_room(grid: list[list[int]], room: Room) -> None:
 28    """Carve a cross-shaped room (remove corners)."""
 29    if room.w < 7 or room.h < 7:
 30        return
 31    cut = min(room.w, room.h) // 4
 32    for dy in range(cut):
 33        for dx in range(cut):
 34            # Top-left corner
 35            grid[room.y + dy][room.x + dx] = WALL
 36            # Top-right corner
 37            grid[room.y + dy][room.x + room.w - 1 - dx] = WALL
 38            # Bottom-left corner
 39            grid[room.y + room.h - 1 - dy][room.x + dx] = WALL
 40            # Bottom-right corner
 41            grid[room.y + room.h - 1 - dy][room.x + room.w - 1 - dx] = WALL
 42
 43
 44def apply_random_decoration(grid: list[list[int]], room: Room, rng: random.Random) -> None:
 45    """Apply a random decoration pattern to a room."""
 46    patterns = [apply_pillars, apply_cross_room_if_big]
 47    pattern = rng.choice(patterns)
 48    pattern(grid, room, rng)
 49
 50
 51def apply_cross_room_if_big(grid: list[list[int]], room: Room, rng: random.Random) -> None:
 52    if room.w >= 7 and room.h >= 7 and rng.random() < 0.3:
 53        apply_cross_room(grid, room)
 54
 55
 56# ============================================================================
 57# Room decorations: rubble, torch holders, alcoves
 58# ============================================================================
 59
 60
 61@dataclass
 62class RoomDecoration:
 63    """A small decorative shape drawn inside a room."""
 64
 65    kind: str  # "rubble", "torch_holder", "alcove"
 66    gx: int  # Grid x
 67    gy: int  # Grid y
 68    variant: int  # For visual variation
 69
 70
 71def generate_room_decorations(rooms: list[Room], seed: int = 0) -> list[RoomDecoration]:
 72    """Generate decorative objects inside rooms (cosmetic only, no collision)."""
 73    rng = random.Random(seed * 7919)
 74    decorations: list[RoomDecoration] = []
 75    kinds = ["rubble", "rubble", "torch_holder", "torch_holder", "alcove"]
 76    for room in rooms:
 77        if room.w < 5 or room.h < 5:
 78            continue
 79        count = rng.randint(0, 3)
 80        for _ in range(count):
 81            # Place near walls but inside the room
 82            edge = rng.choice(["top", "bottom", "left", "right"])
 83            if edge == "top":
 84                gx = rng.randint(room.x + 1, room.x + room.w - 2)
 85                gy = room.y + 1
 86            elif edge == "bottom":
 87                gx = rng.randint(room.x + 1, room.x + room.w - 2)
 88                gy = room.y + room.h - 2
 89            elif edge == "left":
 90                gx = room.x + 1
 91                gy = rng.randint(room.y + 1, room.y + room.h - 2)
 92            else:
 93                gx = room.x + room.w - 2
 94                gy = rng.randint(room.y + 1, room.y + room.h - 2)
 95            kind = rng.choice(kinds)
 96            decorations.append(RoomDecoration(kind=kind, gx=gx, gy=gy, variant=rng.randint(0, 3)))
 97    return decorations
 98
 99
100def draw_room_decorations(renderer, decorations: list[RoomDecoration]) -> None:
101    """Draw all room decorations."""
102    ts = TILE_SIZE
103    for d in decorations:
104        px, py = d.gx * ts, d.gy * ts
105        cx, cy = px + ts // 2, py + ts // 2
106        if d.kind == "rubble":
107            # Small grey pebble shapes
108            renderer.draw_circle((cx - 3, cy + 2), 3, colour=(0.3, 0.28, 0.25, 0.5), filled=True)
109            renderer.draw_circle((cx + 4, cy - 1), 2, colour=(0.28, 0.25, 0.22, 0.45), filled=True)
110            if d.variant > 1:
111                renderer.draw_circle((cx + 1, cy + 5), 2, colour=(0.32, 0.28, 0.24, 0.4), filled=True)
112        elif d.kind == "torch_holder":
113            # Wall-mounted bracket shape
114            renderer.draw_rect((cx - 1, cy - 6), (2, 8), colour=(0.35, 0.25, 0.15, 0.7), filled=True)
115            renderer.draw_rect((cx - 3, cy - 6), (6, 2), colour=(0.35, 0.25, 0.15, 0.7), filled=True)
116            # Unlit ember
117            renderer.draw_circle((cx, cy - 7), 2, colour=(0.4, 0.2, 0.05, 0.5), filled=True)
118        elif d.kind == "alcove":
119            # Recessed niche shape (darker rect with lighter border)
120            renderer.draw_rect((cx - 5, cy - 5), (10, 10), colour=(0.1, 0.08, 0.06, 0.5), filled=True)
121            renderer.draw_rect((cx - 4, cy - 4), (8, 8), colour=(0.18, 0.15, 0.12, 0.4), filled=True)