Fix mobile broom flight with D-pad & touch-action, add Chamber II McGonagalls Wizard Chess
This commit is contained in:
Executable
+247
@@ -0,0 +1,247 @@
|
||||
// McGonagall's Wizard Chess (Zauberschach) Mini-Game
|
||||
(function() {
|
||||
const boardEl = document.getElementById('chessBoard');
|
||||
if (!boardEl) return;
|
||||
|
||||
const statusMsg = document.getElementById('chessStatus');
|
||||
const moveHistoryEl = document.getElementById('moveHistory');
|
||||
const modalBackdrop = document.getElementById('modalBackdrop');
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const modalText = document.getElementById('modalText');
|
||||
const modalBtn = document.getElementById('modalBtn');
|
||||
const btnResetChess = document.getElementById('btnResetChess');
|
||||
|
||||
// Piece Unicode Symbols
|
||||
const PIECES = {
|
||||
'wK': '♔', 'wQ': '♕', 'wR': '♖', 'wB': '♗', 'wN': '♘', 'wP': '♙',
|
||||
'bK': '♚', 'bQ': '♛', 'bR': '♜', 'bB': '♝', 'bN': '♞', 'bP': '♟'
|
||||
};
|
||||
|
||||
// Initial Board State for "Rons Meisterzug" (Smothered Mate / Zauberschach-Taktik)
|
||||
// Black to move: Knight on g4 and Queen on h4 attack White's castled King on h1!
|
||||
let initialPosition = {
|
||||
'h1': 'wK', 'g2': 'wP', 'h2': 'wP', 'f1': 'wR', 'd2': 'wQ', 'b2': 'wP', 'a1': 'wR',
|
||||
'g8': 'bK', 'g7': 'bP', 'h7': 'bP', 'f8': 'bR', 'h4': 'bQ', 'g4': 'bN', 'c6': 'bP'
|
||||
};
|
||||
|
||||
let boardState = {};
|
||||
let selectedSquare = null;
|
||||
let isSolved = false;
|
||||
|
||||
function initBoard() {
|
||||
boardState = Object.assign({}, initialPosition);
|
||||
selectedSquare = null;
|
||||
isSolved = false;
|
||||
if (statusMsg) {
|
||||
statusMsg.innerHTML = "🧙♂️ <strong>Du führst Schwarz (Rons Springer & Hermines Figuren)!</strong> Finde den vernichtenden Zug gegen den weißen König!";
|
||||
statusMsg.style.color = "var(--gold)";
|
||||
}
|
||||
if (moveHistoryEl) moveHistoryEl.innerHTML = "<li>Partie eröffnet: Schwarz am Zug!</li>";
|
||||
renderBoard();
|
||||
}
|
||||
|
||||
// Valid moves for Black pieces in this tactical puzzle
|
||||
function getValidMoves(square) {
|
||||
const piece = boardState[square];
|
||||
if (!piece || !piece.startsWith('b')) return [];
|
||||
|
||||
const file = square.charCodeAt(0) - 97; // 0 to 7 (a-h)
|
||||
const rank = parseInt(square[1], 10) - 1; // 0 to 7 (1-8)
|
||||
const moves = [];
|
||||
|
||||
if (piece === 'bN') {
|
||||
// Knight moves: L-shape
|
||||
const offsets = [
|
||||
[-2, -1], [-2, 1], [-1, -2], [-1, 2],
|
||||
[1, -2], [1, 2], [2, -1], [2, 1]
|
||||
];
|
||||
offsets.forEach(([df, dr]) => {
|
||||
const nf = file + df;
|
||||
const nr = rank + dr;
|
||||
if (nf >= 0 && nf < 8 && nr >= 0 && nr < 8) {
|
||||
const targetSq = String.fromCharCode(97 + nf) + (nr + 1);
|
||||
const targetPiece = boardState[targetSq];
|
||||
if (!targetPiece || targetPiece.startsWith('w')) {
|
||||
moves.push(targetSq);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (piece === 'bQ') {
|
||||
// Queen moves: orthogonal + diagonal
|
||||
const directions = [
|
||||
[0, 1], [0, -1], [1, 0], [-1, 0],
|
||||
[1, 1], [1, -1], [-1, 1], [-1, -1]
|
||||
];
|
||||
directions.forEach(([df, dr]) => {
|
||||
let step = 1;
|
||||
while (true) {
|
||||
const nf = file + df * step;
|
||||
const nr = rank + dr * step;
|
||||
if (nf < 0 || nf >= 8 || nr < 0 || nr >= 8) break;
|
||||
const targetSq = String.fromCharCode(97 + nf) + (nr + 1);
|
||||
const targetPiece = boardState[targetSq];
|
||||
if (!targetPiece) {
|
||||
moves.push(targetSq);
|
||||
} else {
|
||||
if (targetPiece.startsWith('w')) moves.push(targetSq);
|
||||
break;
|
||||
}
|
||||
step++;
|
||||
}
|
||||
});
|
||||
}
|
||||
return moves;
|
||||
}
|
||||
|
||||
function renderBoard() {
|
||||
boardEl.innerHTML = '';
|
||||
// 8 ranks (from 8 down to 1), 8 files (a to h)
|
||||
for (let r = 8; r >= 1; r--) {
|
||||
for (let f = 0; f < 8; f++) {
|
||||
const sq = String.fromCharCode(97 + f) + r;
|
||||
const isLight = (f + r) % 2 !== 0;
|
||||
|
||||
const tile = document.createElement('div');
|
||||
tile.className = `chess-sq ${isLight ? 'light' : 'dark'}`;
|
||||
tile.dataset.sq = sq;
|
||||
|
||||
if (selectedSquare === sq) {
|
||||
tile.classList.add('selected');
|
||||
}
|
||||
|
||||
const piece = boardState[sq];
|
||||
if (piece) {
|
||||
const pSpan = document.createElement('span');
|
||||
pSpan.className = `chess-piece ${piece.startsWith('w') ? 'white-piece' : 'black-piece'}`;
|
||||
pSpan.innerText = PIECES[piece] || '';
|
||||
tile.appendChild(pSpan);
|
||||
}
|
||||
|
||||
// Show valid moves
|
||||
if (selectedSquare) {
|
||||
const validMoves = getValidMoves(selectedSquare);
|
||||
if (validMoves.includes(sq)) {
|
||||
const dot = document.createElement('div');
|
||||
dot.className = piece ? 'move-capture-ring' : 'move-dot';
|
||||
tile.appendChild(dot);
|
||||
}
|
||||
}
|
||||
|
||||
tile.addEventListener('click', () => onSquareClick(sq));
|
||||
boardEl.appendChild(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onSquareClick(sq) {
|
||||
if (isSolved) return;
|
||||
|
||||
const piece = boardState[sq];
|
||||
|
||||
if (selectedSquare) {
|
||||
const validMoves = getValidMoves(selectedSquare);
|
||||
if (validMoves.includes(sq)) {
|
||||
// Execute Move!
|
||||
executeMove(selectedSquare, sq);
|
||||
selectedSquare = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Select new piece if black
|
||||
if (piece && piece.startsWith('b')) {
|
||||
selectedSquare = sq;
|
||||
if (window.soundFX) window.soundFX.playStoneSlide();
|
||||
renderBoard();
|
||||
} else {
|
||||
selectedSquare = null;
|
||||
renderBoard();
|
||||
}
|
||||
}
|
||||
|
||||
async function executeMove(fromSq, toSq) {
|
||||
const piece = boardState[fromSq];
|
||||
const capturedPiece = boardState[toSq];
|
||||
|
||||
delete boardState[fromSq];
|
||||
boardState[toSq] = piece;
|
||||
|
||||
if (window.soundFX) {
|
||||
window.soundFX.playStoneSlide();
|
||||
if (capturedPiece) {
|
||||
window.soundFX.playPoisonSizzle(); // Crushing sound
|
||||
}
|
||||
}
|
||||
|
||||
renderBoard();
|
||||
|
||||
// Check if winning move:
|
||||
// Winning move 1: Knight moves to f2+ (Smothered mate / Royal fork!)
|
||||
// Winning move 2: Queen captures h2# (Schachmatt!)
|
||||
if (piece === 'bN' && toSq === 'f2') {
|
||||
if (moveHistoryEl) {
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = `<strong>1... ♞f2+</strong> (Springer-Schach! Weißer König erstarrt!)`;
|
||||
moveHistoryEl.appendChild(li);
|
||||
}
|
||||
triggerWin("♞f2+", "Rons Springer zerschellt die weiße Verteidigung! Der weiße König hat kein Feld mehr und die weiße Königin muss kapitulieren!");
|
||||
} else if (piece === 'bQ' && toSq === 'h2') {
|
||||
if (moveHistoryEl) {
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = `<strong>1... ♛xh2#</strong> (Schachmatt! Die schwarze Dame schlägt zu!)`;
|
||||
moveHistoryEl.appendChild(li);
|
||||
}
|
||||
triggerWin("♛xh2#", "Schachmatt! Mit kaltblütiger Präzision fällt der weiße König in Trümmer!");
|
||||
} else {
|
||||
if (moveHistoryEl) {
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = `Zug: ${piece} nach ${toSq}. Weiß pariert den Angriff. Versuche es erneut!`;
|
||||
moveHistoryEl.appendChild(li);
|
||||
}
|
||||
if (statusMsg) {
|
||||
statusMsg.innerHTML = "⚠️ <em>Kein Schachmatt! Die weißen Figuren schlagen zurück. Drücke auf 'Zurücksetzen' und versuche es erneut!</em>";
|
||||
statusMsg.style.color = "#e67e22";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function triggerWin(moveNotation, explanation) {
|
||||
isSolved = true;
|
||||
if (window.soundFX) window.soundFX.playMagicSuccess();
|
||||
|
||||
if (statusMsg) {
|
||||
statusMsg.innerHTML = `🏆 <strong>${moveNotation} — SCHACHMATT!</strong> Die gigantischen weißen Steinfiguren senken ihre Schwerter!`;
|
||||
statusMsg.style.color = "#2ecc71";
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/chess/complete', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.new_item && window.notifyNewItem) {
|
||||
window.notifyNewItem(data.new_item);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
modalTitle.innerText = "♟️ Zauberschach Gemeistert!";
|
||||
modalText.innerHTML = `
|
||||
<strong>${moveNotation} — Grandios kombiniert!</strong><br><br>
|
||||
${explanation}<br><br>
|
||||
Die weiße Königin wirft ihr Schwert zu Boden. Die Riesen-Schachfiguren weichen zur Seite und die Tür zur <strong>Flammenkammer (Snapes Flaschenrätsel)</strong> steht weit offen!<br><br>
|
||||
🎒 <em>Ein massiver schwarzer Springer wurde deinem Zauberbeutel hinzugefügt!</em>
|
||||
`;
|
||||
modalBtn.innerText = "Weiter zu Snapes Flammenkammer 🧪";
|
||||
modalBtn.onclick = () => { window.location.href = '/bottles'; };
|
||||
modalBackdrop.classList.add('show');
|
||||
}
|
||||
|
||||
if (btnResetChess) {
|
||||
btnResetChess.addEventListener('click', () => {
|
||||
if (window.soundFX) window.soundFX.playStoneSlide();
|
||||
initBoard();
|
||||
});
|
||||
}
|
||||
|
||||
initBoard();
|
||||
})();
|
||||
Reference in New Issue
Block a user