From 1d80d334e0db7dba00a05a0540bbe2ff0714846e Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 11 Apr 2026 16:31:36 +0700 Subject: [PATCH] refactor(lobby): drop 'New' prefix from Room type and store functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- server/lobby/cleanup.go | 2 +- server/lobby/events.go | 15 ------ server/lobby/room.go | 14 +++--- server/lobby/room_test.go | 6 +-- server/lobby/store.go | 55 +++++++++------------ server/lobby/store_test.go | 93 +++++++---------------------------- server/state/game_pve.go | 6 +-- server/state/game_pve_test.go | 10 ++-- server/state/game_pvp.go | 8 +-- server/state/game_pvp_test.go | 10 ++-- server/state/game_shared.go | 12 ++--- server/state/gameover.go | 4 +- server/state/gameover_test.go | 18 +++---- server/state/home.go | 18 +++---- server/state/state.go | 2 +- server/state/waiting.go | 12 ++--- server/state/waiting_test.go | 14 +++--- server/state/watching.go | 6 +-- server/state/watching_test.go | 48 +++++++++--------- 19 files changed, 136 insertions(+), 217 deletions(-) delete mode 100644 server/lobby/events.go diff --git a/server/lobby/cleanup.go b/server/lobby/cleanup.go index 34b8a34..89af8cc 100644 --- a/server/lobby/cleanup.go +++ b/server/lobby/cleanup.go @@ -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() } } diff --git a/server/lobby/events.go b/server/lobby/events.go deleted file mode 100644 index 22a9196..0000000 --- a/server/lobby/events.go +++ /dev/null @@ -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...) -} diff --git a/server/lobby/room.go b/server/lobby/room.go index be07ba4..9745bbe 100644 --- a/server/lobby/room.go +++ b/server/lobby/room.go @@ -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 } diff --git a/server/lobby/room_test.go b/server/lobby/room_test.go index 913dda3..8939679 100644 --- a/server/lobby/room_test.go +++ b/server/lobby/room_test.go @@ -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", diff --git a/server/lobby/store.go b/server/lobby/store.go index 56e7764..7c520b7 100644 --- a/server/lobby/store.go +++ b/server/lobby/store.go @@ -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 -} diff --git a/server/lobby/store_test.go b/server/lobby/store_test.go index 4015d14..5c741a0 100644 --- a/server/lobby/store_test.go +++ b/server/lobby/store_test.go @@ -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 -} diff --git a/server/state/game_pve.go b/server/state/game_pve.go index 2616df6..54883b6 100644 --- a/server/state/game_pve.go +++ b/server/state/game_pve.go @@ -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 diff --git a/server/state/game_pve_test.go b/server/state/game_pve_test.go index 67fe803..65bea75 100644 --- a/server/state/game_pve_test.go +++ b/server/state/game_pve_test.go @@ -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. diff --git a/server/state/game_pvp.go b/server/state/game_pvp.go index 6af8e40..0b91706 100644 --- a/server/state/game_pvp.go +++ b/server/state/game_pvp.go @@ -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 { diff --git a/server/state/game_pvp_test.go b/server/state/game_pvp_test.go index 16773f6..794878d 100644 --- a/server/state/game_pvp_test.go +++ b/server/state/game_pvp_test.go @@ -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. diff --git a/server/state/game_shared.go b/server/state/game_shared.go index 4dbcf24..e740fe5 100644 --- a/server/state/game_shared.go +++ b/server/state/game_shared.go @@ -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 diff --git a/server/state/gameover.go b/server/state/gameover.go index 7e6129d..b574f01 100644 --- a/server/state/gameover.go +++ b/server/state/gameover.go @@ -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) diff --git a/server/state/gameover_test.go b/server/state/gameover_test.go index 8de76b8..3e85baa 100644 --- a/server/state/gameover_test.go +++ b/server/state/gameover_test.go @@ -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() diff --git a/server/state/home.go b/server/state/home.go index bded294..9e87fae 100644 --- a/server/state/home.go +++ b/server/state/home.go @@ -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{}, diff --git a/server/state/state.go b/server/state/state.go index 85df2a5..6fcf0a3 100644 --- a/server/state/state.go +++ b/server/state/state.go @@ -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) diff --git a/server/state/waiting.go b/server/state/waiting.go index 1894dc9..1407cf7 100644 --- a/server/state/waiting.go +++ b/server/state/waiting.go @@ -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)) diff --git a/server/state/waiting_test.go b/server/state/waiting_test.go index 7d5538f..56cf3df 100644 --- a/server/state/waiting_test.go +++ b/server/state/waiting_test.go @@ -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. diff --git a/server/state/watching.go b/server/state/watching.go index aa48f47..b4c1e27 100644 --- a/server/state/watching.go +++ b/server/state/watching.go @@ -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: diff --git a/server/state/watching_test.go b/server/state/watching_test.go index 9c3baeb..31ee134 100644 --- a/server/state/watching_test.go +++ b/server/state/watching_test.go @@ -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()