f22c5ebbfe
- 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>
179 lines
5.8 KiB
TypeScript
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)
|
|
})
|
|
}
|
|
})
|