feat: dungeon generation — BSP rooms, room types, fog of war
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
27
dungeon/fov.go
Normal file
27
dungeon/fov.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package dungeon
|
||||
|
||||
type Visibility int
|
||||
|
||||
const (
|
||||
Hidden Visibility = iota
|
||||
Visited
|
||||
Visible
|
||||
)
|
||||
|
||||
func UpdateVisibility(floor *Floor) {
|
||||
for i, room := range floor.Rooms {
|
||||
if i == floor.CurrentRoom {
|
||||
room.Visited = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetRoomVisibility(floor *Floor, roomIdx int) Visibility {
|
||||
if roomIdx == floor.CurrentRoom {
|
||||
return Visible
|
||||
}
|
||||
if floor.Rooms[roomIdx].Visited {
|
||||
return Visited
|
||||
}
|
||||
return Hidden
|
||||
}
|
||||
49
dungeon/generator.go
Normal file
49
dungeon/generator.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package dungeon
|
||||
|
||||
import "math/rand"
|
||||
|
||||
type Floor struct {
|
||||
Number int
|
||||
Rooms []*Room
|
||||
CurrentRoom int
|
||||
}
|
||||
|
||||
func GenerateFloor(floorNum int) *Floor {
|
||||
numRooms := 5 + rand.Intn(4)
|
||||
rooms := make([]*Room, numRooms)
|
||||
for i := 0; i < numRooms; i++ {
|
||||
rt := RandomRoomType()
|
||||
rooms[i] = &Room{
|
||||
Type: rt,
|
||||
X: (i % 3) * 20,
|
||||
Y: (i / 3) * 10,
|
||||
Width: 12 + rand.Intn(6),
|
||||
Height: 6 + rand.Intn(4),
|
||||
Neighbors: []int{},
|
||||
}
|
||||
}
|
||||
rooms[numRooms-1].Type = RoomBoss
|
||||
for i := 0; i < numRooms-1; i++ {
|
||||
rooms[i].Neighbors = append(rooms[i].Neighbors, i+1)
|
||||
rooms[i+1].Neighbors = append(rooms[i+1].Neighbors, i)
|
||||
}
|
||||
extras := 1 + rand.Intn(2)
|
||||
for e := 0; e < extras; e++ {
|
||||
a := rand.Intn(numRooms)
|
||||
b := rand.Intn(numRooms)
|
||||
if a != b && !hasNeighbor(rooms[a], b) {
|
||||
rooms[a].Neighbors = append(rooms[a].Neighbors, b)
|
||||
rooms[b].Neighbors = append(rooms[b].Neighbors, a)
|
||||
}
|
||||
}
|
||||
return &Floor{Number: floorNum, Rooms: rooms, CurrentRoom: 0}
|
||||
}
|
||||
|
||||
func hasNeighbor(r *Room, idx int) bool {
|
||||
for _, n := range r.Neighbors {
|
||||
if n == idx {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
42
dungeon/generator_test.go
Normal file
42
dungeon/generator_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package dungeon
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGenerateFloor(t *testing.T) {
|
||||
floor := GenerateFloor(1)
|
||||
if len(floor.Rooms) < 5 || len(floor.Rooms) > 8 {
|
||||
t.Errorf("Room count: got %d, want 5~8", len(floor.Rooms))
|
||||
}
|
||||
bossCount := 0
|
||||
for _, r := range floor.Rooms {
|
||||
if r.Type == RoomBoss {
|
||||
bossCount++
|
||||
}
|
||||
}
|
||||
if bossCount != 1 {
|
||||
t.Errorf("Boss rooms: got %d, want 1", bossCount)
|
||||
}
|
||||
visited := make(map[int]bool)
|
||||
var dfs func(int)
|
||||
dfs = func(idx int) {
|
||||
if visited[idx] { return }
|
||||
visited[idx] = true
|
||||
for _, n := range floor.Rooms[idx].Neighbors { dfs(n) }
|
||||
}
|
||||
dfs(0)
|
||||
if len(visited) != len(floor.Rooms) {
|
||||
t.Errorf("Not all rooms connected: reachable %d / %d", len(visited), len(floor.Rooms))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoomTypeProbability(t *testing.T) {
|
||||
counts := make(map[RoomType]int)
|
||||
n := 10000
|
||||
for i := 0; i < n; i++ {
|
||||
counts[RandomRoomType()]++
|
||||
}
|
||||
combatPct := float64(counts[RoomCombat]) / float64(n) * 100
|
||||
if combatPct < 40 || combatPct > 50 {
|
||||
t.Errorf("Combat room probability: got %.1f%%, want ~45%%", combatPct)
|
||||
}
|
||||
}
|
||||
43
dungeon/room.go
Normal file
43
dungeon/room.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package dungeon
|
||||
|
||||
import "math/rand"
|
||||
|
||||
type RoomType int
|
||||
|
||||
const (
|
||||
RoomCombat RoomType = iota
|
||||
RoomTreasure
|
||||
RoomShop
|
||||
RoomEvent
|
||||
RoomEmpty
|
||||
RoomBoss
|
||||
)
|
||||
|
||||
func (r RoomType) String() string {
|
||||
return [...]string{"Combat", "Treasure", "Shop", "Event", "Empty", "Boss"}[r]
|
||||
}
|
||||
|
||||
type Room struct {
|
||||
Type RoomType
|
||||
X, Y int
|
||||
Width, Height int
|
||||
Visited bool
|
||||
Cleared bool
|
||||
Neighbors []int
|
||||
}
|
||||
|
||||
func RandomRoomType() RoomType {
|
||||
r := rand.Float64() * 100
|
||||
switch {
|
||||
case r < 45:
|
||||
return RoomCombat
|
||||
case r < 60:
|
||||
return RoomTreasure
|
||||
case r < 70:
|
||||
return RoomShop
|
||||
case r < 85:
|
||||
return RoomEvent
|
||||
default:
|
||||
return RoomEmpty
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user