diff --git a/apps/server/src/domains/game/application/services/bot/combat/BotHitStunPolicy.ts b/apps/server/src/domains/game/application/services/bot/combat/BotHitStunPolicy.ts new file mode 100644 index 0000000..3bffbf0 --- /dev/null +++ b/apps/server/src/domains/game/application/services/bot/combat/BotHitStunPolicy.ts @@ -0,0 +1,29 @@ +/** + * BotHitStunPolicy + * Bot被弾硬直時間の判定と更新時刻計算を提供する + */ + +/** Bot被弾硬直ポリシーの初期化入力 */ +export type BotHitStunPolicyParams = { + hitStunMs: number; +}; + +/** Bot被弾硬直の判定と次回硬直終了時刻計算を提供する */ +export class BotHitStunPolicy { + private readonly hitStunMs: number; + + constructor({ hitStunMs }: BotHitStunPolicyParams) { + this.hitStunMs = hitStunMs; + } + + /** 現在時刻と硬直終了時刻から硬直中かどうかを判定する */ + public isStunned(nowMs: number, stunUntilMs: number): boolean { + return nowMs < stunUntilMs; + } + + /** 現在の硬直終了時刻と被弾時刻から次回硬直終了時刻を計算する */ + public calculateNextStunUntilMs(currentStunUntilMs: number, nowMs: number): number { + const candidateStunUntilMs = nowMs + this.hitStunMs; + return Math.max(currentStunUntilMs, candidateStunUntilMs); + } +} diff --git a/apps/server/src/domains/game/application/services/bot/index.ts b/apps/server/src/domains/game/application/services/bot/index.ts index 94bd860..9964fa4 100644 --- a/apps/server/src/domains/game/application/services/bot/index.ts +++ b/apps/server/src/domains/game/application/services/bot/index.ts @@ -17,3 +17,6 @@ /** Bot爆弾アクションハンドラ生成関数を再公開 */ export { createBotBombActionHandler } from "./adapters/BotBombActionAdapter.js"; + +/** Bot被弾硬直ポリシーを再公開 */ +export { BotHitStunPolicy } from "./combat/BotHitStunPolicy.js"; diff --git a/apps/server/src/domains/game/application/services/bot/orchestrators/BotTurnOrchestrator.ts b/apps/server/src/domains/game/application/services/bot/orchestrators/BotTurnOrchestrator.ts index aa432dd..cf2fb6b 100644 --- a/apps/server/src/domains/game/application/services/bot/orchestrators/BotTurnOrchestrator.ts +++ b/apps/server/src/domains/game/application/services/bot/orchestrators/BotTurnOrchestrator.ts @@ -8,6 +8,7 @@ import { moveTowardsTarget } from "../movement/MovePlanner.js"; import { chooseNextTarget } from "../policies/TargetSelectionPolicy.js"; import { decideBombPlacement } from "../policies/BombPlacementPolicy.js"; +import { BotHitStunPolicy } from "../combat/BotHitStunPolicy.js"; import { BotStateStore } from "../state/BotStateStore.js"; import type { BotDecision } from "../types/BotTypes.js"; @@ -18,6 +19,9 @@ /** Botの1tick分の意思決定を提供するオーケストレータ */ export class BotTurnOrchestrator { private stateStore = new BotStateStore(); + private readonly hitStunPolicy = new BotHitStunPolicy({ + hitStunMs: config.GAME_CONFIG.PLAYER_HIT_STUN_MS, + }); public decide( botPlayerId: BotPlayerId, @@ -35,8 +39,21 @@ targetRow: currentRow, lastBombPlacedAtMs: Number.NEGATIVE_INFINITY, bombSeq: 0, + stunUntilMs: Number.NEGATIVE_INFINITY, }); + if (this.hitStunPolicy.isStunned(nowMs, currentState.stunUntilMs)) { + this.stateStore.set(botPlayerId, { + ...currentState, + }); + + return { + nextX: player.x, + nextY: player.y, + placeBombPayload: null, + }; + } + const targetCenterX = currentState.targetCol + 0.5; const targetCenterY = currentState.targetRow + 0.5; const reachedTarget = @@ -69,6 +86,7 @@ targetRow: nextTarget.row, bombSeq: bombDecision.nextBombSeq, lastBombPlacedAtMs: bombDecision.nextLastBombPlacedAtMs, + stunUntilMs: currentState.stunUntilMs, }); return { @@ -78,6 +96,19 @@ }; } + /** 指定Botへ被弾硬直を適用する */ + public applyHitStun(botPlayerId: BotPlayerId, nowMs: number): void { + this.stateStore.update(botPlayerId, (state) => { + return { + ...state, + stunUntilMs: this.hitStunPolicy.calculateNextStunUntilMs( + state.stunUntilMs, + nowMs, + ), + }; + }); + } + public clear(): void { this.stateStore.clear(); } diff --git a/apps/server/src/domains/game/application/services/bot/state/BotStateStore.ts b/apps/server/src/domains/game/application/services/bot/state/BotStateStore.ts index 802f4cf..17abae5 100644 --- a/apps/server/src/domains/game/application/services/bot/state/BotStateStore.ts +++ b/apps/server/src/domains/game/application/services/bot/state/BotStateStore.ts @@ -17,6 +17,19 @@ this.states.set(botPlayerId, state); } + /** 既存状態がある場合のみ更新関数を適用する */ + public update( + botPlayerId: BotPlayerId, + updater: (state: BotState) => BotState, + ): void { + const current = this.states.get(botPlayerId); + if (!current) { + return; + } + + this.states.set(botPlayerId, updater(current)); + } + public clear(): void { this.states.clear(); } diff --git a/apps/server/src/domains/game/application/services/bot/types/BotTypes.ts b/apps/server/src/domains/game/application/services/bot/types/BotTypes.ts index fb3ebf3..8e95cd0 100644 --- a/apps/server/src/domains/game/application/services/bot/types/BotTypes.ts +++ b/apps/server/src/domains/game/application/services/bot/types/BotTypes.ts @@ -10,6 +10,7 @@ targetRow: number; lastBombPlacedAtMs: number; bombSeq: number; + stunUntilMs: number; }; /** 移動先のグリッド座標 */