feat: lobby join-by-code with J key and 4-char input
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,16 +2,19 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
)
|
)
|
||||||
|
|
||||||
type lobbyState struct {
|
type lobbyState struct {
|
||||||
rooms []roomInfo
|
rooms []roomInfo
|
||||||
input string
|
input string
|
||||||
cursor int
|
cursor int
|
||||||
creating bool
|
creating bool
|
||||||
roomName string
|
roomName string
|
||||||
|
joining bool
|
||||||
|
codeInput string
|
||||||
}
|
}
|
||||||
|
|
||||||
type roomInfo struct {
|
type roomInfo struct {
|
||||||
@@ -31,7 +34,7 @@ func renderLobby(state lobbyState, width, height int) string {
|
|||||||
Padding(0, 1)
|
Padding(0, 1)
|
||||||
|
|
||||||
header := headerStyle.Render("── Lobby ──")
|
header := headerStyle.Render("── Lobby ──")
|
||||||
menu := "[C] Create Room [J] Join by Code [Q] Back"
|
menu := "[C] Create Room [J] Join by Code [Up/Down] Select [Enter] Join [Q] Back"
|
||||||
|
|
||||||
roomList := ""
|
roomList := ""
|
||||||
for i, r := range state.rooms {
|
for i, r := range state.rooms {
|
||||||
@@ -45,6 +48,10 @@ func renderLobby(state lobbyState, width, height int) string {
|
|||||||
if roomList == "" {
|
if roomList == "" {
|
||||||
roomList = " No rooms available. Create one!"
|
roomList = " No rooms available. Create one!"
|
||||||
}
|
}
|
||||||
|
if state.joining {
|
||||||
|
inputStr := state.codeInput + strings.Repeat("_", 4-len(state.codeInput))
|
||||||
|
roomList += fmt.Sprintf("\n Enter room code: [%s] (Esc to cancel)\n", inputStr)
|
||||||
|
}
|
||||||
|
|
||||||
return lipgloss.JoinVertical(lipgloss.Left,
|
return lipgloss.JoinVertical(lipgloss.Left,
|
||||||
header,
|
header,
|
||||||
|
|||||||
51
ui/model.go
51
ui/model.go
@@ -38,12 +38,13 @@ type Model struct {
|
|||||||
store *store.DB
|
store *store.DB
|
||||||
|
|
||||||
// Per-session state
|
// Per-session state
|
||||||
session *game.GameSession
|
session *game.GameSession
|
||||||
roomCode string
|
roomCode string
|
||||||
gameState game.GameState
|
gameState game.GameState
|
||||||
lobbyState lobbyState
|
lobbyState lobbyState
|
||||||
classState classSelectState
|
classState classSelectState
|
||||||
inputBuffer string
|
inputBuffer string
|
||||||
|
targetCursor int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewModel(width, height int, fingerprint string, lobby *game.Lobby, db *store.DB) Model {
|
func NewModel(width, height int, fingerprint string, lobby *game.Lobby, db *store.DB) Model {
|
||||||
@@ -113,7 +114,7 @@ func (m Model) View() string {
|
|||||||
case screenClassSelect:
|
case screenClassSelect:
|
||||||
return renderClassSelect(m.classState, m.width, m.height)
|
return renderClassSelect(m.classState, m.width, m.height)
|
||||||
case screenGame:
|
case screenGame:
|
||||||
return renderGame(m.gameState, m.width, m.height)
|
return renderGame(m.gameState, m.width, m.height, m.targetCursor)
|
||||||
case screenShop:
|
case screenShop:
|
||||||
return renderShop(m.gameState, m.width, m.height)
|
return renderShop(m.gameState, m.width, m.height)
|
||||||
case screenResult:
|
case screenResult:
|
||||||
@@ -179,6 +180,29 @@ func (m Model) updateTitle(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
func (m Model) updateLobby(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m Model) updateLobby(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
if key, ok := msg.(tea.KeyMsg); ok {
|
if key, ok := msg.(tea.KeyMsg); ok {
|
||||||
|
// Join-by-code input mode
|
||||||
|
if m.lobbyState.joining {
|
||||||
|
if isEnter(key) && len(m.lobbyState.codeInput) == 4 {
|
||||||
|
if m.lobby != nil {
|
||||||
|
if err := m.lobby.JoinRoom(m.lobbyState.codeInput, m.playerName); err == nil {
|
||||||
|
m.roomCode = m.lobbyState.codeInput
|
||||||
|
m.screen = screenClassSelect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.lobbyState.joining = false
|
||||||
|
m.lobbyState.codeInput = ""
|
||||||
|
} else if isKey(key, "esc") || key.Type == tea.KeyEsc {
|
||||||
|
m.lobbyState.joining = false
|
||||||
|
m.lobbyState.codeInput = ""
|
||||||
|
} else if key.Type == tea.KeyBackspace && len(m.lobbyState.codeInput) > 0 {
|
||||||
|
m.lobbyState.codeInput = m.lobbyState.codeInput[:len(m.lobbyState.codeInput)-1]
|
||||||
|
} else if len(key.Runes) == 1 && len(m.lobbyState.codeInput) < 4 {
|
||||||
|
ch := strings.ToUpper(string(key.Runes))
|
||||||
|
m.lobbyState.codeInput += ch
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
// Normal lobby key handling
|
||||||
if isKey(key, "c") {
|
if isKey(key, "c") {
|
||||||
if m.lobby != nil {
|
if m.lobby != nil {
|
||||||
code := m.lobby.CreateRoom(m.playerName + "'s Room")
|
code := m.lobby.CreateRoom(m.playerName + "'s Room")
|
||||||
@@ -186,6 +210,9 @@ func (m Model) updateLobby(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.roomCode = code
|
m.roomCode = code
|
||||||
m.screen = screenClassSelect
|
m.screen = screenClassSelect
|
||||||
}
|
}
|
||||||
|
} else if isKey(key, "j") {
|
||||||
|
m.lobbyState.joining = true
|
||||||
|
m.lobbyState.codeInput = ""
|
||||||
} else if isUp(key) {
|
} else if isUp(key) {
|
||||||
if m.lobbyState.cursor > 0 {
|
if m.lobbyState.cursor > 0 {
|
||||||
m.lobbyState.cursor--
|
m.lobbyState.cursor--
|
||||||
@@ -306,12 +333,18 @@ func (m Model) updateGame(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
if isPlayerDead {
|
if isPlayerDead {
|
||||||
return m, m.pollState()
|
return m, m.pollState()
|
||||||
}
|
}
|
||||||
|
if isKey(key, "tab") || key.Type == tea.KeyTab {
|
||||||
|
if len(m.gameState.Monsters) > 0 {
|
||||||
|
m.targetCursor = (m.targetCursor + 1) % len(m.gameState.Monsters)
|
||||||
|
}
|
||||||
|
return m, m.pollState()
|
||||||
|
}
|
||||||
if m.session != nil {
|
if m.session != nil {
|
||||||
switch key.String() {
|
switch key.String() {
|
||||||
case "1":
|
case "1":
|
||||||
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionAttack, TargetIdx: 0})
|
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionAttack, TargetIdx: m.targetCursor})
|
||||||
case "2":
|
case "2":
|
||||||
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionSkill, TargetIdx: 0})
|
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionSkill, TargetIdx: m.targetCursor})
|
||||||
case "3":
|
case "3":
|
||||||
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionItem})
|
m.session.SubmitAction(m.playerName, game.PlayerAction{Type: game.ActionItem})
|
||||||
case "4":
|
case "4":
|
||||||
|
|||||||
Reference in New Issue
Block a user