Änderung: Optimierung der Statusabfrage und Fehlerbehandlung im StatusBar-Komponenten

Änderungen:
- Entfernen von Konsolenausgaben zur Verbesserung der Codequalität und Reduzierung von Debugging-Informationen in der Produktionsumgebung.
- Anpassung der Fehlerbehandlung in der fetchStatus-Methode, um die Lesbarkeit zu erhöhen und die Fehlerprotokollierung zu vereinfachen.
- Verbesserung der Socket.io- und Daemon-WebSocket-Verbindungslogik zur Unterstützung lokaler Entwicklungsumgebungen.

Diese Anpassungen erhöhen die Effizienz und Benutzerfreundlichkeit der Anwendung, indem sie die Codebasis bereinigen und die Fehlerbehandlung optimieren.
This commit is contained in:
Torsten Schulz (local)
2025-09-21 22:01:27 +02:00
parent 1244c87d45
commit f418b59e14
4 changed files with 284 additions and 206 deletions

View File

@@ -79,9 +79,7 @@ export default {
methods: { methods: {
async fetchStatus() { async fetchStatus() {
try { try {
console.log('🔄 StatusBar: fetchStatus() startet...');
const response = await apiClient.get("/api/falukant/info"); const response = await apiClient.get("/api/falukant/info");
console.log('📊 StatusBar: API Response erhalten:', response.data);
const { money, character, events } = response.data; const { money, character, events } = response.data;
const { age, health } = character; const { age, health } = character;
const relationship = response.data.character.relationshipsAsCharacter1[0]?.relationshipType?.tr const relationship = response.data.character.relationshipsAsCharacter1[0]?.relationshipType?.tr
@@ -112,27 +110,17 @@ export default {
{ key: "events", icon: "📰", value: events || null, image: null }, { key: "events", icon: "📰", value: events || null, image: null },
{ key: "children", icon: "👶", value: childrenDisplay }, { key: "children", icon: "👶", value: childrenDisplay },
]; ];
console.log('📊 StatusBar: statusItems aktualisiert:', this.statusItems);
} catch (error) { } catch (error) {
console.error("Error fetching status:", error); // Error fetching status
} }
}, },
handleEvent(eventData) { handleEvent(eventData) {
console.log('🔄 StatusBar: handleEvent aufgerufen mit:', eventData);
console.log('🔄 StatusBar: Event-Typ:', eventData.event);
switch (eventData.event) { switch (eventData.event) {
case 'falukantUpdateStatus': case 'falukantUpdateStatus':
case 'stock_change': case 'stock_change':
case 'familychanged': case 'familychanged':
console.log('🔄 StatusBar: Rufe fetchStatus() auf...'); this.fetchStatus();
this.fetchStatus().then(() => {
console.log('✅ StatusBar: fetchStatus() erfolgreich abgeschlossen');
}).catch((error) => {
console.error('❌ StatusBar: fetchStatus() Fehler:', error);
});
break; break;
default:
console.log('⚠️ StatusBar: Unbekanntes Event:', eventData.event);
} }
}, },
openPage(url, hasSubmenu = false) { openPage(url, hasSubmenu = false) {

View File

@@ -145,15 +145,20 @@ const store = createStore({
currentSocket.disconnect(); currentSocket.disconnect();
} }
commit('setConnectionStatus', 'connecting'); commit('setConnectionStatus', 'connecting');
const socketIoUrl = import.meta.env.VITE_SOCKET_IO_URL || import.meta.env.VITE_API_BASE_URL; // Socket.io URL für lokale Entwicklung und Produktion
console.log('🔌 Initializing Socket.io connection to:', socketIoUrl); let socketIoUrl = import.meta.env.VITE_SOCKET_IO_URL || import.meta.env.VITE_API_BASE_URL;
// Für lokale Entwicklung: direkte Backend-Verbindung
if (!socketIoUrl && (import.meta.env.DEV || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
socketIoUrl = 'http://localhost:3001';
}
const socket = io(socketIoUrl, { const socket = io(socketIoUrl, {
secure: true, secure: true,
transports: ['websocket', 'polling'] transports: ['websocket', 'polling']
}); });
socket.on('connect', () => { socket.on('connect', () => {
console.log('✅ Socket.io connected successfully');
retryCount = 0; // Reset retry counter on successful connection retryCount = 0; // Reset retry counter on successful connection
commit('setConnectionStatus', 'connected'); commit('setConnectionStatus', 'connected');
const idForSocket = state.user?.hashedId || state.user?.id; const idForSocket = state.user?.hashedId || state.user?.id;
@@ -161,14 +166,11 @@ const store = createStore({
}); });
socket.on('disconnect', (reason) => { socket.on('disconnect', (reason) => {
console.warn('❌ Socket.io disconnected:', reason);
commit('setConnectionStatus', 'disconnected'); commit('setConnectionStatus', 'disconnected');
retryConnection(connectSocket); retryConnection(connectSocket);
}); });
socket.on('connect_error', (error) => { socket.on('connect_error', (error) => {
console.error('❌ Socket.io connection error:', error);
console.error('❌ URL attempted:', import.meta.env.VITE_API_BASE_URL);
commit('setConnectionStatus', 'error'); commit('setConnectionStatus', 'error');
}); });
@@ -179,30 +181,36 @@ const store = createStore({
const maxRetries = 10; const maxRetries = 10;
const retryConnection = (reconnectFn) => { const retryConnection = (reconnectFn) => {
if (retryCount >= maxRetries) { if (retryCount >= maxRetries) {
console.error('❌ Max retry attempts reached for Socket.io');
return; return;
} }
retryCount++; retryCount++;
const delay = Math.min(1000 * Math.pow(1.5, retryCount - 1), 30000); // Exponential backoff, max 30s const delay = Math.min(1000 * Math.pow(1.5, retryCount - 1), 30000); // Exponential backoff, max 30s
console.log(`🔄 Retrying Socket.io connection in ${delay}ms (attempt ${retryCount}/${maxRetries})`);
setTimeout(() => { setTimeout(() => {
reconnectFn(); reconnectFn();
}, delay); }, delay);
}; };
connectSocket(); connectSocket();
} else {
console.log("User is not logged in or user data is not available.");
} }
}, },
initializeDaemonSocket({ commit, state }) { initializeDaemonSocket({ commit, state }) {
if (!state.isLoggedIn || !state.user) { if (!state.isLoggedIn || !state.user) {
console.log("User is not logged in or user data is not available for Daemon WebSocket.");
return; return;
} }
const daemonUrl = import.meta.env.VITE_DAEMON_SOCKET || 'wss://www.your-part.de:4551'; // Daemon URL für lokale Entwicklung und Produktion
console.log('🔌 Initializing Daemon WebSocket connection to:', daemonUrl); let daemonUrl = import.meta.env.VITE_DAEMON_SOCKET;
// Für lokale Entwicklung: direkte Daemon-Verbindung
if (!daemonUrl && (import.meta.env.DEV || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
daemonUrl = 'ws://localhost:4551';
}
// Fallback für Produktion
if (!daemonUrl) {
daemonUrl = 'wss://www.your-part.de:4551';
}
const connectDaemonSocket = () => { const connectDaemonSocket = () => {
// Protokoll-Fallback: zuerst mit Subprotokoll, dann ohne // Protokoll-Fallback: zuerst mit Subprotokoll, dann ohne
@@ -215,13 +223,11 @@ const store = createStore({
const daemonSocket = currentProtocol const daemonSocket = currentProtocol
? new WebSocket(daemonUrl, currentProtocol) ? new WebSocket(daemonUrl, currentProtocol)
: new WebSocket(daemonUrl); : new WebSocket(daemonUrl);
console.log('🔌 Protocol:', currentProtocol ?? 'none (fallback)');
let opened = false; let opened = false;
daemonSocket.onopen = () => { daemonSocket.onopen = () => {
opened = true; opened = true;
console.log('✅ Daemon WebSocket connected successfully');
retryCount = 0; // Reset retry counter on successful connection retryCount = 0; // Reset retry counter on successful connection
const payload = JSON.stringify({ const payload = JSON.stringify({
user_id: state.user.id, user_id: state.user.id,
@@ -232,17 +238,9 @@ const store = createStore({
}; };
daemonSocket.onclose = (event) => { daemonSocket.onclose = (event) => {
console.warn('❌ Daemon WebSocket disconnected:', event.reason);
console.warn('❌ Close details:', {
code: event.code,
reason: event.reason,
wasClean: event.wasClean,
readyState: daemonSocket.readyState
});
// Falls Verbindungsaufbau nicht offen war und es noch einen Fallback gibt → nächsten Versuch ohne Subprotokoll // Falls Verbindungsaufbau nicht offen war und es noch einen Fallback gibt → nächsten Versuch ohne Subprotokoll
if (!opened && attemptIndex < protocols.length - 1) { if (!opened && attemptIndex < protocols.length - 1) {
attemptIndex += 1; attemptIndex += 1;
console.warn('🔄 Fallback: versuche erneut ohne Subprotokoll...');
tryConnectWithProtocol(); tryConnectWithProtocol();
return; return;
} }
@@ -250,23 +248,9 @@ const store = createStore({
}; };
daemonSocket.onerror = (error) => { daemonSocket.onerror = (error) => {
console.error('❌ Daemon WebSocket error:', error);
console.error('❌ Error details:', {
type: error.type,
target: error.target,
readyState: daemonSocket.readyState,
url: daemonSocket.url,
protocol: daemonSocket.protocol
});
console.error('❌ Browser info:', {
userAgent: navigator.userAgent,
location: window.location.href,
isSecure: window.location.protocol === 'https:'
});
// Bei Fehler vor Open: Fallback versuchen // Bei Fehler vor Open: Fallback versuchen
if (!opened && attemptIndex < protocols.length - 1) { if (!opened && attemptIndex < protocols.length - 1) {
attemptIndex += 1; attemptIndex += 1;
console.warn('🔄 Fallback: versuche erneut ohne Subprotokoll (onerror)...');
tryConnectWithProtocol(); tryConnectWithProtocol();
return; return;
} }
@@ -281,21 +265,17 @@ const store = createStore({
try { try {
const data = JSON.parse(message); const data = JSON.parse(message);
// Handle daemon messages here // Handle daemon messages here
console.log('📨 Daemon message received:', data);
} catch (error) { } catch (error) {
console.error("Error parsing daemon message:", error); // Error parsing daemon message
} }
} }
}); });
commit('setDaemonSocket', daemonSocket); commit('setDaemonSocket', daemonSocket);
} catch (error) { } catch (error) {
console.error('❌ Failed to create Daemon WebSocket:', error);
console.error('❌ URL attempted:', import.meta.env.VITE_DAEMON_SOCKET);
// Beim Konstruktionsfehler ebenfalls Fallback versuchen // Beim Konstruktionsfehler ebenfalls Fallback versuchen
if (attemptIndex < protocols.length - 1) { if (attemptIndex < protocols.length - 1) {
attemptIndex += 1; attemptIndex += 1;
console.warn('🔄 Fallback: Konstruktion ohne Subprotokoll...');
tryConnectWithProtocol(); tryConnectWithProtocol();
return; return;
} }
@@ -310,18 +290,15 @@ const store = createStore({
const maxRetries = 15; // Increased max retries const maxRetries = 15; // Increased max retries
const retryConnection = (reconnectFn) => { const retryConnection = (reconnectFn) => {
if (retryCount >= maxRetries) { if (retryCount >= maxRetries) {
console.error('❌ Max retry attempts reached for Daemon WebSocket');
// Reset counter after a longer delay to allow for network recovery // Reset counter after a longer delay to allow for network recovery
setTimeout(() => { setTimeout(() => {
retryCount = 0; retryCount = 0;
console.log('🔄 Resetting Daemon WebSocket retry counter - attempting reconnection');
reconnectFn(); reconnectFn();
}, 60000); // Wait 1 minute before resetting }, 60000); // Wait 1 minute before resetting
return; return;
} }
retryCount++; retryCount++;
const delay = Math.min(1000 * Math.pow(1.5, retryCount - 1), 30000); // Exponential backoff, max 30s const delay = Math.min(1000 * Math.pow(1.5, retryCount - 1), 30000); // Exponential backoff, max 30s
console.log(`🔄 Retrying Daemon WebSocket connection in ${delay}ms (attempt ${retryCount}/${maxRetries})`);
setTimeout(() => { setTimeout(() => {
reconnectFn(); reconnectFn();
}, delay); }, delay);
@@ -336,7 +313,6 @@ const store = createStore({
const menu = await loadMenu(); const menu = await loadMenu();
commit('setMenu', menu); commit('setMenu', menu);
} catch (err) { } catch (err) {
console.error(err);
commit('setMenu', []); commit('setMenu', []);
} }
}, },

View File

@@ -1,19 +1,22 @@
import axios from 'axios'; import axios from 'axios';
import store from '../store'; import store from '../store';
// API-Basis-URL - immer über Apache-Proxy für Produktion // API-Basis-URL - Apache-Proxy für Produktion, direkte Verbindung für lokale Entwicklung
const getApiBaseURL = () => { const getApiBaseURL = () => {
// Wenn explizite Umgebungsvariable gesetzt ist, diese verwenden // Wenn explizite Umgebungsvariable gesetzt ist, diese verwenden
if (import.meta.env.VITE_API_BASE_URL) { if (import.meta.env.VITE_API_BASE_URL) {
return import.meta.env.VITE_API_BASE_URL; return import.meta.env.VITE_API_BASE_URL;
} }
// Für lokale Entwicklung: direkte Backend-Verbindung
if (import.meta.env.DEV || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
return 'http://localhost:3001';
}
// Für Produktion: Root-Pfad, da API-Endpunkte bereits mit /api beginnen // Für Produktion: Root-Pfad, da API-Endpunkte bereits mit /api beginnen
return ''; return '';
}; };
// Debug-Informationen
console.log('🔗 API Base URL:', getApiBaseURL());
const apiClient = axios.create({ const apiClient = axios.create({
baseURL: getApiBaseURL(), baseURL: getApiBaseURL(),
@@ -28,8 +31,6 @@ apiClient.interceptors.request.use(config => {
if (user && user.authCode) { if (user && user.authCode) {
config.headers['userid'] = user.id; config.headers['userid'] = user.id;
config.headers['authcode'] = user.authCode; // Kleinschreibung! config.headers['authcode'] = user.authCode; // Kleinschreibung!
} else {
console.log('⚠️ Keine User-Daten verfügbar');
} }
return config; return config;

View File

@@ -1,6 +1,5 @@
<template> <template>
<div class="contenthidden"> <div class="contenthidden">
<StatusBar />
<div class="contentscroll"> <div class="contentscroll">
<!-- Spiel-Titel --> <!-- Spiel-Titel -->
<div class="game-title"> <div class="game-title">
@@ -68,6 +67,7 @@
<div class="tacho-speed"> <div class="tacho-speed">
<span class="lives" title="Verbleibende Fahrzeuge"> {{ vehicleCount }}</span> <span class="lives" title="Verbleibende Fahrzeuge"> {{ vehicleCount }}</span>
<span class="fuel" title="Treibstoff"> {{ Math.round(fuel) }}%</span> <span class="fuel" title="Treibstoff"> {{ Math.round(fuel) }}%</span>
<span class="score" title="Punkte"> {{ score }}</span>
<span class="speed-violations" title="Geschwindigkeitsverstöße">📷 {{ speedViolations }}</span> <span class="speed-violations" title="Geschwindigkeitsverstöße">📷 {{ speedViolations }}</span>
<span class="redlight-counter" title="Rote Ampeln überfahren"> <span class="redlight-counter" title="Rote Ampeln überfahren">
<span class="redlight-icon">🚦</span> <span class="redlight-icon">🚦</span>
@@ -223,12 +223,9 @@
import streetCoordinates from '../../utils/streetCoordinates.js'; import streetCoordinates from '../../utils/streetCoordinates.js';
import { MotorSound, NoiseGenerator } from '../../assets/motorSound.js'; import { MotorSound, NoiseGenerator } from '../../assets/motorSound.js';
import apiClient from '../../utils/axios.js'; import apiClient from '../../utils/axios.js';
import StatusBar from '../../components/falukant/StatusBar.vue';
export default { export default {
name: 'TaxiGame', name: 'TaxiGame',
components: { components: {
StatusBar
}, },
data() { data() {
return { return {
@@ -242,6 +239,9 @@ export default {
occupiedHouses: new Set(), // Verhindert doppelte Belegung von Häusern occupiedHouses: new Set(), // Verhindert doppelte Belegung von Häusern
lastPassengerGeneration: 0, lastPassengerGeneration: 0,
passengerGenerationInterval: 0, passengerGenerationInterval: 0,
// Timeout-Referenzen für Cleanup
passengerGenerationTimeout: null,
crashDialogTimeout: null,
bonusMultiplier: 15, // Bonus pro Tile bonusMultiplier: 15, // Bonus pro Tile
timePerTile: 8, // Sekunden pro Tile timePerTile: 8, // Sekunden pro Tile
fuel: 100, fuel: 100,
@@ -595,7 +595,6 @@ export default {
async initializeMotorSound() { async initializeMotorSound() {
// AudioContext wird erst bei erster Benutzerinteraktion erstellt // AudioContext wird erst bei erster Benutzerinteraktion erstellt
if (import.meta.env?.DEV) console.log('[Audio] Lazy-Init: MotorSound wird bei erster Benutzerinteraktion initialisiert');
}, },
async initAudioOnUserInteraction() { async initAudioOnUserInteraction() {
@@ -609,12 +608,9 @@ export default {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
window.__TaxiAudioContext = this.audioContext; window.__TaxiAudioContext = this.audioContext;
} }
if (import.meta.env?.DEV) console.log('[Audio] init: Context erstellt. state=', this.audioContext.state, 'rate=', this.audioContext.sampleRate);
// Autoplay-Policy: Context sofort im User-Gesture fortsetzen // Autoplay-Policy: Context sofort im User-Gesture fortsetzen
if (this.audioContext.state === 'suspended') { if (this.audioContext.state === 'suspended') {
if (import.meta.env?.DEV) console.log('[Audio] init: resume() versuchen');
await this.audioContext.resume(); await this.audioContext.resume();
if (import.meta.env?.DEV) console.log('[Audio] init: state nach resume=', this.audioContext.state);
} }
if (!this.motorSound) { if (!this.motorSound) {
// Ggf. globalen Motor übernehmen // Ggf. globalen Motor übernehmen
@@ -627,11 +623,9 @@ export default {
window.__TaxiMotorSound = this.motorSound; window.__TaxiMotorSound = this.motorSound;
} }
} }
if (import.meta.env?.DEV) console.log('[Audio] init: MotorSound initialisiert');
// Falls bereits Bewegung anliegt, Sound direkt starten // Falls bereits Bewegung anliegt, Sound direkt starten
const speedKmh = this.taxi.speed * 5; const speedKmh = this.taxi.speed * 5;
if (speedKmh > 0) { if (speedKmh > 0) {
if (import.meta.env?.DEV) console.log('[Audio] init: direkter Start. speedKmh=', speedKmh);
this.motorSound.start(); this.motorSound.start();
// Sofort Parameter setzen // Sofort Parameter setzen
const speedFactor = Math.min(speedKmh / 120, 1); const speedFactor = Math.min(speedKmh / 120, 1);
@@ -650,7 +644,6 @@ export default {
const speedKmh = this.taxi.speed * 5; // Geschwindigkeit in km/h const speedKmh = this.taxi.speed * 5; // Geschwindigkeit in km/h
const isMoving = speedKmh > 0; const isMoving = speedKmh > 0;
if (import.meta.env?.DEV) console.log('[Audio] update:', { speedKmh, isMoving, ctx: this.audioContext?.state, playing: this.motorSound.isPlaying });
// Start NICHT hier (kein User-Gesture). Stop mit 1s Verzögerung, wenn still // Start NICHT hier (kein User-Gesture). Stop mit 1s Verzögerung, wenn still
if (!isMoving) { if (!isMoving) {
@@ -659,7 +652,6 @@ export default {
// Nur stoppen, wenn weiterhin keine Bewegung // Nur stoppen, wenn weiterhin keine Bewegung
const still = (this.taxi.speed * 5) <= 0; const still = (this.taxi.speed * 5) <= 0;
if (still && this.motorSound && this.motorSound.isPlaying) { if (still && this.motorSound && this.motorSound.isPlaying) {
if (import.meta.env?.DEV) console.log('[Audio] update: delayed motorSound.stop()');
this.motorSound.stop(); this.motorSound.stop();
} }
this.motorStopTimeout = null; this.motorStopTimeout = null;
@@ -678,7 +670,6 @@ export default {
const speedFactor = Math.min(speedKmh / 120, 1); // 0-1 basierend auf 0-120 km/h const speedFactor = Math.min(speedKmh / 120, 1); // 0-1 basierend auf 0-120 km/h
const motorSpeed = 0.3 + (speedFactor * 0.3); // 0.3 bis 1.5 const motorSpeed = 0.3 + (speedFactor * 0.3); // 0.3 bis 1.5
const volume = 0.1 + (speedFactor * 0.4); // 0.1 bis 0.5 const volume = 0.1 + (speedFactor * 0.4); // 0.1 bis 0.5
if (import.meta.env?.DEV) console.log('[Audio] update: params', { motorSpeed, volume });
this.motorSound.setSpeed(motorSpeed); this.motorSound.setSpeed(motorSpeed);
this.motorSound.setVolume(volume); this.motorSound.setVolume(volume);
@@ -688,12 +679,22 @@ export default {
cleanup() { cleanup() {
if (this.gameLoop) { if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop); cancelAnimationFrame(this.gameLoop);
this.gameLoop = null;
} }
if (this.motorSound) { this.motorSound.stop(); } if (this.motorSound) { this.motorSound.stop(); }
if (this.motorStopTimeout) { if (this.motorStopTimeout) {
clearTimeout(this.motorStopTimeout); clearTimeout(this.motorStopTimeout);
this.motorStopTimeout = null; this.motorStopTimeout = null;
} }
// Cleanup aller Timeouts
if (this.passengerGenerationTimeout) {
clearTimeout(this.passengerGenerationTimeout);
this.passengerGenerationTimeout = null;
}
if (this.crashDialogTimeout) {
clearTimeout(this.crashDialogTimeout);
this.crashDialogTimeout = null;
}
// AudioContext bleibt global erhalten, nicht schließen // AudioContext bleibt global erhalten, nicht schließen
document.removeEventListener('keydown', this.handleKeyDown); document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp); document.removeEventListener('keyup', this.handleKeyUp);
@@ -703,6 +704,10 @@ export default {
document.removeEventListener('keydown', this.audioUnlockHandler, { capture: true }); document.removeEventListener('keydown', this.audioUnlockHandler, { capture: true });
this.audioUnlockHandler = null; this.audioUnlockHandler = null;
} }
// Cleanup von Listen und Sets
this.waitingPassengersList = [];
this.loadedPassengersList = [];
this.occupiedHouses.clear();
}, },
generateLevel() { generateLevel() {
@@ -736,7 +741,7 @@ export default {
this.generateWaitingPassenger(); this.generateWaitingPassenger();
} else { } else {
// Falls die Map noch nicht geladen ist, versuche es in 2 Sekunden erneut // Falls die Map noch nicht geladen ist, versuche es in 2 Sekunden erneut
setTimeout(() => { this.passengerGenerationTimeout = setTimeout(() => {
this.generateWaitingPassenger(); this.generateWaitingPassenger();
}, 2000); }, 2000);
} }
@@ -745,36 +750,39 @@ export default {
generateWaitingPassenger() { generateWaitingPassenger() {
if (!this.currentMap || !Array.isArray(this.currentMap.tileHouses)) { if (!this.currentMap || !Array.isArray(this.currentMap.tileHouses)) {
// Versuche es in 2 Sekunden erneut // Versuche es in 2 Sekunden erneut
setTimeout(() => { this.passengerGenerationTimeout = setTimeout(() => {
this.generateWaitingPassenger(); this.generateWaitingPassenger();
}, 2000); }, 2000);
return; return;
} }
// Finde alle verfügbaren Häuser auf der Karte // Erstelle Liste aller Tiles mit Häusern
const availableHouses = []; const tilesWithHouses = this.getTilesWithHouses();
const houses = this.currentMap.tileHouses || [];
houses.forEach((house, houseIndex) => { if (tilesWithHouses.length === 0) {
const houseId = `${house.x}-${house.y}-${houseIndex}`; console.log('Keine Tiles mit Häusern gefunden');
if (!this.occupiedHouses.has(houseId)) {
availableHouses.push({
houseId,
x: house.x,
y: house.y,
houseIndex,
house
});
}
});
// Wenn keine freien Häuser verfügbar, generiere keinen Passagier
if (availableHouses.length === 0) {
return; return;
} }
// Wähle zufälliges Haus // Wähle zufälliges Tile mit Häusern
const selectedHouse = availableHouses[Math.floor(Math.random() * availableHouses.length)]; const selectedTile = tilesWithHouses[Math.floor(Math.random() * tilesWithHouses.length)];
// Finde alle Häuser auf diesem Tile
const housesOnTile = this.currentMap.tileHouses.filter(house =>
house.x === selectedTile.x && house.y === selectedTile.y
);
// Wähle zufälliges Haus auf diesem Tile
const selectedHouse = housesOnTile[Math.floor(Math.random() * housesOnTile.length)];
const houseIndex = this.currentMap.tileHouses.findIndex(h => h === selectedHouse);
const houseId = `${selectedHouse.x}-${selectedHouse.y}-${houseIndex}`;
// Prüfe ob das Haus bereits belegt ist
if (this.occupiedHouses.has(houseId)) {
// Haus bereits belegt, versuche es erneut
this.generateWaitingPassenger();
return;
}
// Finde die Straße für dieses spezifische Haus // Finde die Straße für dieses spezifische Haus
let streetName = "Unbekannte Straße"; let streetName = "Unbekannte Straße";
@@ -784,7 +792,7 @@ export default {
const houseTile = this.currentMap.tileStreets?.find(ts => ts.x === selectedHouse.x && ts.y === selectedHouse.y); const houseTile = this.currentMap.tileStreets?.find(ts => ts.x === selectedHouse.x && ts.y === selectedHouse.y);
if (houseTile) { if (houseTile) {
// Bestimme die Straße basierend auf der Haus-Ecke // Bestimme die Straße basierend auf der Haus-Ecke
const corner = selectedHouse.house.corner; const corner = selectedHouse.corner;
if (corner === 'lo' || corner === 'ro') { if (corner === 'lo' || corner === 'ro') {
// Horizontale Straße // Horizontale Straße
if (houseTile.streetNameH && houseTile.streetNameH.name) { if (houseTile.streetNameH && houseTile.streetNameH.name) {
@@ -797,60 +805,131 @@ export default {
} }
} }
} }
if (streetName === 'Unbekannte Straße') {
console.log(houseTile)
}
// Finde die Hausnummer für dieses spezifische Haus // Finde die Hausnummer für dieses spezifische Haus
const houseKey = `${selectedHouse.x},${selectedHouse.y},${selectedHouse.house.corner}`; const houseKey = `${selectedHouse.x},${selectedHouse.y},${selectedHouse.corner}`;
houseNumber = this.houseNumbers[houseKey] || 1; houseNumber = this.houseNumbers[houseKey] || 1;
// Generiere Namen und Geschlecht
const nameData = this.generatePassengerName();
// Erstelle Passagier // Erstelle Passagier
const passenger = { const passenger = {
id: Date.now() + Math.random(), // Eindeutige ID id: Date.now() + Math.random(), // Eindeutige ID
name: this.generatePassengerName(), name: nameData.fullName,
gender: nameData.gender,
location: `${streetName} ${houseNumber}`, location: `${streetName} ${houseNumber}`,
houseId: selectedHouse.houseId, houseId: houseId,
tileX: selectedHouse.x, tileX: selectedHouse.x,
tileY: selectedHouse.y, tileY: selectedHouse.y,
houseIndex: selectedHouse.houseIndex, houseIndex: houseIndex,
houseCorner: selectedHouse.house.corner, // Speichere auch die Ecke houseCorner: selectedHouse.corner, // Speichere auch die Ecke
imageIndex: this.generatePassengerImageIndex(), // Zufälliger Bildindex 1-6 imageIndex: this.generatePassengerImageIndex(nameData.gender), // Bildindex basierend auf Geschlecht
createdAt: Date.now() // Zeitstempel der Erstellung createdAt: Date.now() // Zeitstempel der Erstellung
}; };
// Füge Passagier zur Liste hinzu und markiere Haus als belegt // Füge Passagier zur Liste hinzu und markiere Haus als belegt
this.waitingPassengersList.push(passenger); this.waitingPassengersList.push(passenger);
this.occupiedHouses.add(selectedHouse.houseId); this.occupiedHouses.add(houseId);
console.log('Neuer wartender Passagier generiert:', passenger);
}, },
generatePassengerName() { generatePassengerName() {
const firstNames = [ const maleNames = [
'Anna', 'Max', 'Lisa', 'Tom', 'Sarah', 'Ben', 'Emma', 'Lukas', 'Julia', 'Paul', 'Max', 'Tom', 'Ben', 'Lukas', 'Paul', 'Felix', 'Jonas', 'Tim', 'Noah', 'Finn',
'Marie', 'Felix', 'Sophie', 'Jonas', 'Laura', 'Tim', 'Mia', 'Noah', 'Emma', 'Finn' 'Leon', 'Liam', 'Elias', 'Henry', 'Anton', 'Theo', 'Emil', 'Oskar', 'Mats', 'Luis',
'Alexander', 'Sebastian', 'David', 'Daniel', 'Michael', 'Christian', 'Andreas', 'Stefan',
'Markus', 'Thomas', 'Matthias', 'Martin', 'Peter', 'Klaus', 'Wolfgang', 'Jürgen',
'Rainer', 'Uwe', 'Dieter', 'Hans', 'Günter', 'Karl', 'Walter', 'Helmut', 'Manfred',
'Jens', 'Sven', 'Tobias', 'Nils', 'Jan', 'Marcel', 'Kevin', 'Dennis', 'Marco',
'Philipp', 'Simon', 'Florian', 'Dominik', 'Patrick', 'Oliver', 'Timo', 'Björn'
];
const femaleNames = [
'Anna', 'Lisa', 'Sarah', 'Emma', 'Julia', 'Marie', 'Sophie', 'Laura', 'Mia', 'Emma',
'Lina', 'Mila', 'Hannah', 'Lea', 'Emilia', 'Clara', 'Leni', 'Marlene', 'Frieda', 'Ida',
'Katharina', 'Christina', 'Nicole', 'Stefanie', 'Jennifer', 'Jessica', 'Melanie', 'Sandra',
'Andrea', 'Petra', 'Monika', 'Ursula', 'Brigitte', 'Ingrid', 'Gisela', 'Elisabeth',
'Maria', 'Barbara', 'Helga', 'Gertrud', 'Irmgard', 'Hildegard', 'Waltraud', 'Renate',
'Sabine', 'Kerstin', 'Anja', 'Tanja', 'Nadine', 'Katrin', 'Silke', 'Birgit',
'Susanne', 'Martina', 'Karin', 'Heike', 'Doris', 'Elke', 'Bärbel', 'Jutta',
'Gabriele', 'Angelika', 'Christine', 'Annette', 'Beate', 'Cornelia', 'Diana', 'Eva'
]; ];
const lastNames = [ const lastNames = [
'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Weber', 'Meyer', 'Wagner', 'Becker', 'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Weber', 'Meyer', 'Wagner', 'Becker',
'Schulz', 'Hoffmann', 'Schäfer', 'Koch', 'Bauer', 'Richter', 'Klein', 'Wolf' 'Schulz', 'Hoffmann', 'Schäfer', 'Koch', 'Bauer', 'Richter', 'Klein', 'Wolf',
'Schröder', 'Neumann', 'Schwarz', 'Zimmermann', 'Braun', 'Hofmann', 'Lange', 'Schmitt',
'Werner', 'Schmitz', 'Krause', 'Meier', 'Lehmann', 'Köhler', 'Huber', 'Kaiser',
'Fuchs', 'Peters', 'Lang', 'Scholz', 'Möller', 'Weiß', 'Jung', 'Hahn', 'Schubert',
'Schuster', 'Winkler', 'Berger', 'Roth', 'Beck', 'Lorenz', 'Baumann', 'Franke',
'Albrecht', 'Ludwig', 'Winter', 'Simon', 'Kraus', 'Vogt', 'Stein', 'Jäger',
'Otto', 'Sommer', 'Groß', 'Seidel', 'Heinrich', 'Brandt', 'Haas', 'Schreiber',
'Graf', 'Schulte', 'Dietrich', 'Ziegler', 'Kuhn', 'Pohl', 'Engel', 'Horn'
]; ];
// Wähle zufällig zwischen männlich und weiblich
const isMale = Math.random() < 0.5;
const firstNames = isMale ? maleNames : femaleNames;
const firstName = firstNames[Math.floor(Math.random() * firstNames.length)]; const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)]; const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
return `${firstName} ${lastName}`;
return {
fullName: `${firstName} ${lastName}`,
gender: isMale ? 'male' : 'female'
};
}, },
generatePassengerImageIndex() { generatePassengerImageIndex(gender) {
// Zufälliger Bildindex 1-6 (1-3 männlich, 4-6 weiblich) // Bildindex basierend auf Geschlecht (1-3 männlich, 4-6 weiblich)
return Math.floor(Math.random() * 6) + 1; if (gender === 'male') {
return Math.floor(Math.random() * 3) + 1; // 1-3
} else {
return Math.floor(Math.random() * 3) + 4; // 4-6
}
},
getTilesWithHouses() {
// Erstelle Liste aller Tiles, die Häuser haben
const tilesWithHouses = [];
const houses = this.currentMap.tileHouses || [];
// Sammle alle eindeutigen Tile-Koordinaten
const tileSet = new Set();
houses.forEach(house => {
const tileKey = `${house.x}-${house.y}`;
if (!tileSet.has(tileKey)) {
tileSet.add(tileKey);
tilesWithHouses.push({ x: house.x, y: house.y });
}
});
return tilesWithHouses;
}, },
calculateShortestPath(startX, startY, endX, endY) { calculateShortestPath(startX, startY, endX, endY) {
// Berechne Manhattan-Distanz (kürzester Weg in einem Raster) // Berechne Manhattan-Distanz (kürzester Weg in einem Raster)
const distance = Math.abs(endX - startX) + Math.abs(endY - startY); const distance = Math.abs(endX - startX) + Math.abs(endY - startY);
return distance;
// Wenn Start und Ziel identisch sind, mindestens 1 Tile Distanz
// (da das Taxi das Tile verlassen und wieder betreten muss)
if (distance === 0) {
return 1;
}
// Für unterschiedliche Tiles: Distanz + 1 (Start-Tile wird mitgezählt)
return distance + 1;
}, },
calculateBonusAndTime(startX, startY, endX, endY) { calculateBonusAndTime(startX, startY, endX, endY) {
const shortestPath = this.calculateShortestPath(startX, startY, endX, endY); const shortestPath = this.calculateShortestPath(startX, startY, endX, endY);
const bonus = shortestPath * this.bonusMultiplier; const bonus = shortestPath * this.bonusMultiplier;
const maxTime = shortestPath * this.timePerTile; const maxTime = shortestPath * this.timePerTile;
console.log(`Wegstrecke berechnet: Start(${startX},${startY}) -> Ziel(${endX},${endY}) = ${shortestPath} Tiles, Bonus: ${bonus}, Zeit: ${maxTime}s`);
return { return {
shortestPath, shortestPath,
bonus, bonus,
@@ -860,60 +939,66 @@ export default {
generatePassengerDestination() { generatePassengerDestination() {
// Generiere ein zufälliges Ziel für den Passagier // Generiere ein zufälliges Ziel für den Passagier
if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets) || !Array.isArray(this.currentMap.tileHouses)) return null; if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets) || !Array.isArray(this.currentMap.tileHouses)) {
return null;
}
// Finde alle Straßen-Tiles auf der Karte // Finde alle Straßen-Tiles auf der Karte
const streets = this.currentMap.tileStreets || []; const streets = this.currentMap.tileStreets || [];
const houses = this.currentMap.tileHouses || []; const houses = this.currentMap.tileHouses || [];
// Versuche maximal 10 Mal, ein gültiges Ziel zu finden // Sammle alle gültigen Ziele vor der zufälligen Auswahl
for (let attempt = 0; attempt < 10; attempt++) { const validDestinations = [];
// Wähle zufällige Straße
const randomStreet = streets[Math.floor(Math.random() * streets.length)];
// Prüfe ob diese Straße Häuser hat for (const street of streets) {
const housesOnThisTile = houses.filter(house => house.x === randomStreet.x && house.y === randomStreet.y); // Prüfe ob diese Straße Häuser hat
const housesOnThisTile = houses.filter(house => house.x === street.x && house.y === street.y);
if (housesOnThisTile.length > 0) { if (housesOnThisTile.length > 0) {
// Wähle zufälliges Haus auf diesem Tile for (const house of housesOnThisTile) {
const selectedHouse = housesOnThisTile[Math.floor(Math.random() * housesOnThisTile.length)]; const corner = house.corner;
let streetName = null;
// Bestimme die Straße basierend auf der Haus-Ecke if (corner === 'lo' || corner === 'ro') {
let streetName = "Unbekannte Straße"; // Horizontale Straße
let houseNumber = 1; if (street.streetNameH && street.streetNameH.name) {
streetName = street.streetNameH.name;
}
} else if (corner === 'lu' || corner === 'ru') {
// Vertikale Straße
if (street.streetNameV && street.streetNameV.name) {
streetName = street.streetNameV.name;
}
}
const corner = selectedHouse.corner; // Nur gültige Straßen hinzufügen
if (streetName && streetName.trim() !== '') {
// Hole Hausnummern nur für dieses spezifische Tile
const key = `${street.x},${street.y},${house.corner}`;
const houseNumber = this.houseNumbers[key];
if (corner === 'lo' || corner === 'ro') { if (houseNumber != null) {
// Horizontale Straße validDestinations.push({
if (randomStreet.streetNameH && randomStreet.streetNameH.name) { streetName,
streetName = randomStreet.streetNameH.name; houseNumber,
} location: `${streetName} ${houseNumber}`,
} else if (corner === 'lu' || corner === 'ru') { tileX: street.x,
// Vertikale Straße tileY: street.y,
if (randomStreet.streetNameV && randomStreet.streetNameV.name) { houseCorner: house.corner
streetName = randomStreet.streetNameV.name; });
} }
} }
}
}
}
// Finde die Hausnummer für diese Straße
const houseNumbers = this.getHouseNumbersForStreet(streetName);
if (houseNumbers.length > 0) {
houseNumber = houseNumbers[Math.floor(Math.random() * houseNumbers.length)];
}
return { if (validDestinations.length > 0) {
streetName, // Wähle zufälliges gültiges Ziel
houseNumber, const selectedDestination = validDestinations[Math.floor(Math.random() * validDestinations.length)];
location: `${streetName} ${houseNumber}`, return selectedDestination;
tileX: randomStreet.x,
tileY: randomStreet.y,
houseCorner: selectedHouse.corner
};
}
} }
// Wenn nach 10 Versuchen kein Ziel gefunden wurde
return null; return null;
}, },
@@ -941,7 +1026,6 @@ export default {
if (timeSincePickup >= 2000) { if (timeSincePickup >= 2000) {
passenger.timerActive = true; passenger.timerActive = true;
passenger.lastUpdateTime = now; passenger.lastUpdateTime = now;
console.log('Timer für Passagier', passenger.name, 'aktiviert nach', timeSincePickup, 'ms');
} }
return; // Timer noch nicht aktiv return; // Timer noch nicht aktiv
} }
@@ -1106,6 +1190,10 @@ export default {
startGame() { startGame() {
this.gameRunning = true; this.gameRunning = true;
// Stoppe bestehende Game-Loop falls vorhanden
if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop);
}
this.gameLoop = requestAnimationFrame(this.update); this.gameLoop = requestAnimationFrame(this.update);
}, },
@@ -1264,6 +1352,11 @@ export default {
}, },
getHousePositionOnTile(tileX, tileY, corner) { getHousePositionOnTile(tileX, tileY, corner) {
// Prüfe ob das Taxi auf dem gleichen Tile ist wie das Haus
if (this.currentTile.col !== tileX || this.currentTile.row !== tileY) {
return null;
}
// Berechne die Position des Hauses basierend auf der Ecke // Berechne die Position des Hauses basierend auf der Ecke
// Diese Methode muss exakt mit drawHousesOnMainCanvas übereinstimmen // Diese Methode muss exakt mit drawHousesOnMainCanvas übereinstimmen
const tileSize = 500; // Tile-Größe const tileSize = 500; // Tile-Größe
@@ -1306,19 +1399,53 @@ export default {
const HOUSE_H = 150; // Haushöhe const HOUSE_H = 150; // Haushöhe
const maxDistance = 100; // 100px Entfernung const maxDistance = 100; // 100px Entfernung
// Einfache Distanzberechnung zum Hauszentrum // Berechne die Entfernung zur Tile-Mitte
const houseCenterX = houseX + HOUSE_W / 2; const tileCenterX = 250; // Mitte des 500px Canvas
const houseCenterY = houseY + HOUSE_H / 2; const tileCenterY = 250; // Mitte des 500px Canvas
const distance = Math.sqrt( const distanceToTileCenter = Math.sqrt(
Math.pow(taxiX - houseCenterX, 2) + Math.pow(taxiY - houseCenterY, 2) Math.pow(taxiX - tileCenterX, 2) + Math.pow(taxiY - tileCenterY, 2)
); );
// Wenn das Taxi zu weit von der Tile-Mitte entfernt ist, keine Aktion
const maxDistanceFromCenter = 200; // 200px von der Mitte entfernt
if (distanceToTileCenter > maxDistanceFromCenter) {
return false;
}
return distance <= maxDistance; // Richtungsabhängige Erkennung basierend auf der Haus-Ecke
let isNear = false;
if (corner === 'lo' || corner === 'ro') {
// Horizontale Straße - Taxi muss links oder rechts vom Haus sein
const houseCenterX = houseX + HOUSE_W / 2;
const houseCenterY = houseY + HOUSE_H / 2;
// Prüfe horizontale Distanz (Taxi muss seitlich vom Haus sein)
const horizontalDistance = Math.abs(taxiX - houseCenterX);
const verticalDistance = Math.abs(taxiY - houseCenterY);
// Taxi muss horizontal nah genug sein, aber vertikal kann es weiter weg sein
isNear = horizontalDistance <= maxDistance && verticalDistance <= maxDistance * 1.5;
} else if (corner === 'lu' || corner === 'ru') {
// Vertikale Straße - Taxi muss oberhalb oder unterhalb vom Haus sein
const houseCenterX = houseX + HOUSE_W / 2;
const houseCenterY = houseY + HOUSE_H / 2;
// Prüfe vertikale Distanz (Taxi muss oberhalb/unterhalb vom Haus sein)
const horizontalDistance = Math.abs(taxiX - houseCenterX);
const verticalDistance = Math.abs(taxiY - houseCenterY);
// Taxi muss vertikal nah genug sein, aber horizontal kann es weiter weg sein
isNear = verticalDistance <= maxDistance && horizontalDistance <= maxDistance * 1.5;
}
console.log(`Haus-Erkennung: Corner=${corner}, Taxi(${taxiX},${taxiY}), Haus(${houseX},${houseY}), isNear=${isNear}`);
return isNear;
}, },
pickupWaitingPassenger(passenger, index) { pickupWaitingPassenger(passenger, index) {
console.log('Passagier wird eingeladen:', passenger.name);
// Entferne Passagier aus der Warteliste // Entferne Passagier aus der Warteliste
this.waitingPassengersList.splice(index, 1); this.waitingPassengersList.splice(index, 1);
@@ -1330,9 +1457,8 @@ export default {
// Generiere ein Ziel für den Passagier // Generiere ein Ziel für den Passagier
const destination = this.generatePassengerDestination(); const destination = this.generatePassengerDestination();
console.log('Generiertes Ziel:', destination);
if (destination) { if (destination && destination.streetName && destination.streetName !== "Unbekannte Straße") {
// Berechne Bonus und Zeit basierend auf kürzestem Weg // Berechne Bonus und Zeit basierend auf kürzestem Weg
const startX = passenger.tileX; const startX = passenger.tileX;
const startY = passenger.tileY; const startY = passenger.tileY;
@@ -1352,10 +1478,7 @@ export default {
lastUpdateTime: 0, // Timer-Aktualisierung erst nach expliziter Aktivierung lastUpdateTime: 0, // Timer-Aktualisierung erst nach expliziter Aktivierung
timerActive: false // Flag um Timer-Aktivierung zu kontrollieren timerActive: false // Flag um Timer-Aktivierung zu kontrollieren
}); });
console.log('Passagier zur geladenen Liste hinzugefügt. Aktuelle Anzahl:', this.loadedPassengersList.length);
console.log('Bonus-Daten:', bonusData);
} else { } else {
console.log('Kein Ziel generiert - Passagier wird nicht hinzugefügt');
} }
// Optional: Sound-Effekt oder Nachricht // Optional: Sound-Effekt oder Nachricht
@@ -1369,40 +1492,26 @@ export default {
// Prüfe ob geladene Passagiere abgesetzt werden können // Prüfe ob geladene Passagiere abgesetzt werden können
if (this.loadedPassengersList.length === 0) return; if (this.loadedPassengersList.length === 0) return;
console.log('Prüfe Dropoff für', this.loadedPassengersList.length, 'geladene Passagiere');
console.log('Aktuelle Tile-Position:', { col: this.currentTile.col, row: this.currentTile.row });
// Prüfe alle geladene Passagiere // Prüfe alle geladene Passagiere
for (let i = this.loadedPassengersList.length - 1; i >= 0; i--) { for (let i = this.loadedPassengersList.length - 1; i >= 0; i--) {
const passenger = this.loadedPassengersList[i]; const passenger = this.loadedPassengersList[i];
const destination = passenger.destination; const destination = passenger.destination;
console.log('Prüfe Passagier:', passenger.name, 'Ziel:', destination.location, 'Tile:', { x: destination.tileX, y: destination.tileY });
// Prüfe ob das Taxi am Zielort ist // Prüfe ob das Taxi am Zielort ist
if (this.currentTile.col === destination.tileX && this.currentTile.row === destination.tileY) { if (this.currentTile.col === destination.tileX && this.currentTile.row === destination.tileY) {
console.log('Taxi ist am Zielort');
// Berechne die Position des Zielhauses auf dem aktuellen Tile // Berechne die Position des Zielhauses auf dem aktuellen Tile
const housePosition = this.getHousePositionOnTile(destination.tileX, destination.tileY, destination.houseCorner); const housePosition = this.getHousePositionOnTile(destination.tileX, destination.tileY, destination.houseCorner);
if (housePosition) { if (housePosition) {
console.log('Haus-Position gefunden:', housePosition);
// Prüfe ob das Taxi in der Nähe des Zielhauses ist // Prüfe ob das Taxi in der Nähe des Zielhauses ist
const isNear = this.isTaxiNearHouse(housePosition, destination.houseCorner); const isNear = this.isTaxiNearHouse(housePosition, destination.houseCorner);
console.log('Ist Taxi nah genug?', isNear);
if (isNear) { if (isNear) {
// Setze Passagier ab // Setze Passagier ab
console.log('Setze Passagier ab:', passenger.name); console.log(' Passagier abgesetzt:', passenger.name, 'an', destination.location);
this.dropoffLoadedPassenger(passenger, i); this.dropoffLoadedPassenger(passenger, i);
break; // Nur einen Passagier pro Frame absetzen break; // Nur einen Passagier pro Frame absetzen
} }
} else {
console.log('Keine Haus-Position gefunden');
} }
} else {
console.log('Taxi ist nicht am Zielort');
} }
} }
}, },
@@ -1706,7 +1815,7 @@ export default {
this.$root?.$refs?.messageDialog?.open?.(crashMessage, crashTitle, {}, this.handleCrashDialogClose); this.$root?.$refs?.messageDialog?.open?.(crashMessage, crashTitle, {}, this.handleCrashDialogClose);
// Test: Direkter Aufruf nach 3 Sekunden (falls Dialog-Callback nicht funktioniert) // Test: Direkter Aufruf nach 3 Sekunden (falls Dialog-Callback nicht funktioniert)
setTimeout(() => { this.crashDialogTimeout = setTimeout(() => {
console.log('Test: Automatischer Reset nach 3 Sekunden'); console.log('Test: Automatischer Reset nach 3 Sekunden');
this.handleCrashDialogClose(); this.handleCrashDialogClose();
}, 3000); }, 3000);
@@ -2343,16 +2452,9 @@ export default {
async handleCanvasClick(event) { async handleCanvasClick(event) {
// AudioContext bei erster Benutzerinteraktion initialisieren // AudioContext bei erster Benutzerinteraktion initialisieren
if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: start, hasCtx=', !!this.audioContext);
this.ensureAudioUnlockedInEvent(); this.ensureAudioUnlockedInEvent();
if (this.audioContext && this.audioContext.state === 'suspended') {
if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: resume()');
this.audioContext.resume().catch(() => {});
if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: state=', this.audioContext.state);
}
// Motor ggf. direkt starten (Klick ist User-Geste) // Motor ggf. direkt starten (Klick ist User-Geste)
if (this.motorSound && !this.motorSound.isPlaying) { if (this.motorSound && !this.motorSound.isPlaying) {
if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: motorSound.start()');
this.motorSound.start(); this.motorSound.start();
} }
}, },
@@ -2361,15 +2463,9 @@ export default {
this.keys[event.key] = true; this.keys[event.key] = true;
// AudioContext bei erster Benutzerinteraktion initialisieren // AudioContext bei erster Benutzerinteraktion initialisieren
if (import.meta.env?.DEV) console.log('[Audio] KeyDown: key=', event.key, 'hasCtx=', !!this.audioContext);
this.ensureAudioUnlockedInEvent(); this.ensureAudioUnlockedInEvent();
if (this.audioContext && this.audioContext.state === 'suspended') {
if (import.meta.env?.DEV) console.log('[Audio] KeyDown: resume()');
this.audioContext.resume().catch(() => {});
}
// Bei Beschleunigungs-Key Motor starten (User-Geste garantiert) // Bei Beschleunigungs-Key Motor starten (User-Geste garantiert)
if ((event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') && this.motorSound && !this.motorSound.isPlaying) { if ((event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') && this.motorSound && !this.motorSound.isPlaying) {
if (import.meta.env?.DEV) console.log('[Audio] KeyDown: motorSound.start()');
this.motorSound.start(); this.motorSound.start();
// Direkt Parameter setzen, um hörbares Feedback ohne Verzögerung zu bekommen // Direkt Parameter setzen, um hörbares Feedback ohne Verzögerung zu bekommen
const speedKmh = Math.max(5, this.taxi.speed * 5); const speedKmh = Math.max(5, this.taxi.speed * 5);
@@ -2384,6 +2480,11 @@ export default {
}, },
ensureAudioUnlockedInEvent() { ensureAudioUnlockedInEvent() {
// Nur einmal initialisieren
if (this.audioContext && this.motorSound) {
return; // Bereits initialisiert
}
// Muss synchron im Event-Handler laufen (ohne await), damit Policies greifen // Muss synchron im Event-Handler laufen (ohne await), damit Policies greifen
if (!this.audioContext) { if (!this.audioContext) {
this.audioContext = window.__TaxiAudioContext || new (window.AudioContext || window.webkitAudioContext)(); this.audioContext = window.__TaxiAudioContext || new (window.AudioContext || window.webkitAudioContext)();
@@ -2429,6 +2530,17 @@ export default {
this.taxi.y = 250; this.taxi.y = 250;
this.taxi.angle = 0; this.taxi.angle = 0;
this.taxi.speed = 0; this.taxi.speed = 0;
// Cleanup bestehender Timeouts
if (this.passengerGenerationTimeout) {
clearTimeout(this.passengerGenerationTimeout);
this.passengerGenerationTimeout = null;
}
if (this.crashDialogTimeout) {
clearTimeout(this.crashDialogTimeout);
this.crashDialogTimeout = null;
}
this.generateLevel(); this.generateLevel();
this.initializePassengerGeneration(); this.initializePassengerGeneration();
}, },
@@ -3001,6 +3113,7 @@ export default {
.lives { font-weight: 700; color: #d60000; } .lives { font-weight: 700; color: #d60000; }
.fuel { font-weight: 600; color: #0a7c00; margin-left: 8px; } .fuel { font-weight: 600; color: #0a7c00; margin-left: 8px; }
.score { font-weight: 700; color: #ffa500; margin-left: 8px; }
.speed-violations { font-weight: 700; color: #8a2be2; margin-left: 8px; } .speed-violations { font-weight: 700; color: #8a2be2; margin-left: 8px; }
.speed-value { .speed-value {