Files
geograsim/App/tests/unit/sim-12-fluss.test.ts
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

217 lines
7.8 KiB
TypeScript

/**
* Tests fuer SIM-12: Flussmanagement
*/
import { describe, it, expect } from 'vitest'
import {
simulateRound, computeScore, computeCost, computeTotalCost,
checkWinLose, checkFinalWin,
LEVELS, CONTROL_META, STATE_META,
type Controls, type State, type Conditions,
} from '../../src/sims/sim-12-fluss/logic'
import { FlussGame } from '../../src/sims/sim-12-fluss/game'
// Hilfsfunktion: Default-Zustand
function defaultState(): State {
return {
floodLocal: 50, floodDownstream: 40, erosion: 30,
soilFertility: 60, biodiversity: 60, groundwater: 55,
usableLand: 50, economy: 50,
}
}
function emptyControls(): Controls {
return {
straightening: 0, levees: 0, dredging: 0,
floodplainRelease: 0, renaturation: 0, irrigation: 0,
}
}
function defaultConditions(): Conditions {
return { rainfall: 60, extremeWeather: 30, slope: 40, populationPressure: 50, budget: 150 }
}
describe('Fluss-Simulation Logik', () => {
describe('simulateRound', () => {
it('gibt einen neuen State zurueck (Immutabilitaet)', () => {
const state = defaultState()
const result = simulateRound(state, emptyControls(), defaultConditions())
expect(result).not.toBe(state) // Neues Objekt
})
it('ohne Massnahmen aendert sich wenig', () => {
const state = defaultState()
const result = simulateRound(state, emptyControls(), defaultConditions())
// Werte sollten nah am Ausgangszustand sein
for (const key of Object.keys(state) as (keyof State)[]) {
expect(Math.abs(result[key] - state[key])).toBeLessThan(10)
}
})
it('Deiche reduzieren lokales Hochwasser', () => {
const state = defaultState()
const controls = { ...emptyControls(), levees: 80 }
const result = simulateRound(state, controls, defaultConditions())
expect(result.floodLocal).toBeLessThan(state.floodLocal)
})
it('Begradigung erhoeht Hochwasser flussabwaerts', () => {
const state = defaultState()
const controls = { ...emptyControls(), straightening: 80 }
const result = simulateRound(state, controls, defaultConditions())
expect(result.floodDownstream).toBeGreaterThan(state.floodDownstream)
})
it('Renaturierung erhoeht Biodiversitaet', () => {
const state = defaultState()
const controls = { ...emptyControls(), renaturation: 70 }
const result = simulateRound(state, controls, defaultConditions())
expect(result.biodiversity).toBeGreaterThan(state.biodiversity)
})
it('Bewaesserung erhoeht Bodenfruchtbarkeit', () => {
const state = defaultState()
const controls = { ...emptyControls(), irrigation: 60 }
const result = simulateRound(state, controls, defaultConditions())
expect(result.soilFertility).toBeGreaterThan(state.soilFertility)
})
it('Auen freigeben reduziert Hochwasser lokal und flussabwaerts', () => {
const state = defaultState()
const controls = { ...emptyControls(), floodplainRelease: 70 }
const result = simulateRound(state, controls, defaultConditions())
expect(result.floodLocal).toBeLessThan(state.floodLocal)
expect(result.floodDownstream).toBeLessThan(state.floodDownstream)
})
it('alle Werte bleiben zwischen 0 und 100', () => {
// Extreme Kontrollen
const controls: Controls = {
straightening: 100, levees: 100, dredging: 100,
floodplainRelease: 100, renaturation: 100, irrigation: 100,
}
const result = simulateRound(defaultState(), controls, defaultConditions())
for (const key of Object.keys(result) as (keyof State)[]) {
expect(result[key]).toBeGreaterThanOrEqual(0)
expect(result[key]).toBeLessThanOrEqual(100)
}
})
it('Hochwasser-Event erhoeht Floodwerte', () => {
const state = defaultState()
const event = { round: 1, type: 'flood_event' as const, intensity: 80 }
const result = simulateRound(state, emptyControls(), defaultConditions(), event)
expect(result.floodLocal).toBeGreaterThan(state.floodLocal)
})
it('Duerre-Event reduziert Grundwasser', () => {
const state = defaultState()
const event = { round: 1, type: 'drought' as const, intensity: 70 }
const result = simulateRound(state, emptyControls(), defaultConditions(), event)
expect(result.groundwater).toBeLessThan(state.groundwater)
})
})
describe('computeScore', () => {
it('berechnet alle Scores zwischen 0 und 100', () => {
const score = computeScore(defaultState(), { safety: 0.25, ecology: 0.25, agriculture: 0.25, economy: 0.25 })
expect(score.safety).toBeGreaterThanOrEqual(0)
expect(score.safety).toBeLessThanOrEqual(100)
expect(score.ecology).toBeGreaterThanOrEqual(0)
expect(score.total).toBeGreaterThanOrEqual(0)
expect(score.total).toBeLessThanOrEqual(100)
})
it('Gewichtung beeinflusst Gesamtscore', () => {
const state: State = { ...defaultState(), economy: 90 }
const scoreEco = computeScore(state, { safety: 0, ecology: 0, agriculture: 0, economy: 1 })
const scoreSafe = computeScore(state, { safety: 1, ecology: 0, agriculture: 0, economy: 0 })
expect(scoreEco.total).not.toEqual(scoreSafe.total)
})
})
describe('computeCost', () => {
it('0 Intensitaet kostet 0', () => {
expect(computeCost('levees', 0)).toBe(0)
})
it('hoehere Intensitaet kostet mehr (ueberproportional)', () => {
const cost50 = computeCost('levees', 50)
const cost100 = computeCost('levees', 100)
expect(cost100).toBeGreaterThan(cost50 * 1.5) // ueberproportional
})
})
describe('Levels', () => {
it('hat 5 Levels', () => {
expect(LEVELS).toHaveLength(5)
})
it('jedes Level hat gueltige Struktur', () => {
for (const l of LEVELS) {
expect(l.id).toBeTruthy()
expect(l.title).toBeTruthy()
expect(l.rounds).toBeGreaterThan(0)
expect(l.allowedControls.length).toBeGreaterThan(0)
expect(l.conditions.budget).toBeGreaterThan(0)
}
})
it('keine dominante Strategie: extreme Massnahme hat Nachteile', () => {
// Teste ob maximale Begradigung nicht alles verbessert
const state = LEVELS[3].initialState // Level 4 (Gleichgewicht)
const conditions = LEVELS[3].conditions
const extremeControls = { ...emptyControls(), straightening: 100 }
const result = simulateRound({ ...state }, extremeControls, conditions)
// Begradigung muss MINDESTENS einen Nachteil haben
const hasDownside =
result.biodiversity < state.biodiversity ||
result.floodDownstream > state.floodDownstream ||
result.groundwater < state.groundwater ||
result.erosion > state.erosion
expect(hasDownside).toBe(true)
})
})
describe('CONTROL_META / STATE_META', () => {
it('hat Metadaten fuer alle 6 Massnahmen', () => {
expect(Object.keys(CONTROL_META)).toHaveLength(6)
})
it('hat Metadaten fuer alle 8 Zustandsparameter', () => {
expect(Object.keys(STATE_META)).toHaveLength(8)
})
})
})
describe('FlussGame', () => {
it('startet mit level-select Phase', () => {
const game = new FlussGame()
expect(game.phase).toBe('level-select')
})
it('selectLevel setzt korrekt auf', () => {
const game = new FlussGame()
game.selectLevel('L1')
expect(game.level.id).toBe('L1')
expect(game.phase).toBe('intro')
expect(game.round).toBe(0)
expect(game.budgetRemaining).toBe(120) // L1 budget
})
it('serialize / deserialize roundtrip', () => {
const game = new FlussGame()
game.selectLevel('L2')
game.startPlaying()
game.setControl('levees', 40)
game.executeRound()
const json = game.serialize()
const game2 = new FlussGame()
expect(game2.deserialize(json)).toBe(true)
expect(game2.level.id).toBe('L2')
expect(game2.round).toBe(1)
})
})