Files
pinball-duel/src/ai/personalities/DefensiveAI.ts
aevgarik 4e3172de81 Phase 2 DoD closeout: curve physics, AI delta, share, setup UX
- Curve bumper: spin-modifier ±35° на отскоке (детерминированный знак на bumper)
  + визуальный вихрь-индикатор. Теперь curve механически отличается от standard.
- Easy/Medium AI delta: распознавательная дистанция (Easy 150px / Medium 280px),
  reliability (Easy 0.75 / Medium 0.95). difficulty прокидывается из каждой
  personality в shouldActivateFlipperBase. Hard остаётся placeholder для Phase 3.
- Share button на ResultScene: snapshot canvas → PNG blob → platform.shareImage().
  Mock логирует, YG использует SDK share. Telemetry: share_clicked / share_failed.
- Setup UX переписан: палитра типов сверху (4 типа + trash) + tap-палитра-tap-слот
  (unified mobile/desktop) + drag-from-palette ghost (desktop). Long-tap слота без
  выбранного типа удаляет содержимое (быстрый shortcut). Заменил cycle-on-click
  на normalуй setup, как в DoD.

Closes codex finding #2 (Phase 2 DoD gap).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 03:50:16 +03:00

76 lines
2.2 KiB
TypeScript

import type {
AIPlayer,
AIPlayerContext,
} from '../AIPlayer';
import { shouldActivateFlipperBase } from '../AIPlayer';
import type {
AIDifficulty,
AIDifficultyParams,
AIPersonality,
SetupConfig,
} from '../../types';
// DefensiveAI — «Бэтти-Защитница».
// Стратегия: цепкая защита, бустер только на cradle (не реализовано в Phase 1-2).
// Setup: 2 slingshot в углах ворот, 1 турбо рядом, 1 вираж в дальнем углу, 1 пустой.
// см. ~/Knowledge/Projects/pinball-duel/concepts/bot-ai-design.md#1-defensive-«бэтти-защитница»
export class DefensiveAI implements AIPlayer {
readonly personality: AIPersonality = 'defensive';
readonly difficulty: AIDifficulty;
readonly params: AIDifficultyParams;
private lastReactionTimeLeft = 0;
private lastReactionTimeRight = 0;
constructor(difficulty: AIDifficulty, params: AIDifficultyParams) {
this.difficulty = difficulty;
this.params = params;
}
update(ctx: AIPlayerContext): void {
// Defensive — реагирует надёжно на любой приближающийся мяч
const shouldLeft = shouldActivateFlipperBase(
ctx,
'left',
this.params,
this.lastReactionTimeLeft,
this.difficulty,
);
if (shouldLeft) {
if (ctx.leftFlipper.activate()) {
this.lastReactionTimeLeft = performance.now();
}
}
const shouldRight = shouldActivateFlipperBase(
ctx,
'right',
this.params,
this.lastReactionTimeRight,
this.difficulty,
);
if (shouldRight) {
if (ctx.rightFlipper.activate()) {
this.lastReactionTimeRight = performance.now();
}
}
}
generateSetup(): SetupConfig {
// см. bot-ai-design.md#defensive-setup-эвристика
return {
slots: [
{ slotId: 'goal_corner_left', bumperType: 'slingshot' },
{ slotId: 'goal_corner_right', bumperType: 'slingshot' },
{ slotId: 'near_goal_center', bumperType: 'turbo' },
{ slotId: 'mid_left', bumperType: 'curve' },
{ slotId: 'mid_right', bumperType: null }, // 1 пустой слот
],
};
}
destroy(): void {
// No state to clean
}
}