Files
geograsim/App/tests/unit/sim-05-trace.test.ts
T
Adminator f22c5ebbfe Stand 2026-04-13: PHP/MySQL Infrastruktur, Flussmanagement, Stadt-Prototyp
- PHP/MySQL Backend (XAMPP + Produktionsserver)
- Front-Controller, API-Endpunkte, Session-Management
- Flussmanagement-Simulation (Echtzeit, Punkt-basierter Fluss)
- Stadt & Raumplanung (Prototyp, Top-Down Kachelsystem)
- Klimawaechter 3D: Deiche kleiner, Baeume kippen, Budget angepasst
- persistence.ts: Dualer Speicher (localStorage + Server-API)
- 6 Unit-Test-Dateien fuer bestehende Simulationen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:43:42 +02:00

179 lines
5.8 KiB
TypeScript

/**
* 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<string, number>
}
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<string, number> {
const result: Record<string, number> = {}
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)
})
}
})