// Globální proměnné a Stavy let state = { screen: 'main-menu', phase: 'build', // build, defend, qte, shop, gameover stats: { score: 0, coins: 0, wave: 1, streak: 0, maxStreak: 0, kills: 0, totalCoins: 0, gamesPlayed: 0, maxWave: 0 }, build: { progress: 0, // aktuální krok target: 7, // 7 kroků k postavení style: 'castle1' // výchozí vzhled }, castle: { hp: 100, maxHp: 100 }, upgrades: { slowMultiplier: 1, // 1 = normální rychlost, < 1 zpomaleno fairyActive: false }, currentProblem: null, // objekt { text: "2 + 2", answer: 4, type: "normal" } enemies: [], // pole aktivních nepřátel qte: { active: false, timer: null, timeLeft: 0, maxTime: 5000 // 5 sekund }, intervals: { gameLoop: null, spawner: null, fairy: null } }; // --- DOM Elementy --- const els = { // Screens mainMenu: document.getElementById('main-menu'), buildScreen: document.getElementById('build-screen'), defendScreen: document.getElementById('defend-screen'), shopScreen: document.getElementById('shop-screen'), gameOverScreen: document.getElementById('game-over-screen'), statsMenuScreen: document.getElementById('stats-menu-screen'), // Tlačítka Menu btnStart: document.getElementById('btn-start'), btnStatsMenu: document.getElementById('btn-stats-menu'), btnStatsBack: document.getElementById('btn-stats-back'), // Build buildStyleSelection: document.getElementById('build-style-selection'), btnStartBuild: document.getElementById('btn-start-build'), buildActionArea: document.getElementById('build-action-area'), buildVisualStep: document.getElementById('build-visual-step'), buildProgress: document.getElementById('build-progress'), buildProgressText: document.getElementById('build-progress-text'), buildMathProblem: document.getElementById('build-math-problem'), buildInput: document.getElementById('build-input'), buildCompareBtns: document.getElementById('build-compare-buttons'), // Defend HUD waveDisplay: document.getElementById('wave-display'), coinsDisplay: document.getElementById('coins-display'), streakDisplay: document.getElementById('streak-display'), castleHpBar: document.getElementById('castle-hp-bar'), castleHpText: document.getElementById('castle-hp-text'), mainCastle: document.getElementById('main-castle'), enemiesArea: document.getElementById('enemies-area'), gameContainer: document.getElementById('game-container'), defendInput: document.getElementById('defend-input'), defendCompareBtns: document.getElementById('defend-compare-buttons'), // QTE qteContainer: document.getElementById('qte-container'), qteMathProblem: document.getElementById('qte-math-problem'), qteTimerBar: document.getElementById('qte-timer-bar'), // Shop shopWaveNum: document.getElementById('shop-wave-num'), shopCoins: document.getElementById('shop-coins'), btnNextWave: document.getElementById('btn-next-wave'), buyHpBtn: document.getElementById('buy-hp'), buySlowBtn: document.getElementById('buy-slow'), buyFairyBtn: document.getElementById('buy-fairy'), // Game Over statWave: document.getElementById('stat-wave'), statScore: document.getElementById('stat-score'), statStreak: document.getElementById('stat-streak'), statKills: document.getElementById('stat-kills'), btnRestart: document.getElementById('btn-restart'), btnToMain: document.getElementById('btn-to-main'), // Global Stats globalBestWave: document.getElementById('global-best-wave'), globalGamesPlayed: document.getElementById('global-games-played'), globalTotalCoins: document.getElementById('global-total-coins'), // Particles particles: document.getElementById('particles') }; // --- Inicializace --- function init() { loadGlobalStats(); attachEventListeners(); showScreen('main-menu'); } // --- Systém Obrazovek --- function showScreen(screenId) { // Skrytí všech els.mainMenu.classList.add('hidden'); els.buildScreen.classList.add('hidden'); els.defendScreen.classList.add('hidden'); els.shopScreen.classList.add('hidden'); els.gameOverScreen.classList.add('hidden'); els.statsMenuScreen.classList.add('hidden'); // Zobrazení vybrané if (screenId === 'main-menu') els.mainMenu.classList.remove('hidden'); else if (screenId === 'build-screen') els.buildScreen.classList.remove('hidden'); else if (screenId === 'defend-screen') els.defendScreen.classList.remove('hidden'); else if (screenId === 'shop-screen') els.shopScreen.classList.remove('hidden'); else if (screenId === 'game-over-screen') els.gameOverScreen.classList.remove('hidden'); else if (screenId === 'stats-menu-screen') els.statsMenuScreen.classList.remove('hidden'); state.screen = screenId; } // --- Správa událostí (Event Listeners) --- function attachEventListeners() { // Main Menu els.btnStart.addEventListener('click', startGame); els.btnStatsMenu.addEventListener('click', () => { updateGlobalStatsUI(); showScreen('stats-menu-screen'); }); els.btnStatsBack.addEventListener('click', () => showScreen('main-menu')); // Game Over els.btnRestart.addEventListener('click', startGame); els.btnToMain.addEventListener('click', () => showScreen('main-menu')); // Build Style Selection document.querySelectorAll('.style-option').forEach(option => { option.addEventListener('click', (e) => { document.querySelectorAll('.style-option').forEach(opt => opt.classList.remove('selected')); e.target.classList.add('selected'); state.build.style = e.target.dataset.style; }); }); els.btnStartBuild.addEventListener('click', () => { els.buildStyleSelection.classList.add('hidden'); els.buildActionArea.classList.remove('hidden'); els.buildActionArea.style.display = 'flex'; // Nastavíme vizuál budování els.buildVisualStep.style.backgroundImage = `url('assets/${state.build.style}.svg')`; els.buildVisualStep.style.height = '0%'; generateBuildProblem(); }); } // --- Generování matematických příkladů --- function generateMathProblem(difficulty) { // difficulty: 1 (lehké, stavba), 2 (střední, vlny 1-3), 3 (těžší, vlny 4+), 4 (QTE - nejtěžší) const types = ['add', 'sub', 'mul', 'div', 'comp', 'round']; const type = types[Math.floor(Math.random() * types.length)]; let text = ""; let answer = null; let probType = "num"; // num (číselný vstup), comp (porovnávání) const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; switch (type) { case 'add': let maxAdd = difficulty === 1 ? 20 : (difficulty === 2 ? 50 : 100); let aAdd = getRandomInt(1, maxAdd); let bAdd = getRandomInt(1, maxAdd - aAdd > 0 ? maxAdd - aAdd : 10); if (difficulty >= 3) { aAdd = getRandomInt(10, 50); bAdd = getRandomInt(10, 50); } text = `${aAdd} + ${bAdd}`; answer = aAdd + bAdd; break; case 'sub': let maxSub = difficulty <= 2 ? 30 : 100; let aSub = getRandomInt(5, maxSub); let bSub = getRandomInt(1, aSub); text = `${aSub} - ${bSub}`; answer = aSub - bSub; break; case 'mul': let maxMul = difficulty === 1 ? 5 : (difficulty === 2 ? 8 : 10); let aMul = getRandomInt(1, maxMul); let bMul = getRandomInt(1, 10); text = `${aMul} × ${bMul}`; answer = aMul * bMul; break; case 'div': let divisor = getRandomInt(2, difficulty <= 2 ? 5 : 10); let quotient = getRandomInt(1, 10); let dividend = divisor * quotient; text = `${dividend} : ${divisor}`; answer = quotient; break; case 'comp': probType = "comp"; let aComp = getRandomInt(1, 100); let bComp = getRandomInt(1, 100); // snaha o blízka čísla if(Math.random() > 0.5) bComp = aComp + getRandomInt(-5, 5); text = `${aComp} ? ${bComp}`; if (aComp < bComp) answer = '<'; else if (aComp > bComp) answer = '>'; else answer = '='; break; case 'round': let numRound = getRandomInt(11, 99); // Nechceme končící na 0 if (numRound % 10 === 0) numRound += getRandomInt(1, 4); text = `Zaokrouhli ${numRound}`; answer = Math.round(numRound / 10) * 10; break; } return { text, answer, type: probType }; } // --- Herní Loop a Základní stavy --- function startGame() { console.log("Start hry..."); // Reset statistik pro novou hru state.stats.score = 0; state.stats.coins = 0; state.stats.wave = 1; state.stats.streak = 0; state.stats.maxStreak = 0; state.stats.kills = 0; // Reset hradu state.castle.maxHp = 100; state.castle.hp = state.castle.maxHp; // Reset upgrady state.upgrades.slowMultiplier = 1; state.upgrades.fairyActive = false; // Zvýšit počítadlo her state.stats.gamesPlayed++; saveGlobalStats(); startBuildPhase(); } function startBuildPhase() { state.phase = 'build'; state.build.progress = 0; // Obnovit UI pro výběr stylu els.buildStyleSelection.classList.remove('hidden'); els.buildActionArea.classList.add('hidden'); els.buildActionArea.style.display = 'none'; updateBuildUI(); showScreen('build-screen'); } function updateBuildUI() { let percent = (state.build.progress / state.build.target) * 100; els.buildProgress.style.width = `${percent}%`; els.buildProgressText.textContent = `Krok ${state.build.progress} / ${state.build.target}`; // Vizuál rostoucího hradu els.buildVisualStep.style.height = `${percent}%`; } function generateBuildProblem() { state.currentProblem = generateMathProblem(1); // obtížnost 1 pro stavbu els.buildMathProblem.textContent = state.currentProblem.text; if (state.currentProblem.type === 'comp') { els.buildInput.classList.add('hidden'); els.buildCompareBtns.classList.remove('hidden'); } else { els.buildInput.classList.remove('hidden'); els.buildCompareBtns.classList.add('hidden'); els.buildInput.value = ''; els.buildInput.focus(); } } // Zpracování vstupu ve fázi stavby function handleBuildInput(val) { if (state.phase !== 'build') return; // Převod na string pro snadné porovnání (čísla i znaky) if (val.toString() === state.currentProblem.answer.toString()) { // Správně state.build.progress += 1; createParticles(els.buildMathProblem, '#43bccd'); // modré jiskry // Mince za stavbu (1 za správnou odpověď) state.stats.coins += 1; state.stats.totalCoins += 1; saveGlobalStats(); if (state.build.progress >= state.build.target) { state.build.progress = state.build.target; updateBuildUI(); setTimeout(startDefendPhase, 1000); // pauza před obranou } else { updateBuildUI(); generateBuildProblem(); } } else { // Špatně createParticles(els.buildMathProblem, '#e94560'); // červené jiskry, shake els.buildMathProblem.style.animation = 'shake 0.5s'; setTimeout(() => els.buildMathProblem.style.animation = '', 500); if (state.currentProblem.type !== 'comp') { els.buildInput.value = ''; els.buildInput.focus(); } } } // Bindování inputů pro Build els.buildInput.addEventListener('keyup', (e) => { if (e.key === 'Enter' && els.buildInput.value !== '') { handleBuildInput(els.buildInput.value); } }); Array.from(els.buildCompareBtns.children).forEach(btn => { btn.addEventListener('click', (e) => { handleBuildInput(e.target.dataset.val); }); }); function startDefendPhase() { state.phase = 'defend'; console.log("Zahájení obrany vlna:", state.stats.wave); // Aktualizace HUD els.waveDisplay.textContent = state.stats.wave; els.coinsDisplay.textContent = state.stats.coins; els.streakDisplay.textContent = state.stats.streak; updateCastleHpUI(); els.enemiesArea.innerHTML = ''; state.enemies = []; showScreen('defend-screen'); els.defendInput.value = ''; els.defendInput.focus(); // Změna pozadí a vzhledu hradu els.mainCastle.style.backgroundImage = `url('assets/${state.build.style}.svg')`; updateBackground(); // Nastavení obtížnosti podle vlny let difficulty = 2; if (state.stats.wave > 3) difficulty = 3; // Parametry vlny let totalEnemies = 5 + (state.stats.wave * 2); let spawnedEnemies = 0; // Hlavní herní loop pro obranu (pohyb nepřátel) state.intervals.gameLoop = setInterval(() => { if (state.phase !== 'defend') return; updateEnemies(); }, 50); // 20 FPS // Spawner nepřátel let spawnRate = Math.max(1000, 3000 - (state.stats.wave * 200)); // Rychlejší spawn v dalších vlnách // Jestli je to boss level const isBossWave = state.stats.wave % 5 === 0; state.intervals.spawner = setInterval(() => { if (state.phase !== 'defend' || state.qte.active) return; if (spawnedEnemies < totalEnemies) { // Poslední nepřítel ve vlny dělitelné 5 je Boss let spawnBoss = false; if (isBossWave && spawnedEnemies === totalEnemies - 1) { spawnBoss = true; } spawnEnemy(difficulty, spawnBoss); spawnedEnemies++; } else if (state.enemies.length === 0 && !state.qte.active) { // Vlna dokončena endWave(); } // Šance na QTE (Záchrana jednorožce) - cca 15% každou vteřinu spawnu, pokud už není a vlna ještě neskončila if (!state.qte.active && Math.random() < 0.15 && spawnedEnemies < totalEnemies) { triggerQTE(); } }, spawnRate); } function updateBackground() { els.gameContainer.classList.remove('bg-1', 'bg-2', 'bg-3'); if (state.stats.wave <= 3) els.gameContainer.classList.add('bg-1'); else if (state.stats.wave <= 6) els.gameContainer.classList.add('bg-2'); else els.gameContainer.classList.add('bg-3'); } function spawnEnemy(difficulty, isBoss = false) { const enemyEl = document.createElement('div'); enemyEl.classList.add('enemy'); // Grafika const spriteEl = document.createElement('div'); spriteEl.classList.add('enemy-sprite'); if (isBoss) { spriteEl.classList.add('enemy-boss'); difficulty = Math.min(difficulty + 1, 4); // boss je o něco těžší } else { const sprites = ['enemy1.svg', 'enemy2.svg', 'enemy3.svg']; const chosenSprite = sprites[Math.floor(Math.random() * sprites.length)]; spriteEl.style.backgroundImage = `url('assets/${chosenSprite}')`; } // Příklad const mathEl = document.createElement('div'); mathEl.classList.add('enemy-math'); const problem = generateMathProblem(difficulty); mathEl.textContent = problem.text; enemyEl.appendChild(spriteEl); enemyEl.appendChild(mathEl); // Pozice (zprava, náhodná výška) const startX = els.enemiesArea.clientWidth; const heightOffset = isBoss ? 150 : 80; const startY = Math.random() * (els.enemiesArea.clientHeight - heightOffset); enemyEl.style.left = `${startX}px`; enemyEl.style.top = `${startY}px`; els.enemiesArea.appendChild(enemyEl); const speed = (Math.random() * 0.5 + 0.5 + (state.stats.wave * 0.1)) * state.upgrades.slowMultiplier; // Rychlost podle vlny a upgrady const id = Date.now() + Math.random(); state.enemies.push({ id: id, el: enemyEl, x: startX, y: startY, speed: speed, problem: problem }); // Aktualizuj input mód podle toho, co je nejblíž (nejblížící se nepřítel) updateDefendInputMode(); } function updateEnemies() { for (let i = state.enemies.length - 1; i >= 0; i--) { let enemy = state.enemies[i]; // Pohyb doleva enemy.x -= enemy.speed; enemy.el.style.left = `${enemy.x}px`; // Kolize s hradem (x <= 0) if (enemy.x <= 0) { damageCastle(10); enemy.el.remove(); state.enemies.splice(i, 1); // Reset streak resetStreak(); updateDefendInputMode(); } } } function damageCastle(amount) { state.castle.hp -= amount; if (state.castle.hp < 0) state.castle.hp = 0; updateCastleHpUI(); // Efekt na hrad const castleEl = document.querySelector('.castle'); castleEl.style.animation = 'shake 0.5s'; setTimeout(() => castleEl.style.animation = '', 500); if (state.castle.hp === 0) { gameOver(); } } function updateDefendInputMode() { let aliveEnemies = state.enemies.filter(e => !e.dying); if (aliveEnemies.length === 0) return; if (state.qte.active) return; // Nepřepínat vstupy, pokud běží QTE // Najdi nejbližšího nepřítele let closestEnemy = aliveEnemies.reduce((prev, curr) => (prev.x < curr.x) ? prev : curr); // Vizuální označení cíle state.enemies.forEach(e => e.el.classList.remove('targeted')); closestEnemy.el.classList.add('targeted'); if (closestEnemy.problem.type === 'comp') { els.defendInput.classList.add('hidden'); els.defendCompareBtns.classList.remove('hidden'); } else { els.defendInput.classList.remove('hidden'); els.defendCompareBtns.classList.add('hidden'); els.defendInput.focus(); } } function handleDefendInput(val) { if (state.phase !== 'defend' || state.qte.active) return; if (state.enemies.length === 0) { els.defendInput.value = ''; return; } let hit = false; let hitEnemyIndex = -1; // Najít nepřítele se správnou odpovědí (priorita nejbližší) // Seřadit nepřátele od nejbližšího (nejmenší X) let sortedEnemies = [...state.enemies].sort((a, b) => a.x - b.x); for (let enemy of sortedEnemies) { if (!enemy.dying && enemy.problem.answer.toString() === val.toString()) { hit = true; hitEnemyIndex = state.enemies.findIndex(e => e.id === enemy.id); break; } } if (hit && hitEnemyIndex !== -1) { // Zásah let enemy = state.enemies[hitEnemyIndex]; // Označíme nepřítele jako mrtvého, aby už na něj nešlo dál útočit enemy.dying = true; // Princezna vystřelí projektil shootProjectile(enemy, () => { createParticles(enemy.el, '#fca311'); // zlaté částice enemy.el.remove(); let realIdx = state.enemies.findIndex(e => e.id === enemy.id); if (realIdx !== -1) { state.enemies.splice(realIdx, 1); state.stats.kills++; state.stats.score += 10 * (1 + Math.floor(state.stats.streak / 5)); // Bonus za streak // Mince + Streak state.stats.streak++; if (state.stats.streak > state.stats.maxStreak) state.stats.maxStreak = state.stats.streak; // Každých 5 ve streaku = bonusová mince let coinsEarned = 1 + Math.floor(state.stats.streak / 5); state.stats.coins += coinsEarned; state.stats.totalCoins += coinsEarned; els.streakDisplay.textContent = state.stats.streak; els.coinsDisplay.textContent = state.stats.coins; updateDefendInputMode(); } }); els.defendInput.value = ''; } else { // Minutí resetStreak(); // Efekt chyby els.defendInput.style.animation = 'shake 0.3s'; els.defendCompareBtns.style.animation = 'shake 0.3s'; setTimeout(() => { els.defendInput.style.animation = ''; els.defendCompareBtns.style.animation = ''; }, 300); if (els.defendInput.classList.contains('hidden') === false) { els.defendInput.value = ''; } } } function shootProjectile(enemy, onHitCallback) { const proj = document.createElement('div'); proj.classList.add('projectile'); // Zjistit pozici princezny const princessEl = document.querySelector('.princess'); const pRect = princessEl.getBoundingClientRect(); const cRect = els.gameContainer.getBoundingClientRect(); let startX = pRect.left - cRect.left + pRect.width / 2; let startY = pRect.top - cRect.top + pRect.height / 2; proj.style.left = `${startX}px`; proj.style.top = `${startY}px`; els.gameContainer.appendChild(proj); // Animace k nepříteli let duration = 300; // ms let startTime = performance.now(); function animateProjectile(currentTime) { let elapsed = currentTime - startTime; let progress = Math.min(elapsed / duration, 1); let targetX = enemy.x + els.enemiesArea.offsetLeft; let targetY = enemy.y + els.enemiesArea.offsetTop + 40; // zhruba střed nepřítele let currentX = startX + (targetX - startX) * progress; let currentY = startY + (targetY - startY) * progress; proj.style.left = `${currentX}px`; proj.style.top = `${currentY}px`; if (progress < 1) { requestAnimationFrame(animateProjectile); } else { proj.remove(); if (onHitCallback) onHitCallback(); } } requestAnimationFrame(animateProjectile); } function resetStreak() { state.stats.streak = 0; els.streakDisplay.textContent = state.stats.streak; } // Bindování inputů pro Defend els.defendInput.addEventListener('keyup', (e) => { if (state.phase !== 'defend') return; if (e.key === 'Enter' && els.defendInput.value !== '') { handleDefendInput(els.defendInput.value); } }); Array.from(els.defendCompareBtns.children).forEach(btn => { btn.addEventListener('click', (e) => { if (state.phase !== 'defend') return; handleDefendInput(e.target.dataset.val); }); }); // --- QTE (Záchrana jednorožce) --- function triggerQTE() { console.log("QTE triggered!"); state.qte.active = true; state.qte.timeLeft = state.qte.maxTime; // Zobrazení QTE UI els.qteContainer.classList.remove('hidden'); els.qteTimerBar.style.width = '100%'; // Generování těžšího příkladu (obtížnost 4) state.currentProblem = generateMathProblem(4); els.qteMathProblem.textContent = state.currentProblem.text; // Nastavení vstupu pro QTE if (state.currentProblem.type === 'comp') { els.defendInput.classList.add('hidden'); els.defendCompareBtns.classList.remove('hidden'); } else { els.defendInput.classList.remove('hidden'); els.defendCompareBtns.classList.add('hidden'); els.defendInput.value = ''; els.defendInput.focus(); } // Spuštění odpočtu (interval 50ms) state.qte.timer = setInterval(() => { state.qte.timeLeft -= 50; let percent = (state.qte.timeLeft / state.qte.maxTime) * 100; els.qteTimerBar.style.width = `${percent}%`; if (state.qte.timeLeft <= 0) { failQTE(); } }, 50); } function resolveQTEInput(val) { if (val.toString() === state.currentProblem.answer.toString()) { successQTE(); } else { failQTE(); } } function successQTE() { clearInterval(state.qte.timer); // Efekt záchrany (magická vlna) createParticles(els.qteContainer, '#8e44ad'); // fialové createParticles(els.qteContainer, '#f1c40f'); // žluté // Odměna: Zničit všechny aktuální nepřátele na obrazovce state.enemies.forEach(enemy => { createParticles(enemy.el, '#8e44ad'); enemy.el.remove(); state.stats.kills++; state.stats.score += 20; }); state.enemies = []; // vyprázdnit pole // Bonusové mince state.stats.coins += 10; state.stats.totalCoins += 10; els.coinsDisplay.textContent = state.stats.coins; closeQTE(); } function failQTE() { clearInterval(state.qte.timer); // Jednorožec uteče - efekt zklamání els.qteContainer.style.animation = 'shake 0.5s'; setTimeout(() => els.qteContainer.style.animation = '', 500); closeQTE(); } function closeQTE() { els.qteContainer.classList.add('hidden'); state.qte.active = false; // Návrat vstupu zpět na obranu (nebo skrytí, pokud nejsou nepřátelé) updateDefendInputMode(); if (state.enemies.length === 0) { els.defendInput.classList.add('hidden'); els.defendCompareBtns.classList.add('hidden'); } } // Aktualizace vstupů v obraně (sloučení s QTE logic) // Nahradíme původní handlery (ty z předchozího kroku) za nové, které zohledňují QTE els.defendInput.removeEventListener('keyup', () => {}); // Nelze jednoduše odstranit anonymní funkce, takže je přepíšeme // Ošetříme to přidáním nové podmínky přímo do `handleDefendInput` a zachováme původní event listenery // ale přesuneme QTE logiku přímo tam, abychom zamezili duplikacím a problémům. // Protože JS neumožňuje snadno odstranit anonymní event listenery vytvořené dříve, upravíme logiku uvnitř listenerů // tím, že `handleDefendInput` bude vědět o QTE (což už dělá přes return). function checkGlobalInput(e) { if (state.phase !== 'defend') return; // Pokud je aktivní QTE if (state.qte.active && e.key === 'Enter' && els.defendInput.value !== '') { resolveQTEInput(els.defendInput.value); } } // Přiřadíme do globálního listeneru, abychom nepoužívali duplicitní event listenery na inputu window.addEventListener('keyup', (e) => { if (state.phase === 'defend' && state.qte.active) { if (e.key === 'Enter' && els.defendInput.value !== '') { resolveQTEInput(els.defendInput.value); els.defendInput.value = ''; } } }); // A pro tlačítka na porovnávání (pokud je QTE typu comp): Array.from(els.defendCompareBtns.children).forEach(btn => { btn.addEventListener('click', (e) => { if (state.phase === 'defend' && state.qte.active) { resolveQTEInput(e.target.dataset.val); } }); }); function endWave() { console.log("Konec vlny", state.stats.wave); // Clear intervals clearInterval(state.intervals.gameLoop); clearInterval(state.intervals.spawner); state.stats.wave++; if (state.stats.wave > state.stats.maxWave) state.stats.maxWave = state.stats.wave; saveGlobalStats(); showShopPhase(); } function showShopPhase() { state.phase = 'shop'; els.shopWaveNum.textContent = state.stats.wave - 1; els.shopCoins.textContent = state.stats.coins; // Obnovení tlačítek a jejich textů els.buyHpBtn.disabled = state.stats.coins < 50; if (state.upgrades.slowMultiplier <= 0.5) { els.buySlowBtn.textContent = 'MAX (Vyprodáno)'; els.buySlowBtn.disabled = true; } else { els.buySlowBtn.disabled = state.stats.coins < 100; els.buySlowBtn.textContent = 'Koupit (100 🪙)'; } if (state.upgrades.fairyActive) { els.buyFairyBtn.textContent = 'Aktivní (Vyprodáno)'; els.buyFairyBtn.disabled = true; } else { els.buyFairyBtn.disabled = state.stats.coins < 150; els.buyFairyBtn.textContent = 'Koupit (150 🪙)'; } showScreen('shop-screen'); } function updateShopUI() { els.shopCoins.textContent = state.stats.coins; showShopPhase(); // znovuzavolání pro přehodnocení disabled stavů } // Event Listenery pro Obchod els.buyHpBtn.addEventListener('click', () => { if (state.stats.coins >= 50) { state.stats.coins -= 50; state.castle.maxHp += 20; state.castle.hp += 20; // vyléčí i aktuální updateShopUI(); } }); els.buySlowBtn.addEventListener('click', () => { if (state.stats.coins >= 100 && state.upgrades.slowMultiplier > 0.5) { state.stats.coins -= 100; state.upgrades.slowMultiplier -= 0.1; // zpomalení o 10% updateShopUI(); } }); els.buyFairyBtn.addEventListener('click', () => { if (state.stats.coins >= 150 && !state.upgrades.fairyActive) { state.stats.coins -= 150; state.upgrades.fairyActive = true; startFairy(); updateShopUI(); } }); els.btnNextWave.addEventListener('click', () => { if (state.phase === 'shop') { startDefendPhase(); } }); function startFairy() { if (state.intervals.fairy) clearInterval(state.intervals.fairy); state.intervals.fairy = setInterval(() => { if (state.phase === 'defend' && state.enemies.length > 0 && !state.qte.active) { // Víla zničí náhodného nepřítele (nebo prvního) let enemyToKill = state.enemies[0]; createParticles(enemyToKill.el, '#ff9ff3'); // růžová vílí barva enemyToKill.el.remove(); state.enemies.shift(); // odstraní prvního z pole state.stats.kills++; state.stats.score += 10; updateDefendInputMode(); // Ukázat vílu graficky (krátká animace) const fairySprite = document.createElement('div'); fairySprite.textContent = '🧚‍♀️'; fairySprite.style.position = 'absolute'; fairySprite.style.left = enemyToKill.x + 'px'; fairySprite.style.top = enemyToKill.y - 30 + 'px'; fairySprite.style.fontSize = '3rem'; fairySprite.style.animation = 'flyout 1s forwards'; fairySprite.style.setProperty('--tx', `0px`); fairySprite.style.setProperty('--ty', `-100px`); els.enemiesArea.appendChild(fairySprite); setTimeout(() => fairySprite.remove(), 1000); } }, 5000); // Každých 5 sekund víla zasáhne } // Game Over logiky function gameOver() { console.log("Hra skončila"); state.phase = 'gameover'; // Vyčištění všech intervalů if (state.intervals.gameLoop) clearInterval(state.intervals.gameLoop); if (state.intervals.spawner) clearInterval(state.intervals.spawner); if (state.intervals.fairy) clearInterval(state.intervals.fairy); if (state.qte.timer) clearInterval(state.qte.timer); // Skrytí QTE, pokud běželo els.qteContainer.classList.add('hidden'); // Uložení aktuálních max statistik if (state.stats.wave > state.stats.maxWave) state.stats.maxWave = state.stats.wave; saveGlobalStats(); // Zobrazení UI els.statWave.textContent = state.stats.wave; els.statScore.textContent = state.stats.score; els.statStreak.textContent = state.stats.maxStreak; els.statKills.textContent = state.stats.kills; showScreen('game-over-screen'); } function updateCastleHpUI() { els.castleHpText.textContent = `${state.castle.hp} / ${state.castle.maxHp}`; let hpPercent = (state.castle.hp / state.castle.maxHp) * 100; els.castleHpBar.style.width = `${hpPercent}%`; if (hpPercent < 30) els.castleHpBar.style.backgroundColor = 'red'; else els.castleHpBar.style.backgroundColor = '#e94560'; } // Particle system helper function createParticles(element, color) { const rect = element.getBoundingClientRect(); const containerRect = document.getElementById('game-container').getBoundingClientRect(); const centerX = (rect.left - containerRect.left) + rect.width / 2; const centerY = (rect.top - containerRect.top) + rect.height / 2; for (let i = 0; i < 10; i++) { const p = document.createElement('div'); p.classList.add('particle'); p.style.backgroundColor = color; p.style.width = Math.random() * 10 + 5 + 'px'; p.style.height = p.style.width; p.style.left = centerX + 'px'; p.style.top = centerY + 'px'; // Náhodný směr const angle = Math.random() * Math.PI * 2; const velocity = Math.random() * 50 + 20; const tx = Math.cos(angle) * velocity; const ty = Math.sin(angle) * velocity; p.style.setProperty('--tx', `${tx}px`); p.style.setProperty('--ty', `${ty}px`); // Upravit animaci aby použila translate p.style.animation = 'flyout 0.8s forwards'; document.getElementById('particles').appendChild(p); setTimeout(() => p.remove(), 800); } } // Add keyframes for flyout dynamically const styleSheet = document.createElement("style"); styleSheet.innerText = ` @keyframes flyout { 0% { transform: translate(0, 0) scale(1); opacity: 1; } 100% { transform: translate(var(--tx), var(--ty)) scale(0); opacity: 0; } }`; document.head.appendChild(styleSheet); // --- Ukládání / Načítání --- function loadGlobalStats() { const saved = localStorage.getItem('mathFortressStats'); if (saved) { const parsed = JSON.parse(saved); state.stats.maxWave = parsed.maxWave || 0; state.stats.gamesPlayed = parsed.gamesPlayed || 0; state.stats.totalCoins = parsed.totalCoins || 0; } } function saveGlobalStats() { localStorage.setItem('mathFortressStats', JSON.stringify({ maxWave: state.stats.maxWave, gamesPlayed: state.stats.gamesPlayed, totalCoins: state.stats.totalCoins })); } function updateGlobalStatsUI() { els.globalBestWave.textContent = state.stats.maxWave; els.globalGamesPlayed.textContent = state.stats.gamesPlayed; els.globalTotalCoins.textContent = state.stats.totalCoins; } // Spuštění po načtení window.onload = init;