aboutsummaryrefslogtreecommitdiff
path: root/internal/game/render_map.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/render_map.go')
-rw-r--r--internal/game/render_map.go277
1 files changed, 257 insertions, 20 deletions
diff --git a/internal/game/render_map.go b/internal/game/render_map.go
index 7416d06..6b80688 100644
--- a/internal/game/render_map.go
+++ b/internal/game/render_map.go
@@ -17,6 +17,9 @@ type mapGlyphs struct {
connectorH, connectorV rune
upArrow, downArrow rune
leftArrow, rightArrow rune
+ upRight, upLeft rune
+ downRight, downLeft rune
+ connectorNE, connectorNW rune
}
func mapGlyphsForPlayer(unicode bool) mapGlyphs {
@@ -25,12 +28,16 @@ func mapGlyphsForPlayer(unicode bool) mapGlyphs {
topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
side: '║', topFill: '═', connectorH: '-', connectorV: '│',
upArrow: '↑', downArrow: '↓', leftArrow: '←', rightArrow: '→',
+ upRight: '↗', upLeft: '↖', downRight: '↘', downLeft: '↙',
+ connectorNE: '/', connectorNW: '\\',
}
}
return mapGlyphs{
topLeft: '.', topRight: '.', bottomLeft: ':', bottomRight: ':',
side: ':', topFill: '.', connectorH: '-', connectorV: '|',
upArrow: '^', downArrow: 'v', leftArrow: '<', rightArrow: '>',
+ upRight: '/', upLeft: '\\', downRight: '\\', downLeft: '/',
+ connectorNE: '/', connectorNW: '\\',
}
}
@@ -45,15 +52,7 @@ type mapGraph struct {
dist map[int]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, visited map[int]bool) *mapGraph {
mg := &mapGraph{
@@ -84,15 +83,15 @@ func buildGraph(g *Game, startRoomID int, visited map[int]bool) *mapGraph {
continue
}
- for _, d := range bfsDirs {
- targetID, ok := exitTarget(room, d.dir)
+ for dir, delta := range world.DirectionDeltas {
+ targetID, ok := exitTarget(room, dir)
if !ok {
continue
}
if _, seen := mg.roomToPos[targetID]; seen {
continue
}
- nx, ny := n.x+d.dx, n.y+d.dy
+ nx, ny := n.x+delta[0], n.y+delta[1]
mg.posToRoom[[2]int{nx, ny}] = targetID
mg.roomToPos[targetID] = [2]int{nx, ny}
mg.dist[targetID] = mg.dist[n.roomID] + 1
@@ -137,6 +136,7 @@ func buildTinyMap(g *Game, sess *net.Session, roomID int, mg mapGlyphs) []string
ctx := &mapRenderCtx{
g: g, sess: sess, bg: bg, visited: visited, currentRoom: roomID,
atSpec: atSpec, dimSpec: dimSpec, blockedSpec: resolveMapBlocked(g, sess), mg: mg,
+ diagPairs: make(map[[2]int][2]int),
}
grid := make([][]mapCell, 5)
@@ -199,21 +199,145 @@ func buildTinyMap(g *Game, sess *net.Session, roomID int, mg mapGlyphs) []string
}
}
+ // NE connectors: (x,y) -> (x+1, y-1), connector at grid[2*y+1][2*x+3]
+ for y := 0; y <= 1; y++ {
+ for x := -1; x <= 0; x++ {
+ aPos, bPos := [2]int{x, y}, [2]int{x + 1, y - 1}
+ aRoom, aOK := bg.posToRoom[aPos]
+ bRoom, bOK := bg.posToRoom[bPos]
+ if !aOK || !bOK {
+ continue
+ }
+ if cell, ok := ctx.connectorCell(aRoom, bRoom, world.Northeast, world.Southwest); ok {
+ gr, gc := 2*y+1, 2*x+3
+ key := [2]int{gr, gc}
+ if isDiagonalGlyph(grid[gr][gc].char) {
+ if grid[gr][gc].char != cell.char {
+ prev := ctx.diagPairs[key]
+ spec := color.Average(
+ color.Average(ctx.nodeSpec(prev[0]), ctx.nodeSpec(prev[1])),
+ color.Average(ctx.nodeSpec(aRoom), ctx.nodeSpec(bRoom)),
+ )
+ grid[gr][gc] = mapCell{char: 'X', spec: spec}
+ }
+ } else {
+ grid[gr][gc] = cell
+ ctx.diagPairs[key] = [2]int{aRoom, bRoom}
+ }
+ }
+ }
+ }
+ // NW connectors: (x,y) -> (x-1, y-1), connector at grid[2*y+1][2*x+1]
+ for y := 0; y <= 1; y++ {
+ for x := 0; x <= 1; x++ {
+ aPos, bPos := [2]int{x, y}, [2]int{x - 1, y - 1}
+ aRoom, aOK := bg.posToRoom[aPos]
+ bRoom, bOK := bg.posToRoom[bPos]
+ if !aOK || !bOK {
+ continue
+ }
+ if cell, ok := ctx.connectorCell(aRoom, bRoom, world.Northwest, world.Southeast); ok {
+ gr, gc := 2*y+1, 2*x+1
+ key := [2]int{gr, gc}
+ if isDiagonalGlyph(grid[gr][gc].char) {
+ if grid[gr][gc].char != cell.char {
+ prev := ctx.diagPairs[key]
+ spec := color.Average(
+ color.Average(ctx.nodeSpec(prev[0]), ctx.nodeSpec(prev[1])),
+ color.Average(ctx.nodeSpec(aRoom), ctx.nodeSpec(bRoom)),
+ )
+ grid[gr][gc] = mapCell{char: 'X', spec: spec}
+ }
+ } else {
+ grid[gr][gc] = cell
+ ctx.diagPairs[key] = [2]int{aRoom, bRoom}
+ }
+ }
+ }
+ }
+ // SE connectors: (x,y) -> (x+1, y+1), connector at grid[2*y+3][2*x+3]
+ for y := -1; y <= 0; y++ {
+ for x := -1; x <= 0; x++ {
+ aPos, bPos := [2]int{x, y}, [2]int{x + 1, y + 1}
+ aRoom, aOK := bg.posToRoom[aPos]
+ bRoom, bOK := bg.posToRoom[bPos]
+ if !aOK || !bOK {
+ continue
+ }
+ if cell, ok := ctx.connectorCell(aRoom, bRoom, world.Southeast, world.Northwest); ok {
+ gr, gc := 2*y+3, 2*x+3
+ key := [2]int{gr, gc}
+ if isDiagonalGlyph(grid[gr][gc].char) {
+ if grid[gr][gc].char != cell.char {
+ prev := ctx.diagPairs[key]
+ spec := color.Average(
+ color.Average(ctx.nodeSpec(prev[0]), ctx.nodeSpec(prev[1])),
+ color.Average(ctx.nodeSpec(aRoom), ctx.nodeSpec(bRoom)),
+ )
+ grid[gr][gc] = mapCell{char: 'X', spec: spec}
+ }
+ } else {
+ grid[gr][gc] = cell
+ ctx.diagPairs[key] = [2]int{aRoom, bRoom}
+ }
+ }
+ }
+ }
+ // SW connectors: (x,y) -> (x-1, y+1), connector at grid[2*y+3][2*x+1]
+ for y := -1; y <= 0; y++ {
+ for x := 0; x <= 1; x++ {
+ aPos, bPos := [2]int{x, y}, [2]int{x - 1, y + 1}
+ aRoom, aOK := bg.posToRoom[aPos]
+ bRoom, bOK := bg.posToRoom[bPos]
+ if !aOK || !bOK {
+ continue
+ }
+ if cell, ok := ctx.connectorCell(aRoom, bRoom, world.Southwest, world.Northeast); ok {
+ gr, gc := 2*y+3, 2*x+1
+ key := [2]int{gr, gc}
+ if isDiagonalGlyph(grid[gr][gc].char) {
+ if grid[gr][gc].char != cell.char {
+ prev := ctx.diagPairs[key]
+ spec := color.Average(
+ color.Average(ctx.nodeSpec(prev[0]), ctx.nodeSpec(prev[1])),
+ color.Average(ctx.nodeSpec(aRoom), ctx.nodeSpec(bRoom)),
+ )
+ grid[gr][gc] = mapCell{char: 'X', spec: spec}
+ }
+ } else {
+ grid[gr][gc] = cell
+ ctx.diagPairs[key] = [2]int{aRoom, bRoom}
+ }
+ }
+ }
+ }
+
cur, _ := loadRoom(g, roomID)
if cur != nil {
- if target, ok := exitTarget(cur, world.Up); ok {
+ if upTarget, hasUp := exitTarget(cur, world.Up); hasUp {
spec := color.NoColor()
- if exitStateTo(g, sess, roomID, world.Up, target) == exitBlocked {
+ if exitStateTo(g, sess, roomID, world.Up, upTarget) == exitBlocked {
spec = ctx.blockedSpec
}
- grid[1][3] = mapCell{char: mg.upArrow, spec: spec}
+ switch {
+ case grid[1][3].char == ' ':
+ grid[1][3] = mapCell{char: mg.upArrow, spec: spec}
+ case grid[1][1].char == ' ':
+ grid[1][1] = mapCell{char: mg.upArrow, spec: spec}
+ case grid[1][2].char == ' ':
+ grid[1][2] = mapCell{char: mg.upArrow, spec: spec}
+ }
}
- if target, ok := exitTarget(cur, world.Down); ok {
+ if downTarget, hasDown := exitTarget(cur, world.Down); hasDown {
spec := color.NoColor()
- if exitStateTo(g, sess, roomID, world.Down, target) == exitBlocked {
+ if exitStateTo(g, sess, roomID, world.Down, downTarget) == exitBlocked {
spec = ctx.blockedSpec
}
- grid[3][1] = mapCell{char: mg.downArrow, spec: spec}
+ if grid[3][1].char == ' ' {
+ grid[3][1] = mapCell{char: mg.downArrow, spec: spec}
+ } else if grid[3][3].char == ' ' {
+ grid[3][3] = mapCell{char: mg.downArrow, spec: spec}
+ }
}
}
@@ -238,6 +362,7 @@ func buildFullMap(g *Game, sess *net.Session, roomID, mapWidth, mapHeight int, m
ctx := &mapRenderCtx{
g: g, sess: sess, bg: bg, visited: visited, currentRoom: roomID,
atSpec: atSpec, dimSpec: dimSpec, blockedSpec: resolveMapBlocked(g, sess), mg: mg,
+ diagPairs: make(map[[2]int][2]int),
}
grid := make([][]mapCell, mapHeight)
@@ -291,6 +416,98 @@ func buildFullMap(g *Game, sess *net.Session, roomID, mapWidth, mapHeight int, m
}
}
}
+
+ if neID, exists := bg.posToRoom[[2]int{x + 1, y - 1}]; exists {
+ gr := cy + y*2 - 1
+ gc := cx + x*2 + 1
+ if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
+ if cell, ok := ctx.connectorCell(rid, neID, world.Northeast, world.Southwest); ok {
+ key := [2]int{gr, gc}
+ if isDiagonalGlyph(grid[gr][gc].char) {
+ if grid[gr][gc].char != cell.char {
+ prev := ctx.diagPairs[key]
+ spec := color.Average(
+ color.Average(ctx.nodeSpec(prev[0]), ctx.nodeSpec(prev[1])),
+ color.Average(ctx.nodeSpec(rid), ctx.nodeSpec(neID)),
+ )
+ grid[gr][gc] = mapCell{char: 'X', spec: spec}
+ }
+ } else {
+ grid[gr][gc] = cell
+ ctx.diagPairs[key] = [2]int{rid, neID}
+ }
+ }
+ }
+ }
+
+ if nwID, exists := bg.posToRoom[[2]int{x - 1, y - 1}]; exists {
+ gr := cy + y*2 - 1
+ gc := cx + x*2 - 1
+ if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
+ if cell, ok := ctx.connectorCell(rid, nwID, world.Northwest, world.Southeast); ok {
+ key := [2]int{gr, gc}
+ if isDiagonalGlyph(grid[gr][gc].char) {
+ if grid[gr][gc].char != cell.char {
+ prev := ctx.diagPairs[key]
+ spec := color.Average(
+ color.Average(ctx.nodeSpec(prev[0]), ctx.nodeSpec(prev[1])),
+ color.Average(ctx.nodeSpec(rid), ctx.nodeSpec(nwID)),
+ )
+ grid[gr][gc] = mapCell{char: 'X', spec: spec}
+ }
+ } else {
+ grid[gr][gc] = cell
+ ctx.diagPairs[key] = [2]int{rid, nwID}
+ }
+ }
+ }
+ }
+
+ if seID, exists := bg.posToRoom[[2]int{x + 1, y + 1}]; exists {
+ gr := cy + y*2 + 1
+ gc := cx + x*2 + 1
+ if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
+ if cell, ok := ctx.connectorCell(rid, seID, world.Southeast, world.Northwest); ok {
+ key := [2]int{gr, gc}
+ if isDiagonalGlyph(grid[gr][gc].char) {
+ if grid[gr][gc].char != cell.char {
+ prev := ctx.diagPairs[key]
+ spec := color.Average(
+ color.Average(ctx.nodeSpec(prev[0]), ctx.nodeSpec(prev[1])),
+ color.Average(ctx.nodeSpec(rid), ctx.nodeSpec(seID)),
+ )
+ grid[gr][gc] = mapCell{char: 'X', spec: spec}
+ }
+ } else {
+ grid[gr][gc] = cell
+ ctx.diagPairs[key] = [2]int{rid, seID}
+ }
+ }
+ }
+ }
+
+ if swID, exists := bg.posToRoom[[2]int{x - 1, y + 1}]; exists {
+ gr := cy + y*2 + 1
+ gc := cx + x*2 - 1
+ if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
+ if cell, ok := ctx.connectorCell(rid, swID, world.Southwest, world.Northeast); ok {
+ key := [2]int{gr, gc}
+ if isDiagonalGlyph(grid[gr][gc].char) {
+ if grid[gr][gc].char != cell.char {
+ prev := ctx.diagPairs[key]
+ spec := color.Average(
+ color.Average(ctx.nodeSpec(prev[0]), ctx.nodeSpec(prev[1])),
+ color.Average(ctx.nodeSpec(rid), ctx.nodeSpec(swID)),
+ )
+ grid[gr][gc] = mapCell{char: 'X', spec: spec}
+ }
+ } else {
+ grid[gr][gc] = cell
+ ctx.diagPairs[key] = [2]int{rid, swID}
+ }
+ }
+ }
+ }
}
return renderMapCells(grid, colorMode, 0, mapHeight, 0)
@@ -343,6 +560,7 @@ type mapRenderCtx struct {
dimSpec color.ColorSpec
blockedSpec color.ColorSpec
mg mapGlyphs
+ diagPairs map[[2]int][2]int
}
// nodeSpec returns the effective color a room's node is drawn with, mirroring
@@ -441,12 +659,23 @@ func exitStateTo(g *Game, sess *net.Session, from int, dir world.ExitDir, neighb
return exitBlocked
}
+func isDiagonalGlyph(ch rune) bool {
+ return ch == '\\' || ch == '/' || ch == '↗' || ch == '↖' || ch == '↘' || ch == '↙'
+}
+
// barGlyph returns the bidirectional connector glyph for a link's orientation.
func barGlyph(mg mapGlyphs, dir world.ExitDir) rune {
- if dir == world.East || dir == world.West {
+ switch dir {
+ case world.East, world.West:
return mg.connectorH
+ case world.North, world.South:
+ return mg.connectorV
+ case world.Northeast, world.Southwest:
+ return mg.connectorNE
+ case world.Northwest, world.Southeast:
+ return mg.connectorNW
}
- return mg.connectorV
+ return mg.connectorH
}
// arrowGlyph returns the one-way arrow pointing along the direction of travel.
@@ -460,6 +689,14 @@ func arrowGlyph(mg mapGlyphs, dir world.ExitDir) rune {
return mg.downArrow
case world.North:
return mg.upArrow
+ case world.Northeast:
+ return mg.upRight
+ case world.Northwest:
+ return mg.upLeft
+ case world.Southeast:
+ return mg.downRight
+ case world.Southwest:
+ return mg.downLeft
}
return mg.connectorH
}