/** * Evolution system test: each mechanic is verified on an isolated engine so * only the tower under test can kill the test enemies. */ import { GameEngine } from '../src/game/engine.ts' import { TOWERS } from '../src/game/config.ts' import type { Tower, TowerKind, Enemy } from '../src/game/types.ts' let failures = 0 const ok = (label: string, cond: boolean): void => { console.log(`${cond ? '✓' : '✗ FEHLER'}: ${label}`) if (!cond) failures++ } interface Rig { eng: GameEngine tower: Tower spawn(x: number, y: number, kind?: 'normal' | 'flyer' | 'boss'): Enemy } /** fresh engine with one max-level tower in the center */ function rig(kind: TowerKind, targetLevel = 9): Rig { const eng = new GameEngine() eng.startGame('easy') eng.money = 1_000_000 eng.startWave(true) eng.placeTower(kind, 8, 5) const tower = eng.towerAt(8, 5)! while (tower.level < targetLevel) eng.upgradeTower(tower) const spawn = (x: number, y: number, k: 'normal' | 'flyer' | 'boss' = 'normal'): Enemy => { ;(eng as unknown as { spawnEnemy: (kk: 'normal' | 'flyer' | 'boss') => void }).spawnEnemy(k) const e = eng.enemies[eng.enemies.length - 1] e.x = x e.y = y e.speedBase = 0 e.wp = 1 return e } return { eng, tower, spawn } } function forceFire(r: Rig, ticks = 12): void { for (let i = 0; i < ticks; i++) { r.tower.cooldown = 0 r.eng.update(1 / 30) } } const CX = 8 * 48 + 24 const CY = 5 * 48 + 24 // ---------- arrow: multishot + poison ---------- { const r = rig('arrow') ok('Bogenturm L9', r.tower.level === 9) const targets = [r.spawn(CX, CY - 30), r.spawn(CX, CY), r.spawn(CX, CY + 30), r.spawn(CX, CY + 60)] let arrows = 0 for (let i = 0; i < 12; i++) { r.tower.cooldown = 0 r.eng.update(1 / 30) arrows = Math.max(arrows, r.eng.projectiles.filter((p) => p.kind === 'arrow').length) } ok(`Multishot: mehrere Pfeile gleichzeitig (${arrows})`, arrows >= 3) ok('Pfeile treffen (Schaden)', targets.every((e) => e.hp < e.maxHp || e.dead)) ok('Gift-DoT wirkt', r.eng.enemies.some((e) => e.dotDps === 28 && e.dotUntil > r.eng.time) || targets.every((e) => e.dead)) } // ---------- frost: freeze + shatter ---------- { const r = rig('frost') ok('Eisturm L9', r.tower.level === 9) const e = r.spawn(CX, CY - 20, 'boss') for (let i = 0; i < 3; i++) { r.tower.cooldown = 0 r.eng.update(1 / 30) } ok('Einfrieren nach 3 Pulsen (Faktor 0)', e.slowFactor === 0 && !e.dead) const hp = e.hp r.eng.damageEnemy(e, 100) ok('Zerbrechlich: +50% Schaden an Eingefrorenen', Math.abs(hp - e.hp - 150) < 1) } // ---------- tesla: stun + storm ---------- { const r = rig('tesla') ok('Tesla L9', r.tower.level === 9) const group = [r.spawn(CX - 24, CY), r.spawn(CX + 24, CY), r.spawn(CX, CY + 24)] r.tower.counter = 3 // nächster Schuss = Gewitter (jeder 4.) r.tower.cooldown = 0 r.eng.update(1 / 30) const stunned = group.filter((e) => e.slowFactor === 0) ok(`Gewitter trifft + betäubt mehrere (${stunned.length}/3)`, stunned.length >= 2) } // ---------- laser: pierce ---------- { const r = rig('laser') ok('Laser L9', r.tower.level === 9) const line = [r.spawn(CX + 40, CY), r.spawn(CX + 75, CY), r.spawn(CX + 110, CY), r.spawn(CX + 145, CY)] forceFire(r) ok('Durchschlag trifft alle in der Linie', line.every((e) => e.hp < e.maxHp || e.dead)) } // ---------- laser: prism (L4-6 only) ---------- { const r = rig('laser', 5) ok('Laser L5 (Prisma-Stufe)', r.tower.level === 5) r.spawn(CX + 40, CY) const b = r.spawn(CX + 60, CY + 10) const c = r.spawn(CX + 80, CY - 10) forceFire(r) ok('Prisma: Nebenziele nehmen Schaden', (b.hp < b.maxHp || b.dead) && (c.hp < c.maxHp || c.dead)) } // ---------- cannon: flak + burn ---------- { const r = rig('cannon') ok('Kanone L9', r.tower.level === 9) ok('Flak-Upgrade aktiv (trifft Flieger)', TOWERS.cannon.flak![8] === true) const flyer = r.spawn(CX + 60, CY, 'flyer') forceFire(r) ok('Flak: Kanone beschädigt Flieger', flyer.hp < flyer.maxHp || flyer.dead) const boss = r.spawn(CX + 60, CY, 'boss') forceFire(r) ok('Brand-DoT wirkt', boss.dotDps > 0 && boss.dotColor === '#ff9c40') } console.log('---') if (failures === 0) console.log('Alle Evolutions-Mechaniken funktionieren.') else { console.log(`${failures} FEHLER!`) process.exit(1) }