/** * Klimawächter — Mechanik-Trace * * Spielt das Klimawächter-Spiel rein rechnerisch über alle 75 Jahre für * verschiedene Strategien durch und gibt die Schlüssel-Variablen als * ASCII-Tabellen aus. Dient zur Analyse der Spielmechanik: * - Steigt CO₂ ohne Maßnahmen? * - Wie reagiert die Temperatur auf verschiedene Strategien? * - Wann wird die Stadt überflutet? * - Sind die Maßnahmen sinnvoll dimensioniert? * * Wird mit `npx vitest run sim-05-trace` aufgerufen. * Test schlägt nie fehl — er druckt nur. */ import { describe, it } from 'vitest' import { KlimawaechterGame, MEASURES } from '../../src/sims/sim-05-treibhaus/game' interface TraceRow { tick: number co2: number temp: number sea: number flooded: number budget: number pop: number measures: Record } interface Strategy { name: string buy: (g: KlimawaechterGame, tick: number) => void } function runStrategy(strat: Strategy): TraceRow[] { const game = new KlimawaechterGame() // Tutorial überspringen, in 'playing' wechseln ;(game as any).state = 'playing' ;(game as any).tutorialStep = (game as any).tutorialSteps.length const rows: TraceRow[] = [] // Startzustand auch erfassen const snapshot0 = (): TraceRow => ({ tick: 0, co2: game.getResource('co2'), temp: game.getResource('temperature'), sea: game.getResource('sealevel'), flooded: game.getResource('flooded'), budget: game.getResource('budget'), pop: game.getResource('population'), measures: countMeasures(game), }) rows.push(snapshot0()) // 75 Ticks (= 75 Jahre) for (let i = 1; i <= 75; i++) { strat.buy(game, i) // simulateTick ist protected → cast ;(game as any).simulateTick() ;(game as any).tick = i rows.push({ tick: i, co2: game.getResource('co2'), temp: game.getResource('temperature'), sea: game.getResource('sealevel'), flooded: game.getResource('flooded'), budget: game.getResource('budget'), pop: game.getResource('population'), measures: countMeasures(game), }) } return rows } function countMeasures(game: KlimawaechterGame): Record { const result: Record = {} for (const m of MEASURES) { result[m.id] = game.getMeasureCount(m.id) } return result } function fmtRow(r: TraceRow): string { const m = Object.entries(r.measures).filter(([_, c]) => c > 0).map(([k, c]) => `${k}:${c}`).join(',') || '-' return [ String(2025 + r.tick).padStart(4), r.co2.toFixed(0).padStart(5), r.temp.toFixed(2).padStart(6), r.sea.toFixed(1).padStart(6), r.flooded.toFixed(0).padStart(4), r.budget.toFixed(0).padStart(6), String(r.pop).padStart(6), m.padStart(20), ].join(' │ ') } function printTrace(name: string, rows: TraceRow[]) { console.log('\n═══════════════════════════════════════════════════════════════════════════════════') console.log(`STRATEGIE: ${name}`) console.log('═══════════════════════════════════════════════════════════════════════════════════') console.log(' Jahr │ CO₂ │ Temp │ Meer │ Flut │ Budget │ Bev │ Maßnahmen') console.log('──────┼───────┼────────┼───────┼──────┼────────┼────────┼──────────────────────') // Alle 5 Jahre + erstes + letztes for (const r of rows) { if (r.tick === 0 || r.tick === 75 || r.tick % 5 === 0) { console.log(fmtRow(r)) } } // Endbewertung const last = rows[rows.length - 1] const won = last.tick >= 75 && last.temp < 17 && last.budget > 0 && last.flooded < 30 console.log('──────┴───────┴────────┴───────┴──────┴────────┴────────┴──────────────────────') console.log(` Status: ${won ? '✅ GEWONNEN' : '❌ VERLOREN'}`) } // === Strategien === const strategies: Strategy[] = [ { name: 'Nichts tun (Baseline)', buy: () => {}, }, { name: 'Nur Wälder (alle 3 Jahre einer)', buy: (g, t) => { if (t % 3 === 0) g.buyMeasure('forest') }, }, { name: 'Nur Solar (sobald leistbar)', buy: (g) => { while (g.getResource('budget') >= 200) { if (!g.buyMeasure('solar')) break } }, }, { name: 'Nur Wind (sobald leistbar)', buy: (g) => { while (g.getResource('budget') >= 400) { if (!g.buyMeasure('wind')) break } }, }, { name: 'Nur Deiche (gegen Überflutung)', buy: (g) => { while (g.getResource('budget') >= 300) { if (!g.buyMeasure('dike')) break } }, }, { name: 'Wälder + Solar gemischt', buy: (g, t) => { if (t % 4 === 0 && g.getResource('budget') >= 200) g.buyMeasure('solar') else if (g.getResource('budget') >= 50) g.buyMeasure('forest') }, }, { name: 'Optimal: Solar + Wind + 1 Deich', buy: (g, t) => { if (t === 5 && g.getResource('budget') >= 300) g.buyMeasure('dike') if (g.getResource('budget') >= 400) g.buyMeasure('wind') else if (g.getResource('budget') >= 200) g.buyMeasure('solar') }, }, ] describe('Klimawächter — Mechanik-Trace über 75 Jahre', () => { for (const strat of strategies) { it(`Trace: ${strat.name}`, () => { const rows = runStrategy(strat) printTrace(strat.name, rows) }) } })