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

View File

@@ -145,15 +145,20 @@ const store = createStore({
currentSocket.disconnect();
}
commit('setConnectionStatus', 'connecting');
const socketIoUrl = import.meta.env.VITE_SOCKET_IO_URL || import.meta.env.VITE_API_BASE_URL;
console.log('🔌 Initializing Socket.io connection to:', socketIoUrl);
// Socket.io URL für lokale Entwicklung und Produktion
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, {
secure: true,
transports: ['websocket', 'polling']
});
socket.on('connect', () => {
console.log('✅ Socket.io connected successfully');
retryCount = 0; // Reset retry counter on successful connection
commit('setConnectionStatus', 'connected');
const idForSocket = state.user?.hashedId || state.user?.id;
@@ -161,14 +166,11 @@ const store = createStore({
});
socket.on('disconnect', (reason) => {
console.warn('❌ Socket.io disconnected:', reason);
commit('setConnectionStatus', 'disconnected');
retryConnection(connectSocket);
});
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');
});
@@ -179,30 +181,36 @@ const store = createStore({
const maxRetries = 10;
const retryConnection = (reconnectFn) => {
if (retryCount >= maxRetries) {
console.error('❌ Max retry attempts reached for Socket.io');
return;
}
retryCount++;
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(() => {
reconnectFn();
}, delay);
};
connectSocket();
} else {
console.log("User is not logged in or user data is not available.");
}
},
initializeDaemonSocket({ commit, state }) {
if (!state.isLoggedIn || !state.user) {
console.log("User is not logged in or user data is not available for Daemon WebSocket.");
return;
}
const daemonUrl = import.meta.env.VITE_DAEMON_SOCKET || 'wss://www.your-part.de:4551';
console.log('🔌 Initializing Daemon WebSocket connection to:', daemonUrl);
// Daemon URL für lokale Entwicklung und Produktion
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 = () => {
// Protokoll-Fallback: zuerst mit Subprotokoll, dann ohne
@@ -215,13 +223,11 @@ const store = createStore({
const daemonSocket = currentProtocol
? new WebSocket(daemonUrl, currentProtocol)
: new WebSocket(daemonUrl);
console.log('🔌 Protocol:', currentProtocol ?? 'none (fallback)');
let opened = false;
daemonSocket.onopen = () => {
opened = true;
console.log('✅ Daemon WebSocket connected successfully');
retryCount = 0; // Reset retry counter on successful connection
const payload = JSON.stringify({
user_id: state.user.id,
@@ -232,17 +238,9 @@ const store = createStore({
};
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
if (!opened && attemptIndex < protocols.length - 1) {
attemptIndex += 1;
console.warn('🔄 Fallback: versuche erneut ohne Subprotokoll...');
tryConnectWithProtocol();
return;
}
@@ -250,23 +248,9 @@ const store = createStore({
};
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
if (!opened && attemptIndex < protocols.length - 1) {
attemptIndex += 1;
console.warn('🔄 Fallback: versuche erneut ohne Subprotokoll (onerror)...');
tryConnectWithProtocol();
return;
}
@@ -281,21 +265,17 @@ const store = createStore({
try {
const data = JSON.parse(message);
// Handle daemon messages here
console.log('📨 Daemon message received:', data);
} catch (error) {
console.error("Error parsing daemon message:", error);
// Error parsing daemon message
}
}
});
commit('setDaemonSocket', daemonSocket);
} 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
if (attemptIndex < protocols.length - 1) {
attemptIndex += 1;
console.warn('🔄 Fallback: Konstruktion ohne Subprotokoll...');
tryConnectWithProtocol();
return;
}
@@ -310,18 +290,15 @@ const store = createStore({
const maxRetries = 15; // Increased max retries
const retryConnection = (reconnectFn) => {
if (retryCount >= maxRetries) {
console.error('❌ Max retry attempts reached for Daemon WebSocket');
// Reset counter after a longer delay to allow for network recovery
setTimeout(() => {
retryCount = 0;
console.log('🔄 Resetting Daemon WebSocket retry counter - attempting reconnection');
reconnectFn();
}, 60000); // Wait 1 minute before resetting
return;
}
retryCount++;
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(() => {
reconnectFn();
}, delay);
@@ -336,7 +313,6 @@ const store = createStore({
const menu = await loadMenu();
commit('setMenu', menu);
} catch (err) {
console.error(err);
commit('setMenu', []);
}
},

View File

@@ -1,19 +1,22 @@
import axios from 'axios';
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 = () => {
// Wenn explizite Umgebungsvariable gesetzt ist, diese verwenden
if (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
return '';
};
// Debug-Informationen
console.log('🔗 API Base URL:', getApiBaseURL());
const apiClient = axios.create({
baseURL: getApiBaseURL(),
@@ -28,8 +31,6 @@ apiClient.interceptors.request.use(config => {
if (user && user.authCode) {
config.headers['userid'] = user.id;
config.headers['authcode'] = user.authCode; // Kleinschreibung!
} else {
console.log('⚠️ Keine User-Daten verfügbar');
}
return config;

View File

@@ -1,6 +1,5 @@
<template>
<div class="contenthidden">
<StatusBar />
<div class="contentscroll">
<!-- Spiel-Titel -->
<div class="game-title">
@@ -68,6 +67,7 @@
<div class="tacho-speed">
<span class="lives" title="Verbleibende Fahrzeuge"> {{ vehicleCount }}</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="redlight-counter" title="Rote Ampeln überfahren">
<span class="redlight-icon">🚦</span>
@@ -223,12 +223,9 @@
import streetCoordinates from '../../utils/streetCoordinates.js';
import { MotorSound, NoiseGenerator } from '../../assets/motorSound.js';
import apiClient from '../../utils/axios.js';
import StatusBar from '../../components/falukant/StatusBar.vue';
export default {
name: 'TaxiGame',
components: {
StatusBar
},
data() {
return {
@@ -242,6 +239,9 @@ export default {
occupiedHouses: new Set(), // Verhindert doppelte Belegung von Häusern
lastPassengerGeneration: 0,
passengerGenerationInterval: 0,
// Timeout-Referenzen für Cleanup
passengerGenerationTimeout: null,
crashDialogTimeout: null,
bonusMultiplier: 15, // Bonus pro Tile
timePerTile: 8, // Sekunden pro Tile
fuel: 100,
@@ -595,7 +595,6 @@ export default {
async initializeMotorSound() {
// 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() {
@@ -609,12 +608,9 @@ export default {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
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
if (this.audioContext.state === 'suspended') {
if (import.meta.env?.DEV) console.log('[Audio] init: resume() versuchen');
await this.audioContext.resume();
if (import.meta.env?.DEV) console.log('[Audio] init: state nach resume=', this.audioContext.state);
}
if (!this.motorSound) {
// Ggf. globalen Motor übernehmen
@@ -627,11 +623,9 @@ export default {
window.__TaxiMotorSound = this.motorSound;
}
}
if (import.meta.env?.DEV) console.log('[Audio] init: MotorSound initialisiert');
// Falls bereits Bewegung anliegt, Sound direkt starten
const speedKmh = this.taxi.speed * 5;
if (speedKmh > 0) {
if (import.meta.env?.DEV) console.log('[Audio] init: direkter Start. speedKmh=', speedKmh);
this.motorSound.start();
// Sofort Parameter setzen
const speedFactor = Math.min(speedKmh / 120, 1);
@@ -650,7 +644,6 @@ export default {
const speedKmh = this.taxi.speed * 5; // Geschwindigkeit in km/h
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
if (!isMoving) {
@@ -659,7 +652,6 @@ export default {
// Nur stoppen, wenn weiterhin keine Bewegung
const still = (this.taxi.speed * 5) <= 0;
if (still && this.motorSound && this.motorSound.isPlaying) {
if (import.meta.env?.DEV) console.log('[Audio] update: delayed motorSound.stop()');
this.motorSound.stop();
}
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 motorSpeed = 0.3 + (speedFactor * 0.3); // 0.3 bis 1.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.setVolume(volume);
@@ -688,12 +679,22 @@ export default {
cleanup() {
if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop);
this.gameLoop = null;
}
if (this.motorSound) { this.motorSound.stop(); }
if (this.motorStopTimeout) {
clearTimeout(this.motorStopTimeout);
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
document.removeEventListener('keydown', this.handleKeyDown);
document.removeEventListener('keyup', this.handleKeyUp);
@@ -703,6 +704,10 @@ export default {
document.removeEventListener('keydown', this.audioUnlockHandler, { capture: true });
this.audioUnlockHandler = null;
}
// Cleanup von Listen und Sets
this.waitingPassengersList = [];
this.loadedPassengersList = [];
this.occupiedHouses.clear();
},
generateLevel() {
@@ -736,7 +741,7 @@ export default {
this.generateWaitingPassenger();
} else {
// Falls die Map noch nicht geladen ist, versuche es in 2 Sekunden erneut
setTimeout(() => {
this.passengerGenerationTimeout = setTimeout(() => {
this.generateWaitingPassenger();
}, 2000);
}
@@ -745,36 +750,39 @@ export default {
generateWaitingPassenger() {
if (!this.currentMap || !Array.isArray(this.currentMap.tileHouses)) {
// Versuche es in 2 Sekunden erneut
setTimeout(() => {
this.passengerGenerationTimeout = setTimeout(() => {
this.generateWaitingPassenger();
}, 2000);
return;
}
// Finde alle verfügbaren Häuser auf der Karte
const availableHouses = [];
const houses = this.currentMap.tileHouses || [];
// Erstelle Liste aller Tiles mit Häusern
const tilesWithHouses = this.getTilesWithHouses();
houses.forEach((house, houseIndex) => {
const houseId = `${house.x}-${house.y}-${houseIndex}`;
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) {
if (tilesWithHouses.length === 0) {
console.log('Keine Tiles mit Häusern gefunden');
return;
}
// Wähle zufälliges Haus
const selectedHouse = availableHouses[Math.floor(Math.random() * availableHouses.length)];
// Wähle zufälliges Tile mit Häusern
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
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);
if (houseTile) {
// Bestimme die Straße basierend auf der Haus-Ecke
const corner = selectedHouse.house.corner;
const corner = selectedHouse.corner;
if (corner === 'lo' || corner === 'ro') {
// Horizontale Straße
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
const houseKey = `${selectedHouse.x},${selectedHouse.y},${selectedHouse.house.corner}`;
const houseKey = `${selectedHouse.x},${selectedHouse.y},${selectedHouse.corner}`;
houseNumber = this.houseNumbers[houseKey] || 1;
// Generiere Namen und Geschlecht
const nameData = this.generatePassengerName();
// Erstelle Passagier
const passenger = {
id: Date.now() + Math.random(), // Eindeutige ID
name: this.generatePassengerName(),
name: nameData.fullName,
gender: nameData.gender,
location: `${streetName} ${houseNumber}`,
houseId: selectedHouse.houseId,
houseId: houseId,
tileX: selectedHouse.x,
tileY: selectedHouse.y,
houseIndex: selectedHouse.houseIndex,
houseCorner: selectedHouse.house.corner, // Speichere auch die Ecke
imageIndex: this.generatePassengerImageIndex(), // Zufälliger Bildindex 1-6
houseIndex: houseIndex,
houseCorner: selectedHouse.corner, // Speichere auch die Ecke
imageIndex: this.generatePassengerImageIndex(nameData.gender), // Bildindex basierend auf Geschlecht
createdAt: Date.now() // Zeitstempel der Erstellung
};
// Füge Passagier zur Liste hinzu und markiere Haus als belegt
this.waitingPassengersList.push(passenger);
this.occupiedHouses.add(selectedHouse.houseId);
this.occupiedHouses.add(houseId);
console.log('Neuer wartender Passagier generiert:', passenger);
},
generatePassengerName() {
const firstNames = [
'Anna', 'Max', 'Lisa', 'Tom', 'Sarah', 'Ben', 'Emma', 'Lukas', 'Julia', 'Paul',
'Marie', 'Felix', 'Sophie', 'Jonas', 'Laura', 'Tim', 'Mia', 'Noah', 'Emma', 'Finn'
const maleNames = [
'Max', 'Tom', 'Ben', 'Lukas', 'Paul', 'Felix', 'Jonas', 'Tim', 'Noah', '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 = [
'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 lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
return `${firstName} ${lastName}`;
return {
fullName: `${firstName} ${lastName}`,
gender: isMale ? 'male' : 'female'
};
},
generatePassengerImageIndex() {
// Zufälliger Bildindex 1-6 (1-3 männlich, 4-6 weiblich)
return Math.floor(Math.random() * 6) + 1;
generatePassengerImageIndex(gender) {
// Bildindex basierend auf Geschlecht (1-3 männlich, 4-6 weiblich)
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) {
// Berechne Manhattan-Distanz (kürzester Weg in einem Raster)
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) {
const shortestPath = this.calculateShortestPath(startX, startY, endX, endY);
const bonus = shortestPath * this.bonusMultiplier;
const maxTime = shortestPath * this.timePerTile;
console.log(`Wegstrecke berechnet: Start(${startX},${startY}) -> Ziel(${endX},${endY}) = ${shortestPath} Tiles, Bonus: ${bonus}, Zeit: ${maxTime}s`);
return {
shortestPath,
bonus,
@@ -860,60 +939,66 @@ export default {
generatePassengerDestination() {
// 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
const streets = this.currentMap.tileStreets || [];
const houses = this.currentMap.tileHouses || [];
// Versuche maximal 10 Mal, ein gültiges Ziel zu finden
for (let attempt = 0; attempt < 10; attempt++) {
// Wähle zufällige Straße
const randomStreet = streets[Math.floor(Math.random() * streets.length)];
// Sammle alle gültigen Ziele vor der zufälligen Auswahl
const validDestinations = [];
for (const street of streets) {
// Prüfe ob diese Straße Häuser hat
const housesOnThisTile = houses.filter(house => house.x === randomStreet.x && house.y === randomStreet.y);
const housesOnThisTile = houses.filter(house => house.x === street.x && house.y === street.y);
if (housesOnThisTile.length > 0) {
// Wähle zufälliges Haus auf diesem Tile
const selectedHouse = housesOnThisTile[Math.floor(Math.random() * housesOnThisTile.length)];
// Bestimme die Straße basierend auf der Haus-Ecke
let streetName = "Unbekannte Straße";
let houseNumber = 1;
const corner = selectedHouse.corner;
for (const house of housesOnThisTile) {
const corner = house.corner;
let streetName = null;
if (corner === 'lo' || corner === 'ro') {
// Horizontale Straße
if (randomStreet.streetNameH && randomStreet.streetNameH.name) {
streetName = randomStreet.streetNameH.name;
if (street.streetNameH && street.streetNameH.name) {
streetName = street.streetNameH.name;
}
} else if (corner === 'lu' || corner === 'ru') {
// Vertikale Straße
if (randomStreet.streetNameV && randomStreet.streetNameV.name) {
streetName = randomStreet.streetNameV.name;
if (street.streetNameV && street.streetNameV.name) {
streetName = street.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)];
}
// 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];
return {
if (houseNumber != null) {
validDestinations.push({
streetName,
houseNumber,
location: `${streetName} ${houseNumber}`,
tileX: randomStreet.x,
tileY: randomStreet.y,
houseCorner: selectedHouse.corner
};
tileX: street.x,
tileY: street.y,
houseCorner: house.corner
});
}
}
}
}
}
// Wenn nach 10 Versuchen kein Ziel gefunden wurde
if (validDestinations.length > 0) {
// Wähle zufälliges gültiges Ziel
const selectedDestination = validDestinations[Math.floor(Math.random() * validDestinations.length)];
return selectedDestination;
}
return null;
},
@@ -941,7 +1026,6 @@ export default {
if (timeSincePickup >= 2000) {
passenger.timerActive = true;
passenger.lastUpdateTime = now;
console.log('Timer für Passagier', passenger.name, 'aktiviert nach', timeSincePickup, 'ms');
}
return; // Timer noch nicht aktiv
}
@@ -1106,6 +1190,10 @@ export default {
startGame() {
this.gameRunning = true;
// Stoppe bestehende Game-Loop falls vorhanden
if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop);
}
this.gameLoop = requestAnimationFrame(this.update);
},
@@ -1264,6 +1352,11 @@ export default {
},
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
// Diese Methode muss exakt mit drawHousesOnMainCanvas übereinstimmen
const tileSize = 500; // Tile-Größe
@@ -1306,19 +1399,53 @@ export default {
const HOUSE_H = 150; // Haushöhe
const maxDistance = 100; // 100px Entfernung
// Einfache Distanzberechnung zum Hauszentrum
const houseCenterX = houseX + HOUSE_W / 2;
const houseCenterY = houseY + HOUSE_H / 2;
const distance = Math.sqrt(
Math.pow(taxiX - houseCenterX, 2) + Math.pow(taxiY - houseCenterY, 2)
// Berechne die Entfernung zur Tile-Mitte
const tileCenterX = 250; // Mitte des 500px Canvas
const tileCenterY = 250; // Mitte des 500px Canvas
const distanceToTileCenter = Math.sqrt(
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) {
console.log('Passagier wird eingeladen:', passenger.name);
// Entferne Passagier aus der Warteliste
this.waitingPassengersList.splice(index, 1);
@@ -1330,9 +1457,8 @@ export default {
// Generiere ein Ziel für den Passagier
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
const startX = passenger.tileX;
const startY = passenger.tileY;
@@ -1352,10 +1478,7 @@ export default {
lastUpdateTime: 0, // Timer-Aktualisierung erst nach expliziter Aktivierung
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 {
console.log('Kein Ziel generiert - Passagier wird nicht hinzugefügt');
}
// Optional: Sound-Effekt oder Nachricht
@@ -1369,40 +1492,26 @@ export default {
// Prüfe ob geladene Passagiere abgesetzt werden können
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
for (let i = this.loadedPassengersList.length - 1; i >= 0; i--) {
const passenger = this.loadedPassengersList[i];
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
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
const housePosition = this.getHousePositionOnTile(destination.tileX, destination.tileY, destination.houseCorner);
if (housePosition) {
console.log('Haus-Position gefunden:', housePosition);
// Prüfe ob das Taxi in der Nähe des Zielhauses ist
const isNear = this.isTaxiNearHouse(housePosition, destination.houseCorner);
console.log('Ist Taxi nah genug?', isNear);
if (isNear) {
// Setze Passagier ab
console.log('Setze Passagier ab:', passenger.name);
console.log(' Passagier abgesetzt:', passenger.name, 'an', destination.location);
this.dropoffLoadedPassenger(passenger, i);
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);
// Test: Direkter Aufruf nach 3 Sekunden (falls Dialog-Callback nicht funktioniert)
setTimeout(() => {
this.crashDialogTimeout = setTimeout(() => {
console.log('Test: Automatischer Reset nach 3 Sekunden');
this.handleCrashDialogClose();
}, 3000);
@@ -2343,16 +2452,9 @@ export default {
async handleCanvasClick(event) {
// AudioContext bei erster Benutzerinteraktion initialisieren
if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: start, hasCtx=', !!this.audioContext);
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)
if (this.motorSound && !this.motorSound.isPlaying) {
if (import.meta.env?.DEV) console.log('[Audio] CanvasClick: motorSound.start()');
this.motorSound.start();
}
},
@@ -2361,15 +2463,9 @@ export default {
this.keys[event.key] = true;
// AudioContext bei erster Benutzerinteraktion initialisieren
if (import.meta.env?.DEV) console.log('[Audio] KeyDown: key=', event.key, 'hasCtx=', !!this.audioContext);
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)
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();
// Direkt Parameter setzen, um hörbares Feedback ohne Verzögerung zu bekommen
const speedKmh = Math.max(5, this.taxi.speed * 5);
@@ -2384,6 +2480,11 @@ export default {
},
ensureAudioUnlockedInEvent() {
// Nur einmal initialisieren
if (this.audioContext && this.motorSound) {
return; // Bereits initialisiert
}
// Muss synchron im Event-Handler laufen (ohne await), damit Policies greifen
if (!this.audioContext) {
this.audioContext = window.__TaxiAudioContext || new (window.AudioContext || window.webkitAudioContext)();
@@ -2429,6 +2530,17 @@ export default {
this.taxi.y = 250;
this.taxi.angle = 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.initializePassengerGeneration();
},
@@ -3001,6 +3113,7 @@ export default {
.lives { font-weight: 700; color: #d60000; }
.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-value {