From 533e460968b387cdce32587825d097588e2b1161 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Tue, 24 Mar 2026 14:47:53 +0900 Subject: [PATCH] feat: lobby shows player names and classes in room listing Co-Authored-By: Claude Sonnet 4.6 --- game/lobby.go | 52 +++++++++++++++++++++++++++++++++++++++++++--- game/lobby_test.go | 10 ++++----- ui/lobby_view.go | 24 +++++++++++++++++++-- ui/model.go | 13 ++++++++---- 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/game/lobby.go b/game/lobby.go index d278f40..558c4ac 100644 --- a/game/lobby.go +++ b/game/lobby.go @@ -13,10 +13,17 @@ const ( RoomPlaying ) +type LobbyPlayer struct { + Name string + Class string // empty until class selected + Fingerprint string + Ready bool +} + type LobbyRoom struct { Code string Name string - Players []string + Players []LobbyPlayer Status RoomStatus Session *GameSession } @@ -45,7 +52,7 @@ func (l *Lobby) CreateRoom(name string) string { return code } -func (l *Lobby) JoinRoom(code, playerName string) error { +func (l *Lobby) JoinRoom(code, playerName, fingerprint string) error { l.mu.Lock() defer l.mu.Unlock() room, ok := l.rooms[code] @@ -58,10 +65,49 @@ func (l *Lobby) JoinRoom(code, playerName string) error { if room.Status != RoomWaiting { return fmt.Errorf("room %s already in progress", code) } - room.Players = append(room.Players, playerName) + room.Players = append(room.Players, LobbyPlayer{Name: playerName, Fingerprint: fingerprint}) return nil } +func (l *Lobby) SetPlayerClass(code, fingerprint, class string) { + l.mu.Lock() + defer l.mu.Unlock() + if room, ok := l.rooms[code]; ok { + for i := range room.Players { + if room.Players[i].Fingerprint == fingerprint { + room.Players[i].Class = class + } + } + } +} + +func (l *Lobby) SetPlayerReady(code, fingerprint string, ready bool) { + l.mu.Lock() + defer l.mu.Unlock() + if room, ok := l.rooms[code]; ok { + for i := range room.Players { + if room.Players[i].Fingerprint == fingerprint { + room.Players[i].Ready = ready + } + } + } +} + +func (l *Lobby) AllReady(code string) bool { + l.mu.RLock() + defer l.mu.RUnlock() + room, ok := l.rooms[code] + if !ok || len(room.Players) == 0 { + return false + } + for _, p := range room.Players { + if !p.Ready { + return false + } + } + return true +} + func (l *Lobby) GetRoom(code string) *LobbyRoom { l.mu.RLock() defer l.mu.RUnlock() diff --git a/game/lobby_test.go b/game/lobby_test.go index 5a60040..1ec2fbd 100644 --- a/game/lobby_test.go +++ b/game/lobby_test.go @@ -17,7 +17,7 @@ func TestCreateRoom(t *testing.T) { func TestJoinRoom(t *testing.T) { lobby := NewLobby() code := lobby.CreateRoom("Test Room") - err := lobby.JoinRoom(code, "player1") + err := lobby.JoinRoom(code, "player1", "fp-player1") if err != nil { t.Errorf("Join failed: %v", err) } @@ -30,7 +30,7 @@ func TestJoinRoom(t *testing.T) { func TestRoomStatusTransition(t *testing.T) { l := NewLobby() code := l.CreateRoom("Test") - l.JoinRoom(code, "Alice") + l.JoinRoom(code, "Alice", "fp-alice") r := l.GetRoom(code) if r.Status != RoomWaiting { t.Errorf("new room should be Waiting, got %d", r.Status) @@ -40,7 +40,7 @@ func TestRoomStatusTransition(t *testing.T) { if r.Status != RoomPlaying { t.Errorf("started room should be Playing, got %d", r.Status) } - err := l.JoinRoom(code, "Bob") + err := l.JoinRoom(code, "Bob", "fp-bob") if err == nil { t.Error("should not be able to join a Playing room") } @@ -50,9 +50,9 @@ func TestJoinRoomFull(t *testing.T) { lobby := NewLobby() code := lobby.CreateRoom("Test Room") for i := 0; i < 4; i++ { - lobby.JoinRoom(code, "player") + lobby.JoinRoom(code, "player", "fp-player") } - err := lobby.JoinRoom(code, "player5") + err := lobby.JoinRoom(code, "player5", "fp-player5") if err == nil { t.Error("Should reject 5th player") } diff --git a/ui/lobby_view.go b/ui/lobby_view.go index df822df..76e8d12 100644 --- a/ui/lobby_view.go +++ b/ui/lobby_view.go @@ -20,10 +20,16 @@ type lobbyState struct { type roomInfo struct { Code string Name string - Players int + Players []playerInfo Status string } +type playerInfo struct { + Name string + Class string + Ready bool +} + func renderLobby(state lobbyState, width, height int) string { headerStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("205")). @@ -43,7 +49,21 @@ func renderLobby(state lobbyState, width, height int) string { marker = "> " } roomList += fmt.Sprintf("%s%s [%s] (%d/4) %s\n", - marker, r.Name, r.Code, r.Players, r.Status) + marker, r.Name, r.Code, len(r.Players), r.Status) + // Show players in selected room + if i == state.cursor { + for _, p := range r.Players { + cls := p.Class + if cls == "" { + cls = "..." + } + readyMark := " " + if p.Ready { + readyMark = "✓ " + } + roomList += fmt.Sprintf(" %s%s (%s)\n", readyMark, p.Name, cls) + } + } } if roomList == "" { roomList = " No rooms available. Create one!" diff --git a/ui/model.go b/ui/model.go index 54301c7..8f87bf9 100644 --- a/ui/model.go +++ b/ui/model.go @@ -209,7 +209,7 @@ func (m Model) updateLobby(msg tea.Msg) (tea.Model, tea.Cmd) { 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 { + if err := m.lobby.JoinRoom(m.lobbyState.codeInput, m.playerName, m.fingerprint); err == nil { m.roomCode = m.lobbyState.codeInput m.screen = screenClassSelect } @@ -231,7 +231,7 @@ func (m Model) updateLobby(msg tea.Msg) (tea.Model, tea.Cmd) { if isKey(key, "c") { if m.lobby != nil { code := m.lobby.CreateRoom(m.playerName + "'s Room") - m.lobby.JoinRoom(code, m.playerName) + m.lobby.JoinRoom(code, m.playerName, m.fingerprint) m.roomCode = code m.screen = screenClassSelect } @@ -249,7 +249,7 @@ func (m Model) updateLobby(msg tea.Msg) (tea.Model, tea.Cmd) { } else if isEnter(key) { if m.lobby != nil && len(m.lobbyState.rooms) > 0 { r := m.lobbyState.rooms[m.lobbyState.cursor] - if err := m.lobby.JoinRoom(r.Code, m.playerName); err == nil { + if err := m.lobby.JoinRoom(r.Code, m.playerName, m.fingerprint); err == nil { m.roomCode = r.Code m.screen = screenClassSelect } @@ -274,6 +274,7 @@ func (m Model) updateClassSelect(msg tea.Msg) (tea.Model, tea.Cmd) { } else if isEnter(key) { if m.lobby != nil { selectedClass := classOptions[m.classState.cursor].class + m.lobby.SetPlayerClass(m.roomCode, m.fingerprint, selectedClass.String()) room := m.lobby.GetRoom(m.roomCode) if room != nil { if room.Session == nil { @@ -523,10 +524,14 @@ func (m Model) withRefreshedLobby() Model { if r.Status == game.RoomPlaying { status = "Playing" } + players := make([]playerInfo, len(r.Players)) + for j, p := range r.Players { + players[j] = playerInfo{Name: p.Name, Class: p.Class, Ready: p.Ready} + } m.lobbyState.rooms[i] = roomInfo{ Code: r.Code, Name: r.Name, - Players: len(r.Players), + Players: players, Status: status, } }