diff --git a/frontend/src/views/minigames/TaxiGame.vue b/frontend/src/views/minigames/TaxiGame.vue
index fd10073..42bc243 100644
--- a/frontend/src/views/minigames/TaxiGame.vue
+++ b/frontend/src/views/minigames/TaxiGame.vue
@@ -66,13 +66,18 @@
+ ❤️ {{ vehicleCount }}
+ ⛽ {{ Math.round(fuel) }}%
+ 📷 {{ speedViolations }}
🚦
{{ redLightViolations }}
- ⏱️
- {{ taxi.speed * 5 }}
- km/h
+
+ ⏱️
+ {{ taxi.speed * 5 }}
+ km/h
+
@@ -143,15 +148,7 @@
{{ $t('minigames.taxi.passengers') }}
-
- {{ crashes }}
- Unfälle
-
-
- {{ fuel }}%
- {{ $t('minigames.taxi.fuel') }}
-
@@ -244,8 +241,18 @@ export default {
lastTrafficLightTick: 0,
redLightViolations: 0,
redLightLastCount: {},
+ lastViolationSound: 0,
lastMinimapDraw: 0,
minimapDrawInterval: 120,
+ radarImg: null,
+ activeRadar: false,
+ radarAtTopEdge: true, // legacy flag (nicht mehr genutzt)
+ radarTicketShown: false,
+ speedViolations: 0,
+ radarAxis: null, // 'H' (horizontale Messlinie: y=konstant) oder 'V' (vertikale Messlinie: x=konstant)
+ radarLinePos: 0,
+ vehicleCount: 5,
+ redLightSincePenalty: 0,
maps: [], // Geladene Maps aus der Datenbank
currentMap: null, // Aktuell verwendete Map
selectedMapId: null, // ID der ausgewählten Map
@@ -466,36 +473,55 @@ export default {
let newRow = this.currentTile.row;
let newCol = this.currentTile.col;
let moved = false;
+ let moveDir = null; // 'right'|'left'|'down'|'up'
// Nach rechts: sobald rechte Kante den Rand erreicht (>=)
if (this.taxi.x + this.taxi.width >= tileSize && newCol < cols - 1) {
this.taxi.x = 0; // direkt an linken Rand des neuen Tiles
newCol += 1;
moved = true;
+ moveDir = 'right';
}
// Nach links: sobald linke Kante den Rand erreicht (<= 0)
if (!moved && this.taxi.x <= 0 && newCol > 0) {
this.taxi.x = tileSize - this.taxi.width; // direkt an rechten Rand des neuen Tiles
newCol -= 1;
moved = true;
+ moveDir = 'left';
}
// Nach unten: sobald untere Kante den Rand erreicht (>=)
if (!moved && this.taxi.y + this.taxi.height >= tileSize && newRow < rows - 1) {
this.taxi.y = 0; // direkt an oberen Rand des neuen Tiles
newRow += 1;
moved = true;
+ moveDir = 'down';
}
// Nach oben: sobald obere Kante den Rand erreicht (<= 0)
if (!moved && this.taxi.y <= 0 && newRow > 0) {
this.taxi.y = tileSize - this.taxi.height; // direkt an unteren Rand des neuen Tiles
newRow -= 1;
moved = true;
+ moveDir = 'up';
}
// Falls Wechsel möglich war, übernehme neue Tile-Position
if (moved) {
this.currentTile.row = newRow;
this.currentTile.col = newCol;
+ // Radar mit 12% Wahrscheinlichkeit aktivieren beim Betreten des neuen Tiles
+ this.activeRadar = Math.random() < 0.12;
+ // Achse und Position festlegen: Messung findet bei 150px vom Rand statt
+ if (this.activeRadar) {
+ const size = this.tiles.size;
+ if (moveDir === 'right') { this.radarAxis = 'V'; this.radarLinePos = 150; }
+ else if (moveDir === 'left') { this.radarAxis = 'V'; this.radarLinePos = size - 150; }
+ else if (moveDir === 'down') { this.radarAxis = 'H'; this.radarLinePos = 150; }
+ else if (moveDir === 'up') { this.radarAxis = 'H'; this.radarLinePos = size - 150; }
+ else { this.radarAxis = 'H'; this.radarLinePos = 150; }
+ } else {
+ this.radarAxis = null;
+ }
+ this.radarTicketShown = false;
return;
}
@@ -733,6 +759,9 @@ export default {
this.checkCollisions();
this.render();
+ // Radar-Messung prüfen (falls aktiv)
+ this.checkRadarMeasurement();
+
// Minimap zeichnen (gedrosselt)
const nowTs = Date.now();
if (nowTs - this.lastMinimapDraw >= this.minimapDrawInterval) {
@@ -961,9 +990,108 @@ export default {
if (now - last > 500) { // 0.5s Sperrzeit
this.redLightLastCount[key] = now;
this.redLightViolations += 1;
+ this.playRedLightSound();
+ // Drei Rotlichtverstöße => Crash-Penalty
+ this.redLightSincePenalty = (this.redLightSincePenalty || 0) + 1;
+ if (this.redLightSincePenalty >= 3) {
+ this.redLightSincePenalty = 0;
+ this.crashes += 1;
+ this.decrementVehicle('redlight');
+ const msg = 'Du hast wegen Rotlicht-Übertretungen ein Auto verloren.';
+ const title = 'Hinweis';
+ this.$root?.$refs?.messageDialog?.open?.(msg, title, {}, () => {
+ if (this.vehicleCount === 0) {
+ this.handleOutOfVehicles();
+ }
+ });
+ }
}
}
},
+
+ checkRadarMeasurement() {
+ if (!this.activeRadar || this.radarTicketShown || !this.radarAxis) return;
+ const size = this.tiles.size;
+ const thresholdSpeedLevel = 10; // >50 km/h
+ const speedLevel = this.taxi.speed;
+
+ const prevCX = this.prevTaxiX + this.taxi.width / 2;
+ const prevCY = this.prevTaxiY + this.taxi.height / 2;
+ const currCX = this.taxi.x + this.taxi.width / 2;
+ const currCY = this.taxi.y + this.taxi.height / 2;
+
+ if (this.radarAxis === 'H') {
+ const y = this.radarLinePos;
+ // Richtung beachten: von oben nach unten über y (wenn y nahe oben), sonst umgekehrt
+ if (y <= size / 2) {
+ if (speedLevel > thresholdSpeedLevel && prevCY < y && currCY >= y) this.issueSpeedTicket();
+ } else {
+ if (speedLevel > thresholdSpeedLevel && prevCY > y && currCY <= y) this.issueSpeedTicket();
+ }
+ } else if (this.radarAxis === 'V') {
+ const x = this.radarLinePos;
+ if (x <= size / 2) {
+ if (speedLevel > thresholdSpeedLevel && prevCX < x && currCX >= x) this.issueSpeedTicket();
+ } else {
+ if (speedLevel > thresholdSpeedLevel && prevCX > x && currCX <= x) this.issueSpeedTicket();
+ }
+ }
+ },
+
+ issueSpeedTicket() {
+ this.radarTicketShown = true;
+ this.speedViolations += 1;
+ this.playCameraSound();
+ },
+
+ playCameraSound() {
+ try {
+ this.ensureAudioUnlockedInEvent();
+ if (!this.audioContext) return;
+ const now = this.audioContext.currentTime;
+ const osc = this.audioContext.createOscillator();
+ const gain = this.audioContext.createGain();
+ // kurzer Klick (Kamera-Shutter)
+ osc.type = 'square';
+ osc.frequency.setValueAtTime(2400, now);
+ gain.gain.setValueAtTime(0, now);
+ gain.gain.linearRampToValueAtTime(0.4, now + 0.005);
+ gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.06);
+ osc.connect(gain).connect(this.audioContext.destination);
+ osc.start(now);
+ osc.stop(now + 0.07);
+ } catch (e) {
+ // ignore
+ }
+ },
+
+ playRedLightSound() {
+ // Ein kurzer Piepton über die bestehende AudioContext-Pipeline
+ try {
+ this.ensureAudioUnlockedInEvent();
+ if (!this.audioContext) return;
+ const now = this.audioContext.currentTime;
+ // Entprellen global: max 1x pro 550ms (Ton ist länger)
+ const wall = (this._rlLastWall || 0);
+ if (now < wall) return;
+ this._rlLastWall = now + 0.55;
+
+ const osc = this.audioContext.createOscillator();
+ const gain = this.audioContext.createGain();
+ osc.type = 'square';
+ // etwas höher
+ osc.frequency.setValueAtTime(1320, now);
+ gain.gain.setValueAtTime(0, now);
+ // etwas lauter und länger
+ gain.gain.linearRampToValueAtTime(0.35, now + 0.01);
+ gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.5);
+ osc.connect(gain).connect(this.audioContext.destination);
+ osc.start(now);
+ osc.stop(now + 0.52);
+ } catch (e) {
+ // ignore
+ }
+ },
handleCrash() {
// Verhindere mehrfache Crashes in kurzer Zeit
@@ -1028,6 +1156,25 @@ export default {
this.canvas.focus();
}
console.log('Nach nextTick - isPaused:', this.isPaused);
+ if (this.vehicleCount === 0) {
+ this.handleOutOfVehicles();
+ }
+ });
+ },
+
+ decrementVehicle(reason = '') {
+ this.vehicleCount = Math.max(0, this.vehicleCount - 1);
+ },
+
+ handleOutOfVehicles() {
+ const title = 'Hinweis';
+ const msg = 'Keine Fahrzeuge mehr. Neustart.';
+ this.$root?.$refs?.messageDialog?.open?.(msg, title, {}, () => {
+ this.restartLevel();
+ this.vehicleCount = 5;
+ this.crashes = 0;
+ this.redLightViolations = 0;
+ this.redLightSincePenalty = 0;
});
},
@@ -1225,6 +1372,15 @@ export default {
// Zeichne Straßen
this.drawRoads();
+
+ // Radar-Icon oben rechts (50x50), wenn aktiv
+ if (this.activeRadar) {
+ const img = this.tiles.images['radar'];
+ if (img && img.complete) {
+ const size = 50;
+ this.ctx.drawImage(img, this.canvas.width - size - 4, 4, size, size);
+ }
+ }
// Zeichne Hindernisse
this.obstacles.forEach(obstacle => {
@@ -1700,6 +1856,10 @@ export default {
img.src = `/images/taxi/${key}.svg`;
this.tiles.images[key] = img;
}
+ // Radar-Icon
+ const radar = new Image();
+ radar.src = '/images/taxi/radar.svg';
+ this.tiles.images['radar'] = radar;
},
loadTaxiImage() {
@@ -2181,7 +2341,7 @@ export default {
.tacho-speed {
display: flex;
- justify-content: flex-end;
+ justify-content: space-between;
align-items: center;
gap: 6px;
font-size: 10pt;
@@ -2192,11 +2352,17 @@ export default {
.redlight-counter { display: inline-flex; align-items: center; gap: 4px; margin-right: 8px; }
.redlight-icon { font-size: 10pt; }
.redlight-value { font-weight: 700; min-width: 16px; text-align: right; }
+.speed-group { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
+.redlight-counter { white-space: nowrap; }
.tacho-icon {
font-size: 10pt;
}
+.lives { font-weight: 700; color: #d60000; }
+.fuel { font-weight: 600; color: #0a7c00; margin-left: 8px; }
+.speed-violations { font-weight: 700; color: #8a2be2; margin-left: 8px; }
+
.speed-value {
font-weight: bold;
color: #2196F3;