diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index ec7626b..b495a92 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -2,7 +2,11 @@ * GameManager * ゲームセッション集合の生成,更新,参照管理を統括する */ -import type { gameTypes, GameResultPayload, PlaceBombPayload } from "@repo/shared"; +import type { + gameTypes, + GameResultPayload, + PlaceBombPayload, +} from "@repo/shared"; import { Player } from "./entities/player/Player.js"; import { GameRoomSession } from "./application/services/GameRoomSession"; import { GameSessionLifecycleService } from "./application/services/GameSessionLifecycleService"; @@ -23,8 +27,15 @@ 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); + this.lifecycleService = new GameSessionLifecycleService( + this.sessionRef, + this.activePlayerIds, + roomId, + ); + this.playerOperationService = new GamePlayerOperationService( + this.sessionRef, + this.activePlayerIds, + ); } // 外部(GameHandlerなど)から開始時刻を取得できるようにする @@ -48,12 +59,17 @@ * @param onTick 毎フレーム実行される送信用のコールバック関数 */ startRoomSession( - playerIds: string[], + playerIds: string[], onTick: (data: gameTypes.TickData) => void, onGameEnd: (payload: GameResultPayload) => void, onBotPlaceBomb?: (ownerId: string, payload: PlaceBombPayload) => void, ) { - this.lifecycleService.startRoomSession(playerIds, onTick, onGameEnd, onBotPlaceBomb); + this.lifecycleService.startRoomSession( + playerIds, + onTick, + onGameEnd, + onBotPlaceBomb, + ); } // 現在セッションのプレイヤーを取得 @@ -74,4 +90,4 @@ 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 f215ddf..a6eefb5 100644 --- a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts +++ b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts @@ -52,18 +52,27 @@ publishPongToSocket(payload: PongPayload): void; publishUpdatePlayersToSocket( socketId: string, - players: UpdatePlayersPayload + players: UpdatePlayersPayload, ): void; publishMapCellUpdatesToRoom( roomId: roomTypes.Room["roomId"], - cellUpdates: UpdateMapCellsPayload + cellUpdates: UpdateMapCellsPayload, ): void; publishGameEndToRoom(roomId: roomTypes.Room["roomId"]): void; - publishGameResultToRoom(roomId: roomTypes.Room["roomId"], payload: GameResultPayload): void; - publishGameStartToRoom(roomId: roomTypes.Room["roomId"], payload: GameStartPayload): void; + publishGameResultToRoom( + roomId: roomTypes.Room["roomId"], + payload: GameResultPayload, + ): void; + publishGameStartToRoom( + roomId: roomTypes.Room["roomId"], + payload: GameStartPayload, + ): void; publishCurrentPlayersToSocket(players: CurrentPlayersPayload): void; publishGameStartToSocket(payload: GameStartPayload): void; - publishPlayerRemovedToRoom(roomId: roomTypes.Room["roomId"], removedPlayerId: RemovePlayerPayload): void; + publishPlayerRemovedToRoom( + roomId: roomTypes.Room["roomId"], + removedPlayerId: RemovePlayerPayload, + ): void; } /** 爆弾ユースケースが利用する送信出力ポート */ @@ -71,9 +80,12 @@ publishBombPlacedToOthersInRoom( roomId: roomTypes.Room["roomId"], ownerSocketId: string, - payload: BombPlacedPayload + payload: BombPlacedPayload, ): void; - publishBombPlacedAckToSocket(socketId: string, payload: BombPlacedAckPayload): void; + publishBombPlacedAckToSocket( + socketId: string, + payload: BombPlacedAckPayload, + ): void; } /** 爆弾設置ユースケースが利用する爆弾状態入力ポート */ diff --git a/apps/server/src/domains/game/application/services/BotAiService.ts b/apps/server/src/domains/game/application/services/BotAiService.ts index cc30224..96132c7 100644 --- a/apps/server/src/domains/game/application/services/BotAiService.ts +++ b/apps/server/src/domains/game/application/services/BotAiService.ts @@ -53,10 +53,10 @@ { col, row: row - 1 }, ].filter((candidate) => { return ( - candidate.col >= 0 - && candidate.col < GRID_COLS - && candidate.row >= 0 - && candidate.row < GRID_ROWS + candidate.col >= 0 && + candidate.col < GRID_COLS && + candidate.row >= 0 && + candidate.row < GRID_ROWS ); }); @@ -66,22 +66,25 @@ const unpaintedCandidates = candidates.filter((candidate) => { return ( - getCellTeamId(gridColors, candidate.col, candidate.row, GRID_COLS) - === UNPAINTED_TEAM_ID + getCellTeamId(gridColors, candidate.col, candidate.row, GRID_COLS) === + UNPAINTED_TEAM_ID ); }); if ( - unpaintedCandidates.length > 0 - && Math.random() < clamp(UNPAINTED_PRIORITY_STRENGTH, 0, 1) + unpaintedCandidates.length > 0 && + Math.random() < clamp(UNPAINTED_PRIORITY_STRENGTH, 0, 1) ) { return ( - unpaintedCandidates[Math.floor(Math.random() * unpaintedCandidates.length)] - ?? { col, row } + unpaintedCandidates[ + Math.floor(Math.random() * unpaintedCandidates.length) + ] ?? { col, row } ); } - return candidates[Math.floor(Math.random() * candidates.length)] ?? { col, row }; + return ( + candidates[Math.floor(Math.random() * candidates.length)] ?? { col, row } + ); }; const moveTowardsTarget = ( @@ -97,9 +100,9 @@ const distance = Math.hypot(diffX, diffY); const maxStep = - config.GAME_CONFIG.PLAYER_SPEED - * (config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS / 1000) - * clamp(config.BOT_AI_CONFIG.MOVE_SMOOTHNESS, 0.1, 2); + config.GAME_CONFIG.PLAYER_SPEED * + (config.GAME_CONFIG.PLAYER_POSITION_UPDATE_MS / 1000) * + clamp(config.BOT_AI_CONFIG.MOVE_SMOOTHNESS, 0.1, 2); if (distance <= maxStep || distance === 0) { return { nextX: targetX, nextY: targetY }; @@ -140,20 +143,27 @@ const targetCenterX = currentState.targetCol + 0.5; const targetCenterY = currentState.targetRow + 0.5; const reachedTarget = - Math.hypot(targetCenterX - player.x, targetCenterY - player.y) - <= config.BOT_AI_CONFIG.TARGET_REACHED_EPSILON; + Math.hypot(targetCenterX - player.x, targetCenterY - player.y) <= + config.BOT_AI_CONFIG.TARGET_REACHED_EPSILON; const nextTarget = reachedTarget ? chooseNextTarget(currentCol, currentRow, gridColors) : { col: currentState.targetCol, row: currentState.targetRow }; - const moved = moveTowardsTarget(player.x, player.y, nextTarget.col, nextTarget.row); + const moved = moveTowardsTarget( + player.x, + player.y, + nextTarget.col, + nextTarget.row, + ); let placeBombPayload: PlaceBombPayload | null = null; - const canPlaceBomb = nowMs - currentState.lastBombPlacedAtMs >= BOMB_COOLDOWN_MS; + const canPlaceBomb = + nowMs - currentState.lastBombPlacedAtMs >= BOMB_COOLDOWN_MS; if ( - canPlaceBomb - && Math.random() < clamp(config.BOT_AI_CONFIG.BOMB_PLACE_PROBABILITY_PER_TICK, 0, 1) + canPlaceBomb && + Math.random() < + clamp(config.BOT_AI_CONFIG.BOMB_PLACE_PROBABILITY_PER_TICK, 0, 1) ) { const nextBombSeq = currentState.bombSeq + 1; placeBombPayload = { diff --git a/apps/server/src/domains/game/application/services/GameRoomSession.ts b/apps/server/src/domains/game/application/services/GameRoomSession.ts index f95e829..d6a2d72 100644 --- a/apps/server/src/domains/game/application/services/GameRoomSession.ts +++ b/apps/server/src/domains/game/application/services/GameRoomSession.ts @@ -69,7 +69,9 @@ this.mapStore, onTick, () => { - const resultPayload = buildGameResultPayload(this.mapStore.getGridColorsSnapshot()); + const resultPayload = buildGameResultPayload( + this.mapStore.getGridColorsSnapshot(), + ); this.dispose(); onGameEnd(resultPayload); }, diff --git a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts index 1e5715f..3fb9d09 100644 --- a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts +++ b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts @@ -3,9 +3,17 @@ * ゲームセッションの開始,参照,終了時クリーンアップを管理する */ import { config } from "@server/config"; -import type { gameTypes, GameResultPayload, PlaceBombPayload } from "@repo/shared"; +import type { + gameTypes, + GameResultPayload, + PlaceBombPayload, +} from "@repo/shared"; import { logEvent } from "@server/logging/logger"; -import { gameDomainLogEvents, logResults, logScopes } from "@server/logging/index"; +import { + gameDomainLogEvents, + logResults, + logScopes, +} from "@server/logging/index"; import { GameRoomSession } from "./GameRoomSession"; type GameSessionRef = { current: GameRoomSession | null }; @@ -16,7 +24,7 @@ constructor( private sessionRef: GameSessionRef, private activePlayerIds: ActivePlayerIndex, - private roomId: string + private roomId: string, ) {} public getRoomStartTime(): number | undefined { @@ -28,7 +36,10 @@ } public shouldBroadcastBombPlaced(dedupeKey: string, nowMs: number): boolean { - return this.sessionRef.current?.shouldBroadcastBombPlaced(dedupeKey, nowMs) ?? false; + return ( + this.sessionRef.current?.shouldBroadcastBombPlaced(dedupeKey, nowMs) ?? + false + ); } public issueServerBombId(): string { @@ -64,11 +75,16 @@ }); this.sessionRef.current = session; - session.start(tickRate, onTick, (payload) => { - this.activePlayerIds.clear(); - this.sessionRef.current = null; - onGameEnd(payload); - }, onBotPlaceBomb); + session.start( + tickRate, + onTick, + (payload) => { + this.activePlayerIds.clear(); + this.sessionRef.current = null; + onGameEnd(payload); + }, + onBotPlaceBomb, + ); logEvent(logScopes.GAME_SESSION_LIFECYCLE_SERVICE, { event: gameDomainLogEvents.SESSION_START, @@ -83,4 +99,4 @@ this.sessionRef.current = null; this.activePlayerIds.clear(); } -} \ No newline at end of file +} diff --git a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts index e793e05..9956be5 100644 --- a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts @@ -8,14 +8,22 @@ StartGamePort, } from "../ports/gameUseCasePorts"; import { logEvent } from "@server/logging/logger"; -import { gameUseCaseLogEvents, logResults, logScopes } from "@server/logging/index"; +import { + gameUseCaseLogEvents, + logResults, + logScopes, +} from "@server/logging/index"; import { placeBombUseCase } from "./placeBombUseCase"; -const excludeRecipientFromPlayerUpdates = ( +const excludeRecipientFromPlayerUpdates = < + TPlayerUpdate extends { id: string }, +>( playerUpdates: TPlayerUpdate[], - recipientId: string + recipientId: string, ): TPlayerUpdate[] => { - return playerUpdates.filter((playerUpdate) => playerUpdate.id !== recipientId); + return playerUpdates.filter( + (playerUpdate) => playerUpdate.id !== recipientId, + ); }; type StartGameUseCaseParams = { @@ -31,11 +39,11 @@ | "publishGameEndToRoom" | "publishGameResultToRoom" | "publishGameStartToRoom" - > & Pick< - BombOutputPort, - | "publishBombPlacedToOthersInRoom" - | "publishBombPlacedAckToSocket" - >; + > & + Pick< + BombOutputPort, + "publishBombPlacedToOthersInRoom" | "publishBombPlacedAckToSocket" + >; }; /** ゲームセッション開始とティック通知,終了通知を実行する */ @@ -56,7 +64,7 @@ updateRecipients.forEach((playerId) => { const updatesForPlayer = excludeRecipientFromPlayerUpdates( tickData.playerUpdates, - playerId + playerId, ); if (updatesForPlayer.length === 0) { diff --git a/apps/server/src/domains/game/loop/GameLoop.ts b/apps/server/src/domains/game/loop/GameLoop.ts index d4a6095..7c32b3f 100644 --- a/apps/server/src/domains/game/loop/GameLoop.ts +++ b/apps/server/src/domains/game/loop/GameLoop.ts @@ -8,8 +8,15 @@ import { config } from "@server/config"; import type { gameTypes, PlaceBombPayload } from "@repo/shared"; import { logEvent } from "@server/logging/logger"; -import { gameDomainLogEvents, logResults, logScopes } from "@server/logging/index"; -import { BotAiService, isBotPlayerId } from "../application/services/BotAiService"; +import { + gameDomainLogEvents, + logResults, + logScopes, +} from "@server/logging/index"; +import { + BotAiService, + isBotPlayerId, +} from "../application/services/BotAiService"; import { setPlayerPosition } from "../entities/player/playerMovement.js"; /** ルーム内ゲーム進行を定周期で実行するループ管理クラス */ @@ -20,7 +27,8 @@ private endMonotonicTimeMs: number = 0; private nextTickAtMs: number = 0; private readonly maxCatchUpTicks: number = 3; - private lastSentPlayers: Map = new Map(); + private lastSentPlayers: Map = + new Map(); private botAiService: BotAiService = new BotAiService(); constructor( @@ -30,7 +38,10 @@ private mapStore: MapStore, private onTick: (data: gameTypes.TickData) => void, private onGameEnd: () => void, - private onBotPlaceBomb?: (ownerId: string, payload: PlaceBombPayload) => void, + private onBotPlaceBomb?: ( + ownerId: string, + payload: PlaceBombPayload, + ) => void, ) {} start() { @@ -39,7 +50,8 @@ const nowMs = performance.now(); this.startMonotonicTimeMs = nowMs; - this.endMonotonicTimeMs = nowMs + config.GAME_CONFIG.GAME_DURATION_SEC * 1000; + this.endMonotonicTimeMs = + nowMs + config.GAME_CONFIG.GAME_DURATION_SEC * 1000; this.nextTickAtMs = nowMs + this.tickRate; this.lastSentPlayers.clear(); this.isRunning = true; @@ -75,7 +87,10 @@ let processedTicks = 0; - while (nowMs >= this.nextTickAtMs && processedTicks < this.maxCatchUpTicks) { + while ( + nowMs >= this.nextTickAtMs && + processedTicks < this.maxCatchUpTicks + ) { this.processSingleTick(); this.nextTickAtMs += this.tickRate; processedTicks += 1; @@ -99,7 +114,10 @@ const changedPlayers: gameTypes.TickData["playerUpdates"] = []; const activePlayerIds = new Set(); const nowMs = performance.now(); - const elapsedMs = Math.max(0, Math.round(nowMs - this.startMonotonicTimeMs)); + const elapsedMs = Math.max( + 0, + Math.round(nowMs - this.startMonotonicTimeMs), + ); const gridColorsSnapshot = this.mapStore.getGridColorsSnapshot(); // 1. 各プレイヤーの座標処理とマス塗りの判定 @@ -177,7 +195,10 @@ event: gameDomainLogEvents.GAME_LOOP, result: logResults.STOPPED, roomId: this.roomId, - elapsedMs: Math.max(0, Math.round(performance.now() - this.startMonotonicTimeMs)), + elapsedMs: Math.max( + 0, + Math.round(performance.now() - this.startMonotonicTimeMs), + ), }); } -} \ No newline at end of file +}