Add WebAssembly support for assets and chunks, implement gameover screen rendering, and enhance server gameplay logic with dynamic speeds, team naming, and score components.
This commit is contained in:
@@ -2,6 +2,130 @@
|
||||
let wasmReady = false;
|
||||
let gameStarted = false;
|
||||
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';
|
||||
|
||||
// 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';
|
||||
}
|
||||
// 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;
|
||||
@@ -89,8 +213,11 @@ async function initWASM() {
|
||||
go.run(result.instance);
|
||||
wasmReady = true;
|
||||
|
||||
// Hide loading screen
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
// Switch to menu state
|
||||
setUIState(UIState.MENU);
|
||||
|
||||
// Enable all start buttons
|
||||
enableStartButtons();
|
||||
|
||||
console.log('✅ WASM loaded successfully');
|
||||
|
||||
@@ -104,53 +231,32 @@ async function initWASM() {
|
||||
}
|
||||
}
|
||||
|
||||
// Menu Navigation
|
||||
function showMainMenu() {
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function showCoopMenu() {
|
||||
hideAllScreens();
|
||||
document.getElementById('coopMenu').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function showSettings() {
|
||||
hideAllScreens();
|
||||
document.getElementById('settingsMenu').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function showLeaderboard() {
|
||||
hideAllScreens();
|
||||
document.getElementById('leaderboardMenu').classList.remove('hidden');
|
||||
loadLeaderboard();
|
||||
}
|
||||
|
||||
function hideAllScreens() {
|
||||
document.querySelectorAll('.overlay-screen').forEach(screen => {
|
||||
screen.classList.add('hidden');
|
||||
// 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)');
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
// Canvas sichtbar machen für Gameplay
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.classList.add('game-active');
|
||||
}
|
||||
}
|
||||
|
||||
function showMenu() {
|
||||
document.getElementById('menu').style.display = 'flex';
|
||||
document.getElementById('menu').classList.remove('hidden');
|
||||
showMainMenu();
|
||||
// Canvas verstecken im Menü
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.classList.remove('game-active');
|
||||
}
|
||||
}
|
||||
// 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() {
|
||||
@@ -166,23 +272,17 @@ function startSoloGame() {
|
||||
localStorage.setItem('escape_game_mode', 'solo');
|
||||
localStorage.setItem('escape_room_id', '');
|
||||
|
||||
// Hide ALL screens including main menu
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
gameStarted = true;
|
||||
|
||||
// Canvas sichtbar machen
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.classList.add('game-active');
|
||||
}
|
||||
// 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 started:', playerName);
|
||||
console.log('🎮 Solo game starting - waiting for server auto-start...');
|
||||
}
|
||||
|
||||
function createRoom() {
|
||||
@@ -202,12 +302,8 @@ function createRoom() {
|
||||
localStorage.setItem('escape_team_name', teamName);
|
||||
localStorage.setItem('escape_is_host', 'true');
|
||||
|
||||
// Verstecke ALLE Screens inkl. Hauptmenü
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
|
||||
// Zeige HTML Lobby Screen
|
||||
document.getElementById('lobbyScreen').classList.remove('hidden');
|
||||
// Show Lobby
|
||||
setUIState(UIState.LOBBY);
|
||||
document.getElementById('lobbyRoomCode').textContent = roomID;
|
||||
document.getElementById('lobbyHostControls').classList.remove('hidden');
|
||||
document.getElementById('lobbyStatus').textContent = 'Du bist Host - starte wenn bereit!';
|
||||
@@ -242,12 +338,8 @@ function joinRoom() {
|
||||
localStorage.setItem('escape_team_name', teamName);
|
||||
localStorage.setItem('escape_is_host', 'false');
|
||||
|
||||
// Verstecke ALLE Screens inkl. Hauptmenü
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
|
||||
// Zeige HTML Lobby Screen
|
||||
document.getElementById('lobbyScreen').classList.remove('hidden');
|
||||
// Show Lobby
|
||||
setUIState(UIState.LOBBY);
|
||||
document.getElementById('lobbyRoomCode').textContent = roomID;
|
||||
document.getElementById('lobbyHostControls').classList.add('hidden');
|
||||
document.getElementById('lobbyStatus').textContent = 'Warte auf Host...';
|
||||
@@ -291,6 +383,42 @@ function updateLobbyPlayers(players) {
|
||||
console.log('👥 Lobby players updated:', players.length);
|
||||
}
|
||||
|
||||
// Update Lobby Team Name (called by WASM)
|
||||
function updateLobbyTeamName(teamName, isHost) {
|
||||
const teamNameBox = document.getElementById('lobbyTeamNameBox');
|
||||
const teamNameDisplay = document.getElementById('teamNameDisplay');
|
||||
|
||||
// Zeige Team-Name Box nur für Host
|
||||
if (isHost) {
|
||||
teamNameBox.classList.remove('hidden');
|
||||
|
||||
// Setup Event Listener für Input-Feld (nur einmal)
|
||||
const input = document.getElementById('lobbyTeamName');
|
||||
if (!input.dataset.listenerAdded) {
|
||||
input.addEventListener('input', function() {
|
||||
const newTeamName = this.value.toUpperCase().trim();
|
||||
if (newTeamName && window.setTeamName_WASM) {
|
||||
window.setTeamName_WASM(newTeamName);
|
||||
}
|
||||
});
|
||||
input.dataset.listenerAdded = 'true';
|
||||
}
|
||||
} else {
|
||||
teamNameBox.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Aktualisiere Team-Name Anzeige
|
||||
if (teamName && teamName !== '') {
|
||||
teamNameDisplay.textContent = teamName;
|
||||
teamNameDisplay.style.color = '#00ff00';
|
||||
} else {
|
||||
teamNameDisplay.textContent = 'Nicht gesetzt';
|
||||
teamNameDisplay.style.color = '#888';
|
||||
}
|
||||
|
||||
console.log('🏷️ Team name updated:', teamName, 'isHost:', isHost);
|
||||
}
|
||||
|
||||
function loadLeaderboard() {
|
||||
const list = document.getElementById('leaderboardList');
|
||||
list.innerHTML = '<div style="text-align:center; padding:20px;">Lädt Leaderboard...</div>';
|
||||
@@ -308,6 +436,9 @@ function loadLeaderboard() {
|
||||
|
||||
// Called by WASM to update leaderboard
|
||||
function updateLeaderboard(entries) {
|
||||
// Store full leaderboard data globally
|
||||
currentLeaderboard = entries || [];
|
||||
|
||||
// Update ALL leaderboard displays
|
||||
const list = document.getElementById('leaderboardList');
|
||||
const startList = document.getElementById('startLeaderboardList');
|
||||
@@ -423,8 +554,7 @@ document.addEventListener('keydown', (e) => {
|
||||
|
||||
// Show Game Over Screen (called by WASM)
|
||||
function showGameOver(score) {
|
||||
hideAllScreens();
|
||||
document.getElementById('gameOverScreen').classList.remove('hidden');
|
||||
setUIState(UIState.GAME_OVER);
|
||||
document.getElementById('finalScore').textContent = score;
|
||||
|
||||
// Update local highscore
|
||||
@@ -436,29 +566,137 @@ function showGameOver(score) {
|
||||
// Request leaderboard via direct WebSocket
|
||||
requestLeaderboardDirect();
|
||||
|
||||
console.log('💀 Game Over! Score:', score);
|
||||
// Note: Proof-Code wird jetzt direkt vom Server über score_response WebSocket Nachricht gesendet
|
||||
// und von WASM (connection_wasm.go) automatisch an saveHighscoreCode() weitergeleitet
|
||||
|
||||
console.log('💀 Game Over! Score:', score, '- Warte auf Proof-Code vom Server...');
|
||||
}
|
||||
|
||||
// Called by WASM when game actually starts
|
||||
function onGameStarted() {
|
||||
console.log('🎮 Game Started - Making canvas visible');
|
||||
hideAllScreens();
|
||||
document.getElementById('menu').style.display = 'none';
|
||||
gameStarted = true;
|
||||
setUIState(UIState.PLAYING);
|
||||
}
|
||||
|
||||
// Canvas sichtbar machen
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (canvas) {
|
||||
canvas.classList.add('game-active');
|
||||
// ===== MY CODES MANAGEMENT =====
|
||||
|
||||
// Save highscore code to localStorage
|
||||
function saveHighscoreCode(score, proofCode, playerName) {
|
||||
const codes = getMySavedCodes();
|
||||
const newCode = {
|
||||
score: score,
|
||||
proof: proofCode,
|
||||
player_name: playerName,
|
||||
timestamp: Date.now(),
|
||||
date: new Date().toLocaleString('de-DE')
|
||||
};
|
||||
codes.push(newCode);
|
||||
localStorage.setItem('escape_highscore_codes', JSON.stringify(codes));
|
||||
console.log('💾 Highscore-Code gespeichert:', proofCode, 'für Score:', score);
|
||||
}
|
||||
|
||||
// Get all saved codes from localStorage
|
||||
function getMySavedCodes() {
|
||||
const stored = localStorage.getItem('escape_highscore_codes');
|
||||
if (!stored) return [];
|
||||
try {
|
||||
return JSON.parse(stored);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Load and display my codes
|
||||
function loadMyCodes() {
|
||||
const codes = getMySavedCodes();
|
||||
const list = document.getElementById('myCodesList');
|
||||
|
||||
if (codes.length === 0) {
|
||||
list.innerHTML = '<div style="color: #888; text-align: center; padding: 20px;">Noch keine Highscores erreicht!</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by score descending
|
||||
codes.sort((a, b) => b.score - a.score);
|
||||
|
||||
// Request current leaderboard to check positions
|
||||
requestLeaderboardForCodes(codes);
|
||||
}
|
||||
|
||||
// Request leaderboard and then display codes with positions
|
||||
function requestLeaderboardForCodes(codes) {
|
||||
const list = document.getElementById('myCodesList');
|
||||
list.innerHTML = '<div style="color: #888; text-align: center; padding: 20px;">Lade Positionen...</div>';
|
||||
|
||||
// Use the direct leaderboard WebSocket
|
||||
requestLeaderboardDirect();
|
||||
|
||||
// Wait a bit for leaderboard to arrive, then display
|
||||
setTimeout(() => {
|
||||
displayMyCodesWithPositions(codes);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Display codes with their leaderboard positions
|
||||
function displayMyCodesWithPositions(codes) {
|
||||
const list = document.getElementById('myCodesList');
|
||||
let html = '';
|
||||
|
||||
codes.forEach((code, index) => {
|
||||
// Try to find position in current leaderboard
|
||||
const position = findPositionInLeaderboard(code.proof);
|
||||
const positionText = position > 0 ? `#${position}` : 'Nicht in Top 10';
|
||||
const positionColor = position > 0 ? '#fc0' : '#888';
|
||||
|
||||
html += `
|
||||
<div style="background: rgba(0,0,0,0.4); border: 2px solid #fc0; padding: 12px; margin: 8px 0; border-radius: 4px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<div>
|
||||
<span style="color: #fc0; font-size: 20px; font-weight: bold;">${code.score} Punkte</span>
|
||||
<span style="color: ${positionColor}; font-size: 14px; margin-left: 10px;">${positionText}</span>
|
||||
</div>
|
||||
<button onclick="deleteHighscoreCode(${index})" style="background: #ff4444; border: none; color: white; padding: 5px 10px; font-size: 10px; cursor: pointer; border-radius: 3px;">LÖSCHEN</button>
|
||||
</div>
|
||||
<div style="font-family: sans-serif; font-size: 12px; color: #ccc;">
|
||||
<div><strong>Name:</strong> ${code.player_name}</div>
|
||||
<div><strong>Code:</strong> <span style="color: #fc0; font-family: monospace;">${code.proof}</span></div>
|
||||
<div><strong>Datum:</strong> ${code.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
list.innerHTML = html;
|
||||
}
|
||||
|
||||
// Find position of a proof code in the current leaderboard
|
||||
function findPositionInLeaderboard(proofCode) {
|
||||
if (!currentLeaderboard || currentLeaderboard.length === 0) return -1;
|
||||
|
||||
// Find the entry with matching proof code
|
||||
const index = currentLeaderboard.findIndex(entry => entry.proof_code === proofCode);
|
||||
return index >= 0 ? index + 1 : -1; // Return 1-based position
|
||||
}
|
||||
|
||||
// Delete a highscore code
|
||||
function deleteHighscoreCode(index) {
|
||||
if (!confirm('Diesen Highscore-Code wirklich löschen?')) return;
|
||||
|
||||
const codes = getMySavedCodes();
|
||||
codes.splice(index, 1);
|
||||
localStorage.setItem('escape_highscore_codes', JSON.stringify(codes));
|
||||
console.log('🗑️ Highscore-Code gelöscht');
|
||||
loadMyCodes(); // Reload display
|
||||
}
|
||||
|
||||
// Export functions for WASM to call
|
||||
window.showMenu = showMenu;
|
||||
window.hideMenu = hideMenu;
|
||||
window.updateLeaderboard = updateLeaderboard;
|
||||
window.showGameOver = showGameOver;
|
||||
window.onGameStarted = onGameStarted;
|
||||
window.saveHighscoreCode = saveHighscoreCode;
|
||||
|
||||
// Initialize on load
|
||||
initWASM();
|
||||
|
||||
Reference in New Issue
Block a user