diff --git a/apps/server/src/application/coordinators/disconnectCoordinator.ts b/apps/server/src/application/coordinators/disconnectCoordinator.ts index a830dda..71d0884 100644 --- a/apps/server/src/application/coordinators/disconnectCoordinator.ts +++ b/apps/server/src/application/coordinators/disconnectCoordinator.ts @@ -3,11 +3,11 @@ * DISCONNECTイベントの調停を行い,ゲーム離脱処理とルーム離脱処理を順序実行する */ import { - type DisconnectPlayerPort, type GameOutputPort, } from "@server/domains/game/application/ports/gameUseCasePorts"; import type { DisconnectRoomPort, + FindGameByPlayerPort, FindRoomByIdPort, FindRoomByPlayerPort, RoomOutputPort, @@ -18,8 +18,7 @@ /** 切断調停で利用する入力ポートと出力ポートの契約 */ export type DisconnectCoordinatorParams = { socketId: string; - gameManager: DisconnectPlayerPort; - roomManager: DisconnectRoomPort & FindRoomByPlayerPort & FindRoomByIdPort; + roomManager: DisconnectRoomPort & FindRoomByPlayerPort & FindRoomByIdPort & FindGameByPlayerPort; gameOutput: Pick; roomOutput: Pick; }; @@ -27,19 +26,21 @@ /** 切断時にゲーム処理とルーム処理を調停し,一貫した離脱処理を実行する */ export const disconnectCoordinator = ({ socketId, - gameManager, roomManager, gameOutput, roomOutput, }: DisconnectCoordinatorParams) => { const roomId = roomManager.getRoomByPlayerId(socketId)?.roomId; + const gameManager = roomManager.getGameManagerByPlayerId(socketId); - disconnectUseCase({ - gameManager, - roomId, - playerId: socketId, - output: gameOutput, - }); + if (gameManager) { + disconnectUseCase({ + gameManager, + roomId, + playerId: socketId, + output: gameOutput, + }); + } roomDisconnectUseCase({ roomManager, diff --git a/apps/server/src/application/coordinators/readyForGameCoordinator.ts b/apps/server/src/application/coordinators/readyForGameCoordinator.ts index 41e1836..8c1acc8 100644 --- a/apps/server/src/application/coordinators/readyForGameCoordinator.ts +++ b/apps/server/src/application/coordinators/readyForGameCoordinator.ts @@ -4,26 +4,24 @@ */ import { type GameOutputPort, - type ReadyForGamePort, } from "@server/domains/game/application/ports/gameUseCasePorts"; -import type { FindRoomByPlayerPort } from "@server/domains/room/application/ports/roomUseCasePorts"; +import type { FindGameByPlayerPort, FindRoomByPlayerPort } from "@server/domains/room/application/ports/roomUseCasePorts"; import { readyForGameUseCase } from "@server/domains/game/application/useCases/readyForGameUseCase"; type ReadyForGameCoordinatorParams = { socketId: string; - gameManager: ReadyForGamePort; - roomManager: FindRoomByPlayerPort; + roomManager: FindRoomByPlayerPort & FindGameByPlayerPort; output: Pick; }; /** READY_FOR_GAME受信時に所属ルームを解決し,準備状態ユースケースを実行する */ export const readyForGameCoordinator = ({ socketId, - gameManager, roomManager, output, }: ReadyForGameCoordinatorParams) => { const room = roomManager.getRoomByPlayerId(socketId); + const gameManager = roomManager.getGameManagerByPlayerId(socketId); readyForGameUseCase({ socketId, diff --git a/apps/server/src/application/coordinators/startGameCoordinator.ts b/apps/server/src/application/coordinators/startGameCoordinator.ts index e233507..7704ace 100644 --- a/apps/server/src/application/coordinators/startGameCoordinator.ts +++ b/apps/server/src/application/coordinators/startGameCoordinator.ts @@ -4,9 +4,9 @@ */ import { type GameOutputPort, - type StartGamePort, } from "@server/domains/game/application/ports/gameUseCasePorts"; import type { + FindGameByRoomPort, FindRoomByOwnerPort, RoomPhaseTransitionPort, } from "@server/domains/room/application/ports/roomUseCasePorts"; @@ -17,8 +17,7 @@ type StartGameCoordinatorParams = { ownerId: string; - gameManager: StartGamePort; - roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort; + roomManager: FindRoomByOwnerPort & RoomPhaseTransitionPort & FindGameByRoomPort; output: Pick< GameOutputPort, | "publishUpdatePlayersToRoom" @@ -33,7 +32,6 @@ /** START_GAME受信時にルーム状態遷移を判定し,ゲーム開始ユースケースを実行する */ export const startGameCoordinator = ({ ownerId, - gameManager, roomManager, output, }: StartGameCoordinatorParams) => { @@ -77,6 +75,10 @@ }); const playerIds = updatedRoom.players.map((player) => player.id); + const gameManager = roomManager.getGameManagerByRoomId(updatedRoom.roomId); + if (!gameManager) { + return; + } startGameUseCase({ roomId: updatedRoom.roomId, diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index 24695c8..0591e50 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -8,26 +8,28 @@ import { GameSessionLifecycleService } from "./application/services/GameSessionLifecycleService"; import { GamePlayerOperationService } from "./application/services/GamePlayerOperationService"; +type GameSessionRef = { + current: GameRoomSession | null; +}; + // プレイヤー集合の生成・更新・参照管理クラス /** ゲームセッションのライフサイクルとプレイヤー操作を統括するマネージャ */ export class GameManager { - private sessions: Map; - private playerToRoom: Map; - private roomToPlayers: Map>; + private sessionRef: GameSessionRef; + private activePlayerIds: Set; private lifecycleService: GameSessionLifecycleService; private playerOperationService: GamePlayerOperationService; - constructor() { - this.sessions = new Map(); - this.playerToRoom = new Map(); - this.roomToPlayers = new Map(); - this.lifecycleService = new GameSessionLifecycleService(this.sessions, this.playerToRoom, this.roomToPlayers); - this.playerOperationService = new GamePlayerOperationService(this.sessions, this.playerToRoom, this.roomToPlayers); + constructor(roomId: string) { + this.sessionRef = { current: null }; + this.activePlayerIds = new Set(); + this.lifecycleService = new GameSessionLifecycleService(this.sessionRef, this.activePlayerIds, roomId); + this.playerOperationService = new GamePlayerOperationService(this.sessionRef, this.activePlayerIds); } // 外部(GameHandlerなど)から開始時刻を取得できるようにする - getRoomStartTime(roomId: string): number | undefined { - return this.lifecycleService.getRoomStartTime(roomId); + getRoomStartTime(): number | undefined { + return this.lifecycleService.getRoomStartTime(); } // プレイヤー登録解除処理 @@ -42,31 +44,33 @@ /** * 20Hz固定のゲームループを開始する - * @param roomId ルームID * @param playerIds このルームに参加しているプレイヤーのIDリスト * @param onTick 毎フレーム実行される送信用のコールバック関数 */ startRoomSession( - roomId: string, playerIds: string[], onTick: (data: gameTypes.TickData) => void, onGameEnd: (payload: GameResultPayload) => void ) { - this.lifecycleService.startRoomSession(roomId, playerIds, onTick, onGameEnd); + this.lifecycleService.startRoomSession(playerIds, onTick, onGameEnd); } - // 指定ルームのプレイヤーを取得 - getRoomPlayers(roomId: string): Player[] { - return this.lifecycleService.getRoomPlayers(roomId); + // 現在セッションのプレイヤーを取得 + getRoomPlayers(): Player[] { + return this.lifecycleService.getRoomPlayers(); } - // roomId で対象セッションを選択し,爆弾設置イベントを配信すべきか判定する - shouldBroadcastBombPlacedForRoom(roomId: string, dedupeKey: string, nowMs: number): boolean { - return this.lifecycleService.shouldBroadcastBombPlacedForRoom(roomId, dedupeKey, nowMs); + // 爆弾設置イベントを配信すべきか判定し,配信時は重複排除状態を更新する + shouldBroadcastBombPlaced(dedupeKey: string, nowMs: number): boolean { + return this.lifecycleService.shouldBroadcastBombPlaced(dedupeKey, nowMs); } - // roomId で対象セッションを選択し,サーバー採番の爆弾IDを生成する - issueServerBombIdForRoom(roomId: string): string { - return this.lifecycleService.issueServerBombIdForRoom(roomId); + // サーバー採番の爆弾IDを生成する + issueServerBombId(): string { + return this.lifecycleService.issueServerBombId(); + } + + dispose(): void { + this.lifecycleService.dispose(); } } \ No newline at end of file diff --git a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts index 2bded70..2a36a52 100644 --- a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts +++ b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts @@ -21,18 +21,17 @@ /** ゲーム開始ユースケースが利用するゲーム管理入力ポート */ export interface StartGamePort { startRoomSession( - roomId: string, playerIds: string[], onTick: (data: gameTypes.TickData) => void, onGameEnd: (payload: GameResultPayload) => void ): void; - getRoomStartTime(roomId: string): number | undefined; + getRoomStartTime(): number | undefined; } /** 準備完了ユースケースが利用するゲーム状態参照入力ポート */ export interface ReadyForGamePort { - getRoomPlayers(roomId: string): playerTypes.PlayerData[]; - getRoomStartTime(roomId: string): number | undefined; + getRoomPlayers(): playerTypes.PlayerData[]; + getRoomStartTime(): number | undefined; } /** 移動入力ユースケースが利用するプレイヤー操作入力ポート */ @@ -80,8 +79,8 @@ /** 爆弾設置ユースケースが利用する爆弾状態入力ポート */ export interface BombPlacementPort { - shouldBroadcastBombPlacedForRoom(roomId: string, dedupeKey: string, nowMs: number): boolean; - issueServerBombIdForRoom(roomId: string): string; + shouldBroadcastBombPlaced(dedupeKey: string, nowMs: number): boolean; + issueServerBombId(): string; } /** 爆弾設置ユースケースの入力値 */ diff --git a/apps/server/src/domains/game/application/services/GamePlayerOperationService.ts b/apps/server/src/domains/game/application/services/GamePlayerOperationService.ts index c528d50..8367b1d 100644 --- a/apps/server/src/domains/game/application/services/GamePlayerOperationService.ts +++ b/apps/server/src/domains/game/application/services/GamePlayerOperationService.ts @@ -6,21 +6,19 @@ import { gameDomainLogEvents, logResults, logScopes } from "@server/logging/index"; import { GameRoomSession } from "./GameRoomSession"; -type SessionStore = Map; -type PlayerRoomIndex = Map; -type RoomPlayersIndex = Map>; +type GameSessionRef = { current: GameRoomSession | null }; +type ActivePlayerIndex = Set; /** プレイヤー移動とセッション離脱処理を提供するサービス */ export class GamePlayerOperationService { constructor( - private sessions: SessionStore, - private playerToRoom: PlayerRoomIndex, - private roomToPlayers: RoomPlayersIndex + private sessionRef: GameSessionRef, + private activePlayerIds: ActivePlayerIndex ) {} public movePlayer(id: string, x: number, y: number): void { - const roomId = this.playerToRoom.get(id); - if (!roomId) { + const session = this.sessionRef.current; + if (!session || !this.activePlayerIds.has(id)) { logEvent(logScopes.GAME_PLAYER_OPERATION_SERVICE, { event: gameDomainLogEvents.PLAYER_MOVE, result: logResults.IGNORED_PLAYER_NOT_IN_SESSION, @@ -29,12 +27,12 @@ return; } - this.sessions.get(roomId)?.movePlayer(id, x, y); + session.movePlayer(id, x, y); } public removePlayer(id: string): void { - const roomId = this.playerToRoom.get(id); - if (!roomId) { + const session = this.sessionRef.current; + if (!session || !this.activePlayerIds.has(id)) { logEvent(logScopes.GAME_PLAYER_OPERATION_SERVICE, { event: gameDomainLogEvents.PLAYER_REMOVE, result: logResults.IGNORED_PLAYER_NOT_IN_SESSION, @@ -43,43 +41,18 @@ return; } - const roomPlayerSet = this.roomToPlayers.get(roomId); - const session = this.sessions.get(roomId); - if (!session) { - this.removePlayerFromIndexes(roomId, id, roomPlayerSet); - return; - } - const removed = session.removePlayer(id); - this.removePlayerFromIndexes(roomId, id, roomPlayerSet); + this.activePlayerIds.delete(id); if (removed && session.getPlayers().length === 0) { session.dispose(); - this.sessions.delete(roomId); - this.roomToPlayers.delete(roomId); + this.sessionRef.current = null; + this.activePlayerIds.clear(); logEvent(logScopes.GAME_PLAYER_OPERATION_SERVICE, { event: gameDomainLogEvents.PLAYER_REMOVE, result: logResults.SESSION_DISPOSED_EMPTY_ROOM, - roomId, socketId: id, }); } } - - private removePlayerFromIndexes( - roomId: string, - playerId: string, - roomPlayerSet: Set | undefined - ): void { - this.playerToRoom.delete(playerId); - - if (!roomPlayerSet) { - return; - } - - roomPlayerSet.delete(playerId); - if (roomPlayerSet.size === 0) { - this.roomToPlayers.delete(roomId); - } - } } \ No newline at end of file diff --git a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts index 24ba90d..0f86e1b 100644 --- a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts +++ b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts @@ -8,87 +8,78 @@ import { gameDomainLogEvents, logResults, logScopes } from "@server/logging/index"; import { GameRoomSession } from "./GameRoomSession"; -type SessionStore = Map; -type PlayerRoomIndex = Map; -type RoomPlayersIndex = Map>; +type GameSessionRef = { current: GameRoomSession | null }; +type ActivePlayerIndex = Set; /** ゲームセッションのライフサイクル操作を提供するサービス */ export class GameSessionLifecycleService { constructor( - private sessions: SessionStore, - private playerToRoom: PlayerRoomIndex, - private roomToPlayers: RoomPlayersIndex + private sessionRef: GameSessionRef, + private activePlayerIds: ActivePlayerIndex, + private roomId: string ) {} - public getRoomStartTime(roomId: string): number | undefined { - return this.sessions.get(roomId)?.getStartTime(); + public getRoomStartTime(): number | undefined { + return this.sessionRef.current?.getStartTime(); } - public getRoomPlayers(roomId: string) { - return this.sessions.get(roomId)?.getPlayers() ?? []; + public getRoomPlayers() { + return this.sessionRef.current?.getPlayers() ?? []; } - public shouldBroadcastBombPlacedForRoom(roomId: string, dedupeKey: string, nowMs: number): boolean { - return this.sessions.get(roomId)?.shouldBroadcastBombPlaced(dedupeKey, nowMs) ?? false; + public shouldBroadcastBombPlaced(dedupeKey: string, nowMs: number): boolean { + return this.sessionRef.current?.shouldBroadcastBombPlaced(dedupeKey, nowMs) ?? false; } - public issueServerBombIdForRoom(roomId: string): string { - const session = this.sessions.get(roomId); + public issueServerBombId(): string { + const session = this.sessionRef.current; if (!session) { - throw new Error(`Game session not found for roomId: ${roomId}`); + throw new Error("Game session not found"); } return session.issueServerBombId(); } public startRoomSession( - roomId: string, playerIds: string[], onTick: (data: gameTypes.TickData) => void, onGameEnd: (payload: GameResultPayload) => void ) { - if (this.sessions.has(roomId)) { + if (this.sessionRef.current) { logEvent(logScopes.GAME_SESSION_LIFECYCLE_SERVICE, { event: gameDomainLogEvents.SESSION_START, result: logResults.IGNORED_ALREADY_RUNNING, - roomId, + roomId: this.roomId, }); return; } const tickRate = config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; - const session = new GameRoomSession(roomId, playerIds); - const roomPlayerSet = new Set(playerIds); + const session = new GameRoomSession(this.roomId, playerIds); + this.activePlayerIds.clear(); playerIds.forEach((playerId) => { - this.playerToRoom.set(playerId, roomId); + this.activePlayerIds.add(playerId); }); - this.roomToPlayers.set(roomId, roomPlayerSet); - this.sessions.set(roomId, session); + this.sessionRef.current = session; session.start(tickRate, onTick, (payload) => { - this.clearRoomPlayerIndex(roomId); - this.sessions.delete(roomId); + this.activePlayerIds.clear(); + this.sessionRef.current = null; onGameEnd(payload); }); logEvent(logScopes.GAME_SESSION_LIFECYCLE_SERVICE, { event: gameDomainLogEvents.SESSION_START, result: logResults.STARTED, - roomId, + roomId: this.roomId, playerCount: playerIds.length, }); } - private clearRoomPlayerIndex(roomId: string): void { - const roomPlayerSet = this.roomToPlayers.get(roomId); - if (!roomPlayerSet) { - return; - } - - roomPlayerSet.forEach((playerId) => { - this.playerToRoom.delete(playerId); - }); - this.roomToPlayers.delete(roomId); + public dispose(): void { + this.sessionRef.current?.dispose(); + this.sessionRef.current = null; + this.activePlayerIds.clear(); } } \ No newline at end of file diff --git a/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts b/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts index b30790d..beddd68 100644 --- a/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts @@ -12,11 +12,9 @@ createBombDedupeKey, createBombPlacedPayload, } from "@server/domains/game/entities/bomb/bombPlacement"; -import { resolveRoomIdBySocketId } from "./useCaseRoomResolver"; -import type { FindRoomByPlayerPort } from "@server/domains/room/application/ports/roomUseCasePorts"; type PlaceBombUseCaseParams = { - roomResolver: FindRoomByPlayerPort; + roomId: string; bombStore: BombPlacementPort; input: PlaceBombInput; output: BombOutputPort; @@ -24,22 +22,17 @@ /** 爆弾設置入力を重複排除と採番付きでルームへ配信する */ export const placeBombUseCase = ({ - roomResolver, + roomId, bombStore, input, output, }: PlaceBombUseCaseParams): void => { - const roomId = resolveRoomIdBySocketId(roomResolver, input.socketId); - if (!roomId) { - return; - } - const dedupeKey = createBombDedupeKey(input.socketId, input.payload.requestId); - if (!bombStore.shouldBroadcastBombPlacedForRoom(roomId, dedupeKey, input.nowMs)) { + if (!bombStore.shouldBroadcastBombPlaced(dedupeKey, input.nowMs)) { return; } - const bombId = bombStore.issueServerBombIdForRoom(roomId); + const bombId = bombStore.issueServerBombId(); output.publishBombPlacedToOthersInRoom( roomId, diff --git a/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts b/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts index 6961ae2..59c3c59 100644 --- a/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts @@ -10,7 +10,7 @@ type ReadyForGameUseCaseParams = { socketId: string; roomId?: string; - gameManager: ReadyForGamePort; + gameManager?: ReadyForGamePort; output: Pick; }; @@ -21,7 +21,7 @@ gameManager, output, }: ReadyForGameUseCaseParams) => { - if (!roomId) { + if (!roomId || !gameManager) { output.publishCurrentPlayersToSocket([]); logEvent(logScopes.GAME_USE_CASE, { event: gameUseCaseLogEvents.READY_FOR_GAME, @@ -31,7 +31,7 @@ return; } - const roomPlayers = gameManager.getRoomPlayers(roomId); + const roomPlayers = gameManager.getRoomPlayers(); output.publishCurrentPlayersToSocket(roomPlayers); logEvent(logScopes.GAME_USE_CASE, { @@ -42,7 +42,7 @@ totalPlayers: roomPlayers.length, }); - const startTime = gameManager.getRoomStartTime(roomId); + const startTime = gameManager.getRoomStartTime(); if (!startTime) { return; } diff --git a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts index d4eb749..6a205a5 100644 --- a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts @@ -38,7 +38,6 @@ output, }: StartGameUseCaseParams) => { gameManager.startRoomSession( - roomId, playerIds, (tickData) => { if (tickData.playerUpdates.length > 0) { @@ -73,6 +72,6 @@ } ); - const startTime = gameManager.getRoomStartTime(roomId) || Date.now(); + const startTime = gameManager.getRoomStartTime() || Date.now(); output.publishGameStartToRoom(roomId, { startTime }); }; diff --git a/apps/server/src/domains/room/RoomManager.ts b/apps/server/src/domains/room/RoomManager.ts index 47a86ed..ed8c4a6 100644 --- a/apps/server/src/domains/room/RoomManager.ts +++ b/apps/server/src/domains/room/RoomManager.ts @@ -4,6 +4,7 @@ */ import type { roomTypes } from "@repo/shared"; import { roomConsts } from "@repo/shared"; +import { GameManager } from "@server/domains/game/GameManager"; import { RoomJoinService } from "./application/services/RoomJoinService"; import { RoomExitService } from "./application/services/RoomExitService"; import { RoomQueryService } from "./application/services/RoomQueryService"; @@ -12,6 +13,7 @@ /** ルーム操作の公開インターフェースを提供するマネージャ */ export class RoomManager { private rooms: Map = new Map(); + private gameManagers: Map = new Map(); private roomJoinService: RoomJoinService; private roomExitService: RoomExitService; private roomQueryService: RoomQueryService; @@ -24,12 +26,20 @@ // ルームにプレイヤーを追加する,ルームが未作成なら新規作成する public addPlayerToRoom(roomId: string, socketId: string, playerName: string): JoinRoomResult { - return this.roomJoinService.addPlayerToRoom(roomId, socketId, playerName); + const joinResult = this.roomJoinService.addPlayerToRoom(roomId, socketId, playerName); + + if (joinResult.status === "joined" && !this.gameManagers.has(roomId)) { + this.gameManagers.set(roomId, new GameManager(roomId)); + } + + return joinResult; } // プレイヤーをルームから削除し,更新が発生したルーム配列を返す public removePlayer(socketId: string): roomTypes.Room[] { - return this.roomExitService.removePlayer(socketId); + const updatedRooms = this.roomExitService.removePlayer(socketId); + this.cleanupDisposedRooms(); + return updatedRooms; } // オーナーIDからルームを取得する @@ -47,6 +57,19 @@ return this.roomQueryService.getRoomByPlayerId(playerId); } + public getGameManagerByRoomId(roomId: string): GameManager | undefined { + return this.gameManagers.get(roomId); + } + + public getGameManagerByPlayerId(playerId: string): GameManager | undefined { + const roomId = this.roomQueryService.getRoomByPlayerId(playerId)?.roomId; + if (!roomId) { + return undefined; + } + + return this.gameManagers.get(roomId); + } + // ルーム状態をPLAYINGへ更新する public markRoomPlaying(roomId: string): roomTypes.Room | undefined { const room = this.roomQueryService.getRoomById(roomId); @@ -68,4 +91,15 @@ room.status = roomConsts.RoomPhase.WAITING; return room; } + + private cleanupDisposedRooms(): void { + for (const [roomId, gameManager] of this.gameManagers.entries()) { + if (this.rooms.has(roomId)) { + continue; + } + + gameManager.dispose(); + this.gameManagers.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 b32c711..7702349 100644 --- a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts +++ b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts @@ -3,6 +3,20 @@ * ルームユースケースが依存する操作ポートを定義する */ import type { roomTypes } from "@repo/shared"; +import type { + BombPlacementPort, + DisconnectPlayerPort, + MovePlayerPort, + ReadyForGamePort, + StartGamePort, +} from "@server/domains/game/application/ports/gameUseCasePorts"; + +export type RoomScopedGamePort = + & StartGamePort + & ReadyForGamePort + & MovePlayerPort + & BombPlacementPort + & DisconnectPlayerPort; /** ルーム参加処理の実行結果 */ export type JoinRoomResult = { @@ -46,3 +60,13 @@ export interface FindRoomByIdPort { getRoomById(roomId: string): roomTypes.Room | undefined; } + +/** ルームIDでゲーム管理を解決する参照ポート */ +export interface FindGameByRoomPort { + getGameManagerByRoomId(roomId: string): RoomScopedGamePort | undefined; +} + +/** プレイヤーIDでゲーム管理を解決する参照ポート */ +export interface FindGameByPlayerPort { + getGameManagerByPlayerId(playerId: string): RoomScopedGamePort | undefined; +} diff --git a/apps/server/src/network/SocketManager.ts b/apps/server/src/network/SocketManager.ts index 279a30c..a0c4ee0 100644 --- a/apps/server/src/network/SocketManager.ts +++ b/apps/server/src/network/SocketManager.ts @@ -5,7 +5,6 @@ import { Server } from "socket.io"; import type { SocketConnectionManagerBundle, - SocketConnectionGamePort, SocketConnectionRoomPort, } from "./types/connectionPorts"; import { registerConnectionHandlers } from "./handlers/registerConnectionHandlers"; @@ -17,12 +16,10 @@ constructor( io: Server, - gameManager: SocketConnectionGamePort, roomManager: SocketConnectionRoomPort ) { this.io = io; this.managers = { - gameManager, roomManager, }; } diff --git a/apps/server/src/network/bootstrap/boot.ts b/apps/server/src/network/bootstrap/boot.ts index 29ba1ff..130d54d 100644 --- a/apps/server/src/network/bootstrap/boot.ts +++ b/apps/server/src/network/bootstrap/boot.ts @@ -3,7 +3,6 @@ * HTTPサーバにSocket.IOと各マネージャを接続して起動準備を行う */ import type { Server as HttpServer } from "http"; -import { GameManager } from "@server/domains/game/GameManager"; import { RoomManager } from "@server/domains/room/RoomManager"; import { SocketManager } from "../SocketManager"; import { createIo } from "./createIo"; @@ -12,9 +11,8 @@ export const boot = (httpServer: HttpServer) => { // ネットワーク層とドメイン層の依存を構築する const io = createIo(httpServer); - const gameManager = new GameManager(); const roomManager = new RoomManager(); - const socketManager = new SocketManager(io, gameManager, roomManager); + const socketManager = new SocketManager(io, roomManager); socketManager.initialize(); }; diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index 525b6d9..6d3db79 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -7,16 +7,12 @@ import { readyForGameCoordinator } from "@server/application/coordinators/readyForGameCoordinator"; import { startGameCoordinator } from "@server/application/coordinators/startGameCoordinator"; import type { + FindGameByRoomPort, + FindGameByPlayerPort, FindRoomByOwnerPort, FindRoomByPlayerPort, RoomPhaseTransitionPort, } from "@server/domains/room/application/ports/roomUseCasePorts"; -import type { - BombPlacementPort, - MovePlayerPort, - ReadyForGamePort, - StartGamePort, -} from "@server/domains/game/application/ports/gameUseCasePorts"; import { movePlayerUseCase } from "@server/domains/game/application/useCases/movePlayerUseCase"; import { placeBombUseCase } from "@server/domains/game/application/useCases/placeBombUseCase"; import { pingUseCase } from "@server/domains/game/application/useCases/pingUseCase"; @@ -37,8 +33,7 @@ export const registerGameHandlers = ( io: Server, socket: Socket, - gameManager: StartGamePort & ReadyForGamePort & MovePlayerPort & BombPlacementPort, - roomManager: FindRoomByOwnerPort & FindRoomByPlayerPort & RoomPhaseTransitionPort + roomManager: FindRoomByOwnerPort & FindRoomByPlayerPort & RoomPhaseTransitionPort & FindGameByRoomPort & FindGameByPlayerPort ) => { const common = createCommonHandlerContext(io, socket); const gameOutputAdapter = createGameOutputAdapter(common); @@ -72,7 +67,6 @@ onEvent(protocol.SocketEvents.START_GAME, () => { startGameCoordinator({ ownerId: socket.id, - gameManager, roomManager, output: gameOutputAdapter, }); @@ -82,7 +76,6 @@ onEvent(protocol.SocketEvents.READY_FOR_GAME, () => { readyForGameCoordinator({ socketId: socket.id, - gameManager, roomManager, output: gameOutputAdapter, }); @@ -94,6 +87,11 @@ return; } + const gameManager = roomManager.getGameManagerByPlayerId(socket.id); + if (!gameManager) { + return; + } + movePlayerUseCase({ gameManager, playerId: socket.id, @@ -107,8 +105,14 @@ return; } + const roomId = roomManager.getRoomByPlayerId(socket.id)?.roomId; + const gameManager = roomManager.getGameManagerByPlayerId(socket.id); + if (!roomId || !gameManager) { + return; + } + placeBombUseCase({ - roomResolver: roomManager, + roomId, bombStore: gameManager, input: { socketId: socket.id, diff --git a/apps/server/src/network/handlers/registerConnectionHandlers.ts b/apps/server/src/network/handlers/registerConnectionHandlers.ts index 66850a8..35e602a 100644 --- a/apps/server/src/network/handlers/registerConnectionHandlers.ts +++ b/apps/server/src/network/handlers/registerConnectionHandlers.ts @@ -18,7 +18,6 @@ /** ソケット接続と切断イベントに対する共通ハンドラを登録する */ export const registerConnectionHandlers = ({ io, - gameManager, roomManager, }: RegisterConnectionHandlersParams) => { const gameDisconnectOutputAdapter = createGameDisconnectOutputAdapter(io); @@ -33,7 +32,7 @@ }); registerRoomHandlers(io, socket, roomManager); - registerGameHandlers(io, socket, gameManager, roomManager); + registerGameHandlers(io, socket, roomManager); socket.on(protocol.SocketEvents.DISCONNECT, () => { // 切断ログ記録後にドメイン別の後処理を実行する @@ -45,7 +44,6 @@ disconnectCoordinator({ socketId: socket.id, - gameManager, roomManager, gameOutput: gameDisconnectOutputAdapter, roomOutput: roomDisconnectOutputAdapter, diff --git a/apps/server/src/network/types/connectionPorts.ts b/apps/server/src/network/types/connectionPorts.ts index c3713a4..4651b69 100644 --- a/apps/server/src/network/types/connectionPorts.ts +++ b/apps/server/src/network/types/connectionPorts.ts @@ -4,14 +4,9 @@ */ import type { Server } from "socket.io"; import type { - BombPlacementPort, - DisconnectPlayerPort, - MovePlayerPort, - ReadyForGamePort, - StartGamePort, -} from "@server/domains/game/application/ports/gameUseCasePorts"; -import type { DisconnectRoomPort, + FindGameByRoomPort, + FindGameByPlayerPort, FindRoomByOwnerPort, FindRoomByIdPort, FindRoomByPlayerPort, @@ -20,24 +15,14 @@ } from "@server/domains/room/application/ports/roomUseCasePorts"; import type { DisconnectCoordinatorParams } from "../../application/coordinators/disconnectCoordinator"; -/** 接続時のゲーム処理で利用する入力ポート集合 */ -export type ConnectionGamePort = - & StartGamePort - & ReadyForGamePort - & MovePlayerPort - & BombPlacementPort; - /** 接続時のルーム処理で利用する入力ポート集合 */ export type ConnectionRoomPort = & JoinRoomPort & FindRoomByOwnerPort & FindRoomByPlayerPort - & RoomPhaseTransitionPort; - -/** ソケット接続全体で利用するゲーム管理ポート集合 */ -export type SocketConnectionGamePort = - & ConnectionGamePort - & DisconnectPlayerPort; + & RoomPhaseTransitionPort + & FindGameByRoomPort + & FindGameByPlayerPort; /** ソケット接続全体で利用するルーム管理ポート集合 */ export type SocketConnectionRoomPort = @@ -47,13 +32,9 @@ /** ソケット接続ハンドラで受け取るマネージャ依存の束 */ export type SocketConnectionManagerBundle = { - gameManager: SocketConnectionGamePort; roomManager: SocketConnectionRoomPort; }; -/** 切断時のゲーム処理で利用する入力ポート */ -export type DisconnectGamePort = Pick; - /** 切断時のルーム処理で利用する入力ポート集合 */ export type DisconnectRoomHandlerPort = Pick< SocketConnectionRoomPort,