diff --git a/apps/server/src/domains/game/GameLoop.ts b/apps/server/src/domains/game/GameLoop.ts deleted file mode 100644 index 1838556..0000000 --- a/apps/server/src/domains/game/GameLoop.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Player } from "./entities/player/Player.js"; -import { MapStore } from "./entities/map/MapStore"; -import { getPlayerGridIndex } from "./entities/player/playerPosition.js"; -import { config } from "@repo/shared"; -import type { gridMapTypes } from "@repo/shared"; -import { logEvent } from "@server/logging/logEvent"; - -// コールバックで渡すデータの型定義 -export interface TickData { - players: { - id: string; - x: number; - y: number; - teamId: number; - }[]; - cellUpdates: gridMapTypes.CellUpdate[]; -} - -export class GameLoop { - private loopId: NodeJS.Timeout | null = null; - private startTime: number = 0; - - constructor( - private roomId: string, - private tickRate: number, - private playerIds: string[], - private players: Map, - private mapStore: MapStore, - private onTick: (data: TickData) => void, - private onGameEnd: () => void // ゲーム終了時のコールバック - ) {} - - start() { - // 既にループが回っている場合は何もしない - if (this.loopId) return; - - this.startTime = Date.now(); - - this.loopId = setInterval(() => { - // 時間経過のチェック - const elapsedTimeMs = Date.now() - this.startTime; - if (elapsedTimeMs >= config.GAME_CONFIG.GAME_DURATION_SEC * 1000) { - // ゲーム終了時にループを止めて終了処理へ - this.stop(); - this.onGameEnd(); - return; // 今回のフレームの座標更新はスキップ - } - - const playersData: TickData["players"] = []; - - // 1. 各プレイヤーの座標処理とマス塗りの判定 - this.playerIds.forEach(id => { - const player = this.players.get(id); - if (!player) return; - - const gridIndex = getPlayerGridIndex(player); - if (gridIndex !== null) { - this.mapStore.paintCell(gridIndex, player.teamId); - } - - // 送信用のプレイヤーデータを構築 - playersData.push({ - id: player.id, - x: player.x, - y: player.y, - teamId: player.teamId, - }); - }); - - // 2. マスの差分(Diff)を取得 - const cellUpdates = this.mapStore.getAndClearUpdates(); - - // 3. 通信層(GameHandler)へデータを渡す - this.onTick({ - players: playersData, - cellUpdates: cellUpdates, - }); - - }, this.tickRate); - - logEvent("GameLoop", { - event: "GAME_LOOP", - result: "started", - roomId: this.roomId, - tickRate: this.tickRate, - }); - } - - stop() { - if (this.loopId) { - clearInterval(this.loopId); - this.loopId = null; - logEvent("GameLoop", { - event: "GAME_LOOP", - result: "stopped", - roomId: this.roomId, - }); - } - } -} \ No newline at end of file diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index cf8725f..799d858 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -1,4 +1,4 @@ -import { type TickData } from "./GameLoop"; +import { type TickData } from "./loop/GameLoop"; import { Player } from "./entities/player/Player.js"; import { GameSessionService } from "./application/services/GameSessionService"; diff --git a/apps/server/src/domains/game/GameSessionManager.ts b/apps/server/src/domains/game/GameSessionManager.ts index ebd6505..0606222 100644 --- a/apps/server/src/domains/game/GameSessionManager.ts +++ b/apps/server/src/domains/game/GameSessionManager.ts @@ -1,4 +1,4 @@ -import { type TickData } from "./GameLoop"; +import { type TickData } from "./loop/GameLoop"; import { Player } from "./entities/player/Player.js"; import { GameSessionService } from "./application/services/GameSessionService"; diff --git a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts index 7cdb2d0..2686513 100644 --- a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts +++ b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts @@ -1,4 +1,4 @@ -import type { TickData } from "../../GameLoop"; +import type { TickData } from "../../loop/GameLoop"; import type { playerTypes } from "@repo/shared"; export interface StartGamePort { diff --git a/apps/server/src/domains/game/application/services/GameRoomSession.ts b/apps/server/src/domains/game/application/services/GameRoomSession.ts index 302fda2..4ac08b7 100644 --- a/apps/server/src/domains/game/application/services/GameRoomSession.ts +++ b/apps/server/src/domains/game/application/services/GameRoomSession.ts @@ -1,5 +1,5 @@ import { logEvent } from "@server/logging/logEvent"; -import { GameLoop, type TickData } from "../../GameLoop"; +import { GameLoop, type TickData } from "../../loop/GameLoop"; import { Player } from "../../entities/player/Player.js"; import { MapStore } from "../../entities/map/MapStore"; import { createSpawnedPlayer } from "../../entities/player/playerSpawn.js"; diff --git a/apps/server/src/domains/game/application/services/GameSessionService.ts b/apps/server/src/domains/game/application/services/GameSessionService.ts index 634ac0a..020282b 100644 --- a/apps/server/src/domains/game/application/services/GameSessionService.ts +++ b/apps/server/src/domains/game/application/services/GameSessionService.ts @@ -1,5 +1,5 @@ import { config } from "@repo/shared"; -import { type TickData } from "../../GameLoop"; +import { type TickData } from "../../loop/GameLoop"; import { logEvent } from "@server/logging/logEvent"; import { GameRoomSession } from "./GameRoomSession"; diff --git a/apps/server/src/domains/game/entities/map/MapStore.ts b/apps/server/src/domains/game/entities/map/MapStore.ts index a23f0ff..15b03f8 100644 --- a/apps/server/src/domains/game/entities/map/MapStore.ts +++ b/apps/server/src/domains/game/entities/map/MapStore.ts @@ -1,6 +1,8 @@ // apps/server/src/domains/game/entities/map/MapStore.ts import type { gridMapTypes } from "@repo/shared"; -import { config } from "@repo/shared"; +import { createInitialGridColors } from "./mapGrid.js"; +import { paintCellIfChanged } from "./mapPainting.js"; +import { drainPendingUpdates } from "./mapUpdates.js"; export class MapStore { // 全マスの現在の色(teamId)を保持 @@ -10,8 +12,7 @@ constructor() { // 初期状態は -1 (無色) などで初期化 - const totalCells = config.GAME_CONFIG.GRID_COLS * config.GAME_CONFIG.GRID_ROWS; - this.gridColors = new Array(totalCells).fill(-1); + this.gridColors = createInitialGridColors(); this.pendingUpdates = []; } @@ -19,18 +20,18 @@ * マスを塗り、色が変化した場合のみ差分キューに追加する */ public paintCell(index: number, teamId: number): void { - if (this.gridColors[index] !== teamId) { - this.gridColors[index] = teamId; - this.pendingUpdates.push({ index, teamId }); - } + paintCellIfChanged({ + gridColors: this.gridColors, + pendingUpdates: this.pendingUpdates, + index, + teamId, + }); } /** * 溜まっている差分を取得し、キューをクリアする(ループ送信時に使用) */ public getAndClearUpdates(): gridMapTypes.CellUpdate[] { - const updates = [...this.pendingUpdates]; - this.pendingUpdates = []; - return updates; + return drainPendingUpdates(this.pendingUpdates); } } \ No newline at end of file diff --git a/apps/server/src/domains/game/entities/map/mapGrid.ts b/apps/server/src/domains/game/entities/map/mapGrid.ts new file mode 100644 index 0000000..c08cc21 --- /dev/null +++ b/apps/server/src/domains/game/entities/map/mapGrid.ts @@ -0,0 +1,6 @@ +import { config } from "@repo/shared"; + +export const createInitialGridColors = (): number[] => { + const totalCells = config.GAME_CONFIG.GRID_COLS * config.GAME_CONFIG.GRID_ROWS; + return new Array(totalCells).fill(-1); +}; diff --git a/apps/server/src/domains/game/entities/map/mapPainting.ts b/apps/server/src/domains/game/entities/map/mapPainting.ts new file mode 100644 index 0000000..c03eab1 --- /dev/null +++ b/apps/server/src/domains/game/entities/map/mapPainting.ts @@ -0,0 +1,22 @@ +import type { gridMapTypes } from "@repo/shared"; + +type PaintCellParams = { + gridColors: number[]; + pendingUpdates: gridMapTypes.CellUpdate[]; + index: number; + teamId: number; +}; + +export const paintCellIfChanged = ({ + gridColors, + pendingUpdates, + index, + teamId, +}: PaintCellParams): void => { + if (gridColors[index] === teamId) { + return; + } + + gridColors[index] = teamId; + pendingUpdates.push({ index, teamId }); +}; diff --git a/apps/server/src/domains/game/entities/map/mapUpdates.ts b/apps/server/src/domains/game/entities/map/mapUpdates.ts new file mode 100644 index 0000000..091ed0b --- /dev/null +++ b/apps/server/src/domains/game/entities/map/mapUpdates.ts @@ -0,0 +1,9 @@ +import type { gridMapTypes } from "@repo/shared"; + +export const drainPendingUpdates = ( + pendingUpdates: gridMapTypes.CellUpdate[] +): gridMapTypes.CellUpdate[] => { + const updates = [...pendingUpdates]; + pendingUpdates.length = 0; + return updates; +}; diff --git a/apps/server/src/domains/game/loop/GameLoop.ts b/apps/server/src/domains/game/loop/GameLoop.ts new file mode 100644 index 0000000..de3617c --- /dev/null +++ b/apps/server/src/domains/game/loop/GameLoop.ts @@ -0,0 +1,100 @@ +import { Player } from "../entities/player/Player.js"; +import { MapStore } from "../entities/map/MapStore"; +import { getPlayerGridIndex } from "../entities/player/playerPosition.js"; +import { config } from "@repo/shared"; +import type { gridMapTypes } from "@repo/shared"; +import { logEvent } from "@server/logging/logEvent"; + +// コールバックで渡すデータの型定義 +export interface TickData { + players: { + id: string; + x: number; + y: number; + teamId: number; + }[]; + cellUpdates: gridMapTypes.CellUpdate[]; +} + +export class GameLoop { + private loopId: NodeJS.Timeout | null = null; + private startTime: number = 0; + + constructor( + private roomId: string, + private tickRate: number, + private playerIds: string[], + private players: Map, + private mapStore: MapStore, + private onTick: (data: TickData) => void, + private onGameEnd: () => void // ゲーム終了時のコールバック + ) {} + + start() { + // 既にループが回っている場合は何もしない + if (this.loopId) return; + + this.startTime = Date.now(); + + this.loopId = setInterval(() => { + // 時間経過のチェック + const elapsedTimeMs = Date.now() - this.startTime; + if (elapsedTimeMs >= config.GAME_CONFIG.GAME_DURATION_SEC * 1000) { + // ゲーム終了時にループを止めて終了処理へ + this.stop(); + this.onGameEnd(); + return; // 今回のフレームの座標更新はスキップ + } + + const playersData: TickData["players"] = []; + + // 1. 各プレイヤーの座標処理とマス塗りの判定 + this.playerIds.forEach(id => { + const player = this.players.get(id); + if (!player) return; + + const gridIndex = getPlayerGridIndex(player); + if (gridIndex !== null) { + this.mapStore.paintCell(gridIndex, player.teamId); + } + + // 送信用のプレイヤーデータを構築 + playersData.push({ + id: player.id, + x: player.x, + y: player.y, + teamId: player.teamId, + }); + }); + + // 2. マスの差分(Diff)を取得 + const cellUpdates = this.mapStore.getAndClearUpdates(); + + // 3. 通信層(GameHandler)へデータを渡す + this.onTick({ + players: playersData, + cellUpdates: cellUpdates, + }); + + }, this.tickRate); + + logEvent("GameLoop", { + event: "GAME_LOOP", + result: "started", + roomId: this.roomId, + tickRate: this.tickRate, + }); + } + + stop() { + if (this.loopId) { + clearInterval(this.loopId); + this.loopId = null; + logEvent("GameLoop", { + event: "GAME_LOOP", + result: "stopped", + roomId: this.roomId, + }); + } + } +} \ No newline at end of file