diff --git a/apps/server/src/application/coordinators/coordinatorDeps.ts b/apps/server/src/application/coordinators/coordinatorDeps.ts new file mode 100644 index 0000000..1092676 --- /dev/null +++ b/apps/server/src/application/coordinators/coordinatorDeps.ts @@ -0,0 +1,32 @@ +/** + * coordinatorDeps + * コーディネータが利用する依存束ね型を定義する + */ +import type { + CleanupGameRuntimePort, + DisconnectRoomPort, + FindGameByPlayerPort, + FindGameByRoomPort, + FindRoomByIdPort, + FindRoomByOwnerPort, + FindRoomByPlayerPort, + RoomPhaseTransitionPort, +} from "@server/domains/room/application/ports/roomUseCasePorts"; + +/** START_GAME調停で利用する依存集合 */ +export type StartGameCoordinatorDeps = { + roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort; + runtimeRegistry: FindGameByRoomPort; +}; + +/** READY_FOR_GAME調停で利用する依存集合 */ +export type ReadyForGameCoordinatorDeps = { + roomManager: FindRoomByPlayerPort; + runtimeRegistry: FindGameByPlayerPort; +}; + +/** DISCONNECT調停で利用する依存集合 */ +export type DisconnectCoordinatorDeps = { + roomManager: DisconnectRoomPort & FindRoomByPlayerPort & FindRoomByIdPort; + runtimeRegistry: FindGameByPlayerPort & CleanupGameRuntimePort; +}; diff --git a/apps/server/src/application/coordinators/disconnectCoordinator.ts b/apps/server/src/application/coordinators/disconnectCoordinator.ts index fdcfc85..acd27c8 100644 --- a/apps/server/src/application/coordinators/disconnectCoordinator.ts +++ b/apps/server/src/application/coordinators/disconnectCoordinator.ts @@ -5,10 +5,8 @@ import { type GameOutputPort, } from "@server/domains/game/application/ports/gameUseCasePorts"; -import type { - DisconnectDeps, - RoomOutputPort, -} from "@server/domains/room/application/ports/roomUseCasePorts"; +import type { RoomOutputPort } from "@server/domains/room/application/ports/roomUseCasePorts"; +import type { DisconnectCoordinatorDeps } from "./coordinatorDeps"; import { resolveRuntimeByPlayerId } from "@server/domains/room/application/services/RoomRuntimeResolver"; import { disconnectUseCase } from "@server/domains/game/application/useCases/disconnectUseCase"; import { roomDisconnectUseCase } from "@server/domains/room/application/useCases/roomDisconnectUseCase"; @@ -16,7 +14,7 @@ /** 切断調停で利用する入力ポートと出力ポートの契約 */ export type DisconnectCoordinatorParams = { socketId: string; -} & DisconnectDeps & { +} & DisconnectCoordinatorDeps & { gameOutput: Pick; roomOutput: Pick; }; diff --git a/apps/server/src/application/coordinators/readyForGameCoordinator.ts b/apps/server/src/application/coordinators/readyForGameCoordinator.ts index 68d8f44..897a981 100644 --- a/apps/server/src/application/coordinators/readyForGameCoordinator.ts +++ b/apps/server/src/application/coordinators/readyForGameCoordinator.ts @@ -5,13 +5,13 @@ import { type GameOutputPort, } from "@server/domains/game/application/ports/gameUseCasePorts"; -import type { ReadyForGameDeps } from "@server/domains/room/application/ports/roomUseCasePorts"; +import type { ReadyForGameCoordinatorDeps } from "./coordinatorDeps"; import { resolveRuntimeByPlayerId } from "@server/domains/room/application/services/RoomRuntimeResolver"; import { readyForGameUseCase } from "@server/domains/game/application/useCases/readyForGameUseCase"; type ReadyForGameCoordinatorParams = { socketId: string; -} & ReadyForGameDeps & { +} & ReadyForGameCoordinatorDeps & { output: Pick; }; diff --git a/apps/server/src/application/coordinators/startGameCoordinator.ts b/apps/server/src/application/coordinators/startGameCoordinator.ts index 75198dd..7e835ba 100644 --- a/apps/server/src/application/coordinators/startGameCoordinator.ts +++ b/apps/server/src/application/coordinators/startGameCoordinator.ts @@ -5,17 +5,14 @@ import { type GameOutputPort, } from "@server/domains/game/application/ports/gameUseCasePorts"; -import type { - StartGameDeps, -} from "@server/domains/room/application/ports/roomUseCasePorts"; +import type { StartGameCoordinatorDeps } from "./coordinatorDeps"; import { startGameUseCase } from "@server/domains/game/application/useCases/startGameUseCase"; import { logEvent } from "@server/logging/logger"; import { gameUseCaseLogEvents, logResults, logScopes } from "@server/logging/index"; -import { roomConsts } from "@repo/shared"; type StartGameCoordinatorParams = { ownerId: string; -} & StartGameDeps & { +} & StartGameCoordinatorDeps & { output: Pick< GameOutputPort, | "publishUpdatePlayersToSocket" @@ -43,7 +40,18 @@ return; } - if (room.status === roomConsts.RoomPhase.PLAYING) { + const transitionResult = roomManager.markRoomPlaying(room.roomId); + if (transitionResult.status === "not_found") { + logEvent(logScopes.GAME_USE_CASE, { + event: gameUseCaseLogEvents.START_GAME, + result: logResults.IGNORED_ROOM_NOT_FOUND, + roomId: room.roomId, + socketId: ownerId, + }); + return; + } + + if (transitionResult.status === "invalid_transition") { logEvent(logScopes.GAME_USE_CASE, { event: gameUseCaseLogEvents.START_GAME, result: logResults.IGNORED_ALREADY_PLAYING, @@ -53,14 +61,8 @@ return; } - const updatedRoom = roomManager.markRoomPlaying(room.roomId); + const updatedRoom = transitionResult.room; if (!updatedRoom) { - logEvent(logScopes.GAME_USE_CASE, { - event: gameUseCaseLogEvents.START_GAME, - result: logResults.IGNORED_ROOM_NOT_FOUND, - roomId: room.roomId, - socketId: ownerId, - }); return; } diff --git a/apps/server/src/domains/room/RoomManager.ts b/apps/server/src/domains/room/RoomManager.ts index 4205a81..fc446c6 100644 --- a/apps/server/src/domains/room/RoomManager.ts +++ b/apps/server/src/domains/room/RoomManager.ts @@ -7,7 +7,11 @@ import { RoomExitService } from "./application/services/RoomExitService"; import { RoomPhaseService } from "./application/services/RoomPhaseService"; import { RoomQueryService } from "./application/services/RoomQueryService"; -import type { JoinRoomResult } from "./application/ports/roomUseCasePorts"; +import type { + JoinRoomResult, + RoomDisconnectResult, + RoomPhaseTransitionResult, +} from "./application/ports/roomUseCasePorts"; /** ルーム操作の公開インターフェースを提供するマネージャ */ export class RoomManager { @@ -30,7 +34,7 @@ } // プレイヤーをルームから削除し,更新が発生したルーム配列を返す - public removePlayer(socketId: string): roomTypes.Room[] { + public removePlayer(socketId: string): RoomDisconnectResult { return this.roomExitService.removePlayer(socketId); } @@ -50,12 +54,12 @@ } // ルーム状態をPLAYINGへ更新する - public markRoomPlaying(roomId: string): roomTypes.Room | undefined { + public markRoomPlaying(roomId: string): RoomPhaseTransitionResult { return this.roomPhaseService.markRoomPlaying(roomId); } // ルーム状態をWAITINGへ更新する - public markRoomWaiting(roomId: string): roomTypes.Room | undefined { + public markRoomWaiting(roomId: string): RoomPhaseTransitionResult { return this.roomPhaseService.markRoomWaiting(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 034ee0d..f727fcb 100644 --- a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts +++ b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts @@ -38,9 +38,15 @@ /** ルーム切断ユースケースが利用する退出操作ポート */ export interface DisconnectRoomPort { - removePlayer(socketId: string): roomTypes.Room[]; + removePlayer(socketId: string): RoomDisconnectResult; } +/** 退出処理で更新対象となったルーム情報 */ +export type RoomDisconnectResult = { + updatedRooms: roomTypes.Room[]; + deletedRoomIds: string[]; +}; + /** 切断調停で利用するプレイヤー所属ルーム参照ポート */ export interface FindRoomByPlayerPort { getRoomByPlayerId(playerId: string): roomTypes.Room | undefined; @@ -53,10 +59,16 @@ /** ゲーム開始調停で利用するルーム状態遷移ポート */ export interface RoomPhaseTransitionPort { - markRoomPlaying(roomId: string): roomTypes.Room | undefined; - markRoomWaiting(roomId: string): roomTypes.Room | undefined; + markRoomPlaying(roomId: string): RoomPhaseTransitionResult; + markRoomWaiting(roomId: string): RoomPhaseTransitionResult; } +/** ルーム状態遷移の実行結果 */ +export type RoomPhaseTransitionResult = { + status: "updated" | "not_found" | "invalid_transition"; + room?: roomTypes.Room; +}; + /** ルームIDでの存在確認に利用する参照ポート */ export interface FindRoomByIdPort { getRoomById(roomId: string): roomTypes.Room | undefined; @@ -81,21 +93,3 @@ export interface FindGameByPlayerPort { getGameManagerByPlayerId(playerId: string): RoomScopedGamePort | undefined; } - -/** START_GAME調停で利用する依存集合 */ -export type StartGameDeps = { - roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort; - runtimeRegistry: FindGameByRoomPort; -}; - -/** READY_FOR_GAME調停で利用する依存集合 */ -export type ReadyForGameDeps = { - roomManager: FindRoomByPlayerPort; - runtimeRegistry: FindGameByPlayerPort; -}; - -/** DISCONNECT調停で利用する依存集合 */ -export type DisconnectDeps = { - roomManager: DisconnectRoomPort & FindRoomByPlayerPort & FindRoomByIdPort; - runtimeRegistry: FindGameByPlayerPort & CleanupGameRuntimePort; -}; diff --git a/apps/server/src/domains/room/application/services/RoomExitService.ts b/apps/server/src/domains/room/application/services/RoomExitService.ts index 15a3e64..1c54f38 100644 --- a/apps/server/src/domains/room/application/services/RoomExitService.ts +++ b/apps/server/src/domains/room/application/services/RoomExitService.ts @@ -3,6 +3,7 @@ * ルーム退出処理とオーナー移譲処理を担うサービス */ import type { roomTypes } from "@repo/shared"; +import type { RoomDisconnectResult } from "../ports/roomUseCasePorts"; import { logEvent } from "@server/logging/logger"; import { logResults, logScopes, roomDomainLogEvents } from "@server/logging/index"; @@ -10,8 +11,9 @@ export class RoomExitService { constructor(private rooms: Map) {} - public removePlayer(socketId: string): roomTypes.Room[] { + public removePlayer(socketId: string): RoomDisconnectResult { const updatedRooms: roomTypes.Room[] = []; + const deletedRoomIds: string[] = []; for (const [roomId, room] of this.rooms.entries()) { const playerIndex = room.players.findIndex((player) => player.id === socketId); @@ -30,6 +32,7 @@ if (room.players.length === 0) { this.rooms.delete(roomId); + deletedRoomIds.push(roomId); logEvent(logScopes.ROOM_EXIT_SERVICE, { event: roomDomainLogEvents.ROOM_DELETE, result: logResults.DELETED, @@ -54,6 +57,9 @@ updatedRooms.push(room); } - return updatedRooms; + return { + updatedRooms, + deletedRoomIds, + }; } } diff --git a/apps/server/src/domains/room/application/services/RoomPhaseService.ts b/apps/server/src/domains/room/application/services/RoomPhaseService.ts index 997c27a..b79a237 100644 --- a/apps/server/src/domains/room/application/services/RoomPhaseService.ts +++ b/apps/server/src/domains/room/application/services/RoomPhaseService.ts @@ -4,28 +4,49 @@ */ import { roomConsts } from "@repo/shared"; import type { roomTypes } from "@repo/shared"; +import type { RoomPhaseTransitionResult } from "../ports/roomUseCasePorts"; /** ルーム状態のフェーズ遷移を管理するサービス */ export class RoomPhaseService { constructor(private rooms: Map) {} - public markRoomPlaying(roomId: string): roomTypes.Room | undefined { + public markRoomPlaying(roomId: string): RoomPhaseTransitionResult { const room = this.rooms.get(roomId); if (!room) { - return undefined; + return { status: "not_found" }; + } + + if (room.status === roomConsts.RoomPhase.PLAYING) { + return { + status: "invalid_transition", + room, + }; } room.status = roomConsts.RoomPhase.PLAYING; - return room; + return { + status: "updated", + room, + }; } - public markRoomWaiting(roomId: string): roomTypes.Room | undefined { + public markRoomWaiting(roomId: string): RoomPhaseTransitionResult { const room = this.rooms.get(roomId); if (!room) { - return undefined; + return { status: "not_found" }; + } + + if (room.status === roomConsts.RoomPhase.WAITING) { + return { + status: "invalid_transition", + room, + }; } room.status = roomConsts.RoomPhase.WAITING; - return room; + return { + status: "updated", + room, + }; } } diff --git a/apps/server/src/domains/room/application/useCases/roomDisconnectUseCase.ts b/apps/server/src/domains/room/application/useCases/roomDisconnectUseCase.ts index 6c395ba..61b9a11 100644 --- a/apps/server/src/domains/room/application/useCases/roomDisconnectUseCase.ts +++ b/apps/server/src/domains/room/application/useCases/roomDisconnectUseCase.ts @@ -5,15 +5,13 @@ import type { CleanupGameRuntimePort, DisconnectRoomPort, - FindRoomByIdPort, - FindRoomByPlayerPort, } from "../ports/roomUseCasePorts"; import type { RoomOutputPort } from "../ports/roomUseCasePorts"; import { logEvent } from "@server/logging/logger"; import { logResults, logScopes, roomUseCaseLogEvents } from "@server/logging/index"; type RoomDisconnectUseCaseParams = { - roomManager: DisconnectRoomPort & FindRoomByPlayerPort & FindRoomByIdPort; + roomManager: DisconnectRoomPort; runtimeRegistry: CleanupGameRuntimePort; socketId: string; output: Pick; @@ -26,8 +24,7 @@ socketId, output, }: RoomDisconnectUseCaseParams) => { - const beforeRoomId = roomManager.getRoomByPlayerId(socketId)?.roomId; - const updatedRooms = roomManager.removePlayer(socketId); + const { updatedRooms, deletedRoomIds } = roomManager.removePlayer(socketId); logEvent(logScopes.ROOM_USE_CASE, { event: roomUseCaseLogEvents.DISCONNECT, result: logResults.PROCESSED, @@ -47,11 +44,7 @@ }); }); - if (!beforeRoomId) { - return; - } - - if (!roomManager.getRoomById(beforeRoomId)) { - runtimeRegistry.cleanupGameManagerForRoom(beforeRoomId); - } + deletedRoomIds.forEach((roomId) => { + runtimeRegistry.cleanupGameManagerForRoom(roomId); + }); }; diff --git a/apps/server/src/network/types/connectionPorts.ts b/apps/server/src/network/types/connectionPorts.ts index dbaf4bc..49454e8 100644 --- a/apps/server/src/network/types/connectionPorts.ts +++ b/apps/server/src/network/types/connectionPorts.ts @@ -4,8 +4,10 @@ */ import type { Server } from "socket.io"; import type { + DisconnectCoordinatorDeps, +} from "@server/application/coordinators/coordinatorDeps"; +import type { CleanupGameRuntimePort, - DisconnectDeps, DisconnectRoomPort, EnsureGameRuntimePort, FindGameByRoomPort, @@ -42,7 +44,7 @@ & CleanupGameRuntimePort; /** ソケット接続ハンドラで受け取るマネージャ依存の束 */ -export type SocketConnectionManagerBundle = DisconnectDeps & { +export type SocketConnectionManagerBundle = DisconnectCoordinatorDeps & { roomManager: SocketConnectionRoomPort; runtimeRegistry: SocketConnectionRuntimePort; };