Energiemanager: Blackouts fuer Lehrer sichtbar machen
- game.html: bei Blackout sofort action:'blackout' an /progress feuern (setId/Tag/Block/Frequenz/Ursache, score:0) — Fehlversuch bleibt auch bei Set-Abbruch sichtbar, kein player_progress/Sterne. - progress.php: neuer action:'blackout' -> eigene assessments-Zeile, KEIN XP/plays/best_stars. - live.php: Blackout-Flag (event:'blackout') durch die Tag-Summary reichen (blackout/blackoutCount pro Session). - teacher.html: needsHelp energiemanager (>=2 Blackouts oder Tag <4/8); Blackout-Spalte + '🔌 neu planen'-Status in Aktuell; Tag-Balken als 🔌 Blackout kennzeichnen; Run-Karte: eigenes Blackout-Kaertchen + '🔌 N Blackouts'-Badge im fertigen Set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -211,6 +211,8 @@ if ($method === 'GET') {
|
||||
'score' => null,
|
||||
'durationMs' => 0,
|
||||
'completed' => null,
|
||||
'blackout' => false,
|
||||
'blackoutCount' => 0,
|
||||
'rowCount' => 0,
|
||||
];
|
||||
}
|
||||
@@ -224,6 +226,9 @@ if ($method === 'GET') {
|
||||
if ($dmv > $currentSess['durationMs']) $currentSess['durationMs'] = $dmv;
|
||||
$cp = $extractCompleted($row);
|
||||
if ($cp !== null) $currentSess['completed'] = $cp;
|
||||
// Blackout-Fehlversuch (Energiemanager) markieren.
|
||||
$rEvt = $row['results'] ? json_decode($row['results'], true) : [];
|
||||
if (($rEvt['event'] ?? '') === 'blackout') { $currentSess['blackout'] = true; $currentSess['blackoutCount']++; }
|
||||
$prevTs = $ts;
|
||||
}
|
||||
if ($currentSess !== null) $sessions[] = $currentSess;
|
||||
@@ -276,6 +281,8 @@ if ($method === 'GET') {
|
||||
'stars' => $s['stars'],
|
||||
'score' => $s['score'],
|
||||
'completed' => $s['completed'],
|
||||
'blackout' => $s['blackout'],
|
||||
'blackoutCount' => $s['blackoutCount'],
|
||||
'success' => $success,
|
||||
'rowCount' => $s['rowCount'],
|
||||
];
|
||||
|
||||
@@ -162,6 +162,26 @@ if ($method === 'POST') {
|
||||
}
|
||||
|
||||
// === Pre/Post-Antworten speichern ===
|
||||
// === Blackout (Energiemanager): Fehlversuch sofort protokollieren ===
|
||||
// Ein Blackout beendet das Set NICHT — die Schüler:in plant neu. Damit der
|
||||
// Fehlversuch aber auch bei Abbruch beim Lehrer sichtbar bleibt, schreiben
|
||||
// wir ihn als eigene assessments-Zeile (score:0 → roter Tag-Balken).
|
||||
// KEIN player_progress-Update: ein Blackout gibt weder Sterne noch XP.
|
||||
if ($action === 'blackout') {
|
||||
$sessionId = Session::requireStudent();
|
||||
$simId = $body['simId'] ?? '';
|
||||
if (!$simId) Response::error('simId erforderlich');
|
||||
$session = $db->fetchOne('SELECT class_id FROM student_sessions WHERE id = ?', [$sessionId]);
|
||||
if (!defined('GGS_MAX_SESSION_MS')) define('GGS_MAX_SESSION_MS', 7200000);
|
||||
$durationMs = max(0, min((int)($body['durationMs'] ?? 0), GGS_MAX_SESSION_MS));
|
||||
$data = json_encode($body['data'] ?? []);
|
||||
$db->execute(
|
||||
'INSERT INTO assessments (session_id, sim_id, class_id, results, duration_ms, submitted_at) VALUES (?, ?, ?, ?, ?, NOW())',
|
||||
[$sessionId, $simId, $session['class_id'] ?? null, $data, $durationMs]
|
||||
);
|
||||
Response::ok(['recorded' => 'blackout']);
|
||||
}
|
||||
|
||||
if ($action === 'answer') {
|
||||
$sessionId = Session::requireStudent();
|
||||
$simId = $body['simId'] ?? '';
|
||||
|
||||
@@ -1817,6 +1817,9 @@
|
||||
setRun.blackouts = (setRun.blackouts || 0) + 1;
|
||||
|
||||
var tooLow = effFreq < FREQ_LOW;
|
||||
// Blackout sofort ans Lehrer-Backend melden — auch wenn das Set danach
|
||||
// abgebrochen wird, bleibt der Fehlversuch sichtbar (kein Set-Abschluss nötig).
|
||||
submitBlackoutToBackend(blockIdx, effFreq, tooLow);
|
||||
var freqTxt = effFreq.toFixed(2).replace('.', ',');
|
||||
var cause = tooLow
|
||||
? ('Die Netzfrequenz ist auf <b>' + freqTxt + ' Hz</b> gefallen — es wurde <b>zu wenig</b> Strom erzeugt. '
|
||||
@@ -2034,6 +2037,42 @@
|
||||
renderSetSummaryUI(summary);
|
||||
}
|
||||
|
||||
// Ein Blackout ist der echte Verlierzustand → sofort als eigenes Fehl-Event
|
||||
// melden. score:0 färbt den Tag-Balken rot; kein player_progress-Update
|
||||
// (Blackout gibt keine Sterne/XP). Ursache/Frequenz/Block für die Lehrer-Sicht.
|
||||
function submitBlackoutToBackend(blockIdx, effFreq, tooLow) {
|
||||
if (!window.EM_SESSION_ID) return; // Free-Mode: kein Submit
|
||||
var apiBase = window.EM_API_BASE || '/geograsim/App/api';
|
||||
var setMeta = SETS[currentSetIdx];
|
||||
try {
|
||||
fetch(apiBase + '/progress', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
action: 'blackout',
|
||||
simId: 'energiemanager',
|
||||
durationMs: Math.max(0, Date.now() - setRun.startedAt),
|
||||
data: {
|
||||
event: 'blackout',
|
||||
score: 0,
|
||||
setId: setMeta.id,
|
||||
setTitle: setMeta.title,
|
||||
setIcon: setMeta.icon,
|
||||
day: currentDayIdx + 1,
|
||||
dayTotal: setMeta.days.length,
|
||||
dayTitle: DAY.title,
|
||||
block: blockIdx + 1,
|
||||
blockTotal: DAY.blocks.length,
|
||||
freq: Math.round(effFreq * 100) / 100,
|
||||
cause: tooLow ? 'unterfrequenz' : 'ueberfrequenz',
|
||||
blackoutSeq: setRun.blackouts || 0
|
||||
}
|
||||
})
|
||||
}).catch(function(e){ console.log('[energiemanager] blackout-submit fehlgeschlagen', e && e.message); });
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function submitSetToBackend(summary) {
|
||||
if (!window.EM_SESSION_ID) return; // Free-Mode: kein Submit
|
||||
var apiBase = window.EM_API_BASE || '/geograsim/App/api';
|
||||
|
||||
+31
-3
@@ -784,7 +784,8 @@ var LIVE_PRIMARY_FIELDS = {
|
||||
return v ? icon + v + idxBadge : '—';
|
||||
}},
|
||||
{k:'day', label:'Tag', fmt:function(v, row){ var s = row.state||{}; return s.dayTotal ? v+' <span style="color:#8a8a8a">/ '+s.dayTotal+'</span>' : '—'; }},
|
||||
{k:'block', label:'Block', fmt:function(v, row){ var s = row.state||{}; if (s.phase === 'plan') return '<span style="color:#8a8a8a">plant</span>'; return s.blockTotal ? (v+1)+' <span style="color:#8a8a8a">/ '+s.blockTotal+'</span>' : '—'; }},
|
||||
{k:'block', label:'Block', fmt:function(v, row){ var s = row.state||{}; if (s.phase === 'blackout') return '<span style="color:#d8453a;font-weight:700">🔌 neu planen</span>'; if (s.phase === 'plan') return '<span style="color:#8a8a8a">plant</span>'; return s.blockTotal ? (v+1)+' <span style="color:#8a8a8a">/ '+s.blockTotal+'</span>' : '—'; }},
|
||||
{k:'blackouts', label:'🔌', align:'center', fmt:function(v){ if (!v) return '<span style="color:#8a8a8a">–</span>'; var c = v >= 2 ? '#d8453a' : '#c9913a'; return '<span style="color:'+c+';font-weight:700">'+v+'</span>'; }},
|
||||
{k:'balanced', label:'Tag ausg.', fmt:function(v, row){ var s = row.state||{}; if (s.phase !== 'done') return '<span style="color:#8a8a8a">—</span>'; var color = v === s.blockTotal ? '#5a8a5e' : v >= 4 ? '#c9913a' : '#c07a6b'; return '<span style="color:'+color+';font-weight:700">'+v+'/'+s.blockTotal+'</span>'; }},
|
||||
{k:'setBlocksBalanced', label:'Set ausg.', fmt:function(v, row){ var s = row.state||{}; var max = (s.setTotal && s.dayTotal && s.blockTotal) ? s.dayTotal * s.blockTotal : 0; if (!max) return '—'; var color = v >= max*0.87 ? '#5a8a5e' : v >= max*0.5 ? '#c9913a' : '#c07a6b'; return '<span style="color:'+color+';font-weight:700">'+v+'/'+max+'</span>'; }},
|
||||
{k:'upperLake', label:'⬆ See', unit:'GWh'},
|
||||
@@ -1281,6 +1282,7 @@ function _liveNeedsHelp(simId, s) {
|
||||
case 'farmer': return (s.cash==null?1000:s.cash) < 0 || ((s.attempts||0) >= 6 && s.hit_rate != null && s.hit_rate < 30);
|
||||
case 'logistik': return (s.balance==null?1000:s.balance) < 0;
|
||||
case 'eu-werkstatt': return (s.attemptsThisStep||0) >= 6;
|
||||
case 'energiemanager': return (s.blackouts||0) >= 2 || (s.phase === 'done' && (s.balanced||0) < 4);
|
||||
case 'tourismustal': return (s.money==null?1:s.money) < 0 || (s.lossStreak||0) >= 8;
|
||||
case 'tourismusregion': return (s.budget==null?1:s.budget) < -400 || (s.env==null?100:s.env) < 30;
|
||||
}
|
||||
@@ -2379,6 +2381,9 @@ async function renderLiveSummary() {
|
||||
var inProgress = !aborted && (sess.isLive || sess.success === 'running');
|
||||
// Farbe = Erfolg (Ampel). Kein Wert → neutral grau. Abbruch → rot-gestrichelt.
|
||||
var col = aborted ? '#c0392b' : (score != null ? _tagTraffic(score) : '#aab2b8');
|
||||
// Energiemanager-Blackout: eigener Fehlversuch (score 0). Als 🔌 kennzeichnen,
|
||||
// solange die Session nicht doch noch gut abgeschlossen wurde.
|
||||
var isBlackout = !!sess.blackout && sess.success !== 'good' && (score == null || score < 50);
|
||||
var barH = 18; // EINHEITLICHE Höhe — der Erfolg steckt in der Farbe, nicht in der Höhe
|
||||
// Rahmen kodiert den Zustand: fertig = geschlossen · läuft = rechts gepunktet · Abbruch = gestrichelt
|
||||
var border = aborted ? 'border:1.5px dashed rgba(255,255,255,.7);'
|
||||
@@ -2387,7 +2392,8 @@ async function renderLiveSummary() {
|
||||
// Laufende Spiele: langsam blinkender Block. Pausiert → Hauptbalken ruhig
|
||||
// (der halbhohe Schwanz blinkt stattdessen).
|
||||
var anim = (inProgress && !sess.paused) ? 'animation:_tagblink 1.8s ease-in-out infinite;' : '';
|
||||
var stateWord = !inProgress ? (aborted ? 'abgebrochen' : 'fertig') : (sess.paused ? 'pausiert ⏸' : 'läuft noch');
|
||||
var stateWord = isBlackout ? 'Blackout 🔌' : (!inProgress ? (aborted ? 'abgebrochen' : 'fertig') : (sess.paused ? 'pausiert ⏸' : 'läuft noch'));
|
||||
if (isBlackout) icon = '🔌';
|
||||
var tip = (sess.simTitle||sess.simId) + (sess.lessonLabel?' · '+sess.lessonLabel:'')
|
||||
+ ' · ' + _fmtHM(sess.startMs) + '–' + _fmtHM(endMs)
|
||||
+ ' · ' + _liveFmtMin(realDurSec) + ' · ' + stateWord
|
||||
@@ -5114,6 +5120,28 @@ function renderSonnensystemRun(dt, dur, r, d) {
|
||||
}
|
||||
|
||||
function renderEnergiemanagerRun(dt, dur, r, d) {
|
||||
// Eigener Fehlversuch: Blackout (kein Set-Abschluss).
|
||||
if (d.event === 'blackout') {
|
||||
var causeTxt = d.cause === 'ueberfrequenz'
|
||||
? 'Überfrequenz — es wurde zu viel Strom erzeugt (pumpen fehlte).'
|
||||
: 'Unterfrequenz — es wurde zu wenig Strom erzeugt (turbinieren fehlte).';
|
||||
var freqTxt = (d.freq != null) ? String(d.freq).replace('.', ',') + ' Hz' : '—';
|
||||
return ''
|
||||
+ '<div class="run-card" style="border-left:4px solid #d8453a">'
|
||||
+ '<div class="run-head">'
|
||||
+ '<div><strong>🔌 Blackout · '+(d.setIcon||'⚡')+' '+esc(d.setTitle||d.setId||'?')+'</strong>'
|
||||
+ '<div class="run-meta">'+dt+' · '+dur+'</div>'
|
||||
+ '</div>'
|
||||
+ '<div style="text-align:right;color:#d8453a;font-weight:700;font-size:.78rem">Netz<br>zusammengebrochen</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="run-stats">'
|
||||
+ '<span>Tag <b>'+(d.day||'?')+'</b>/'+(d.dayTotal||'?')+(d.dayTitle?' · '+esc(d.dayTitle):'')+'</span>'
|
||||
+ '<span>Block <b>'+(d.block||'?')+'</b>/'+(d.blockTotal||'?')+'</span>'
|
||||
+ '<span>Frequenz: <b>'+freqTxt+'</b></span>'
|
||||
+ '</div>'
|
||||
+ '<div style="margin-top:.4rem;font-size:.72rem;color:#c07a6b">'+causeTxt+'</div>'
|
||||
+ '</div>';
|
||||
}
|
||||
var stars = Math.max(0, Math.min(5, r.stars || 0));
|
||||
var starsHtml = '<span class="run-stars">'+'★'.repeat(stars)+'<span class="run-stars-empty">'+'★'.repeat(5-stars)+'</span></span>';
|
||||
var setIcon = d.setIcon || '⚡';
|
||||
@@ -5140,7 +5168,7 @@ function renderEnergiemanagerRun(dt, dur, r, d) {
|
||||
+ '<div class="run-card">'
|
||||
+ '<div class="run-head">'
|
||||
+ '<div><strong>'+setIcon+' Set '+esc(setTitle)+'</strong>'
|
||||
+ '<div class="run-meta">'+dt+' · '+dur+' · '+(d.daysPlayed||0)+' Tage</div>'
|
||||
+ '<div class="run-meta">'+dt+' · '+dur+' · '+(d.daysPlayed||0)+' Tage'+((d.blackouts||0)>0 ? ' · <span style="color:#d8453a;font-weight:700">🔌 '+d.blackouts+' Blackout'+(d.blackouts===1?'':'s')+'</span>' : '')+'</div>'
|
||||
+ '</div>'
|
||||
+ '<div style="text-align:right">'+starsHtml+'<div style="font-size:.7rem;color:'+quoteColor+';font-weight:700;margin-top:.2rem">'+quote+'% ausgegl.</div></div>'
|
||||
+ '</div>'
|
||||
|
||||
Reference in New Issue
Block a user