import { describe, expect, it } from 'vitest'; import { WAVES } from '../../app/static/js/config.js'; import { createRng } from '../../app/static/js/logic/rng.js'; import { WaveDirector } from '../../app/static/js/logic/waveDirector.js'; /** Minimaler Spielersatz: zählt gespawnte Gegner und lässt sie auf Befehl sterben. */ function harness(cfgOverrides = {}) { const log = { spawned: 0, started: [], cleared: [] }; let alive = 0; const director = new WaveDirector({ rng: createRng(1), cfg: { ...WAVES, ...cfgOverrides }, spawnGroup: (group) => { log.spawned += group.count; alive += group.count; }, aliveCount: () => alive, onWaveStart: (plan) => log.started.push(plan.number), onWaveCleared: (n) => log.cleared.push(n), }); return { director, log, killAll: () => { alive = 0; }, get alive() { return alive; }, }; } /** Lässt die Zeit in 50-ms-Schritten laufen. */ function run(director, from, until, step = 50) { let now = from; while (now < until) { now += step; director.update(now, step); } return now; } describe('WaveDirector', () => { it('startet die erste Welle nach der Startverzögerung', () => { const { director, log } = harness(); director.start(0); expect(director.state).toBe('intermission'); run(director, 0, WAVES.firstWaveDelayMs - 50); expect(log.started).toEqual([]); run(director, WAVES.firstWaveDelayMs - 50, WAVES.firstWaveDelayMs); expect(log.started).toEqual([1]); expect(director.state).toBe('active'); }); it('spawnt alle Gruppen der Welle nach Plan', () => { const { director, log } = harness(); director.start(0); const now = run(director, 0, WAVES.firstWaveDelayMs); const planned = director.plan.totalEnemies; run(director, now, now + 60_000); expect(log.spawned).toBe(planned); expect(director.pendingEnemies).toBe(0); }); it('meldet eine Welle erst als geschafft, wenn alle Gegner besiegt sind', () => { const h = harness(); h.director.start(0); let now = run(h.director, 0, 60_000); expect(h.log.cleared).toEqual([]); h.killAll(); now = run(h.director, now, now + 100); expect(h.log.cleared).toEqual([1]); expect(h.director.state).toBe('intermission'); expect(h.director.intermissionRemaining(now)).toBeGreaterThan(WAVES.intermissionMs - 200); }); it('startet nach der Pause die nächste, größere Welle', () => { const h = harness(); h.director.start(0); let now = run(h.director, 0, 60_000); const firstSize = h.director.plan.totalEnemies; h.killAll(); now = run(h.director, now, now + WAVES.intermissionMs + 200); expect(h.log.started).toEqual([1, 2]); run(h.director, now, now + 60_000); expect(h.director.plan.totalEnemies).toBeGreaterThan(firstSize); }); it('hält den Spawn-Ablauf an, solange zu viele Gegner leben', () => { const h = harness({ maxAliveEnemies: 3 }); h.director.start(0); // Wellenstart plus ein Frame: Die erste Gruppe (≥4 Krabbler) überschreitet das Limit sofort. let now = run(h.director, 0, WAVES.firstWaveDelayMs + 50); const spawnedBefore = h.log.spawned; expect(h.alive).toBeGreaterThanOrEqual(3); now = run(h.director, now, now + 60_000); expect(h.log.spawned).toBe(spawnedBefore); h.killAll(); run(h.director, now, now + 60_000); expect(h.log.spawned).toBeGreaterThan(spawnedBefore); }); it('tut vor dem Start nichts', () => { const { director, log } = harness(); run(director, 0, 10_000); expect(log.started).toEqual([]); expect(director.pendingEnemies).toBe(0); }); });