/** * =============================================== * PEXESO.JS - LOGOPEDICKÁ PEXESO HRA S LEVELMI * =============================================== * * Hlavný súbor pre pexeso hru podporujúcu: * - Level systém s načítavaním z levels.js * - Singleplayer režim pre levely * - Multiplayer režim pre custom hry (až 4 hráči) * - Speech recognition a rečové cvičenia * - Stars hodnotenie system * - Progress tracking * * Autor: Adam Reňak * Verzia: 2.0 * Dátum: 2025 */ // ========================================== // GLOBÁLNE PREMENNÉ PRE PRELOADING // ========================================== let preloadedImages = {}; // Cache pre prednačítané obrázky (objekt kde kľúč je cesta k obrázku) let totalResources = 0; // Celkový počet zdrojov na načítanie let loadedResources = 0; // Počet už načítaných zdrojov let isPreloadingComplete = false; // Flag či je preloading úplne hotový /** * ================================================ * HLAVNÁ FUNKCIA PRE SPUSTENIE PRELOADINGU * ================================================ * Táto funkcia sa spúšťa pri načítaní stránky (DOMContentLoaded). * Postupne: * 1. Získa konfiguráciu levelu z URL * 2. Zbiera všetky obrázky na načítanie * 3. Načíta všetky obrázky paralelne * 4. Aktualizuje progress bar * 5. Skryje loading screen a spustí hru */ /** * ================================================ * ZÍSKANIE KONFIGURÁCIE LEVELU Z URL * ================================================ * Funkcia číta URL parametre a načíta konfiguráciu levelu. * * URL parametre: * - worldId: ID sveta (napr. 'world_r') * - levelId: ID levelu (napr. 'level_r_1') * - custom: či je to custom hra (true/false) * * @returns {Object} Konfigurácia levelu alebo fallback konfigurácia */ function getLevelConfigFromURL() { const urlParams = new URLSearchParams(window.location.search); const worldId = urlParams.get('worldId') || urlParams.get('world'); const levelId = urlParams.get('levelId') || urlParams.get('level'); const isCustom = urlParams.get('custom') === 'true'; console.log('🔍 URL parametre:', { worldId, levelId, isCustom }); // Ak máme levelId, pokús sa načítať konfiguráciu z levels.js if (levelId && typeof window.getLevelConfig === 'function') { const config = window.getLevelConfig(levelId); if (config) { console.log('📋 Načítaná level konfigurácia:', config); return config; } } // Ak je to custom hra, vráť custom konfiguráciu if (isCustom) { console.log('🎨 Custom hra detekovaná'); return getCustomGameConfig(urlParams); } // Fallback konfigurácia (ak nič iné nefunguje) console.warn('⚠️ Používam fallback konfiguráciu'); return { id: 'fallback', worldId: worldId || 'world_r', words: ['rak', 'ryba', 'ruka', 'ruža', 'raja', 'rožky'], gameConfig: { pairs: 6, // Počet párov kariet timeLimit: null // Bez časového limitu } }; } /** * ================================================ * ZBER VŠETKÝCH OBRÁZKOV PRE PEXESO * ================================================ * Funkcia zbiera všetky obrázky ktoré sa používajú v pexeso hre: * 1. Obrázky slov (karty) * 2. Rub karty (baník) * 3. UI elementy (menu, hviezdy, ikony) * 4. Pozadia svetov * 5. Základné pozadie * * @param {Object} levelConfig - Konfigurácia levelu * @returns {Array} Pole ciest k obrázkom */ function collectAllPexesoImages(levelConfig) { const images = []; console.log('📦 Zberam obrázky pre pexeso...'); // ========================================== // 1. OBRÁZKY SLOV (KARTY) - najdôležitejšie! // ========================================== if (levelConfig && levelConfig.words && Array.isArray(levelConfig.words)) { levelConfig.words.forEach(word => { const imagePath = `images/slova/${word}.png`; images.push(imagePath); }); console.log(` ✅ Pridaných ${levelConfig.words.length} obrázkov slov (kariet)`); } else { console.warn(' ⚠️ Žiadne slová v levelConfig!'); } // ========================================== // 2. RUB KARTY (BANÍK LOGO) // ========================================== images.push('images/banik.png'); console.log(' ✅ Pridaný rub karty (banik.png)'); // ========================================== // 3. UI ELEMENTY // ========================================== images.push( 'images/menubutton.png', // Menu tlačidlo 'images/star_active.png', // Aktívna hviezda 'images/star_inactive.png', // Neaktívna hviezda 'images/banik.ico' // Ikona ); console.log(' ✅ Pridané UI elementy (menu button, hviezdy, ikona)'); // ========================================== // 4. POZADIE SVETA (ak existuje) // ========================================== if (levelConfig && levelConfig.worldId) { // Mapa worldId -> cesta k obrázku pozadia const worldBackgrounds = { 'world_r': 'images/worlds/world_r.png', 'world_l': 'images/worlds/world_l.png', 'world_s': 'images/worlds/world_s.png', 'world_z': 'images/worlds/world_z.jpg', 'world_c': 'images/worlds/world_c.png', 'world_š': 'images/worlds/world_sh.png', 'world_ž': 'images/worlds/world_zh.png', 'world_č': 'images/worlds/world_ch.png', 'world_d': 'images/worlds/world_d.png', 'world_t': 'images/worlds/world_t.png', 'world_n': 'images/worlds/world_n.png', 'world_k': 'images/worlds/world_k.png', 'world_g': 'images/worlds/world_g.png' }; const worldBg = worldBackgrounds[levelConfig.worldId]; if (worldBg) { images.push(worldBg); console.log(` ✅ Pridané pozadie sveta: ${levelConfig.worldId}`); } } // ========================================== // 5. ZÁKLADNÉ POZADIE // ========================================== images.push('images/pozadie.jpg'); console.log(' ✅ Pridané základné pozadie'); // ========================================== // 6. CURSOR OBRÁZKY // ========================================== images.push( 'images/cursor.png', 'images/active_cursor4.png' ); console.log(' ✅ Pridané cursor obrázky'); console.log(`📦 Celkovo zozbieraných ${images.length} obrázkov`); return images; } /** * ================================================ * NAČÍTANIE JEDNÉHO OBRÁZKA * ================================================ * Funkcia načíta jeden obrázok pomocou Promise. * Ak je obrázok už načítaný (v cache), vráti ho. * * @param {string} imagePath - Cesta k obrázku * @returns {Promise} Promise ktorý sa resolves keď je obrázok načítaný */ function preloadImage(imagePath) { return new Promise((resolve) => { // Ak už je obrázok načítaný, vráť ho z cache if (preloadedImages[imagePath]) { updateProgress(); // Aktualizuj progress bar resolve(preloadedImages[imagePath]); return; } // Vytvor nový Image objekt const img = new Image(); // Pri úspešnom načítaní img.onload = () => { preloadedImages[imagePath] = img; // Ulož do cache updateProgress(); // Aktualizuj progress bar console.log(`✅ Načítané: ${imagePath}`); resolve(img); }; // Pri chybe načítania (obrázok neexistuje) img.onerror = () => { console.warn(`⚠️ Chyba pri načítaní: ${imagePath}`); updateProgress(); // Aktualizuj progress bar aj pri chybe resolve(null); // Pokračuj ďalej (nechaj hru pokračovať) }; // Spusti načítanie obrázka img.src = imagePath; }); } /** * ================================================ * AKTUALIZÁCIA PROGRESS BARU * ================================================ * Funkcia aktualizuje progress bar podľa počtu načítaných zdrojov. * Zobrazuje: * - Percentuálny ukazovateľ (0-100%) * - Počet načítaných zdrojov (napr. "15/20 zdrojov") * - Textovú správu ("Načítavam obrázky..." / "Hotovo!") */ function updateProgress() { loadedResources++; // Zvýš počet načítaných zdrojov // Vypočítaj percentuálny pokrok (zaokrúhlený na celé číslo) const percentage = Math.round((loadedResources / totalResources) * 100); // ========================================== // Aktualizuj šírku progress baru (žltý pásik) // ========================================== const progressFill = document.getElementById('loading-progress-fill'); if (progressFill) { progressFill.style.width = `${percentage}%`; } // ========================================== // Aktualizuj text s percentami (napr. "75%") // ========================================== const progressPercentage = document.getElementById('loading-progress-percentage'); if (progressPercentage) { progressPercentage.textContent = `${percentage}%`; } // ========================================== // Aktualizuj text s počtom zdrojov (napr. "15/20 zdrojov") // ========================================== const progressDetails = document.getElementById('loading-progress-details'); if (progressDetails) { progressDetails.textContent = `${loadedResources}/${totalResources} zdrojov`; } // ========================================== // Aktualizuj hlavnú loading správu // ========================================== const loadingMessage = document.getElementById('loading-message'); if (loadingMessage) { if (percentage < 100) { loadingMessage.textContent = 'Načítavam obrázky...'; } else { loadingMessage.textContent = 'Hotovo! Spúšťam hru...'; } } // Log do konzoly pre debugging console.log(`📊 Progress: ${percentage}% (${loadedResources}/${totalResources})`); } /** * ================================================ * SKRYTIE LOADING SCREENU S ANIMÁCIOU * ================================================ * Funkcia skryje loading screen s fade-out animáciou. * Najprv nastaví opacity na 0 (fade out), potom skryje element. */ function hideLoadingScreen() { const loadingScreen = document.getElementById('loading-screen'); if (loadingScreen) { // Fade out animácia (opacity -> 0) loadingScreen.style.opacity = '0'; // Po 500ms úplne skry element (display: none) setTimeout(() => { loadingScreen.style.display = 'none'; }, 500); } console.log('👋 Loading screen skrytý, hra pripravená!'); } /** * ================================================ * POMOCNÉ FUNKCIE PRE OSTATNÉ ČASTI KÓDU * ================================================ */ /** * Získanie prednačítaného obrázka z cache * @param {string} imagePath - Cesta k obrázku * @returns {Image|null} Načítaný obrázok alebo null */ function getPreloadedImage(imagePath) { return preloadedImages[imagePath] || null; } /** * Custom game konfigurácia (pre multiplayer alebo custom hry) * @param {URLSearchParams} urlParams - URL parametre * @returns {Object} Custom konfigurácia */ function getCustomGameConfig(urlParams) { // Tu môžeš pridať logiku pre custom hry // Napríklad načítanie slov z URL parametrov const wordsParam = urlParams.get('words'); let words = ['rak', 'ryba', 'ruka', 'ruža']; // Default slová if (wordsParam) { try { // Skús parsovať ako JSON (pre formát: ["slovo1","slovo2"]) words = JSON.parse(decodeURIComponent(wordsParam)); } catch (e) { // Ak JSON parse zlyhá, skús split pomocou čiarky (pre formát: slovo1,slovo2) words = wordsParam.split(',').map(w => w.trim()); } } return { id: 'custom', worldId: 'custom', words: words, gameConfig: { pairs: Math.min(words.length, parseInt(urlParams.get('pairs')) || 6), timeLimit: parseInt(urlParams.get('timeLimit')) || null } }; } // ========================================== // EXPORT PRE OSTATNÉ ČASTI KÓDU // ========================================== // Sprístupní funkcie globálne aby sa dali použiť v iných častiach kódu if (typeof window !== 'undefined') { window.preloadedImages = preloadedImages; window.getPreloadedImage = getPreloadedImage; window.isPreloadingComplete = isPreloadingComplete; } // ========================================== // EVENT LISTENER - SPUSTENIE PRI NAČÍTANÍ STRÁNKY // ========================================== // DÔLEŽITÉ: Tento kód sa musí spustiť PRED ostatným kódom v pexeso.js! // Preto ho dávame hneď na začiatok súboru. document.addEventListener('DOMContentLoaded', async function() { console.log('🎮 Inicializujem pexeso hru...'); // 🔊 Inicializácia zvukových efektov initializeSounds(); try { // 1. Spustenie preloadingu console.log('🎮 Spúšťam preloading pre pexeso...'); // Získaj konfiguráciu levelu z URL parametrov const levelConfig = getLevelConfigFromURL(); console.log('📋 Level config:', levelConfig); // Zisti všetky obrázky ktoré treba načítať const imagesToLoad = collectAllPexesoImages(levelConfig); totalResources = imagesToLoad.length; console.log(`📦 Celkovo načítavam ${totalResources} obrázkov...`); // Načítaj všetky obrázky paralelne const promises = imagesToLoad.map(imagePath => preloadImage(imagePath)); await Promise.all(promises); console.log('✅ Všetky obrázky úspešne načítané!'); isPreloadingComplete = true; // 2. Po preloadingu - skrytie loading screen a inicializácia hry setTimeout(() => { hideLoadingScreen(); // 3. Získanie parametrov z URL a inicializácia správnej hry const params = getURLParameters(); if (params.custom) { // Custom hra s vlastnými parametrami initCustomGame(params); } else if (params.worldId && params.levelId) { // Level hra z worlds menu initLevelGame(params.worldId, params.levelId); } else { // Fallback - ukážková hra initDemoGame(); } // 4. Nastavenie speech recognition setupSpeechRecognition(); // 5. Nastavenie event listenerov setupEventListeners(); }, 500); } catch (error) { console.error('❌ Chyba pri preloadingu:', error); // Aj pri chybe spusti hru (aby hra fungovala aj s chybami) hideLoadingScreen(); // Získanie parametrov z URL const params = getURLParameters(); if (params.custom) { initCustomGame(params); } else if (params.worldId && params.levelId) { initLevelGame(params.worldId, params.levelId); } else { initDemoGame(); } setupSpeechRecognition(); setupEventListeners(); } }); // ========================================== // GLOBÁLNE PREMENNÉ A KONFIGURÁCIA // ========================================== let currentLevel = null; // Aktuálny level config let gameState = null; // Stav hry let gameCards = []; // Pole všetkých kariet let flippedCards = []; // Práve otočené karty let matchedPairs = 0; // Počet nájdených párov let totalPairs = 0; // Celkový počet párov let gameTime = 0; // Herný čas v sekundách let gameAttempts = 0; // Počet pokusov let timerInterval = null; // Interval pre časovač let isProcessingMatch = false; // Zamedzenie viacnásobného klikania let speechAttempts = 0; // Pokusy na speech recognition let maxSpeechAttempts = 3; // Maximálny počet pokusov let correctSpeechCount = 0; // Počet správne vyslovenych slov // Multiplayer premenné let players = []; // Pole hráčov (pre custom hry) let currentPlayerIndex = 0; // Index aktuálneho hráča let isMultiplayerMode = false; // Či je multiplayer režim // Speech recognition let recognition = null; // Speech recognition objekt // ========================================== // 🔊 ZVUKOVÝ SYSTÉM // ========================================== // Zvukové efekty pre pexeso hru let sounds = { flipCard: null, // Otočenie karty - flipcard.mp3 matchFound: null, // Nájdený pár - collectpoints.mp3 matchWrong: null, // Nenájdený pár - incorrect.mp3 speechCorrect: null, // Správna výslovnosť - spravne.mp3 speechWrong: null, // Nesprávna výslovnosť - zle.mp3 gameEnd: null, // Koniec hry/výhra - winfantasia.mp3 mouseClick: null, // Kliknutie myšou - mouseclick.mp3 tickTock: null // Tick-tock posledných 10s - clock-tic-tac.mp3 }; /** * Inicializácia zvukových efektov pomocou Howler.js */ function initializeSounds() { console.log('🔊 Inicializujem zvukové efekty...'); try { // Kontrola či je Howler.js načítaný if (typeof Howl === 'undefined') { console.warn('⚠️ Howler.js nie je načítaný! Zvuky nebudú fungovať.'); return; } // Otočenie karty sounds.flipCard = new Howl({ src: ['zvuky/effects/flipcard.mp3'], volume: 0.5, onloaderror: () => console.warn('⚠️ Nepodarilo sa načítať: flipcard.mp3') }); // Nájdený pár sounds.matchFound = new Howl({ src: ['zvuky/effects/collectpoints.mp3'], volume: 0.5, onloaderror: () => console.warn('⚠️ Nepodarilo sa načítať: collectpoints.mp3') }); // Nenájdený pár sounds.matchWrong = new Howl({ src: ['zvuky/effects/incorrect.mp3'], volume: 0.5, onloaderror: () => console.warn('⚠️ Nepodarilo sa načítať: incorrect.mp3') }); // Správna výslovnosť sounds.speechCorrect = new Howl({ src: ['zvuky/effects/spravne.mp3'], volume: 0.5, onloaderror: () => console.warn('⚠️ Nepodarilo sa načítať: spravne.mp3') }); // Nesprávna výslovnosť sounds.speechWrong = new Howl({ src: ['zvuky/effects/zle.mp3'], volume: 0.5, onloaderror: () => console.warn('⚠️ Nepodarilo sa načítať: zle.mp3') }); // Koniec hry / výhra sounds.gameEnd = new Howl({ src: ['zvuky/effects/winfantasia.mp3'], volume: 0.5, onloaderror: () => console.warn('⚠️ Nepodarilo sa načítať: winfantasia.mp3') }); // Kliknutie myšou sounds.mouseClick = new Howl({ src: ['zvuky/effects/mouseclick.mp3'], volume: 0.1, onloaderror: () => console.warn('⚠️ Nepodarilo sa načítať: mouseclick.mp3') }); // Tick-tock (posledných 10 sekúnd) sounds.tickTock = new Howl({ src: ['zvuky/effects/timer-20.mp3'], volume: 0.4, loop: true, // Opakuje sa kým sa nezastaví onloaderror: () => console.warn('⚠️ Nepodarilo sa načítať: clock-tic-tac.mp3') }); console.log('✅ Zvukové efekty načítané'); } catch (error) { console.error('❌ Chyba pri inicializácii zvukov:', error); } } /** * Prehranie zvukového efektu * @param {string} soundKey - Kľúč zvuku (napr. 'flipCard') */ function playSound(soundKey) { try { const sound = sounds[soundKey]; if (!sound) { console.warn(`⚠️ Zvuk "${soundKey}" neexistuje`); return; } // Pre tick-tock používame play/stop, nie restart if (soundKey === 'tickTock') { if (!sound.playing()) { sound.play(); } } else { // Pre ostatné zvuky - zastavíme a znova prehrávame if (sound.playing()) { sound.stop(); } sound.play(); } } catch (error) { console.warn(`⚠️ Chyba pri prehrávaní zvuku "${soundKey}":`, error); } } /** * Zastavenie zvukového efektu * @param {string} soundKey - Kľúč zvuku (napr. 'tickTock') */ function stopSound(soundKey) { try { const sound = sounds[soundKey]; if (!sound) { return; } if (sound.playing()) { sound.stop(); } } catch (error) { console.warn(`⚠️ Chyba pri zastavení zvuku "${soundKey}":`, error); } } // ========================================== // INICIALIZÁCIA HRY // ========================================== /** * Hlavná inicializačná funkcia - spúšťa sa pri načítaní stránky */ /** * Získanie parametrov z URL * @returns {Object} Objekt s URL parametrami */ function getURLParameters() { const urlParams = new URLSearchParams(window.location.search); return { custom: urlParams.get('custom') === 'true', worldId: urlParams.get('worldId'), levelId: urlParams.get('levelId'), words: urlParams.get('words'), players: urlParams.get('players'), pairs: parseInt(urlParams.get('pairs')) || 10, timeLimit: parseInt(urlParams.get('timeLimit')) || null }; } // ========================================== // INICIALIZÁCIA RÔZNYCH TYPOV HIER // ========================================== /** * Inicializácia level hry z worlds menu * @param {string} worldId - ID sveta * @param {string} levelId - ID levelu */ function initLevelGame(worldId, levelId) { console.log(`🌍 Načítavam level: ${worldId} - ${levelId}`); // Získanie level configu if (typeof getLevelConfig === 'function') { currentLevel = getLevelConfig(levelId); } if (!currentLevel) { console.error('❌ Level sa nepodarilo načítať!'); showErrorMessage('Level sa nepodarilo načítať. Vraciam sa na menu.'); setTimeout(() => { window.location.href = 'worldsmenu.html'; }, 3000); return; } // Nastavenie hry pre singleplayer level isMultiplayerMode = false; players = [{ name: 'Hráč', score: 0 }]; // Nastavenie počtu párov z level configu if (currentLevel.gameConfig && currentLevel.gameConfig.pairs) { totalPairs = currentLevel.gameConfig.pairs; } else { totalPairs = 8; // Defaultná hodnota } // Inicializácia hry initGame(); } /** * Inicializácia custom hry s vlastnými parametrami * @param {Object} params - Parametre z URL */ function initCustomGame(params) { console.log('🎨 Načítavam custom hru...'); // Parsovanie slov a hráčov z URL let customWords = []; let customPlayers = []; try { if (params.words) { customWords = JSON.parse(decodeURIComponent(params.words)); } if (params.players) { customPlayers = JSON.parse(decodeURIComponent(params.players)); } } catch (error) { console.error('❌ Chyba pri parsovaní custom parametrov:', error); } // Vytvorenie fake level configu pre custom hru currentLevel = { id: 'custom', name: 'Custom hra', words: customWords.length > 0 ? customWords : ['rak', 'ryba', 'ruka', 'ráno'], // fallback gameConfig: { pairs: params.pairs || Math.min(customWords.length, 8) }, timeLimit: params.timeLimit }; // Nastavenie multiplayer režimu if (customPlayers.length > 1) { isMultiplayerMode = true; players = customPlayers; } else { isMultiplayerMode = false; players = [{ name: customPlayers[0]?.name || 'Hráč', score: 0 }]; } totalPairs = currentLevel.gameConfig.pairs; // Inicializácia hry initGame(); } /** * Inicializácia demo hry (fallback) */ function initDemoGame() { console.log('🎯 Spúšťam demo hru...'); // Vytvorenie demo level configu currentLevel = { id: 'demo', name: 'Demo hra', words: ['rak', 'ryba', 'ruka', 'rakva', 'rádio', 'krava', 'drak', 'zebra', 'tiger', 'traktor'], gameConfig: { pairs: 8 }, timeLimit: null }; isMultiplayerMode = false; players = [{ name: 'Hráč', score: 0 }]; totalPairs = 8; initGame(); } // ========================================== // HLAVNÁ HERNÁ LOGIKA // ========================================== /** * Hlavná inicializačná funkcia hry */ function initGame() { console.log('🚀 Inicializujem hernú logiku...'); // Reset stavu hry resetGameState(); // Aktualizácia UI updateTopPanel(); updateSidePanel(); // Generovanie herného poľa generateGameBoard(); // Spustenie časovača startGameTimer(); console.log('✅ Hra je pripravená!'); } /** * Reset všetkých herných premenných */ function resetGameState() { gameCards = []; flippedCards = []; matchedPairs = 0; gameTime = 0; gameAttempts = 0; isProcessingMatch = false; speechAttempts = 0; correctSpeechCount = 0; currentPlayerIndex = 0; // Reset skóre hráčov players.forEach(player => player.score = 0); // Zastavenie existujúceho časovača if (timerInterval) { clearInterval(timerInterval); timerInterval = null; } } /** * Aktualizácia horného panelu s časom a pokusmi */ function updateTopPanel() { // Aktualizácia času const timeElement = document.getElementById('game-time'); if (timeElement) { const minutes = Math.floor(gameTime / 60); const seconds = gameTime % 60; timeElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } // Aktualizácia pokusov const attemptsElement = document.getElementById('game-attempts'); if (attemptsElement) { attemptsElement.textContent = gameAttempts.toString(); } } /** * Aktualizácia bočného panelu s hráčmi */ function updateSidePanel() { const sidePanels = document.querySelectorAll('.players-panel'); sidePanels.forEach((panel, index) => { if (index < players.length) { // Zobrazenie hráča panel.style.display = 'flex'; const nameElement = panel.querySelector('.name h1'); const scoreElement = panel.querySelector('.score span'); if (nameElement) nameElement.textContent = players[index].name; if (scoreElement) scoreElement.textContent = players[index].score.toString(); // Zvýraznenie aktuálneho hráča if (isMultiplayerMode && index === currentPlayerIndex) { panel.style.border = '4px solid #ECCF17'; panel.style.boxShadow = '0 0 20px 2px #ECCF17 inset'; } else { panel.style.border = '4px solid #AC3F0B'; panel.style.boxShadow = 'none'; } } else { // Skrytie nepotrebných panelov panel.style.display = 'none'; } }); } // ========================================== // GENEROVANIE HERNÉHO POĽA // ========================================== /** * Hlavná funkcia pre generovanie herného poľa s kartami */ function generateGameBoard() { console.log('🃏 Generujem herné pole...'); // Výber slov pre hru const selectedWords = selectWordsForGame(); // Vytvorenie párov kariet gameCards = createCardPairs(selectedWords); // Zamichanie kariet shuffleArray(gameCards); // Vykreslenie kariet na obrazovku renderGameBoard(); console.log(`✅ Vygenerované ${gameCards.length} kariet (${totalPairs} párov)`); } /** * Výber slov pre aktuálnu hru * @returns {Array} Pole vybraných slov */ function selectWordsForGame() { let availableWords = currentLevel.words || []; // Ak nemáme dostatok slov, pridáme duplicity while (availableWords.length < totalPairs) { availableWords = [...availableWords, ...currentLevel.words]; } // Náhodný výber požadovaného počtu slov const shuffled = [...availableWords]; shuffleArray(shuffled); return shuffled.slice(0, totalPairs); } /** * Vytvorenie párov kariet z vybraných slov * @param {Array} words - Pole slov * @returns {Array} Pole objektov kariet */ function createCardPairs(words) { const cards = []; let cardId = 0; // Pre každé slovo vytvoríme 2 karty (pár) words.forEach(word => { // Prvá karta páru cards.push({ id: cardId++, word: word, imagePath: `images/slova/${word}.png`, isFlipped: false, isMatched: false, pairId: word // Identifikátor páru }); // Druhá karta páru cards.push({ id: cardId++, word: word, imagePath: `images/slova/${word}.png`, isFlipped: false, isMatched: false, pairId: word }); }); return cards; } /** * Vykreslenie herného poľa do DOM */ function renderGameBoard() { const gameCanvas = document.getElementById('gameCanvas'); if (!gameCanvas) { console.error('❌ gameCanvas element nenájdený!'); return; } // Vyčistenie existujúceho obsahu gameCanvas.innerHTML = ''; // Pridanie CSS triedy pre správnu veľkosť kariet gameCanvas.className = `cards-${gameCards.length}`; // Vytvorenie a pridanie kariet gameCards.forEach((cardData, index) => { const cardElement = createCardElement(cardData, index); gameCanvas.appendChild(cardElement); }); } /** * Vytvorenie HTML elementu pre jednu kartu * @param {Object} cardData - Dáta karty * @param {number} index - Index karty * @returns {HTMLElement} HTML element karty */ function createCardElement(cardData, index) { // Hlavný kontajner karty const cardContainer = document.createElement('div'); cardContainer.className = 'card-container'; cardContainer.dataset.cardId = cardData.id; cardContainer.dataset.pairId = cardData.pairId; // Obrázok karty - na začiatku baník, po otočení slovo const cardImage = document.createElement('img'); cardImage.src = 'images/banik.png'; // Zadná strana - baník cardImage.alt = 'Pexeso karta'; cardImage.className = 'card-image'; cardImage.dataset.wordImage = cardData.imagePath; // Uložíme cestu k slovu // Text karty const cardText = document.createElement('span'); cardText.textContent = 'PEXESO'; cardText.className = 'card-text'; cardText.dataset.wordText = cardData.word; // Uložíme text slova // Pridanie obsahu do kontajnera cardContainer.appendChild(cardImage); cardContainer.appendChild(cardText); // Event listener pre kliknutie na kartu cardContainer.addEventListener('click', () => handleCardClick(cardData.id)); return cardContainer; } // ========================================== // HERNÉ MECHANIZMY - OTÁČANIE KARIET // ========================================== /** * Spracovanie kliknutia na kartu * @param {number} cardId - ID kliknutej karty */ function handleCardClick(cardId) { console.log(`🖱️ Klik na kartu ID: ${cardId}`); // Kontroly pred otočením karty if (isProcessingMatch) { console.log('⏳ Spracovávam match, ignorujem klik'); return; } const card = gameCards.find(c => c.id === cardId); if (!card) { console.error('❌ Karta nebola nájdená!'); return; } if (card.isFlipped || card.isMatched) { console.log('ℹ️ Karta už je otočená alebo nájdená'); return; } if (flippedCards.length >= 2) { console.log('ℹ️ Už sú otočené 2 karty'); return; } // Otočenie karty flipCard(cardId); // Kontrola na zhodu ak sú otočené 2 karty if (flippedCards.length === 2) { gameAttempts++; updateTopPanel(); setTimeout(() => { checkCardMatch(); }, 600); // Skrátené z 1000ms na 600ms } } /** * Otočenie konkrétnej karty * @param {number} cardId - ID karty na otočenie */ function flipCard(cardId) { const card = gameCards.find(c => c.id === cardId); const cardElement = document.querySelector(`[data-card-id="${cardId}"]`); if (!card || !cardElement) return; // Zmena stavu karty card.isFlipped = true; flippedCards.push(card); // 🔊 Zvuk otočenia karty playSound('flipCard'); // Animácia otočenia karty (bez problémového "fix") cardElement.classList.add('flipping'); setTimeout(() => { // Zmena obrázka z baníka na slovo - použijeme prednačítaný obrázok z cache const image = cardElement.querySelector('.card-image'); const text = cardElement.querySelector('.card-text'); if (image) { const wordImagePath = image.dataset.wordImage; // Ak existuje prednačítaný obrázok v cache, použijeme jeho src if (preloadedImages[wordImagePath]) { image.src = preloadedImages[wordImagePath].src; } else { // Fallback - ak z nejakého dôvodu nie je obrázok v cache image.src = wordImagePath; } } if (text) text.textContent = text.dataset.wordText; // Zmena na text slova // Pridanie CSS triedy pre otočenú kartu cardElement.classList.add('flipped'); cardElement.classList.remove('flipping'); }, 150); // Polovica animácie console.log(`✅ Karta otočená: ${card.word}`); } /** * Kontrola zhody medzi dvoma otočenými kartami */ function checkCardMatch() { if (flippedCards.length !== 2) return; isProcessingMatch = true; const [card1, card2] = flippedCards; console.log(`🔍 Kontrolujem zhodu: ${card1.word} vs ${card2.word}`); if (card1.pairId === card2.pairId) { // Zhoda nájdená! handleMatchFound(card1, card2); } else { // Zhoda nenájdená handleMatchNotFound(card1, card2); } } /** * Spracovanie nájdenej zhody * @param {Object} card1 - Prvá karta páru * @param {Object} card2 - Druhá karta páru */ function handleMatchFound(card1, card2) { console.log(`🎉 Pár nájdený: ${card1.word}!`); // 🔊 Zvuk nájdeného páru playSound('matchFound'); // Označenie kariet ako nájdených card1.isMatched = true; card2.isMatched = true; matchedPairs++; // Vizuálne označenie nájdených kariet (bez opacity - to príde až po správnej odpovedi) const card1Element = document.querySelector(`[data-card-id="${card1.id}"]`); const card2Element = document.querySelector(`[data-card-id="${card2.id}"]`); if (card1Element) { card1Element.classList.add('matched'); } if (card2Element) { card2Element.classList.add('matched'); } // BOD SA NEPRIPOČÍTAVA! Pripočíta sa až po správnej odpovedi v completeSpeechExercise() // Aktualizácia zobrazenia side panelu (bez zmeny skóre) updateSidePanel(); // Spustenie rečového cvičenia startSpeechExercise(card1.word); } /** * Spracovanie nenájdenej zhody * @param {Object} card1 - Prvá karta * @param {Object} card2 - Druhá karta */ function handleMatchNotFound(card1, card2) { console.log(`❌ Pár nenájdený: ${card1.word} vs ${card2.word}`); // 🔊 Zvuk nenájdeného páru playSound('matchWrong'); // Pridanie animácie pre nesprávne páry const card1Element = document.querySelector(`[data-card-id="${card1.id}"]`); const card2Element = document.querySelector(`[data-card-id="${card2.id}"]`); if (card1Element) { card1Element.classList.add('wrong-match'); setTimeout(() => card1Element.classList.remove('wrong-match'), 500); } if (card2Element) { card2Element.classList.add('wrong-match'); setTimeout(() => card2Element.classList.remove('wrong-match'), 500); } // Otočenie kariet späť po kratšom čase setTimeout(() => { flipCardBack(card1.id); flipCardBack(card2.id); // Prepnutie hráča v multiplayer režime if (isMultiplayerMode) { switchToNextPlayer(); } resetFlippedCards(); }, 1000); // Trochu dlhšie kvôli animácii wrong-match } /** * Otočenie karty späť (zakrytie) * @param {number} cardId - ID karty */ function flipCardBack(cardId) { const card = gameCards.find(c => c.id === cardId); const cardElement = document.querySelector(`[data-card-id="${cardId}"]`); if (!card || !cardElement) return; // Zmena stavu karty card.isFlipped = false; // Animácia otočenia karty späť cardElement.classList.add('flipping'); setTimeout(() => { // Zmena obrázka späť na baníka a textu na PEXESO const image = cardElement.querySelector('.card-image'); const text = cardElement.querySelector('.card-text'); if (image) image.src = 'images/banik.png'; // Zmena späť na baníka if (text) text.textContent = 'PEXESO'; // Zmena späť na PEXESO // Odstránenie CSS triedy cardElement.classList.remove('flipped'); cardElement.classList.remove('flipping'); }, 150); // Polovica animácie } /** * Reset zoznamu otočených kariet */ function resetFlippedCards() { flippedCards = []; isProcessingMatch = false; } /** * Prepnutie na ďalšieho hráča (multiplayer) */ function switchToNextPlayer() { if (!isMultiplayerMode) return; currentPlayerIndex = (currentPlayerIndex + 1) % players.length; updateSidePanel(); console.log(`🔄 Hráč na rade: ${players[currentPlayerIndex].name}`); } // ========================================== // REČOVÉ CVIČENIA // ========================================== /** * Spustenie rečového cvičenia pre dané slovo * @param {string} word - Slovo na precvičenie */ function startSpeechExercise(word) { console.log(`🎤 Spúšťam rečové cvičenie pre slovo: ${word}`); // ⏰ Zastavenie časovača počas rečového cvičenia stopGameTimer(); // Zobrazenie modalu pre cvičenie showSpeechExerciseModal(word); // Reset pokusov speechAttempts = 0; } /** * Zobrazenie modalu pre rečové cvičenie * @param {string} word - Slovo na precvičenie */ function showSpeechExerciseModal(word) { const modal = document.getElementById('cvicenie'); const wordDisplay = document.getElementById('word-display'); const image = document.getElementById('cvicenie-image'); const button = document.getElementById('rozpoznanie'); if (!modal || !wordDisplay || !image || !button) { console.error('❌ Modal elementy pre cvičenie nenájdené!'); completeSpeechExercise(true); // Pokračuj bez cvičenia return; } // Nastavenie obsahu modalu wordDisplay.textContent = word.toUpperCase(); image.src = `images/slova/${word}.png`; image.alt = word; // Zobrazenie modalu modal.style.display = 'block'; // Event listener pre tlačidlo button.onclick = () => startListening(word); } /** * Skrytie modalu pre rečové cvičenie */ function hideSpeechExerciseModal() { const modal = document.getElementById('cvicenie'); if (modal) { modal.style.display = 'none'; } } // ========================================== // SPEECH RECOGNITION // ========================================== /** * Nastavenie speech recognition */ function setupSpeechRecognition() { if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognition = new SpeechRecognition(); recognition.continuous = false; recognition.interimResults = false; recognition.lang = 'sk-SK'; recognition.onstart = handleRecognitionStart; recognition.onresult = handleRecognitionResult; recognition.onend = handleRecognitionEnd; recognition.onerror = handleRecognitionError; console.log('✅ Speech recognition nastavené'); } else { console.warn('⚠️ Speech recognition nie je podporované'); } } /** * Začiatok počúvania * @param {string} expectedWord - Očakávané slovo */ function startListening(expectedWord) { if (!recognition) { console.error('❌ Speech recognition nie je dostupné'); return; } console.log(`🎤 Začínam počúvať slovo: ${expectedWord}`); // Nastavenie očakávaného slova recognition.expectedWord = expectedWord; // Spustenie rozpoznávania try { recognition.start(); updateListeningButton(true); } catch (error) { console.error('❌ Chyba pri spustení rozpoznávania:', error); } } /** * Spracovanie začiatku rozpoznávania */ function handleRecognitionStart() { console.log('🎤 Rozpoznávanie spustené'); updateListeningButton(true); } /** * Spracovanie výsledku rozpoznávania * @param {SpeechRecognitionEvent} event - Event s výsledkom */ function handleRecognitionResult(event) { const result = event.results[0][0].transcript.toLowerCase().trim(); const expectedWord = recognition.expectedWord.toLowerCase(); console.log(`🗣️ Rozpoznané: "${result}", očakávané: "${expectedWord}"`); speechAttempts++; if (result.includes(expectedWord) || expectedWord.includes(result)) { // Správne vyslovené handleCorrectSpeech(); } else { // Nesprávne vyslovené handleIncorrectSpeech(result, expectedWord); } } /** * Spracovanie správnej výslovnosti */ function handleCorrectSpeech() { console.log('✅ Slovo správne vyslovené!'); correctSpeechCount++; // 🔊 Zvuk správnej výslovnosti playSound('speechCorrect'); showSpeechFeedback(true); setTimeout(() => { completeSpeechExercise(true); }, 1500); // Skrátené z 2000ms na 1500ms } /** * Spracovanie nesprávnej výslovnosti * @param {string} spokenWord - Vyslovené slovo * @param {string} expectedWord - Očakávané slovo */ function handleIncorrectSpeech(spokenWord, expectedWord) { console.log(`❌ Nesprávne vyslovené. Pokus ${speechAttempts}/${maxSpeechAttempts}`); // 🔊 Zvuk nesprávnej výslovnosti playSound('speechWrong'); showSpeechFeedback(false, speechAttempts, maxSpeechAttempts); if (speechAttempts >= maxSpeechAttempts) { // Vyčerpané pokusy setTimeout(() => { completeSpeechExercise(false); }, 1500); // Skrátené z 2000ms na 1500ms } else { // Ďalší pokus setTimeout(() => { hideSpeechFeedback(); }, 1500); // Skrátené z 2000ms na 1500ms } } /** * Spracovanie konca rozpoznávania */ function handleRecognitionEnd() { console.log('🎤 Rozpoznávanie ukončené'); updateListeningButton(false); } /** * Spracovanie chyby rozpoznávania * @param {SpeechRecognitionEvent} event - Error event */ function handleRecognitionError(event) { console.error('❌ Chyba rozpoznávania:', event.error); updateListeningButton(false); // Pri chybe ukončíme cvičenie setTimeout(() => { completeSpeechExercise(false); }, 1000); } /** * Aktualizácia tlačidla počúvania * @param {boolean} isListening - Či práve počúvame */ function updateListeningButton(isListening) { const button = document.getElementById('rozpoznanie'); const buttonContainer = button?.parentElement; if (!button || !buttonContainer) return; if (isListening) { buttonContainer.classList.add('recording'); button.querySelector('a').textContent = 'POČÚVAM...'; } else { buttonContainer.classList.remove('recording'); button.querySelector('a').textContent = 'HOVORIŤ'; } } /** * Zobrazenie feedback-u po rečovom cvičení * @param {boolean} isCorrect - Či bolo slovo správne vyslovené * @param {number} attempt - Aktuálny pokus (voliteľné) * @param {number} maxAttempts - Maximálny počet pokusov (voliteľné) */ function showSpeechFeedback(isCorrect, attempt = null, maxAttempts = null) { const vysledokDiv = document.getElementById('vysledok'); if (!vysledokDiv) return; let content = ''; if (isCorrect) { // Správna odpoveď content = `
Správne
VÝBORNE!
`; } else { // Nesprávna odpoveď const remainingAttempts = maxAttempts - attempt; content = `
Nesprávne
SKÚSTE ZNOVA ${remainingAttempts > 0 ? `
Zostávajú ${remainingAttempts} pokusy` : `
Žiadne pokusy nezostali` }
`; } vysledokDiv.innerHTML = content; vysledokDiv.classList.add('show'); } /** * Skrytie speech feedback-u */ function hideSpeechFeedback() { const vysledokDiv = document.getElementById('vysledok'); if (vysledokDiv) { vysledokDiv.classList.remove('show'); vysledokDiv.innerHTML = ''; } } /** * Dokončenie rečového cvičenia * @param {boolean} wasSuccessful - Či bolo cvičenie úspešné */ function completeSpeechExercise(wasSuccessful) { console.log(`🎯 Rečové cvičenie dokončené. Úspech: ${wasSuccessful}`); // Skrytie modalu hideSpeechExerciseModal(); hideSpeechFeedback(); // ⏰ Obnovenie časovača po rečovom cvičení resumeGameTimer(); // Ak bolo rečové cvičenie neúspešné, otočíme karty späť if (!wasSuccessful && flippedCards.length === 2) { // Otočenie kariet späť po neúspešnom rečovom cvičení setTimeout(() => { const [card1, card2] = flippedCards; // Zmeníme stav kariet card1.isMatched = false; card2.isMatched = false; matchedPairs--; // Vizuálne odstránenie matched triedy a opacity const card1Element = document.querySelector(`[data-card-id="${card1.id}"]`); const card2Element = document.querySelector(`[data-card-id="${card2.id}"]`); if (card1Element) { card1Element.classList.remove('matched'); card1Element.style.opacity = '1'; // Vrátenie plnej opacity } if (card2Element) { card2Element.classList.remove('matched'); card2Element.style.opacity = '1'; // Vrátenie plnej opacity } // Otočenie kariet späť flipCardBack(card1.id); flipCardBack(card2.id); // Prepnutie hráča ak je multiplayer if (isMultiplayerMode) { switchToNextPlayer(); } // BOD SA NEODČÍTAVA - nebol nikdy pridaný! // Aktualizácia zobrazenia side panelu updateSidePanel(); resetFlippedCards(); }, 500); } else if (wasSuccessful && flippedCards.length === 2) { // Pri úspešnom cvičení nastavíme opacity 0.5 pre nájdené páry const [card1, card2] = flippedCards; const card1Element = document.querySelector(`[data-card-id="${card1.id}"]`); const card2Element = document.querySelector(`[data-card-id="${card2.id}"]`); if (card1Element) { card1Element.style.opacity = '0.5'; // Zníženie opacity po správnej odpovedi } if (card2Element) { card2Element.style.opacity = '0.5'; // Zníženie opacity po správnej odpovedi } // TERAZ PRIDÁME BOD HRÁČOVI - len pri úspešnom cvičení! if (isMultiplayerMode) { players[currentPlayerIndex].score++; } else { // Pre single player mode if (players[0]) { players[0].score++; } } // Aktualizácia zobrazenia side panelu s novým skóre updateSidePanel(); // Reset flipped cards pre úspešné cvičenie resetFlippedCards(); } else { // Reset flipped cards resetFlippedCards(); } // Kontrola konca hry if (matchedPairs >= totalPairs && wasSuccessful) { setTimeout(() => { endGame(); }, 500); } } // ========================================== // ČASOVAČ A HERNÝ ČAS // ========================================== /** * Spustenie herného časovača */ function startGameTimer() { if (timerInterval) { clearInterval(timerInterval); } // Ak je nastavený časový limit, začíname odpočítavaním if (currentLevel.timeLimit) { gameTime = currentLevel.timeLimit; // Nastavíme čas na časový limit (odpočítavanie) } else { gameTime = 0; // Normálne počítanie od nuly } timerInterval = setInterval(() => { // Ak je nastavený časový limit, odpočítavame if (currentLevel.timeLimit) { gameTime--; // Odpočítavanie času od časového limitu k nule // Kontrola či čas vypršal if (gameTime <= 0) { gameTime = 0; // Zaistíme, že čas nebude záporný console.log('⏰ Čas vypršal!'); endGameTimeOut(); } } else { gameTime++; // Normálne počítanie času od nuly nahor } // 🔊 Tick-tock efekt posledných 10 sekúnd (len pri countdown) if (currentLevel.timeLimit && gameTime <= 10 && gameTime > 0) { playSound('tickTock'); } else { stopSound('tickTock'); } updateTopPanel(); // Aktualizácia zobrazenia času }, 1000); console.log('⏰ Časovač spustený'); } /** * Obnovenie herného časovača po pauze (bez resetovania času) */ function resumeGameTimer() { console.log('⏰ RESUME: Začínam obnovovať časovač...'); console.log('⏰ RESUME: currentLevel:', currentLevel); console.log('⏰ RESUME: currentLevel.timeLimit:', currentLevel?.timeLimit); console.log('⏰ RESUME: gameTime pred obnovením:', gameTime); console.log('⏰ RESUME: timerInterval pred clear:', timerInterval); if (timerInterval) { clearInterval(timerInterval); console.log('⏰ RESUME: Starý interval vyčistený'); } // Spustenie intervalu bez resetovania gameTime timerInterval = setInterval(() => { // Ak je nastavený časový limit, odpočítavame if (currentLevel.timeLimit) { gameTime--; // Odpočítavanie času od časového limitu k nule // Kontrola či čas vypršal if (gameTime <= 0) { gameTime = 0; // Zaistíme, že čas nebude záporný console.log('⏰ Čas vypršal!'); endGameTimeOut(); } } else { gameTime++; // Normálne počítanie času od nuly nahor } // 🔊 Tick-tock efekt posledných 10 sekúnd (len pri countdown) if (currentLevel.timeLimit && gameTime <= 10 && gameTime > 0) { playSound('tickTock'); } else { stopSound('tickTock'); } updateTopPanel(); // Aktualizácia zobrazenia času }, 1000); console.log('⏰ RESUME: Nový interval vytvorený, ID:', timerInterval); console.log('⏰ Časovač obnovený (bez resetovania času)'); } /** * Zastavenie herného časovača */ function stopGameTimer() { if (timerInterval) { clearInterval(timerInterval); timerInterval = null; console.log('⏰ Časovač zastavený'); } // 🔊 Zastavenie tick-tock zvuku stopSound('tickTock'); } // ========================================== // KONIEC HRY // ========================================== /** * Ukončenie hry - úspešné dokončenie */ function endGame() { console.log('🎉 Hra ukončená - víťazstvo!'); stopGameTimer(); // 🔊 Zvuk konca hry playSound('gameEnd'); // Výpočet výsledkov const gameResults = calculateGameResults(); // Uloženie progress (len pre level hry) if (!isMultiplayerMode && currentLevel.id !== 'demo' && currentLevel.id !== 'custom') { saveGameProgress(gameResults); } // Zobrazenie end game modalu showEndGameModal(gameResults); } /** * Ukončenie hry - vypršal čas */ function endGameTimeOut() { console.log('⏰ Hra ukončená - vypršal čas!'); stopGameTimer(); // 🔊 Zvuk konca hry (timeout) playSound('gameEnd'); const gameResults = calculateGameResults(); gameResults.isTimeOut = true; gameResults.stars = 0; // Žiadne hviezdy pri timeoutu showEndGameModal(gameResults); } /** * Výpočet výsledkov hry * @returns {Object} Objekt s výsledkami hry */ function calculateGameResults() { // OPRAVA: Prepočítanie času pre countdown časovač // Ak používame countdown (timeLimit existuje), musíme prepočítať uplynulý čas let actualTimeSpent = gameTime; if (currentLevel.timeLimit) { // gameTime obsahuje ostávajúci čas (napr. 20s ostáva) // actualTimeSpent musí obsahovať uplynulý čas (napr. 40s uplynulo) actualTimeSpent = currentLevel.timeLimit - gameTime; } // Ak nemá timeLimit, gameTime je už správne (počíta od 0 nahor) const results = { totalTime: actualTimeSpent, // Používame prepočítaný čas namiesto gameTime totalAttempts: gameAttempts, matchedPairs: matchedPairs, totalPairs: totalPairs, correctSpeechCount: correctSpeechCount, completionPercentage: Math.round((matchedPairs / totalPairs) * 100), stars: 0, isTimeOut: false, isVictory: matchedPairs >= totalPairs }; // Výpočet hviezd (podobne ako v game.js) if (results.isVictory) { results.stars = calculateStars(results); } return results; } /** * Výpočet počtu hviezd na základe výkonu * @param {Object} results - Výsledky hry * @returns {number} Počet hviezd (0-3) */ function calculateStars(results) { let stars = 1; // Základná hviezda za dokončenie // Hodnotenie LEN podľa rečových cvičení (správne vyslovených slov) const speechRatio = results.correctSpeechCount / results.matchedPairs; if (speechRatio >= 0.7) stars = 2; // 70%+ správne vyslovených = 2 hviezdy if (speechRatio >= 0.9) stars = 3; // 90%+ správne vyslovených = 3 hviezdy return stars; } /** * Uloženie herného pokroku * @param {Object} results - Výsledky hry */ function saveGameProgress(results) { if (typeof window.progressManager === 'undefined') { console.warn('⚠️ Progress manager nie je dostupný'); return; } try { window.progressManager.saveLevelProgress( currentLevel.worldId, currentLevel.id, results.stars, { time: results.totalTime, attempts: results.totalAttempts, speechCorrect: results.correctSpeechCount, completion: results.completionPercentage } ); console.log('✅ Progress uložený'); } catch (error) { console.error('❌ Chyba pri ukladaní progress:', error); } } /** * Zobrazenie end game modalu s výsledkami * @param {Object} results - Výsledky hry */ function showEndGameModal(results) { const modal = document.getElementById('endgame'); if (!modal) { console.error('❌ End game modal nenájdený!'); return; } // Aktualizácia obsahu modalu updateEndGameModalContent(results); // Zobrazenie modalu modal.style.display = 'block'; // Nastavenie event listenerov pre tlačidlá setupEndGameButtons(); } /** * Aktualizácia obsahu end game modalu * @param {Object} results - Výsledky hry */ function updateEndGameModalContent(results) { const modal = document.getElementById('endgame'); const contentDiv = modal.querySelector('.cvicenie-content-2'); if (!contentDiv) { console.error('❌ Content div nenájdený v modale!'); return; } // Vyčistenie obsahu contentDiv.innerHTML = ''; // Rozlíšenie medzi timeout a victory if (results.isTimeOut) { // ====================================== // MODAL PRE VYPRŠANIE ČASU // ====================================== // Titulok const title = document.createElement('h1'); title.textContent = 'ČAS VYPRŠAL!'; contentDiv.appendChild(title); // Stats sekcia - skóre hráčov const statsDiv = document.createElement('div'); statsDiv.className = 'stats'; // Nadpis skóre const scoreHeader = document.createElement('div'); scoreHeader.innerHTML = 'SKÓRE:'; statsDiv.appendChild(scoreHeader); // Zoradenie hráčov podľa skóre const sortedPlayers = [...players].sort((a, b) => b.score - a.score); // Skóre každého hráča sortedPlayers.forEach((player) => { const playerDiv = document.createElement('div'); const pluralForm = player.score === 1 ? 'pár' : player.score < 5 ? 'páry' : 'párov'; playerDiv.innerHTML = `${player.name}: ${player.score} ${pluralForm}`; statsDiv.appendChild(playerDiv); }); contentDiv.appendChild(statsDiv); } else { // ====================================== // MODAL PRE VÝHRU // ====================================== // Titulok const title = document.createElement('h1'); title.textContent = isMultiplayerMode ? 'HRA SKONČILA!' : 'VYHRAL SI!'; contentDiv.appendChild(title); // Stats sekcia const statsDiv = document.createElement('div'); statsDiv.className = 'stats'; // Čas const timeDiv = document.createElement('div'); const minutes = Math.floor(results.totalTime / 60); const seconds = results.totalTime % 60; timeDiv.innerHTML = `Čas: ${minutes}:${seconds.toString().padStart(2, '0')}`; statsDiv.appendChild(timeDiv); // Pokusy const attemptsDiv = document.createElement('div'); attemptsDiv.innerHTML = `Pokusy: ${results.totalAttempts}`; statsDiv.appendChild(attemptsDiv); // Nadpis skóre const scoreHeader = document.createElement('div'); scoreHeader.innerHTML = 'SKÓRE:'; statsDiv.appendChild(scoreHeader); // Zoradenie hráčov podľa skóre const sortedPlayers = [...players].sort((a, b) => b.score - a.score); // Skóre každého hráča sortedPlayers.forEach((player) => { const playerDiv = document.createElement('div'); const pluralForm = player.score === 1 ? 'pár' : player.score < 5 ? 'páry' : 'párov'; playerDiv.innerHTML = `${player.name}: ${player.score} ${pluralForm}`; statsDiv.appendChild(playerDiv); }); contentDiv.appendChild(statsDiv); // Hviezdy (len pre single player) if (!isMultiplayerMode) { const starsDiv = document.createElement('div'); starsDiv.id = 'modal-stars'; starsDiv.className = 'modal-stars'; contentDiv.appendChild(starsDiv); // Aktualizácia hviezd updateModalStars(results.stars); } } // ====================================== // TLAČIDLÁ (spoločné pre oba typy) // ====================================== const gameendDiv = document.createElement('div'); gameendDiv.className = 'gameend'; const nav = document.createElement('nav'); nav.className = 'main-menu'; const ul = document.createElement('ul'); // Tlačidlo "Hrať znova" const restartLi = document.createElement('li'); const restartBtn = document.createElement('button'); restartBtn.textContent = 'Hrať znova'; restartBtn.className = 'menu-button'; restartBtn.onclick = restartCurrentLevel; restartLi.appendChild(restartBtn); ul.appendChild(restartLi); // Tlačidlo "Späť do menu" const menuLi = document.createElement('li'); const menuBtn = document.createElement('button'); menuBtn.textContent = 'Späť do menu'; menuBtn.className = 'menu-button'; menuBtn.onclick = returnToMenu; menuLi.appendChild(menuBtn); ul.appendChild(menuLi); nav.appendChild(ul); gameendDiv.appendChild(nav); contentDiv.appendChild(gameendDiv); } /** * Aktualizácia hviezd v modali * @param {number} starsCount - Počet hviezd */ function updateModalStars(starsCount) { const starsContainer = document.getElementById('modal-stars'); if (!starsContainer) return; starsContainer.innerHTML = ''; for (let i = 1; i <= 3; i++) { const star = document.createElement('img'); star.src = i <= starsCount ? 'images/star_active.png' : 'images/star_inactive.png'; star.alt = i <= starsCount ? 'Aktívna hviezda' : 'Neaktívna hviezda'; starsContainer.appendChild(star); } } /** * Nastavenie event listenerov pre tlačidlá v end game modali */ function setupEndGameButtons() { // Tlačidlo "Hrať znova" const restartBtn = document.querySelector('#endgame button[onclick="restartCurrentLevel()"]'); if (restartBtn) { restartBtn.onclick = restartCurrentLevel; } // Tlačidlo "Ďalší level" const nextBtn = document.querySelector('#endgame button[onclick="goToNextLevel()"]'); if (nextBtn) { nextBtn.onclick = goToNextLevel; } // Tlačidlo "Späť do menu" const menuBtn = document.querySelector('#endgame button[onclick="returnToMenu()"]'); if (menuBtn) { menuBtn.onclick = returnToMenu; } } // ========================================== // NAVIGAČNÉ FUNKCIE // ========================================== /** * Reštart aktuálneho levelu */ function restartCurrentLevel() { console.log('🔄 Reštartujem level...'); // Skrytie end game modalu const modal = document.getElementById('endgame'); if (modal) modal.style.display = 'none'; // Reštart hry initGame(); } /** * Prechod na ďalší level */ function goToNextLevel() { console.log('➡️ Prechod na ďalší level...'); // Pre custom hry alebo demo nie je ďalší level - len reštartuj aktuálny if (currentLevel && (currentLevel.id === 'custom' || currentLevel.id === 'demo')) { restartCurrentLevel(); return; } // Skontroluj, či je funkcia getNextLevel dostupná (z config/levels.js) if (typeof getNextLevel !== 'function') { console.error('❌ Funkcia getNextLevel nie je dostupná - config/levels.js pravdepodobne nie je načítaný'); returnToMenu(); return; } // Skontroluj, že poznáme aktuálny level if (!currentLevel || !currentLevel.id) { console.error('❌ Neznámy aktuálny level, vraciam sa do menu'); returnToMenu(); return; } // Získaj konfiguráciu ďalšieho levelu const nextLevel = getNextLevel(currentLevel.id); // Ak ďalší level neexistuje (posledný level sveta), vráť sa do menu if (!nextLevel) { console.log(`Ďalší level po ${currentLevel.id} neexistuje - koniec sveta`); returnToMenu(); return; } // Mapovanie typu hry na správny HTML súbor // Ďalší level nemusí byť pexeso - môže to byť baník alebo mario const gameUrls = { 'banik': 'game.html', 'pexeso': 'pexeso.html', 'mario': 'superjozino.html' }; const targetUrl = gameUrls[nextLevel.gameType]; // Ak typ hry nepoznáme, logni chybu a vráť sa do menu if (!targetUrl) { console.error(`❌ Neznámy typ hry: ${nextLevel.gameType}`); returnToMenu(); return; } // Presmeruj na ďalší level so správnymi URL parametrami const nextUrl = `${targetUrl}?worldId=${nextLevel.worldId}&levelId=${nextLevel.id}`; console.log(`✅ Presmerovávam na ďalší level: ${nextUrl}`); window.location.href = nextUrl; } /** * Návrat do hlavného menu */ function returnToMenu() { console.log('🏠 Návrat do menu...'); if (currentLevel.id === 'custom') { // Pre custom hry návrat na index window.location.href = 'worldsmenu.html'; } else { // Pre level hry návrat na worlds menu window.location.href = 'worldsmenu.html'; } } // ========================================== // EVENT LISTENERY A UTILITY FUNKCIE // ========================================== /** * Nastavenie globálnych event listenerov */ function setupEventListeners() { console.log('🎮 Nastavujem event listenery...'); // Tlačidlo menu (pauza) - otvorenie const menuButton = document.getElementById('menuButton'); if (menuButton) { menuButton.addEventListener('click', openPauseMenu); console.log('✅ Menu button listener nastavený'); } else { console.warn('⚠️ Menu button nenájdený!'); } // NOVÉ PAUSE MENU - tlačidlá // 1. Close button (X) const pauseCloseBtn = document.getElementById('pauseCloseBtn'); if (pauseCloseBtn) { pauseCloseBtn.addEventListener('click', function() { console.log('🖱️ Klik na close button (X)'); closePauseMenu(); }); console.log('✅ Pause close button listener nastavený'); } else { console.warn('⚠️ Pause close button nenájdený!'); } // 2. Resume button (Pokračovať) const pauseResumeBtn = document.getElementById('pauseResumeBtn'); if (pauseResumeBtn) { pauseResumeBtn.addEventListener('click', function() { console.log('🖱️ Klik na Resume button'); closePauseMenu(); }); console.log('✅ Pause resume button listener nastavený'); } else { console.warn('⚠️ Pause resume button nenájdený!'); } // 3. Restart button (Hrať znova) const pauseRestartBtn = document.getElementById('pauseRestartBtn'); if (pauseRestartBtn) { pauseRestartBtn.addEventListener('click', function() { console.log('🖱️ Klik na Restart button'); closePauseMenu(); restartCurrentLevel(); }); console.log('✅ Pause restart button listener nastavený'); } else { console.warn('⚠️ Pause restart button nenájdený!'); } // 4. Menu button (Späť do menu) const pauseMenuBtn = document.getElementById('pauseMenuBtn'); if (pauseMenuBtn) { pauseMenuBtn.addEventListener('click', function() { console.log('🖱️ Klik na Menu button'); returnToMenu(); }); console.log('✅ Pause menu button listener nastavený'); } else { console.warn('⚠️ Pause menu button nenájdený!'); } // Klávesové skratky (voliteľné) document.addEventListener('keydown', handleKeyPress); console.log('✅ Všetky event listenery nastavené'); } /** * Otvorenie pauza menu */ function openPauseMenu() { console.log('⏸️ Otváram NOVÉ pauza menu...'); // Pozastavenie časovača if (timerInterval) { clearInterval(timerInterval); console.log('⏸️ Časovač zastavený, ID:', timerInterval); } // Zobrazenie nového pause modalu const pauseModal = document.getElementById('pause-modal'); const blurBg = document.getElementById('blur-background'); if (pauseModal) { pauseModal.style.display = 'flex'; console.log('⏸️ Pause modal zobrazený'); } else { console.error('❌ Pause modal nenájdený!'); } if (blurBg) { blurBg.style.display = 'block'; } document.body.classList.add('dialog-open'); document.body.style.overflow = 'hidden'; console.log('⏸️ Pause menu otvorené'); } /** * Zatvorenie pauza menu */ function closePauseMenu() { console.log('▶️ Zatváram NOVÉ pauza menu...'); console.log('▶️ gameTime pred obnovením:', gameTime); console.log('▶️ currentLevel:', currentLevel); console.log('▶️ timerInterval pred obnovením:', timerInterval); // Skrytie nového pause modalu const pauseModal = document.getElementById('pause-modal'); const blurBg = document.getElementById('blur-background'); if (pauseModal) { pauseModal.style.display = 'none'; console.log('▶️ Pause modal skrytý'); } else { console.error('❌ Pause modal nenájdený!'); } if (blurBg) { blurBg.style.display = 'none'; } document.body.classList.remove('dialog-open'); document.body.style.overflow = 'auto'; console.log('▶️ Teraz volám resumeGameTimer()...'); // Obnovenie časovača (bez resetovania času) resumeGameTimer(); console.log('▶️ Pause menu zatvorené'); } /** * Spracovanie klávesových skratiek * @param {KeyboardEvent} event - Event klávesy */ function handleKeyPress(event) { switch (event.key) { case 'Escape': openPauseMenu(); break; case 'r': case 'R': if (event.ctrlKey) { event.preventDefault(); restartCurrentLevel(); } break; } } /** * Zamichanie poľa (Fisher-Yates algoritmus) * @param {Array} array - Pole na zamichanie * @returns {Array} Zamichané pole */ function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } /** * Zobrazenie chybovej správy * @param {string} message - Chybová správa */ function showErrorMessage(message) { console.error(`❌ ${message}`); // Jednoduché zobrazenie cez alert (môže sa nahradiť vlastným modalom) alert(message); } // ========================================== // GLOBÁLNE FUNKCIE PRE HTML ONCLICK // ========================================== /** * Globálne funkcie pre onclick v HTML * Tieto funkcie sú dostupné z HTML súborov */ // Staré funkcie (pre spätnú kompatibilitu, ak sú ešte v HTML) // window.openDialog1 = openPauseMenu; // window.closeDialog1 = closePauseMenu; // Nové funkcie pre pause menu window.openPauseMenu = openPauseMenu; window.closePauseMenu = closePauseMenu; window.restartCurrentLevel = restartCurrentLevel; window.goToNextLevel = goToNextLevel; window.returnToMenu = returnToMenu; // ========================================== // 🔊 GLOBÁLNY CLICK LISTENER // ========================================== // Prehrá zvuk pri každom kliknutí myšou document.addEventListener('click', function() { playSound('mouseClick'); }); // ========================================== // KONIEC SÚBORU // ========================================== console.log('📋 pexeso.js načítaný - verzia 2.4 (so zvukmi)');