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>
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* SIM-09: Energiemix-Simulator — LOGIK
|
||||
*
|
||||
* Modell:
|
||||
* - Schüler*innen stellen einen Energiemix zusammen
|
||||
* - Drei Zielgrößen: CO₂-Ausstoß, Kosten, Versorgungssicherheit
|
||||
* - Jede Energiequelle hat spezifische Werte für alle drei Dimensionen
|
||||
* - Zielkonflikt sichtbar machen: billiger = dreckiger, sauber = teurer/unsicherer
|
||||
*
|
||||
* Didaktik:
|
||||
* - Keine "richtige" Antwort — es geht um das Abwägen
|
||||
* - Urteilskompetenz: verschiedene Perspektiven einnehmen
|
||||
* - Gamification: Highscore für besten Kompromiss
|
||||
*/
|
||||
|
||||
import { Simulation, SimulationMeta } from '@core/simulation'
|
||||
|
||||
const META: SimulationMeta = {
|
||||
id: 'sim-09',
|
||||
name: 'Energiemix-Simulator',
|
||||
educationLevels: [5, 6, 7, 8, 9, 10],
|
||||
primaryLevel: 6,
|
||||
kompetenzbereich: 'Nachhaltiger Umgang mit Energie und Ressourcen',
|
||||
lernziele: [
|
||||
'Erneuerbare und nicht erneuerbare Energieträger vergleichen',
|
||||
'Zielkonflikte zwischen Kosten, Umwelt und Versorgungssicherheit erkennen',
|
||||
'Eigene Position zu Energiepolitik bilden und begründen',
|
||||
],
|
||||
basiskonzepte: ['Leistungserstellung und Nachhaltigkeit', 'Ökonomische Prinzipien und Entscheidungsfindung'],
|
||||
dpiMinuten: 25,
|
||||
typ: 'sachsimulation',
|
||||
tier: 1,
|
||||
requiresReading: true,
|
||||
}
|
||||
|
||||
export interface EnergySource {
|
||||
id: string
|
||||
name: string
|
||||
emoji: string
|
||||
color: string
|
||||
co2PerGWh: number // Tonnen CO₂ pro GWh (Lifecycle)
|
||||
costPerMWh: number // EUR pro MWh
|
||||
reliability: number // 0-1 (1 = immer verfügbar)
|
||||
maxShare: number // maximaler realistischer Anteil (0-1)
|
||||
renewable: boolean
|
||||
description: string
|
||||
}
|
||||
|
||||
export const ENERGY_SOURCES: EnergySource[] = [
|
||||
{
|
||||
id: 'coal', name: 'Kohle', emoji: '🪨', color: '#4a4a4a',
|
||||
co2PerGWh: 820, costPerMWh: 65, reliability: 0.85, maxShare: 1,
|
||||
renewable: false, description: 'Billig aber sehr CO₂-intensiv'
|
||||
},
|
||||
{
|
||||
id: 'gas', name: 'Erdgas', emoji: '🔥', color: '#c4a35a',
|
||||
co2PerGWh: 490, costPerMWh: 55, reliability: 0.87, maxShare: 0.8,
|
||||
renewable: false, description: 'Hälfte des CO₂ von Kohle, flexibel'
|
||||
},
|
||||
{
|
||||
id: 'nuclear', name: 'Atomkraft', emoji: '⚛️', color: '#8a5caa',
|
||||
co2PerGWh: 12, costPerMWh: 90, reliability: 0.92, maxShare: 0.6,
|
||||
renewable: false, description: 'Kaum CO₂, aber teuer und Atommüll'
|
||||
},
|
||||
{
|
||||
id: 'wind', name: 'Windkraft', emoji: '🌬️', color: '#5a9aaa',
|
||||
co2PerGWh: 11, costPerMWh: 45, reliability: 0.35, maxShare: 0.5,
|
||||
renewable: true, description: 'Günstig und sauber, aber wetterabhängig'
|
||||
},
|
||||
{
|
||||
id: 'solar', name: 'Solarenergie', emoji: '☀️', color: '#e8c84a',
|
||||
co2PerGWh: 45, costPerMWh: 40, reliability: 0.25, maxShare: 0.4,
|
||||
renewable: true, description: 'Billigste Quelle, aber nur tagsüber'
|
||||
},
|
||||
{
|
||||
id: 'hydro', name: 'Wasserkraft', emoji: '💧', color: '#4a7c8a',
|
||||
co2PerGWh: 24, costPerMWh: 50, reliability: 0.55, maxShare: 0.3,
|
||||
renewable: true, description: 'Zuverlässig, aber Standort-begrenzt'
|
||||
},
|
||||
]
|
||||
|
||||
export interface MixResult {
|
||||
totalCO2: number // Tonnen CO₂ pro GWh (gewichteter Durchschnitt)
|
||||
totalCost: number // EUR pro MWh
|
||||
totalReliability: number // 0-1
|
||||
renewableShare: number // 0-100%
|
||||
score: number // Gesamtbewertung 0-100
|
||||
rating: string // z.B. "Gut balanciert"
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Ergebnisse eines Energiemixes
|
||||
* @param mix Map von SourceID → Anteil (0-100, Summe sollte 100 sein)
|
||||
*/
|
||||
export function computeMixResult(mix: Record<string, number>): MixResult {
|
||||
let totalCO2 = 0
|
||||
let totalCost = 0
|
||||
let totalReliability = 0
|
||||
let renewableShare = 0
|
||||
let totalShare = 0
|
||||
|
||||
for (const source of ENERGY_SOURCES) {
|
||||
const share = (mix[source.id] || 0) / 100
|
||||
totalShare += share
|
||||
totalCO2 += source.co2PerGWh * share
|
||||
totalCost += source.costPerMWh * share
|
||||
totalReliability += source.reliability * share
|
||||
if (source.renewable) renewableShare += share * 100
|
||||
}
|
||||
|
||||
// Normalisieren falls Summe ≠ 100
|
||||
if (totalShare > 0 && Math.abs(totalShare - 1) > 0.01) {
|
||||
totalCO2 /= totalShare
|
||||
totalCost /= totalShare
|
||||
totalReliability /= totalShare
|
||||
renewableShare /= totalShare
|
||||
}
|
||||
|
||||
// Score: Multi-Kriterien-Bewertung
|
||||
const co2Score = Math.max(0, 100 - totalCO2 / 8) // 0 CO₂ = 100, 800 = 0
|
||||
const costScore = Math.max(0, 100 - (totalCost - 30) / 0.7) // 30€ = 100, 100€ = 0
|
||||
const reliScore = totalReliability * 100
|
||||
const score = Math.round((co2Score * 0.4 + costScore * 0.3 + reliScore * 0.3))
|
||||
|
||||
let rating = 'Experimentell'
|
||||
if (score >= 80) rating = 'Exzellent! 🌟'
|
||||
else if (score >= 65) rating = 'Gut balanciert 👍'
|
||||
else if (score >= 50) rating = 'Solide Basis'
|
||||
else if (score >= 35) rating = 'Verbesserungswürdig'
|
||||
|
||||
return {
|
||||
totalCO2: Math.round(totalCO2),
|
||||
totalCost: Math.round(totalCost),
|
||||
totalReliability: Math.round(totalReliability * 100) / 100,
|
||||
renewableShare: Math.round(renewableShare),
|
||||
score,
|
||||
rating,
|
||||
}
|
||||
}
|
||||
|
||||
export class EnergiemixSimulation extends Simulation {
|
||||
constructor() {
|
||||
super(META)
|
||||
// Startwerte: aktueller europäischer Mix (circa)
|
||||
this.state.variables = {
|
||||
coal: 15, gas: 20, nuclear: 20,
|
||||
wind: 18, solar: 12, hydro: 15,
|
||||
}
|
||||
}
|
||||
|
||||
getVariableRanges() {
|
||||
const ranges: Record<string, { min: number; max: number; default: number; unit: string; label: string }> = {}
|
||||
for (const source of ENERGY_SOURCES) {
|
||||
ranges[source.id] = {
|
||||
min: 0,
|
||||
max: Math.round(source.maxShare * 100),
|
||||
default: this.state.variables[source.id] || 0,
|
||||
unit: '%',
|
||||
label: `${source.emoji} ${source.name}`,
|
||||
}
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
/** Normalisiert den Mix auf 100% */
|
||||
normalizeMix(): void {
|
||||
const total = ENERGY_SOURCES.reduce((s, src) => s + this.getVariable(src.id), 0)
|
||||
if (total > 0) {
|
||||
for (const src of ENERGY_SOURCES) {
|
||||
this.state.variables[src.id] = Math.round(this.getVariable(src.id) / total * 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compute() {
|
||||
const mix: Record<string, number> = {}
|
||||
for (const src of ENERGY_SOURCES) {
|
||||
mix[src.id] = this.getVariable(src.id)
|
||||
}
|
||||
const result = computeMixResult(mix)
|
||||
this.state.results = result
|
||||
return result as unknown as Record<string, number>
|
||||
}
|
||||
|
||||
protected onVariableChange(): void {
|
||||
this.compute()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user