diff options
Diffstat (limited to 'internal/game/map.go')
| -rw-r--r-- | internal/game/map.go | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/internal/game/map.go b/internal/game/map.go new file mode 100644 index 0000000..cd1012b --- /dev/null +++ b/internal/game/map.go @@ -0,0 +1,191 @@ +package game + +import ( + "thirdcollapse/internal/world" +) + +type mapGraph struct { + posToRoom map[[2]int]int + roomToPos map[int][2]int +} + +var bfsDirs = []struct { + dir world.ExitDir + dx, dy int +}{ + {world.North, 0, -1}, + {world.South, 0, 1}, + {world.East, 1, 0}, + {world.West, -1, 0}, +} + +func buildGraph(g *Game, startRoomID int) *mapGraph { + mg := &mapGraph{ + posToRoom: make(map[[2]int]int), + roomToPos: make(map[int][2]int), + } + + type node struct { + roomID int + x, y int + } + queue := []node{{startRoomID, 0, 0}} + mg.posToRoom[[2]int{0, 0}] = startRoomID + mg.roomToPos[startRoomID] = [2]int{0, 0} + + for len(queue) > 0 { + n := queue[0] + queue = queue[1:] + + room, ok := loadRoom(g, n.roomID) + if !ok { + continue + } + + for _, d := range bfsDirs { + targetID, ok := exitTarget(room, d.dir) + if !ok { + continue + } + if _, visited := mg.roomToPos[targetID]; visited { + continue + } + nx, ny := n.x + d.dx, n.y + d.dy + mg.posToRoom[[2]int{nx, ny}] = targetID + mg.roomToPos[targetID] = [2]int{nx, ny} + queue = append(queue, node{targetID, nx, ny}) + } + } + + return mg +} + +func buildTinyMap(g *Game, roomID int) []string { + mg := buildGraph(g, roomID) + + grid := make([][]rune, 5) + for i := range grid { + grid[i] = make([]rune, 5) + for j := range grid[i] { + grid[i][j] = ' ' + } + } + + for y := -1; y <= 1; y++ { + for x := -1; x <= 1; x++ { + pos := [2]int{x, y} + rid, ok := mg.posToRoom[pos] + if !ok { + continue + } + gr := (y + 1) * 2 + gc := (x + 1) * 2 + if rid == roomID { + grid[gr][gc] = '@' + } else { + grid[gr][gc] = roomMapSymbol(g, rid) + } + } + } + + for y := -1; y <= 1; y++ { + for x := -1; x <= 0; x++ { + leftPos := [2]int{x, y} + rightPos := [2]int{x + 1, y} + leftRoom, leftOK := mg.posToRoom[leftPos] + rightRoom, rightOK := mg.posToRoom[rightPos] + if !leftOK || !rightOK { + continue + } + if exitsConnect(g, leftRoom, rightRoom, world.East, world.West) { + grid[(y+1)*2][(x+1)*2+1] = '─' + } + } + } + + for y := -1; y <= 0; y++ { + for x := -1; x <= 1; x++ { + topPos := [2]int{x, y} + bottomPos := [2]int{x, y + 1} + topRoom, topOK := mg.posToRoom[topPos] + bottomRoom, bottomOK := mg.posToRoom[bottomPos] + if !topOK || !bottomOK { + continue + } + if exitsConnect(g, topRoom, bottomRoom, world.South, world.North) { + grid[(y+1)*2+1][(x+1)*2] = '│' + } + } + } + + cur, _ := loadRoom(g, roomID) + if cur != nil { + if _, ok := exitTarget(cur, world.Up); ok { + grid[1][3] = '↑' + } + if _, ok := exitTarget(cur, world.Down); ok { + grid[3][1] = '↓' + } + } + + lines := make([]string, 7) + lines[0] = "╔═════╗" + for row := 0; row < 5; row++ { + lines[row+1] = "║" + string(grid[row]) + "║" + } + lines[6] = "╚═════╝" + + return lines +} + +func exitsConnect(g *Game, room1, room2 int, dir12, dir21 world.ExitDir) bool { + r1, ok := loadRoom(g, room1) + if !ok { + return false + } + if id, ok := exitTarget(r1, dir12); ok && id == room2 { + return true + } + r2, ok := loadRoom(g, room2) + if !ok { + return false + } + if id, ok := exitTarget(r2, dir21); ok && id == room1 { + return true + } + return false +} + +func exitTarget(room *world.Room, dir world.ExitDir) (int, bool) { + if room == nil { + return 0, false + } + exit, ok := room.Exits[dir] + if !ok { + return 0, false + } + return exit.Room, true +} + +func loadRoom(g *Game, roomID int) (*world.Room, bool) { + if roomID == 0 { + return nil, false + } + room, err := g.World.LoadRoom(roomID) + if err != nil { + return nil, false + } + return room, true +} + +func roomMapSymbol(g *Game, roomID int) rune { + room, ok := loadRoom(g, roomID) + if !ok { + return '?' + } + if room.MapSymbol != "" { + runes := []rune(room.MapSymbol) + return runes[0] + } + return 'o' +} |
