DP-Logopedicka-Platforma/js/pexeso/pexeso.js
2026-04-24 14:58:15 +02:00

2341 lines
73 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ===============================================
* 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 = `
<center>
<img src="images/spravne.png" alt="Správne">
<div class="success-message">
<span style="color: #00ff00; font-size: 32px; font-weight: bold;">VÝBORNE!</span>
</div>
</center>
`;
} else {
// Nesprávna odpoveď
const remainingAttempts = maxAttempts - attempt;
content = `
<center>
<img src="images/nespravne.png" alt="Nesprávne">
<div class="attempt-message">
<span style="color: #ff6b6b; font-size: 28px;">SKÚSTE ZNOVA</span>
${remainingAttempts > 0 ?
`<br><span style="color: #ffffff; font-size: 20px;">Zostávajú ${remainingAttempts} pokusy</span>` :
`<br><span style="color: #ff6b6b; font-size: 20px;">Žiadne pokusy nezostali</span>`
}
</div>
</center>
`;
}
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 = '<a>SKÓRE:</a>';
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 = `<a>${player.name}: </a><span>${player.score} ${pluralForm}</span>`;
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 = `<a>Čas: </a><span>${minutes}:${seconds.toString().padStart(2, '0')}</span>`;
statsDiv.appendChild(timeDiv);
// Pokusy
const attemptsDiv = document.createElement('div');
attemptsDiv.innerHTML = `<a>Pokusy: </a><span>${results.totalAttempts}</span>`;
statsDiv.appendChild(attemptsDiv);
// Nadpis skóre
const scoreHeader = document.createElement('div');
scoreHeader.innerHTML = '<a>SKÓRE:</a>';
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 = `<a>${player.name}: </a><span>${player.score} ${pluralForm}</span>`;
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)');