Compare commits
3 Commits
master
...
advanced_m
| Author | SHA1 | Date | |
|---|---|---|---|
| 8502f70bda | |||
| 75cf63f906 | |||
| 5804db396f |
@@ -128,3 +128,9 @@ Este documento sirve para llevar un control diario del desarrollo, decisiones t
|
|||||||
- Definido el **Manifiesto Técnico (v2.0)**: Visión de un "Puente Híbrido" entre juego de mesa físico y motor narrativo digital (LLM).
|
- Definido el **Manifiesto Técnico (v2.0)**: Visión de un "Puente Híbrido" entre juego de mesa físico y motor narrativo digital (LLM).
|
||||||
- **Generación Procedural:** Algoritmo de mazmorras basado en tiles de 4x4 con expansión orgánica.
|
- **Generación Procedural:** Algoritmo de mazmorras basado en tiles de 4x4 con expansión orgánica.
|
||||||
- **Motor Gráfico:** Three.js con cámara isométrica ortográfica y controles restringidos (N, S, E, W).
|
- **Motor Gráfico:** Three.js con cámara isométrica ortográfica y controles restringidos (N, S, E, W).
|
||||||
|
|
||||||
|
## [2025-12-29] Dungeon Generation Fixes
|
||||||
|
- **Alignment Fix:** Implemented explicit `doorCoordinates` for all asymmetric tiles (L, T) and corridors. This replaces the generic center-based logic and ensures precise "bit-to-bit" alignment of connections, solving visual drift and 1-cell gaps.
|
||||||
|
- **Deck Randomness Fix:** Removed a legacy debug fixed deck that was limiting the game to 7 cards.
|
||||||
|
- **Infinite Generation:** Improved `drawCompatibleCard` to automatically reshuffle the discard pile back into the deck if the current draw pile is exhausted during a search. This prevents "Could not find compatible tile" errors.
|
||||||
|
- **Balance Update:** Rebalanced the deck composition to increase room frequency (~50% rooms, ~50% paths).
|
||||||
|
|||||||
@@ -17,57 +17,28 @@ export class DungeonDeck {
|
|||||||
shuffleDeck() {
|
shuffleDeck() {
|
||||||
// Create a deck with abstract tile types
|
// Create a deck with abstract tile types
|
||||||
this.deck = [
|
this.deck = [
|
||||||
// 8 rooms 4x4 (abstract)
|
// 15 rooms 4x4 (abstract)
|
||||||
{ type: 'room_4x4' },
|
{ type: 'room_4x4' }, { type: 'room_4x4' }, { type: 'room_4x4' }, { type: 'room_4x4' },
|
||||||
{ type: 'room_4x4' },
|
{ type: 'room_4x4' }, { type: 'room_4x4' }, { type: 'room_4x4' }, { type: 'room_4x4' },
|
||||||
{ type: 'room_4x4' },
|
{ type: 'room_4x4' }, { type: 'room_4x4' }, { type: 'room_4x4' }, { type: 'room_4x4' },
|
||||||
{ type: 'room_4x4' },
|
{ type: 'room_4x4' }, { type: 'room_4x4' }, { type: 'room_4x4' },
|
||||||
{ type: 'room_4x4' },
|
|
||||||
{ type: 'room_4x4' },
|
|
||||||
{ type: 'room_4x4' },
|
|
||||||
{ type: 'room_4x4' },
|
|
||||||
|
|
||||||
// 4 rooms 4x6 (abstract)
|
// 8 rooms 4x6 (abstract)
|
||||||
{ type: 'room_4x6' },
|
{ type: 'room_4x6' }, { type: 'room_4x6' }, { type: 'room_4x6' }, { type: 'room_4x6' },
|
||||||
{ type: 'room_4x6' },
|
{ type: 'room_4x6' }, { type: 'room_4x6' }, { type: 'room_4x6' }, { type: 'room_4x6' },
|
||||||
{ type: 'room_4x6' },
|
|
||||||
{ type: 'room_4x6' },
|
|
||||||
|
|
||||||
// 12 corridors (abstract)
|
// 10 corridors (abstract)
|
||||||
{ type: 'corridor' },
|
{ type: 'corridor' }, { type: 'corridor' }, { type: 'corridor' }, { type: 'corridor' },
|
||||||
{ type: 'corridor' },
|
{ type: 'corridor' }, { type: 'corridor' }, { type: 'corridor' }, { type: 'corridor' },
|
||||||
{ type: 'corridor' },
|
{ type: 'corridor' }, { type: 'corridor' },
|
||||||
{ type: 'corridor' },
|
|
||||||
{ type: 'corridor' },
|
|
||||||
{ type: 'corridor' },
|
|
||||||
{ type: 'corridor' },
|
|
||||||
{ type: 'corridor' },
|
|
||||||
{ type: 'corridor' },
|
|
||||||
{ type: 'corridor' },
|
|
||||||
{ type: 'corridor' },
|
|
||||||
{ type: 'corridor' },
|
|
||||||
|
|
||||||
// 10 L-shapes (abstract) - increased from 6
|
// 8 L-shapes (abstract)
|
||||||
{ type: 'L' },
|
{ type: 'L' }, { type: 'L' }, { type: 'L' }, { type: 'L' },
|
||||||
{ type: 'L' },
|
{ type: 'L' }, { type: 'L' }, { type: 'L' }, { type: 'L' },
|
||||||
{ type: 'L' },
|
|
||||||
{ type: 'L' },
|
|
||||||
{ type: 'L' },
|
|
||||||
{ type: 'L' },
|
|
||||||
{ type: 'L' },
|
|
||||||
{ type: 'L' },
|
|
||||||
{ type: 'L' },
|
|
||||||
{ type: 'L' },
|
|
||||||
|
|
||||||
// 8 T-junctions (abstract) - increased from 4
|
// 6 T-junctions (abstract)
|
||||||
{ type: 'T' },
|
{ type: 'T' }, { type: 'T' }, { type: 'T' }, { type: 'T' },
|
||||||
{ type: 'T' },
|
{ type: 'T' }, { type: 'T' }
|
||||||
{ type: 'T' },
|
|
||||||
{ type: 'T' },
|
|
||||||
{ type: 'T' },
|
|
||||||
{ type: 'T' },
|
|
||||||
{ type: 'T' },
|
|
||||||
{ type: 'T' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Fisher-Yates shuffle
|
// Fisher-Yates shuffle
|
||||||
@@ -75,7 +46,13 @@ export class DungeonDeck {
|
|||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
[this.deck[i], this.deck[j]] = [this.deck[j], this.deck[i]];
|
[this.deck[i], this.deck[j]] = [this.deck[j], this.deck[i]];
|
||||||
}
|
}
|
||||||
console.log("Dungeon Deck Shuffled:", this.deck.length, "cards");
|
|
||||||
|
// DEBUG: Fixed deck sequence for faster testing
|
||||||
|
// Fixed deck removed.
|
||||||
|
this.deck = this.deck.sort(() => Math.random() - 0.5);
|
||||||
|
|
||||||
|
console.log("%c[DungeonDeck] SHUFFLED! Top 5 cards:", "color: orange; font-weight: bold;",
|
||||||
|
this.deck.slice(-5).map(c => c.type).join(', '));
|
||||||
}
|
}
|
||||||
|
|
||||||
drawCard() {
|
drawCard() {
|
||||||
@@ -91,6 +68,7 @@ export class DungeonDeck {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const card = this.deck.pop();
|
const card = this.deck.pop();
|
||||||
|
console.log(`%c[DungeonDeck] Drew: ${card.type}`, "color: cyan");
|
||||||
this.discardPile.push(card);
|
this.discardPile.push(card);
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
@@ -101,24 +79,59 @@ export class DungeonDeck {
|
|||||||
* @param {number} maxAttempts - Maximum number of cards to try
|
* @param {number} maxAttempts - Maximum number of cards to try
|
||||||
* @returns {object|null} - Abstract tile card or null if none found
|
* @returns {object|null} - Abstract tile card or null if none found
|
||||||
*/
|
*/
|
||||||
drawCompatibleCard(originTileType, maxAttempts = 10) {
|
drawCompatibleCard(originTileType, maxAttempts = 50) {
|
||||||
const validTypes = this.getCompatibleTypes(originTileType);
|
const validTypes = this.getCompatibleTypes(originTileType);
|
||||||
|
// console.log(`[DungeonDeck] Searching for ${validTypes.join('|')} (Origin: ${originTileType})`);
|
||||||
|
|
||||||
for (let i = 0; i < maxAttempts && this.deck.length > 0; i++) {
|
// Check cards currently in deck
|
||||||
const card = this.drawCard();
|
let checkedCount = 0;
|
||||||
|
const initialDeckSize = this.deck.length;
|
||||||
|
|
||||||
|
// Loop until we find a match or give up
|
||||||
|
// We use a pragmatic limit to prevent infinite loops (e.g., if even with discards we have 0 valid cards)
|
||||||
|
const safetyLimit = this.deck.length + this.discardPile.length + 20;
|
||||||
|
|
||||||
|
for (let i = 0; i < safetyLimit; i++) {
|
||||||
|
// If we have checked all cards relationships in the current deck, AND we have cards in discard...
|
||||||
|
// Reshuffle discards in to give us a chance.
|
||||||
|
if (checkedCount >= this.deck.length && this.discardPile.length > 0) {
|
||||||
|
console.log(`[DungeonDeck] Cycled current deck (${this.deck.length} cards) without match. Reshuffling ${this.discardPile.length} discards.`);
|
||||||
|
this.deck = [...this.deck, ...this.discardPile];
|
||||||
|
this.discardPile = [];
|
||||||
|
this.shuffleDeck(); // Shuffle everything together
|
||||||
|
checkedCount = 0; // Reset counter since we have a new deck state
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.deck.length === 0) {
|
||||||
|
if (this.discardPile.length > 0) {
|
||||||
|
// Current deck empty, pull from discards
|
||||||
|
this.deck = [...this.discardPile];
|
||||||
|
this.discardPile = [];
|
||||||
|
this.shuffleDeck();
|
||||||
|
checkedCount = 0;
|
||||||
|
} else {
|
||||||
|
console.error("No cards left anywhere!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = this.deck.pop();
|
||||||
|
checkedCount++;
|
||||||
|
|
||||||
// Check if this abstract type is compatible
|
// Check if this abstract type is compatible
|
||||||
if (validTypes.includes(card.type)) {
|
if (validTypes.includes(card.type)) {
|
||||||
|
// Found one!
|
||||||
|
console.log(`[DungeonDeck] MATCH! Accepted ${card.type}`);
|
||||||
|
this.discardPile.push(card);
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put incompatible card back at the bottom of the deck
|
// Incompatible: Put back at bottom (unshift) to try next
|
||||||
this.deck.unshift(card);
|
this.deck.unshift(card);
|
||||||
this.discardPile.pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(`Could not find compatible tile for ${originTileType} after ${maxAttempts} attempts`);
|
console.warn(`Could not find compatible tile for ${originTileType} after ${safetyLimit} attempts. (Deck: ${this.deck.length}, Discard: ${this.discardPile.length})`);
|
||||||
return null;
|
return null; // Return null (game will handle it, maybe stop exploring)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -126,7 +126,11 @@ const L_NE = {
|
|||||||
[1, 1, 1, 1],
|
[1, 1, 1, 1],
|
||||||
[1, 1, 1, 1]
|
[1, 1, 1, 1]
|
||||||
],
|
],
|
||||||
exits: ['N', 'E']
|
exits: ['N', 'E'],
|
||||||
|
doorCoordinates: {
|
||||||
|
'N': { x: 0, y: 0 }, // Anchor: Top-Left of path (col 0,1 -> 0)
|
||||||
|
'E': { x: 3, y: 2 } // Anchor: Top-Left of path (row 2,3 -> 2)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const L_SE = {
|
const L_SE = {
|
||||||
@@ -141,7 +145,11 @@ const L_SE = {
|
|||||||
[1, 1, 0, 0],
|
[1, 1, 0, 0],
|
||||||
[1, 1, 0, 0]
|
[1, 1, 0, 0]
|
||||||
],
|
],
|
||||||
exits: ['S', 'E']
|
exits: ['S', 'E'],
|
||||||
|
doorCoordinates: {
|
||||||
|
'S': { x: 0, y: 3 }, // Anchor: Top-Left (col 0,1 -> 0)
|
||||||
|
'E': { x: 3, y: 0 } // Anchor: Top-Left (row 0,1 -> 0)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const L_WS = {
|
const L_WS = {
|
||||||
@@ -156,7 +164,11 @@ const L_WS = {
|
|||||||
[0, 0, 1, 1],
|
[0, 0, 1, 1],
|
||||||
[0, 0, 1, 1]
|
[0, 0, 1, 1]
|
||||||
],
|
],
|
||||||
exits: ['W', 'S']
|
exits: ['W', 'S'],
|
||||||
|
doorCoordinates: {
|
||||||
|
'W': { x: 0, y: 0 }, // Anchor: Top-Left (row 0,1 -> 0)
|
||||||
|
'S': { x: 2, y: 3 } // Anchor: Top-Left (col 2,3 -> 2)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const L_WN = {
|
const L_WN = {
|
||||||
@@ -171,7 +183,11 @@ const L_WN = {
|
|||||||
[1, 1, 1, 1],
|
[1, 1, 1, 1],
|
||||||
[1, 1, 1, 1]
|
[1, 1, 1, 1]
|
||||||
],
|
],
|
||||||
exits: ['W', 'N']
|
exits: ['W', 'N'],
|
||||||
|
doorCoordinates: {
|
||||||
|
'W': { x: 0, y: 2 }, // Anchor: Top-Left (row 2,3 -> 2)
|
||||||
|
'N': { x: 2, y: 0 } // Anchor: Top-Left (col 2,3 -> 2)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -192,7 +208,12 @@ const T_NES = {
|
|||||||
[1, 1, 0, 0],
|
[1, 1, 0, 0],
|
||||||
[1, 1, 0, 0]
|
[1, 1, 0, 0]
|
||||||
],
|
],
|
||||||
exits: ['N', 'E', 'S']
|
exits: ['N', 'E', 'S'],
|
||||||
|
doorCoordinates: {
|
||||||
|
'N': { x: 0, y: 0 }, // Col 0,1 -> 0
|
||||||
|
'E': { x: 3, y: 2 }, // Row 2,3 -> 2
|
||||||
|
'S': { x: 0, y: 5 } // Col 0,1 -> 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const T_WNE = {
|
const T_WNE = {
|
||||||
@@ -207,7 +228,12 @@ const T_WNE = {
|
|||||||
[1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1]
|
[1, 1, 1, 1, 1, 1]
|
||||||
],
|
],
|
||||||
exits: ['W', 'N', 'E']
|
exits: ['W', 'N', 'E'],
|
||||||
|
doorCoordinates: {
|
||||||
|
'W': { x: 0, y: 2 }, // Row 2,3 -> 2
|
||||||
|
'N': { x: 2, y: 0 }, // Col 2,3 -> 2
|
||||||
|
'E': { x: 5, y: 2 } // Row 2,3 -> 2
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const T_WSE = {
|
const T_WSE = {
|
||||||
@@ -222,7 +248,12 @@ const T_WSE = {
|
|||||||
[0, 0, 1, 1, 0, 0],
|
[0, 0, 1, 1, 0, 0],
|
||||||
[0, 0, 1, 1, 0, 0]
|
[0, 0, 1, 1, 0, 0]
|
||||||
],
|
],
|
||||||
exits: ['W', 'S', 'E']
|
exits: ['W', 'S', 'E'],
|
||||||
|
doorCoordinates: {
|
||||||
|
'W': { x: 0, y: 0 }, // Row 0,1 -> 0
|
||||||
|
'S': { x: 2, y: 3 }, // Col 2,3 -> 2
|
||||||
|
'E': { x: 5, y: 0 } // Row 0,1 -> 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const T_WNS = {
|
const T_WNS = {
|
||||||
@@ -239,13 +270,21 @@ const T_WNS = {
|
|||||||
[0, 0, 1, 1],
|
[0, 0, 1, 1],
|
||||||
[0, 0, 1, 1]
|
[0, 0, 1, 1]
|
||||||
],
|
],
|
||||||
exits: ['W', 'N', 'S']
|
exits: ['W', 'N', 'S'],
|
||||||
|
doorCoordinates: {
|
||||||
|
'W': { x: 0, y: 2 }, // Row 2,3 -> 2
|
||||||
|
'N': { x: 2, y: 0 }, // Col 2,3 -> 2
|
||||||
|
'S': { x: 2, y: 5 } // Col 2,3 -> 2
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// CORRIDORS (2x6 or 6x2 with 2-tile rows)
|
// CORRIDORS (2x6 or 6x2 with 2-tile rows)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
const CORRIDOR_DOORS_EW = { 'E': { x: 5, y: 0 }, 'W': { x: 0, y: 0 } };
|
||||||
|
const CORRIDOR_DOORS_NS = { 'N': { x: 0, y: 0 }, 'S': { x: 0, y: 5 } };
|
||||||
|
|
||||||
// Corridor 1 - East-West (horizontal)
|
// Corridor 1 - East-West (horizontal)
|
||||||
const CORRIDOR1_EW = {
|
const CORRIDOR1_EW = {
|
||||||
id: 'corridor1_EW',
|
id: 'corridor1_EW',
|
||||||
@@ -257,7 +296,8 @@ const CORRIDOR1_EW = {
|
|||||||
[1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1]
|
[1, 1, 1, 1, 1, 1]
|
||||||
],
|
],
|
||||||
exits: ['E', 'W']
|
exits: ['E', 'W'],
|
||||||
|
doorCoordinates: CORRIDOR_DOORS_EW
|
||||||
};
|
};
|
||||||
|
|
||||||
// Corridor 1 - North-South (vertical)
|
// Corridor 1 - North-South (vertical)
|
||||||
@@ -275,7 +315,8 @@ const CORRIDOR1_NS = {
|
|||||||
[1, 1],
|
[1, 1],
|
||||||
[1, 1]
|
[1, 1]
|
||||||
],
|
],
|
||||||
exits: ['N', 'S']
|
exits: ['N', 'S'],
|
||||||
|
doorCoordinates: CORRIDOR_DOORS_NS
|
||||||
};
|
};
|
||||||
|
|
||||||
// Corridor 2 - East-West (horizontal)
|
// Corridor 2 - East-West (horizontal)
|
||||||
@@ -289,7 +330,8 @@ const CORRIDOR2_EW = {
|
|||||||
[1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1]
|
[1, 1, 1, 1, 1, 1]
|
||||||
],
|
],
|
||||||
exits: ['E', 'W']
|
exits: ['E', 'W'],
|
||||||
|
doorCoordinates: CORRIDOR_DOORS_EW
|
||||||
};
|
};
|
||||||
|
|
||||||
// Corridor 2 - North-South (vertical)
|
// Corridor 2 - North-South (vertical)
|
||||||
@@ -307,7 +349,8 @@ const CORRIDOR2_NS = {
|
|||||||
[1, 1],
|
[1, 1],
|
||||||
[1, 1]
|
[1, 1]
|
||||||
],
|
],
|
||||||
exits: ['N', 'S']
|
exits: ['N', 'S'],
|
||||||
|
doorCoordinates: CORRIDOR_DOORS_NS
|
||||||
};
|
};
|
||||||
|
|
||||||
// Corridor 3 - East-West (horizontal)
|
// Corridor 3 - East-West (horizontal)
|
||||||
@@ -321,7 +364,8 @@ const CORRIDOR3_EW = {
|
|||||||
[1, 1, 1, 1, 1, 1],
|
[1, 1, 1, 1, 1, 1],
|
||||||
[1, 1, 1, 1, 1, 1]
|
[1, 1, 1, 1, 1, 1]
|
||||||
],
|
],
|
||||||
exits: ['E', 'W']
|
exits: ['E', 'W'],
|
||||||
|
doorCoordinates: CORRIDOR_DOORS_EW
|
||||||
};
|
};
|
||||||
|
|
||||||
// Corridor 3 - North-South (vertical)
|
// Corridor 3 - North-South (vertical)
|
||||||
@@ -339,7 +383,8 @@ const CORRIDOR3_NS = {
|
|||||||
[1, 1],
|
[1, 1],
|
||||||
[1, 1]
|
[1, 1]
|
||||||
],
|
],
|
||||||
exits: ['N', 'S']
|
exits: ['N', 'S'],
|
||||||
|
doorCoordinates: CORRIDOR_DOORS_NS
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
379
src/main.js
379
src/main.js
@@ -1,7 +1,7 @@
|
|||||||
import './style.css';
|
import './style.css';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||||
import { io } from "socket.io-client";
|
// import { io } from "socket.io-client";
|
||||||
import { turnManager, PHASES } from './systems/TurnManager.js';
|
import { turnManager, PHASES } from './systems/TurnManager.js';
|
||||||
import { dungeonDeck } from './dungeon/DungeonDecks.js';
|
import { dungeonDeck } from './dungeon/DungeonDecks.js';
|
||||||
import * as TileDefinitions from './dungeon/TileDefinitions.js';
|
import * as TileDefinitions from './dungeon/TileDefinitions.js';
|
||||||
@@ -11,7 +11,13 @@ import { TILE_DEFINITIONS, getTileDefinition } from './dungeon/TileDefinitions.j
|
|||||||
// --- NETWORK SETUP ---
|
// --- NETWORK SETUP ---
|
||||||
// Dynamic connection to support playing from mobile on the same network
|
// Dynamic connection to support playing from mobile on the same network
|
||||||
const socketUrl = `http://${window.location.hostname}:3001`;
|
const socketUrl = `http://${window.location.hostname}:3001`;
|
||||||
const socket = io(socketUrl);
|
// const socket = io(socketUrl);
|
||||||
|
// MOCK SOCKET to avoid connection errors while debugging
|
||||||
|
const socket = {
|
||||||
|
on: (event, callback) => console.log(`[MockSocket] Listening for ${event}`),
|
||||||
|
emit: (event, data) => console.log(`[MockSocket] Emitting ${event}`, data),
|
||||||
|
id: 'mock-id'
|
||||||
|
};
|
||||||
let lobbyCode = null;
|
let lobbyCode = null;
|
||||||
|
|
||||||
socket.on("connect", () => {
|
socket.on("connect", () => {
|
||||||
@@ -101,6 +107,35 @@ function generateDungeon() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to create a flipped version of a tile
|
||||||
|
function createFlippedTile(tileDef, direction) {
|
||||||
|
const flipped = { ...tileDef };
|
||||||
|
|
||||||
|
// Create a new flipped walkability matrix
|
||||||
|
const { walkability, width, height } = tileDef;
|
||||||
|
const newWalkability = [];
|
||||||
|
|
||||||
|
if (direction === 'horizontal') {
|
||||||
|
// Flip horizontally (mirror left-right)
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
newWalkability[y] = [...walkability[y]].reverse();
|
||||||
|
}
|
||||||
|
flipped.id = tileDef.id + '_flipped_h';
|
||||||
|
} else {
|
||||||
|
// Flip vertically (mirror top-bottom)
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
newWalkability[y] = walkability[height - 1 - y];
|
||||||
|
}
|
||||||
|
flipped.id = tileDef.id + '_flipped_v';
|
||||||
|
}
|
||||||
|
|
||||||
|
flipped.walkability = newWalkability;
|
||||||
|
// Note: We keep the same image, the flip is only for walkability logic
|
||||||
|
// If you want visual flipping, you'd need to modify the rendering code
|
||||||
|
|
||||||
|
return flipped;
|
||||||
|
}
|
||||||
|
|
||||||
// --- EXPLORACIÓN DINÁMICA ---
|
// --- EXPLORACIÓN DINÁMICA ---
|
||||||
function exploreRoom(originRoom, door) {
|
function exploreRoom(originRoom, door) {
|
||||||
// Draw an event card
|
// Draw an event card
|
||||||
@@ -119,7 +154,7 @@ function exploreRoom(originRoom, door) {
|
|||||||
|
|
||||||
// Try to draw a compatible abstract tile type (up to 10 attempts)
|
// Try to draw a compatible abstract tile type (up to 10 attempts)
|
||||||
let card = null;
|
let card = null;
|
||||||
let alignmentOffset = 0;
|
let placementOffset = 0; // Offset for L-shapes based on selection
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 10;
|
const maxAttempts = 10;
|
||||||
|
|
||||||
@@ -151,10 +186,71 @@ function exploreRoom(originRoom, door) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'L':
|
case 'L':
|
||||||
candidates = [...TileDefinitions.L_SHAPES];
|
// NEW RULE: Select L-shape based on corridor exit direction
|
||||||
|
if (door.side === 'N') {
|
||||||
|
// North exit (vertical corridor going up): choose LSE or LSW
|
||||||
|
const lOptions = [
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.L_SE, offset: -1 }, // LSE with offset -1
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.L_WS, offset: 1 } // LSW with offset +1
|
||||||
|
];
|
||||||
|
const selected = lOptions[Math.floor(Math.random() * lOptions.length)];
|
||||||
|
card = selected.tile;
|
||||||
|
placementOffset = selected.offset;
|
||||||
|
console.log(`✓ Selected ${card.id} for N exit with offset ${placementOffset}`);
|
||||||
|
} else if (door.side === 'S') {
|
||||||
|
// South exit (vertical corridor going down): choose LNE or LNW
|
||||||
|
const lOptions = [
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.L_NE, offset: 1 }, // LNE with offset 0
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.L_WN, offset: -1 } // LNW with offset +2
|
||||||
|
];
|
||||||
|
const selected = lOptions[Math.floor(Math.random() * lOptions.length)];
|
||||||
|
card = selected.tile;
|
||||||
|
placementOffset = selected.offset;
|
||||||
|
console.log(`✓ Selected ${card.id} for S exit with offset ${placementOffset}`);
|
||||||
|
} else if (door.side === 'E') {
|
||||||
|
// East exit: choose LWN or LWS
|
||||||
|
const lOptions = [
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.L_WN, offset: 1 }, // LWN with offset 0
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.L_WS, offset: -1 } // LWS with offset +2
|
||||||
|
];
|
||||||
|
const selected = lOptions[Math.floor(Math.random() * lOptions.length)];
|
||||||
|
card = selected.tile;
|
||||||
|
placementOffset = selected.offset;
|
||||||
|
console.log(`✓ Selected ${card.id} for E exit with offset ${placementOffset}`);
|
||||||
|
} else if (door.side === 'W') {
|
||||||
|
// West exit: choose LNE or LSE
|
||||||
|
const lOptions = [
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.L_NE, offset: 1 }, // LNE with offset +2
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.L_SE, offset: -1 } // LSE with offset 0
|
||||||
|
];
|
||||||
|
const selected = lOptions[Math.floor(Math.random() * lOptions.length)];
|
||||||
|
card = selected.tile;
|
||||||
|
placementOffset = selected.offset;
|
||||||
|
console.log(`✓ Selected ${card.id} for W exit with offset ${placementOffset}`);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'T':
|
case 'T':
|
||||||
|
// T-junction placement based on corridor exit direction
|
||||||
|
if (door.side === 'N') {
|
||||||
|
// North exit: choose T_NES, T_WNS, or T_WSE
|
||||||
|
const tOptions = [
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.T_NES, offset: -1 }, // T_NES with offset -1
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.T_WNS, offset: 1 }, // T_WNS with offset +1
|
||||||
|
{ tile: TileDefinitions.TILE_DEFINITIONS.T_WSE, offset: 0 } // T_WSE with offset 0 (placeholder)
|
||||||
|
];
|
||||||
|
const selected = tOptions[Math.floor(Math.random() * tOptions.length)];
|
||||||
|
card = selected.tile;
|
||||||
|
placementOffset = selected.offset;
|
||||||
|
console.log(`✓ Selected ${card.id} for N exit with offset ${placementOffset}`);
|
||||||
|
} else {
|
||||||
|
// For other exits, use all T-junctions (fallback)
|
||||||
candidates = [...TileDefinitions.T_JUNCTIONS];
|
candidates = [...TileDefinitions.T_JUNCTIONS];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we already selected a specific card (L-shapes with N/S exits), skip validation
|
||||||
|
if (card) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,21 +260,37 @@ function exploreRoom(originRoom, door) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try all candidates and collect those that fit
|
// For rooms: randomly select one and optionally flip it
|
||||||
|
if (abstractCard.type === 'room_4x4' || abstractCard.type === 'room_4x6') {
|
||||||
|
const selectedRoom = candidates[Math.floor(Math.random() * candidates.length)];
|
||||||
|
|
||||||
|
// Randomly decide to flip horizontally or vertically
|
||||||
|
const shouldFlip = Math.random() < 0.5;
|
||||||
|
const flipDirection = Math.random() < 0.5 ? 'horizontal' : 'vertical';
|
||||||
|
|
||||||
|
if (shouldFlip) {
|
||||||
|
card = createFlippedTile(selectedRoom, flipDirection);
|
||||||
|
console.log(`✓ Selected ${selectedRoom.id} and flipped ${flipDirection}`);
|
||||||
|
} else {
|
||||||
|
card = selectedRoom;
|
||||||
|
console.log(`✓ Selected ${selectedRoom.id} without flipping`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For corridors and T-junctions: validate and select
|
||||||
const fittingVariants = [];
|
const fittingVariants = [];
|
||||||
for (const variant of candidates) {
|
for (const variant of candidates) {
|
||||||
const connectionResult = canConnectTiles(originRoom, variant, door.side);
|
const connectionResult = canConnectTiles(originRoom, variant, door.side);
|
||||||
if (connectionResult.valid) {
|
if (connectionResult.valid) {
|
||||||
fittingVariants.push({ variant, offset: connectionResult.offset });
|
fittingVariants.push(variant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fittingVariants.length > 0) {
|
if (fittingVariants.length > 0) {
|
||||||
// RANDOM selection from fitting variants
|
// RANDOM selection from fitting variants
|
||||||
const selected = fittingVariants[Math.floor(Math.random() * fittingVariants.length)];
|
card = fittingVariants[Math.floor(Math.random() * fittingVariants.length)];
|
||||||
card = selected.variant;
|
console.log(`✓ Selected ${card.id} (${card.tileType}) randomly from ${fittingVariants.length} fitting variants`);
|
||||||
alignmentOffset = selected.offset;
|
|
||||||
console.log(`✓ Selected ${card.id} (${card.tileType}) randomly from ${fittingVariants.length} fitting variants, offset ${alignmentOffset}`);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`✗ No ${abstractCard.type} variant fits, trying another tile type...`);
|
console.log(`✗ No ${abstractCard.type} variant fits, trying another tile type...`);
|
||||||
attempts++;
|
attempts++;
|
||||||
@@ -193,35 +305,33 @@ function exploreRoom(originRoom, door) {
|
|||||||
const nextTileDef = card;
|
const nextTileDef = card;
|
||||||
const newRoomId = ROOMS.rooms.length + 1;
|
const newRoomId = ROOMS.rooms.length + 1;
|
||||||
|
|
||||||
// Calculate entry door position in the new tile
|
// Calculate entry door position relative to the new tile origin
|
||||||
|
// We use external adjacent coordinates (-1 or width/height) to place the new tile
|
||||||
|
// strictly adjacent to the previous one, preventing 1-cell overlaps.
|
||||||
let entryGridX, entryGridY;
|
let entryGridX, entryGridY;
|
||||||
|
|
||||||
|
// Use explicit door coordinates if available (for asymmetric tiles like L/T)
|
||||||
|
const doorCoords = nextTileDef.doorCoordinates ? nextTileDef.doorCoordinates[entrySide] : null;
|
||||||
|
|
||||||
if (entrySide === 'N') {
|
if (entrySide === 'N') {
|
||||||
entryGridX = Math.floor(nextTileDef.width / 2);
|
entryGridX = doorCoords ? doorCoords.x : Math.floor(nextTileDef.width / 2);
|
||||||
entryGridY = 0;
|
entryGridY = -1;
|
||||||
} else if (entrySide === 'S') {
|
} else if (entrySide === 'S') {
|
||||||
entryGridX = Math.floor(nextTileDef.width / 2);
|
entryGridX = doorCoords ? doorCoords.x : Math.floor(nextTileDef.width / 2);
|
||||||
entryGridY = nextTileDef.height;
|
entryGridY = nextTileDef.height;
|
||||||
} else if (entrySide === 'E') {
|
} else if (entrySide === 'E') {
|
||||||
entryGridX = nextTileDef.width;
|
entryGridX = nextTileDef.width;
|
||||||
entryGridY = Math.floor(nextTileDef.height / 2);
|
entryGridY = doorCoords ? doorCoords.y : Math.floor(nextTileDef.height / 2);
|
||||||
} else if (entrySide === 'W') {
|
} else if (entrySide === 'W') {
|
||||||
entryGridX = 0;
|
entryGridX = -1;
|
||||||
entryGridY = Math.floor(nextTileDef.height / 2);
|
entryGridY = doorCoords ? doorCoords.y : Math.floor(nextTileDef.height / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate absolute position for the new tile
|
// Calculate absolute position for the new tile
|
||||||
let newX = originRoom.tile.x + door.gridX - entryGridX;
|
let newX = originRoom.tile.x + door.gridX - entryGridX;
|
||||||
let newY = originRoom.tile.y + door.gridY - entryGridY;
|
let newY = originRoom.tile.y + door.gridY - entryGridY;
|
||||||
|
|
||||||
// Apply alignment offset based on exit direction
|
// Manual placementOffset removed: Handled by explicit doorCoordinates via TileDefinitions.js
|
||||||
if (door.side === 'N' || door.side === 'S') {
|
|
||||||
// Vertical connection - offset horizontally
|
|
||||||
newX += alignmentOffset;
|
|
||||||
} else {
|
|
||||||
// Horizontal connection - offset vertically
|
|
||||||
newY += alignmentOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for collisions
|
// Check for collisions
|
||||||
if (!isAreaFree(newX, newY, nextTileDef.width, nextTileDef.height)) {
|
if (!isAreaFree(newX, newY, nextTileDef.width, nextTileDef.height)) {
|
||||||
@@ -258,28 +368,38 @@ function exploreRoom(originRoom, door) {
|
|||||||
nextTileDef.exits.forEach(exitDir => {
|
nextTileDef.exits.forEach(exitDir => {
|
||||||
if (exitDir === entrySide) return; // Already have this connection
|
if (exitDir === entrySide) return; // Already have this connection
|
||||||
|
|
||||||
|
// Check if this is an L or T tile to make exit open by default
|
||||||
|
// For L and T tiles, exits are open passages, not closed doors
|
||||||
|
const isOpenPassage = (nextTileDef.tileType === 'L' || nextTileDef.tileType === 'T');
|
||||||
|
|
||||||
const exitDoor = {
|
const exitDoor = {
|
||||||
side: exitDir,
|
side: exitDir,
|
||||||
leadsTo: null,
|
leadsTo: null,
|
||||||
isOpen: false,
|
isOpen: isOpenPassage, // Open by default for L/T
|
||||||
isDoor: true, // Will be determined when connected
|
isDoor: !isOpenPassage, // Not a blocking door for L/T
|
||||||
id: `door_${newRoomId}_${exitDir}`
|
id: `door_${newRoomId}_${exitDir}`
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate door coordinates
|
// Calculate door coordinates using explicit definition or default centering
|
||||||
|
if (nextTileDef.doorCoordinates && nextTileDef.doorCoordinates[exitDir]) {
|
||||||
|
exitDoor.gridX = nextTileDef.doorCoordinates[exitDir].x;
|
||||||
|
exitDoor.gridY = nextTileDef.doorCoordinates[exitDir].y;
|
||||||
|
} else {
|
||||||
|
// Default centering logic
|
||||||
if (exitDir === 'N') {
|
if (exitDir === 'N') {
|
||||||
exitDoor.gridX = Math.floor(nextTileDef.width / 2);
|
exitDoor.gridX = Math.floor(nextTileDef.width / 2);
|
||||||
exitDoor.gridY = 0;
|
exitDoor.gridY = 0;
|
||||||
} else if (exitDir === 'S') {
|
} else if (exitDir === 'S') {
|
||||||
exitDoor.gridX = Math.floor(nextTileDef.width / 2);
|
exitDoor.gridX = Math.floor(nextTileDef.width / 2);
|
||||||
exitDoor.gridY = nextTileDef.height;
|
exitDoor.gridY = nextTileDef.height - 1;
|
||||||
} else if (exitDir === 'E') {
|
} else if (exitDir === 'E') {
|
||||||
exitDoor.gridX = nextTileDef.width;
|
exitDoor.gridX = nextTileDef.width - 1;
|
||||||
exitDoor.gridY = Math.floor(nextTileDef.height / 2);
|
exitDoor.gridY = Math.floor(nextTileDef.height / 2);
|
||||||
} else if (exitDir === 'W') {
|
} else if (exitDir === 'W') {
|
||||||
exitDoor.gridX = 0;
|
exitDoor.gridX = 0;
|
||||||
exitDoor.gridY = Math.floor(nextTileDef.height / 2);
|
exitDoor.gridY = Math.floor(nextTileDef.height / 2);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newRoom.doors.push(exitDoor);
|
newRoom.doors.push(exitDoor);
|
||||||
});
|
});
|
||||||
@@ -743,7 +863,7 @@ function shouldPlaceDoor(tileTypeA, tileTypeB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate walkability alignment between two tiles at their connection point
|
// Validate walkability alignment between two tiles at their connection point
|
||||||
// Returns: { valid: boolean, offset: number } - offset is how much to shift tileB to align with tileA
|
// Returns: { valid: boolean } - whether the tiles can connect
|
||||||
function validateWalkabilityAlignment(tileDefA, posA, tileDefB, posB, exitSide) {
|
function validateWalkabilityAlignment(tileDefA, posA, tileDefB, posB, exitSide) {
|
||||||
// Get the edge cells that will connect
|
// Get the edge cells that will connect
|
||||||
const edgeA = getEdgeCells(tileDefA, exitSide);
|
const edgeA = getEdgeCells(tileDefA, exitSide);
|
||||||
@@ -753,101 +873,18 @@ function validateWalkabilityAlignment(tileDefA, posA, tileDefB, posB, exitSide)
|
|||||||
console.log(`[ALIGN] EdgeA (${tileDefA.id}):`, edgeA);
|
console.log(`[ALIGN] EdgeA (${tileDefA.id}):`, edgeA);
|
||||||
console.log(`[ALIGN] EdgeB (${tileDefB.id}):`, edgeB);
|
console.log(`[ALIGN] EdgeB (${tileDefB.id}):`, edgeB);
|
||||||
|
|
||||||
// Special handling for corridor connections
|
// For now, we just check that both edges have at least some walkable cells
|
||||||
// Corridors are 2 tiles wide, rooms/L/T are typically 4 tiles wide
|
// The actual alignment will be handled manually by the user
|
||||||
// We need to find where the corridor's walkable area aligns with the room's walkable area
|
const hasWalkableA = edgeA.some(cell => cell > 0);
|
||||||
|
const hasWalkableB = edgeB.some(cell => cell > 0);
|
||||||
|
|
||||||
if (edgeA.length !== edgeB.length) {
|
if (!hasWalkableA || !hasWalkableB) {
|
||||||
// Different edge lengths - need to find alignment
|
console.warn('[ALIGN] One or both edges have no walkable cells');
|
||||||
const smallerEdge = edgeA.length < edgeB.length ? edgeA : edgeB;
|
return { valid: false };
|
||||||
const largerEdge = edgeA.length < edgeB.length ? edgeB : edgeA;
|
|
||||||
const isASmaller = edgeA.length < edgeB.length;
|
|
||||||
|
|
||||||
console.log(`[ALIGN] Different sizes: ${edgeA.length} vs ${edgeB.length}`);
|
|
||||||
console.log(`[ALIGN] isASmaller: ${isASmaller}`);
|
|
||||||
|
|
||||||
// Find walkable cells in smaller edge
|
|
||||||
const smallerWalkable = smallerEdge.filter(cell => cell > 0);
|
|
||||||
if (smallerWalkable.length === 0) {
|
|
||||||
console.warn('[ALIGN] No walkable cells in smaller edge');
|
|
||||||
return { valid: false, offset: 0 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const smallerWidth = smallerEdge.length;
|
console.log(`✓ [ALIGN] Both edges have walkable cells - connection allowed`);
|
||||||
const largerWidth = largerEdge.length;
|
return { valid: true };
|
||||||
|
|
||||||
// FIRST: Try offset 0 (no displacement needed)
|
|
||||||
let validAtZero = true;
|
|
||||||
for (let i = 0; i < smallerWidth; i++) {
|
|
||||||
const smallCell = smallerEdge[i];
|
|
||||||
const largeCell = largerEdge[i];
|
|
||||||
|
|
||||||
const isSmallWalkable = smallCell > 0;
|
|
||||||
const isLargeWalkable = largeCell > 0;
|
|
||||||
|
|
||||||
console.log(`[ALIGN] Offset 0, index ${i}: small=${smallCell}(${isSmallWalkable}) vs large=${largeCell}(${isLargeWalkable})`);
|
|
||||||
|
|
||||||
if (isSmallWalkable !== isLargeWalkable) {
|
|
||||||
validAtZero = false;
|
|
||||||
console.log(`[ALIGN] ❌ Offset 0 FAILED at index ${i}: walkability mismatch`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validAtZero) {
|
|
||||||
console.log(`✓ [ALIGN] Valid alignment at offset 0 (no displacement needed)`);
|
|
||||||
return { valid: true, offset: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// If offset 0 doesn't work, try offset of 2 (corridor width)
|
|
||||||
// This aligns the corridor with the other walkable section of the L/T
|
|
||||||
const offset = 2;
|
|
||||||
if (offset <= largerWidth - smallerWidth) {
|
|
||||||
let valid = true;
|
|
||||||
for (let i = 0; i < smallerWidth; i++) {
|
|
||||||
const smallCell = smallerEdge[i];
|
|
||||||
const largeCell = largerEdge[offset + i];
|
|
||||||
|
|
||||||
const isSmallWalkable = smallCell > 0;
|
|
||||||
const isLargeWalkable = largeCell > 0;
|
|
||||||
|
|
||||||
if (isSmallWalkable !== isLargeWalkable) {
|
|
||||||
valid = false;
|
|
||||||
console.log(`[ALIGN] Offset ${offset} failed at index ${i}: ${smallCell} vs ${largeCell}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
// Calculate final offset
|
|
||||||
// If A (corridor) is smaller than B (L/T), we need to shift B by +offset
|
|
||||||
// If B is smaller than A, we need to shift B by -offset
|
|
||||||
const finalOffset = isASmaller ? offset : -offset;
|
|
||||||
console.log(`✓ [ALIGN] Valid alignment at offset ${offset}, final offset: ${finalOffset} (isASmaller: ${isASmaller})`);
|
|
||||||
return { valid: true, offset: finalOffset };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.warn('[ALIGN] Could not find valid alignment for edges of different sizes');
|
|
||||||
return { valid: false, offset: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same length - check direct alignment
|
|
||||||
for (let i = 0; i < edgeA.length; i++) {
|
|
||||||
const cellA = edgeA[i];
|
|
||||||
const cellB = edgeB[i];
|
|
||||||
|
|
||||||
// Rule: Cannot connect 0 (not walkable) with >0 (walkable)
|
|
||||||
const isAWalkable = cellA > 0;
|
|
||||||
const isBWalkable = cellB > 0;
|
|
||||||
|
|
||||||
if (isAWalkable !== isBWalkable) {
|
|
||||||
console.warn(`[ALIGN] Walkability mismatch at index ${i}: ${cellA} vs ${cellB}`);
|
|
||||||
return { valid: false, offset: 0 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: true, offset: 0 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get edge cells from a tile definition for a given side
|
// Get edge cells from a tile definition for a given side
|
||||||
@@ -892,7 +929,7 @@ function getOppositeSide(side) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if two tiles can connect based on type rules and walkability alignment
|
// Check if two tiles can connect based on type rules and walkability alignment
|
||||||
// Returns: { valid: boolean, offset: number } - offset for positioning the new tile
|
// Returns: { valid: boolean } - whether the tiles can connect
|
||||||
function canConnectTiles(roomA, tileDefB, exitSide) {
|
function canConnectTiles(roomA, tileDefB, exitSide) {
|
||||||
const tileDefA = roomA.tileDef;
|
const tileDefA = roomA.tileDef;
|
||||||
|
|
||||||
@@ -909,7 +946,7 @@ function canConnectTiles(roomA, tileDefB, exitSide) {
|
|||||||
|
|
||||||
if (!validConnections[typeA] || !validConnections[typeA].includes(typeB)) {
|
if (!validConnections[typeA] || !validConnections[typeA].includes(typeB)) {
|
||||||
console.warn(`Invalid connection: ${typeA} cannot connect to ${typeB}`);
|
console.warn(`Invalid connection: ${typeA} cannot connect to ${typeB}`);
|
||||||
return { valid: false, offset: 0 };
|
return { valid: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRITICAL: Check that tileB has an exit in the opposite direction
|
// CRITICAL: Check that tileB has an exit in the opposite direction
|
||||||
@@ -917,17 +954,17 @@ function canConnectTiles(roomA, tileDefB, exitSide) {
|
|||||||
const requiredExit = getOppositeSide(exitSide);
|
const requiredExit = getOppositeSide(exitSide);
|
||||||
if (!tileDefB.exits.includes(requiredExit)) {
|
if (!tileDefB.exits.includes(requiredExit)) {
|
||||||
console.warn(`Exit direction mismatch: ${tileDefB.id} doesn't have required exit '${requiredExit}' (exiting via '${exitSide}')`);
|
console.warn(`Exit direction mismatch: ${tileDefB.id} doesn't have required exit '${requiredExit}' (exiting via '${exitSide}')`);
|
||||||
return { valid: false, offset: 0 };
|
return { valid: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check walkability alignment and get offset
|
// Check walkability alignment
|
||||||
const alignmentResult = validateWalkabilityAlignment(tileDefA, roomA.tile, tileDefB, null, exitSide);
|
const alignmentResult = validateWalkabilityAlignment(tileDefA, roomA.tile, tileDefB, null, exitSide);
|
||||||
if (!alignmentResult.valid) {
|
if (!alignmentResult.valid) {
|
||||||
console.warn('Walkability alignment failed');
|
console.warn('Walkability alignment failed');
|
||||||
return { valid: false, offset: 0 };
|
return { valid: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
return alignmentResult;
|
return { valid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CREACIÓN DE MARCADORES ---
|
// --- CREACIÓN DE MARCADORES ---
|
||||||
@@ -1274,9 +1311,77 @@ async function animateMovement() {
|
|||||||
SESSION.selectedUnitId = null; // Deseleccionar unidad al terminar de mover
|
SESSION.selectedUnitId = null; // Deseleccionar unidad al terminar de mover
|
||||||
updateSelectionVisuals();
|
updateSelectionVisuals();
|
||||||
SESSION.isAnimating = false;
|
SESSION.isAnimating = false;
|
||||||
|
|
||||||
|
// Auto-exploration check for open passages (L/T tiles)
|
||||||
|
checkAutoExploration(unit);
|
||||||
|
|
||||||
drawMinimap(); // Actualizar posición final del jugador
|
drawMinimap(); // Actualizar posición final del jugador
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if unit is on an open passage leading to unexplored area
|
||||||
|
function checkAutoExploration(unit) {
|
||||||
|
// Find unit's current room
|
||||||
|
const currentRoom = ROOMS.rooms.find(r => r.entities.some(e => e.id === unit.id));
|
||||||
|
if (!currentRoom) return;
|
||||||
|
|
||||||
|
// DEBUG: Log unit position for troubleshooting
|
||||||
|
console.log(`[AutoExplore] Unit at (${unit.x}, ${unit.y}) in Room ${currentRoom.id}`);
|
||||||
|
|
||||||
|
const door = currentRoom.doors.find(d => {
|
||||||
|
const doorGlobalX = currentRoom.tile.x + d.gridX;
|
||||||
|
const doorGlobalY = currentRoom.tile.y + d.gridY;
|
||||||
|
|
||||||
|
// DEBUG: Log each door check
|
||||||
|
console.log(`[CheckDoor] ${d.id} (${d.side}): Global(${doorGlobalX}, ${doorGlobalY}) vs Unit(${unit.x}, ${unit.y})`);
|
||||||
|
|
||||||
|
// Check 1: Is Unit on the door line? (Internal or External adjacent)
|
||||||
|
let isMainAxisMatch = false;
|
||||||
|
let isCrossAxisMatch = false;
|
||||||
|
|
||||||
|
if (d.side === 'N' || d.side === 'S') {
|
||||||
|
// Main axis = Y. Internal: doorGlobalY. External: +/- 1
|
||||||
|
const deltaY = unit.y - doorGlobalY;
|
||||||
|
// Allow 0 (internal) or 1 step out (external)
|
||||||
|
if (d.side === 'N') isMainAxisMatch = (deltaY === 0 || deltaY === -1);
|
||||||
|
else isMainAxisMatch = (deltaY === 0 || deltaY === 1);
|
||||||
|
|
||||||
|
// Cross axis = X. Allow +/- 1 for corridor width
|
||||||
|
isCrossAxisMatch = Math.abs(unit.x - doorGlobalX) <= 1;
|
||||||
|
|
||||||
|
} else { // E or W
|
||||||
|
// Main axis = X. Internal: doorGlobalX. External: +/- 1
|
||||||
|
const deltaX = unit.x - doorGlobalX;
|
||||||
|
// Allow 0 (internal) or 1 step out (external)
|
||||||
|
if (d.side === 'W') isMainAxisMatch = (deltaX === 0 || deltaX === -1);
|
||||||
|
else isMainAxisMatch = (deltaX === 0 || deltaX === 1);
|
||||||
|
|
||||||
|
// Cross axis = Y. Allow +/- 1 for corridor width
|
||||||
|
isCrossAxisMatch = Math.abs(unit.y - doorGlobalY) <= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMainAxisMatch && isCrossAxisMatch) {
|
||||||
|
console.log(`[AutoExplore] MATCH! Door ${d.id} (${d.side}). Unit(${unit.x}, ${unit.y}) vs Door(${doorGlobalX}, ${doorGlobalY})`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (door) {
|
||||||
|
console.log(`[AutoExplore] Found door at unit pos: ${door.id}, Open: ${door.isOpen}, LeadsTo: ${door.leadsTo}`);
|
||||||
|
|
||||||
|
if (door.isOpen && door.leadsTo === null) {
|
||||||
|
console.log("[AutoExplore] Triggering exploration...");
|
||||||
|
// Use timeout to allow movement animation to settle/feel natural
|
||||||
|
setTimeout(() => {
|
||||||
|
const newRoom = exploreRoom(currentRoom, door);
|
||||||
|
if (newRoom) {
|
||||||
|
renderRoom(newRoom);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verifica si la unidad ha entrado físicamente en una sala diferente a la registrada
|
// Verifica si la unidad ha entrado físicamente en una sala diferente a la registrada
|
||||||
function detectRoomChange(unit, currentLogicalRoom) {
|
function detectRoomChange(unit, currentLogicalRoom) {
|
||||||
for (const room of ROOMS.rooms) {
|
for (const room of ROOMS.rooms) {
|
||||||
@@ -1488,6 +1593,14 @@ async function renderRoom(room) {
|
|||||||
tileMesh.position.z = originPos.z + (worldHeight / 2) - (CONFIG.CELL_SIZE / 2);
|
tileMesh.position.z = originPos.z + (worldHeight / 2) - (CONFIG.CELL_SIZE / 2);
|
||||||
tileMesh.position.y = 0;
|
tileMesh.position.y = 0;
|
||||||
|
|
||||||
|
// Random rotation for 4x4 rooms to add variety
|
||||||
|
if (tileDef.id.includes('room_4x4')) {
|
||||||
|
const rotations = [0, Math.PI / 2, Math.PI, Math.PI * 1.5];
|
||||||
|
const randomRotation = rotations[Math.floor(Math.random() * rotations.length)];
|
||||||
|
tileMesh.rotation.z = randomRotation;
|
||||||
|
console.log(`[DEBUG] Room ${room.id} rotated ${(randomRotation * 180 / Math.PI).toFixed(0)}°`);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`[DEBUG] renderRoom ${room.id} | MeshPos:`, tileMesh.position);
|
console.log(`[DEBUG] renderRoom ${room.id} | MeshPos:`, tileMesh.position);
|
||||||
|
|
||||||
scene.add(tileMesh);
|
scene.add(tileMesh);
|
||||||
@@ -1520,6 +1633,14 @@ async function renderRoom(room) {
|
|||||||
|
|
||||||
for (const config of wallConfigs) {
|
for (const config of wallConfigs) {
|
||||||
const wallSide = config.side;
|
const wallSide = config.side;
|
||||||
|
|
||||||
|
// Skip wall rendering for L and T tiles on their exit sides
|
||||||
|
if ((room.tileDef.tileType === 'L' || room.tileDef.tileType === 'T') &&
|
||||||
|
room.tileDef.exits.includes(wallSide)) {
|
||||||
|
console.log(`Skipping wall on ${wallSide} for ${room.tileDef.tileType} tile (exit side)`);
|
||||||
|
continue; // Don't render wall on exit sides
|
||||||
|
}
|
||||||
|
|
||||||
const door = doorsOnSides[wallSide];
|
const door = doorsOnSides[wallSide];
|
||||||
|
|
||||||
// Función helper para crear un segmento de pared
|
// Función helper para crear un segmento de pared
|
||||||
|
|||||||
Reference in New Issue
Block a user