Pressing Q in game now cleans up the session and returns to lobby, instead of calling tea.Quit which terminates the SSH connection and shows "Connection lost" on web. Only Ctrl+C force-quits now. Also cleans up room on exit so abandoned rooms don't persist in lobby. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
172 lines
3.3 KiB
Go
172 lines
3.3 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/tolelom/catacombs/game"
|
|
"github.com/tolelom/catacombs/store"
|
|
)
|
|
|
|
type tickMsg struct{}
|
|
|
|
type Model struct {
|
|
currentScreen Screen
|
|
ctx *Context
|
|
}
|
|
|
|
func NewModel(width, height int, fingerprint string, lobby *game.Lobby, db *store.DB) Model {
|
|
if width == 0 {
|
|
width = 80
|
|
}
|
|
if height == 0 {
|
|
height = 24
|
|
}
|
|
ctx := &Context{
|
|
Width: width,
|
|
Height: height,
|
|
Fingerprint: fingerprint,
|
|
Lobby: lobby,
|
|
Store: db,
|
|
}
|
|
|
|
// Determine initial screen
|
|
var initialScreen Screen
|
|
if fingerprint != "" && db != nil {
|
|
if name, err := db.GetProfile(fingerprint); err == nil {
|
|
ctx.PlayerName = name
|
|
}
|
|
}
|
|
initialScreen = NewTitleScreen()
|
|
|
|
return Model{
|
|
currentScreen: initialScreen,
|
|
ctx: ctx,
|
|
}
|
|
}
|
|
|
|
func (m Model) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.WindowSizeMsg:
|
|
m.ctx.Width = msg.Width
|
|
m.ctx.Height = msg.Height
|
|
if m.ctx.Width == 0 {
|
|
m.ctx.Width = 80
|
|
}
|
|
if m.ctx.Height == 0 {
|
|
m.ctx.Height = 24
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
next, cmd := m.currentScreen.Update(msg, m.ctx)
|
|
m.currentScreen = next
|
|
return m, cmd
|
|
}
|
|
|
|
func (m Model) View() string {
|
|
if m.ctx.Width < 80 || m.ctx.Height < 24 {
|
|
return fmt.Sprintf("Terminal too small (%dx%d). Minimum: 80x24.", m.ctx.Width, m.ctx.Height)
|
|
}
|
|
return m.currentScreen.View(m.ctx)
|
|
}
|
|
|
|
// Key helper functions used by all screens.
|
|
func isKey(key tea.KeyMsg, names ...string) bool {
|
|
s := key.String()
|
|
for _, n := range names {
|
|
if s == n {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isEnter(key tea.KeyMsg) bool {
|
|
return isKey(key, "enter") || key.Type == tea.KeyEnter
|
|
}
|
|
|
|
func isQuit(key tea.KeyMsg) bool {
|
|
return isKey(key, "q", "ctrl+c") || key.Type == tea.KeyCtrlC
|
|
}
|
|
|
|
func isForceQuit(key tea.KeyMsg) bool {
|
|
return isKey(key, "ctrl+c") || key.Type == tea.KeyCtrlC
|
|
}
|
|
|
|
func isUp(key tea.KeyMsg) bool {
|
|
return isKey(key, "up") || key.Type == tea.KeyUp
|
|
}
|
|
|
|
func isDown(key tea.KeyMsg) bool {
|
|
return isKey(key, "down") || key.Type == tea.KeyDown
|
|
}
|
|
|
|
// Keep these for backward compatibility with tests
|
|
// screen enum kept temporarily for test compatibility
|
|
type screen int
|
|
|
|
const (
|
|
screenTitle screen = iota
|
|
screenLobby
|
|
screenClassSelect
|
|
screenWaiting
|
|
screenGame
|
|
screenShop
|
|
screenResult
|
|
screenHelp
|
|
screenStats
|
|
screenAchievements
|
|
screenLeaderboard
|
|
screenNickname
|
|
)
|
|
|
|
// screenType returns the screen enum for the current screen (for test compatibility).
|
|
func (m Model) screenType() screen {
|
|
switch m.currentScreen.(type) {
|
|
case *TitleScreen:
|
|
return screenTitle
|
|
case *LobbyScreen:
|
|
return screenLobby
|
|
case *ClassSelectScreen:
|
|
return screenClassSelect
|
|
case *WaitingScreen:
|
|
return screenWaiting
|
|
case *GameScreen:
|
|
return screenGame
|
|
case *ShopScreen:
|
|
return screenShop
|
|
case *ResultScreen:
|
|
return screenResult
|
|
case *HelpScreen:
|
|
return screenHelp
|
|
case *StatsScreen:
|
|
return screenStats
|
|
case *AchievementsScreen:
|
|
return screenAchievements
|
|
case *LeaderboardScreen:
|
|
return screenLeaderboard
|
|
case *NicknameScreen:
|
|
return screenNickname
|
|
}
|
|
return screenTitle
|
|
}
|
|
|
|
// Convenience accessors for test compatibility
|
|
func (m Model) playerName() string {
|
|
return m.ctx.PlayerName
|
|
}
|
|
|
|
func (m Model) roomCode() string {
|
|
return m.ctx.RoomCode
|
|
}
|
|
|
|
func (m Model) session() *game.GameSession {
|
|
return m.ctx.Session
|
|
}
|
|
|