From 75cf63f906d4732e00ce9e5ee5eedfb68d2ef22b Mon Sep 17 00:00:00 2001 From: marti Date: Mon, 29 Dec 2025 21:42:12 +0100 Subject: [PATCH] chore: update devlog and finalize deck balance --- DEVLOG.md | 6 ++ src/dungeon/DungeonDecks.js | 123 ++++++++++++++++++--------------- src/dungeon/TileDefinitions.js | 73 +++++++++++++++---- 3 files changed, 133 insertions(+), 69 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index 03296b3..a37b952 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -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). - **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). + +## [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). diff --git a/src/dungeon/DungeonDecks.js b/src/dungeon/DungeonDecks.js index 9190a9e..b27dc9d 100644 --- a/src/dungeon/DungeonDecks.js +++ b/src/dungeon/DungeonDecks.js @@ -17,57 +17,28 @@ export class DungeonDeck { shuffleDeck() { // Create a deck with abstract tile types this.deck = [ - // 8 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' }, + // 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' }, - // 4 rooms 4x6 (abstract) - { type: 'room_4x6' }, - { type: 'room_4x6' }, - { type: 'room_4x6' }, - { type: 'room_4x6' }, + // 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' }, - // 12 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' }, + // 10 corridors (abstract) + { 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 - { type: 'L' }, - { type: 'L' }, - { type: 'L' }, - { type: 'L' }, - { type: 'L' }, - { type: 'L' }, - { type: 'L' }, - { type: 'L' }, - { type: 'L' }, - { type: 'L' }, + // 8 L-shapes (abstract) + { type: 'L' }, { type: 'L' }, { type: 'L' }, { type: 'L' }, + { type: 'L' }, { type: 'L' }, { type: 'L' }, { type: 'L' }, - // 8 T-junctions (abstract) - increased from 4 - { type: 'T' }, - { type: 'T' }, - { type: 'T' }, - { type: 'T' }, - { type: 'T' }, - { type: 'T' }, - { type: 'T' }, - { type: 'T' } + // 6 T-junctions (abstract) + { type: 'T' }, { type: 'T' }, { type: 'T' }, { type: 'T' }, + { type: 'T' }, { type: 'T' } ]; // Fisher-Yates shuffle @@ -75,7 +46,13 @@ export class DungeonDeck { const j = Math.floor(Math.random() * (i + 1)); [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() { @@ -91,6 +68,7 @@ export class DungeonDeck { } const card = this.deck.pop(); + console.log(`%c[DungeonDeck] Drew: ${card.type}`, "color: cyan"); this.discardPile.push(card); return card; } @@ -101,24 +79,59 @@ export class DungeonDeck { * @param {number} maxAttempts - Maximum number of cards to try * @returns {object|null} - Abstract tile card or null if none found */ - drawCompatibleCard(originTileType, maxAttempts = 10) { + drawCompatibleCard(originTileType, maxAttempts = 50) { 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++) { - const card = this.drawCard(); + // Check cards currently in deck + 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 if (validTypes.includes(card.type)) { + // Found one! + console.log(`[DungeonDeck] MATCH! Accepted ${card.type}`); + this.discardPile.push(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.discardPile.pop(); } - console.warn(`Could not find compatible tile for ${originTileType} after ${maxAttempts} attempts`); - return null; + console.warn(`Could not find compatible tile for ${originTileType} after ${safetyLimit} attempts. (Deck: ${this.deck.length}, Discard: ${this.discardPile.length})`); + return null; // Return null (game will handle it, maybe stop exploring) } /** diff --git a/src/dungeon/TileDefinitions.js b/src/dungeon/TileDefinitions.js index 95eef71..a9b8235 100644 --- a/src/dungeon/TileDefinitions.js +++ b/src/dungeon/TileDefinitions.js @@ -126,7 +126,11 @@ const L_NE = { [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 = { @@ -141,7 +145,11 @@ const L_SE = { [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 = { @@ -156,7 +164,11 @@ const L_WS = { [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 = { @@ -171,7 +183,11 @@ const L_WN = { [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] ], - 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 = { @@ -207,7 +228,12 @@ const T_WNE = { [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 = { @@ -222,7 +248,12 @@ const T_WSE = { [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 = { @@ -239,13 +270,21 @@ const T_WNS = { [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) // ============================================================================ +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) const CORRIDOR1_EW = { id: 'corridor1_EW', @@ -257,7 +296,8 @@ const CORRIDOR1_EW = { [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) @@ -275,7 +315,8 @@ const CORRIDOR1_NS = { [1, 1], [1, 1] ], - exits: ['N', 'S'] + exits: ['N', 'S'], + doorCoordinates: CORRIDOR_DOORS_NS }; // Corridor 2 - East-West (horizontal) @@ -289,7 +330,8 @@ const CORRIDOR2_EW = { [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) @@ -307,7 +349,8 @@ const CORRIDOR2_NS = { [1, 1], [1, 1] ], - exits: ['N', 'S'] + exits: ['N', 'S'], + doorCoordinates: CORRIDOR_DOORS_NS }; // Corridor 3 - East-West (horizontal) @@ -321,7 +364,8 @@ const CORRIDOR3_EW = { [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) @@ -339,7 +383,8 @@ const CORRIDOR3_NS = { [1, 1], [1, 1] ], - exits: ['N', 'S'] + exits: ['N', 'S'], + doorCoordinates: CORRIDOR_DOORS_NS }; // ============================================================================