Lehrer-Live: laufende Klima-Balken farbig (Ampel) + "Heute bereits erledigt" zeigt alle heutigen Durchgaenge

- klima game-2d/3d: GGS_LIVE_STATE liefert laufenden Punktestand (score, gleiche Formel wie Endbewertung)
- live.php summary: laufende Sessions jeder Sim bekommen Ampel-Score (nicht mehr nur Farmer) -> Tag-Ansicht faerbt laufende Balken statt grau
- teacher.html _renderInactiveToday: zeigt jetzt ALLE heute abgeschlossenen Laeufe (auch von gerade aktiven Schueler:innen); nur das laufende Spiel bleibt oben. Titel -> "Heute bereits erledigt"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 23:19:23 +02:00
parent 8cc48b1bc6
commit 2160a99896
4 changed files with 44 additions and 8 deletions
+8 -2
View File
@@ -339,8 +339,14 @@ if ($method === 'GET') {
$startMs = ((int)$lr['started_ts']) * 1000;
$endMs = ((int)$lr['last_seen_ts']) * 1000;
$durSec = max(0, intval(($endMs - $startMs) / 1000));
// Live-Erfolgs-Score für die Ampelfarbe: Farmer = aktuelle Trefferquote.
$liveScore = ($simId === 'farmer' && isset($state['hit_rate'])) ? max(0, min(100, (int)$state['hit_rate'])) : null;
// Live-Erfolgs-Score für die Ampelfarbe: Farmer = aktuelle Trefferquote,
// sonst der vom Sim gelieferte laufende Punktestand (0100, z. B. Klima).
$liveScore = null;
if ($simId === 'farmer' && isset($state['hit_rate'])) {
$liveScore = max(0, min(100, (int)$state['hit_rate']));
} elseif (isset($state['score']) && is_numeric($state['score'])) {
$liveScore = max(0, min(100, (int)round($state['score'])));
}
$byStudent[$sid]['sessions'][] = [
'simId' => $simId,
'simTitle' => $mi['title'] ?? $simId,
+14
View File
@@ -1330,8 +1330,22 @@ window.GGS_LIVE_STATE = function () {
speed: state.speed || 0,
measures: Array.isArray(state.actionLog) ? state.actionLog.filter(a=>a.action==='buy').length : 0,
levelWon: !!state.levelWon,
// Laufender Punktestand (0100, gleiche Formel wie am Ende) → Ampelfarbe im
// Lehrer-Live (Tag-Ansicht färbt laufende Balken danach ein).
score: _liveKlimaScore(),
};
};
// Live-Score aus dem aktuellen Stand — nur während des Spiels sinnvoll.
function _liveKlimaScore() {
if (!state || !state.diff) return null;
if (state.phase !== 'playing' && state.phase !== 'citizen' && state.phase !== 'end') return null;
var sp = state.diff.startPopulation || 6000;
var tempScore = Math.max(0, Math.min(35, 35 - ((state.currentTemp||0) - 15.5) * 20));
var floodScore = Math.max(0, 25 - (state.floodedPct||0) * 0.8);
var budgetScore = Math.max(0, Math.min(20, (state.budget||0) / 10));
var popScore = Math.max(0, Math.min(20, ((state.population||0) / sp) * 20));
return Math.round(tempScore + floodScore + budgetScore + popScore);
}
Object.assign(state, {
phase: 'level-select',
speed: 1, lastTickMs: 0,
+14
View File
@@ -1570,8 +1570,22 @@ window.GGS_LIVE_STATE = function () {
hasShield: !!state.hasMountainShield,
hasBeach: !!state.hasSandBeach,
placing: typeof placementId !== 'undefined' && placementId !== null ? placementId : null,
// Laufender Punktestand (0100, gleiche Formel wie am Ende) → Ampelfarbe im
// Lehrer-Live (Tag-Ansicht färbt laufende Balken danach ein).
score: _liveKlimaScore(),
};
};
// Live-Score aus dem aktuellen Stand — nur während des Spiels sinnvoll.
function _liveKlimaScore() {
if (!state || !state.diff) return null;
if (state.phase !== 'playing' && state.phase !== 'citizen' && state.phase !== 'end') return null;
var sp = state.diff.startPopulation || 6000;
var tempScore = Math.max(0, Math.min(35, 35 - ((state.currentTemp||0) - 15.5) * 20));
var floodScore = Math.max(0, 25 - (state.floodedPct||0) * 0.8);
var budgetScore = Math.max(0, Math.min(20, (state.budget||0) / 10));
var popScore = Math.max(0, Math.min(20, ((state.population||0) / sp) * 20));
return Math.round(tempScore + floodScore + budgetScore + popScore);
}
/* ============================================================
UI HELPERS
+8 -6
View File
@@ -1378,18 +1378,20 @@ async function _renderInactiveToday(activeSet) {
if (!data || data.error || !Array.isArray(data.students)) return '';
var groups = [];
data.students.forEach(function(s){
if (activeSet && activeSet[s.studentId]) return; // gerade online → oben
var sess = (s.sessions||[]).filter(function(x){ return x.startMs; });
if (!sess.length) return; // heute nichts gespielt
if (sess.some(function(x){ return x.isLive || x.success==='running'; })) return; // läuft noch → oben
// Alle HEUTE abgeschlossenen Durchgänge (das gerade laufende Spiel steht
// oben in der Live-Liste und wird hier ausgelassen). Auch für Schüler:innen,
// die gerade online sind — ihre schon fertigen Läufe sollen sichtbar bleiben.
var sess = (s.sessions||[]).filter(function(x){ return x.startMs && !x.isLive && x.success!=='running'; });
if (!sess.length) return; // heute nichts abgeschlossen
sess.sort(function(a,b){ return (a.startMs||0)-(b.startMs||0); }); // chronologisch
var lastEnd = 0; sess.forEach(function(x){ if((x.endMs||0)>lastEnd) lastEnd = x.endMs||0; });
groups.push({ s:s, sess:sess, lastEnd:lastEnd });
});
if (!groups.length) return '';
groups.sort(function(a,b){ return b.lastEnd - a.lastEnd; }); // zuletzt aktive Person oben
var html = '<div class="card" style="margin-top:.8rem"><div class="card-h">Heute bereits aktiv · '+groups.length+'</div>';
html += '<p style="font-size:.7rem;color:#8a8a8a;margin-bottom:.5rem">Heute schon gearbeitet, gerade aber offline — alle Aktivitäten mit Uhrzeit und Leistung.</p>';
var totalRuns = groups.reduce(function(n,g){ return n + g.sess.length; }, 0);
var html = '<div class="card" style="margin-top:.8rem"><div class="card-h">Heute bereits erledigt · '+groups.length+' Schüler*innen · '+totalRuns+' Durchgänge</div>';
html += '<p style="font-size:.7rem;color:#8a8a8a;margin-bottom:.5rem">Alle heute abgeschlossenen Durchgänge (Weltküche, Klima, Farmer …) mit Uhrzeit und Leistung. Gerade laufende Spiele stehen oben.</p>';
html += '<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:.74rem">';
html += '<thead><tr style="background:#f7f6f3;border-bottom:2px solid rgba(0,0,0,.08)">'
+ '<th style="text-align:left;padding:.4rem .5rem;white-space:nowrap">Schüler*in</th>'