diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index 332ef67..97ef4cf 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -1,68 +1,42 @@ -import { Player } from "./entities/Player.js"; -import { config } from "@repo/shared"; -import { MapStore } from "./states/MapStore"; -import { gridMapLogic } from "@repo/shared"; -import type { gridMapTypes } from "@repo/shared"; import { GameLoop, type TickData } from "./GameLoop"; +import type { gridMapTypes } from "@repo/shared"; +import { Player } from "./entities/Player.js"; +import { PlayerRegistry } from "./application/services/PlayerRegistry"; +import { GameSessionService } from "./application/services/GameSessionService"; // プレイヤー集合の生成・更新・参照管理クラス export class GameManager { - private players: Map; - private mapStore: MapStore; - private gameLoops: Map; // NodeJS.Timeout から変更 - private roomStartTimes: Map; // ルームごとのゲーム開始時間を保持する + private playerRegistry: PlayerRegistry; + private gameSessionService: GameSessionService; constructor() { - this.players = new Map(); - this.mapStore = new MapStore(); - this.gameLoops = new Map(); - this.roomStartTimes = new Map(); + this.playerRegistry = new PlayerRegistry(); + this.gameSessionService = new GameSessionService(this.playerRegistry.getPlayersRef()); } // 外部(GameHandlerなど)から開始時刻を取得できるようにする getRoomStartTime(roomId: string): number | undefined { - return this.roomStartTimes.get(roomId); + return this.gameSessionService.getRoomStartTime(roomId); } // 新規プレイヤー登録と初期位置設定処理 addPlayer(id: string): Player { - const player = new Player(id); - player.x = config.GAME_CONFIG.GRID_COLS / 2; - player.y = config.GAME_CONFIG.GRID_ROWS / 2; - this.players.set(id, player); - console.log("[GameManager] player added", { playerId: id, totalPlayers: this.players.size }); - return player; + return this.playerRegistry.addPlayer(id); } // プレイヤー登録解除処理 removePlayer(id: string) { - const existed = this.players.delete(id); - if (existed) { - console.log("[GameManager] player removed", { playerId: id, totalPlayers: this.players.size }); - } else { - console.log("[GameManager] player remove ignored (not found)", { playerId: id }); - } + this.playerRegistry.removePlayer(id); } // 指定IDプレイヤー参照取得 getPlayer(id: string) { - return this.players.get(id); + return this.playerRegistry.getPlayer(id); } // 指定プレイヤー座標更新処理 movePlayer(id: string, x: number, y: number) { - const player = this.players.get(id); - if (player) { - console.log(`Move Request -> ID:${id.slice(0,4)} x:${Math.round(x)} y:${Math.round(y)}`); - if (typeof x !== "number" || typeof y !== "number" || isNaN(x) || isNaN(y)) { - console.log("⚠️ 無効なデータなので無視しました"); - return; - } - player.x = x; - player.y = y; - } else { - console.log("[GameManager] move ignored (player not found)", { playerId: id }); - } + this.playerRegistry.movePlayer(id, x, y); } /** @@ -77,67 +51,23 @@ onTick: (data: TickData) => void, onGameEnd: () => void ) { - if (this.gameLoops.has(roomId)) { - console.log("[GameManager] startGameLoop ignored (already running)", { roomId }); - return; - } - - const tickRate = config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; - - // ループ開始時に、このルームの開始時刻を記憶する - this.roomStartTimes.set(roomId, Date.now()); - - // GameLoopインスタンスを生成し、参照を渡す - const loop = new GameLoop( - roomId, - tickRate, - playerIds, - this.players, - this.mapStore, - onTick, - () => { - // GameLoopが終了した時の処理 - this.roomStartTimes.delete(roomId); - this.gameLoops.delete(roomId); - onGameEnd(); // GameHandlerへ終了を伝える - } - ); - - loop.start(); - this.gameLoops.set(roomId, loop); - console.log("[GameManager] game loop started", { roomId, playerCount: playerIds.length }); + this.gameSessionService.startGameLoop(roomId, playerIds, onTick, onGameEnd); } /** * ゲームループを停止する */ stopGameLoop(roomId: string) { - const loop = this.gameLoops.get(roomId); - if (loop) { - loop.stop(); - this.gameLoops.delete(roomId); - this.roomStartTimes.delete(roomId); // 停止時も忘れずクリア - console.log("[GameManager] game loop stopped", { roomId }); - } else { - console.log("[GameManager] stopGameLoop ignored (not running)", { roomId }); - } + this.gameSessionService.stopGameLoop(roomId); } // 登録中全プレイヤー配列取得 getAllPlayers() { - return Array.from(this.players.values()); + return this.playerRegistry.getAllPlayers(); } // 【一時的】移動したプレイヤーの足元を塗り、差分を返すメソッド public paintAndGetUpdates(playerId: string): gridMapTypes.CellUpdate[] { - const player = this.players.get(playerId); - if (!player) return []; - - const gridIndex = gridMapLogic.getGridIndexFromPosition(player.x, player.y); - if (gridIndex !== null) { - this.mapStore.paintCell(gridIndex, player.teamId); - } - - return this.mapStore.getAndClearUpdates(); + return this.gameSessionService.paintAndGetUpdates(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 new file mode 100644 index 0000000..ab33e56 --- /dev/null +++ b/apps/server/src/domains/game/application/services/GameSessionService.ts @@ -0,0 +1,78 @@ +import { config, gridMapLogic } from "@repo/shared"; +import type { gridMapTypes } from "@repo/shared"; +import { GameLoop, type TickData } from "../../GameLoop"; +import { Player } from "../../entities/Player.js"; +import { MapStore } from "../../states/MapStore"; + +export class GameSessionService { + private mapStore: MapStore; + private gameLoops: Map; + private roomStartTimes: Map; + + constructor(private players: Map) { + this.mapStore = new MapStore(); + this.gameLoops = new Map(); + this.roomStartTimes = new Map(); + } + + public getRoomStartTime(roomId: string): number | undefined { + return this.roomStartTimes.get(roomId); + } + + public startGameLoop( + roomId: string, + playerIds: string[], + onTick: (data: TickData) => void, + onGameEnd: () => void + ) { + if (this.gameLoops.has(roomId)) { + console.log("[GameManager] startGameLoop ignored (already running)", { roomId }); + return; + } + + const tickRate = config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS; + this.roomStartTimes.set(roomId, Date.now()); + + const loop = new GameLoop( + roomId, + tickRate, + playerIds, + this.players, + this.mapStore, + onTick, + () => { + this.roomStartTimes.delete(roomId); + this.gameLoops.delete(roomId); + onGameEnd(); + } + ); + + loop.start(); + this.gameLoops.set(roomId, loop); + console.log("[GameManager] game loop started", { roomId, playerCount: playerIds.length }); + } + + public stopGameLoop(roomId: string) { + const loop = this.gameLoops.get(roomId); + if (loop) { + loop.stop(); + this.gameLoops.delete(roomId); + this.roomStartTimes.delete(roomId); + console.log("[GameManager] game loop stopped", { roomId }); + } else { + console.log("[GameManager] stopGameLoop ignored (not running)", { roomId }); + } + } + + public paintAndGetUpdates(playerId: string): gridMapTypes.CellUpdate[] { + const player = this.players.get(playerId); + if (!player) return []; + + const gridIndex = gridMapLogic.getGridIndexFromPosition(player.x, player.y); + if (gridIndex !== null) { + this.mapStore.paintCell(gridIndex, player.teamId); + } + + return this.mapStore.getAndClearUpdates(); + } +} diff --git a/apps/server/src/domains/game/application/services/PlayerRegistry.ts b/apps/server/src/domains/game/application/services/PlayerRegistry.ts new file mode 100644 index 0000000..5233ce4 --- /dev/null +++ b/apps/server/src/domains/game/application/services/PlayerRegistry.ts @@ -0,0 +1,55 @@ +import { config } from "@repo/shared"; +import { Player } from "../../entities/Player.js"; + +export class PlayerRegistry { + private players: Map; + + constructor() { + this.players = new Map(); + } + + public addPlayer(id: string): Player { + const player = new Player(id); + player.x = config.GAME_CONFIG.GRID_COLS / 2; + player.y = config.GAME_CONFIG.GRID_ROWS / 2; + this.players.set(id, player); + console.log("[GameManager] player added", { playerId: id, totalPlayers: this.players.size }); + return player; + } + + public removePlayer(id: string) { + const existed = this.players.delete(id); + if (existed) { + console.log("[GameManager] player removed", { playerId: id, totalPlayers: this.players.size }); + } else { + console.log("[GameManager] player remove ignored (not found)", { playerId: id }); + } + } + + public getPlayer(id: string): Player | undefined { + return this.players.get(id); + } + + public movePlayer(id: string, x: number, y: number) { + const player = this.players.get(id); + if (player) { + console.log(`Move Request -> ID:${id.slice(0, 4)} x:${Math.round(x)} y:${Math.round(y)}`); + if (typeof x !== "number" || typeof y !== "number" || isNaN(x) || isNaN(y)) { + console.log("⚠️ 無効なデータなので無視しました"); + return; + } + player.x = x; + player.y = y; + } else { + console.log("[GameManager] move ignored (player not found)", { playerId: id }); + } + } + + public getAllPlayers(): Player[] { + return Array.from(this.players.values()); + } + + public getPlayersRef(): Map { + return this.players; + } +}