import { WeaponType } from '../data/Interfaces'; /** * Score system (task 9.3, req 12.1-12.8). * * Scoring table: * - Ninja sword kill ×2.0 base * - Shuriken kill ×1.0 base * - Perfect parry counterkill ×3.0 base * - 5-combo "刃接触" bonus +1500 * - Flawless level (no damage) ×3.0 total * - Remaining time bonus +10 pts / remaining sec * * Everything is deterministic and `Math.random`-free so QA can reproduce * every score calculation in unit tests. */ export const BASE_ENEMY_SCORE = 100; export const COMBO_BONUS = 1500; export const COMBO_THRESHOLD = 5; export const TIME_BONUS_PER_SEC = 10; export interface IScoreSnapshot { baseScore: number; comboBonus: number; timeBonus: number; flawlessMultiplier: number; finalScore: number; killCount: number; comboCount: number; consecutiveBladeHits: number; } export class ScoreSystem { private baseScore = 0; private comboBonus = 0; private killCount = 0; private consecutiveBladeHits = 0; private comboCount = 0; private flawless = true; private timeBonus = 0; public reset(): void { this.baseScore = 0; this.comboBonus = 0; this.killCount = 0; this.consecutiveBladeHits = 0; this.comboCount = 0; this.flawless = true; this.timeBonus = 0; } /** Record a kill weighted by weapon type (req 12.1-12.2). */ public recordEnemyKill(weapon: WeaponType): void { this.killCount++; const multiplier = weapon === WeaponType.NinjaSword ? 2 : 1; this.baseScore += BASE_ENEMY_SCORE * multiplier; } /** Perfect parry followed by a counter-kill (req 12.3). */ public recordParryKill(): void { this.killCount++; this.baseScore += BASE_ENEMY_SCORE * 3; } /** Record a single blade contact; every 5 contacts award a combo bonus (req 12.4). */ public recordBladeContact(): void { this.consecutiveBladeHits++; if (this.consecutiveBladeHits >= COMBO_THRESHOLD) { this.comboCount++; this.comboBonus += COMBO_BONUS; this.consecutiveBladeHits = 0; } } /** Resets the consecutive blade counter if the combo is broken. */ public breakBladeChain(): void { this.consecutiveBladeHits = 0; } /** Player took damage → flawless multiplier lost (req 12.5). */ public markTaken(): void { this.flawless = false; } /** Stage-end timing bonus (req 12.6). */ public setRemainingTimeBonus(remainingSec: number): void { this.timeBonus = Math.max(0, Math.floor(remainingSec)) * TIME_BONUS_PER_SEC; } public snapshot(): IScoreSnapshot { const flawlessMultiplier = this.flawless ? 3 : 1; const finalScore = (this.baseScore + this.comboBonus + this.timeBonus) * flawlessMultiplier; return { baseScore: this.baseScore, comboBonus: this.comboBonus, timeBonus: this.timeBonus, flawlessMultiplier, finalScore, killCount: this.killCount, comboCount: this.comboCount, consecutiveBladeHits: this.consecutiveBladeHits, }; } }