248 lines
9.6 KiB
JavaScript
Executable File
248 lines
9.6 KiB
JavaScript
Executable File
// 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();
|
|
})();
|