159 lines
6.1 KiB
JavaScript
Executable File
159 lines
6.1 KiB
JavaScript
Executable File
// Alchemy Potion Sliding Block Puzzle (Schieberätsel)
|
|
(function() {
|
|
const gridEl = document.getElementById('slidingGrid');
|
|
if (!gridEl) return;
|
|
|
|
const movesVal = document.getElementById('slidingMoves');
|
|
const timerVal = document.getElementById('slidingTimer');
|
|
const modalBackdrop = document.getElementById('modalBackdrop');
|
|
const modalTitle = document.getElementById('modalTitle');
|
|
const modalText = document.getElementById('modalText');
|
|
const modalBtn = document.getElementById('modalBtn');
|
|
const btnScramble = document.getElementById('btnScramble');
|
|
|
|
const SIZE = 3; // 3x3 sliding puzzle (8 tiles + 1 blank)
|
|
let tiles = [];
|
|
let blankIndex = 8;
|
|
let moves = 0;
|
|
let timerInterval = null;
|
|
let secondsElapsed = 0;
|
|
let isSolved = false;
|
|
|
|
// Tile images / potion runes
|
|
const tileSymbols = [
|
|
{ id: 1, icon: "🧪", name: "Alraune", color: "#2ecc71" },
|
|
{ id: 2, icon: "🍷", name: "Brennnesselwein", color: "#e74c3c" },
|
|
{ id: 3, icon: "⚡", name: "Blitzphiole", color: "#f1c40f" },
|
|
{ id: 4, icon: "🔮", name: "Phönix-Träne", color: "#9b59b6" },
|
|
{ id: 5, icon: "🐉", name: "Drachenblut", color: "#e67e22" },
|
|
{ id: 6, icon: "💀", name: "Tobsuchtsgift", color: "#1abc9c" },
|
|
{ id: 7, icon: "💎", name: "Elixier des Lebens", color: "#3498db" },
|
|
{ id: 8, icon: "🕯️", name: "Ewige Flamme", color: "#d35400" },
|
|
{ id: 0, icon: "", name: "Leerer Runen-Sockel", color: "transparent" } // Blank
|
|
];
|
|
|
|
function init() {
|
|
tiles = [1, 2, 3, 4, 5, 6, 7, 8, 0];
|
|
blankIndex = 8;
|
|
moves = 0;
|
|
secondsElapsed = 0;
|
|
isSolved = false;
|
|
if (movesVal) movesVal.innerText = moves;
|
|
if (timerVal) timerVal.innerText = '0s';
|
|
if (timerInterval) clearInterval(timerInterval);
|
|
timerInterval = setInterval(() => {
|
|
if (!isSolved) {
|
|
secondsElapsed++;
|
|
if (timerVal) timerVal.innerText = secondsElapsed + 's';
|
|
}
|
|
}, 1000);
|
|
|
|
scramble();
|
|
render();
|
|
}
|
|
|
|
function scramble() {
|
|
// Perform 60 random valid moves from blank to ensure solvability
|
|
for (let i = 0; i < 60; i++) {
|
|
const neighbors = getNeighbors(blankIndex);
|
|
const randomNeighbor = neighbors[Math.floor(Math.random() * neighbors.length)];
|
|
tiles[blankIndex] = tiles[randomNeighbor];
|
|
tiles[randomNeighbor] = 0;
|
|
blankIndex = randomNeighbor;
|
|
}
|
|
moves = 0;
|
|
if (movesVal) movesVal.innerText = moves;
|
|
}
|
|
|
|
function getNeighbors(idx) {
|
|
const row = Math.floor(idx / SIZE);
|
|
const col = idx % SIZE;
|
|
const res = [];
|
|
if (row > 0) res.push(idx - SIZE); // Up
|
|
if (row < SIZE - 1) res.push(idx + SIZE); // Down
|
|
if (col > 0) res.push(idx - 1); // Left
|
|
if (col < SIZE - 1) res.push(idx + 1); // Right
|
|
return res;
|
|
}
|
|
|
|
function render() {
|
|
gridEl.innerHTML = '';
|
|
tiles.forEach((val, idx) => {
|
|
const tile = document.createElement('div');
|
|
tile.className = 'sliding-tile' + (val === 0 ? ' blank' : '');
|
|
tile.dataset.index = idx;
|
|
|
|
if (val !== 0) {
|
|
const info = tileSymbols.find(s => s.id === val);
|
|
tile.innerHTML = `
|
|
<span class="tile-icon">${info.icon}</span>
|
|
<span class="tile-num">${val}</span>
|
|
<span class="tile-name">${info.name}</span>
|
|
`;
|
|
tile.style.borderColor = info.color;
|
|
}
|
|
|
|
tile.addEventListener('click', () => onTileClick(idx));
|
|
gridEl.appendChild(tile);
|
|
});
|
|
}
|
|
|
|
function onTileClick(idx) {
|
|
if (isSolved) return;
|
|
const neighbors = getNeighbors(idx);
|
|
if (neighbors.includes(blankIndex)) {
|
|
// Swap with blank
|
|
tiles[blankIndex] = tiles[idx];
|
|
tiles[idx] = 0;
|
|
blankIndex = idx;
|
|
moves++;
|
|
if (movesVal) movesVal.innerText = moves;
|
|
|
|
if (window.soundFX) window.soundFX.playStoneSlide();
|
|
render();
|
|
checkWin();
|
|
}
|
|
}
|
|
|
|
function checkWin() {
|
|
const winning = [1, 2, 3, 4, 5, 6, 7, 8, 0];
|
|
const match = tiles.every((v, i) => v === winning[i]);
|
|
if (match) {
|
|
isSolved = true;
|
|
clearInterval(timerInterval);
|
|
if (window.soundFX) window.soundFX.playMagicSuccess();
|
|
|
|
fetch('/api/sliding/complete', { method: 'POST' }).catch(console.error);
|
|
|
|
if (window.showChamberVictory) {
|
|
window.showChamberVictory({
|
|
chamberBadge: "Alchemie-Zusatzprüfung",
|
|
title: "✨ Schiebesiegel Gelöst!",
|
|
statsHtml: `<span>⚗️ Züge: <strong>${moves}</strong></span><span>⏱️ Zeit: <strong>${secondsElapsed}s</strong></span>`,
|
|
storyText: "Mit geschicktem Schieben hast du alle 8 alchemistischen Essenzen in harmonische Ordnung gebracht! Das antike Relief leuchtet golden auf.",
|
|
nextUrl: "/bottles",
|
|
nextBtnText: "➔ Weiter zu Snapes Flaschen 🧪"
|
|
});
|
|
} else {
|
|
modalTitle.innerText = "✨ Schieberätsel gelöst!";
|
|
modalText.innerHTML = `Mit geschicktem Schieben hast du alle 8 alchemistischen Phiolen in die perfekte harmonische Ordnung gebracht!<br><br>
|
|
<strong>Züge:</strong> ${moves}<br>
|
|
<strong>Zeit:</strong> ${secondsElapsed} Sekunden<br><br>
|
|
Die Runenplatte leuchtet golden auf und der Weg zur Flammenkammer öffnet sich!`;
|
|
modalBtn.innerText = "Zur Flammenkammer 🧪";
|
|
modalBtn.onclick = () => { window.location.href = '/bottles'; };
|
|
modalBackdrop.classList.add('show');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (btnScramble) {
|
|
btnScramble.addEventListener('click', () => {
|
|
if (window.soundFX) window.soundFX.playStoneSlide();
|
|
init();
|
|
});
|
|
}
|
|
|
|
init();
|
|
})();
|