Weiter am Taskgame gebastelt
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled

This commit is contained in:
2026-05-02 23:10:41 +02:00
parent c472093f62
commit ca0e933d95
76 changed files with 987 additions and 288 deletions

View File

@@ -52,6 +52,9 @@
margin-top: 1rem;
height: 2.75rem;
}
#confirmModal { display:none; }
#confirmModal.open { display:flex; }
.level-display {
display: flex;
@@ -190,7 +193,7 @@
<div id="gameCard" class="game-card" style="display:none;">
<div class="game-label" id="gameLabel"></div>
<div class="game-text" id="gameText"></div>
<div class="game-timer" id="gameTimer"></div>
<div class="game-timer" id="gameTimer"></div>
<div class="game-btn-row">
<button class="btn-primary" id="gameBtn" onclick="handleGameBtn()" style="width:100%;height:100%;"></button>
</div>
@@ -205,13 +208,32 @@
</div>
</div>
<!-- Temporäre Öffnung -->
<div id="tempOpeningBox" class="game-card" style="display:none;">
<div class="game-label">🔓 Temporäre Öffnung erforderlich</div>
<div class="game-text" id="tempOpeningTask"></div>
<div id="tempOpeningCodeRow" style="display:none; margin-top:1rem; text-align:center;">
<div class="game-label">Entsperrcode</div>
<div id="tempOpeningCode" style="font-size:1.8rem; font-weight:700; letter-spacing:0.18em; padding:0.6rem 0; color:var(--color-primary);"></div>
</div>
<div class="game-btn-row">
<button class="btn-primary" onclick="doEndTempOpening()">✓ Erledigt</button>
</div>
</div>
<!-- Finisher -->
<div id="finisherBox" style="display:none;">
<div class="trophy">🏆</div>
<h2>Level 6 erreicht!</h2>
<div class="game-label" id="finisherTitle"></div>
<div class="game-text" id="finisherText" style="margin-top:0.5rem;text-align:left;"></div>
<button class="btn-primary" id="btnFinisherOk" style="margin-top:1.25rem;">✓ OK</button>
<div id="finisherStart" style="margin-top:1.25rem;">
<button class="btn-primary" onclick="doStartFinisher()">▶ Starten</button>
</div>
<div id="finisherRunning" style="display:none;margin-top:1.25rem;">
<div class="game-timer active" id="finisherTimer">00:00</div>
<button class="btn-primary" onclick="doEndFinisher()" style="margin-top:1rem;">✓ Erledigt</button>
</div>
</div>
<!-- Debug -->
@@ -340,6 +362,7 @@
async function startWithExcludedToys(gameSetId, excludedToyIds) {
const params = new URLSearchParams({ aufgabenGruppeId: gameSetId });
if (lockId) params.append('lockId', lockId);
excludedToyIds.forEach(id => params.append('excludedToyIds', id));
const r = await fetch('/lock-game/init?' + params.toString(), { method: 'POST' });
@@ -417,7 +440,9 @@
await loadAndShowToys(sel.value);
}
// ── Game Loop ─────────────────────────────────────────────────────────────
// ── Benötigt-Checkboxen ───────────────────────────────────────────────────
// ── Game Loop ─────────────────────────────────────────────────────────────
function setGameCard(label, text, action, btnLabel) {
document.getElementById('gameLabel').textContent = label;
@@ -432,10 +457,16 @@
async function runGameLoop() {
hide('gameCard');
hide('finisherBox');
hide('tempOpeningBox');
clearTimer();
if (_state.level >= 6) {
await showFinisherFlow();
if (_state.finisher) {
showFinisherUI();
return;
}
if (_state.tempOpeningTime) {
showTempOpeningDialog();
return;
}
@@ -460,12 +491,14 @@
let sperre;
try { sperre = JSON.parse(_state.lockInQueue); } catch { sperre = {}; }
setGameCard('🔒 Neue Sperre', sperre.text || sperre.kurzText || '', 'queue-start', '▶ Starten');
} else if (_state.taskInQueue) {
let aufgabe;
try { aufgabe = JSON.parse(_state.taskInQueue); } catch { aufgabe = {}; }
const hasDuration = !!(aufgabe.sekundenVon || aufgabe.sekundenBis);
setGameCard('🎯 Neue Aufgabe', aufgabe.text || '', hasDuration ? 'queue-start' : 'queue-done',
hasDuration ? '▶ Starten' : '✓ Erledigt');
}
show('gameBox');
show('gameCard');
@@ -497,19 +530,38 @@
switch (_gameAction) {
case 'queue-start': doQueueStart(); break;
case 'queue-done': doQueueDone(); break;
case 'active-running': doCancelCountdown(); break;
case 'active-running': openConfirmModal('Aufgabe wirklich abbrechen?', () => doCancelCountdown()); break;
case 'active-done': doErledigt(); break;
}
}
async function doQueueStart() {
try {
await checkAndShowLocks();
const wasLock = !!_state.lockInQueue;
let tempUnlockRequired = false;
if (wasLock) {
try { tempUnlockRequired = JSON.parse(_state.lockInQueue).tempUnlockRequired === true; } catch (_) {}
}
const r = await fetch('/lock-game/apply-task', { method: 'POST' });
if (!r.ok) { showError('Fehler beim Starten'); return; }
const stateR = await fetch('/lock-game/state');
_state = await stateR.json();
await runGameLoop();
if (wasLock && tempUnlockRequired) {
await fetch('/lock-game/start-temp-opening', { method: 'POST' });
const stateR = await fetch('/lock-game/state');
_state = await stateR.json();
showTempOpeningDialog();
} else if (wasLock) {
const nextR = await fetch('/lock-game/abandon-task', { method: 'POST' });
if (!nextR.ok) { showError('Fehler beim Ziehen'); return; }
const stateR = await fetch('/lock-game/state');
_state = await stateR.json();
await runGameLoop();
} else {
const stateR = await fetch('/lock-game/state');
_state = await stateR.json();
await runGameLoop();
}
} catch (e) { showError(e.message || 'Fehler (Starten)'); }
}
@@ -526,16 +578,28 @@
} catch (e) { showError(e.message || 'Fehler (Erledigt)'); }
}
function doCancelCountdown() {
async function doCancelCountdown() {
clearTimer();
const lockR = await fetch('/lock-game/check-locks', { method: 'POST' });
if (lockR.ok) {
const texts = await lockR.json();
for (const text of (texts || [])) {
if (text != null && text !== '') await waitForReleaseOk(text);
}
}
_gameAction = 'active-done';
document.getElementById('gameBtn').textContent = '✓ Erledigt';
}
async function doErledigt() {
try {
await checkAndShowLocks();
if (_state.level >= 6) { await showFinisherFlow(); return; }
const lockR = await fetch('/lock-game/check-locks', { method: 'POST' });
if (lockR.ok) {
const texts = await lockR.json();
for (const text of (texts || [])) {
if (text != null && text !== '') await waitForReleaseOk(text);
}
}
const r = await fetch('/lock-game/next-task', { method: 'POST' });
if (!r.ok) { showError('Fehler beim Ziehen'); return; }
const stateR = await fetch('/lock-game/state');
@@ -557,51 +621,87 @@
}
}
async function showFinisherFlow() {
function showTempOpeningDialog() {
show('gameBox');
hide('gameCard');
hide('lockReleaseBox');
hide('finisherBox');
// 1. Release-Texte sequenziell anzeigen
document.getElementById('tempOpeningTask').textContent = _state.activeTask || '';
const code = _state.tempOpeningCode;
if (code) {
document.getElementById('tempOpeningCode').textContent = code;
show('tempOpeningCodeRow');
} else {
hide('tempOpeningCodeRow');
}
show('tempOpeningBox');
}
async function doEndTempOpening() {
try {
const r = await fetch('/lock-game/release-locks');
if (r.ok) {
const texts = await r.json();
for (const text of texts) {
await waitForReleaseOk(text);
}
}
} catch (_) { /* ignorieren */ }
await fetch('/lock-game/end-temp-opening', { method: 'POST' });
await fetch('/lock-game/abandon-task', { method: 'POST' });
const stateR = await fetch('/lock-game/state');
_state = await stateR.json();
hide('tempOpeningBox');
await runGameLoop();
} catch (e) { showError(e.message || 'Fehler beim Abschluss der temporären Öffnung'); }
}
// 2. Finisher laden und Zeit messen
const finisherStartTime = Date.now();
let finisher = null;
try {
const r = await fetch('/lock-game/finisher');
if (r.ok) finisher = await r.json();
} catch (_) { /* ignorieren */ }
function showFinisherUI() {
show('gameBox');
hide('gameCard');
hide('lockReleaseBox');
document.getElementById('finisherTitle').textContent = finisher?.kurzText || '';
document.getElementById('finisherText').textContent = finisher?.text || 'Glückwunsch du hast Level 6 erreicht!';
let finisher = {};
try { finisher = JSON.parse(_state.finisher); } catch (_) {}
document.getElementById('finisherTitle').textContent = finisher.kurzText || '';
document.getElementById('finisherText').textContent = finisher.text || '';
// 3. Warten bis Nutzer OK drückt
await new Promise(resolve => {
document.getElementById('btnFinisherOk').onclick = resolve;
show('finisherBox');
});
if (_state.finisherStartedAt) {
hide('finisherStart');
show('finisherRunning');
startElapsedTimer(new Date(_state.finisherStartedAt));
} else {
show('finisherStart');
hide('finisherRunning');
}
show('finisherBox');
}
// 4. Zeit berechnen und Spiel beenden
const timeInMinutes = Math.round((Date.now() - finisherStartTime) / 60000);
const params = new URLSearchParams({ timeInMinutes });
if (lockId) params.set('lockId', lockId);
await fetch('/lock-game/complete?' + params.toString(), { method: 'POST' });
async function doStartFinisher() {
await fetch('/lock-game/start-finisher', { method: 'POST' });
const r = await fetch('/lock-game/state');
_state = await r.json();
hide('finisherStart');
show('finisherRunning');
startElapsedTimer(new Date(_state.finisherStartedAt));
}
async function doEndFinisher() {
clearTimer();
await fetch('/lock-game/end-finisher', { method: 'POST' });
const url = '/lock-game/complete' + (lockId ? '?lockId=' + lockId : '');
await fetch(url, { method: 'POST' });
goBack();
}
function startElapsedTimer(startDate) {
clearTimer();
const el = document.getElementById('finisherTimer');
_timerInt = setInterval(() => {
const diff = Math.floor((Date.now() - startDate) / 1000);
const m = String(Math.floor(diff / 60)).padStart(2, '0');
const s = String(diff % 60).padStart(2, '0');
el.textContent = m + ':' + s;
}, 1000);
}
function waitForReleaseOk(text) {
return new Promise(resolve => {
document.getElementById('releaseText').textContent = text;
hide('gameCard');
document.getElementById('releaseText').textContent = text || '';
document.getElementById('btnReleaseOk').onclick = () => {
hide('lockReleaseBox');
resolve();
@@ -658,7 +758,37 @@
box.style.display = '';
}
// ── Confirm Modal ─────────────────────────────────────────────────────────
const _confirmModal = document.getElementById('confirmModal');
document.getElementById('confirmModalCancel').addEventListener('click', closeConfirmModal);
document.getElementById('confirmModalOk').addEventListener('click', closeConfirmModal);
_confirmModal.addEventListener('click', e => { if (e.target === _confirmModal) closeConfirmModal(); });
document.addEventListener('keydown', e => { if (e.key === 'Escape' && _confirmModal.classList.contains('open')) closeConfirmModal(); });
function closeConfirmModal() { _confirmModal.classList.remove('open'); }
function openConfirmModal(text, onConfirm) {
document.getElementById('confirmModalText').textContent = text;
const okBtn = document.getElementById('confirmModalOk');
const newOk = okBtn.cloneNode(true);
okBtn.parentNode.replaceChild(newOk, okBtn);
newOk.addEventListener('click', () => { closeConfirmModal(); onConfirm(); });
_confirmModal.classList.add('open');
}
boot();
</script>
<div class="modal-backdrop" id="confirmModal">
<div class="modal" style="max-width:420px;">
<h2>Bestätigung</h2>
<p id="confirmModalText" style="color:var(--color-text);margin-bottom:1.25rem;line-height:1.5;"></p>
<div class="modal-actions">
<button class="btn-cancel" id="confirmModalCancel">Nein</button>
<button class="btn-save" id="confirmModalOk" style="background:var(--color-danger,#e74c3c);">Ja, abbrechen</button>
</div>
</div>
</div>
</body>
</html>