diff --git a/apps/client/src/hooks/useAppFlow.ts b/apps/client/src/hooks/useAppFlow.ts index c6ffc5d..d4359fc 100644 --- a/apps/client/src/hooks/useAppFlow.ts +++ b/apps/client/src/hooks/useAppFlow.ts @@ -35,7 +35,8 @@ type JoinFailureReason = | domain.room.JoinRoomRejectedPayload["reason"] - | "timeout"; + | "timeout" + | "playing"; type JoinFailure = { reason: JoinFailureReason; @@ -119,6 +120,10 @@ return `ルーム ${joinFailure.roomId ?? ""} は満員です`; } + if (joinFailure.reason === "playing") { + return `ルーム ${joinFailure.roomId ?? ""} はゲーム中のため参加できません`; + } + if (joinFailure.reason === "duplicate") { return `ルーム ${joinFailure.roomId ?? ""} への参加要求が重複しました`; } diff --git a/apps/server/src/application/coordinators/coordinatorDeps.ts b/apps/server/src/application/coordinators/coordinatorDeps.ts index d222a64..11cede5 100644 --- a/apps/server/src/application/coordinators/coordinatorDeps.ts +++ b/apps/server/src/application/coordinators/coordinatorDeps.ts @@ -4,6 +4,7 @@ */ import type { CleanupGameRuntimePort, + DeleteRoomPort, DisconnectRoomPort, FindGameByRoomPort, FindRoomByIdPort, @@ -14,8 +15,8 @@ /** START_GAME調停で利用する依存集合 */ export type StartGameCoordinatorDeps = { - roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort; - runtimeRegistry: FindGameByRoomPort; + roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort & DeleteRoomPort; + runtimeRegistry: FindGameByRoomPort & CleanupGameRuntimePort; }; /** READY_FOR_GAME調停で利用する依存集合 */ diff --git a/apps/server/src/application/coordinators/startGameCoordinator.ts b/apps/server/src/application/coordinators/startGameCoordinator.ts index dc67565..72407a7 100644 --- a/apps/server/src/application/coordinators/startGameCoordinator.ts +++ b/apps/server/src/application/coordinators/startGameCoordinator.ts @@ -106,7 +106,8 @@ gameSession: gameManager, bombStore: gameManager, onGameEnd: () => { - roomManager.markRoomWaiting(updatedRoom.roomId); + roomManager.deleteRoom(updatedRoom.roomId); + runtimeRegistry.cleanupGameManagerForRoom(updatedRoom.roomId); }, output, }); diff --git a/apps/server/src/domains/room/RoomManager.ts b/apps/server/src/domains/room/RoomManager.ts index 6a7be2e..67b532c 100644 --- a/apps/server/src/domains/room/RoomManager.ts +++ b/apps/server/src/domains/room/RoomManager.ts @@ -62,4 +62,9 @@ public markRoomWaiting(roomId: string): RoomPhaseTransitionResult { return this.roomPhaseService.markRoomWaiting(roomId); } + + // ルームを削除する + public deleteRoom(roomId: string): boolean { + return this.rooms.delete(roomId); + } } \ No newline at end of file diff --git a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts index 8826220..e0cbd1e 100644 --- a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts +++ b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts @@ -26,7 +26,7 @@ /** ルーム参加処理の実行結果 */ export type JoinRoomResult = { room: domain.room.Room; - status: "joined" | "duplicate" | "full"; + status: "joined" | "duplicate" | "full" | "playing"; }; /** ルームユースケースが利用する出力ポート */ @@ -78,6 +78,11 @@ getRoomById(roomId: string): domain.room.Room | undefined; } +/** ゲーム終了時にルームを削除する操作ポート */ +export interface DeleteRoomPort { + deleteRoom(roomId: string): boolean; +} + /** ルーム参加後にゲームランタイムを確保する操作ポート */ export interface EnsureGameRuntimePort { ensureGameManagerForRoom(roomId: string): void; diff --git a/apps/server/src/domains/room/application/services/RoomJoinService.ts b/apps/server/src/domains/room/application/services/RoomJoinService.ts index 8b72619..979b5b2 100644 --- a/apps/server/src/domains/room/application/services/RoomJoinService.ts +++ b/apps/server/src/domains/room/application/services/RoomJoinService.ts @@ -32,6 +32,17 @@ }); } + // 待機中以外のルームへの参加を拒否する + if (room.status !== domain.room.RoomPhase.WAITING) { + logEvent(logScopes.ROOM_JOIN_SERVICE, { + event: roomDomainLogEvents.PLAYER_JOIN, + result: logResults.REJECTED_ROOM_PLAYING, + roomId, + socketId, + }); + return { room, status: "playing" }; + } + // 同一ソケットの重複参加を防止する const alreadyJoined = room.players.some((player) => player.id === socketId); if (alreadyJoined) { diff --git a/apps/server/src/logging/constants/results.ts b/apps/server/src/logging/constants/results.ts index f5ff65f..6ab8c75 100644 --- a/apps/server/src/logging/constants/results.ts +++ b/apps/server/src/logging/constants/results.ts @@ -28,6 +28,7 @@ REJECTED: "rejected", REJECTED_DUPLICATE: "rejected_duplicate", REJECTED_ROOM_FULL: "rejected_room_full", + REJECTED_ROOM_PLAYING: "rejected_room_playing", REMOVED: "removed", SESSION_DISPOSED_EMPTY_ROOM: "session_disposed_empty_room", STARTED: "started", diff --git a/apps/server/src/logging/contracts/payloadByScope.ts b/apps/server/src/logging/contracts/payloadByScope.ts index 8620418..044d3d0 100644 --- a/apps/server/src/logging/contracts/payloadByScope.ts +++ b/apps/server/src/logging/contracts/payloadByScope.ts @@ -27,6 +27,7 @@ result: | typeof logResults.REJECTED_ROOM_FULL | typeof logResults.REJECTED_DUPLICATE + | typeof logResults.REJECTED_ROOM_PLAYING | typeof logResults.IGNORED_INVALID_PAYLOAD | typeof logResults.IGNORED_MISSING_ROOM; socketId: string; @@ -224,6 +225,7 @@ result: | typeof logResults.IGNORED_DUPLICATE | typeof logResults.IGNORED_ROOM_FULL + | typeof logResults.REJECTED_ROOM_PLAYING | typeof logResults.JOINED; roomId: string; socketId: string; diff --git a/apps/server/src/network/handlers/room/roomEventOrchestrators.ts b/apps/server/src/network/handlers/room/roomEventOrchestrators.ts index b3b4996..4fbd750 100644 --- a/apps/server/src/network/handlers/room/roomEventOrchestrators.ts +++ b/apps/server/src/network/handlers/room/roomEventOrchestrators.ts @@ -49,6 +49,15 @@ }); return; + case "playing": + logEvent(logScopes.NETWORK, { + event: roomUseCaseLogEvents.JOIN_ROOM, + result: logResults.REJECTED_ROOM_PLAYING, + roomId: payload.roomId, + socketId: deps.socketId, + }); + return; + case "duplicate": logEvent(logScopes.NETWORK, { event: roomUseCaseLogEvents.JOIN_ROOM, diff --git a/packages/shared/src/domains/room/room.type.ts b/packages/shared/src/domains/room/room.type.ts index ce8c529..6fad7d7 100644 --- a/packages/shared/src/domains/room/room.type.ts +++ b/packages/shared/src/domains/room/room.type.ts @@ -31,7 +31,7 @@ } /** ルーム参加拒否理由 */ -export type JoinRoomRejectedReason = "full" | "duplicate"; +export type JoinRoomRejectedReason = "full" | "duplicate" | "playing"; /** ルーム参加拒否通知ペイロード */ export interface JoinRoomRejectedPayload { diff --git a/test/load-bot.constants.ts b/test/load-bot.constants.ts index 48a406f..b8e9ad8 100644 --- a/test/load-bot.constants.ts +++ b/test/load-bot.constants.ts @@ -6,7 +6,7 @@ export const URL = NETWORK_CONFIG.PROD_SERVER_URL; export const DEV_URL = NETWORK_CONFIG.DEV_SERVER_URL; -export const BOTS = 10; +export const BOTS = 99; export const DURATION_MS = Infinity; export const JOIN_DELAY_MS = 25; export const MOVE_TICK_MS = GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; @@ -19,7 +19,7 @@ export const MAX_Y = GAME_CONFIG.GRID_ROWS; export const BOT_CAN_MOVE = true; export const BOT_CAN_PLACE_BOMB = true; -export const ROOM_ID = "12"; +export const ROOM_ID = "1"; export const START_GAME = true; export const SOCKET_PATH = NETWORK_CONFIG.SOCKET_IO_PATH; export const SOCKET_TRANSPORTS = [...NETWORK_CONFIG.SOCKET_TRANSPORTS];