first commmit
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { EnemyType, ILevelConfig, ILevelObjective } from '../data/Interfaces';
|
||||
|
||||
/**
|
||||
* Level lifecycle manager (task 7.1 + 7.2).
|
||||
*
|
||||
* Given an `ILevelConfig`, this class:
|
||||
* - ticks the time-limit countdown (req 8.1-8.5)
|
||||
* - tracks kill counters by enemy type
|
||||
* - evaluates the level objective each tick
|
||||
* - emits a structured `LevelResult` when the level ends
|
||||
*
|
||||
* Scene + parallax rendering live in the Cocos view layer; this module is
|
||||
* intentionally engine-agnostic so the whole progression logic is Jest-
|
||||
* testable.
|
||||
*/
|
||||
|
||||
export type LevelStatus = 'running' | 'victory' | 'timeout' | 'player_dead';
|
||||
|
||||
export interface ILevelResult {
|
||||
status: LevelStatus;
|
||||
elapsedSec: number;
|
||||
kills: Record<string, number>;
|
||||
remainingSec: number;
|
||||
}
|
||||
|
||||
export class LevelMgr {
|
||||
private elapsedSec = 0;
|
||||
private kills = new Map<EnemyType, number>();
|
||||
private totalKills = 0;
|
||||
private bossKilled = false;
|
||||
private reachedTop = false;
|
||||
private playerDead = false;
|
||||
public readonly level: ILevelConfig;
|
||||
|
||||
constructor(level: ILevelConfig) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public tick(dtSec: number): LevelStatus {
|
||||
if (this.isTerminal()) return this.currentStatus();
|
||||
this.elapsedSec += dtSec;
|
||||
return this.currentStatus();
|
||||
}
|
||||
|
||||
public onEnemyKilled(enemy: EnemyType): void {
|
||||
this.kills.set(enemy, (this.kills.get(enemy) ?? 0) + 1);
|
||||
this.totalKills++;
|
||||
}
|
||||
|
||||
public onBossKilled(): void {
|
||||
this.bossKilled = true;
|
||||
}
|
||||
|
||||
public onReachedTop(): void {
|
||||
this.reachedTop = true;
|
||||
}
|
||||
|
||||
public onPlayerDied(): void {
|
||||
this.playerDead = true;
|
||||
}
|
||||
|
||||
public result(): ILevelResult {
|
||||
return {
|
||||
status: this.currentStatus(),
|
||||
elapsedSec: this.elapsedSec,
|
||||
kills: this.killsAsObject(),
|
||||
remainingSec: Math.max(0, this.level.timeLimitSec - this.elapsedSec),
|
||||
};
|
||||
}
|
||||
|
||||
public get totalKillsCount(): number {
|
||||
return this.totalKills;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
private currentStatus(): LevelStatus {
|
||||
if (this.playerDead) return 'player_dead';
|
||||
if (this.evaluateObjective(this.level.objective)) return 'victory';
|
||||
if (this.elapsedSec >= this.level.timeLimitSec) return 'timeout';
|
||||
return 'running';
|
||||
}
|
||||
|
||||
private isTerminal(): boolean {
|
||||
const s = this.currentStatus();
|
||||
return s !== 'running';
|
||||
}
|
||||
|
||||
private evaluateObjective(o: ILevelObjective): boolean {
|
||||
if (o.kind === 'kill_count' && o.enemy && o.count) {
|
||||
return (this.kills.get(o.enemy) ?? 0) >= o.count;
|
||||
}
|
||||
if (o.kind === 'reach_top') {
|
||||
return this.reachedTop;
|
||||
}
|
||||
if (o.kind === 'defeat_boss') {
|
||||
return this.bossKilled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private killsAsObject(): Record<string, number> {
|
||||
const out: Record<string, number> = {};
|
||||
for (const [k, v] of this.kills.entries()) out[k] = v;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user