Änderung: Hinzufügung der Haus-Logik zur Taxi-Map

Ä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.
This commit is contained in:
Torsten Schulz (local)
2025-09-18 14:27:14 +02:00
parent ab8e12cbcd
commit 7207274ab5
8 changed files with 290 additions and 13 deletions

View File

@@ -107,6 +107,7 @@ import TaxiLevelStats from './taxi/taxiLevelStats.js';
import TaxiMapType from './taxi/taxiMapType.js'; import TaxiMapType from './taxi/taxiMapType.js';
import TaxiMap from './taxi/taxiMap.js'; import TaxiMap from './taxi/taxiMap.js';
import TaxiMapTile from './taxi/taxiMapTile.js'; import TaxiMapTile from './taxi/taxiMapTile.js';
import TaxiMapTileHouse from './taxi/taxiMapTileHouse.js';
import TaxiStreetName from './taxi/taxiStreetName.js'; import TaxiStreetName from './taxi/taxiStreetName.js';
import TaxiMapTileStreet from './taxi/taxiMapTileStreet.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_h_id', as: 'streetNameH' });
TaxiMapTileStreet.belongsTo(TaxiStreetName, { foreignKey: 'street_name_v_id', as: 'streetNameV' }); 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' });
} }

View File

@@ -96,7 +96,7 @@ import Match3UserProgress from './match3/userProgress.js';
import Match3UserLevelProgress from './match3/userLevelProgress.js'; import Match3UserLevelProgress from './match3/userLevelProgress.js';
// — Taxi Minigame — // — 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) — // — Politische Ämter (Politics) —
import PoliticalOfficeType from './falukant/type/political_office_type.js'; import PoliticalOfficeType from './falukant/type/political_office_type.js';
@@ -242,6 +242,7 @@ const models = {
TaxiMapTile, TaxiMapTile,
TaxiStreetName, TaxiStreetName,
TaxiMapTileStreet, TaxiMapTileStreet,
TaxiMapTileHouse,
}; };
export default models; export default models;

View File

@@ -5,5 +5,6 @@ import TaxiMap from './taxiMap.js';
import TaxiMapTile from './taxiMapTile.js'; import TaxiMapTile from './taxiMapTile.js';
import TaxiStreetName from './taxiStreetName.js'; import TaxiStreetName from './taxiStreetName.js';
import TaxiMapTileStreet from './taxiMapTileStreet.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 };

View File

@@ -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;

View File

@@ -4,6 +4,7 @@ import TaxiMapType from '../models/taxi/taxiMapType.js';
import TaxiMapTile from '../models/taxi/taxiMapTile.js'; import TaxiMapTile from '../models/taxi/taxiMapTile.js';
import TaxiStreetName from '../models/taxi/taxiStreetName.js'; import TaxiStreetName from '../models/taxi/taxiStreetName.js';
import TaxiMapTileStreet from '../models/taxi/taxiMapTileStreet.js'; import TaxiMapTileStreet from '../models/taxi/taxiMapTileStreet.js';
import TaxiMapTileHouse from '../models/taxi/taxiMapTileHouse.js';
class TaxiMapService extends BaseService { class TaxiMapService extends BaseService {
constructor() { constructor() {
@@ -43,7 +44,8 @@ class TaxiMapService extends BaseService {
{ model: TaxiStreetName, as: 'streetNameH' }, { model: TaxiStreetName, as: 'streetNameH' },
{ model: TaxiStreetName, as: 'streetNameV' } { model: TaxiStreetName, as: 'streetNameV' }
] ]
} },
{ model: TaxiMapTileHouse, as: 'tileHouses' }
], ],
order: [['name', 'ASC']] order: [['name', 'ASC']]
}); });
@@ -74,7 +76,8 @@ class TaxiMapService extends BaseService {
{ model: TaxiStreetName, as: 'streetNameH' }, { model: TaxiStreetName, as: 'streetNameH' },
{ model: TaxiStreetName, as: 'streetNameV' } { model: TaxiStreetName, as: 'streetNameV' }
] ]
} },
{ model: TaxiMapTileHouse, as: 'tileHouses' }
] ]
}); });
return map; return map;
@@ -112,7 +115,7 @@ class TaxiMapService extends BaseService {
*/ */
async createMap(mapData) { async createMap(mapData) {
try { try {
const { tiles, tileStreetNames, ...mapFields } = mapData; const { tiles, tileStreetNames, tileHouses, ...mapFields } = mapData;
// mapData JSON ist entfernt map erstellen ohne mapData // mapData JSON ist entfernt map erstellen ohne mapData
const map = await TaxiMap.create(mapFields); const map = await TaxiMap.create(mapFields);
// Tiles upsert (optional) // Tiles upsert (optional)
@@ -123,6 +126,9 @@ class TaxiMapService extends BaseService {
if (tileStreetNames && Object.keys(tileStreetNames).length > 0) { if (tileStreetNames && Object.keys(tileStreetNames).length > 0) {
await this.upsertTileStreetNames(map.id, tileStreetNames); 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); return await this.getMapById(map.id);
} catch (error) { } catch (error) {
console.error('Error creating map:', error); console.error('Error creating map:', error);
@@ -135,7 +141,7 @@ class TaxiMapService extends BaseService {
*/ */
async updateMap(mapId, updateData) { async updateMap(mapId, updateData) {
try { try {
const { tiles, tileStreetNames, ...mapFields } = updateData; const { tiles, tileStreetNames, tileHouses, ...mapFields } = updateData;
const [updatedRowsCount] = await TaxiMap.update(mapFields, { const [updatedRowsCount] = await TaxiMap.update(mapFields, {
where: { id: mapId } where: { id: mapId }
}); });
@@ -151,6 +157,9 @@ class TaxiMapService extends BaseService {
if (tileStreetNames && Object.keys(tileStreetNames).length > 0) { if (tileStreetNames && Object.keys(tileStreetNames).length > 0) {
await this.upsertTileStreetNames(mapId, tileStreetNames); await this.upsertTileStreetNames(mapId, tileStreetNames);
} }
if (tileHouses && Object.keys(tileHouses).length > 0) {
await this.upsertTileHouses(mapId, tileHouses);
}
return await this.getMapById(mapId); return await this.getMapById(mapId);
} catch (error) { } catch (error) {
console.error('Error updating map:', error); console.error('Error updating map:', error);
@@ -205,6 +214,24 @@ class TaxiMapService extends BaseService {
return { success: true }; 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) * Löscht eine Map (soft delete)
*/ */

View File

@@ -17,6 +17,14 @@ const initializeTaxi = async () => {
console.warn('⚠️ Konnte taxi_map_tile nicht synchronisieren:', e?.message || e); 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) // Stelle sicher: timestamps-Spalten in taxi_street_name vorhanden (ältere DBs hatten evtl. kein updated_at)
try { try {
await sequelize.query(` await sequelize.query(`

View File

@@ -151,8 +151,18 @@
<h4>{{ $t('admin.taxiTools.mapEditor.extraElements') }}</h4> <h4>{{ $t('admin.taxiTools.mapEditor.extraElements') }}</h4>
<div class="panel-body"> <div class="panel-body">
<div v-if="allowedHouseCorners.length" class="corner-chooser"> <div v-if="allowedHouseCorners.length" class="corner-chooser">
<button v-for="pos in allowedHouseCorners" :key="pos" class="corner-btn" @click="confirmHouseCorner(pos)"> <button
{{ pos.toUpperCase() }} v-for="pos in allowedHouseCorners"
:key="pos"
class="corner-btn"
:title="pos.toUpperCase()"
@click="confirmHouseCorner(pos)">
<div class="corner-preview">
<span class="q" :class="{ active: pos === 'lo' }"></span>
<span class="q" :class="{ active: pos === 'ro' }"></span>
<span class="q" :class="{ active: pos === 'lu' }"></span>
<span class="q" :class="{ active: pos === 'ru' }"></span>
</div>
</button> </button>
</div> </div>
<h5 v-if="pendingCorner" class="extra-subtitle">Haus</h5> <h5 v-if="pendingCorner" class="extra-subtitle">Haus</h5>
@@ -375,7 +385,9 @@ const HOUSE_TYPE_MATRIX = {
fuelvertical: { lo: ['right'], ro: [], lu: ['right'], ru: [] } 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 { export default {
name: 'AdminTaxiToolsView', name: 'AdminTaxiToolsView',
@@ -1033,9 +1045,9 @@ export default {
doorClass(deg) { doorClass(deg) {
switch (deg) { switch (deg) {
case 0: return 'door-bottom'; case 0: return 'door-bottom';
case 90: return 'door-right'; case 90: return 'door-left';
case 180: return 'door-top'; case 180: return 'door-top';
case 270: return 'door-left'; case 270: return 'door-right';
default: return 'door-bottom'; default: return 'door-bottom';
} }
}, },
@@ -1146,6 +1158,7 @@ export default {
meta: c.extraHouses ? { houses: { ...c.extraHouses } } : null meta: c.extraHouses ? { houses: { ...c.extraHouses } } : null
})); }));
const tileStreetNames = this.collectTileStreetNames(); const tileStreetNames = this.collectTileStreetNames();
const tileHouses = this.collectTileHouses();
const mapData = { const mapData = {
...this.mapForm, ...this.mapForm,
width: this.boardWidth, width: this.boardWidth,
@@ -1153,7 +1166,8 @@ export default {
tileSize: 50, tileSize: 50,
mapTypeId: 1, mapTypeId: 1,
tiles, tiles,
tileStreetNames tileStreetNames,
tileHouses
}; };
let savedMap; let savedMap;
@@ -1196,6 +1210,17 @@ export default {
return result; 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) { async deleteMap(mapId) {
if (confirm('Möchtest du diese Map wirklich löschen?')) { if (confirm('Möchtest du diese Map wirklich löschen?')) {
try { try {

View File

@@ -235,6 +235,7 @@ export default {
size: 500, // 400x400px pro Tile size: 500, // 400x400px pro Tile
images: {} images: {}
}, },
houseImage: null,
maps: [], // Geladene Maps aus der Datenbank maps: [], // Geladene Maps aus der Datenbank
currentMap: null, // Aktuell verwendete Map currentMap: null, // Aktuell verwendete Map
selectedMapId: null, // ID der ausgewählten Map selectedMapId: null, // ID der ausgewählten Map
@@ -246,6 +247,7 @@ export default {
audioContext: null, audioContext: null,
audioUnlockHandler: null, audioUnlockHandler: null,
selectedStreetName: null selectedStreetName: null
,houseNumbers: {}
} }
}, },
computed: { computed: {
@@ -285,6 +287,7 @@ export default {
this.initializeMinimap(); this.initializeMinimap();
this.loadTiles(); this.loadTiles();
this.loadTaxiImage(); this.loadTaxiImage();
this.loadHouseImage();
this.loadMaps(); this.loadMaps();
this.setupEventListeners(); this.setupEventListeners();
await this.initializeMotorSound(); await this.initializeMotorSound();
@@ -1116,9 +1119,11 @@ export default {
if (this.tiles.images[tileType]) { if (this.tiles.images[tileType]) {
this.ctx.drawImage(this.tiles.images[tileType], 0, 0, tileSize, tileSize); 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 absCol = this.currentTile.col + (this.currentMap.offsetX || 0);
const absRow = this.currentTile.row + (this.currentMap.offsetY || 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); this.drawStreetNamesOnMainCanvas(this.ctx, tileSize, tileType, absCol, absRow);
} }
} }
@@ -1176,6 +1181,55 @@ export default {
drawText(hName, size - fontPx, cy, -Math.PI / 2, sel); 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) { getTileType(row, col, rows, cols) {
// Ecken // Ecken
@@ -1336,6 +1390,16 @@ export default {
img.src = '/images/taxi/taxi.svg'; img.src = '/images/taxi/taxi.svg';
// console.log('Lade Taxi-Bild von:', '/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() { async loadMaps() {
try { try {
@@ -1352,6 +1416,7 @@ export default {
// Minimap neu zeichnen nach dem Laden der Map // Minimap neu zeichnen nach dem Laden der Map
this.$nextTick(() => { this.$nextTick(() => {
this.computeHouseNumbers();
this.drawMinimap(); this.drawMinimap();
}); });
} }
@@ -1382,6 +1447,7 @@ export default {
// Minimap neu zeichnen // Minimap neu zeichnen
this.$nextTick(() => { this.$nextTick(() => {
this.computeHouseNumbers();
this.drawMinimap(); this.drawMinimap();
}); });
} }
@@ -1542,6 +1608,8 @@ export default {
map.mapData = grid; map.mapData = grid;
map.offsetX = minX; map.offsetX = minX;
map.offsetY = minY; map.offsetY = minY;
// tileHouses vom Backend übernehmen (Array von Zeilen)
map.tileHouses = Array.isArray(map.tileHouses) ? map.tileHouses : [];
return map; return map;
}, },
onSelectStreet(name) { onSelectStreet(name) {
@@ -1595,6 +1663,92 @@ export default {
} }
ctx.restore(); 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;
}
}
} }
} }
} }