From 654bd07b9b3916bb3fb6e6568881b12d2b1e3c1a Mon Sep 17 00:00:00 2001 From: prosolis <5590409+prosolis@users.noreply.github.com> Date: Fri, 8 May 2026 15:01:42 -0700 Subject: [PATCH] Adv 2.0 D&D Phase 11 D10: ASCII !zone map renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the flat numbered list in !zone map with a horizontal ASCII layout — room glyphs joined by ── on top, status markers (✓/▶/·) underneath, plus a one-line legend. Closes the "ASCII !map renderer" sub-item of D6 polish from gogobee_dungeon_zones.md §8. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/plugin/dnd_zone_cmd.go | 54 +++++++++++++++++++++++----- internal/plugin/dnd_zone_cmd_test.go | 16 +++++++++ 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/internal/plugin/dnd_zone_cmd.go b/internal/plugin/dnd_zone_cmd.go index 6368806..80986e2 100644 --- a/internal/plugin/dnd_zone_cmd.go +++ b/internal/plugin/dnd_zone_cmd.go @@ -286,18 +286,56 @@ func (p *AdventurePlugin) zoneCmdMap(ctx MessageContext) error { } var b strings.Builder b.WriteString(fmt.Sprintf("**%s — Map**\n```\n", zone.Display)) - for i, rt := range run.RoomSeq { - marker := " " + b.WriteString(renderZoneMap(run.RoomSeq, run.CurrentRoom, cleared)) + b.WriteString("\n```\n") + b.WriteString("_E=Entry ?=Exploration T=Trap ★=Elite ☠=Boss · ✓ cleared ▶ here · pending_") + return p.SendDM(ctx.Sender, b.String()) +} + +// renderZoneMap produces a horizontal ASCII layout: a row of room glyphs +// connected with `──`, with a status row underneath showing cleared/here/ +// pending markers per room. Glyphs match the legend printed alongside the +// map. roomSeq is the run's room order; current is the 0-based index of +// the player's location; cleared maps room index → true for finished rooms. +func renderZoneMap(roomSeq []RoomType, current int, cleared map[int]bool) string { + if len(roomSeq) == 0 { + return "(no rooms)" + } + var top, bot strings.Builder + for i, rt := range roomSeq { + if i > 0 { + top.WriteString("──") + bot.WriteString(" ") + } + top.WriteString(roomGlyph(rt)) switch { case cleared[i]: - marker = "✓ " - case i == run.CurrentRoom: - marker = "▶ " + bot.WriteString("✓") + case i == current: + bot.WriteString("▶") + default: + bot.WriteString("·") } - b.WriteString(fmt.Sprintf("%s[%d] %s\n", marker, i+1, prettyRoomType(rt))) } - b.WriteString("```") - return p.SendDM(ctx.Sender, b.String()) + return top.String() + "\n" + bot.String() +} + +// roomGlyph returns the single-character ASCII map glyph for a RoomType. +func roomGlyph(rt RoomType) string { + switch rt { + case RoomEntry: + return "E" + case RoomExploration: + return "?" + case RoomTrap: + return "T" + case RoomElite: + return "★" + case RoomBoss: + return "☠" + default: + return "·" + } } // ── advance ───────────────────────────────────────────────────────────────── diff --git a/internal/plugin/dnd_zone_cmd_test.go b/internal/plugin/dnd_zone_cmd_test.go index 6fbd092..8214722 100644 --- a/internal/plugin/dnd_zone_cmd_test.go +++ b/internal/plugin/dnd_zone_cmd_test.go @@ -375,3 +375,19 @@ func TestTauntComplimentResponseLines_Deterministic(t *testing.T) { t.Errorf("taunt and compliment shouldn't collide on same key: %q", t1) } } + +func TestRenderZoneMap_LayoutAndMarkers(t *testing.T) { + seq := []RoomType{RoomEntry, RoomExploration, RoomTrap, RoomElite, RoomBoss} + cleared := map[int]bool{0: true, 1: true} + got := renderZoneMap(seq, 2, cleared) + want := "E──?──T──★──☠\n✓ ✓ ▶ · ·" + if got != want { + t.Errorf("renderZoneMap mismatch:\n got: %q\nwant: %q", got, want) + } +} + +func TestRenderZoneMap_Empty(t *testing.T) { + if got := renderZoneMap(nil, 0, nil); got != "(no rooms)" { + t.Errorf("empty seq: got %q", got) + } +}