/** * 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) }) })