Files
yourpart3/frontend/src/views/minigames/TaxiGame.vue
Torsten Schulz (local) 935928af75 Änderung: Bereinigung und Optimierung der Taxi-Map-Logik
Ä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.
2025-09-15 19:28:57 +02:00

1137 lines
29 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>