diff --git a/apps/client/src/scenes/game/application/BombHitContextProvider.ts b/apps/client/src/scenes/game/application/BombHitContextProvider.ts index f79e43a..691c13b 100644 --- a/apps/client/src/scenes/game/application/BombHitContextProvider.ts +++ b/apps/client/src/scenes/game/application/BombHitContextProvider.ts @@ -4,7 +4,6 @@ * プレイヤー管理構造から最小の判定DTOへ変換して返す */ import { config } from "@client/config"; -import { LocalPlayerController } from "@client/scenes/game/entities/player/PlayerController"; import type { domain } from "@repo/shared"; type TeamCollisionCircle = domain.game.bombHit.TeamCollisionCircle; @@ -31,40 +30,20 @@ this.myId = myId; } - /** ローカルプレイヤーの判定用DTOを取得する */ - public getLocalPlayerCircle(): TeamCollisionCircle | null { + /** 自プレイヤーの被弾判定用円情報を取得する(不在ならnull) */ + public getLocalReportableCircle(): ReportablePlayerCircle | null { const me = this.players[this.myId]; - if (!me || !(me instanceof LocalPlayerController)) { - return null; - } + if (!me) return null; const position = me.getPosition(); const snapshot = me.getSnapshot(); return { + playerId: this.myId, x: position.x, y: position.y, radius: config.GAME_CONFIG.PLAYER_RADIUS, teamId: snapshot.teamId, }; } - - /** 被弾報告対象として自プレイヤーの円情報を取得する */ - public getReportablePlayerCircles(): ReportablePlayerCircle[] { - const me = this.players[this.myId]; - if (!me) return []; - - const position = me.getPosition(); - const snapshot = me.getSnapshot(); - - return [ - { - playerId: this.myId, - x: position.x, - y: position.y, - radius: config.GAME_CONFIG.PLAYER_RADIUS, - teamId: snapshot.teamId, - }, - ]; - } } diff --git a/apps/client/src/scenes/game/application/BombHitOrchestrator.ts b/apps/client/src/scenes/game/application/BombHitOrchestrator.ts index f9ad5bc..f0484ea 100644 --- a/apps/client/src/scenes/game/application/BombHitOrchestrator.ts +++ b/apps/client/src/scenes/game/application/BombHitOrchestrator.ts @@ -17,7 +17,7 @@ /** 爆弾爆発イベントの判定結果を表す型 */ export type BombHitEvaluationResult = { status: "duplicate" | "missing-local-player" | "no-hit" | "hit"; - hitPlayerIds: string[]; + hitPlayerId: string | null; }; /** 爆弾当たり判定の実行順序を制御する */ @@ -32,49 +32,30 @@ /** 爆弾爆発イベントを受けて自プレイヤーの当たり判定を実行し結果を返す */ public handleBombExploded(payload: BombExplodedPayload): BombHitEvaluationResult { if (this.handledBombIds.has(payload.bombId)) { - return { - status: "duplicate", - hitPlayerIds: [], - }; + return { status: "duplicate", hitPlayerId: null }; } this.handledBombIds.add(payload.bombId); - const reportablePlayers = this.contextProvider.getReportablePlayerCircles(); - if (reportablePlayers.length === 0) { - return { - status: "missing-local-player", - hitPlayerIds: [], - }; + const localPlayer = this.contextProvider.getLocalReportableCircle(); + if (!localPlayer) { + return { status: "missing-local-player", hitPlayerId: null }; } - const hitPlayerIds = reportablePlayers - .filter((player) => { - const result = checkBombHit({ - bomb: { - x: payload.x, - y: payload.y, - radius: payload.radius, - teamId: payload.teamId, - }, - player, - }); + const result = checkBombHit({ + bomb: { + x: payload.x, + y: payload.y, + radius: payload.radius, + teamId: payload.teamId, + }, + player: localPlayer, + }); - return result.isHit; - }) - .map((player) => player.playerId); - - if (hitPlayerIds.length === 0) { - return { - status: "no-hit", - hitPlayerIds: [], - }; + if (!result.isHit) { + return { status: "no-hit", hitPlayerId: null }; } - return { - status: "hit", - hitPlayerIds, - }; - + return { status: "hit", hitPlayerId: localPlayer.playerId }; } /** 判定済み状態を初期化する */ diff --git a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts index 6597771..c2fe46e 100644 --- a/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts +++ b/apps/client/src/scenes/game/application/combat/CombatLifecycleFacade.ts @@ -62,19 +62,14 @@ /** 爆弾爆発時の判定と後続処理を実行する */ public handleBombExploded(payload: BombExplodedPayload): void { const result = this.bombHitOrchestrator.handleBombExploded(payload); - const hasLocalHit = result.hitPlayerIds.includes(this.myId); - if (hasLocalHit) { - this.playerDeathPolicy.applyLocalHitStun(); - this.playerHitEffectOrchestrator.handleLocalBombHit(this.myId); + if (!result.hitPlayerId) return; + + this.playerDeathPolicy.applyLocalHitStun(); + this.playerHitEffectOrchestrator.handleLocalBombHit(this.myId); + + if (this.hitReportPolicy.shouldSendReport(result.status, payload.bombId, result.hitPlayerId)) { + this.onSendBombHitReport(payload.bombId, result.hitPlayerId); } - - result.hitPlayerIds.forEach((targetPlayerId) => { - if (!this.hitReportPolicy.shouldSendReport(result.status, payload.bombId, targetPlayerId)) { - return; - } - - this.onSendBombHitReport(payload.bombId, targetPlayerId); - }); } /** ネットワーク被弾通知を適用する */ diff --git a/apps/server/src/domains/game/GameManager.ts b/apps/server/src/domains/game/GameManager.ts index 207db37..2f0c8cf 100644 --- a/apps/server/src/domains/game/GameManager.ts +++ b/apps/server/src/domains/game/GameManager.ts @@ -5,10 +5,11 @@ import type { domain, GameResultPayload, - PlaceBombPayload, } from "@repo/shared"; import { Player } from "./entities/player/Player.js"; import { GameRoomSession } from "./application/services/GameRoomSession"; +import type { GameSessionCallbacks } from "./application/services/GameRoomSession"; +import type { ActiveBombRegistration } from "./application/ports/gameUseCasePorts"; import { GameSessionLifecycleService } from "./application/services/GameSessionLifecycleService"; import { GamePlayerOperationService } from "./application/services/GamePlayerOperationService"; @@ -66,18 +67,12 @@ startRoomSession( playerIds: string[], playerNamesById: Record, - onTick: (data: domain.game.tick.TickData) => void, - onGameEnd: (payload: GameResultPayload) => void, - onBotPlaceBomb?: (ownerId: string, payload: PlaceBombPayload) => void, - onBotBombHit?: (targetPlayerId: string, bombId: string) => void, + callbacks: GameSessionCallbacks, ) { this.lifecycleService.startRoomSession( playerIds, playerNamesById, - onTick, - onGameEnd, - onBotPlaceBomb, - onBotBombHit, + callbacks, ); } @@ -102,25 +97,8 @@ } /** 設置済み爆弾をアクティブレジストリに登録する */ - registerActiveBomb( - bombId: string, - ownerPlayerId: string, - x: number, - y: number, - explodeAtElapsedMs: number, - ): void { - this.lifecycleService.registerActiveBomb( - bombId, - ownerPlayerId, - x, - y, - explodeAtElapsedMs, - ); - } - - /** 指定プレイヤーがBotなら被弾硬直を適用する */ - applyBotHitStun(playerId: string, nowMs: number): boolean { - return this.lifecycleService.applyBotHitStun(playerId, nowMs); + registerActiveBomb(registration: ActiveBombRegistration): void { + this.lifecycleService.registerActiveBomb(registration); } dispose(): void { diff --git a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts index c1f0b72..bf0d735 100644 --- a/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts +++ b/apps/server/src/domains/game/application/ports/gameUseCasePorts.ts @@ -17,16 +17,14 @@ UpdateMapCellsPayload, UpdatePlayersPayload, } from "@repo/shared"; +import type { GameSessionCallbacks } from "../services/GameRoomSession"; /** ゲーム開始ユースケースが利用するゲーム管理入力ポート */ export interface StartGamePort { startRoomSession( playerIds: string[], playerNamesById: Record, - onTick: (data: domain.game.tick.TickData) => void, - onGameEnd: (payload: GameResultPayload) => void, - onBotPlaceBomb?: (ownerId: string, payload: PlaceBombPayload) => void, - onBotBombHit?: (targetPlayerId: string, bombId: string) => void, + callbacks: GameSessionCallbacks, ): void; getRoomStartTime(): number | undefined; } @@ -120,25 +118,23 @@ export interface BombPlacementPort { shouldBroadcastBombPlaced(dedupeKey: string, nowMs: number): boolean; issueServerBombId(): string; - registerActiveBomb( - bombId: string, - ownerPlayerId: string, - x: number, - y: number, - explodeAtElapsedMs: number, - ): void; + registerActiveBomb(registration: ActiveBombRegistration): void; } +/** registerActiveBomb に渡す爆弾登録情報 */ +export type ActiveBombRegistration = { + bombId: string; + ownerPlayerId: string; + x: number; + y: number; + explodeAtElapsedMs: number; +}; + /** 被弾報告ユースケースが利用する重複排除入力ポート */ export interface BombHitReportValidationPort { shouldBroadcastBombHitReport(dedupeKey: string, nowMs: number): boolean; } -/** 被弾報告ユースケースが利用するBot被弾反映入力ポート */ -export interface BotHitReactionPort { - applyBotHitStun(playerId: string, nowMs: number): boolean; -} - /** 爆弾設置ユースケースの入力値 */ export type PlaceBombInput = { socketId: string; diff --git a/apps/server/src/domains/game/application/services/GameRoomSession.ts b/apps/server/src/domains/game/application/services/GameRoomSession.ts index 82c6f97..f89c7da 100644 --- a/apps/server/src/domains/game/application/services/GameRoomSession.ts +++ b/apps/server/src/domains/game/application/services/GameRoomSession.ts @@ -8,9 +8,10 @@ logResults, logScopes, } from "@server/logging/index"; -import type { domain, GameResultPayload } from "@repo/shared"; +import type { domain, GameResultPayload, PlaceBombPayload } from "@repo/shared"; +import type { ActiveBombRegistration } from "../ports/gameUseCasePorts"; import { config } from "@server/config"; -import { GameLoop } from "../../loop/GameLoop"; +import { GameLoop, type GameLoopCallbacks } from "../../loop/GameLoop"; import { Player } from "../../entities/player/Player.js"; import { MapStore } from "../../entities/map/MapStore"; import { BombStateStore } from "../../entities/bomb/BombStateStore"; @@ -21,7 +22,14 @@ } from "../../entities/player/playerMovement.js"; import { buildGameResultPayload } from "./gameResultCalculator.js"; import { TeamAssignmentService } from "../services/TeamAssignmentService.js"; -import type { PlaceBombPayload } from "@repo/shared"; + +/** GameRoomSession のコールバック集合 */ +export type GameSessionCallbacks = { + onTick: (data: domain.game.tick.TickData) => void; + onGameEnd: (payload: GameResultPayload) => void; + onBotPlaceBomb?: (ownerId: string, payload: PlaceBombPayload) => void; + onBotBombHit?: (targetPlayerId: string, bombId: string) => void; +}; /** ルーム単位のゲーム状態とループ進行を保持するセッションクラス */ export class GameRoomSession { @@ -57,10 +65,7 @@ public start( tickRate: number, - onTick: (data: domain.game.tick.TickData) => void, - onGameEnd: (payload: GameResultPayload) => void, - onBotPlaceBomb?: (ownerId: string, payload: PlaceBombPayload) => void, - onBotBombHit?: (targetPlayerId: string, bombId: string) => void, + callbacks: GameSessionCallbacks, ): void { if (this.gameLoop) { return; @@ -73,23 +78,28 @@ ).GAME_START_DELAY_MS; const startDelayMs = Math.max(0, gameStartDelayMs ?? 0); this.startTime = Date.now() + startDelayMs; - this.gameLoop = new GameLoop( - this.roomId, - tickRate, - this.players, - this.mapStore, - this.bombStateStore.activeBombRegistry, - onTick, - () => { + + const loopCallbacks: GameLoopCallbacks = { + onTick: callbacks.onTick, + onGameEnd: () => { const resultPayload = buildGameResultPayload( this.mapStore.getGridColorsSnapshot(), ); this.dispose(); - onGameEnd(resultPayload); + callbacks.onGameEnd(resultPayload); }, - onBotPlaceBomb, - onBotBombHit, - ); + onBotPlaceBomb: callbacks.onBotPlaceBomb, + onBotBombHit: callbacks.onBotBombHit, + }; + + this.gameLoop = new GameLoop({ + roomId: this.roomId, + tickRate, + players: this.players, + mapStore: this.mapStore, + activeBombRegistry: this.bombStateStore.activeBombRegistry, + callbacks: loopCallbacks, + }); if (startDelayMs === 0) { this.gameLoop.start(); @@ -180,33 +190,18 @@ } /** 設置済み爆弾をアクティブレジストリに登録する */ - public registerActiveBomb( - bombId: string, - ownerPlayerId: string, - x: number, - y: number, - explodeAtElapsedMs: number, - ): void { - const player = this.players.get(ownerPlayerId); + public registerActiveBomb(registration: ActiveBombRegistration): void { + const player = this.players.get(registration.ownerPlayerId); const ownerTeamId = player?.teamId ?? -1; this.bombStateStore.activeBombRegistry.registerBomb({ - bombId, - x, - y, - explodeAtElapsedMs, + bombId: registration.bombId, + x: registration.x, + y: registration.y, + explodeAtElapsedMs: registration.explodeAtElapsedMs, ownerTeamId, }); } - /** 指定プレイヤーがBotなら被弾硬直を適用する */ - public applyBotHitStun(playerId: string, nowMs: number): boolean { - if (!this.gameLoop) { - return false; - } - - return this.gameLoop.applyBotHitStun(playerId, nowMs); - } - public dispose(): void { if (this.startDelayTimer) { clearTimeout(this.startDelayTimer); diff --git a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts index 3c5f54e..6a66cdc 100644 --- a/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts +++ b/apps/server/src/domains/game/application/services/GameSessionLifecycleService.ts @@ -3,10 +3,10 @@ * ゲームセッションの開始,参照,終了時クリーンアップを管理する */ import { config } from "@server/config"; +import type { ActiveBombRegistration } from "../ports/gameUseCasePorts"; import type { domain, GameResultPayload, - PlaceBombPayload, } from "@repo/shared"; import { logEvent } from "@server/logging/logger"; import { @@ -14,7 +14,7 @@ logResults, logScopes, } from "@server/logging/index"; -import { GameRoomSession } from "./GameRoomSession"; +import { GameRoomSession, type GameSessionCallbacks } from "./GameRoomSession"; type GameSessionRef = { current: GameRoomSession | null }; type ActivePlayerIndex = Set; @@ -62,39 +62,14 @@ } /** 設置済み爆弾をアクティブレジストリに登録する */ - public registerActiveBomb( - bombId: string, - ownerPlayerId: string, - x: number, - y: number, - explodeAtElapsedMs: number, - ): void { - this.sessionRef.current?.registerActiveBomb( - bombId, - ownerPlayerId, - x, - y, - explodeAtElapsedMs, - ); - } - - /** 指定プレイヤーがBotなら被弾硬直を適用する */ - public applyBotHitStun(playerId: string, nowMs: number): boolean { - const session = this.sessionRef.current; - if (!session) { - return false; - } - - return session.applyBotHitStun(playerId, nowMs); + public registerActiveBomb(registration: ActiveBombRegistration): void { + this.sessionRef.current?.registerActiveBomb(registration); } public startRoomSession( playerIds: string[], playerNamesById: Record, - onTick: (data: domain.game.tick.TickData) => void, - onGameEnd: (payload: GameResultPayload) => void, - onBotPlaceBomb?: (ownerId: string, payload: PlaceBombPayload) => void, - onBotBombHit?: (targetPlayerId: string, bombId: string) => void, + callbacks: GameSessionCallbacks, ) { if (this.sessionRef.current) { logEvent(logScopes.GAME_SESSION_LIFECYCLE_SERVICE, { @@ -118,17 +93,14 @@ }); this.sessionRef.current = session; - session.start( - tickRate, - onTick, - (payload) => { + session.start(tickRate, { + ...callbacks, + onGameEnd: (payload) => { this.activePlayerIds.clear(); this.sessionRef.current = null; - onGameEnd(payload); + callbacks.onGameEnd(payload); }, - onBotPlaceBomb, - onBotBombHit, - ); + }); logEvent(logScopes.GAME_SESSION_LIFECYCLE_SERVICE, { event: gameDomainLogEvents.SESSION_START, diff --git a/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts b/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts index a34671d..e814e83 100644 --- a/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/placeBombUseCase.ts @@ -34,13 +34,13 @@ const bombId = bombStore.issueServerBombId(); - bombStore.registerActiveBomb( + bombStore.registerActiveBomb({ bombId, - input.socketId, - input.payload.x, - input.payload.y, - input.payload.explodeAtElapsedMs, - ); + ownerPlayerId: input.socketId, + x: input.payload.x, + y: input.payload.y, + explodeAtElapsedMs: input.payload.explodeAtElapsedMs, + }); output.publishBombPlacedToOthersInRoom( roomId, diff --git a/apps/server/src/domains/game/application/useCases/reportBombHitUseCase.ts b/apps/server/src/domains/game/application/useCases/reportBombHitUseCase.ts index cac9fbc..4a675ae 100644 --- a/apps/server/src/domains/game/application/useCases/reportBombHitUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/reportBombHitUseCase.ts @@ -1,9 +1,9 @@ /** * reportBombHitUseCase * 被弾報告を受け取り,死亡通知の配信処理へ橋渡しする + * Bot被弾はサーバー側GameLoopで直接検知するため,自プレイヤーの報告のみ受け付ける */ import type { - BotHitReactionPort, BombHitOutputPort, BombHitReportValidationPort, ReportBombHitInput, @@ -13,29 +13,14 @@ type ReportBombHitUseCaseParams = { roomId: string; validation: BombHitReportValidationPort; - botHitReaction: BotHitReactionPort; input: ReportBombHitInput; output: BombHitOutputPort; }; -/** 被弾報告を死亡通知へ変換して配信する */ -const publishPlayerDeadFromBombHit = ( - roomId: string, - input: ReportBombHitInput, - output: BombHitOutputPort, -): void => { - const deadPlayerId = input.payload.targetPlayerId ?? input.socketId; - - output.publishPlayerDeadToOthersInRoom(roomId, deadPlayerId, { - playerId: deadPlayerId, - }); -}; - /** 被弾報告を受け取り,死亡通知を同一ルームへ配信する */ export const reportBombHitUseCase = ({ roomId, validation, - botHitReaction, input, output, }: ReportBombHitUseCaseParams): void => { @@ -43,10 +28,9 @@ return; } - const targetPlayerId = input.payload.targetPlayerId; - if (targetPlayerId) { - botHitReaction.applyBotHitStun(targetPlayerId, input.nowMs); - } + const deadPlayerId = input.socketId; - publishPlayerDeadFromBombHit(roomId, input, output); + output.publishPlayerDeadToOthersInRoom(roomId, deadPlayerId, { + playerId: deadPlayerId, + }); }; diff --git a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts index 45b4a60..c800c3c 100644 --- a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts @@ -65,39 +65,41 @@ gameSession.startRoomSession( playerIds, playerNamesById, - (tickData) => { - if (tickData.playerUpdates.length > 0) { - updateRecipients.forEach((playerId) => { - const updatesForPlayer = excludeRecipientFromPlayerUpdates( - tickData.playerUpdates, - playerId, - ); + { + onTick: (tickData) => { + if (tickData.playerUpdates.length > 0) { + updateRecipients.forEach((playerId) => { + const updatesForPlayer = excludeRecipientFromPlayerUpdates( + tickData.playerUpdates, + playerId, + ); - if (updatesForPlayer.length === 0) { - return; - } + if (updatesForPlayer.length === 0) { + return; + } - output.publishUpdatePlayersToSocket(playerId, updatesForPlayer); + output.publishUpdatePlayersToSocket(playerId, updatesForPlayer); + }); + } + + if (tickData.cellUpdates.length > 0) { + output.publishMapCellUpdatesToRoom(roomId, tickData.cellUpdates); + } + }, + onGameEnd: (resultPayload) => { + logEvent(logScopes.GAME_USE_CASE, { + event: gameUseCaseLogEvents.GAME_END, + result: logResults.EMITTED, + roomId, + reason: "duration_elapsed", }); - } - - if (tickData.cellUpdates.length > 0) { - output.publishMapCellUpdatesToRoom(roomId, tickData.cellUpdates); - } + output.publishGameEndToRoom(roomId); + output.publishGameResultToRoom(roomId, resultPayload); + onGameEnd(); + }, + onBotPlaceBomb: handleBotBombAction, + onBotBombHit: handleBotBombHit, }, - (resultPayload) => { - logEvent(logScopes.GAME_USE_CASE, { - event: gameUseCaseLogEvents.GAME_END, - result: logResults.EMITTED, - roomId, - reason: "duration_elapsed", - }); - output.publishGameEndToRoom(roomId); - output.publishGameResultToRoom(roomId, resultPayload); - onGameEnd(); - }, - handleBotBombAction, - handleBotBombHit, ); const startTime = gameSession.getRoomStartTime() || Date.now(); diff --git a/apps/server/src/domains/game/loop/GameLoop.ts b/apps/server/src/domains/game/loop/GameLoop.ts index 3bb9a10..e2471d1 100644 --- a/apps/server/src/domains/game/loop/GameLoop.ts +++ b/apps/server/src/domains/game/loop/GameLoop.ts @@ -24,6 +24,24 @@ const { checkBombHit } = domain.game.bombHit; +/** GameLoop の初期化入力 */ +export type GameLoopOptions = { + roomId: string; + tickRate: number; + players: Map; + mapStore: MapStore; + activeBombRegistry: ActiveBombRegistry; + callbacks: GameLoopCallbacks; +}; + +/** GameLoop のコールバック集合 */ +export type GameLoopCallbacks = { + onTick: (data: domain.game.tick.TickData) => void; + onGameEnd: () => void; + onBotPlaceBomb?: (ownerId: string, payload: PlaceBombPayload) => void; + onBotBombHit?: (targetPlayerId: string, bombId: string) => void; +}; + /** ルーム内ゲーム進行を定周期で実行するループ管理クラス */ export class GameLoop { private loopId: NodeJS.Timeout | null = null; @@ -38,23 +56,21 @@ private botTurnOrchestrator: BotTurnOrchestrator = new BotTurnOrchestrator(); - constructor( - private roomId: string, - private tickRate: number, - private players: Map, - private mapStore: MapStore, - private activeBombRegistry: ActiveBombRegistry, - private onTick: (data: domain.game.tick.TickData) => void, - private onGameEnd: () => void, - private onBotPlaceBomb?: ( - ownerId: string, - payload: PlaceBombPayload, - ) => void, - private onBotBombHit?: ( - targetPlayerId: string, - bombId: string, - ) => void, - ) {} + private readonly roomId: string; + private readonly tickRate: number; + private readonly players: Map; + private readonly mapStore: MapStore; + private readonly activeBombRegistry: ActiveBombRegistry; + private readonly callbacks: GameLoopCallbacks; + + constructor(options: GameLoopOptions) { + this.roomId = options.roomId; + this.tickRate = options.tickRate; + this.players = options.players; + this.mapStore = options.mapStore; + this.activeBombRegistry = options.activeBombRegistry; + this.callbacks = options.callbacks; + } start() { // 既にループが回っている場合は何もしない @@ -93,7 +109,7 @@ let nowMs = performance.now(); if (nowMs >= this.endMonotonicTimeMs) { this.stop(); - this.onGameEnd(); + this.callbacks.onGameEnd(); return; } @@ -110,7 +126,7 @@ nowMs = performance.now(); if (nowMs >= this.endMonotonicTimeMs) { this.stop(); - this.onGameEnd(); + this.callbacks.onGameEnd(); return; } } @@ -133,7 +149,7 @@ this.updateBotPlayers(wallClockNowMs, elapsedMs, gridColorsSnapshot); this.detectBotBombHits(elapsedMs, wallClockNowMs); const tickData = this.buildTickData(); - this.onTick(tickData); + this.callbacks.onTick(tickData); } private updateBotPlayers( @@ -155,32 +171,16 @@ ); setPlayerPosition(player, decision.nextX, decision.nextY); - if (decision.placeBombPayload && this.onBotPlaceBomb) { - this.onBotPlaceBomb(player.id, decision.placeBombPayload); + if (decision.placeBombPayload && this.callbacks.onBotPlaceBomb) { + this.callbacks.onBotPlaceBomb(player.id, decision.placeBombPayload); } } }); } - /** 指定プレイヤーがBotなら被弾硬直を適用する */ - public applyBotHitStun(playerId: string, nowMs: number): boolean { - const player = this.players.get(playerId); - const isBotControlled = - !!player && - (isBotPlayerId(player.id) || - this.disconnectedBotControlledPlayerIds.has(player.id)); - - if (!isBotControlled) { - return false; - } - - this.botTurnOrchestrator.applyHitStun(playerId as BotPlayerId, nowMs); - return true; - } - /** 爆発済み爆弾とBotプレイヤーの当たり判定を実行する */ private detectBotBombHits(elapsedMs: number, nowMs: number): void { - const onBotBombHit = this.onBotBombHit; + const onBotBombHit = this.callbacks.onBotBombHit; if (!onBotBombHit) return; const explodedBombs = diff --git a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts index e0cbd1e..af90407 100644 --- a/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts +++ b/apps/server/src/domains/room/application/ports/roomUseCasePorts.ts @@ -4,7 +4,6 @@ */ import { domain } from "@repo/shared"; import type { - BotHitReactionPort, BombHitReportValidationPort, BombPlacementPort, DisconnectPlayerPort, @@ -20,7 +19,6 @@ & MovePlayerPort & BombPlacementPort & BombHitReportValidationPort - & BotHitReactionPort & DisconnectPlayerPort; /** ルーム参加処理の実行結果 */ diff --git a/apps/server/src/network/handlers/game/gameEventOrchestrators.ts b/apps/server/src/network/handlers/game/gameEventOrchestrators.ts index 91b34e9..206ac49 100644 --- a/apps/server/src/network/handlers/game/gameEventOrchestrators.ts +++ b/apps/server/src/network/handlers/game/gameEventOrchestrators.ts @@ -143,7 +143,6 @@ reportBombHitUseCase({ roomId, validation: gameManager, - botHitReaction: gameManager, input: { socketId: deps.socketId, payload,