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).
|
||||
- **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).
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user