diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index 799d858..118abb7 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -1,28 +1,36 @@ import { type TickData } from "./loop/GameLoop"; import { Player } from "./entities/player/Player.js"; -import { GameSessionService } from "./application/services/GameSessionService"; +import { GameRoomSession } from "./application/services/GameRoomSession"; +import { GameSessionLifecycleService } from "./application/services/GameSessionLifecycleService"; +import { GamePlayerOperationService } from "./application/services/GamePlayerOperationService"; // プレイヤー集合の生成・更新・参照管理クラス export class GameManager { - private gameSessionService: GameSessionService; + private sessions: Map; + private playerToRoom: Map; + private lifecycleService: GameSessionLifecycleService; + private playerOperationService: GamePlayerOperationService; constructor() { - this.gameSessionService = new GameSessionService(); + this.sessions = new Map(); + this.playerToRoom = new Map(); + this.lifecycleService = new GameSessionLifecycleService(this.sessions, this.playerToRoom); + this.playerOperationService = new GamePlayerOperationService(this.sessions, this.playerToRoom); } // 外部(GameHandlerなど)から開始時刻を取得できるようにする getRoomStartTime(roomId: string): number | undefined { - return this.gameSessionService.getRoomStartTime(roomId); + return this.lifecycleService.getRoomStartTime(roomId); } // プレイヤー登録解除処理 removePlayer(id: string) { - this.gameSessionService.removePlayer(id); + this.playerOperationService.removePlayer(id); } // 指定プレイヤー座標更新処理 movePlayer(id: string, x: number, y: number) { - this.gameSessionService.movePlayer(id, x, y); + this.playerOperationService.movePlayer(id, x, y); } /** @@ -37,11 +45,11 @@ onTick: (data: TickData) => void, onGameEnd: () => void ) { - this.gameSessionService.startRoomSession(roomId, playerIds, onTick, onGameEnd); + this.lifecycleService.startRoomSession(roomId, playerIds, onTick, onGameEnd); } // 指定ルームのプレイヤーを取得 getRoomPlayers(roomId: string): Player[] { - return this.gameSessionService.getRoomPlayers(roomId); + return this.lifecycleService.getRoomPlayers(roomId); } } \ No newline at end of file diff --git a/apps/server/src/domains/game/application/services/GamePlayerOperationService.ts b/apps/server/src/domains/game/application/services/GamePlayerOperationService.ts new file mode 100644 index 0000000..9c039c7 --- /dev/null +++ b/apps/server/src/domains/game/application/services/GamePlayerOperationService.ts @@ -0,0 +1,58 @@ +import { logEvent } from "@server/logging/logEvent"; +import { GameRoomSession } from "./GameRoomSession"; + +type SessionStore = Map; +type PlayerRoomIndex = Map; + +export class GamePlayerOperationService { + constructor( + private sessions: SessionStore, + private playerToRoom: PlayerRoomIndex + ) {} + + public movePlayer(id: string, x: number, y: number): void { + const roomId = this.playerToRoom.get(id); + if (!roomId) { + logEvent("GameSessionService", { + event: "MOVE", + result: "ignored_player_not_in_session", + socketId: id, + }); + return; + } + + this.sessions.get(roomId)?.movePlayer(id, x, y); + } + + public removePlayer(id: string): void { + const roomId = this.playerToRoom.get(id); + if (!roomId) { + logEvent("GameSessionService", { + event: "REMOVE_PLAYER", + result: "ignored_player_not_in_session", + socketId: id, + }); + return; + } + + const session = this.sessions.get(roomId); + if (!session) { + this.playerToRoom.delete(id); + return; + } + + const removed = session.removePlayer(id); + this.playerToRoom.delete(id); + + if (removed && session.isEmpty()) { + session.dispose(); + this.sessions.delete(roomId); + logEvent("GameSessionService", { + event: "REMOVE_PLAYER", + result: "session_disposed_empty_room", + roomId, + socketId: id, + }); + } + } +} \ 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 new file mode 100644 index 0000000..333d259 --- /dev/null +++ b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts @@ -0,0 +1,67 @@ +import { config } from "@repo/shared"; +import { type TickData } from "../../loop/GameLoop"; +import { logEvent } from "@server/logging/logEvent"; +import { GameRoomSession } from "./GameRoomSession"; + +type SessionStore = Map; +type PlayerRoomIndex = Map; + +export class GameSessionLifecycleService { + constructor( + private sessions: SessionStore, + private playerToRoom: PlayerRoomIndex + ) {} + + public getRoomStartTime(roomId: string): number | undefined { + return this.sessions.get(roomId)?.getStartTime(); + } + + public getRoomPlayers(roomId: string) { + return this.sessions.get(roomId)?.getPlayers() ?? []; + } + + public startRoomSession( + roomId: string, + playerIds: string[], + onTick: (data: TickData) => void, + onGameEnd: () => void + ) { + if (this.sessions.has(roomId)) { + logEvent("GameSessionService", { + event: "START_GAME_LOOP", + result: "ignored_already_running", + roomId, + }); + return; + } + + const tickRate = config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; + const session = new GameRoomSession(roomId, playerIds); + + playerIds.forEach((playerId) => { + this.playerToRoom.set(playerId, roomId); + }); + + this.sessions.set(roomId, session); + session.start(tickRate, onTick, () => { + this.clearRoomPlayerIndex(roomId); + this.sessions.delete(roomId); + onGameEnd(); + }); + + logEvent("GameSessionService", { + event: "START_GAME_LOOP", + result: "started", + roomId, + playerCount: playerIds.length, + }); + } + + private clearRoomPlayerIndex(roomId: string): void { + Array.from(this.playerToRoom.entries()).forEach(([playerId, mappedRoomId]) => { + if (mappedRoomId === roomId) { + this.playerToRoom.delete(playerId); + } + }); + } +} \ No newline at end of file diff --git a/apps/server/src/domains/game/application/services/GameSessionService.ts b/apps/server/src/domains/game/application/services/GameSessionService.ts deleted file mode 100644 index 020282b..0000000 --- a/apps/server/src/domains/game/application/services/GameSessionService.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { config } from "@repo/shared"; -import { type TickData } from "../../loop/GameLoop"; -import { logEvent } from "@server/logging/logEvent"; -import { GameRoomSession } from "./GameRoomSession"; - -export class GameSessionService { - private sessions: Map; - private playerToRoom: Map; - - constructor() { - this.sessions = new Map(); - this.playerToRoom = new Map(); - } - - public getRoomStartTime(roomId: string): number | undefined { - return this.sessions.get(roomId)?.getStartTime(); - } - - public getRoomPlayers(roomId: string) { - return this.sessions.get(roomId)?.getPlayers() ?? []; - } - - public startRoomSession( - roomId: string, - playerIds: string[], - onTick: (data: TickData) => void, - onGameEnd: () => void - ) { - if (this.sessions.has(roomId)) { - logEvent("GameSessionService", { - event: "START_GAME_LOOP", - result: "ignored_already_running", - roomId, - }); - return; - } - - const tickRate = config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; - const session = new GameRoomSession(roomId, playerIds); - - playerIds.forEach((playerId) => { - this.playerToRoom.set(playerId, roomId); - }); - - this.sessions.set(roomId, session); - session.start(tickRate, onTick, () => { - this.clearRoomPlayerIndex(roomId); - this.sessions.delete(roomId); - onGameEnd(); - }); - - logEvent("GameSessionService", { - event: "START_GAME_LOOP", - result: "started", - roomId, - playerCount: playerIds.length, - }); - } - - public movePlayer(id: string, x: number, y: number): void { - const roomId = this.playerToRoom.get(id); - if (!roomId) { - logEvent("GameSessionService", { - event: "MOVE", - result: "ignored_player_not_in_session", - socketId: id, - }); - return; - } - - this.sessions.get(roomId)?.movePlayer(id, x, y); - } - - public removePlayer(id: string): void { - const roomId = this.playerToRoom.get(id); - if (!roomId) { - logEvent("GameSessionService", { - event: "REMOVE_PLAYER", - result: "ignored_player_not_in_session", - socketId: id, - }); - return; - } - - const session = this.sessions.get(roomId); - if (!session) { - this.playerToRoom.delete(id); - return; - } - - const removed = session.removePlayer(id); - this.playerToRoom.delete(id); - - if (removed && session.isEmpty()) { - session.dispose(); - this.sessions.delete(roomId); - logEvent("GameSessionService", { - event: "REMOVE_PLAYER", - result: "session_disposed_empty_room", - roomId, - socketId: id, - }); - } - } - - private clearRoomPlayerIndex(roomId: string): void { - Array.from(this.playerToRoom.entries()).forEach(([playerId, mappedRoomId]) => { - if (mappedRoomId === roomId) { - this.playerToRoom.delete(playerId); - } - }); - } -} diff --git a/apps/server/src/domains/game/application/useCases/disconnectUseCase.ts b/apps/server/src/domains/game/application/useCases/disconnectUseCase.ts index 49d886c..ce9ce89 100644 --- a/apps/server/src/domains/game/application/useCases/disconnectUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/disconnectUseCase.ts @@ -2,17 +2,17 @@ import { logEvent } from "@server/logging/logEvent"; type DisconnectUseCaseParams = { - gameSessionManager: DisconnectPlayerPort; + gameManager: DisconnectPlayerPort; playerId: string; publishPlayerRemoved: (playerId: string) => void; }; export const disconnectUseCase = ({ - gameSessionManager, + gameManager, playerId, publishPlayerRemoved, }: DisconnectUseCaseParams) => { - gameSessionManager.removePlayer(playerId); + gameManager.removePlayer(playerId); publishPlayerRemoved(playerId); logEvent("GameUseCase", { event: "DISCONNECT", diff --git a/apps/server/src/domains/game/application/useCases/movePlayerUseCase.ts b/apps/server/src/domains/game/application/useCases/movePlayerUseCase.ts index 029f15d..9bcece1 100644 --- a/apps/server/src/domains/game/application/useCases/movePlayerUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/movePlayerUseCase.ts @@ -2,15 +2,15 @@ import type { MovePlayerPort } from "../ports/gameUseCasePorts"; type MovePlayerUseCaseParams = { - gameSessionManager: MovePlayerPort; + gameManager: MovePlayerPort; playerId: string; move: playerTypes.MovePayload; }; export const movePlayerUseCase = ({ - gameSessionManager, + gameManager, playerId, move, }: MovePlayerUseCaseParams) => { - gameSessionManager.movePlayer(playerId, move.x, move.y); + gameManager.movePlayer(playerId, move.x, move.y); }; diff --git a/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts b/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts index 21dd04e..067a1e2 100644 --- a/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts @@ -5,7 +5,7 @@ type ReadyForGameUseCaseParams = { socketId: string; roomId?: string; - gameSessionManager: ReadyForGamePort; + gameManager: ReadyForGamePort; publishCurrentPlayers: (players: playerTypes.PlayerData[]) => void; publishGameStart: (payload: { startTime: number }) => void; }; @@ -13,7 +13,7 @@ export const readyForGameUseCase = ({ socketId, roomId, - gameSessionManager, + gameManager, publishCurrentPlayers, publishGameStart, }: ReadyForGameUseCaseParams) => { @@ -27,7 +27,7 @@ return; } - const roomPlayers = gameSessionManager.getRoomPlayers(roomId); + const roomPlayers = gameManager.getRoomPlayers(roomId); publishCurrentPlayers(roomPlayers); logEvent("GameUseCase", { @@ -38,7 +38,7 @@ totalPlayers: roomPlayers.length, }); - const startTime = gameSessionManager.getRoomStartTime(roomId); + const startTime = gameManager.getRoomStartTime(roomId); 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 cc449c9..b8991db 100644 --- a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts @@ -6,7 +6,7 @@ type StartGameUseCaseParams = { ownerId: string; - gameSessionManager: StartGamePort; + gameManager: StartGamePort; roomManager: RoomManager; publishUpdatePlayer: (roomId: string, playerData: playerTypes.PlayerData) => void; publishMapCellUpdates: (roomId: string, cellUpdates: gridMapTypes.CellUpdate[]) => void; @@ -16,7 +16,7 @@ export const startGameUseCase = ({ ownerId, - gameSessionManager, + gameManager, roomManager, publishUpdatePlayer, publishMapCellUpdates, @@ -55,7 +55,7 @@ const playerIds = room.players.map((p: { id: string }) => p.id); - gameSessionManager.startRoomSession( + gameManager.startRoomSession( room.roomId, playerIds, (tickData) => { @@ -79,6 +79,6 @@ } ); - const startTime = gameSessionManager.getRoomStartTime(room.roomId) || Date.now(); + const startTime = gameManager.getRoomStartTime(room.roomId) || Date.now(); publishGameStart(room.roomId, { startTime }); }; diff --git a/apps/server/src/network/SocketManager.ts b/apps/server/src/network/SocketManager.ts index 18ffa85..39372ed 100644 --- a/apps/server/src/network/SocketManager.ts +++ b/apps/server/src/network/SocketManager.ts @@ -10,12 +10,12 @@ /** Socket.IOの接続ハンドラ登録を統括する */ export class SocketManager { private io: Server; - private gameSessionManager: GameManager; + private gameManager: GameManager; private roomManager: RoomManager; - constructor(io: Server, gameSessionManager: GameManager, roomManager: RoomManager) { + constructor(io: Server, gameManager: GameManager, roomManager: RoomManager) { this.io = io; - this.gameSessionManager = gameSessionManager; + this.gameManager = gameManager; this.roomManager = roomManager; } @@ -23,7 +23,7 @@ // 接続時に必要な各ドメインハンドラを登録する registerConnectionHandlers({ io: this.io, - gameSessionManager: this.gameSessionManager, + gameManager: this.gameManager, roomManager: this.roomManager, }); } diff --git a/apps/server/src/network/bootstrap/boot.ts b/apps/server/src/network/bootstrap/boot.ts index 46a1ae2..29ba1ff 100644 --- a/apps/server/src/network/bootstrap/boot.ts +++ b/apps/server/src/network/bootstrap/boot.ts @@ -12,9 +12,9 @@ export const boot = (httpServer: HttpServer) => { // ネットワーク層とドメイン層の依存を構築する const io = createIo(httpServer); - const gameSessionManager = new GameManager(); + const gameManager = new GameManager(); const roomManager = new RoomManager(); - const socketManager = new SocketManager(io, gameSessionManager, roomManager); + const socketManager = new SocketManager(io, gameManager, roomManager); socketManager.initialize(); }; diff --git a/apps/server/src/network/handlers/game/handleGameDisconnect.ts b/apps/server/src/network/handlers/game/handleGameDisconnect.ts index bd28c55..0a7be66 100644 --- a/apps/server/src/network/handlers/game/handleGameDisconnect.ts +++ b/apps/server/src/network/handlers/game/handleGameDisconnect.ts @@ -10,14 +10,14 @@ /** 切断したプレイヤーをゲーム管理から除外し通知する */ export const handleGameDisconnect = ( io: Server, - gameSessionManager: GameManager, + gameManager: GameManager, roomId: string | undefined, playerId: string ) => { const gameDisconnectPublisher = createGameDisconnectPublisher(io); disconnectUseCase({ - gameSessionManager, + gameManager, playerId, publishPlayerRemoved: (removedPlayerId) => { if (!roomId) { diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index ff417c1..29d3511 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -20,7 +20,7 @@ export const registerGameHandlers = ( io: Server, socket: Socket, - gameSessionManager: GameManager, + gameManager: GameManager, roomManager: RoomManager ) => { const common = createCommonHandlerContext(io, socket); @@ -47,7 +47,7 @@ socket.on(protocol.SocketEvents.START_GAME, () => { startGameUseCase({ ownerId: socket.id, - gameSessionManager, + gameManager, roomManager, publishUpdatePlayer: gamePublisher.publishUpdatePlayerToRoom, publishMapCellUpdates: gamePublisher.publishMapCellUpdatesToRoom, @@ -63,7 +63,7 @@ readyForGameUseCase({ socketId: socket.id, roomId, - gameSessionManager, + gameManager, publishCurrentPlayers: gamePublisher.publishCurrentPlayersToSocket, publishGameStart: gamePublisher.publishGameStartToSocket, }); @@ -81,7 +81,7 @@ } movePlayerUseCase({ - gameSessionManager, + gameManager, playerId: socket.id, move: data, }); diff --git a/apps/server/src/network/handlers/registerConnectionHandlers.ts b/apps/server/src/network/handlers/registerConnectionHandlers.ts index f6b4319..fa871f9 100644 --- a/apps/server/src/network/handlers/registerConnectionHandlers.ts +++ b/apps/server/src/network/handlers/registerConnectionHandlers.ts @@ -12,14 +12,14 @@ type RegisterConnectionHandlersParams = { io: Server; - gameSessionManager: GameManager; + gameManager: GameManager; roomManager: RoomManager; }; /** ソケット接続と切断イベントに対する共通ハンドラを登録する */ export const registerConnectionHandlers = ({ io, - gameSessionManager, + gameManager, roomManager, }: RegisterConnectionHandlersParams) => { io.on(protocol.SocketEvents.CONNECT, (socket: Socket) => { @@ -31,7 +31,7 @@ }); registerRoomHandlers(io, socket, roomManager); - registerGameHandlers(io, socket, gameSessionManager, roomManager); + registerGameHandlers(io, socket, gameManager, roomManager); socket.on(protocol.SocketEvents.DISCONNECT, () => { // 切断ログ記録後にドメイン別の後処理を実行する @@ -43,7 +43,7 @@ const roomId = roomManager.getRoomByPlayerId(socket.id)?.roomId; - handleGameDisconnect(io, gameSessionManager, roomId, socket.id); + handleGameDisconnect(io, gameManager, roomId, socket.id); handleRoomDisconnect(io, socket, roomManager); }); });