chore: update devlog and finalize deck balance

This commit is contained in:
2025-12-29 21:42:12 +01:00
parent 5804db396f
commit 75cf63f906
3 changed files with 133 additions and 69 deletions

View File

@@ -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).

View File

@@ -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)
}
/**

View File

@@ -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
};
// ============================================================================