diff --git a/apps/server/src/domains/game/entities/map/MapStore.ts b/apps/server/src/domains/game/entities/map/MapStore.ts index d02fffd..191417a 100644 --- a/apps/server/src/domains/game/entities/map/MapStore.ts +++ b/apps/server/src/domains/game/entities/map/MapStore.ts @@ -5,14 +5,13 @@ import { domain } from "@repo/shared"; import { createInitialGridColors } from "./mapGrid.js"; import { paintCellIfChanged } from "./mapPainting.js"; -import { swapPendingUpdates } from "./mapUpdates.js"; /** ルーム内マップの塗り状態と更新差分を管理するストア */ export class MapStore { // 全マスの現在の色(teamId)を保持 private gridColors: number[]; // 次回の送信ループで送る差分リスト - public pendingUpdates: domain.game.gridMap.CellUpdate[]; + private pendingUpdates: domain.game.gridMap.CellUpdate[]; constructor() { // 初期状態は -1 (無色) などで初期化 @@ -34,9 +33,12 @@ /** * 溜まっている差分を取得し,キューをクリアする(ループ送信時に使用) + * 参照をそのまま返却し新しい空配列で差し替えることでコピーを回避する */ public getAndClearUpdates(): domain.game.gridMap.CellUpdate[] { - return swapPendingUpdates(this); + const updates = this.pendingUpdates; + this.pendingUpdates = []; + return updates; } /** 現在のマップ塗り状態をスナップショットとして返す */ diff --git a/apps/server/src/domains/game/entities/map/mapUpdates.ts b/apps/server/src/domains/game/entities/map/mapUpdates.ts deleted file mode 100644 index 4446d55..0000000 --- a/apps/server/src/domains/game/entities/map/mapUpdates.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * mapUpdates - * マップ差分キューの取り出しとクリア処理を提供する - */ -import { domain } from "@repo/shared"; - -/** - * 差分キューの参照をそのまま返却し,呼び出し元の配列を新しい空配列で差し替える - * スプレッドコピーを避けてゼロコピーで返却する - */ -export const swapPendingUpdates = ( - owner: { pendingUpdates: domain.game.gridMap.CellUpdate[] }, -): domain.game.gridMap.CellUpdate[] => { - const updates = owner.pendingUpdates; - owner.pendingUpdates = []; - return updates; -}; diff --git a/apps/server/src/domains/game/loop/GameLoop.ts b/apps/server/src/domains/game/loop/GameLoop.ts index bb3c464..a9c21ad 100644 --- a/apps/server/src/domains/game/loop/GameLoop.ts +++ b/apps/server/src/domains/game/loop/GameLoop.ts @@ -47,6 +47,9 @@ onBotBombHit?: (targetPlayerId: string, bombId: string) => void; }; +/** プレイヤーのグリッド位置キャッシュを含むエントリ */ +type PlayerGridCacheEntry = PlayerGridEntry & { player: Player }; + /** ルーム内ゲーム進行を定周期で実行するループ管理クラス */ export class GameLoop { private loopId: NodeJS.Timeout | null = null; @@ -252,7 +255,7 @@ const changedPlayers: domain.game.tick.TickData["playerUpdates"] = []; // 全プレイヤーのグリッド位置を1度だけ計算してキャッシュする - const gridEntries: (PlayerGridEntry & { player: Player })[] = []; + const gridEntries: PlayerGridCacheEntry[] = []; this.players.forEach((player) => { gridEntries.push({ playerId: player.id, @@ -262,18 +265,13 @@ }); }); - // 競合判定: 同一セルに異なるチームがいるかを解決する - const cellTeamMap = resolveUncontestedCells(gridEntries); + // 競合判定を経てマップを塗る + this.paintUncontestedCells(gridEntries); - // 競合のないセルのみ塗り,プレイヤー差分を収集する - for (const { playerId, gridIndex, player } of gridEntries) { + // プレイヤー差分を収集する + for (const { playerId, player } of gridEntries) { activePlayerIds.add(playerId); - if (gridIndex !== null && isCellPaintable(cellTeamMap, gridIndex)) { - this.mapStore.paintCell(gridIndex, player.teamId); - } - - // 送信用のプレイヤーデータを構築 const playerData: domain.game.tick.PlayerPositionUpdate = { id: player.id, x: player.x, @@ -295,6 +293,17 @@ return changedPlayers; } + /** 競合判定を行い,単一チームが占有するセルのみを塗る */ + private paintUncontestedCells(gridEntries: PlayerGridCacheEntry[]): void { + const cellTeamMap = resolveUncontestedCells(gridEntries); + + for (const { gridIndex, player } of gridEntries) { + if (gridIndex !== null && isCellPaintable(cellTeamMap, gridIndex)) { + this.mapStore.paintCell(gridIndex, player.teamId); + } + } + } + private cleanupInactivePlayerSnapshots(activePlayerIds: Set): void { Array.from(this.lastSentPlayers.keys()).forEach((playerId) => { if (!activePlayerIds.has(playerId)) {