From 7207274ab5a2ab0d4bb53f8b0d05fc80a7950b33 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 18 Sep 2025 14:27:14 +0200 Subject: [PATCH] =?UTF-8?q?=C3=84nderung:=20Hinzuf=C3=BCgung=20der=20Haus-?= =?UTF-8?q?Logik=20zur=20Taxi-Map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Änderungen: - Integration des neuen Modells TaxiMapTileHouse zur Verwaltung von Häusern auf der Karte. - Anpassung der TaxiMap- und TaxiMapService-Logik zur Unterstützung der Hausplatzierung und -verwaltung. - Erweiterung der Benutzeroberfläche in TaxiToolsView.vue zur Erfassung und Anzeige von Hausinformationen. - Implementierung von Methoden zur Speicherung und Aktualisierung von Hausdaten in der Datenbank. Diese Anpassungen verbessern die Funktionalität und Benutzererfahrung im Taxi-Minispiel, indem sie eine detaillierte Verwaltung von Häusern auf der Karte ermöglichen. --- backend/models/associations.js | 5 + backend/models/index.js | 3 +- backend/models/taxi/index.js | 3 +- backend/models/taxi/taxiMapTileHouse.js | 56 ++++++++ backend/services/taxiMapService.js | 35 ++++- backend/utils/initializeTaxi.js | 8 ++ frontend/src/views/admin/TaxiToolsView.vue | 37 ++++- frontend/src/views/minigames/TaxiGame.vue | 156 ++++++++++++++++++++- 8 files changed, 290 insertions(+), 13 deletions(-) create mode 100644 backend/models/taxi/taxiMapTileHouse.js diff --git a/backend/models/associations.js b/backend/models/associations.js index 350d460..9b7859d 100644 --- a/backend/models/associations.js +++ b/backend/models/associations.js @@ -107,6 +107,7 @@ import TaxiLevelStats from './taxi/taxiLevelStats.js'; import TaxiMapType from './taxi/taxiMapType.js'; import TaxiMap from './taxi/taxiMap.js'; import TaxiMapTile from './taxi/taxiMapTile.js'; +import TaxiMapTileHouse from './taxi/taxiMapTileHouse.js'; import TaxiStreetName from './taxi/taxiStreetName.js'; import TaxiMapTileStreet from './taxi/taxiMapTileStreet.js'; @@ -815,4 +816,8 @@ export default function setupAssociations() { TaxiMapTileStreet.belongsTo(TaxiStreetName, { foreignKey: 'street_name_h_id', as: 'streetNameH' }); TaxiMapTileStreet.belongsTo(TaxiStreetName, { foreignKey: 'street_name_v_id', as: 'streetNameV' }); + + // Houses per tile (one row per corner) + TaxiMap.hasMany(TaxiMapTileHouse, { foreignKey: 'map_id', as: 'tileHouses' }); + TaxiMapTileHouse.belongsTo(TaxiMap, { foreignKey: 'map_id', as: 'map' }); } diff --git a/backend/models/index.js b/backend/models/index.js index 3db1b1f..bf3710b 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -96,7 +96,7 @@ import Match3UserProgress from './match3/userProgress.js'; import Match3UserLevelProgress from './match3/userLevelProgress.js'; // — Taxi Minigame — -import { TaxiGameState, TaxiLevelStats, TaxiMapType, TaxiMap, TaxiMapTile, TaxiStreetName, TaxiMapTileStreet } from './taxi/index.js'; +import { TaxiGameState, TaxiLevelStats, TaxiMapType, TaxiMap, TaxiMapTile, TaxiStreetName, TaxiMapTileStreet, TaxiMapTileHouse } from './taxi/index.js'; // — Politische Ämter (Politics) — import PoliticalOfficeType from './falukant/type/political_office_type.js'; @@ -242,6 +242,7 @@ const models = { TaxiMapTile, TaxiStreetName, TaxiMapTileStreet, + TaxiMapTileHouse, }; export default models; diff --git a/backend/models/taxi/index.js b/backend/models/taxi/index.js index a846b52..5fddefb 100644 --- a/backend/models/taxi/index.js +++ b/backend/models/taxi/index.js @@ -5,5 +5,6 @@ import TaxiMap from './taxiMap.js'; import TaxiMapTile from './taxiMapTile.js'; import TaxiStreetName from './taxiStreetName.js'; import TaxiMapTileStreet from './taxiMapTileStreet.js'; +import TaxiMapTileHouse from './taxiMapTileHouse.js'; -export { TaxiGameState, TaxiLevelStats, TaxiMapType, TaxiMap, TaxiMapTile, TaxiStreetName, TaxiMapTileStreet }; +export { TaxiGameState, TaxiLevelStats, TaxiMapType, TaxiMap, TaxiMapTile, TaxiStreetName, TaxiMapTileStreet, TaxiMapTileHouse }; diff --git a/backend/models/taxi/taxiMapTileHouse.js b/backend/models/taxi/taxiMapTileHouse.js new file mode 100644 index 0000000..12adb2a --- /dev/null +++ b/backend/models/taxi/taxiMapTileHouse.js @@ -0,0 +1,56 @@ +import { DataTypes } from 'sequelize'; +import { sequelize } from '../../utils/sequelize.js'; + +const TaxiMapTileHouse = sequelize.define('TaxiMapTileHouse', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + mapId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + x: { + type: DataTypes.INTEGER, + allowNull: false, + comment: 'X-coordinate of the tile within the map' + }, + y: { + type: DataTypes.INTEGER, + allowNull: false, + comment: 'Y-coordinate of the tile within the map' + }, + corner: { + type: DataTypes.ENUM('lo', 'ro', 'lu', 'ru'), + allowNull: false, + comment: 'Corner position: lo, ro, lu, ru' + }, + rotation: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + comment: 'Rotation in degrees: 0, 90, 180, 270' + } +}, { + tableName: 'taxi_map_tile_house', + schema: 'taxi', + timestamps: true, + underscored: true, + indexes: [ + { + unique: true, + fields: ['map_id', 'x', 'y', 'corner'] + }, + { + fields: ['map_id'] + }, + { + fields: ['x', 'y'] + } + ] +}); + +export default TaxiMapTileHouse; + + diff --git a/backend/services/taxiMapService.js b/backend/services/taxiMapService.js index 32851e1..dc38bb7 100644 --- a/backend/services/taxiMapService.js +++ b/backend/services/taxiMapService.js @@ -4,6 +4,7 @@ import TaxiMapType from '../models/taxi/taxiMapType.js'; import TaxiMapTile from '../models/taxi/taxiMapTile.js'; import TaxiStreetName from '../models/taxi/taxiStreetName.js'; import TaxiMapTileStreet from '../models/taxi/taxiMapTileStreet.js'; +import TaxiMapTileHouse from '../models/taxi/taxiMapTileHouse.js'; class TaxiMapService extends BaseService { constructor() { @@ -43,7 +44,8 @@ class TaxiMapService extends BaseService { { model: TaxiStreetName, as: 'streetNameH' }, { model: TaxiStreetName, as: 'streetNameV' } ] - } + }, + { model: TaxiMapTileHouse, as: 'tileHouses' } ], order: [['name', 'ASC']] }); @@ -74,7 +76,8 @@ class TaxiMapService extends BaseService { { model: TaxiStreetName, as: 'streetNameH' }, { model: TaxiStreetName, as: 'streetNameV' } ] - } + }, + { model: TaxiMapTileHouse, as: 'tileHouses' } ] }); return map; @@ -112,7 +115,7 @@ class TaxiMapService extends BaseService { */ async createMap(mapData) { try { - const { tiles, tileStreetNames, ...mapFields } = mapData; + const { tiles, tileStreetNames, tileHouses, ...mapFields } = mapData; // mapData JSON ist entfernt – map erstellen ohne mapData const map = await TaxiMap.create(mapFields); // Tiles upsert (optional) @@ -123,6 +126,9 @@ class TaxiMapService extends BaseService { if (tileStreetNames && Object.keys(tileStreetNames).length > 0) { await this.upsertTileStreetNames(map.id, tileStreetNames); } + if (tileHouses && Object.keys(tileHouses).length > 0) { + await this.upsertTileHouses(map.id, tileHouses); + } return await this.getMapById(map.id); } catch (error) { console.error('Error creating map:', error); @@ -135,7 +141,7 @@ class TaxiMapService extends BaseService { */ async updateMap(mapId, updateData) { try { - const { tiles, tileStreetNames, ...mapFields } = updateData; + const { tiles, tileStreetNames, tileHouses, ...mapFields } = updateData; const [updatedRowsCount] = await TaxiMap.update(mapFields, { where: { id: mapId } }); @@ -151,6 +157,9 @@ class TaxiMapService extends BaseService { if (tileStreetNames && Object.keys(tileStreetNames).length > 0) { await this.upsertTileStreetNames(mapId, tileStreetNames); } + if (tileHouses && Object.keys(tileHouses).length > 0) { + await this.upsertTileHouses(mapId, tileHouses); + } return await this.getMapById(mapId); } catch (error) { console.error('Error updating map:', error); @@ -205,6 +214,24 @@ class TaxiMapService extends BaseService { return { success: true }; } + /** + * Speichert Häuser pro Tile (je Ecke eine Zeile) + * @param {number} mapId + * @param {{[cellKey:string]: {[corner:string]: number}}} tileHouses + */ + async upsertTileHouses(mapId, tileHouses) { + // Löschen und neu anlegen pro Zelle ist am einfachsten und robust + const entries = Object.entries(tileHouses || {}); + for (const [cellKey, cornerMap] of entries) { + const [x, y] = cellKey.split(',').map(Number); + await TaxiMapTileHouse.destroy({ where: { map_id: mapId, x, y } }); + for (const [corner, rotation] of Object.entries(cornerMap || {})) { + await TaxiMapTileHouse.create({ mapId, x, y, corner, rotation: Number(rotation) || 0 }); + } + } + return { success: true }; + } + /** * Löscht eine Map (soft delete) */ diff --git a/backend/utils/initializeTaxi.js b/backend/utils/initializeTaxi.js index 446e4d2..778aaee 100644 --- a/backend/utils/initializeTaxi.js +++ b/backend/utils/initializeTaxi.js @@ -17,6 +17,14 @@ const initializeTaxi = async () => { console.warn('⚠️ Konnte taxi_map_tile nicht synchronisieren:', e?.message || e); } + // Stelle sicher, dass die neue Tabelle taxi_map_tile_house existiert (Häuser je Ecke) + try { + await (await import('../models/taxi/taxiMapTileHouse.js')).default.sync({ alter: true, force: false }); + console.log('✅ Tabelle taxi.taxi_map_tile_house ist synchronisiert'); + } catch (e) { + console.warn('⚠️ Konnte taxi_map_tile_house nicht synchronisieren:', e?.message || e); + } + // Stelle sicher: timestamps-Spalten in taxi_street_name vorhanden (ältere DBs hatten evtl. kein updated_at) try { await sequelize.query(` diff --git a/frontend/src/views/admin/TaxiToolsView.vue b/frontend/src/views/admin/TaxiToolsView.vue index 2c0d06c..30e642f 100644 --- a/frontend/src/views/admin/TaxiToolsView.vue +++ b/frontend/src/views/admin/TaxiToolsView.vue @@ -151,8 +151,18 @@

{{ $t('admin.taxiTools.mapEditor.extraElements') }}

-
Haus
@@ -375,7 +385,9 @@ const HOUSE_TYPE_MATRIX = { fuelvertical: { lo: ['right'], ro: [], lu: ['right'], ru: [] } }; -const DIRECTION_TO_ROTATION = { bottom: 0, right: 90, top: 180, left: 270 }; +// Canvas-Rotation ist im Browser positiv im Uhrzeigersinn (y-Achse nach unten). +// Bild zeigt Tür unten (0°). Für "rechts" benötigen wir -90° (= 270°), für "links" +90°. +const DIRECTION_TO_ROTATION = { bottom: 0, right: 270, top: 180, left: 90 }; export default { name: 'AdminTaxiToolsView', @@ -1033,9 +1045,9 @@ export default { doorClass(deg) { switch (deg) { case 0: return 'door-bottom'; - case 90: return 'door-right'; + case 90: return 'door-left'; case 180: return 'door-top'; - case 270: return 'door-left'; + case 270: return 'door-right'; default: return 'door-bottom'; } }, @@ -1146,6 +1158,7 @@ export default { meta: c.extraHouses ? { houses: { ...c.extraHouses } } : null })); const tileStreetNames = this.collectTileStreetNames(); + const tileHouses = this.collectTileHouses(); const mapData = { ...this.mapForm, width: this.boardWidth, @@ -1153,7 +1166,8 @@ export default { tileSize: 50, mapTypeId: 1, tiles, - tileStreetNames + tileStreetNames, + tileHouses }; let savedMap; @@ -1196,6 +1210,17 @@ export default { return result; }, + collectTileHouses() { + const result = {}; + for (const [key, cell] of Object.entries(this.boardCells)) { + if (!cell.tileType) continue; + if (cell.extraHouses && Object.keys(cell.extraHouses).length > 0) { + result[key] = { ...cell.extraHouses }; + } + } + return result; + }, + async deleteMap(mapId) { if (confirm('Möchtest du diese Map wirklich löschen?')) { try { diff --git a/frontend/src/views/minigames/TaxiGame.vue b/frontend/src/views/minigames/TaxiGame.vue index 4867af4..986e273 100644 --- a/frontend/src/views/minigames/TaxiGame.vue +++ b/frontend/src/views/minigames/TaxiGame.vue @@ -235,6 +235,7 @@ export default { size: 500, // 400x400px pro Tile images: {} }, + houseImage: null, maps: [], // Geladene Maps aus der Datenbank currentMap: null, // Aktuell verwendete Map selectedMapId: null, // ID der ausgewählten Map @@ -246,6 +247,7 @@ export default { audioContext: null, audioUnlockHandler: null, selectedStreetName: null + ,houseNumbers: {} } }, computed: { @@ -285,6 +287,7 @@ export default { this.initializeMinimap(); this.loadTiles(); this.loadTaxiImage(); + this.loadHouseImage(); this.loadMaps(); this.setupEventListeners(); await this.initializeMotorSound(); @@ -1116,9 +1119,11 @@ export default { if (this.tiles.images[tileType]) { this.ctx.drawImage(this.tiles.images[tileType], 0, 0, tileSize, tileSize); } - // Straßennamen auf Haupt-Canvas zeichnen + // Häuser zeichnen (aus tileHouses) const absCol = this.currentTile.col + (this.currentMap.offsetX || 0); const absRow = this.currentTile.row + (this.currentMap.offsetY || 0); + this.drawHousesOnMainCanvas(this.ctx, tileSize, absCol, absRow); + // Straßennamen auf Haupt-Canvas zeichnen this.drawStreetNamesOnMainCanvas(this.ctx, tileSize, tileType, absCol, absRow); } } @@ -1176,6 +1181,55 @@ export default { drawText(hName, size - fontPx, cy, -Math.PI / 2, sel); } }, + drawHousesOnMainCanvas(ctx, size, col, row) { + if (!this.currentMap) return; + const houses = Array.isArray(this.currentMap.tileHouses) ? this.currentMap.tileHouses : []; + if (!houses.length || !this.houseImage) return; + // Finde alle Häuser für diese Zelle + const list = houses.filter(h => h.x === col && h.y === row); + if (!list.length) return; + // Hausgröße 150x150px; an Ecken platzieren mit Abstand 3px + const HOUSE_W = 150; + const HOUSE_H = 150; + const pad = 30; + for (const h of list) { + const rot = (Number(h.rotation) || 0) % 360; + const isPortrait = (rot % 180) !== 0; // 90 oder 270 Grad + const w = isPortrait ? HOUSE_H : HOUSE_W; + const hgt = isPortrait ? HOUSE_W : HOUSE_H; + + let px = 0, py = 0; + switch (h.corner) { + case 'lo': px = pad; py = pad; break; + case 'ro': px = size - w - pad; py = pad; break; + case 'lu': px = pad; py = size - hgt - pad; break; + case 'ru': px = size - w - pad; py = size - hgt - pad; break; + default: continue; + } + // Rotation: Bild zeigt Tür unten (0°). 90/180/270 drehen um Zentrum. + const cx = px + w / 2; + const cy = py + hgt / 2; + ctx.save(); + ctx.translate(cx, cy); + const rad = rot * Math.PI / 180; + ctx.rotate(rad); + ctx.drawImage(this.houseImage, -HOUSE_W/2, -HOUSE_H/2, HOUSE_W, HOUSE_H); + // Hausnummer zeichnen (über dem Bild zentriert) + const key = `${h.x},${h.y},${h.corner}`; + const num = this.houseNumbers[key]; + if (num != null) { + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.font = 'bold 20px sans-serif'; + ctx.lineWidth = 4; + ctx.strokeStyle = 'rgba(255,255,255,0.9)'; + ctx.strokeText(String(num), 0, 0); + ctx.fillStyle = '#000'; + ctx.fillText(String(num), 0, 0); + } + ctx.restore(); + } + }, getTileType(row, col, rows, cols) { // Ecken @@ -1336,6 +1390,16 @@ export default { img.src = '/images/taxi/taxi.svg'; // console.log('Lade Taxi-Bild von:', '/images/taxi/taxi.svg'); }, + loadHouseImage() { + const img = new Image(); + img.onload = () => { + this.houseImage = img; + }; + img.onerror = () => { + console.warn('Fehler beim Laden von house_small.png'); + }; + img.src = '/images/taxi/house_small.png'; + }, async loadMaps() { try { @@ -1352,6 +1416,7 @@ export default { // Minimap neu zeichnen nach dem Laden der Map this.$nextTick(() => { + this.computeHouseNumbers(); this.drawMinimap(); }); } @@ -1382,6 +1447,7 @@ export default { // Minimap neu zeichnen this.$nextTick(() => { + this.computeHouseNumbers(); this.drawMinimap(); }); } @@ -1542,6 +1608,8 @@ export default { map.mapData = grid; map.offsetX = minX; map.offsetY = minY; + // tileHouses vom Backend übernehmen (Array von Zeilen) + map.tileHouses = Array.isArray(map.tileHouses) ? map.tileHouses : []; return map; }, onSelectStreet(name) { @@ -1595,6 +1663,92 @@ export default { } ctx.restore(); + }, + // ---- Hausnummern-Berechnung ---- + computeHouseNumbers() { + this.houseNumbers = {}; + if (!this.currentMap || !this.currentMap.tileHouses || !this.currentMap.mapData) return; + const map = this.currentMap; + const offsetX = map.offsetX || 0; + const offsetY = map.offsetY || 0; + const streetIndex = new Map(); + const getEntry = (name) => { if (!streetIndex.has(name)) streetIndex.set(name, { tiles: new Map() }); return streetIndex.get(name); }; + const tileStreets = Array.isArray(map.tileStreets) ? map.tileStreets : []; + for (const ts of tileStreets) { + const x = ts.x; const y = ts.y; const gridX = x - offsetX; const gridY = y - offsetY; + const tileType = (map.mapData[gridY] && map.mapData[gridY][gridX]) || null; if (!tileType) continue; + const makeTileObj = (name) => { const key = `${x},${y}`; const e = getEntry(name); const t = e.tiles.get(key) || { x, y, tileType, h: false, v: false }; e.tiles.set(key, t); return t; }; + if (ts.streetNameH && ts.streetNameH.name) { makeTileObj(ts.streetNameH.name).h = true; } + if (ts.streetNameV && ts.streetNameV.name) { makeTileObj(ts.streetNameV.name).v = true; } + } + const allowedDirections = (tileType) => { + switch (tileType) { + case 'cornertopleft': return { left: true, up: true, right: false, down: false }; + case 'cornertopright': return { right: true, up: true, left: false, down: false }; + case 'cornerbottomleft': return { left: true, down: true, right: false, up: false }; + case 'cornerbottomright': return { right: true, down: true, left: false, up: false }; + case 'horizontal': + case 'fuelhorizontal': return { left: true, right: true, up: false, down: false }; + case 'vertical': + case 'fuelvertical': return { up: true, down: true, left: false, right: false }; + case 'cross': return { left: true, right: true, up: true, down: true }; + case 'tup': return { up: true, left: true, right: true, down: false }; + case 'tdown': return { down: true, left: true, right: true, up: false }; + case 'tleft': return { left: true, up: true, down: true, right: false }; + case 'tright': return { right: true, up: true, down: true, left: false }; + default: return { left: false, right: false, up: false, down: false }; + } + }; + const getTile = (entry, x, y) => entry.tiles.get(`${x},${y}`); + for (const [name, entry] of streetIndex.entries()) { + const nodes = new Map(); + const addNode = (x, y, axis) => nodes.set(`${x},${y},${axis}`, { x, y, axis }); + for (const t of entry.tiles.values()) { if (t.h) addNode(t.x, t.y, 'h'); if (t.v) addNode(t.x, t.y, 'v'); } + const adj = new Map(); + const addEdge = (a, b) => { if (!nodes.has(a) || !nodes.has(b)) return; if (!adj.has(a)) adj.set(a, new Set()); if (!adj.has(b)) adj.set(b, new Set()); adj.get(a).add(b); adj.get(b).add(a); }; + for (const t of entry.tiles.values()) { + const dirs = allowedDirections(t.tileType); const keyH = `${t.x},${t.y},h`; const keyV = `${t.x},${t.y},v`; + if (t.h) { if (dirs.left) { const n = getTile(entry, t.x - 1, t.y); if (n && n.h && allowedDirections(n.tileType).right) addEdge(keyH, `${n.x},${n.y},h`); } if (dirs.right) { const n = getTile(entry, t.x + 1, t.y); if (n && n.h && allowedDirections(n.tileType).left) addEdge(keyH, `${n.x},${n.y},h`); } } + if (t.v) { if (dirs.up) { const n = getTile(entry, t.x, t.y - 1); if (n && n.v && allowedDirections(n.tileType).down) addEdge(keyV, `${n.x},${n.y},v`); } if (dirs.down) { const n = getTile(entry, t.x, t.y + 1); if (n && n.v && allowedDirections(n.tileType).up) addEdge(keyV, `${n.x},${n.y},v`); } } + 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]; + 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); + const { x, y, axis } = nodes.get(curr); + const neighbors = Array.from(adj.get(curr) || []); const next = neighbors.find(n => !visited.has(n)); + let dir = { dx: 0, dy: 0 }; if (next) { const nNode = nodes.get(next); dir = { dx: nNode.x - x, dy: nNode.y - y }; } else if (prev) { const pNode = nodes.get(prev); dir = { dx: x - pNode.x, dy: y - pNode.y }; } + if (prev) { const prevAxis = nodes.get(prev).axis; if (prevAxis !== axis) { if (prevAxis === 'v' && axis === 'h') currOddSide = 'bottom'; else if (prevAxis === 'h' && axis === 'v') currOddSide = 'left'; } } + const houseMap = this.currentMap.tileHouses || []; const housesHere = houseMap.filter(hh => hh.x === x && hh.y === y); + if (housesHere.length) { + const doorSideFromRotation = (rot) => { + const r = ((Number(rot) || 0) % 360 + 360) % 360; + if (r === 0) return 'bottom'; + if (r === 90) return 'left'; + if (r === 180) return 'top'; + if (r === 270) return 'right'; + return 'bottom'; + }; + // Nur Häuser berücksichtigen, deren Tür zur aktuellen Straßenachse passt + const housesFiltered = housesHere.filter(hh => { + const side = doorSideFromRotation(hh.rotation); + return axis === 'h' ? (side === 'top' || side === 'bottom') : (side === 'left' || side === 'right'); + }); + 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)); } + }; + 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']); + for (const c of orderCorners(oddCorners)) { if (housesFiltered.find(hh => hh.corner === c)) { const k = `${x},${y},${c}`; if (this.houseNumbers[k] == null) { this.houseNumbers[k] = odd; odd += 2; } } } + for (const c of orderCorners(evenCorners)) { if (housesFiltered.find(hh => hh.corner === c)) { const k = `${x},${y},${c}`; if (this.houseNumbers[k] == null) { this.houseNumbers[k] = even; even += 2; } } } + } + prev = curr; curr = next || null; + } + } } } }