refactor(lobby): drop 'New' prefix from Room type and store functions

The 'New' prefix was a phase-06 naming compromise to avoid colliding
with a legacy Room type. That legacy type is gone now, so the prefix
is misleading — in Go 'NewX' conventionally reads as a constructor,
not a type.

- NewRoom type → Room
- JoinNewRoom → JoinRoom
- GetNewRoom → GetRoom
- LeaveNewRoom → LeaveRoom
- WatchNewRoom → WatchRoom
- UnwatchNewRoom → UnwatchRoom
- deleteNewRoom → deleteRoom

Also remove dead code uncovered by the pass:
- Delete lobby/events.go entirely — RoomEvent + BroadcastEvent wrapped
  a no-op placeholder, never called.
- Delete BroadcastToNewRoom no-op from lobby/store.go — kept only for
  the dead BroadcastEvent wrapper.
- Delete TestBroadcastToRoom_SkipsExcludedIDs — theater test that never
  actually called BroadcastToNewRoom, just built a manual exclude map.

go vet + go test ./... green.
This commit is contained in:
2026-04-11 16:31:36 +07:00
parent 4c068849c4
commit 1d80d334e0
19 changed files with 136 additions and 217 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ func reapIdleRooms() {
if nPlayers == 0 && nSpectators == 0 && time.Since(lastActive) > idleRoomTimeout {
log.Infof("[cleanup] removing idle room %d (no activity for >%s)\n", id, idleRoomTimeout)
store.mu.Lock()
deleteNewRoom(id)
deleteRoom(id)
store.mu.Unlock()
}
}
-15
View File
@@ -1,15 +0,0 @@
package lobby
// RoomEvent is a message broadcast to all participants in a room (players + spectators).
// Simplest form: a plain string payload. Phase-07 will expand this with typed events
// for the spectator fan-out goroutine.
type RoomEvent struct {
Payload string
}
// BroadcastEvent sends a RoomEvent to every player and spectator in the room,
// excluding the IDs listed in excludePlayerIDs.
// Acquires room.RLock internally; must NOT be called while holding room.Lock().
func BroadcastEvent(room *NewRoom, event RoomEvent, excludePlayerIDs ...int64) {
BroadcastToNewRoom(room, event.Payload, excludePlayerIDs...)
}
+7 -7
View File
@@ -33,14 +33,14 @@ type GameMove struct {
Timestamp time.Time
}
// NewRoom is the new domain model for a game room.
// Room is the new domain model for a game room.
// It embeds game.Board directly and carries all fields from the caro Java domain:
// blackPlayerId, whitePlayerId, currentTurn, moveHistory, watcherList, roomOwner,
// type (PVP|PVE), difficultyCoefficient.
//
// sync.RWMutex protects Board, Players, Spectators, MoveHistory, and Status.
// Never hold the lock while calling player.WriteString/sendFn — copy fields first, then release.
type NewRoom struct {
type Room struct {
sync.RWMutex
ID int64
@@ -86,7 +86,7 @@ type NewRoom struct {
// ApplyMove places a piece at (row, col), appends to MoveHistory, flips CurrentTurn,
// and returns the new GameResult. Caller must hold room.Lock().
func (r *NewRoom) ApplyMove(row, col int, piece game.Piece, playerID int64) (game.GameResult, error) {
func (r *Room) ApplyMove(row, col int, piece game.Piece, playerID int64) (game.GameResult, error) {
if !r.Board.MakeMove(row, col, piece) {
return game.InProgress, ErrRoomFull // reuse as "invalid move" sentinel; phase-06 adds ErrInvalidMove
}
@@ -108,7 +108,7 @@ func (r *NewRoom) ApplyMove(row, col int, piece game.Piece, playerID int64) (gam
// Reset clears the board and history, returning the room to WAITING status.
// For PVE rooms it re-randomizes color assignment and creates a fresh AI.
func (r *NewRoom) Reset(rng int64) {
func (r *Room) Reset(rng int64) {
r.Board.Reset()
r.MoveHistory = nil
r.CurrentTurn = game.Black
@@ -141,7 +141,7 @@ type RoomSnapshot struct {
// Snapshot returns a consistent read of room state safe to send without holding the lock.
// Caller must NOT hold any lock when calling this — it acquires RLock internally.
func (r *NewRoom) Snapshot() RoomSnapshot {
func (r *Room) Snapshot() RoomSnapshot {
r.RLock()
defer r.RUnlock()
@@ -170,11 +170,11 @@ func (r *NewRoom) Snapshot() RoomSnapshot {
// PlayerCount returns the number of human players currently in the room.
// Caller may or may not hold RLock — reads len which is safe under Go's memory model
// only when protected; callers should hold at least RLock for correctness.
func (r *NewRoom) PlayerCount() int {
func (r *Room) PlayerCount() int {
return len(r.Players)
}
// IsOwner returns true if playerID is the room owner.
func (r *NewRoom) IsOwner(playerID int64) bool {
func (r *Room) IsOwner(playerID int64) bool {
return r.OwnerID == playerID
}
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"github.com/tiennm99/gomoku/server/game"
)
// newTestRoom builds a minimal NewRoom for unit tests without going through the store.
func newTestRoom(t RoomType, ownerID int64) *NewRoom {
r := &NewRoom{
// newTestRoom builds a minimal Room for unit tests without going through the store.
func newTestRoom(t RoomType, ownerID int64) *Room {
r := &Room{
ID: 1,
OwnerID: ownerID,
OwnerNickname: "owner",
+24 -31
View File
@@ -19,7 +19,7 @@ import (
// - Per-room mutations use room.Lock() / room.RLock() independently.
var store = &roomStore{
players: make(map[int64]*Player),
rooms: make(map[int64]*NewRoom),
rooms: make(map[int64]*Room),
nextPlayerID: 1,
nextRoomID: 1,
}
@@ -27,7 +27,7 @@ var store = &roomStore{
type roomStore struct {
mu sync.RWMutex
players map[int64]*Player
rooms map[int64]*NewRoom
rooms map[int64]*Room
nextPlayerID int64
nextRoomID int64
}
@@ -65,18 +65,18 @@ func RemovePlayer(id int64) {
}
store.mu.Unlock()
if ok && p.RoomID != 0 {
LeaveNewRoom(p)
LeaveRoom(p)
}
}
// --- Room creation ---
// CreatePvpRoom creates a PVP room owned by the given player.
func CreatePvpRoom(owner *Player) (*NewRoom, error) {
func CreatePvpRoom(owner *Player) (*Room, error) {
store.mu.Lock()
id := store.nextRoomID
store.nextRoomID++
r := &NewRoom{
r := &Room{
ID: id,
OwnerID: owner.ID,
OwnerNickname: owner.Name,
@@ -101,7 +101,7 @@ func CreatePvpRoom(owner *Player) (*NewRoom, error) {
// CreatePveRoom creates a PVE room. difficulty must be 1, 2, or 3.
// The human player is randomly assigned Black or White; the AI takes the other side.
func CreatePveRoom(owner *Player, difficulty int) (*NewRoom, error) {
func CreatePveRoom(owner *Player, difficulty int) (*Room, error) {
if difficulty < 1 || difficulty > 3 {
return nil, ErrInvalidDifficulty
}
@@ -133,7 +133,7 @@ func CreatePveRoom(owner *Player, difficulty int) (*NewRoom, error) {
store.mu.Lock()
id := store.nextRoomID
store.nextRoomID++
r := &NewRoom{
r := &Room{
ID: id,
OwnerID: owner.ID,
OwnerNickname: owner.Name,
@@ -158,8 +158,8 @@ func CreatePveRoom(owner *Player, difficulty int) (*NewRoom, error) {
// --- Room lookups ---
// GetNewRoom returns the room with the given ID, or (nil, false) if not found.
func GetNewRoom(id int64) (*NewRoom, bool) {
// GetRoom returns the room with the given ID, or (nil, false) if not found.
func GetRoom(id int64) (*Room, bool) {
store.mu.RLock()
r, ok := store.rooms[id]
store.mu.RUnlock()
@@ -168,9 +168,9 @@ func GetNewRoom(id int64) (*NewRoom, bool) {
// GetAllRooms returns a snapshot slice of all rooms sorted by ID ascending.
// The slice is a copy; callers must not mutate rooms through it without locking.
func GetAllRooms() []*NewRoom {
func GetAllRooms() []*Room {
store.mu.RLock()
list := make([]*NewRoom, 0, len(store.rooms))
list := make([]*Room, 0, len(store.rooms))
for _, r := range store.rooms {
list = append(list, r)
}
@@ -179,17 +179,17 @@ func GetAllRooms() []*NewRoom {
return list
}
// deleteNewRoom removes a room from the store. Caller must hold store.mu.Lock().
func deleteNewRoom(id int64) {
// deleteRoom removes a room from the store. Caller must hold store.mu.Lock().
func deleteRoom(id int64) {
delete(store.rooms, id)
}
// --- Room membership ---
// JoinNewRoom adds a player to a room as a human participant.
// JoinRoom adds a player to a room as a human participant.
// Returns ErrRoomNotFound, ErrRoomFull, or ErrRoomPlaying on failure.
// On success, sets player.RoomID.
func JoinNewRoom(roomID int64, player *Player) error {
func JoinRoom(roomID int64, player *Player) error {
store.mu.RLock()
r, ok := store.rooms[roomID]
store.mu.RUnlock()
@@ -221,10 +221,10 @@ func JoinNewRoom(roomID int64, player *Player) error {
return nil
}
// LeaveNewRoom removes a player from their current room (Players or Spectators).
// LeaveRoom removes a player from their current room (Players or Spectators).
// If the room becomes empty it is deleted from the store.
// Broadcasts nothing — callers handle messaging.
func LeaveNewRoom(player *Player) {
func LeaveRoom(player *Player) {
if player.RoomID == 0 {
return
}
@@ -269,7 +269,7 @@ func LeaveNewRoom(player *Player) {
for _, sp := range r.Spectators {
spectatorsToEject = append(spectatorsToEject, sp)
}
// Clear spectator map so UnwatchNewRoom calls from their state machines are no-ops.
// Clear spectator map so UnwatchRoom calls from their state machines are no-ops.
r.Spectators = make(map[int64]*Player)
}
@@ -296,7 +296,7 @@ func LeaveNewRoom(player *Player) {
if empty {
store.mu.Lock()
deleteNewRoom(roomID)
deleteRoom(roomID)
store.mu.Unlock()
}
}
@@ -334,9 +334,9 @@ func pushWatchGameExitToCmdCh(player *Player) {
}
}
// WatchNewRoom adds a player to a room's Spectators list.
// WatchRoom adds a player to a room's Spectators list.
// Returns ErrRoomNotFound if the room does not exist.
func WatchNewRoom(roomID int64, player *Player) error {
func WatchRoom(roomID int64, player *Player) error {
store.mu.RLock()
r, ok := store.rooms[roomID]
store.mu.RUnlock()
@@ -354,8 +354,8 @@ func WatchNewRoom(roomID int64, player *Player) error {
return nil
}
// UnwatchNewRoom removes a spectator from their room.
func UnwatchNewRoom(player *Player) {
// UnwatchRoom removes a spectator from their room.
func UnwatchRoom(player *Player) {
if player.RoomID == 0 {
return
}
@@ -379,14 +379,7 @@ func UnwatchNewRoom(player *Player) {
if empty {
store.mu.Lock()
deleteNewRoom(roomID)
deleteRoom(roomID)
store.mu.Unlock()
}
}
// BroadcastToNewRoom is a placeholder kept for BroadcastEvent compatibility.
// The new state machine uses state.broadcastResponse (typed protobuf) instead.
// String-based broadcast is no longer supported after phase-06.
func BroadcastToNewRoom(_ *NewRoom, _ string, _ ...int64) {
// no-op: all broadcasts now use typed *protocol.Response via player.Send
}
+17 -76
View File
@@ -2,7 +2,6 @@ package lobby
import (
"fmt"
"sync"
"testing"
)
@@ -10,7 +9,7 @@ import (
func resetStore() {
store.mu.Lock()
store.players = make(map[int64]*Player)
store.rooms = make(map[int64]*NewRoom)
store.rooms = make(map[int64]*Room)
store.nextPlayerID = 1
store.nextRoomID = 1
store.mu.Unlock()
@@ -104,7 +103,7 @@ func TestCreatePveRoom_InvalidDifficultyRejected(t *testing.T) {
}
}
// --- JoinNewRoom tests ---
// --- JoinRoom tests ---
func TestJoinRoom_FullRejected(t *testing.T) {
resetStore()
@@ -113,13 +112,13 @@ func TestJoinRoom_FullRejected(t *testing.T) {
p3 := makePlayer("frank")
r, _ := CreatePvpRoom(owner)
if err := JoinNewRoom(r.ID, owner); err != nil {
if err := JoinRoom(r.ID, owner); err != nil {
t.Fatalf("owner join: %v", err)
}
if err := JoinNewRoom(r.ID, p2); err != nil {
if err := JoinRoom(r.ID, p2); err != nil {
t.Fatalf("second player join: %v", err)
}
if err := JoinNewRoom(r.ID, p3); err != ErrRoomFull {
if err := JoinRoom(r.ID, p3); err != ErrRoomFull {
t.Errorf("expected ErrRoomFull, got %v", err)
}
}
@@ -128,32 +127,32 @@ func TestJoinRoom_NotFoundRejected(t *testing.T) {
resetStore()
p := makePlayer("ghost")
err := JoinNewRoom(999, p)
err := JoinRoom(999, p)
if err != ErrRoomNotFound {
t.Errorf("expected ErrRoomNotFound, got %v", err)
}
}
// --- LeaveNewRoom tests ---
// --- LeaveRoom tests ---
func TestLeaveRoom_RemovesPlayerAndCleansEmptyRoom(t *testing.T) {
resetStore()
owner := makePlayer("grace")
r, _ := CreatePvpRoom(owner)
_ = JoinNewRoom(r.ID, owner)
_ = JoinRoom(r.ID, owner)
LeaveNewRoom(owner)
LeaveRoom(owner)
if owner.RoomID != 0 {
t.Errorf("player.RoomID should be 0 after leave, got %d", owner.RoomID)
}
if _, ok := GetNewRoom(r.ID); ok {
if _, ok := GetRoom(r.ID); ok {
t.Error("empty room should be deleted from store")
}
}
// --- WatchNewRoom / UnwatchNewRoom tests ---
// --- WatchRoom / UnwatchRoom tests ---
func TestWatchRoom_AddsSpectator(t *testing.T) {
resetStore()
@@ -161,10 +160,10 @@ func TestWatchRoom_AddsSpectator(t *testing.T) {
spectator := makePlayer("iris")
r, _ := CreatePvpRoom(owner)
_ = JoinNewRoom(r.ID, owner)
_ = JoinRoom(r.ID, owner)
if err := WatchNewRoom(r.ID, spectator); err != nil {
t.Fatalf("WatchNewRoom: %v", err)
if err := WatchRoom(r.ID, spectator); err != nil {
t.Fatalf("WatchRoom: %v", err)
}
r.RLock()
@@ -185,10 +184,10 @@ func TestUnwatchRoom_RemovesSpectator(t *testing.T) {
spectator := makePlayer("kim")
r, _ := CreatePvpRoom(owner)
_ = JoinNewRoom(r.ID, owner)
_ = WatchNewRoom(r.ID, spectator)
_ = JoinRoom(r.ID, owner)
_ = WatchRoom(r.ID, spectator)
UnwatchNewRoom(spectator)
UnwatchRoom(spectator)
r.RLock()
_, found := r.Spectators[spectator.ID]
@@ -227,61 +226,3 @@ func TestGetAllRooms_ReturnsSnapshot(t *testing.T) {
}
}
// --- Broadcast test ---
func TestBroadcastToRoom_SkipsExcludedIDs(t *testing.T) {
resetStore()
type call struct{ id int64 }
var mu sync.Mutex
var calls []call
makeFakePlayer := func(name string) *Player {
p := RegisterPlayer(name)
// Override WriteString via a wrapper conn — simplest: capture via closure
// by replacing the data channel and using a custom approach.
// Since Player.WriteString uses p.conn which is nil in tests,
// we verify via BroadcastToNewRoom's internal path.
// We'll wire sendFn indirectly: use a testable broadcast helper.
_ = p // suppress unused warning
return p
}
// Use a direct test of BroadcastToNewRoom's exclude logic via targets list.
// We build a room, manually wire WriteString-compatible players,
// then assert excluded player never receives the message.
owner := makeFakePlayer("maya")
other := makeFakePlayer("ned")
r, _ := CreatePvpRoom(owner)
r.Lock()
r.Players[owner.ID] = owner
r.Players[other.ID] = other
r.Unlock()
// Replace WriteString with a recorder via a thin intercept.
// Since Player.WriteString calls p.conn.Write and conn is nil in tests,
// we test the exclude logic directly: collect targets without nil-conn panic.
r.RLock()
var targets []int64
for id := range r.Players {
targets = append(targets, id)
}
r.RUnlock()
excluded := map[int64]bool{owner.ID: true}
var reached []int64
mu.Lock()
for _, id := range targets {
if !excluded[id] {
reached = append(reached, id)
}
}
mu.Unlock()
if len(reached) != 1 || reached[0] != other.ID {
t.Errorf("expected only other.ID=%d in reached, got %v", other.ID, reached)
}
_ = calls // suppress unused
}
+3 -3
View File
@@ -14,7 +14,7 @@ import (
type gamePveState struct{}
func (*gamePveState) Next(player *lobby.Player) (consts.StateID, error) {
room, ok := lobby.GetNewRoom(player.RoomID)
room, ok := lobby.GetRoom(player.RoomID)
if !ok {
log.Errorf("[pve] player %d: room not found\n", player.ID)
return consts.StateHome, nil
@@ -64,7 +64,7 @@ func (*gamePveState) Next(player *lobby.Player) (consts.StateID, error) {
// applyHumanMove validates/applies the human's move then triggers AI response.
// Returns (nextState, true) when the state should change; (0, false) on invalid move.
func applyHumanMove(player *lobby.Player, room *lobby.NewRoom, humanPiece game.Piece, row, col int) (consts.StateID, bool) {
func applyHumanMove(player *lobby.Player, room *lobby.Room, humanPiece game.Piece, row, col int) (consts.StateID, bool) {
if row < 0 || row >= game.BoardSize || col < 0 || col >= game.BoardSize {
_ = player.Send(&protocol.Response{
Payload: &protocol.Response_GameMoveOutOfBounds{
@@ -105,7 +105,7 @@ func applyHumanMove(player *lobby.Player, room *lobby.NewRoom, humanPiece game.P
// runAIMove computes and applies the AI's next move, broadcasts it, and checks result.
// Returns (nextState, true) if the game ends, (0, false) to continue.
func runAIMove(room *lobby.NewRoom) (consts.StateID, bool) {
func runAIMove(room *lobby.Room) (consts.StateID, bool) {
room.RLock()
ai := room.AI
board := room.Board.Clone() // safe value copy for AI computation
+5 -5
View File
@@ -12,7 +12,7 @@ import (
// setupPveGame creates a PVE room with the human assigned Black (AI=White).
// seed=42 gives deterministic AI behavior.
func setupPveGame(t *testing.T, difficulty int) (human *lobby.Player, room *lobby.NewRoom) {
func setupPveGame(t *testing.T, difficulty int) (human *lobby.Player, room *lobby.Room) {
t.Helper()
human = makeRegisteredPlayer(t, "Human")
@@ -20,8 +20,8 @@ func setupPveGame(t *testing.T, difficulty int) (human *lobby.Player, room *lobb
if err != nil {
t.Fatalf("CreatePveRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, human); err != nil {
t.Fatalf("JoinNewRoom human: %v", err)
if err := lobby.JoinRoom(room.ID, human); err != nil {
t.Fatalf("JoinRoom human: %v", err)
}
// Override AI with fixed seed for determinism.
@@ -180,8 +180,8 @@ func TestPveAIFirstWhenHumanIsWhite(t *testing.T) {
if err != nil {
t.Fatalf("CreatePveRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, human); err != nil {
t.Fatalf("JoinNewRoom: %v", err)
if err := lobby.JoinRoom(room.ID, human); err != nil {
t.Fatalf("JoinRoom: %v", err)
}
// Force human = White, AI = Black.
+4 -4
View File
@@ -16,7 +16,7 @@ import (
type gamePvpState struct{}
func (*gamePvpState) Next(player *lobby.Player) (consts.StateID, error) {
room, ok := lobby.GetNewRoom(player.RoomID)
room, ok := lobby.GetRoom(player.RoomID)
if !ok {
log.Errorf("[pvp] player %d: room not found\n", player.ID)
return consts.StateHome, nil
@@ -74,7 +74,7 @@ func (*gamePvpState) Next(player *lobby.Player) (consts.StateID, error) {
// signalGameOver closes room.GameOverCh exactly once so the other player goroutine
// unblocks and transitions to StateGameOver.
func signalGameOver(room *lobby.NewRoom) {
func signalGameOver(room *lobby.Room) {
room.Lock()
ch := room.GameOverCh
if ch != nil {
@@ -88,7 +88,7 @@ func signalGameOver(room *lobby.NewRoom) {
// applyPvpMove validates and applies a move. Returns (nextState, true) when
// the state should change; (0, false) when move is rejected (stay in loop).
func applyPvpMove(player *lobby.Player, room *lobby.NewRoom, row, col int) (consts.StateID, bool) {
func applyPvpMove(player *lobby.Player, room *lobby.Room, row, col int) (consts.StateID, bool) {
myPiece := playerPieceInRoom(room, player.ID)
if myPiece == game.Empty {
log.Errorf("[pvp] player %d not assigned a piece in room %d\n", player.ID, room.ID)
@@ -151,7 +151,7 @@ func applyPvpMove(player *lobby.Player, room *lobby.NewRoom, row, col int) (cons
}
// broadcastForfeit sends a GameOver response declaring the opponent as winner.
func broadcastForfeit(room *lobby.NewRoom, disconnected *lobby.Player) {
func broadcastForfeit(room *lobby.Room, disconnected *lobby.Player) {
room.RLock()
var winner *lobby.Player
for id, p := range room.Players {
+5 -5
View File
@@ -11,7 +11,7 @@ import (
)
// setupPvpGame creates a fully-started PVP room (2 players, colors assigned, status=Playing).
func setupPvpGame(t *testing.T) (black *lobby.Player, white *lobby.Player, room *lobby.NewRoom) {
func setupPvpGame(t *testing.T) (black *lobby.Player, white *lobby.Player, room *lobby.Room) {
t.Helper()
black = makeRegisteredPlayer(t, "Black")
white = makeRegisteredPlayer(t, "White")
@@ -20,11 +20,11 @@ func setupPvpGame(t *testing.T) (black *lobby.Player, white *lobby.Player, room
if err != nil {
t.Fatalf("CreatePvpRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, black); err != nil {
t.Fatalf("JoinNewRoom black: %v", err)
if err := lobby.JoinRoom(room.ID, black); err != nil {
t.Fatalf("JoinRoom black: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, white); err != nil {
t.Fatalf("JoinNewRoom white: %v", err)
if err := lobby.JoinRoom(room.ID, white); err != nil {
t.Fatalf("JoinRoom white: %v", err)
}
// Assign colors deterministically.
+6 -6
View File
@@ -9,7 +9,7 @@ import (
// sendRoomSnapshot sends the current room state to a newly-joined spectator.
// Order: WatchGameSuccessResponse → GameStartingResponse → one GameMoveSuccessResponse per history entry.
// Uses Snapshot() to avoid holding a lock while calling player.Send.
func sendRoomSnapshot(player *lobby.Player, room *lobby.NewRoom) {
func sendRoomSnapshot(player *lobby.Player, room *lobby.Room) {
snap := room.Snapshot()
status := protocol.RoomStatus_WAITING
@@ -62,7 +62,7 @@ func sendRoomSnapshot(player *lobby.Player, room *lobby.NewRoom) {
// buildGameStartingResponse constructs a GameStartingResponse from room state.
// Acquires RLock internally for safety.
func buildGameStartingResponse(room *lobby.NewRoom) *protocol.Response {
func buildGameStartingResponse(room *lobby.Room) *protocol.Response {
room.RLock()
blackID := room.BlackPlayerID
whiteID := room.WhitePlayerID
@@ -87,7 +87,7 @@ func buildGameStartingResponse(room *lobby.NewRoom) *protocol.Response {
}
// resolveNickname maps a playerID to a nickname. Returns "AI" for -1 (PVE AI slot).
func resolveNickname(room *lobby.NewRoom, playerID int64) string {
func resolveNickname(room *lobby.Room, playerID int64) string {
if playerID == -1 {
return "AI"
}
@@ -141,7 +141,7 @@ func buildGameOverResponse(result game.GameResult, winnerNickname string) *proto
// broadcastResponse sends resp to all players and spectators in the room.
// Acquires RLock internally; must NOT be called while holding room.Lock().
func broadcastResponse(room *lobby.NewRoom, resp *protocol.Response) {
func broadcastResponse(room *lobby.Room, resp *protocol.Response) {
room.RLock()
targets := make([]*lobby.Player, 0, len(room.Players)+len(room.Spectators))
for _, p := range room.Players {
@@ -159,7 +159,7 @@ func broadcastResponse(room *lobby.NewRoom, resp *protocol.Response) {
// playerPieceInRoom returns the game.Piece assigned to playerID in room.
// Returns game.Empty if not assigned.
func playerPieceInRoom(room *lobby.NewRoom, playerID int64) game.Piece {
func playerPieceInRoom(room *lobby.Room, playerID int64) game.Piece {
room.RLock()
black := room.BlackPlayerID
white := room.WhitePlayerID
@@ -175,7 +175,7 @@ func playerPieceInRoom(room *lobby.NewRoom, playerID int64) game.Piece {
// winnerNicknameFor returns the display name of the winner given a GameResult.
// Returns empty string for a draw.
func winnerNicknameFor(room *lobby.NewRoom, result game.GameResult) string {
func winnerNicknameFor(room *lobby.Room, result game.GameResult) string {
room.RLock()
blackID := room.BlackPlayerID
whiteID := room.WhitePlayerID
+2 -2
View File
@@ -17,7 +17,7 @@ import (
type gameOverState struct{}
func (*gameOverState) Next(player *lobby.Player) (consts.StateID, error) {
room, ok := lobby.GetNewRoom(player.RoomID)
room, ok := lobby.GetRoom(player.RoomID)
if !ok {
// Room gone (e.g. opponent left and room was deleted) — go home.
return consts.StateHome, nil
@@ -45,7 +45,7 @@ func (*gameOverState) Next(player *lobby.Player) (consts.StateID, error) {
}
// handleGameReset resets the room and starts a fresh game.
func handleGameReset(room *lobby.NewRoom) (consts.StateID, error) {
func handleGameReset(room *lobby.Room) (consts.StateID, error) {
seed := rand.Int63()
room.Lock()
room.Reset(seed)
+9 -9
View File
@@ -11,7 +11,7 @@ import (
)
// setupFinishedPvpRoom creates a PVP room in Finished state for gameover tests.
func setupFinishedPvpRoom(t *testing.T) (*lobby.Player, *lobby.Player, *lobby.NewRoom) {
func setupFinishedPvpRoom(t *testing.T) (*lobby.Player, *lobby.Player, *lobby.Room) {
t.Helper()
black := makeRegisteredPlayer(t, "Black")
white := makeRegisteredPlayer(t, "White")
@@ -20,11 +20,11 @@ func setupFinishedPvpRoom(t *testing.T) (*lobby.Player, *lobby.Player, *lobby.Ne
if err != nil {
t.Fatalf("CreatePvpRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, black); err != nil {
t.Fatalf("JoinNewRoom black: %v", err)
if err := lobby.JoinRoom(room.ID, black); err != nil {
t.Fatalf("JoinRoom black: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, white); err != nil {
t.Fatalf("JoinNewRoom white: %v", err)
if err := lobby.JoinRoom(room.ID, white); err != nil {
t.Fatalf("JoinRoom white: %v", err)
}
room.Lock()
@@ -82,8 +82,8 @@ func TestGameoverResetTransitionsToPve(t *testing.T) {
if err != nil {
t.Fatalf("CreatePveRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, human); err != nil {
t.Fatalf("JoinNewRoom: %v", err)
if err := lobby.JoinRoom(room.ID, human); err != nil {
t.Fatalf("JoinRoom: %v", err)
}
room.Lock()
@@ -154,8 +154,8 @@ func TestGameoverPveResetRandomizesColors(t *testing.T) {
if err != nil {
t.Fatalf("CreatePveRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, human); err != nil {
t.Fatalf("JoinNewRoom: %v", err)
if err := lobby.JoinRoom(room.ID, human); err != nil {
t.Fatalf("JoinRoom: %v", err)
}
room.Lock()
+9 -9
View File
@@ -49,8 +49,8 @@ func handleCreateRoom(player *lobby.Player) (consts.StateID, error) {
}
// Owner joins as a player immediately.
if err := lobby.JoinNewRoom(room.ID, player); err != nil {
log.Errorf("[home] JoinNewRoom error player %d room %d: %v\n", player.ID, room.ID, err)
if err := lobby.JoinRoom(room.ID, player); err != nil {
log.Errorf("[home] JoinRoom error player %d room %d: %v\n", player.ID, room.ID, err)
return consts.StateHome, nil
}
@@ -90,8 +90,8 @@ func handleCreatePveRoom(player *lobby.Player, req *protocol.Request) (consts.St
return consts.StateHome, nil
}
if err := lobby.JoinNewRoom(room.ID, player); err != nil {
log.Errorf("[home] JoinNewRoom (PVE) error player %d room %d: %v\n", player.ID, room.ID, err)
if err := lobby.JoinRoom(room.ID, player); err != nil {
log.Errorf("[home] JoinRoom (PVE) error player %d room %d: %v\n", player.ID, room.ID, err)
return consts.StateHome, nil
}
@@ -110,12 +110,12 @@ func handleCreatePveRoom(player *lobby.Player, req *protocol.Request) (consts.St
func handleJoinRoom(player *lobby.Player, req *protocol.Request) (consts.StateID, error) {
roomID := int64(req.GetJoinRoom().GetRoomId())
err := lobby.JoinNewRoom(roomID, player)
err := lobby.JoinRoom(roomID, player)
if err != nil {
switch err {
case lobby.ErrRoomFull, lobby.ErrRoomPlaying:
owner := ""
if r, ok := lobby.GetNewRoom(roomID); ok {
if r, ok := lobby.GetRoom(roomID); ok {
owner = r.OwnerNickname
}
_ = player.Send(&protocol.Response{
@@ -138,7 +138,7 @@ func handleJoinRoom(player *lobby.Player, req *protocol.Request) (consts.StateID
return consts.StateHome, nil
}
room, ok := lobby.GetNewRoom(roomID)
room, ok := lobby.GetRoom(roomID)
if !ok {
return consts.StateHome, nil
}
@@ -170,7 +170,7 @@ func handleJoinRoom(player *lobby.Player, req *protocol.Request) (consts.StateID
func handleWatchGame(player *lobby.Player, req *protocol.Request) (consts.StateID, error) {
roomID := int64(req.GetWatchGame().GetRoomId())
room, ok := lobby.GetNewRoom(roomID)
room, ok := lobby.GetRoom(roomID)
if !ok {
_ = player.Send(&protocol.Response{
Payload: &protocol.Response_RoomPlayFailNotFound{
@@ -180,7 +180,7 @@ func handleWatchGame(player *lobby.Player, req *protocol.Request) (consts.StateI
return consts.StateHome, nil
}
if err := lobby.WatchNewRoom(roomID, player); err != nil {
if err := lobby.WatchRoom(roomID, player); err != nil {
_ = player.Send(&protocol.Response{
Payload: &protocol.Response_RoomPlayFailNotFound{
RoomPlayFailNotFound: &protocol.RoomPlayFailNotFoundResponse{},
+1 -1
View File
@@ -49,7 +49,7 @@ func Run(player *lobby.Player) {
defer func() {
// Cleanup: remove from room if still in one.
if player.RoomID != 0 {
lobby.LeaveNewRoom(player)
lobby.LeaveRoom(player)
}
lobby.RemovePlayer(player.ID)
log.Infof("[state] player %d state machine exited\n", player.ID)
+6 -6
View File
@@ -23,7 +23,7 @@ import (
type waitingState struct{}
func (*waitingState) Next(player *lobby.Player) (consts.StateID, error) {
room, ok := lobby.GetNewRoom(player.RoomID)
room, ok := lobby.GetRoom(player.RoomID)
if !ok {
log.Errorf("[waiting] player %d: room %d not found\n", player.ID, player.RoomID)
return consts.StateHome, nil
@@ -37,7 +37,7 @@ func (*waitingState) Next(player *lobby.Player) (consts.StateID, error) {
}
// ownerWait loops until the owner triggers a valid GameStartingRequest or exits.
func ownerWait(player *lobby.Player, room *lobby.NewRoom) (consts.StateID, error) {
func ownerWait(player *lobby.Player, room *lobby.Room) (consts.StateID, error) {
for {
req, ok := <-player.CmdCh
if !ok {
@@ -92,7 +92,7 @@ func ownerWait(player *lobby.Player, room *lobby.NewRoom) (consts.StateID, error
}
// joinerWait blocks until the owner starts the game (via StartCh) or the joiner exits.
func joinerWait(player *lobby.Player, room *lobby.NewRoom) (consts.StateID, error) {
func joinerWait(player *lobby.Player, room *lobby.Room) (consts.StateID, error) {
room.RLock()
startCh := room.StartCh
room.RUnlock()
@@ -134,7 +134,7 @@ func joinerWait(player *lobby.Player, room *lobby.NewRoom) (consts.StateID, erro
// assignColors randomly assigns Black/White to the two players in a PVP room.
// Caller must NOT hold room.Lock() before calling — acquires it internally.
func assignColors(room *lobby.NewRoom) {
func assignColors(room *lobby.Room) {
room.Lock()
playerIDs := make([]int64, 0, 2)
for id := range room.Players {
@@ -159,9 +159,9 @@ func assignColors(room *lobby.NewRoom) {
// leaveRoom removes player from their current room cleanly.
// Notifies remaining players via ClientExitResponse.
func leaveRoom(player *lobby.Player, room *lobby.NewRoom) {
func leaveRoom(player *lobby.Player, room *lobby.Room) {
roomID := room.ID
lobby.LeaveNewRoom(player)
lobby.LeaveRoom(player)
room.RLock()
targets := make([]*lobby.Player, 0, len(room.Players))
+7 -7
View File
@@ -20,7 +20,7 @@ func makeRegisteredPlayer(t *testing.T, name string) *lobby.Player {
}
// setupPvpRoomWithOwner creates a PVP room with owner already joined.
func setupPvpRoomWithOwner(t *testing.T) (*lobby.Player, *lobby.NewRoom) {
func setupPvpRoomWithOwner(t *testing.T) (*lobby.Player, *lobby.Room) {
t.Helper()
owner := makeRegisteredPlayer(t, "Owner")
@@ -28,8 +28,8 @@ func setupPvpRoomWithOwner(t *testing.T) (*lobby.Player, *lobby.NewRoom) {
if err != nil {
t.Fatalf("CreatePvpRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, owner); err != nil {
t.Fatalf("JoinNewRoom owner: %v", err)
if err := lobby.JoinRoom(room.ID, owner); err != nil {
t.Fatalf("JoinRoom owner: %v", err)
}
return owner, room
}
@@ -78,8 +78,8 @@ func TestWaitingOwnerGameStartingRequiresFullRoom(t *testing.T) {
func TestWaitingOwnerStartsWhenFull(t *testing.T) {
owner, room := setupPvpRoomWithOwner(t)
joiner := makeRegisteredPlayer(t, "Joiner")
if err := lobby.JoinNewRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinNewRoom joiner: %v", err)
if err := lobby.JoinRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinRoom joiner: %v", err)
}
go func() {
@@ -118,8 +118,8 @@ func TestWaitingOwnerStartsWhenFull(t *testing.T) {
func TestWaitingJoinerTransitionsOnStartCh(t *testing.T) {
owner, room := setupPvpRoomWithOwner(t)
joiner := makeRegisteredPlayer(t, "Joiner")
if err := lobby.JoinNewRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinNewRoom joiner: %v", err)
if err := lobby.JoinRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinRoom joiner: %v", err)
}
// Simulate owner triggering start: close StartCh and mark room playing.
+3 -3
View File
@@ -25,13 +25,13 @@ func (*watchingState) Next(player *lobby.Player) (consts.StateID, error) {
req, ok := <-player.CmdCh
if !ok {
// Connection closed or channel drained — clean up quietly.
lobby.UnwatchNewRoom(player)
lobby.UnwatchRoom(player)
return 0, ErrClientExit
}
switch req.Payload.(type) {
case *protocol.Request_WatchGameExit:
lobby.UnwatchNewRoom(player)
lobby.UnwatchRoom(player)
_ = player.Send(&protocol.Response{
Payload: &protocol.Response_ShowOptions{
ShowOptions: &protocol.ShowOptionsResponse{},
@@ -40,7 +40,7 @@ func (*watchingState) Next(player *lobby.Player) (consts.StateID, error) {
return consts.StateHome, nil
case *protocol.Request_ClientExit:
lobby.UnwatchNewRoom(player)
lobby.UnwatchRoom(player)
return 0, ErrClientExit
case *protocol.Request_GameMove:
+24 -24
View File
@@ -12,24 +12,24 @@ import (
// setupWatchingPlayer creates a room with two players, then adds a spectator
// and returns the spectator player + the room.
func setupWatchingPlayer(t *testing.T) (*lobby.Player, *lobby.NewRoom) {
func setupWatchingPlayer(t *testing.T) (*lobby.Player, *lobby.Room) {
t.Helper()
owner := makeRegisteredPlayer(t, "Black")
room, err := lobby.CreatePvpRoom(owner)
if err != nil {
t.Fatalf("CreatePvpRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, owner); err != nil {
t.Fatalf("JoinNewRoom owner: %v", err)
if err := lobby.JoinRoom(room.ID, owner); err != nil {
t.Fatalf("JoinRoom owner: %v", err)
}
joiner := makeRegisteredPlayer(t, "White")
if err := lobby.JoinNewRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinNewRoom joiner: %v", err)
if err := lobby.JoinRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinRoom joiner: %v", err)
}
spectator := makeRegisteredPlayer(t, "Watcher")
if err := lobby.WatchNewRoom(room.ID, spectator); err != nil {
t.Fatalf("WatchNewRoom: %v", err)
if err := lobby.WatchRoom(room.ID, spectator); err != nil {
t.Fatalf("WatchRoom: %v", err)
}
return spectator, room
}
@@ -178,12 +178,12 @@ func TestSnapshot_SendsOwnerStartingAndHistory(t *testing.T) {
if err != nil {
t.Fatalf("CreatePvpRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, owner); err != nil {
t.Fatalf("JoinNewRoom owner: %v", err)
if err := lobby.JoinRoom(room.ID, owner); err != nil {
t.Fatalf("JoinRoom owner: %v", err)
}
joiner := makeRegisteredPlayer(t, "White")
if err := lobby.JoinNewRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinNewRoom joiner: %v", err)
if err := lobby.JoinRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinRoom joiner: %v", err)
}
// Set up color assignment and some move history.
@@ -238,8 +238,8 @@ func TestHomeWatchGame_RouteToWatching(t *testing.T) {
if err != nil {
t.Fatalf("CreatePvpRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, owner); err != nil {
t.Fatalf("JoinNewRoom: %v", err)
if err := lobby.JoinRoom(room.ID, owner); err != nil {
t.Fatalf("JoinRoom: %v", err)
}
spectator := makeRegisteredPlayer(t, "Spec")
@@ -315,17 +315,17 @@ func TestDeleteRoom_EjectsSpectators(t *testing.T) {
if err != nil {
t.Fatalf("CreatePvpRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, owner); err != nil {
t.Fatalf("JoinNewRoom: %v", err)
if err := lobby.JoinRoom(room.ID, owner); err != nil {
t.Fatalf("JoinRoom: %v", err)
}
spectator := makeRegisteredPlayer(t, "Watcher")
if err := lobby.WatchNewRoom(room.ID, spectator); err != nil {
t.Fatalf("WatchNewRoom: %v", err)
if err := lobby.WatchRoom(room.ID, spectator); err != nil {
t.Fatalf("WatchRoom: %v", err)
}
// Owner leaves — should eject spectator.
lobby.LeaveNewRoom(owner)
lobby.LeaveRoom(owner)
// Give async sends a moment to land.
time.Sleep(20 * time.Millisecond)
@@ -361,16 +361,16 @@ func TestRoomSummary_IncludesSpectators(t *testing.T) {
if err != nil {
t.Fatalf("CreatePvpRoom: %v", err)
}
if err := lobby.JoinNewRoom(room.ID, owner); err != nil {
t.Fatalf("JoinNewRoom: %v", err)
if err := lobby.JoinRoom(room.ID, owner); err != nil {
t.Fatalf("JoinRoom: %v", err)
}
joiner := makeRegisteredPlayer(t, "Joiner")
if err := lobby.JoinNewRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinNewRoom joiner: %v", err)
if err := lobby.JoinRoom(room.ID, joiner); err != nil {
t.Fatalf("JoinRoom joiner: %v", err)
}
spectator := makeRegisteredPlayer(t, "Watcher")
if err := lobby.WatchNewRoom(room.ID, spectator); err != nil {
t.Fatalf("WatchNewRoom: %v", err)
if err := lobby.WatchRoom(room.ID, spectator); err != nil {
t.Fatalf("WatchRoom: %v", err)
}
room.RLock()