diff --git a/frontend/src/views/minigames/TaxiGame.vue b/frontend/src/views/minigames/TaxiGame.vue index f1ec1e8..8e57aa1 100644 --- a/frontend/src/views/minigames/TaxiGame.vue +++ b/frontend/src/views/minigames/TaxiGame.vue @@ -467,7 +467,7 @@ export default { const tileType = this.mapTileTypeToStreetCoordinates(this.getCurrentTileType()); // Toleranzfenster um die Mittellinien, damit der Trigger nicht frame-genau sein muss - const tol = 0.01; // ~5px bei 500px Tilegröße + const tol = 0.03; // ~15px bei 500px Tilegröße – breitere Toleranz const nearYCenter = ry > 0.5 - tol && ry < 0.5 + tol; const nearXCenter = rx > 0.5 - tol && rx < 0.5 + tol; @@ -492,6 +492,29 @@ export default { if (car.direction === 'down' && nearYCenter) return 'left'; if (car.direction === 'right' && nearXCenter) return 'up'; return null; + case 'tdown': { + // T-Kreuzung: unten, links, rechts offen; oben verboten + if (!(nearXCenter || nearYCenter)) { + // Sonderfall: Von oben kommend früher zwingen, bevor die Mitte exakt erreicht ist + if (car.direction === 'up' && ry <= 0.5 + tol) { + return Math.random() < 0.5 ? 'left' : 'right'; + } + return null; + } + const choices = []; + if (car.direction === 'down') { + // Von oben kommend: NICHT weiter nach unten fahren + choices.push('left', 'right'); + } else if (car.direction === 'left' || car.direction === 'right') { + // Geradeaus erlaubt (links/rechts), außerdem nach unten + choices.push(car.direction, 'down'); + } else if (car.direction === 'up') { + // Von oben: nur links oder rechts, niemals weiter nach oben + choices.push('left', 'right'); + } + if (choices.length === 0) return null; + return choices[Math.floor(Math.random() * choices.length)]; + } default: // Kreuzung: bei Zentrumslinien abbiegen erlaubt – Richtung später wählen if (nearXCenter || nearYCenter) return this.getRandomTargetDirection(car.direction); @@ -698,6 +721,10 @@ export default { if (moved) { this.currentTile.row = newRow; this.currentTile.col = newCol; + + // Entferne alle Autos vom alten Tile + this.cars = []; + // Nach Tile-Wechsel: eine Frame lang Rotlicht-Prüfung aussetzen // und prev-Position auf aktuelle setzen, um false positives zu vermeiden this.prevTaxiX = this.taxi.x; @@ -1472,7 +1499,14 @@ export default { lastPosition: { x: spawnPosition.x, y: spawnPosition.y }, // Letzte Position für Bewegung hasTurnedAtIntersection: false, // Flag um mehrfaches Abbiegen zu verhindern targetDirection: null, // Zielrichtung an der Kreuzung (wird beim Tile-Betreten festgelegt) - targetLane: null // Zielspur basierend auf Zielrichtung + targetLane: null, // Zielspur basierend auf Zielrichtung + laneAxis: spawnPosition.laneAxis, + laneCoord: spawnPosition.laneCoord, + willTurn: spawnPosition.willTurn, + turnDirection: spawnPosition.turnDirection, + turnX: spawnPosition.turnX, + turnY: spawnPosition.turnY, + turnAngle: spawnPosition.turnAngle }; this.cars.push(car); @@ -1486,13 +1520,13 @@ export default { // Basierend auf der tile-Struktur und welche Seiten offen sind switch (tileType) { case 'cornertopleft': - return ['left', 'up']; // Bewegt sich nach links (von rechts spawnen) und nach oben (von unten spawnen) + return ['right', 'down']; // Kommt von links (bewegt sich nach rechts) und von oben (bewegt sich nach unten) case 'cornertopright': - return ['right', 'up']; // Bewegt sich nach rechts (von links spawnen) und nach oben (von unten spawnen) + return ['left', 'down']; // Kommt von rechts (bewegt sich nach links) und von oben (bewegt sich nach unten) case 'cornerbottomleft': - return ['left', 'down']; // Bewegt sich nach links (von rechts spawnen) und nach unten (von oben spawnen) + return ['right', 'up']; // Kommt von links (bewegt sich nach rechts) und von unten (bewegt sich nach oben) case 'cornerbottomright': - return ['left', 'up']; // Bewegt sich nach links (von rechts spawnen) und nach oben (von unten spawnen) + return ['left', 'up']; // Kommt von rechts (bewegt sich nach links) und von unten (bewegt sich nach oben) case 'horizontal': case 'fuelhorizontal': return ['left', 'right']; // Bewegt sich nach links (von rechts spawnen) und nach rechts (von links spawnen) @@ -1504,16 +1538,190 @@ export default { case 'tup': return ['up', 'left', 'right']; // Bewegt sich nach oben, links und rechts case 'tdown': - return ['down', 'left', 'right']; // Bewegt sich nach unten, links und rechts + return ['up', 'left', 'right']; // Fahrzeuge kommen von unten/links/rechts (nicht von oben) case 'tleft': - return ['left', 'up', 'down']; // Bewegt sich nach links, oben und unten + return ['right', 'up', 'down']; // Kommt von links (bewegt sich nach rechts), von oben (bewegt sich nach unten) und von unten (bewegt sich nach oben) case 'tright': - return ['right', 'up', 'down']; // Bewegt sich nach rechts, oben und unten + return ['left', 'up', 'down']; // Kommt von rechts (bewegt sich nach links), von oben (bewegt sich nach unten) und von unten (bewegt sich nach oben) default: return []; // Keine erlaubten Spawn-Richtungen } }, + // Berechnet ob und wo ein Auto abbiegen wird + calculateTurnInfo(tileType, direction) { + const tileSize = this.tiles.size; + + // Für tdown: Autos müssen abbiegen + if (tileType === 'tdown') { + if (direction === 'up') { + // Kommt von unten, muss nach links oder rechts abbiegen + const turnDirection = Math.random() < 0.5 ? 'left' : 'right'; + const turnX = turnDirection === 'left' ? 0.375 * tileSize : 0.625 * tileSize; + const turnY = 0.5 * tileSize; // Mitte des Tiles + const turnAngle = turnDirection === 'left' ? Math.PI : 0; + + return { + willTurn: true, + turnDirection: turnDirection, + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } + } + + // Für cornerBottomRight: Verbindet untere und rechte Seite + // Kurve von unten nach rechts oder von rechts nach unten + if (tileType === 'cornerbottomright') { + if (direction === 'left') { + // Kommt von rechts (Y-Spur ~212.5px = 0.425), biegt nach unten ab (X-Spur ~212.5px = 0.425) + const laneMargin = 0.03; + const laneCenter = 0.375 + (0.5 - 0.375) / 2; // Mitte der linken/oberen Spur = 0.4375 + const turnX = laneCenter * tileSize; // X-Position nach dem Abbiegen + const turnY = laneCenter * tileSize; // Y-Position beim Abbiegen + const turnAngle = Math.PI / 2; // nach unten + + return { + willTurn: true, + turnDirection: 'down', + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } else if (direction === 'up') { + // Kommt von unten (X-Spur ~281px = 0.5625), biegt nach rechts ab (Y-Spur ~281px = 0.5625) + const laneCenter = 0.5 + (0.625 - 0.5) / 2; // Mitte der rechten/unteren Spur = 0.5625 + const turnX = laneCenter * tileSize; // X-Position beim Abbiegen + const turnY = laneCenter * tileSize; // Y-Position nach dem Abbiegen + const turnAngle = 0; // nach rechts + + return { + willTurn: true, + turnDirection: 'right', + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } + } + + // Für cornerTopRight: Verbindet obere und rechte Seite + // Kurve von oben nach rechts oder von rechts nach oben + if (tileType === 'cornertopright') { + if (direction === 'left') { + // Kommt von rechts (Y-Spur ~212.5px), biegt nach oben ab (X-Spur ~281px) + const laneCenterLower = 0.375 + (0.5 - 0.375) / 2; // 0.4375 + const laneCenterUpper = 0.5 + (0.625 - 0.5) / 2; // 0.5625 + const turnX = laneCenterUpper * tileSize; // X-Position nach dem Abbiegen + const turnY = laneCenterLower * tileSize; // Y-Position beim Abbiegen + const turnAngle = -Math.PI / 2; // nach oben + + return { + willTurn: true, + turnDirection: 'up', + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } else if (direction === 'down') { + // Kommt von oben (X-Spur ~281px), biegt nach rechts ab (Y-Spur ~281px) + const laneCenter = 0.5 + (0.625 - 0.5) / 2; // 0.5625 + const turnX = laneCenter * tileSize; + const turnY = laneCenter * tileSize; + const turnAngle = 0; // nach rechts + + return { + willTurn: true, + turnDirection: 'right', + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } + } + + // Für cornerTopLeft: Verbindet obere und linke Seite + // Kurve von oben nach links oder von links nach oben + if (tileType === 'cornertopleft') { + if (direction === 'right') { + // Kommt von links (Y-Spur ~281px), biegt nach oben ab (X-Spur ~281px) + const laneCenter = 0.5 + (0.625 - 0.5) / 2; // 0.5625 + const turnX = laneCenter * tileSize; + const turnY = laneCenter * tileSize; + const turnAngle = -Math.PI / 2; // nach oben + + return { + willTurn: true, + turnDirection: 'up', + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } else if (direction === 'down') { + // Kommt von oben (X-Spur ~212.5px), biegt nach links ab (Y-Spur ~281px) + const laneCenterLower = 0.375 + (0.5 - 0.375) / 2; // 0.4375 + const laneCenterUpper = 0.5 + (0.625 - 0.5) / 2; // 0.5625 + const turnX = laneCenterLower * tileSize; // X-Position beim Abbiegen + const turnY = laneCenterUpper * tileSize; // Y-Position nach dem Abbiegen + const turnAngle = Math.PI; // nach links + + return { + willTurn: true, + turnDirection: 'left', + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } + } + + // Für cornerBottomLeft: Verbindet untere und linke Seite + // Kurve von unten nach links oder von links nach unten + if (tileType === 'cornerbottomleft') { + if (direction === 'right') { + // Kommt von links (Y-Spur ~281px), biegt nach unten ab (X-Spur ~212.5px) + const laneCenterLower = 0.375 + (0.5 - 0.375) / 2; // 0.4375 + const laneCenterUpper = 0.5 + (0.625 - 0.5) / 2; // 0.5625 + const turnX = laneCenterLower * tileSize; // X-Position nach dem Abbiegen + const turnY = laneCenterUpper * tileSize; // Y-Position beim Abbiegen + const turnAngle = Math.PI / 2; // nach unten + + return { + willTurn: true, + turnDirection: 'down', + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } else if (direction === 'up') { + // Kommt von unten (X-Spur ~281px), biegt nach links ab (Y-Spur ~212.5px) + const laneCenterLower = 0.375 + (0.5 - 0.375) / 2; // 0.4375 + const laneCenterUpper = 0.5 + (0.625 - 0.5) / 2; // 0.5625 + const turnX = laneCenterUpper * tileSize; // X-Position beim Abbiegen + const turnY = laneCenterLower * tileSize; // Y-Position nach dem Abbiegen + const turnAngle = Math.PI; // nach links + + return { + willTurn: true, + turnDirection: 'left', + turnX: turnX, + turnY: turnY, + turnAngle: turnAngle + }; + } + } + + // Standard: Kein Abbiegen + return { + willTurn: false, + turnDirection: null, + turnX: null, + turnY: null, + turnAngle: null + }; + }, + // Finde eine zufällige befahrbare Spawn-Position für ein Auto getRandomCarSpawnPosition() { if (!this.currentMap || !this.currentMap.tiles) { @@ -1548,26 +1756,35 @@ export default { const spawnPositionsMap = { 'right': { relativeX: -0.02, pickRelY: () => 0.5 + laneMargin + Math.random() * (0.625 - 0.5 - 2 * laneMargin), angle: 0, direction: 'right', laneAxis: 'H' }, 'left': { relativeX: 1.02, pickRelY: () => 0.375 + laneMargin + Math.random() * (0.5 - 0.375 - 2 * laneMargin), angle: Math.PI, direction: 'left', laneAxis: 'H' }, - 'down': { pickRelX: () => 0.375 + laneMargin + Math.random() * (0.5 - 0.375 - 2 * laneMargin), relativeY: -0.02, angle: Math.PI / 2, direction: 'down', laneAxis: 'V' }, - 'up': { pickRelX: () => 0.5 + laneMargin + Math.random() * (0.625 - 0.5 - 2 * laneMargin), relativeY: 1.02, angle: -Math.PI / 2, direction: 'up', laneAxis: 'V' } + 'up': { pickRelX: () => 0.5 + laneMargin + Math.random() * (0.625 - 0.5 - 2 * laneMargin), relativeY: 1.02, angle: -Math.PI / 2, direction: 'up', laneAxis: 'V' }, + 'down': { pickRelX: () => 0.375 + laneMargin + Math.random() * (0.5 - 0.375 - 2 * laneMargin), relativeY: -0.02, angle: Math.PI / 2, direction: 'down', laneAxis: 'V' } }; // Wähle eine zufällige erlaubte Richtung - const randomDirection = allowedDirections[Math.floor(Math.random() * allowedDirections.length)]; + const filtered = allowedDirections; + const randomDirection = filtered[Math.floor(Math.random() * filtered.length)]; const spawnPos = spawnPositionsMap[randomDirection]; if (!spawnPos) { return null; } + // Berechne ob und wo das Auto abbiegen wird + const turnInfo = this.calculateTurnInfo(tileType, randomDirection); - - // Konvertiere relative Koordinaten zu absoluten Pixeln + // Alle Autos spawnen außerhalb des Tiles (normale Positionierung) + // x und y sind die Mittelpunkte der Spawn-Position let x = (spawnPos.relativeX != null ? spawnPos.relativeX : (spawnPos.pickRelX ? spawnPos.pickRelX() : 0.5)) * tileSize; let y = (spawnPos.relativeY != null ? spawnPos.relativeY : (spawnPos.pickRelY ? spawnPos.pickRelY() : 0.5)) * tileSize; + let angle = spawnPos.angle; + let laneCoord; + // Berechne laneCoord basierend auf der Mittelpunkt-Position (BEVOR wir zu linker oberer Ecke konvertieren) + // laneCoord ist IMMER die aktuelle Spur-Position auf der Querachse + // Beim Abbiegen wird laneCoord später in updateCarMovement aktualisiert + laneCoord = spawnPos.laneAxis === 'H' ? y : x; - + // Jetzt konvertiere zu linker oberer Ecke für die Rendering-Position // Passe Koordinaten an: Die Spawn-Position ist die Straßenlinie, aber wir brauchen die linke obere Ecke des Autos // Das Auto muss auf der Linie zentriert sein switch (randomDirection) { @@ -1586,22 +1803,19 @@ export default { if (y > tileSize) y = tileSize; // Unten außerhalb break; } - - - - // laneCoord speichert die Zielspur (Mitte) auf der Querachse - // Verwende die bereits berechneten absoluten x/y, um exakt dieselbe Spur zu halten - const laneCoord = spawnPos.laneAxis === 'H' - ? (y + carHeight / 2) // Y-Mitte - : (x + carWidth / 2); // X-Mitte return { x: x, y: y, - angle: spawnPos.angle, - direction: spawnPos.direction, + angle: angle, + direction: randomDirection, laneAxis: spawnPos.laneAxis, - laneCoord: laneCoord + laneCoord: laneCoord, + willTurn: turnInfo.willTurn, + turnDirection: turnInfo.turnDirection, + turnX: turnInfo.turnX, + turnY: turnInfo.turnY, + turnAngle: turnInfo.turnAngle }; }, @@ -1757,9 +1971,129 @@ export default { // Aktualisiere die Bewegung eines einzelnen Autos (vereinfachte Taxi-Logik) updateCarMovement(car) { + // 0) Prüfe ob das Auto abbiegen sollte (neue Turn-Logik) + if (car.willTurn && !car.hasTurnedAtIntersection) { + // Auto soll abbiegen - prüfe ob es die Turn-Position erreicht oder überschritten hat + const carCenterX = car.x + car.width / 2; + const carCenterY = car.y + car.height / 2; + + let shouldTurn = false; + + // Prüfe ob das Auto die Turn-Position erreicht hat (abhängig von der Fahrtrichtung) + if (car.direction === 'up') { + // Fährt nach oben - prüfe ob Y-Position erreicht ist + shouldTurn = carCenterY <= car.turnY; + } else if (car.direction === 'down') { + // Fährt nach unten - prüfe ob Y-Position erreicht ist + shouldTurn = carCenterY >= car.turnY; + } else if (car.direction === 'left') { + // Fährt nach links - prüfe ob X-Position erreicht ist + shouldTurn = carCenterX <= car.turnX; + } else if (car.direction === 'right') { + // Fährt nach rechts - prüfe ob X-Position erreicht ist + shouldTurn = carCenterX >= car.turnX; + } + + if (shouldTurn) { + // Merke alte Richtung und Position für Animation + const oldDirection = car.direction; + + car.direction = car.turnDirection; + car.hasTurnedAtIntersection = true; + + // Starte Animation für sanfte Drehung (0.5 Sekunden) + car.isTurning = true; + car.turnStartAngle = car.angle; + car.turnTargetAngle = car.turnAngle; + car.turnStartTime = Date.now(); + car.turnDuration = 250; // 0.25 Sekunden + + // Speichere Start- und Ziel-Position für smooth Interpolation + car.turnStartX = car.x; + car.turnStartY = car.y; + + // Berechne Ziel-Position + if (oldDirection === 'up' || oldDirection === 'down') { + // War vertikal, wird horizontal - setze Y-Position auf turnY + car.turnTargetY = car.turnY - car.height / 2; + car.turnTargetX = car.x; // X bleibt gleich + } else { + // War horizontal, wird vertikal - setze X-Position auf turnX + car.turnTargetX = car.turnX - car.width / 2; + car.turnTargetY = car.y; // Y bleibt gleich + } + + // Aktualisiere laneAxis und laneCoord für die neue Richtung + // laneCoord ist die Position auf der Querachse (wo das Auto bleiben soll) + if (car.turnDirection === 'left' || car.turnDirection === 'right') { + // Fährt jetzt horizontal - X ändert sich, Y bleibt fix + car.laneAxis = 'H'; + car.laneCoord = car.turnY; // Bleibe auf dieser Y-Position + } else { + // Fährt jetzt vertikal - Y ändert sich, X bleibt fix + car.laneAxis = 'V'; + car.laneCoord = car.turnX; // Bleibe auf dieser X-Position + } + } + } + + // Animiere Drehung wenn Auto gerade abbiegt + if (car.isTurning) { + const now = Date.now(); + const elapsed = now - car.turnStartTime; + const progress = Math.min(elapsed / car.turnDuration, 1.0); // 0.0 bis 1.0 + + // Easing-Funktion für smooth animation (ease-in-out) + const eased = progress < 0.5 + ? 2 * progress * progress + : 1 - Math.pow(-2 * progress + 2, 2) / 2; + + // Normalisiere Winkel in [-π, π] + const normalizeAngle = (a) => { + while (a > Math.PI) a -= 2 * Math.PI; + while (a < -Math.PI) a += 2 * Math.PI; + return a; + }; + + // Interpoliere Winkel + let startAngle = normalizeAngle(car.turnStartAngle); + let targetAngle = normalizeAngle(car.turnTargetAngle); + + // Kürzesten Weg für Rotation finden mit atan2 + // Berechne den Unterschied und verwende atan2 für korrekten Quadranten + let diff = Math.atan2(Math.sin(targetAngle - startAngle), Math.cos(targetAngle - startAngle)); + + // Debug: Log bei Start der Animation + if (elapsed < 50 && !car._loggedTurn) { + console.log(`Turn animation: start=${(startAngle * 180 / Math.PI).toFixed(1)}° target=${(targetAngle * 180 / Math.PI).toFixed(1)}° diff=${(diff * 180 / Math.PI).toFixed(1)}°`); + car._loggedTurn = true; + } + + car.angle = normalizeAngle(startAngle + diff * eased); + + // Animation beenden wenn fertig + if (progress >= 1.0) { + car.isTurning = false; + car.angle = targetAngle; + } + } - // 1) An Abbiegepunkt? Dann (ggf.) vorgegebene Zielrichtung übernehmen - const forcedDir = this.shouldCarTurnAtIntersection(car); + // 1) Alte Turn-Logik (nur für Autos ohne vorberechnete Turns - z.B. T-Kreuzungen ohne willTurn) + // Diese Logik wird nur noch für Tile-Typen ohne vorberechnete Turn-Info verwendet + let forcedDir = null; + if (!car.hasTurnedAtIntersection && !car.willTurn) { + forcedDir = this.shouldCarTurnAtIntersection(car); + // Strikter Zwang für T-Down: von unten kommend (direction 'up') niemals geradeaus weiter + const streetType = this.mapTileTypeToStreetCoordinates(this.getCurrentTileType()); + if (!forcedDir && streetType === 'tDown' && car.direction === 'up') { + const cy = car.y + car.height / 2; + const tolAbs = 0.03 * this.tiles.size; // ~15px + const centerYAbs = 0.5 * this.tiles.size; + if (Math.abs(cy - centerYAbs) <= tolAbs || cy <= centerYAbs + tolAbs) { + forcedDir = Math.random() < 0.5 ? 'left' : 'right'; + } + } + } if (forcedDir) { const tileType = this.getCurrentTileType(); // forcedDir hat Vorrang – NICHT durch allowedDirections einschränken @@ -1804,11 +2138,14 @@ export default { case 'right': newX = car.x + car.speed; break; } - // Querachse fixieren + // Querachse fixieren - Auto bleibt auf seiner Spur + // Während der Turn-Animation: Sanft zur neuen Spur gleiten if (car.laneAxis === 'H') { + // Horizontale Bewegung - Y-Position fixieren const centerY = car.laneCoord; newY = centerY - car.height / 2; } else if (car.laneAxis === 'V') { + // Vertikale Bewegung - X-Position fixieren const centerX = car.laneCoord; newX = centerX - car.width / 2; } @@ -3377,25 +3714,9 @@ export default { // Transformiere zum Zentrum des Autos für Rotation this.ctx.translate(centerX, centerY); - // Rotiere basierend auf der Fahrtrichtung (nicht angle) - let rotationAngle = 0; - switch (car.direction) { - case 'up': - rotationAngle = -Math.PI / 2; - break; - case 'down': - rotationAngle = Math.PI / 2; - break; - case 'left': - rotationAngle = Math.PI; - break; - case 'right': - rotationAngle = 0; - break; - } - + // Verwende car.angle für smooth rotation während des Abbiegens // Auto-Bild korrigieren (90° + 180° für korrekte Ausrichtung) - const finalAngle = rotationAngle + Math.PI / 2 + Math.PI; + const finalAngle = car.angle + Math.PI / 2 + Math.PI; this.ctx.rotate(finalAngle); if (this.carImage) {