AP4 Spielanleitungen & Hints: gemeinsames Guide-Modul + MindMatch Paar-Blitz
- game-guide.js/.css (neu): gemeinsames "So funktioniert's"-Overlay mit ?-Button
in der Header-Tools-Leiste, Auto-Open beim Erstbesuch (localStorage
mf-guide-seen:<slug>), Escape/Backdrop schliesst, Fokus-Trap; deutsches Inline-
Registry fuer 7 Spiele (Networker bleibt bewusst bei seinem Inline-Guide). API
window.MindfluxGuide = {open, close, registerHintProvider} fuer einheitliche
Tipp-Buttons. Injektion via theme.js nur auf Game-Routen (wie FluxTimer), keine
Template-Edits.
- mindmatch.js: Hint "Paar-Blitz" (3 Ladungen) deckt ~1s ein ungematchtes Paar
auf und kostet +1 Zug -> Score/Sterne degradieren ueber die bestehende
Serverformel (null Backend-Aenderung); Reset in newGame, Verstecken in
finish/cancelGame; .mm-card.hinted-Glow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WFxcjtc9HNP4VECT6Ur1tE
This commit is contained in:
@@ -34,6 +34,10 @@
|
||||
let timerId = null;
|
||||
let running = false; // läuft gerade eine Runde? (steuert den Abbrechen-Button)
|
||||
|
||||
// Tipp „Paar-Blitz": deckt kurz ein noch nicht gematchtes Paar auf (kostet +1 Zug).
|
||||
let hintsLeft = 3;
|
||||
let hintUi = null; // Handle des Tipp-Buttons (von MindfluxGuide oder eigen)
|
||||
|
||||
/* Punkteformel – identisch zu GameScoreService.calculateScore (Server bleibt autoritativ). */
|
||||
function calculateScore(p, mv, secs) {
|
||||
if (p <= 0) return 0;
|
||||
@@ -175,6 +179,7 @@
|
||||
stopTimer();
|
||||
running = false;
|
||||
cancelBtn.hidden = true;
|
||||
hideHint();
|
||||
|
||||
const localScore = calculateScore(totalPairs, moves, seconds);
|
||||
const starCount = starsFor(totalPairs, moves);
|
||||
@@ -224,9 +229,95 @@
|
||||
lock = false;
|
||||
resultEl.hidden = true;
|
||||
cancelBtn.hidden = true;
|
||||
hideHint();
|
||||
setIdle('Spiel abgebrochen – wähle ein Deck und starte ein neues Spiel.');
|
||||
}
|
||||
|
||||
/* ---------- Tipp „Paar-Blitz" ----------
|
||||
Deckt EIN noch nicht gematchtes Paar ~1 s auf und kostet +1 Zug, damit Score/Sterne
|
||||
über die bestehende (Server-)Formel degradieren. Keine Backend-Änderung nötig. */
|
||||
function revealHintPair() {
|
||||
if (hintsLeft <= 0 || !running || lock) return hintsLeft;
|
||||
|
||||
const cards = Array.from(board.querySelectorAll('.mm-card:not(.matched)'));
|
||||
if (!cards.length) return hintsLeft;
|
||||
|
||||
let a = null;
|
||||
if (firstCard && !firstCard.classList.contains('matched')) {
|
||||
a = firstCard; // schon eine Karte offen -> dessen Partner zeigen
|
||||
} else {
|
||||
const closed = cards.filter((c) => !c.classList.contains('flipped'));
|
||||
const pool = closed.length ? closed : cards;
|
||||
a = pool[Math.floor(Math.random() * pool.length)];
|
||||
}
|
||||
const b = cards.find((c) => c !== a && c.dataset.pairId === a.dataset.pairId);
|
||||
if (!a || !b) return hintsLeft;
|
||||
|
||||
hintsLeft--;
|
||||
|
||||
// +1 Zug: der Server rechnet Score/Sterne allein aus moves.
|
||||
moves++;
|
||||
movesEl.textContent = String(moves);
|
||||
|
||||
[a, b].forEach((c) => c.classList.add('flipped', 'hinted'));
|
||||
|
||||
lock = true; // kurz sperren, damit niemand ins aufgedeckte Paar klickt
|
||||
setTimeout(() => {
|
||||
[a, b].forEach((c) => {
|
||||
c.classList.remove('hinted');
|
||||
if (c === firstCard || c.classList.contains('matched')) return;
|
||||
c.classList.remove('flipped');
|
||||
});
|
||||
lock = false;
|
||||
}, 1000);
|
||||
|
||||
return hintsLeft;
|
||||
}
|
||||
|
||||
function refreshHint() { if (hintUi && hintUi.update) hintUi.update(hintsLeft); }
|
||||
function showHint() { if (hintUi) { if (hintUi.show) hintUi.show(); refreshHint(); } }
|
||||
function hideHint() { if (hintUi && hintUi.hide) hintUi.hide(); }
|
||||
|
||||
/* Fallback-Button in der .mm-bar-group, falls MindfluxGuide nicht verfügbar ist. */
|
||||
function buildOwnHintButton() {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'mm-btn mm-btn-hint';
|
||||
function paint() {
|
||||
btn.textContent = `Tipp (${hintsLeft})`;
|
||||
btn.disabled = hintsLeft <= 0 || !running;
|
||||
}
|
||||
btn.addEventListener('click', () => { revealHintPair(); paint(); });
|
||||
const group = document.querySelector('.mm-bar-group');
|
||||
if (group) group.appendChild(btn);
|
||||
paint();
|
||||
return {
|
||||
update: paint,
|
||||
show() { btn.hidden = false; paint(); },
|
||||
hide() { btn.hidden = true; }
|
||||
};
|
||||
}
|
||||
|
||||
/* Bevorzugt den einheitlichen Tipp-Button von MindfluxGuide; wartet kurz auf dessen
|
||||
API (Ladereihenfolge nicht garantiert) und fällt sonst auf einen eigenen Button zurück. */
|
||||
function setupHint(tries) {
|
||||
if (hintUi) return;
|
||||
const api = window.MindfluxGuide;
|
||||
if (api && typeof api.registerHintProvider === 'function') {
|
||||
hintUi = api.registerHintProvider({
|
||||
label: 'Tipp',
|
||||
remaining: () => hintsLeft,
|
||||
onHint: revealHintPair
|
||||
});
|
||||
} else if ((tries || 0) < 25) {
|
||||
setTimeout(() => setupHint((tries || 0) + 1), 60);
|
||||
return;
|
||||
} else {
|
||||
hintUi = buildOwnHintButton();
|
||||
}
|
||||
if (running) showHint(); else hideHint();
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const res = await fetch(`/api/games/${GAME}/stats`);
|
||||
@@ -249,8 +340,10 @@
|
||||
resultEl.hidden = true;
|
||||
firstCard = null;
|
||||
lock = false;
|
||||
hintsLeft = 3;
|
||||
|
||||
if (!deckPairs || !deckPairs.length) {
|
||||
hideHint();
|
||||
setIdle('Dieses Deck hat noch keine Paare.');
|
||||
return;
|
||||
}
|
||||
@@ -262,6 +355,7 @@
|
||||
|
||||
running = true;
|
||||
cancelBtn.hidden = false;
|
||||
showHint();
|
||||
}
|
||||
|
||||
async function loadDecks() {
|
||||
@@ -292,6 +386,7 @@
|
||||
againBtn.addEventListener('click', newGame);
|
||||
cancelBtn.addEventListener('click', cancelGame);
|
||||
deckSelect.addEventListener('change', newGame);
|
||||
setupHint();
|
||||
|
||||
await loadStats();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user