chore: update devlog and finalize deck balance
This commit is contained in:
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user