1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
package world
// GridConflict describes a layout conflict found while embedding rooms onto a
// 3D grid via their exit deltas.
//
// - "twist": one room is forced onto two different grid cells (it was already
// placed at Existing but a later exit wants it at Want).
// - "overlap": two distinct rooms want the same grid cell (Occupier already
// holds Want).
type GridConflict struct {
Kind string // "twist" or "overlap"
From int // room whose exit produced the conflict
Dir ExitDir // exit direction taken from From
Target int // room being placed
Want [3]int // grid cell Target was assigned
Existing [3]int // where Target was already placed (twist only)
Occupier int // room already at Want (overlap only)
}
// RoomGrid is the embedding of one connected component onto a 3D grid.
type RoomGrid struct {
Coord map[int][3]int // room -> grid coordinate
RoomAt map[[3]int]int // grid coordinate -> room
Dist map[int]int // room -> BFS hop distance from the seed
}
// BuildGrid lays out the rooms reachable from seed onto a 3D grid, assigning
// each room a coordinate by accumulating per-exit DirectionDeltas3D offsets in
// BFS order. The seed sits at the origin (0,0,0).
//
// - load returns a room and false to skip one that cannot be read; its exits
// are then not followed, but any coordinate already assigned to it stands.
// - include, when non-nil, restricts placement to targets for which it returns
// true (pass nil to place every reachable target).
// - edgeInclude, when non-nil, restricts traversal to exits for which it
// returns true (pass nil to follow every exit whose target passes include).
// It receives (fromRoomID, dir, targetRoomID) and is checked before include.
// - onConflict, when non-nil, is called for each twist/overlap encountered.
//
// The first assignment to a room or cell always wins; conflicting placements are
// skipped. In a world that passes grid validation there are no conflicts, so the
// layout is unambiguous.
func BuildGrid(seed int, load func(int) (*Room, bool), include func(int) bool, edgeInclude func(int, ExitDir, int) bool, onConflict func(GridConflict)) RoomGrid {
g := RoomGrid{
Coord: map[int][3]int{seed: {0, 0, 0}},
RoomAt: map[[3]int]int{{0, 0, 0}: seed},
Dist: map[int]int{seed: 0},
}
queue := []int{seed}
for len(queue) > 0 {
rid := queue[0]
queue = queue[1:]
room, ok := load(rid)
if !ok {
continue
}
c := g.Coord[rid]
for _, dir := range ExitOrder {
exit, ok := room.Exits[dir]
if !ok || exit.Room <= 0 {
continue
}
target := exit.Room
if include != nil && !include(target) {
continue
}
d := DirectionDeltas3D[dir]
want := [3]int{c[0] + d[0], c[1] + d[1], c[2] + d[2]}
if existing, seen := g.Coord[target]; seen {
if existing != want && onConflict != nil {
onConflict(GridConflict{
Kind: "twist", From: rid, Dir: dir,
Target: target, Want: want, Existing: existing,
})
}
continue
}
if occupier, used := g.RoomAt[want]; used && occupier != target {
if onConflict != nil {
onConflict(GridConflict{
Kind: "overlap", From: rid, Dir: dir,
Target: target, Want: want, Occupier: occupier,
})
}
continue
}
g.Coord[target] = want
g.RoomAt[want] = target
g.Dist[target] = g.Dist[rid] + 1
queue = append(queue, target)
}
}
return g
}
// BuildGridConflicts lays out the rooms reachable from seed exactly like
// BuildGrid but returns every twist/overlap conflict encountered instead of
// silently skipping them. Callers pass a load closure that may return *edited
// copies* of rooms to model a hypothetical edit (e.g. an insert or a remove)
// without writing anything to disk; a non-empty result means the modeled world
// cannot be embedded on the grid. include and edgeInclude default to "place
// and follow everything" (geometry is independent of gating), matching the
// startup validator's validateRoomGrid.
func BuildGridConflicts(seed int, load func(int) (*Room, bool)) []GridConflict {
var conflicts []GridConflict
BuildGrid(seed, load, nil, nil, func(gc GridConflict) {
conflicts = append(conflicts, gc)
})
return conflicts
}
|