diff --git a/apps/server/src/application/coordinators/startGameCoordinator.ts b/apps/server/src/application/coordinators/startGameCoordinator.ts index efb030c..602e37b 100644 --- a/apps/server/src/application/coordinators/startGameCoordinator.ts +++ b/apps/server/src/application/coordinators/startGameCoordinator.ts @@ -16,6 +16,7 @@ ownerId: string; gameManager: StartGamePort; roomManager: StartGameRoomPort; + onRoomGameEnded?: (roomId: string) => void; output: Pick< GameOutputPort, | "publishUpdatePlayersToRoom" @@ -31,6 +32,7 @@ ownerId, gameManager, roomManager, + onRoomGameEnded, output, }: StartGameCoordinatorParams) => { const room = roomManager.getRoomByOwnerId(ownerId); @@ -80,6 +82,7 @@ gameManager, onGameEnd: () => { roomManager.markRoomWaiting(updatedRoom.roomId); + onRoomGameEnded?.(updatedRoom.roomId); }, output, }); diff --git a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts index c0e5a7b..9845676 100644 --- a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts +++ b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts @@ -30,3 +30,8 @@ export interface FindRoomByPlayerPort { getRoomByPlayerId(playerId: string): roomTypes.Room | undefined; } + +/** ルームIDでの存在確認に利用する参照ポート */ +export interface FindRoomByIdPort { + getRoomById(roomId: string): roomTypes.Room | undefined; +} diff --git a/apps/server/src/network/handlers/GameHandler.ts b/apps/server/src/network/handlers/GameHandler.ts index f317903..139735f 100644 --- a/apps/server/src/network/handlers/GameHandler.ts +++ b/apps/server/src/network/handlers/GameHandler.ts @@ -5,3 +5,6 @@ */ /** ゲームイベント受信ハンドラ登録関数を外部参照向けに再公開 */ export { registerGameHandlers } from "./game/registerGameHandlers"; + +/** ルーム終了時の爆弾状態掃除関数を外部参照向けに再公開 */ +export { clearBombRoomState } from "./game/registerGameHandlers"; diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index 679430a..379f0d6 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -22,6 +22,7 @@ import { createGameOutputAdapter } from "./createGameOutputAdapter"; const roomBombDedupTable = new Map>(); const roomBombSerialTable = new Map(); +const isBombRoomStateDebugEnabled = process.env.NODE_ENV !== "production"; const cleanupExpiredBombDedup = (roomId: string, nowMs: number) => { const roomTable = roomBombDedupTable.get(roomId); @@ -58,6 +59,20 @@ return `${roomId}:${serial}`; }; +/** 指定ルームの爆弾採番状態と重複排除状態を破棄する */ +export const clearBombRoomState = (roomId: string, reason: "game-ended" | "room-deleted"): void => { + const hadDedupState = roomBombDedupTable.delete(roomId); + const hadSerialState = roomBombSerialTable.delete(roomId); + + if (!isBombRoomStateDebugEnabled) { + return; + } + + console.debug( + `[BombState] cleared room=${roomId} reason=${reason} dedup=${hadDedupState} serial=${hadSerialState}` + ); +}; + /** ゲーム受信イベントごとの入力検証関数を保持するテーブル */ const gamePayloadValidators = { [protocol.SocketEvents.PING]: isPingPayload, @@ -107,6 +122,9 @@ ownerId: socket.id, gameManager, roomManager, + onRoomGameEnded: (roomId) => { + clearBombRoomState(roomId, "game-ended"); + }, output: gameOutputAdapter, }); }); diff --git a/apps/server/src/network/handlers/registerConnectionHandlers.ts b/apps/server/src/network/handlers/registerConnectionHandlers.ts index 66850a8..555dc51 100644 --- a/apps/server/src/network/handlers/registerConnectionHandlers.ts +++ b/apps/server/src/network/handlers/registerConnectionHandlers.ts @@ -7,7 +7,7 @@ import { disconnectCoordinator } from "@server/application/coordinators/disconnectCoordinator"; import { logEvent } from "@server/logging/logger"; import { logResults, logScopes } from "@server/logging/index"; -import { registerGameHandlers } from "./GameHandler"; +import { clearBombRoomState, registerGameHandlers } from "./GameHandler"; import { registerRoomHandlers } from "./RoomHandler"; import { createGameDisconnectOutputAdapter } from "./game/createGameOutputAdapter"; import { createRoomDisconnectOutputAdapter } from "./room/createRoomOutputAdapter"; @@ -36,6 +36,8 @@ registerGameHandlers(io, socket, gameManager, roomManager); socket.on(protocol.SocketEvents.DISCONNECT, () => { + const roomIdBeforeDisconnect = roomManager.getRoomByPlayerId(socket.id)?.roomId; + // 切断ログ記録後にドメイン別の後処理を実行する logEvent(logScopes.NETWORK, { event: protocol.SocketEvents.DISCONNECT, @@ -50,6 +52,10 @@ gameOutput: gameDisconnectOutputAdapter, roomOutput: roomDisconnectOutputAdapter, }); + + if (roomIdBeforeDisconnect && !roomManager.getRoomById(roomIdBeforeDisconnect)) { + clearBombRoomState(roomIdBeforeDisconnect, "room-deleted"); + } }); }); }; diff --git a/apps/server/src/network/types/connectionPorts.ts b/apps/server/src/network/types/connectionPorts.ts index dcb61a3..bd7a12a 100644 --- a/apps/server/src/network/types/connectionPorts.ts +++ b/apps/server/src/network/types/connectionPorts.ts @@ -13,6 +13,7 @@ } from "@server/domains/game/application/ports/gameUseCasePorts"; import type { DisconnectRoomPort, + FindRoomByIdPort, FindRoomByPlayerPort, JoinRoomPort, } from "@server/domains/room/application/ports/roomUseCasePorts"; @@ -39,6 +40,7 @@ export type SocketConnectionRoomPort = & ConnectionRoomPort & DisconnectRoomPort + & FindRoomByIdPort & FindRoomByPlayerPort; /** ソケット接続ハンドラで受け取るマネージャ依存の束 */