diff options
Diffstat (limited to 'internal/game/render_map.go')
| -rw-r--r-- | internal/game/render_map.go | 277 |
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 } |
