aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/color/color.go3
-rw-r--r--internal/game/cmd_symbol.go4
-rw-r--r--internal/game/map_test.go3
-rw-r--r--internal/game/render_map.go77
-rw-r--r--internal/world/room.go44
5 files changed, 57 insertions, 74 deletions
diff --git a/internal/color/color.go b/internal/color/color.go
index 13fca69..e971382 100644
--- a/internal/color/color.go
+++ b/internal/color/color.go
@@ -298,9 +298,6 @@ func Parse(input string) ColorSpec {
// parseColorIndex parses a 1-2 digit hexadecimal color index (00-FF, case
// insensitive) into its 0-255 value. Out-of-range or non-hex input fails.
func parseColorIndex(s string) (int, bool) {
- if s == "" {
- return 0, false
- }
n, err := strconv.ParseUint(s, 16, 0)
if err != nil || n > 255 {
return 0, false
diff --git a/internal/game/cmd_symbol.go b/internal/game/cmd_symbol.go
index 0c3014b..b18f3f7 100644
--- a/internal/game/cmd_symbol.go
+++ b/internal/game/cmd_symbol.go
@@ -28,7 +28,7 @@ func (g *Game) executeSymbol(sess *net.Session, args []string, rawInput string)
return
}
- // ponytail: extract symbol char from rawInput to preserve case, since
+ // extract symbol char from rawInput to preserve case, since
// handleGameCommand lowercases args before dispatch (same pattern as say).
char := args[0]
if idx := strings.Index(strings.ToLower(rawInput), "symbol"); idx >= 0 {
@@ -54,7 +54,7 @@ func (g *Game) executeSymbol(sess *net.Session, args []string, rawInput string)
if !cleared {
r, size := utf8.DecodeRuneInString(char)
if size == 0 || size != len(char) || r == utf8.RuneError {
- // ponytail: color-only mode — args are a color spec, keep existing char
+ // color-only mode — args are a color spec, keep existing char
colorSpec := strings.Join(args, " ")
spec := color.Parse(colorSpec)
if spec.Empty() {
diff --git a/internal/game/map_test.go b/internal/game/map_test.go
index 05e70d0..4869d38 100644
--- a/internal/game/map_test.go
+++ b/internal/game/map_test.go
@@ -25,7 +25,6 @@ func writeTempRoom(t *testing.T, dir string, id int, body string) {
}
}
-
// TestMapConnectorGlyphs verifies the directional link rendering: bidirectional
// links draw a bar, one-way (or one-side-blocked) links draw a directional
// arrow, and links with no traversable direction draw a blocked 'X'.
@@ -105,8 +104,6 @@ func TestMapConnectorGlyphs(t *testing.T) {
}
}
-
-
func TestBuildTinyMap(t *testing.T) {
g := &Game{
Deps: Deps{World: world.New("../../data")},
diff --git a/internal/game/render_map.go b/internal/game/render_map.go
index 049db06..ff9ffa5 100644
--- a/internal/game/render_map.go
+++ b/internal/game/render_map.go
@@ -174,8 +174,8 @@ func buildTinyMap(g *Game, sess *net.Session, roomID int, mg mapGlyphs) []string
if !leftOK || !rightOK {
continue
}
- if exitsConnect(g, leftRoom, rightRoom, world.East, world.West) {
- grid[(y+1)*2][(x+1)*2+1] = ctx.connectorCell(leftRoom, rightRoom, world.East, world.West)
+ if cell, ok := ctx.connectorCell(leftRoom, rightRoom, world.East, world.West); ok {
+ grid[(y+1)*2][(x+1)*2+1] = cell
}
}
}
@@ -189,8 +189,8 @@ func buildTinyMap(g *Game, sess *net.Session, roomID int, mg mapGlyphs) []string
if !topOK || !bottomOK {
continue
}
- if exitsConnect(g, topRoom, bottomRoom, world.South, world.North) {
- grid[(y+1)*2+1][(x+1)*2] = ctx.connectorCell(topRoom, bottomRoom, world.South, world.North)
+ if cell, ok := ctx.connectorCell(topRoom, bottomRoom, world.South, world.North); ok {
+ grid[(y+1)*2+1][(x+1)*2] = cell
}
}
}
@@ -260,22 +260,22 @@ func buildFullMap(g *Game, sess *net.Session, roomID, mapWidth, mapHeight int, m
for pos, rid := range bg.posToRoom {
x, y := pos[0], pos[1]
- if rightID, ok := bg.posToRoom[[2]int{x + 1, y}]; ok {
- if exitsConnect(g, rid, rightID, world.East, world.West) {
- gr := cy + y*2
- gc := cx + x*2 + 1
- if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
- grid[gr][gc] = ctx.connectorCell(rid, rightID, world.East, world.West)
+ if rightID, exists := bg.posToRoom[[2]int{x + 1, y}]; exists {
+ gr := cy + y*2
+ gc := cx + x*2 + 1
+ if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
+ if cell, ok := ctx.connectorCell(rid, rightID, world.East, world.West); ok {
+ grid[gr][gc] = cell
}
}
}
- if bottomID, ok := bg.posToRoom[[2]int{x, y + 1}]; ok {
- if exitsConnect(g, rid, bottomID, world.South, world.North) {
- gr := cy + y*2 + 1
- gc := cx + x*2
- if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
- grid[gr][gc] = ctx.connectorCell(rid, bottomID, world.South, world.North)
+ if bottomID, exists := bg.posToRoom[[2]int{x, y + 1}]; exists {
+ gr := cy + y*2 + 1
+ gc := cx + x*2
+ if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
+ if cell, ok := ctx.connectorCell(rid, bottomID, world.South, world.North); ok {
+ grid[gr][gc] = cell
}
}
}
@@ -302,7 +302,7 @@ func resolveMapAt(g *Game, sess *net.Session) color.ColorSpec {
if sess != nil {
return g.resolveColor(sess, "map_at")
}
- return color.NoColor()
+ return color.Parse("0F")
}
func resolveDim(g *Game, sess *net.Session) color.ColorSpec {
@@ -347,17 +347,23 @@ func (c *mapRenderCtx) nodeSpec(roomID int) color.ColorSpec {
}
// connectorCell builds the link cell between two grid-adjacent rooms based on
-// the per-direction traversability of the two exits joining them:
-// - both directions open -> bidirectional bar (- / |)
-// - exactly one open -> arrow pointing along the open direction
-// - neither open (>=1 blocked) -> blocked 'X'
+// the per-direction traversability of the two exits joining them. ok is false
+// when there is no link at all, so the caller draws nothing:
+// - both directions open -> bidirectional bar (- / |)
+// - exactly one open -> arrow pointing along the open direction
+// - >=1 exists but none open -> blocked 'X'
+// - neither exit exists -> ok == false (no cell)
//
// Bars and arrows use the normal link coloring (dim if an endpoint is unvisited,
// otherwise the gradient average); only 'X' uses the blocked color.
-func (c *mapRenderCtx) connectorCell(roomA, roomB int, dirAB, dirBA world.ExitDir) mapCell {
+func (c *mapRenderCtx) connectorCell(roomA, roomB int, dirAB, dirBA world.ExitDir) (mapCell, bool) {
fwd := exitStateTo(c.g, c.sess, roomA, dirAB, roomB) // A -> B
bwd := exitStateTo(c.g, c.sess, roomB, dirBA, roomA) // B -> A
+ if fwd == exitAbsent && bwd == exitAbsent {
+ return mapCell{}, false
+ }
+
var glyph rune
switch {
case fwd == exitOpen && bwd == exitOpen:
@@ -367,15 +373,14 @@ func (c *mapRenderCtx) connectorCell(roomA, roomB int, dirAB, dirBA world.ExitDi
case bwd == exitOpen:
glyph = arrowGlyph(c.mg, dirBA)
default:
- // The caller only draws a connector when at least one exit exists, so
- // reaching here means every existing direction is blocked.
- return mapCell{char: 'X', spec: c.blockedSpec}
+ // At least one exit exists but none are currently traversable.
+ return mapCell{char: 'X', spec: c.blockedSpec}, true
}
if c.visited != nil && (!c.visited[roomA] || !c.visited[roomB]) {
- return mapCell{char: glyph, spec: c.dimSpec}
+ return mapCell{char: glyph, spec: c.dimSpec}, true
}
- return mapCell{char: glyph, spec: color.Average(c.nodeSpec(roomA), c.nodeSpec(roomB))}
+ return mapCell{char: glyph, spec: color.Average(c.nodeSpec(roomA), c.nodeSpec(roomB))}, true
}
type exitState int
@@ -451,7 +456,7 @@ func roomMapSymbol(g *Game, sess *net.Session, roomID int, unvisited bool) (rune
if data.Color != "" {
spec = color.Parse(data.Color)
}
- // ponytail: non-ASCII custom symbols fall back to 'o'
+ // non-ASCII custom symbols fall back to 'o'
// when unicode mode is off, but preserve the color.
if !sess.Player.OptionBool("unicode") && r > 127 {
return 'o', spec
@@ -470,22 +475,6 @@ func roomMapSymbol(g *Game, sess *net.Session, roomID int, unvisited bool) (rune
return '■', roomSpec
}
-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
- }
- id, ok := exitTarget(r2, dir21)
- return ok && id == room1
-}
-
func exitTarget(room *world.Room, dir world.ExitDir) (int, bool) {
if room == nil {
return 0, false
diff --git a/internal/world/room.go b/internal/world/room.go
index d542bf4..8f13b82 100644
--- a/internal/world/room.go
+++ b/internal/world/room.go
@@ -66,15 +66,15 @@ func (e *ExitDef) UnmarshalYAML(value *yaml.Node) error {
}
type Room struct {
- ID int `yaml:"id"`
- Name string `yaml:"name"`
- Color string `yaml:"color"`
- Description behavior.DescList `yaml:"description"`
- Exits map[ExitDir]ExitDef `yaml:"exits"`
- Objects []RoomObject `yaml:"objects"`
- ItemSpawns []SpawnDef `yaml:"item_spawns"`
- Mobs []RoomMob `yaml:"mobs"`
- OnEnter []EnterStep `yaml:"on_enter"`
+ ID int `yaml:"id"`
+ Name string `yaml:"name"`
+ Color string `yaml:"color"`
+ Description behavior.DescList `yaml:"description"`
+ Exits map[ExitDir]ExitDef `yaml:"exits"`
+ Objects []RoomObject `yaml:"objects"`
+ ItemSpawns []SpawnDef `yaml:"item_spawns"`
+ Mobs []RoomMob `yaml:"mobs"`
+ OnEnter []EnterStep `yaml:"on_enter"`
Hazard string `yaml:"hazard"`
BlockTransport bool `yaml:"block_transport"`
Triggers []TriggerDef `yaml:"triggers"`
@@ -100,19 +100,19 @@ func (rm *RoomMob) UnmarshalYAML(value *yaml.Node) error {
}
type EnterStep struct {
- Message string `yaml:"message"`
- Condition *behavior.Condition `yaml:"condition"`
- Delay int `yaml:"delay"`
- SetFlags map[string]any `yaml:"set_flags"`
- SetPlayerFlags map[string]any `yaml:"set_player_flags"`
- Broadcast string `yaml:"broadcast"`
- BroadcastGlobal string `yaml:"broadcast_global"`
- SpawnMob *SpawnMobConfig `yaml:"spawn_mob"`
- DespawnMob string `yaml:"despawn_mob"`
- GiveItem string `yaml:"give_item"`
- TakeItem string `yaml:"take_item"`
- Teleport int `yaml:"teleport"`
- Heal int `yaml:"heal"`
+ Message string `yaml:"message"`
+ Condition *behavior.Condition `yaml:"condition"`
+ Delay int `yaml:"delay"`
+ SetFlags map[string]any `yaml:"set_flags"`
+ SetPlayerFlags map[string]any `yaml:"set_player_flags"`
+ Broadcast string `yaml:"broadcast"`
+ BroadcastGlobal string `yaml:"broadcast_global"`
+ SpawnMob *SpawnMobConfig `yaml:"spawn_mob"`
+ DespawnMob string `yaml:"despawn_mob"`
+ GiveItem string `yaml:"give_item"`
+ TakeItem string `yaml:"take_item"`
+ Teleport int `yaml:"teleport"`
+ Heal int `yaml:"heal"`
}
// IsTimed reports whether the step carries enter-sequence semantics (any