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/startGameCoordinator.ts b/apps/server/src/application/coordinators/startGameCoordinator.ts index dc67565..4f0304c 100644 --- a/apps/server/src/application/coordinators/startGameCoordinator.ts +++ b/apps/server/src/application/coordinators/startGameCoordinator.ts @@ -106,7 +106,7 @@ gameSession: gameManager, bombStore: gameManager, onGameEnd: () => { - roomManager.markRoomWaiting(updatedRoom.roomId); + roomManager.markRoomResult(updatedRoom.roomId); }, output, }); diff --git a/apps/server/src/domains/room/RoomManager.ts b/apps/server/src/domains/room/RoomManager.ts index 6a7be2e..bacbff0 100644 --- a/apps/server/src/domains/room/RoomManager.ts +++ b/apps/server/src/domains/room/RoomManager.ts @@ -58,6 +58,11 @@ return this.roomPhaseService.markRoomPlaying(roomId); } + // ルーム状態をRESULTへ更新する + public markRoomResult(roomId: string): RoomPhaseTransitionResult { + return this.roomPhaseService.markRoomResult(roomId); + } + // ルーム状態をWAITINGへ更新する public markRoomWaiting(roomId: string): RoomPhaseTransitionResult { return this.roomPhaseService.markRoomWaiting(roomId); diff --git a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts index 8826220..14d52ce 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"; }; /** ルームユースケースが利用する出力ポート */ @@ -64,6 +64,7 @@ /** ゲーム開始調停で利用するルーム状態遷移ポート */ export interface RoomPhaseTransitionPort { markRoomPlaying(roomId: string): RoomPhaseTransitionResult; + markRoomResult(roomId: string): RoomPhaseTransitionResult; markRoomWaiting(roomId: string): RoomPhaseTransitionResult; } 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/domains/room/application/services/RoomPhaseService.ts b/apps/server/src/domains/room/application/services/RoomPhaseService.ts index 3ee0466..f28de2f 100644 --- a/apps/server/src/domains/room/application/services/RoomPhaseService.ts +++ b/apps/server/src/domains/room/application/services/RoomPhaseService.ts @@ -29,6 +29,26 @@ }; } + public markRoomResult(roomId: string): RoomPhaseTransitionResult { + const room = this.rooms.get(roomId); + if (!room) { + return { status: "not_found" }; + } + + if (room.status !== domain.room.RoomPhase.PLAYING) { + return { + status: "invalid_transition", + room, + }; + } + + room.status = domain.room.RoomPhase.RESULT; + return { + status: "updated", + room, + }; + } + public markRoomWaiting(roomId: string): RoomPhaseTransitionResult { const room = this.rooms.get(roomId); if (!room) { 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/apps/server/src/network/types/connectionPorts.ts b/apps/server/src/network/types/connectionPorts.ts index b025c7b..c96e091 100644 --- a/apps/server/src/network/types/connectionPorts.ts +++ b/apps/server/src/network/types/connectionPorts.ts @@ -35,7 +35,7 @@ /** ゲームイベント調停で利用するルーム依存ポート */ export type GameEventRoomUseCasePort = Pick< ConnectionRoomPort, - "getRoomByOwnerId" | "getRoomByPlayerId" | "markRoomPlaying" | "markRoomWaiting" + "getRoomByOwnerId" | "getRoomByPlayerId" | "markRoomPlaying" | "markRoomResult" | "markRoomWaiting" >; /** ゲームイベント調停で利用するランタイム依存ポート */ 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];