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)