Änderungen: - Entfernen der Methode `getMapByPosition` aus dem `TaxiMapController` und der zugehörigen Logik im `TaxiMapService`, um die Komplexität zu reduzieren. - Anpassung der Datenbankmodelle für `TaxiMap`, `TaxiLevelStats` und `TaxiMapType`, um die Tabellennamen zu vereinheitlichen. - Aktualisierung der Routen im `taxiMapRouter`, um die entfernte Funktionalität zu reflektieren. - Hinzufügung von neuen Importen in `index.js`, um die neuen Modelle zu integrieren. - Verbesserung der Benutzeroberfläche durch neue Erfolgsmeldungen in den Übersetzungsdateien für die Admin-Oberfläche. Diese Anpassungen tragen zur Vereinfachung der Codebasis und zur Verbesserung der Benutzererfahrung im Taxi-Minispiel bei.
1137 lines
29 KiB
Vue
1137 lines
29 KiB
Vue
<template>
|
||
<div class="contenthidden">
|
||
<StatusBar />
|
||
<div class="contentscroll">
|
||
<!-- Spiel-Titel -->
|
||
<div class="game-title">
|
||
<h1>{{ $t('minigames.taxi.title') }}</h1>
|
||
<p>{{ $t('minigames.taxi.description') }}</p>
|
||
</div>
|
||
|
||
<!-- Spiel-Layout -->
|
||
<div class="game-layout">
|
||
<!-- Spielbrett (links) -->
|
||
<div class="game-board-section">
|
||
<!-- Pause-Anzeige -->
|
||
<div v-if="isPaused" class="pause-overlay">
|
||
<div class="pause-message">
|
||
<h2>{{ $t('minigames.taxi.paused') }}</h2>
|
||
<button @click="togglePause" class="resume-button">
|
||
{{ $t('minigames.taxi.resume') }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Spielbereich mit Canvas und Legende -->
|
||
<div class="game-area">
|
||
<!-- Legende (links) -->
|
||
<div class="controls-legend">
|
||
<h3>Steuerung</h3>
|
||
<div class="legend-grid">
|
||
<div class="legend-item">
|
||
<span class="legend-key">↑ W</span>
|
||
<span class="legend-text">Gas geben</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<span class="legend-key">↓ X</span>
|
||
<span class="legend-text">Bremsen</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<span class="legend-key">→ D</span>
|
||
<span class="legend-text">Rechts lenken</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<span class="legend-key">← A</span>
|
||
<span class="legend-text">Links lenken</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<span class="legend-key">S</span>
|
||
<span class="legend-text">Passagier aufnehmen</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<span class="legend-key">Q</span>
|
||
<span class="legend-text">Passagier absetzen</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<span class="legend-key">Enter</span>
|
||
<span class="legend-text">Tanken</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Ziele -->
|
||
<div class="game-objectives">
|
||
<h4>Ziele</h4>
|
||
<ul>
|
||
<li>Fahre zu <span class="objective-green">grünen Markierungen</span> um Passagiere aufzunehmen</li>
|
||
<li>Fahre zu <span class="objective-red">roten Markierungen</span> um Passagiere abzuliefern</li>
|
||
<li><span class="objective-yellow">Gelbe Markierungen</span> sind Tankstellen</li>
|
||
<li>Vermeide Kollisionen mit anderen Fahrzeugen</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Spiel-Canvas -->
|
||
<div class="game-canvas-container">
|
||
<canvas
|
||
ref="gameCanvas"
|
||
width="400"
|
||
height="400"
|
||
class="game-canvas"
|
||
@click="handleCanvasClick"
|
||
@keydown="handleKeyDown"
|
||
tabindex="0"
|
||
></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Spiel-Controls -->
|
||
<div class="game-controls">
|
||
<button @click="togglePause" class="control-button">
|
||
{{ isPaused ? $t('minigames.taxi.resume') : $t('minigames.taxi.pause') }}
|
||
</button>
|
||
<button @click="restartLevel" class="control-button">
|
||
{{ $t('minigames.taxi.restartLevel') }}
|
||
</button>
|
||
<button @click="goBack" class="control-button">
|
||
{{ $t('minigames.backToGames') }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sidebar (rechts) -->
|
||
<div class="sidebar-section">
|
||
<!-- Statistiken -->
|
||
<div class="stats-card">
|
||
<div class="stats-header">
|
||
<div class="stats-header-content">
|
||
<h3 class="stats-title">{{ $t('minigames.taxi.gameStats') }}</h3>
|
||
<button class="toggle-button" @click="toggleStats">
|
||
<span class="toggle-icon">{{ toggleIcon }}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="stats-list" v-if="isStatsExpanded">
|
||
<div class="stat-row">
|
||
<span class="stat-value score-value">{{ score }}</span>
|
||
<span class="stat-label">{{ $t('minigames.taxi.score') }}</span>
|
||
</div>
|
||
|
||
<div class="stat-row">
|
||
<span class="stat-value money-value">€{{ money }}</span>
|
||
<span class="stat-label">{{ $t('minigames.taxi.money') }}</span>
|
||
</div>
|
||
|
||
<div class="stat-row">
|
||
<span class="stat-value passengers-value">{{ passengersDelivered }}</span>
|
||
<span class="stat-label">{{ $t('minigames.taxi.passengers') }}</span>
|
||
</div>
|
||
|
||
<div class="stat-row">
|
||
<span class="stat-value fuel-value">{{ fuel }}%</span>
|
||
<span class="stat-label">{{ $t('minigames.taxi.fuel') }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Minimap -->
|
||
<div class="minimap-card">
|
||
<div class="minimap-header">
|
||
<h3 class="minimap-title">Minimap</h3>
|
||
</div>
|
||
<div class="minimap-container">
|
||
<canvas
|
||
ref="minimapCanvas"
|
||
width="200"
|
||
height="150"
|
||
class="minimap-canvas"
|
||
></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import streetCoordinates from '../../utils/streetCoordinates.js';
|
||
import apiClient from '../../utils/axios.js';
|
||
|
||
export default {
|
||
name: 'TaxiGame',
|
||
data() {
|
||
return {
|
||
isPaused: false,
|
||
score: 0,
|
||
money: 0,
|
||
passengersDelivered: 0,
|
||
fuel: 100,
|
||
currentLevel: 1,
|
||
gameRunning: false,
|
||
gameLoop: null,
|
||
canvas: null,
|
||
ctx: null,
|
||
minimapCanvas: null,
|
||
minimapCtx: null,
|
||
isStatsExpanded: true,
|
||
taxi: {
|
||
x: 200,
|
||
y: 200,
|
||
width: 20,
|
||
height: 15,
|
||
angle: 0,
|
||
speed: 0,
|
||
maxSpeed: 3
|
||
},
|
||
tiles: {
|
||
size: 50, // 400px / 8 tiles = 50px per tile
|
||
images: {}
|
||
},
|
||
passengers: [],
|
||
destinations: [],
|
||
gasStations: [],
|
||
obstacles: [],
|
||
keys: {}
|
||
}
|
||
},
|
||
mounted() {
|
||
this.initializeGame();
|
||
this.initializeMinimap();
|
||
this.loadTiles();
|
||
this.setupEventListeners();
|
||
},
|
||
beforeUnmount() {
|
||
this.cleanup();
|
||
},
|
||
methods: {
|
||
initializeGame() {
|
||
this.canvas = this.$refs.gameCanvas;
|
||
this.ctx = this.canvas.getContext('2d');
|
||
this.canvas.focus();
|
||
this.generateLevel();
|
||
this.startGame();
|
||
},
|
||
|
||
setupEventListeners() {
|
||
document.addEventListener('keydown', this.handleKeyDown);
|
||
document.addEventListener('keyup', this.handleKeyUp);
|
||
},
|
||
|
||
cleanup() {
|
||
if (this.gameLoop) {
|
||
cancelAnimationFrame(this.gameLoop);
|
||
}
|
||
document.removeEventListener('keydown', this.handleKeyDown);
|
||
document.removeEventListener('keyup', this.handleKeyUp);
|
||
},
|
||
|
||
generateLevel() {
|
||
this.passengers = [];
|
||
this.destinations = [];
|
||
this.gasStations = [];
|
||
this.obstacles = [];
|
||
|
||
// Generiere Passagiere (auf Straßen)
|
||
for (let i = 0; i < 3; i++) {
|
||
const position = this.getRandomRoadPosition();
|
||
this.passengers.push({
|
||
x: position.x,
|
||
y: position.y,
|
||
width: 15,
|
||
height: 15,
|
||
pickedUp: false
|
||
});
|
||
}
|
||
|
||
// Generiere Ziele (auf Straßen)
|
||
for (let i = 0; i < 3; i++) {
|
||
const position = this.getRandomRoadPosition();
|
||
this.destinations.push({
|
||
x: position.x,
|
||
y: position.y,
|
||
width: 15,
|
||
height: 15,
|
||
completed: false
|
||
});
|
||
}
|
||
|
||
// Generiere Tankstellen (auf Straßen)
|
||
const fuelPosition = this.getRandomRoadPosition();
|
||
this.gasStations.push({
|
||
x: fuelPosition.x,
|
||
y: fuelPosition.y,
|
||
width: 20,
|
||
height: 20
|
||
});
|
||
|
||
// Generiere Hindernisse (außerhalb der Straßen)
|
||
for (let i = 0; i < 3; i++) {
|
||
const position = this.getRandomOffRoadPosition();
|
||
this.obstacles.push({
|
||
x: position.x,
|
||
y: position.y,
|
||
width: 20,
|
||
height: 20
|
||
});
|
||
}
|
||
},
|
||
|
||
getRandomRoadPosition() {
|
||
const tileSize = this.tiles.size;
|
||
const cols = 8;
|
||
const rows = 8;
|
||
|
||
let attempts = 0;
|
||
while (attempts < 100) {
|
||
const tileCol = Math.floor(Math.random() * cols);
|
||
const tileRow = Math.floor(Math.random() * rows);
|
||
const tileType = this.getTileType(tileRow, tileCol, rows, cols);
|
||
|
||
// Zufällige Position innerhalb des Tiles
|
||
const relativeX = Math.random();
|
||
const relativeY = Math.random();
|
||
|
||
if (streetCoordinates.isPointDriveable(relativeX, relativeY, tileType, 1)) {
|
||
return {
|
||
x: tileCol * tileSize + relativeX * tileSize,
|
||
y: tileRow * tileSize + relativeY * tileSize
|
||
};
|
||
}
|
||
attempts++;
|
||
}
|
||
|
||
// Fallback: Mitte des Spielfelds
|
||
return { x: 200, y: 200 };
|
||
},
|
||
|
||
getRandomOffRoadPosition() {
|
||
const tileSize = this.tiles.size;
|
||
const cols = 8;
|
||
const rows = 8;
|
||
|
||
let attempts = 0;
|
||
while (attempts < 100) {
|
||
const tileCol = Math.floor(Math.random() * cols);
|
||
const tileRow = Math.floor(Math.random() * rows);
|
||
const tileType = this.getTileType(tileRow, tileCol, rows, cols);
|
||
|
||
// Zufällige Position innerhalb des Tiles
|
||
const relativeX = Math.random();
|
||
const relativeY = Math.random();
|
||
|
||
if (!streetCoordinates.isPointDriveable(relativeX, relativeY, tileType, 1)) {
|
||
return {
|
||
x: tileCol * tileSize + relativeX * tileSize,
|
||
y: tileRow * tileSize + relativeY * tileSize
|
||
};
|
||
}
|
||
attempts++;
|
||
}
|
||
|
||
// Fallback: Ecke des Spielfelds
|
||
return { x: 20, y: 20 };
|
||
},
|
||
|
||
startGame() {
|
||
this.gameRunning = true;
|
||
this.gameLoop = requestAnimationFrame(this.update);
|
||
},
|
||
|
||
update() {
|
||
if (!this.gameRunning || this.isPaused) {
|
||
this.gameLoop = requestAnimationFrame(this.update);
|
||
return;
|
||
}
|
||
|
||
this.updateTaxi();
|
||
this.handlePassengerActions();
|
||
this.checkCollisions();
|
||
this.render();
|
||
|
||
// Minimap zeichnen
|
||
this.drawMinimap();
|
||
|
||
this.gameLoop = requestAnimationFrame(this.update);
|
||
},
|
||
|
||
updateTaxi() {
|
||
// Bewegung basierend auf gedrückten Tasten
|
||
if (this.keys['ArrowUp'] || this.keys['w'] || this.keys['W']) {
|
||
this.taxi.speed = Math.min(this.taxi.speed + 0.2, this.taxi.maxSpeed);
|
||
} else if (this.keys['ArrowDown'] || this.keys['x'] || this.keys['X']) {
|
||
this.taxi.speed = Math.max(this.taxi.speed - 0.2, -this.taxi.maxSpeed);
|
||
} else {
|
||
this.taxi.speed *= 0.9; // Reibung
|
||
}
|
||
|
||
if (this.keys['ArrowLeft'] || this.keys['a'] || this.keys['A']) {
|
||
this.taxi.angle -= 0.1;
|
||
}
|
||
if (this.keys['ArrowRight'] || this.keys['d'] || this.keys['D']) {
|
||
this.taxi.angle += 0.1;
|
||
}
|
||
|
||
// Aktualisiere Position
|
||
this.taxi.x += Math.cos(this.taxi.angle) * this.taxi.speed;
|
||
this.taxi.y += Math.sin(this.taxi.angle) * this.taxi.speed;
|
||
|
||
// Begrenze auf Canvas
|
||
this.taxi.x = Math.max(0, Math.min(this.canvas.width - this.taxi.width, this.taxi.x));
|
||
this.taxi.y = Math.max(0, Math.min(this.canvas.height - this.taxi.height, this.taxi.y));
|
||
|
||
// Verbrauche Treibstoff
|
||
if (Math.abs(this.taxi.speed) > 0.1) {
|
||
this.fuel = Math.max(0, this.fuel - 0.1);
|
||
}
|
||
},
|
||
|
||
handlePassengerActions() {
|
||
// S - Passagier aufnehmen
|
||
if (this.keys['s'] || this.keys['S']) {
|
||
this.pickupPassenger();
|
||
}
|
||
|
||
// Q - Passagier absetzen
|
||
if (this.keys['q'] || this.keys['Q']) {
|
||
this.dropoffPassenger();
|
||
}
|
||
|
||
// Enter - Tanken
|
||
if (this.keys['Enter']) {
|
||
this.refuel();
|
||
}
|
||
},
|
||
|
||
pickupPassenger() {
|
||
// Finde nächsten Passagier in der Nähe
|
||
for (let i = 0; i < this.passengers.length; i++) {
|
||
const passenger = this.passengers[i];
|
||
if (!passenger.pickedUp && this.checkCollision(this.taxi, passenger)) {
|
||
passenger.pickedUp = true;
|
||
this.score += 10;
|
||
break;
|
||
}
|
||
}
|
||
},
|
||
|
||
dropoffPassenger() {
|
||
// Finde nächstes Ziel in der Nähe
|
||
for (let i = 0; i < this.destinations.length; i++) {
|
||
const destination = this.destinations[i];
|
||
if (!destination.completed && this.checkCollision(this.taxi, destination)) {
|
||
destination.completed = true;
|
||
this.passengersDelivered++;
|
||
this.score += 50;
|
||
this.money += 25;
|
||
break;
|
||
}
|
||
}
|
||
},
|
||
|
||
refuel() {
|
||
// Finde nächste Tankstelle in der Nähe
|
||
for (let i = 0; i < this.gasStations.length; i++) {
|
||
const station = this.gasStations[i];
|
||
if (this.checkCollision(this.taxi, station)) {
|
||
this.fuel = Math.min(100, this.fuel + 50);
|
||
this.score += 5;
|
||
break;
|
||
}
|
||
}
|
||
},
|
||
|
||
checkCollisions() {
|
||
// Prüfe Straßenkollisionen
|
||
if (!this.isTaxiOnRoad()) {
|
||
this.taxi.speed = 0;
|
||
this.score = Math.max(0, this.score - 2);
|
||
}
|
||
|
||
// Prüfe Hindernisse
|
||
this.obstacles.forEach(obstacle => {
|
||
if (this.checkCollision(this.taxi, obstacle)) {
|
||
this.taxi.speed = 0;
|
||
this.score = Math.max(0, this.score - 5);
|
||
}
|
||
});
|
||
},
|
||
|
||
isTaxiOnRoad() {
|
||
const tileSize = this.tiles.size;
|
||
const cols = 8;
|
||
const rows = 8;
|
||
|
||
// Bestimme welches Tile das Taxi gerade belegt
|
||
const tileCol = Math.floor(this.taxi.x / tileSize);
|
||
const tileRow = Math.floor(this.taxi.y / tileSize);
|
||
|
||
// Prüfe ob das Taxi innerhalb der Canvas-Grenzen ist
|
||
if (tileCol < 0 || tileCol >= cols || tileRow < 0 || tileRow >= rows) {
|
||
return false;
|
||
}
|
||
|
||
// Bestimme Tile-Typ
|
||
const tileType = this.getTileType(tileRow, tileCol, rows, cols);
|
||
|
||
// Konvertiere Taxi-Position zu relativen Koordinaten innerhalb des Tiles
|
||
const relativeX = (this.taxi.x - tileCol * tileSize) / tileSize;
|
||
const relativeY = (this.taxi.y - tileRow * tileSize) / tileSize;
|
||
|
||
// Prüfe ob das Taxi auf der Straße ist
|
||
return streetCoordinates.isPointDriveable(relativeX, relativeY, tileType, 1);
|
||
},
|
||
|
||
checkCollision(rect1, rect2) {
|
||
return rect1.x < rect2.x + rect2.width &&
|
||
rect1.x + rect1.width > rect2.x &&
|
||
rect1.y < rect2.y + rect2.height &&
|
||
rect1.y + rect1.height > rect2.y;
|
||
},
|
||
|
||
render() {
|
||
// Lösche Canvas
|
||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||
|
||
// Zeichne Straßen
|
||
this.drawRoads();
|
||
|
||
// Zeichne Hindernisse
|
||
this.obstacles.forEach(obstacle => {
|
||
this.ctx.fillStyle = '#666';
|
||
this.ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
|
||
});
|
||
|
||
// Zeichne Passagiere
|
||
this.passengers.forEach(passenger => {
|
||
if (!passenger.pickedUp) {
|
||
this.ctx.fillStyle = '#4CAF50';
|
||
this.ctx.fillRect(passenger.x, passenger.y, passenger.width, passenger.height);
|
||
}
|
||
});
|
||
|
||
// Zeichne Ziele
|
||
this.destinations.forEach(destination => {
|
||
if (!destination.completed) {
|
||
this.ctx.fillStyle = '#F44336';
|
||
this.ctx.fillRect(destination.x, destination.y, destination.width, destination.height);
|
||
}
|
||
});
|
||
|
||
// Zeichne Tankstellen
|
||
this.gasStations.forEach(station => {
|
||
this.ctx.fillStyle = '#FFC107';
|
||
this.ctx.fillRect(station.x, station.y, station.width, station.height);
|
||
});
|
||
|
||
// Zeichne Taxi
|
||
this.ctx.save();
|
||
this.ctx.translate(this.taxi.x + this.taxi.width/2, this.taxi.y + this.taxi.height/2);
|
||
this.ctx.rotate(this.taxi.angle);
|
||
this.ctx.fillStyle = '#2196F3';
|
||
this.ctx.fillRect(-this.taxi.width/2, -this.taxi.height/2, this.taxi.width, this.taxi.height);
|
||
this.ctx.restore();
|
||
},
|
||
|
||
drawRoads() {
|
||
const tileSize = this.tiles.size;
|
||
const cols = 8; // 400px / 50px = 8 tiles
|
||
const rows = 8;
|
||
|
||
// Erstelle ein 8x8 Raster mit Straßen
|
||
for (let row = 0; row < rows; row++) {
|
||
for (let col = 0; col < cols; col++) {
|
||
const x = col * tileSize;
|
||
const y = row * tileSize;
|
||
|
||
// Bestimme Tile-Typ basierend auf Position
|
||
let tileType = this.getTileType(row, col, rows, cols);
|
||
|
||
// Zeichne Straßenregionen basierend auf Koordinaten
|
||
streetCoordinates.drawDriveableRegions(this.ctx, tileType, tileSize, x, y);
|
||
|
||
// Zeichne Tile-Overlay falls verfügbar
|
||
if (this.tiles.images[tileType]) {
|
||
this.ctx.drawImage(this.tiles.images[tileType], x, y, tileSize, tileSize);
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
getTileType(row, col, rows, cols) {
|
||
// Ecken
|
||
if (row === 0 && col === 0) return 'cornertopleft';
|
||
if (row === 0 && col === cols - 1) return 'cornertopright';
|
||
if (row === rows - 1 && col === 0) return 'cornerbottomleft';
|
||
if (row === rows - 1 && col === cols - 1) return 'cornerbottomright';
|
||
|
||
// Ränder
|
||
if (row === 0 || row === rows - 1) return 'horizontal';
|
||
if (col === 0 || col === cols - 1) return 'vertical';
|
||
|
||
// Innere Bereiche - zufällige Straßen
|
||
const rand = Math.random();
|
||
if (rand < 0.3) return 'cross';
|
||
if (rand < 0.5) return 'horizontal';
|
||
if (rand < 0.7) return 'vertical';
|
||
if (rand < 0.8) return 'fuelhorizontal';
|
||
if (rand < 0.9) return 'fuelvertical';
|
||
|
||
return 'horizontal'; // Fallback
|
||
},
|
||
|
||
handleCanvasClick(event) {
|
||
// Canvas-Klick-Handling falls benötigt
|
||
},
|
||
|
||
handleKeyDown(event) {
|
||
this.keys[event.key] = true;
|
||
event.preventDefault();
|
||
},
|
||
|
||
handleKeyUp(event) {
|
||
this.keys[event.key] = false;
|
||
},
|
||
|
||
togglePause() {
|
||
this.isPaused = !this.isPaused;
|
||
},
|
||
|
||
restartLevel() {
|
||
this.score = 0;
|
||
this.money = 0;
|
||
this.passengersDelivered = 0;
|
||
this.fuel = 100;
|
||
this.taxi.x = 400;
|
||
this.taxi.y = 300;
|
||
this.taxi.angle = 0;
|
||
this.taxi.speed = 0;
|
||
this.generateLevel();
|
||
},
|
||
|
||
goBack() {
|
||
this.$router.push('/minigames');
|
||
},
|
||
|
||
toggleStats() {
|
||
this.isStatsExpanded = !this.isStatsExpanded;
|
||
},
|
||
|
||
initializeMinimap() {
|
||
this.minimapCanvas = this.$refs.minimapCanvas;
|
||
this.minimapCtx = this.minimapCanvas.getContext('2d');
|
||
},
|
||
|
||
async loadTiles() {
|
||
const tileNames = [
|
||
'cornerbottomleft', 'cornerbottomright', 'cornertopleft', 'cornertopright',
|
||
'cross', 'fuelhorizontal', 'fuelvertical', 'horizontal', 'vertical'
|
||
];
|
||
|
||
const mapTileNames = [
|
||
'map-cornerbottomleft', 'map-cornerbottomright', 'map-cornertopleft', 'map-cornertopright',
|
||
'map-cross', 'map-fuelhorizontal', 'map-fuelvertical', 'map-horizontal', 'map-vertical'
|
||
];
|
||
|
||
// Lade normale Tiles
|
||
for (const tileName of tileNames) {
|
||
const img = new Image();
|
||
img.src = `/images/taxi/${tileName}.svg`;
|
||
this.tiles.images[tileName] = img;
|
||
}
|
||
|
||
// Lade Map-Tiles
|
||
for (const tileName of mapTileNames) {
|
||
const img = new Image();
|
||
img.src = `/images/taxi/${tileName}.svg`;
|
||
this.tiles.images[tileName] = img;
|
||
}
|
||
},
|
||
|
||
drawMinimap() {
|
||
if (!this.minimapCtx) return;
|
||
|
||
const ctx = this.minimapCtx;
|
||
const canvas = this.minimapCanvas;
|
||
|
||
// Minimap löschen
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
|
||
// Hintergrund
|
||
ctx.fillStyle = '#f0f0f0';
|
||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||
|
||
// Skalierungsfaktor (Hauptspiel zu Minimap)
|
||
const scaleX = canvas.width / 400;
|
||
const scaleY = canvas.height / 400;
|
||
|
||
// Zeichne Map-Tiles
|
||
const tileSize = this.tiles.size;
|
||
const cols = 8;
|
||
const rows = 8;
|
||
|
||
for (let row = 0; row < rows; row++) {
|
||
for (let col = 0; col < cols; col++) {
|
||
const x = col * tileSize * scaleX;
|
||
const y = row * tileSize * scaleY;
|
||
const width = tileSize * scaleX;
|
||
const height = tileSize * scaleY;
|
||
|
||
let tileType = this.getTileType(row, col, rows, cols);
|
||
let mapTileType = 'map-' + tileType;
|
||
|
||
// Zeichne Map-Tile falls verfügbar
|
||
if (this.tiles.images[mapTileType]) {
|
||
ctx.drawImage(this.tiles.images[mapTileType], x, y, width, height);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Passagiere (grün)
|
||
ctx.fillStyle = '#4CAF50';
|
||
this.passengers.forEach(passenger => {
|
||
if (!passenger.pickedUp) {
|
||
ctx.beginPath();
|
||
ctx.arc(passenger.x * scaleX, passenger.y * scaleY, 2, 0, 2 * Math.PI);
|
||
ctx.fill();
|
||
}
|
||
});
|
||
|
||
// Ziele (rot)
|
||
ctx.fillStyle = '#F44336';
|
||
this.destinations.forEach(dest => {
|
||
if (!dest.completed) {
|
||
ctx.beginPath();
|
||
ctx.arc(dest.x * scaleX, dest.y * scaleY, 2, 0, 2 * Math.PI);
|
||
ctx.fill();
|
||
}
|
||
});
|
||
|
||
// Tankstellen (gelb)
|
||
ctx.fillStyle = '#FF9800';
|
||
this.gasStations.forEach(station => {
|
||
ctx.beginPath();
|
||
ctx.arc(station.x * scaleX, station.y * scaleY, 2, 0, 2 * Math.PI);
|
||
ctx.fill();
|
||
});
|
||
|
||
// Taxi (blau)
|
||
ctx.fillStyle = '#2196F3';
|
||
ctx.beginPath();
|
||
ctx.arc(this.taxi.x * scaleX, this.taxi.y * scaleY, 3, 0, 2 * Math.PI);
|
||
ctx.fill();
|
||
|
||
// Taxi-Richtung anzeigen
|
||
ctx.strokeStyle = '#2196F3';
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
const endX = this.taxi.x * scaleX + Math.cos(this.taxi.angle) * 6;
|
||
const endY = this.taxi.y * scaleY + Math.sin(this.taxi.angle) * 6;
|
||
ctx.moveTo(this.taxi.x * scaleX, this.taxi.y * scaleY);
|
||
ctx.lineTo(endX, endY);
|
||
ctx.stroke();
|
||
}
|
||
},
|
||
|
||
computed: {
|
||
toggleIcon() {
|
||
return this.isStatsExpanded ? '−' : '+';
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* Verwendet globale Scroll-Klassen: .contenthidden und .contentscroll */
|
||
|
||
.game-title {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
padding-top: 20px;
|
||
}
|
||
|
||
.game-title h1 {
|
||
margin: 0 0 10px 0;
|
||
font-size: 2rem;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.game-title p {
|
||
margin: 0;
|
||
font-size: 1.1rem;
|
||
color: #666;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* Spiel-Layout */
|
||
.game-layout {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 20px;
|
||
max-width: 1600px;
|
||
margin: 0 auto;
|
||
padding: 0 20px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.game-board-section {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
min-width: 0; /* Verhindert Overflow */
|
||
}
|
||
|
||
.game-area {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 20px;
|
||
align-items: flex-start;
|
||
width: 100%;
|
||
max-width: 1200px;
|
||
}
|
||
|
||
.sidebar-section {
|
||
width: 320px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
position: sticky;
|
||
top: 20px;
|
||
}
|
||
|
||
/* Stats Card */
|
||
.stats-card {
|
||
background: #fff;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.stats-header {
|
||
background: #f8f9fa;
|
||
border-bottom: 1px solid #ddd;
|
||
padding: 15px 20px;
|
||
}
|
||
|
||
.stats-header-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.stats-title {
|
||
margin: 0;
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.toggle-button {
|
||
background: none;
|
||
border: none;
|
||
font-size: 1.2rem;
|
||
cursor: pointer;
|
||
padding: 5px;
|
||
color: #666;
|
||
}
|
||
|
||
.toggle-button:hover {
|
||
color: #333;
|
||
}
|
||
|
||
.stats-list {
|
||
padding: 20px;
|
||
}
|
||
|
||
.stat-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.stat-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 1.2rem;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: 0.9rem;
|
||
color: #666;
|
||
}
|
||
|
||
.score-value { color: #4CAF50; }
|
||
.money-value { color: #FF9800; }
|
||
.passengers-value { color: #2196F3; }
|
||
.fuel-value { color: #9C27B0; }
|
||
|
||
/* Minimap Card */
|
||
.minimap-card {
|
||
background: #fff;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.minimap-header {
|
||
background: #f8f9fa;
|
||
border-bottom: 1px solid #ddd;
|
||
padding: 15px 20px;
|
||
}
|
||
|
||
.minimap-title {
|
||
margin: 0;
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.minimap-container {
|
||
padding: 15px;
|
||
text-align: center;
|
||
}
|
||
|
||
.minimap-canvas {
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background: #f9f9f9;
|
||
max-width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
/* Controls Legend */
|
||
.controls-legend {
|
||
background: #fff;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||
width: 200px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.controls-legend h3 {
|
||
margin: 0 0 15px 0;
|
||
color: #333;
|
||
font-size: 1.1rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.legend-grid {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.legend-key {
|
||
background: #f0f0f0;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 4px 8px;
|
||
font-weight: bold;
|
||
font-size: 0.8rem;
|
||
min-width: 40px;
|
||
text-align: center;
|
||
color: #333;
|
||
}
|
||
|
||
.legend-text {
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
flex: 1;
|
||
}
|
||
|
||
/* Game Objectives in Legend */
|
||
.controls-legend .game-objectives {
|
||
margin-top: 20px;
|
||
padding-top: 15px;
|
||
border-top: 1px solid #eee;
|
||
}
|
||
|
||
.controls-legend .game-objectives h4 {
|
||
margin: 0 0 10px 0;
|
||
color: #333;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.controls-legend .game-objectives ul {
|
||
margin: 0;
|
||
padding-left: 15px;
|
||
color: #666;
|
||
font-size: 0.85rem;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.controls-legend .game-objectives li {
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
/* Game Canvas */
|
||
.game-canvas-container {
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.game-canvas {
|
||
border: 2px solid #ddd;
|
||
border-radius: 8px;
|
||
background: #f0f0f0;
|
||
cursor: crosshair;
|
||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
/* Pause Overlay */
|
||
.pause-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.8);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.pause-message {
|
||
background: white;
|
||
padding: 40px;
|
||
border-radius: 12px;
|
||
text-align: center;
|
||
box-shadow: 0 8px 16px rgba(0,0,0,0.3);
|
||
}
|
||
|
||
.pause-message h2 {
|
||
margin: 0 0 20px 0;
|
||
color: #333;
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.resume-button {
|
||
background: #4CAF50;
|
||
color: white;
|
||
border: none;
|
||
padding: 12px 24px;
|
||
border-radius: 6px;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
transition: background 0.3s;
|
||
}
|
||
|
||
.resume-button:hover {
|
||
background: #45a049;
|
||
}
|
||
|
||
/* Game Controls */
|
||
.game-controls {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin: 20px 0;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
}
|
||
|
||
.control-button {
|
||
background: #F9A22C;
|
||
color: #000;
|
||
border: 1px solid #F9A22C;
|
||
padding: 10px 20px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 0.9rem;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.control-button:hover {
|
||
background: #fdf1db;
|
||
color: #7E471B;
|
||
border: 1px solid #7E471B;
|
||
}
|
||
|
||
.objective-green {
|
||
color: #4CAF50;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.objective-red {
|
||
color: #F44336;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.objective-yellow {
|
||
color: #FF9800;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* Responsive Design */
|
||
@media (max-width: 1024px) {
|
||
.game-layout {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.sidebar-section {
|
||
width: 100%;
|
||
position: static;
|
||
order: -1; /* Sidebar kommt vor dem Spielbrett */
|
||
}
|
||
|
||
.game-board-section {
|
||
width: 100%;
|
||
}
|
||
|
||
.game-area {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.controls-legend {
|
||
width: 100%;
|
||
max-width: 500px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.game-canvas {
|
||
width: 100%;
|
||
height: auto;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.instructions-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.game-controls {
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.control-button {
|
||
width: 200px;
|
||
}
|
||
|
||
.minimap-canvas {
|
||
width: 100%;
|
||
height: auto;
|
||
}
|
||
}
|
||
</style>
|