// Game State let wasmReady = false; let gameStarted = false; let gameStarting = false; // Verhindert doppeltes Starten let audioMuted = false; let currentLeaderboard = []; // Store full leaderboard data with proof codes // UI State Management - Single Source of Truth const UIState = { LOADING: 'loading', MENU: 'menu', LOBBY: 'lobby', PLAYING: 'playing', GAME_OVER: 'gameover', LEADERBOARD: 'leaderboard', SETTINGS: 'settings', COOP_MENU: 'coop_menu', MY_CODES: 'mycodes', IMPRESSUM: 'impressum', DATENSCHUTZ: 'datenschutz' }; let currentUIState = UIState.LOADING; // Central UI State Manager function setUIState(newState) { console.log('🎨 UI State:', currentUIState, '->', newState); currentUIState = newState; const canvas = document.querySelector('canvas'); const loadingScreen = document.getElementById('loading'); // Hide all overlays first document.querySelectorAll('.overlay-screen').forEach(screen => { screen.classList.add('hidden'); }); if (loadingScreen) loadingScreen.style.display = 'none'; // Scroll alle overlay-screens nach oben setTimeout(() => { document.querySelectorAll('.overlay-screen:not(.hidden)').forEach(screen => { screen.scrollTop = 0; }); }, 10); // Manage Canvas and Overlays based on state switch(newState) { case UIState.LOADING: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } if (loadingScreen) loadingScreen.style.display = 'flex'; break; case UIState.MENU: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('menu').classList.remove('hidden'); break; case UIState.LOBBY: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('lobbyScreen').classList.remove('hidden'); break; case UIState.PLAYING: if (canvas) { canvas.classList.add('game-active'); canvas.style.visibility = 'visible'; canvas.focus(); // Canvas fokussieren für Tastatureingaben } // No overlays shown during gameplay break; case UIState.GAME_OVER: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('gameOverScreen').classList.remove('hidden'); break; case UIState.LEADERBOARD: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('leaderboardMenu').classList.remove('hidden'); break; case UIState.SETTINGS: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('settingsMenu').classList.remove('hidden'); break; case UIState.COOP_MENU: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('coopMenu').classList.remove('hidden'); break; case UIState.MY_CODES: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('myCodesMenu').classList.remove('hidden'); break; case UIState.IMPRESSUM: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('impressumMenu').classList.remove('hidden'); break; case UIState.DATENSCHUTZ: if (canvas) { canvas.classList.remove('game-active'); canvas.style.visibility = 'hidden'; } document.getElementById('datenschutzMenu').classList.remove('hidden'); break; } } // WebSocket for Leaderboard (direct JS connection) let leaderboardWS = null; let leaderboardSessionID = 'lb_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); function connectLeaderboardWebSocket() { if (leaderboardWS && leaderboardWS.readyState === WebSocket.OPEN) { return; // Already connected } // Automatisch die richtige WebSocket-URL basierend auf der aktuellen Domain const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const host = window.location.host; const wsURL = `${protocol}//${host}/ws`; console.log('🔌 Verbinde zu WebSocket:', wsURL); leaderboardWS = new WebSocket(wsURL); leaderboardWS.onopen = () => { console.log('📡 Leaderboard WebSocket connected with session:', leaderboardSessionID); // Send JOIN message to register session const joinMsg = JSON.stringify({ type: 'join', payload: { name: leaderboardSessionID, room_id: 'leaderboard_viewer', game_mode: 'solo', is_host: false, team_name: '' } }); leaderboardWS.send(joinMsg); console.log('📝 Registered leaderboard session:', leaderboardSessionID); }; leaderboardWS.onmessage = (event) => { try { const msg = JSON.parse(event.data); if (msg.type === 'leaderboard_response') { console.log('📊 Received leaderboard:', msg.payload?.entries?.length || 0, 'entries'); updateLeaderboard(msg.payload?.entries || []); } } catch (e) { console.error('Failed to parse WebSocket message:', e); } }; leaderboardWS.onerror = (error) => { console.error('❌ Leaderboard WebSocket error:', error); }; leaderboardWS.onclose = () => { console.log('📡 Leaderboard WebSocket closed'); leaderboardWS = null; }; } function requestLeaderboardDirect() { connectLeaderboardWebSocket(); // Wait for connection then request const checkAndRequest = setInterval(() => { if (leaderboardWS && leaderboardWS.readyState === WebSocket.OPEN) { clearInterval(checkAndRequest); const msg = JSON.stringify({ type: 'leaderboard_request', payload: { mode: 'solo', limit: 10 } }); leaderboardWS.send(msg); console.log('📤 Requesting leaderboard via WebSocket (session:', leaderboardSessionID + ')'); } }, 100); // Timeout after 3 seconds setTimeout(() => clearInterval(checkAndRequest), 3000); } // Callback von WASM wenn vollständig geladen window.onWasmReady = function() { console.log('✅ WASM fully initialized'); wasmReady = true; // Canvas fokussierbar machen const canvas = document.querySelector('canvas'); if (canvas) { canvas.setAttribute('tabindex', '1'); console.log('✅ Canvas tabindex gesetzt'); } // Switch to menu state setUIState(UIState.MENU); // Enable all start buttons enableStartButtons(); // Load initial leaderboard via direct WebSocket setTimeout(() => { requestLeaderboardDirect(); }, 500); }; // Cache Management - Version wird bei jedem Build aktualisiert const CACHE_VERSION = 1767643088942; // Wird durch Build-Prozess ersetzt // Fetch mit Cache-Busting async function fetchWithCache(url) { const cacheBustedUrl = `${url}?v=${CACHE_VERSION}`; console.log(`📦 Loading: ${cacheBustedUrl}`); try { const response = await fetch(cacheBustedUrl, { cache: 'no-cache', // Immer vom Server holen wenn Version neu ist headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response; } catch (err) { console.error(`❌ Failed to fetch ${url}:`, err); throw err; } } // Initialize WASM async function initWASM() { const go = new Go(); try { console.log(`🚀 Loading WASM (Cache Version: ${CACHE_VERSION})...`); // WASM mit Cache-Busting laden const response = await fetchWithCache("main.wasm"); const result = await WebAssembly.instantiateStreaming(response, go.importObject); go.run(result.instance); // WICHTIG: wasmReady wird erst in onWasmReady() gesetzt, nicht hier! console.log('✅ WASM runtime started, waiting for full initialization...'); } catch (err) { console.error('❌ Failed to load WASM:', err); document.getElementById('loading').innerHTML = '
Fehler beim Laden: ' + err.message + '
'; } } // Enable start buttons after WASM is ready function enableStartButtons() { const buttons = ['startBtn', 'coopBtn', 'createRoomBtn', 'joinRoomBtn']; buttons.forEach(btnId => { const btn = document.getElementById(btnId); if (btn) { btn.disabled = false; btn.style.opacity = '1'; btn.style.cursor = 'pointer'; } }); console.log('✅ Start-Buttons aktiviert (Solo + Coop)'); } // Menu Navigation // Legacy function wrappers - use setUIState instead function showMainMenu() { setUIState(UIState.MENU); } function showCoopMenu() { setUIState(UIState.COOP_MENU); } function showSettings() { setUIState(UIState.SETTINGS); } function showLeaderboard() { setUIState(UIState.LEADERBOARD); loadLeaderboard(); } function showMyCodes() { setUIState(UIState.MY_CODES); loadMyCodes(); } function showImpressum() { setUIState(UIState.IMPRESSUM); } function showDatenschutz() { setUIState(UIState.DATENSCHUTZ); } function hideAllScreens() { /* Handled by setUIState */ } function hideMenu() { /* Handled by setUIState */ } function showMenu() { setUIState(UIState.MENU); } // Game Functions function startSoloGame() { if (!wasmReady) { alert('Spiel wird noch geladen...'); return; } // Verhindere doppeltes Starten if (gameStarting) { console.log('⚠️ Game is already starting...'); return; } gameStarting = true; const playerName = document.getElementById('playerName').value || 'ANON'; const startBtn = document.getElementById('startBtn'); // Button deaktivieren und Loading anzeigen if (startBtn) { startBtn.disabled = true; startBtn.innerHTML = ' Starte...'; startBtn.style.opacity = '0.6'; } // Store in localStorage for WASM to read localStorage.setItem('escape_player_name', playerName); localStorage.setItem('escape_game_mode', 'solo'); localStorage.setItem('escape_room_id', ''); gameStarted = true; // Don't switch UI state yet - wait for WASM callback onGameStarted() // The server will auto-start solo games after 2 seconds // Trigger WASM game start if (window.startGame) { window.startGame('solo', playerName, ''); } console.log('🎮 Solo game starting - waiting for server auto-start...'); } function createRoom() { if (!wasmReady) { alert('Spiel wird noch geladen...'); return; } // Verhindere doppeltes Starten if (gameStarting) { console.log('⚠️ Game is already starting...'); return; } gameStarting = true; const createBtn = document.getElementById('createRoomBtn'); // Button deaktivieren und Loading anzeigen if (createBtn) { createBtn.disabled = true; createBtn.innerHTML = ' Erstelle Raum...'; createBtn.style.opacity = '0.6'; } const playerName = document.getElementById('playerName').value || 'ANON'; const roomID = 'R' + Math.random().toString(36).substr(2, 5).toUpperCase(); const teamName = 'TEAM'; // Store in localStorage localStorage.setItem('escape_player_name', playerName); localStorage.setItem('escape_game_mode', 'coop'); localStorage.setItem('escape_room_id', roomID); localStorage.setItem('escape_team_name', teamName); localStorage.setItem('escape_is_host', 'true'); // Show Lobby nach kurzer Verzögerung (damit User Feedback sieht) setTimeout(() => { setUIState(UIState.LOBBY); document.getElementById('lobbyRoomCode').textContent = roomID; document.getElementById('lobbyHostControls').classList.remove('hidden'); document.getElementById('lobbyStatus').textContent = 'Du bist Host - starte wenn bereit!'; // Reset gameStarting für Lobby gameStarting = false; }, 300); // Trigger WASM game start (im Hintergrund) if (window.startGame) { console.log('🎮 Calling window.startGame with:', 'coop', playerName, roomID, teamName, true); window.startGame('coop', playerName, roomID, teamName, true); } else { console.error('❌ window.startGame is not defined!'); } console.log('🎮 Room created:', roomID); } function joinRoom() { if (!wasmReady) { alert('Spiel wird noch geladen...'); return; } // Verhindere doppeltes Starten if (gameStarting) { console.log('⚠️ Game is already starting...'); return; } gameStarting = true; const joinBtn = document.getElementById('joinRoomBtn'); // Button deaktivieren und Loading anzeigen if (joinBtn) { joinBtn.disabled = true; joinBtn.innerHTML = ' Trete bei...'; joinBtn.style.opacity = '0.6'; } const playerName = document.getElementById('playerName').value || 'ANON'; const roomID = document.getElementById('joinRoomCode').value.toUpperCase(); const teamName = document.getElementById('teamNameJoin').value || 'TEAM'; if (!roomID || roomID.length < 4) { alert('Bitte gib einen gültigen Raum-Code ein!'); // Reset bei Fehler gameStarting = false; if (joinBtn) { joinBtn.disabled = false; joinBtn.innerHTML = 'RAUM BEITRETEN'; joinBtn.style.opacity = '1'; } return; } // Store in localStorage localStorage.setItem('escape_player_name', playerName); localStorage.setItem('escape_game_mode', 'coop'); localStorage.setItem('escape_room_id', roomID); localStorage.setItem('escape_team_name', teamName); localStorage.setItem('escape_is_host', 'false'); // Show Lobby nach kurzer Verzögerung setTimeout(() => { setUIState(UIState.LOBBY); document.getElementById('lobbyRoomCode').textContent = roomID; document.getElementById('lobbyHostControls').classList.add('hidden'); document.getElementById('lobbyStatus').textContent = 'Warte auf Host...'; // Reset gameStarting für Lobby gameStarting = false; }, 300); // Trigger WASM game start (im Hintergrund) if (window.startGame) { console.log('🎮 Calling window.startGame with:', 'coop', playerName, roomID, teamName, false); window.startGame('coop', playerName, roomID, teamName, false); } else { console.error('❌ window.startGame is not defined!'); } console.log('🎮 Joining room:', roomID); } // Lobby Functions function startGameFromLobby() { // Host startet das Spiel - Lobby bleibt sichtbar bis Server COUNTDOWN/RUNNING sendet // Signal an WASM senden dass Spiel starten soll if (window.startGameFromLobby_WASM) { window.startGameFromLobby_WASM(); } console.log('🎮 Host requested game start - waiting for server...'); } function leaveLobby() { location.reload(); } // Update Lobby Player List (called by WASM) function updateLobbyPlayers(players) { const list = document.getElementById('lobbyPlayerList'); if (!players || players.length === 0) { list.innerHTML = '