Änderung: Hinzufügung der Ampel-Logik zur Taxi-Map
Änderungen: - Erweiterung des TaxiMapTile-Modells um die Spalte trafficLight zur Verwaltung von Ampelzuständen. - Anpassung der TaxiMapService-Logik zur Unterstützung der Ampel-Updates und -Zustände. - Implementierung von Methoden zur Steuerung und Anzeige von Ampeln in der Benutzeroberfläche, einschließlich der neuen Funktionen in TaxiToolsView.vue und TaxiGame.vue. - Verbesserung der Darstellung und Logik zur Ampelsteuerung im Spiel, einschließlich der visuellen Darstellung und der Interaktion mit Ampeln. Diese Anpassungen verbessern die Funktionalität und Benutzererfahrung im Taxi-Minispiel erheblich, indem sie eine realistischere Verkehrssteuerung ermöglichen.
This commit is contained in:
BIN
frontend/public/images/taxi/passenger1.png
Normal file
BIN
frontend/public/images/taxi/passenger1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
BIN
frontend/public/images/taxi/passenger2.png
Normal file
BIN
frontend/public/images/taxi/passenger2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
frontend/public/images/taxi/passenger3.png
Normal file
BIN
frontend/public/images/taxi/passenger3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
BIN
frontend/public/images/taxi/passenger4.png
Normal file
BIN
frontend/public/images/taxi/passenger4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
BIN
frontend/public/images/taxi/passenger5.png
Normal file
BIN
frontend/public/images/taxi/passenger5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
BIN
frontend/public/images/taxi/passenger6.png
Normal file
BIN
frontend/public/images/taxi/passenger6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 175 KiB |
@@ -356,8 +356,8 @@
|
||||
},
|
||||
"gifts": {
|
||||
"Gold Coin": "Goldmünze",
|
||||
"Silk Scarf": "Seidenschale",
|
||||
"Exotic Perfume": "Exotisches Parfum",
|
||||
"Silk Scarf": "Seidenschal",
|
||||
"Exotic Perfume": "Exotisches Parfüm",
|
||||
"Crystal Pendant": "Kristallanhänger",
|
||||
"Leather Journal": "Lederjournal",
|
||||
"Fine Wine": "Feiner Wein",
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
<div class="house-door" :class="doorClass(rotation)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<img v-if="getCellAtPosition(x, y)?.extraTrafficLight" src="/images/taxi/redlight.svg" class="traffic-overlay" alt="Ampel" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,6 +179,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trafficlight-row">
|
||||
<button class="trafficlight-btn" @click="toggleTrafficLight">
|
||||
<img src="/images/taxi/redlight.svg" alt="Ampel" class="trafficlight-icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -249,6 +255,7 @@
|
||||
:alt="getCellAtPosition(x, y).tileType"
|
||||
class="tile-image"
|
||||
/>
|
||||
<img v-if="getCellAtPosition(x, y)?.extraTrafficLight" src="/images/taxi/redlight.svg" class="traffic-overlay" alt="Ampel" />
|
||||
<div v-if="getCellAtPosition(x, y)?.extraHouses" class="house-overlay">
|
||||
<div
|
||||
v-for="(rotation, corner) in getCellAtPosition(x, y).extraHouses"
|
||||
@@ -339,6 +346,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trafficlight-row">
|
||||
<button class="trafficlight-btn" @click="toggleTrafficLight">
|
||||
<img src="/images/taxi/redlight.svg" alt="Ampel" class="trafficlight-icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -795,6 +807,9 @@ export default {
|
||||
// Neue Struktur: Mapping corner -> rotation
|
||||
this.boardCells[key].extraHouses = { ...t.meta.houses };
|
||||
}
|
||||
if (t.meta && t.meta.trafficLight) {
|
||||
this.boardCells[key].extraTrafficLight = true;
|
||||
}
|
||||
}
|
||||
// Straßennamen aus tileStreets übernehmen
|
||||
if (Array.isArray(map.tileStreets)) {
|
||||
@@ -867,6 +882,13 @@ export default {
|
||||
this.computeAllowedHouseCorners();
|
||||
},
|
||||
|
||||
toggleTrafficLight() {
|
||||
if (!this.selectedCellKey) return;
|
||||
const cell = this.boardCells[this.selectedCellKey] || { x: 0, y: 0, tileType: null };
|
||||
const updated = { ...cell, extraTrafficLight: !cell.extraTrafficLight };
|
||||
this.$set ? this.$set(this.boardCells, this.selectedCellKey, updated) : (this.boardCells[this.selectedCellKey] = updated);
|
||||
},
|
||||
|
||||
startPlaceHouse(rotationDeg) {
|
||||
// Neuer Flow: Rotation wählen, nachdem Ecke gewählt wurde
|
||||
if (!this.selectedCellKey || !this.pendingCorner) return;
|
||||
@@ -1155,7 +1177,10 @@ export default {
|
||||
x: c.x,
|
||||
y: c.y,
|
||||
tileType: c.tileType,
|
||||
meta: c.extraHouses ? { houses: { ...c.extraHouses } } : null
|
||||
meta: {
|
||||
...(c.extraHouses ? { houses: { ...c.extraHouses } } : {}),
|
||||
...(c.extraTrafficLight ? { trafficLight: true } : {})
|
||||
}
|
||||
}));
|
||||
const tileStreetNames = this.collectTileStreetNames();
|
||||
const tileHouses = this.collectTileHouses();
|
||||
@@ -1478,10 +1503,15 @@ export default {
|
||||
.house-square.corner-ro { position: absolute; top: 3px; right: 3px; }
|
||||
.house-square.corner-lu { position: absolute; bottom: 3px; left: 3px; }
|
||||
.house-square.corner-ru { position: absolute; bottom: 3px; right: 3px; }
|
||||
.traffic-overlay { position: absolute; width: 25px; height: 25px; left: 50%; top: 50%; transform: translate(-50%, -50%); pointer-events: none; }
|
||||
.corner-chooser { margin-top: 8px; display: flex; gap: 6px; }
|
||||
.corner-btn { padding: 3px; border: 1px solid #ccc; border-radius: 4px; background: #f7f7f7; cursor: pointer; }
|
||||
.corner-btn:hover { background: #eee; }
|
||||
|
||||
.trafficlight-row { margin-top: 10px; }
|
||||
.trafficlight-btn { padding: 4px; border: 1px solid #ccc; border-radius: 6px; background: #fff; cursor: pointer; }
|
||||
.trafficlight-icon { width: 40px; height: 40px; }
|
||||
|
||||
.corner-preview { width: 18px; height: 18px; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
|
||||
.corner-preview .q { width: 100%; height: 100%; background: #000; display: block; }
|
||||
.corner-preview .q.active { background: #d60000; }
|
||||
|
||||
@@ -66,6 +66,10 @@
|
||||
<!-- Tacho-Display -->
|
||||
<div class="tacho-display">
|
||||
<div class="tacho-speed">
|
||||
<span class="redlight-counter" title="Rote Ampeln überfahren">
|
||||
<span class="redlight-icon">🚦</span>
|
||||
<span class="redlight-value">{{ redLightViolations }}</span>
|
||||
</span>
|
||||
<span class="tacho-icon">⏱️</span>
|
||||
<span class="speed-value">{{ taxi.speed * 5 }}</span>
|
||||
<span class="speed-unit">km/h</span>
|
||||
@@ -236,6 +240,9 @@ export default {
|
||||
images: {}
|
||||
},
|
||||
houseImage: null,
|
||||
trafficLightStates: {},
|
||||
lastTrafficLightTick: 0,
|
||||
redLightViolations: 0,
|
||||
maps: [], // Geladene Maps aus der Datenbank
|
||||
currentMap: null, // Aktuell verwendete Map
|
||||
selectedMapId: null, // ID der ausgewählten Map
|
||||
@@ -293,11 +300,43 @@ export default {
|
||||
this.setupEventListeners();
|
||||
await this.initializeMotorSound();
|
||||
this.setupAudioUnlockHandlers();
|
||||
this.lastTrafficLightTick = Date.now();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.cleanup();
|
||||
},
|
||||
methods: {
|
||||
// Ampelschaltung: sekündliche Phasen-Updates; pro Tile ein State
|
||||
updateTrafficLights() {
|
||||
const now = Date.now();
|
||||
if (now - this.lastTrafficLightTick < 1000) return;
|
||||
this.lastTrafficLightTick = now;
|
||||
if (!this.currentMap || !Array.isArray(this.currentMap.tiles)) return;
|
||||
const tiles = this.currentMap.tiles.filter(t => t.trafficLight || (t.meta && t.meta.trafficLight));
|
||||
for (const t of tiles) {
|
||||
const key = `${t.x},${t.y}`;
|
||||
let st = this.trafficLightStates[key];
|
||||
if (!st) {
|
||||
// initialisieren: Phase starten, zufällige Grün/Rot-Dauern 5..15s
|
||||
const r = (min,max)=> Math.floor(min + Math.random()*(max-min+1));
|
||||
st = {
|
||||
phase: 0, // 0:Hgrün/Vrot, 1:Hgelb/Vrotgelb, 2:Hrot/Vgrün, 3:Hrotgelb/Vgelb
|
||||
remaining: r(5,15), // Sekunden bis zur nächsten Umschaltung
|
||||
hDur: r(5,15), // Dauer H-grün (Phase 0)
|
||||
vDur: r(5,15) // Dauer V-grün (Phase 2)
|
||||
};
|
||||
this.trafficLightStates[key] = st;
|
||||
}
|
||||
st.remaining -= 1;
|
||||
if (st.remaining <= 0) {
|
||||
// Phase vorwärts schalten, Zwischenphasen 1s
|
||||
if (st.phase === 0) { st.phase = 1; st.remaining = 1; }
|
||||
else if (st.phase === 1) { st.phase = 2; st.remaining = st.vDur; }
|
||||
else if (st.phase === 2) { st.phase = 3; st.remaining = 1; }
|
||||
else { st.phase = 0; st.remaining = st.hDur; }
|
||||
}
|
||||
}
|
||||
},
|
||||
// Hilfsfunktion: Liefert laufende Nummer für gegebenen Straßennamen
|
||||
getStreetNumberByName(name) {
|
||||
const item = this.streetLegend.find(it => it.name === name);
|
||||
@@ -681,6 +720,9 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ampelschaltung tick
|
||||
this.updateTrafficLights();
|
||||
|
||||
this.updateTaxi();
|
||||
this.handlePassengerActions();
|
||||
this.checkCollisions();
|
||||
@@ -1152,8 +1194,70 @@ export default {
|
||||
// Häuser zeichnen (aus tileHouses)
|
||||
const absCol = this.currentTile.col + (this.currentMap.offsetX || 0);
|
||||
const absRow = this.currentTile.row + (this.currentMap.offsetY || 0);
|
||||
// Haltelinien (falls Ampel vorhanden) – 120px vom Rand, 5px dick, 40px breit
|
||||
if (this.getTrafficLightFor(absCol, absRow)) {
|
||||
const approaches = this.getTileApproaches(tileType);
|
||||
this.drawStopLinesOnMainCanvas(this.ctx, tileSize, approaches);
|
||||
}
|
||||
this.drawHousesOnMainCanvas(this.ctx, tileSize, absCol, absRow);
|
||||
// Straßennamen auf Haupt-Canvas zeichnen
|
||||
// Ampeln an jeder Ecke des Tiles (über den Häusern, unter den Namen)
|
||||
if (this.getTrafficLightFor(absCol, absRow)) {
|
||||
const phase = this.getTrafficLightPhase(absCol, absRow); // 0,1,2,3
|
||||
// Phase-Mapping je Ecke laut Vorgabe:
|
||||
// 0: H grün / V rot
|
||||
// 1: H gelb / V rot-gelb
|
||||
// 2: H rot / V grün
|
||||
// 3: H rot-gelb / V gelb
|
||||
const imgGreen = this.tiles.images['trafficlight-green'];
|
||||
const imgYellow = this.tiles.images['trafficlight-yellow'];
|
||||
const imgRed = this.tiles.images['trafficlight-red'];
|
||||
const imgRedYellow = this.tiles.images['trafficlight-redyellow'];
|
||||
{
|
||||
const sTL = tileSize * 0.16; // um 20% kleiner als zuvor
|
||||
// Positionierung nach Vorgabe:
|
||||
// X: 180px vom linken bzw. rechten Rand, dann je 60px zum näheren Rand und 5px zurück
|
||||
let leftX = 180;
|
||||
let rightX = tileSize - 180 - sTL;
|
||||
leftX = Math.max(0, leftX - 60);
|
||||
rightX = Math.min(tileSize - sTL, rightX + 60);
|
||||
leftX = Math.min(tileSize - sTL, Math.max(0, leftX + 5));
|
||||
rightX = Math.min(tileSize - sTL, Math.max(0, rightX - 5));
|
||||
// Y: oben korrekt, unten 60px weiter nach oben
|
||||
const topY = 180 - sTL;
|
||||
const bottomY = tileSize - 180 - 60;
|
||||
// Sichtbarkeit pro Seite bestimmen und auf Ecken mappen
|
||||
const ap = this.getTileApproaches(tileType);
|
||||
let showTL = false, showTR = false, showBL = false, showBR = false;
|
||||
if (ap.top) { showTL = true; showTR = true; }
|
||||
if (ap.bottom) { showBL = true; showBR = true; }
|
||||
if (ap.left) { showTL = true; showBL = true; }
|
||||
if (ap.right) { showTR = true; showBR = true; }
|
||||
// Tile-spezifische Ausnahmen (z.B. T-Kreuzungen)
|
||||
const hide = this.getCornerHidesForTile(tileType);
|
||||
if (hide.TL) showTL = false;
|
||||
if (hide.TR) showTR = false;
|
||||
if (hide.BL) showBL = false;
|
||||
if (hide.BR) showBR = false;
|
||||
const drawCorner = (corner, x, y) => {
|
||||
let img = imgRed;
|
||||
if (corner === 'top') {
|
||||
if (phase === 0) img = imgGreen; else if (phase === 1) img = imgYellow; else if (phase === 2) img = imgRed; else img = imgRedYellow;
|
||||
} else if (corner === 'bottom') {
|
||||
if (phase === 0) img = imgRed; else if (phase === 1) img = imgRedYellow; else if (phase === 2) img = imgGreen; else img = imgYellow;
|
||||
} else if (corner === 'left') {
|
||||
if (phase === 0) img = imgGreen; else if (phase === 1) img = imgYellow; else if (phase === 2) img = imgRed; else img = imgRedYellow;
|
||||
} else if (corner === 'right') {
|
||||
if (phase === 0) img = imgRed; else if (phase === 1) img = imgRedYellow; else if (phase === 2) img = imgGreen; else img = imgYellow;
|
||||
}
|
||||
if (img && img.complete) this.ctx.drawImage(img, x, y, sTL, sTL);
|
||||
};
|
||||
if (showTL) drawCorner('left', leftX, topY);
|
||||
if (showTR) drawCorner('right', rightX, topY);
|
||||
if (showBL) drawCorner('left', leftX, bottomY);
|
||||
if (showBR) drawCorner('right', rightX, bottomY);
|
||||
}
|
||||
}
|
||||
this.drawStreetNamesOnMainCanvas(this.ctx, tileSize, tileType, absCol, absRow);
|
||||
}
|
||||
}
|
||||
@@ -1172,6 +1276,18 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
getTrafficLightFor(col, row) {
|
||||
// Prüfe Tiles-Array, ob trafficLight-Spalte oder meta.trafficLight gesetzt ist
|
||||
if (!this.currentMap || !Array.isArray(this.currentMap.tiles)) return false;
|
||||
const found = this.currentMap.tiles.find(t => t.x === col && t.y === row && (t.trafficLight === true || (t.meta && t.meta.trafficLight)));
|
||||
return !!found;
|
||||
},
|
||||
getTrafficLightPhase(col, row) {
|
||||
const key = `${col},${row}`;
|
||||
const st = this.trafficLightStates[key];
|
||||
if (!st) return 0; // default
|
||||
return st.phase || 0;
|
||||
},
|
||||
|
||||
drawStreetNamesOnMainCanvas(ctx, size, tileType, col, row) {
|
||||
if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return;
|
||||
@@ -1260,6 +1376,55 @@ export default {
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
// Haltelinien: 120px vom Rand, 5px dick, 40px breit, an allen vier Zufahrten
|
||||
drawStopLinesOnMainCanvas(ctx, size, approaches = { top:true, right:true, bottom:true, left:true }) {
|
||||
const margin = 120; // Basis-Abstand vom Rand
|
||||
const extra = 30; // 10px zurück (von +40 auf +30)
|
||||
const m = margin + extra;
|
||||
const thickness = 5;
|
||||
const width = 60; // +20px breiter (vorher 40)
|
||||
ctx.save();
|
||||
ctx.fillStyle = '#ffffff';
|
||||
// Oben (horizontale Linie, zentriert)
|
||||
if (approaches.top) ctx.fillRect((size - width) / 2 - 30, m - thickness, width, thickness);
|
||||
// Unten
|
||||
if (approaches.bottom) ctx.fillRect((size - width) / 2 + 30, size - m, width, thickness);
|
||||
// Links (vertikale Linie, zentriert)
|
||||
if (approaches.left) ctx.fillRect(m - thickness, (size - width) / 2 + 30, thickness, width);
|
||||
// Rechts
|
||||
if (approaches.right) ctx.fillRect(size - m, (size - width) / 2 - 30, thickness, width);
|
||||
ctx.restore();
|
||||
},
|
||||
// Liefert, von welchen Seiten eine Straße an dieses Tile anbindet
|
||||
getTileApproaches(tileType) {
|
||||
switch (tileType) {
|
||||
case 'horizontal': return { top:false, right:true, bottom:false, left:true };
|
||||
case 'vertical': return { top:true, right:false, bottom:true, left:false };
|
||||
case 'cross': return { top:true, right:true, bottom:true, left:true };
|
||||
case 'cornertopleft': return { top:true, right:false, bottom:false, left:true };
|
||||
case 'cornertopright': return { top:true, right:true, bottom:false, left:false };
|
||||
case 'cornerbottomleft': return { top:false, right:false, bottom:true, left:true };
|
||||
case 'cornerbottomright': return { top:false, right:true, bottom:true, left:false };
|
||||
case 'tup': return { top:true, right:true, bottom:false, left:true }; // T-oben: unten gesperrt
|
||||
case 'tdown': return { top:false, right:true, bottom:true, left:true }; // T-unten: oben gesperrt
|
||||
case 'tleft': return { top:true, right:false, bottom:true, left:true }; // T-links: rechts gesperrt
|
||||
case 'tright': return { top:true, right:true, bottom:true, left:false }; // T-rechts: links gesperrt
|
||||
case 'fuelhorizontal': return { top:false, right:true, bottom:false, left:true };
|
||||
case 'fuelvertical': return { top:true, right:false, bottom:true, left:false };
|
||||
default: return { top:true, right:true, bottom:true, left:true };
|
||||
}
|
||||
},
|
||||
// Ecken-spezifische Ausblendungen je Tiletyp (zusätzlich zu Seiten-Logik)
|
||||
getCornerHidesForTile(tileType) {
|
||||
// TL=oben links, TR=oben rechts, BL=unten links, BR=unten rechts
|
||||
switch (tileType) {
|
||||
case 'tdown': return { TL: true, TR: false, BL: false, BR: false }; // oben gesperrt -> oben links weg
|
||||
case 'tup': return { TL: false, TR: false, BL: false, BR: true }; // unten gesperrt -> unten rechts weg
|
||||
case 'tleft': return { TL: false, TR: true, BL: false, BR: false }; // rechts gesperrt -> oben rechts weg
|
||||
case 'tright': return { TL: false, TR: false, BL: true, BR: false }; // links gesperrt -> unten links weg
|
||||
default: return { TL: false, TR: false, BL: false, BR: false };
|
||||
}
|
||||
},
|
||||
|
||||
getTileType(row, col, rows, cols) {
|
||||
// Ecken
|
||||
@@ -1410,6 +1575,19 @@ export default {
|
||||
img.src = `/images/taxi/${tileName}.svg`;
|
||||
this.tiles.images[tileName] = img;
|
||||
}
|
||||
|
||||
// Lade Ampel-Icon (zentral, 50% Größe)
|
||||
const red = new Image();
|
||||
red.src = '/images/taxi/redlight.svg';
|
||||
this.tiles.images['redlight-svg'] = red;
|
||||
|
||||
// Lade Trafficlight-Zustände (für großes Tile an den Ecken)
|
||||
const tlStates = ['trafficlight-red','trafficlight-yellow','trafficlight-redyellow','trafficlight-green'];
|
||||
for (const key of tlStates) {
|
||||
const img = new Image();
|
||||
img.src = `/images/taxi/${key}.svg`;
|
||||
this.tiles.images[key] = img;
|
||||
}
|
||||
},
|
||||
|
||||
loadTaxiImage() {
|
||||
@@ -1539,6 +1717,15 @@ export default {
|
||||
|
||||
// Straßennamen-Nummern zeichnen, basierend auf tileStreets (nur einmal pro Name)
|
||||
this.drawStreetNumbersOnMinimap(ctx, x, y, tileSize, tileType, absCol, absRow, drawnNames);
|
||||
|
||||
// Ampel im Minimap zentriert (50% Größe)
|
||||
if (this.getTrafficLightFor(absCol, absRow)) {
|
||||
const img = this.tiles.images['redlight-svg'];
|
||||
if (img && img.complete) {
|
||||
const s = tileSize * 0.3; // 60% der bisherigen 50%-Größe
|
||||
ctx.drawImage(img, x + (tileSize - s) / 2, y + (tileSize - s) / 2, s, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1748,7 +1935,36 @@ export default {
|
||||
if (t.h && t.v) { addEdge(keyH, keyV); }
|
||||
}
|
||||
if (nodes.size === 0) continue;
|
||||
let startKey = null; for (const k of nodes.keys()) { if ((adj.get(k)?.size || 0) === 1) { startKey = k; break; } } if (!startKey) startKey = Array.from(nodes.keys()).sort()[0];
|
||||
// Startknoten deterministisch wählen:
|
||||
// 1) bevorzuge Endpunkte (Grad=1)
|
||||
// 2) bevorzuge horizontale Segmente (axis==='h') und wähle den mit kleinstem x (links)
|
||||
// 3) sonst vertikale Segmente (axis==='v') mit kleinstem y (oben)
|
||||
const nodeEntries = Array.from(nodes.entries());
|
||||
const deg = (k) => (adj.get(k)?.size || 0);
|
||||
let candidates = nodeEntries.filter(([k]) => deg(k) === 1);
|
||||
if (candidates.length === 0) candidates = nodeEntries;
|
||||
let horiz = candidates.filter(([, n]) => n.axis === 'h');
|
||||
let startPair = null;
|
||||
if (horiz.length > 0) {
|
||||
// Links-vorne nimmt Vorrang (kleinstes x, dann kleinstes y)
|
||||
startPair = horiz.reduce((best, curr) => {
|
||||
if (!best) return curr;
|
||||
const bn = best[1], cn = curr[1];
|
||||
if (cn.x < bn.x) return curr;
|
||||
if (cn.x === bn.x && cn.y < bn.y) return curr;
|
||||
return best;
|
||||
}, null);
|
||||
} else {
|
||||
const vert = candidates.filter(([, n]) => n.axis === 'v');
|
||||
startPair = vert.reduce((best, curr) => {
|
||||
if (!best) return curr;
|
||||
const bn = best[1], cn = curr[1];
|
||||
if (cn.y < bn.y) return curr;
|
||||
if (cn.y === bn.y && cn.x < bn.x) return curr;
|
||||
return best;
|
||||
}, null) || candidates[0];
|
||||
}
|
||||
let startKey = startPair ? startPair[0] : (nodeEntries[0] && nodeEntries[0][0]);
|
||||
const visited = new Set(); let odd = 1, even = 2; let prev = null; let curr = startKey; let currOddSide = (nodes.get(curr).axis === 'h') ? 'top' : 'left';
|
||||
while (curr) {
|
||||
visited.add(curr);
|
||||
@@ -1773,8 +1989,15 @@ export default {
|
||||
});
|
||||
if (!housesFiltered.length) { prev = curr; curr = next || null; continue; }
|
||||
const orderCorners = (list) => {
|
||||
if (axis === 'h') { const movingRight = dir.dx > 0 || (dir.dx === 0 && !prev); const seq = movingRight ? ['lo','ro','lu','ru'] : ['ro','lo','ru','lu']; return list.slice().sort((a,b)=> seq.indexOf(a) - seq.indexOf(b)); }
|
||||
else { const movingDown = dir.dy > 0 || (dir.dy === 0 && !prev); const seq = movingDown ? ['lo','lu','ro','ru'] : ['lu','lo','ru','ro']; return list.slice().sort((a,b)=> seq.indexOf(a) - seq.indexOf(b)); }
|
||||
if (axis === 'h') {
|
||||
// Erzwinge Nummerierung beginnend von links nach rechts
|
||||
const seq = ['lo','ro','lu','ru'];
|
||||
return list.slice().sort((a,b)=> seq.indexOf(a) - seq.indexOf(b));
|
||||
} else {
|
||||
const movingDown = dir.dy > 0 || (dir.dy === 0 && !prev);
|
||||
const seq = movingDown ? ['lo','lu','ro','ru'] : ['lu','lo','ru','ro'];
|
||||
return list.slice().sort((a,b)=> seq.indexOf(a) - seq.indexOf(b));
|
||||
}
|
||||
};
|
||||
const oddCorners = (axis === 'h') ? (currOddSide === 'top' ? ['lo','ro'] : ['lu','ru']) : (currOddSide === 'left' ? ['lo','lu'] : ['ro','ru']);
|
||||
const evenCorners = (axis === 'h') ? (currOddSide === 'top' ? ['lu','ru'] : ['lo','ro']) : (currOddSide === 'left' ? ['ro','ru'] : ['lo','lu']);
|
||||
@@ -1854,6 +2077,10 @@ export default {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.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; }
|
||||
|
||||
.tacho-icon {
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user