feat: arrow-key room navigation, neighbor visibility, map UX improvements
- Exploration uses Up/Down + Enter instead of number keys - Adjacent rooms shown with cursor selection in HUD - Neighboring rooms visible on fog of war map - Room numbers displayed on tile map with type-colored markers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/tolelom/catacombs/game"
|
||||
)
|
||||
|
||||
func renderGame(state game.GameState, width, height int, targetCursor int) string {
|
||||
func renderGame(state game.GameState, width, height int, targetCursor int, moveCursor int) string {
|
||||
mapView := renderMap(state.Floor)
|
||||
hudView := renderHUD(state, targetCursor)
|
||||
hudView := renderHUD(state, targetCursor, moveCursor)
|
||||
logView := renderCombatLog(state.CombatLog)
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left,
|
||||
@@ -32,7 +32,7 @@ func renderMap(floor *dungeon.Floor) string {
|
||||
return header + "\n" + dungeon.RenderFloor(floor, floor.CurrentRoom, true)
|
||||
}
|
||||
|
||||
func renderHUD(state game.GameState, targetCursor int) string {
|
||||
func renderHUD(state game.GameState, targetCursor int, moveCursor int) string {
|
||||
var sb strings.Builder
|
||||
border := lipgloss.NewStyle().
|
||||
Border(lipgloss.NormalBorder()).
|
||||
@@ -112,7 +112,32 @@ func renderHUD(state game.GameState, targetCursor int) string {
|
||||
}
|
||||
}
|
||||
} else if state.Phase == game.PhaseExploring {
|
||||
sb.WriteString("\nChoose a room to enter (number) or [Q] quit")
|
||||
if state.Floor != nil && state.Floor.CurrentRoom >= 0 && state.Floor.CurrentRoom < len(state.Floor.Rooms) {
|
||||
current := state.Floor.Rooms[state.Floor.CurrentRoom]
|
||||
if len(current.Neighbors) > 0 {
|
||||
sb.WriteString("\n")
|
||||
selectedStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("46")).Bold(true)
|
||||
normalStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("255"))
|
||||
for i, n := range current.Neighbors {
|
||||
if n >= 0 && n < len(state.Floor.Rooms) {
|
||||
r := state.Floor.Rooms[n]
|
||||
status := r.Type.String()
|
||||
if r.Cleared {
|
||||
status = "Cleared"
|
||||
}
|
||||
marker := " "
|
||||
style := normalStyle
|
||||
if i == moveCursor {
|
||||
marker = "> "
|
||||
style = selectedStyle
|
||||
}
|
||||
sb.WriteString(style.Render(fmt.Sprintf("%sRoom %d: %s", marker, n, status)))
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.WriteString("[Up/Down] Select [Enter] Move [Q] Quit")
|
||||
}
|
||||
|
||||
return border.Render(sb.String())
|
||||
|
||||
35
ui/model.go
35
ui/model.go
@@ -46,6 +46,7 @@ type Model struct {
|
||||
classState classSelectState
|
||||
inputBuffer string
|
||||
targetCursor int
|
||||
moveCursor int // selected neighbor index during exploration
|
||||
}
|
||||
|
||||
func NewModel(width, height int, fingerprint string, lobby *game.Lobby, db *store.DB) Model {
|
||||
@@ -115,7 +116,7 @@ func (m Model) View() string {
|
||||
case screenClassSelect:
|
||||
return renderClassSelect(m.classState, m.width, m.height)
|
||||
case screenGame:
|
||||
return renderGame(m.gameState, m.width, m.height, m.targetCursor)
|
||||
return renderGame(m.gameState, m.width, m.height, m.targetCursor, m.moveCursor)
|
||||
case screenShop:
|
||||
return renderShop(m.gameState, m.width, m.height)
|
||||
case screenResult:
|
||||
@@ -312,16 +313,27 @@ func (m Model) updateGame(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if key, ok := msg.(tea.KeyMsg); ok {
|
||||
switch m.gameState.Phase {
|
||||
case game.PhaseExploring:
|
||||
if key.String() >= "0" && key.String() <= "9" {
|
||||
idx := int(key.String()[0] - '0')
|
||||
if m.session != nil {
|
||||
m.session.EnterRoom(idx)
|
||||
neighbors := m.getNeighbors()
|
||||
if isUp(key) {
|
||||
if m.moveCursor > 0 {
|
||||
m.moveCursor--
|
||||
}
|
||||
} else if isDown(key) {
|
||||
if m.moveCursor < len(neighbors)-1 {
|
||||
m.moveCursor++
|
||||
}
|
||||
} else if isEnter(key) {
|
||||
if m.session != nil && len(neighbors) > 0 {
|
||||
roomIdx := neighbors[m.moveCursor]
|
||||
m.session.EnterRoom(roomIdx)
|
||||
m.gameState = m.session.GetState()
|
||||
// If combat started, begin polling
|
||||
m.moveCursor = 0
|
||||
if m.gameState.Phase == game.PhaseCombat {
|
||||
return m, m.pollState()
|
||||
}
|
||||
}
|
||||
} else if isQuit(key) {
|
||||
return m, tea.Quit
|
||||
}
|
||||
case game.PhaseCombat:
|
||||
isPlayerDead := false
|
||||
@@ -361,6 +373,17 @@ func (m Model) updateGame(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) getNeighbors() []int {
|
||||
if m.gameState.Floor == nil {
|
||||
return nil
|
||||
}
|
||||
cur := m.gameState.Floor.CurrentRoom
|
||||
if cur < 0 || cur >= len(m.gameState.Floor.Rooms) {
|
||||
return nil
|
||||
}
|
||||
return m.gameState.Floor.Rooms[cur].Neighbors
|
||||
}
|
||||
|
||||
func (m Model) updateShop(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if key, ok := msg.(tea.KeyMsg); ok {
|
||||
switch key.String() {
|
||||
|
||||
Reference in New Issue
Block a user