Clean up logs and fix variant reference error

This commit is contained in:
2026-01-02 23:13:52 +01:00
parent 970ff224c3
commit 8bb0dd8780
12 changed files with 106 additions and 801 deletions

View File

@@ -9,7 +9,7 @@ export class DungeonDeck {
}
generateMissionDeck(objectiveTileId) {
console.log("🔍 Inspecting TILES object keys:", Object.keys(TILES));
this.cards = [];
// 1. Create a "Pool" of standard dungeon tiles
@@ -65,7 +65,7 @@ export class DungeonDeck {
// --- Step 5: Stack ---
this.cards = [...topPool, ...bottomPool];
console.log(`Deck Generated: ${this.cards.length} cards.`);
}
shuffleArray(array) {

View File

@@ -41,7 +41,7 @@ export class DungeonGenerator {
}
this.placeCardFinal(firstCard, 0, 0, DIRECTIONS.NORTH);
console.log(`🏰 Dungeon started with ${firstCard.name}`);
// 2. Transition to door selection
this.state = PLACEMENT_STATE.WAITING_DOOR;
@@ -52,9 +52,7 @@ export class DungeonGenerator {
* Player selects a door to expand from
*/
selectDoor(exitPoint) {
console.log('[DungeonGenerator] selectDoor called with:', exitPoint);
console.log('[DungeonGenerator] Current state:', this.state);
console.log('[DungeonGenerator] Available exits:', this.availableExits);
if (this.state !== PLACEMENT_STATE.WAITING_DOOR) {
console.warn("Not in door selection mode");
@@ -81,7 +79,7 @@ export class DungeonGenerator {
// Draw next card
this.currentCard = this.deck.draw();
if (!this.currentCard) {
console.log("Deck empty - dungeon complete");
this.state = PLACEMENT_STATE.COMPLETE;
this.notifyStateChange();
return false;
@@ -113,7 +111,7 @@ export class DungeonGenerator {
this.notifyPlacementUpdate();
this.notifyStateChange();
console.log(`📦 Placing ${this.currentCard.name} at (${this.placementX}, ${this.placementY})`);
return true;
}
@@ -127,7 +125,7 @@ export class DungeonGenerator {
const currentIndex = rotations.indexOf(this.placementRotation);
this.placementRotation = rotations[(currentIndex + 1) % 4];
console.log(`🔄 Rotated to ${this.placementRotation}`);
this.notifyPlacementUpdate();
}
@@ -140,7 +138,7 @@ export class DungeonGenerator {
this.placementX += dx;
this.placementY += dy;
console.log(`↔️ Moved to (${this.placementX}, ${this.placementY})`);
this.notifyPlacementUpdate();
}
@@ -168,13 +166,13 @@ export class DungeonGenerator {
return false;
}
console.log(`[confirmPlacement] Placing at (${this.placementX}, ${this.placementY}) rotation: ${this.placementRotation}`);
// Round to integers (tiles must be on grid cells)
const finalX = Math.round(this.placementX);
const finalY = Math.round(this.placementY);
console.log(`[confirmPlacement] Rounded to (${finalX}, ${finalY})`);
// Place the tile
this.placeCardFinal(
@@ -191,7 +189,7 @@ export class DungeonGenerator {
this.notifyPlacementUpdate(); // Clear preview
this.notifyStateChange();
console.log("✅ Tile placed successfully");
return true;
}
@@ -199,12 +197,7 @@ export class DungeonGenerator {
* Internal: Actually place a card on the grid
*/
placeCardFinal(card, x, y, rotation) {
console.log('[placeCardFinal] Card:', card);
console.log('[placeCardFinal] Card.variants:', card.variants);
console.log('[placeCardFinal] Rotation:', rotation, 'Type:', typeof rotation);
const variant = card.variants[rotation];
console.log('[placeCardFinal] Variant:', variant);
const instance = {
id: `tile_${this.placedTiles.length}`,
@@ -224,9 +217,7 @@ export class DungeonGenerator {
* Update list of exits player can choose from
*/
updateAvailableExits(instance, variant, anchorX, anchorY) {
console.log('[updateAvailableExits] ===== NUEVO CODIGO ===== Called for tile:', instance.id);
console.log('[updateAvailableExits] Variant exits:', variant.exits);
console.log('[updateAvailableExits] Anchor:', anchorX, anchorY);
// Add new exits from this tile
for (const ex of variant.exits) {
@@ -236,7 +227,7 @@ export class DungeonGenerator {
const leadingTo = this.neighbor(gx, gy, ex.direction);
const isOccupied = this.grid.isOccupied(leadingTo.x, leadingTo.y);
console.log(`[updateAvailableExits] Exit at (${gx}, ${gy}) dir ${ex.direction} -> leads to (${leadingTo.x}, ${leadingTo.y}) occupied: ${isOccupied}`);
if (!isOccupied) {
this.availableExits.push({
@@ -245,11 +236,11 @@ export class DungeonGenerator {
direction: ex.direction,
tileId: instance.id
});
console.log('[updateAvailableExits] ✓ Added exit');
}
}
console.log('[updateAvailableExits] Total available exits now:', this.availableExits.length);
// Remove exits that are now blocked or connected
this.availableExits = this.availableExits.filter(exit => {

View File

@@ -1,50 +0,0 @@
tryPlaceCard(card, targetExit) {
const requiredDirection = this.opposite(targetExit.direction);
const targetCell = this.neighbor(targetExit.x, targetExit.y, targetExit.direction);
const rotations = [DIRECTIONS.NORTH, DIRECTIONS.EAST, DIRECTIONS.SOUTH, DIRECTIONS.WEST];
// const shuffled = this.shuffle(rotations);
// 🔧 DEBUG: Force SOUTH rotation only
const forcedRotations = [DIRECTIONS.SOUTH];
console.log('⚠️ FORCED ROTATION TO SOUTH ONLY FOR TESTING');
let bestPlacement = null;
let maxConnections = -1;
for (const rotation of forcedRotations) {
const rotatedExits = this.rotateExits(card.exits, rotation);
const candidates = rotatedExits.filter(e => e.direction === requiredDirection);
for (const candidate of candidates) {
const anchorX = targetCell.x - candidate.x;
const anchorY = targetCell.y - candidate.y;
if (this.grid.canPlace(card, anchorX, anchorY, rotation)) {
let score = 0;
for (const exit of rotatedExits) {
const globalX = anchorX + exit.x;
const globalY = anchorY + exit.y;
const neighbor = this.neighbor(globalX, globalY, exit.direction);
if (this.grid.occupiedCells.has(`${neighbor.x},${neighbor.y}`)) {
score++;
}
}
if (score > maxConnections) {
maxConnections = score;
bestPlacement = { card, anchorX, anchorY, rotation };
}
}
}
}
if (bestPlacement) {
this.placeTileAt(bestPlacement.card, bestPlacement.anchorX, bestPlacement.anchorY, bestPlacement.rotation);
console.log(`✅ Best Placement Selected: Score ${maxConnections}`);
return true;
}
return false;
}

View File

@@ -67,7 +67,6 @@ export class GridSystem {
}
}
this.tiles.push(tileInstance);
console.log(`[Grid] Placed ${tileInstance.id} at ${anchorX},${anchorY} (Rot: ${tileInstance.rotation})`);
}
/**

View File

@@ -100,6 +100,11 @@ export const TILES = {
textures: ['/assets/images/dungeon1/tiles/L.png'],
variants: {
[DIRECTIONS.NORTH]: {
// Shape: Top and Left are solid.
// 1111 (Top-Most)
// 1111
// 1100
// 1100
width: 4, height: 4,
layout: [
[1, 1, 1, 1],
@@ -113,19 +118,30 @@ export const TILES = {
]
},
[DIRECTIONS.EAST]: {
// Rotated 90 CW: Top -> Right, Left -> Top.
// Shape: Top and Right are solid.
// 1111 (Top-Most)
// 1111
// 0011
// 0011
width: 4, height: 4,
layout: [
[1, 1, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 1],
[1, 1, 1, 1]
[1, 1, 1, 1],
[0, 0, 1, 1],
[0, 0, 1, 1]
],
exits: [
{ x: 0, y: 0, direction: DIRECTIONS.SOUTH }, { x: 1, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 2, y: 3, direction: DIRECTIONS.NORTH }, { x: 3, y: 3, direction: DIRECTIONS.NORTH }
{ x: 3, y: 0, direction: DIRECTIONS.SOUTH }, { x: 2, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 0, y: 3, direction: DIRECTIONS.WEST }, { x: 0, y: 2, direction: DIRECTIONS.WEST }
]
},
[DIRECTIONS.SOUTH]: {
// Rotated 180: Bottom and Right are solid.
// 0011
// 0011
// 1111
// 1111
width: 4, height: 4,
layout: [
[0, 0, 1, 1],
@@ -139,16 +155,21 @@ export const TILES = {
]
},
[DIRECTIONS.WEST]: {
// Rotated 270: Bottom and Left are solid.
// 1100
// 1100
// 1111
// 1111
width: 4, height: 4,
layout: [
[1, 1, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 1],
[1, 1, 1, 1],
[0, 0, 1, 1],
[0, 0, 1, 1]
[1, 1, 1, 1]
],
exits: [
{ x: 3, y: 2, direction: DIRECTIONS.EAST }, { x: 3, y: 3, direction: DIRECTIONS.EAST },
{ x: 0, y: 1, direction: DIRECTIONS.WEST }, { x: 0, y: 0, direction: DIRECTIONS.WEST }
{ x: 3, y: 1, direction: DIRECTIONS.EAST }, { x: 3, y: 0, direction: DIRECTIONS.EAST },
{ x: 0, y: 3, direction: DIRECTIONS.NORTH }, { x: 1, y: 3, direction: DIRECTIONS.NORTH }
]
}
}
@@ -164,6 +185,11 @@ export const TILES = {
textures: ['/assets/images/dungeon1/tiles/T.png'],
variants: {
[DIRECTIONS.NORTH]: {
// T Shape (Top bar)
// 111111
// 111111
// 001100
// 001100
width: 6, height: 4,
layout: [
[1, 1, 1, 1, 1, 1],
@@ -178,22 +204,36 @@ export const TILES = {
]
},
[DIRECTIONS.EAST]: {
// Rotated 90 CW: Bar is at Right (East)
// 0011
// 0011
// 1111
// 1111
// 0011
// 0011
width: 4, height: 6,
layout: [
[1, 1, 0, 0],
[1, 1, 0, 0],
[0, 0, 1, 1],
[0, 0, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 0, 0],
[1, 1, 0, 0]
[0, 0, 1, 1],
[0, 0, 1, 1]
],
exits: [
{ x: 2, y: 0, direction: DIRECTIONS.SOUTH }, { x: 3, y: 0, direction: DIRECTIONS.SOUTH },
// Stem points Left (West)
{ x: 0, y: 2, direction: DIRECTIONS.WEST }, { x: 0, y: 3, direction: DIRECTIONS.WEST },
// Bar Top/Bottom
{ x: 2, y: 5, direction: DIRECTIONS.NORTH }, { x: 3, y: 5, direction: DIRECTIONS.NORTH },
{ x: 0, y: 2, direction: DIRECTIONS.WEST }, { x: 0, y: 3, direction: DIRECTIONS.WEST }
{ x: 2, y: 0, direction: DIRECTIONS.SOUTH }, { x: 3, y: 0, direction: DIRECTIONS.SOUTH }
]
},
[DIRECTIONS.SOUTH]: {
// Rotated 180: Bar is Bottom
// 001100
// 001100
// 111111
// 111111
width: 6, height: 4,
layout: [
[0, 0, 1, 1, 0, 0],
@@ -208,17 +248,26 @@ export const TILES = {
]
},
[DIRECTIONS.WEST]: {
// Rotated 270: Bar is Left (West)
// 1100
// 1100
// 1111
// 1111
// 1100
// 1100
width: 4, height: 6,
layout: [
[0, 0, 1, 1],
[0, 0, 1, 1],
[1, 1, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 1],
[1, 1, 1, 1],
[0, 0, 1, 1],
[0, 0, 1, 1]
[1, 1, 0, 0],
[1, 1, 0, 0]
],
exits: [
// Stem points Right (East)
{ x: 3, y: 2, direction: DIRECTIONS.EAST }, { x: 3, y: 3, direction: DIRECTIONS.EAST },
// Bar Top/Bottom
{ x: 0, y: 5, direction: DIRECTIONS.NORTH }, { x: 1, y: 5, direction: DIRECTIONS.NORTH },
{ x: 0, y: 0, direction: DIRECTIONS.SOUTH }, { x: 1, y: 0, direction: DIRECTIONS.SOUTH }
]

View File

@@ -1,181 +0,0 @@
import { DIRECTIONS } from './Constants.js';
import { GridSystem } from './GridSystem.js';
import { DungeonDeck } from './DungeonDeck.js';
export class DungeonGenerator {
constructor() {
this.grid = new GridSystem();
this.deck = new DungeonDeck();
this.pendingExits = [];
this.placedTiles = [];
this.isComplete = false;
}
startDungeon(missionConfig) {
const objectiveId = missionConfig?.type === 'quest' ? 'room_objective' : 'room_dungeon';
this.deck.generateMissionDeck(objectiveId);
const firstCard = this.deck.draw();
if (!firstCard) {
console.error("❌ Deck empty!");
return;
}
// Place first tile with NORTH variant
this.placeTileAt(firstCard, 0, 0, DIRECTIONS.NORTH);
console.log(`🏰 Dungeon started with ${firstCard.name}`);
}
step() {
if (this.isComplete || this.pendingExits.length === 0) {
this.isComplete = true;
return false;
}
const card = this.deck.draw();
if (!card) {
this.isComplete = true;
return false;
}
const targetExit = this.pendingExits.shift();
const placed = this.tryPlaceCard(card, targetExit);
if (!placed) {
this.pendingExits.push(targetExit);
console.warn(`⚠️ Failed to place ${card.name} - returning exit to pending queue`);
}
return true;
}
tryPlaceCard(card, targetExit) {
console.log(`\n🃏 Trying to place card: ${card.name} (ID: ${card.id})`);
// Use standard opposite logic for Directions (String) since names don't change
const requiredDirection = this.opposite(targetExit.direction);
const targetCell = this.neighbor(targetExit.x, targetExit.y, targetExit.direction);
const rotations = [DIRECTIONS.NORTH, DIRECTIONS.EAST, DIRECTIONS.SOUTH, DIRECTIONS.WEST];
const shuffled = this.shuffle(rotations);
let bestPlacement = null;
let maxConnections = -1;
// Try ALL rotations
for (const rotation of shuffled) {
// 1. Get the pre-calculated VARIANT for this rotation
const variant = card.variants[rotation];
if (!variant) continue;
const variantExits = variant.exits;
// 2. Find candidate exits on this variant that face the required direction
// Note: variant.exits are ALREADY correctly oriented for this rotation
const candidates = variantExits.filter(e => e.direction === requiredDirection);
for (const candidate of candidates) {
// Determine absolute anchor position
// Since variant coords are all positive relative to (0,0), anchor calculation is simple subtraction
const anchorX = targetCell.x - candidate.x;
const anchorY = targetCell.y - candidate.y;
if (this.grid.canPlace(card, anchorX, anchorY, rotation)) {
// Start Score at 0
let score = 0;
// Calculate score
for (const exit of variantExits) {
const globalX = anchorX + exit.x;
const globalY = anchorY + exit.y;
const neighbor = this.neighbor(globalX, globalY, exit.direction);
const neighborKey = `${neighbor.x},${neighbor.y}`;
if (this.grid.occupiedCells.has(neighborKey)) {
score++;
}
}
// Prefer higher score.
if (score > maxConnections) {
maxConnections = score;
bestPlacement = { card, anchorX, anchorY, rotation };
}
}
}
}
if (bestPlacement) {
this.placeTileAt(bestPlacement.card, bestPlacement.anchorX, bestPlacement.anchorY, bestPlacement.rotation);
console.log(`✅ Placed ${card.name} at (${bestPlacement.anchorX}, ${bestPlacement.anchorY}) Rot:${bestPlacement.rotation} Score:${maxConnections}`);
return true;
}
console.log(`❌ Could not place ${card.name} in any rotation`);
return false;
}
placeTileAt(tileDef, x, y, rotation) {
const instance = {
id: `tile_${this.placedTiles.length}`,
defId: tileDef.id,
x, y, rotation
};
this.grid.placeTile(instance, tileDef); // Grid system now handles looking up the variant
this.placedTiles.push(instance);
// Add exits from the variant
const variant = tileDef.variants[rotation];
for (const exit of variant.exits) {
const globalX = x + exit.x;
const globalY = y + exit.y;
const neighborCell = this.neighbor(globalX, globalY, exit.direction);
const key = `${neighborCell.x},${neighborCell.y}`;
// Only add exit if it leads to empty space
if (!this.grid.occupiedCells.has(key)) {
this.pendingExits.push({
x: globalX,
y: globalY,
direction: exit.direction
});
}
}
// Cleanup pending exits that are now connected
this.pendingExits = this.pendingExits.filter(ex => {
const n = this.neighbor(ex.x, ex.y, ex.direction);
return !this.grid.occupiedCells.has(`${n.x},${n.y}`);
});
}
neighbor(x, y, direction) {
switch (direction) {
case DIRECTIONS.NORTH: return { x, y: y + 1 };
case DIRECTIONS.SOUTH: return { x, y: y - 1 };
case DIRECTIONS.EAST: return { x: x + 1, y };
case DIRECTIONS.WEST: return { x: x - 1, y };
}
}
opposite(direction) {
switch (direction) {
case DIRECTIONS.NORTH: return DIRECTIONS.SOUTH;
case DIRECTIONS.SOUTH: return DIRECTIONS.NORTH;
case DIRECTIONS.EAST: return DIRECTIONS.WEST;
case DIRECTIONS.WEST: return DIRECTIONS.EAST;
}
}
shuffle(arr) {
const copy = [...arr];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
}
}

View File

@@ -1,134 +0,0 @@
import { DIRECTIONS } from './Constants.js';
export class GridSystem {
/**
* The GridSystem maintains the "Source of Truth" for the dungeon layout.
* It knows which cells are occupied and by whom.
* Dependencies: Constants.js (DIRECTIONS)
*/
constructor() {
// We use a Map for O(1) lookups.
// Key: "x,y" (String) -> Value: "tileId" (String)
this.occupiedCells = new Map();
// We also keep a list of placed tile objects for easier iteration if needed later.
this.tiles = [];
}
/**
* Checks if a tile can be placed at the given coordinates with the given rotation.
* Needs: The Tile Definition (to know size), the target X,Y, and desired Rotation.
*/
canPlace(tileDef, startX, startY, rotation) {
// 1. Calculate the real-world coordinates of every single cell this tile would occupy.
const cells = this.getGlobalCells(tileDef, startX, startY, rotation);
// 2. Check each cell against our Map of occupied spots.
for (const cell of cells) {
const key = `${cell.x},${cell.y}`;
if (this.occupiedCells.has(key)) {
return false; // COLLISION! Spot already taken.
}
}
return true; // All clear.
}
/**
* Officially registers a tile onto the board.
* Should only be called AFTER canPlace returns true.
*/
placeTile(tileInstance, tileDef) {
const cells = this.getGlobalCells(tileDef, tileInstance.x, tileInstance.y, tileInstance.rotation);
// Record every cell in our Map
for (const cell of cells) {
const key = `${cell.x},${cell.y}`;
this.occupiedCells.set(key, tileInstance.id);
}
// Store the instance
this.tiles.push(tileInstance);
console.log(`Placed tile ${tileInstance.id} at ${tileInstance.x},${tileInstance.y}`);
}
/**
* THE CORRECTED MAGIC MATH FUNCTION.
* Now uses Pre-Calculated Variants from TileDefinitions.
* NO DYNAMIC ROTATION MATH performed here.
* All variants are treated as "North" oriented blocks.
*/
getGlobalCells(tileDef, startX, startY, rotation) {
const cells = [];
// 1. Retrieve the specific variant for this rotation
const variant = tileDef.variants[rotation];
if (!variant) {
console.error(`Missing variant for rotation ${rotation} in tile ${tileDef.id}`);
return cells;
}
const layout = variant.layout;
if (!layout) {
console.error("Variant missing layout. ID:", tileDef?.id, rotation);
return cells;
}
const numberOfRows = layout.length;
// Iterate through matrix rows
for (let row = 0; row < numberOfRows; row++) {
const rowData = layout[row];
const numberOfCols = rowData.length;
for (let col = 0; col < numberOfCols; col++) {
const cellValue = rowData[col];
// Skip empty cells (0)
if (cellValue === 0) continue;
// Map Matrix (Row, Col) to Local Grid (lx, ly)
// Matrix Row 0 is the "Top" (Max Y) relative to the tile's origin
// Matrix Row (Rows-1) is the "Bottom" (Y=0)
// lx grows right (col)
// ly grows up (rows reversed)
const lx = col;
const ly = (numberOfRows - 1) - row;
// SIMPLIFIED LOGIC:
// Since the variant layout is ALREADY rotated, we always just ADD the offsets.
// We treat every variant as if it's placed "North-wise" at the anchor point.
const gx = startX + lx;
const gy = startY + ly;
cells.push({ x: gx, y: gy, value: cellValue });
}
}
return cells;
}
/**
* Transforms a local point (like an exit definition) to Global Coordinates.
* Simplified: Just adds offsets because variants carry pre-rotated coords.
*/
getGlobalPoint(localX, localY, tileInstance) {
const startX = tileInstance.x;
const startY = tileInstance.y;
// Simple translation. Rotation is handled by the variant properties upstream.
return {
x: startX + localX,
y: startY + localY
};
}
/**
* DEPRECATED / LEGACY
* Kept just in case, but shouldn't be needed with explicit variants.
*/
getRotatedDirection(originalDirection, tileRotation) {
// With explicit variants, the exit.direction is ALREADY the final global direction.
return originalDirection;
}
}

View File

@@ -18,7 +18,7 @@ export class GameEngine {
}
startMission(missionConfig) {
console.log('[GameEngine] Starting mission:', missionConfig.name);
this.dungeon.startDungeon(missionConfig);
// Create player at center of first tile
@@ -40,7 +40,7 @@ export class GameEngine {
this.onEntityUpdate(this.player);
}
console.log('[GameEngine] Player created at', this.player.x, this.player.y);
}
onCellClick(x, y) {
@@ -50,7 +50,7 @@ export class GameEngine {
if (this.onEntitySelect) {
this.onEntitySelect(this.player.id, true);
}
console.log('[GameEngine] Player selected');
return;
}
@@ -59,7 +59,7 @@ export class GameEngine {
if (this.canMoveTo(x, y)) {
this.movePlayer(x, y);
} else {
console.log('[GameEngine] Cannot move there');
}
}
}
@@ -86,7 +86,7 @@ export class GameEngine {
this.onEntitySelect(this.player.id, false);
}
console.log('[GameEngine] Player moved to', x, y);
}
isPlayerAdjacentToDoor(doorExit) {

View File

@@ -1,196 +0,0 @@
import { DungeonGenerator } from '../dungeon/DungeonGenerator.js';
import { TurnManager } from './TurnManager.js';
import { GAME_PHASES } from './GameConstants.js';
import { Entity } from './Entity.js';
export class GameEngine {
constructor() {
this.dungeon = new DungeonGenerator();
this.turnManager = new TurnManager();
this.missionConfig = null;
this.player = null;
this.selectedPath = [];
this.selectedEntityId = null;
// Simple event callbacks (external systems can assign these)
this.onPathChange = null; // (path) => {}
this.onEntityUpdate = null; // (entity) => {}
this.onEntityMove = null; // (entity, path) => {}
this.onEntitySelect = null; // (entityId, isSelected) => {}
this.setupEventHooks();
}
setupEventHooks() {
this.turnManager.on('phase_changed', (phase) => this.handlePhaseChange(phase));
}
startMission(missionConfig) {
this.missionConfig = missionConfig;
console.log(`[GameEngine] Starting mission: ${missionConfig.name}`);
// 1. Initialize Dungeon (Places the first room)
this.dungeon.startDungeon(missionConfig);
// 2. Initialize Player
// Find a valid starting spot (first occupied cell)
const startingSpot = this.dungeon.grid.occupiedCells.keys().next().value;
const [startX, startY] = startingSpot ? startingSpot.split(',').map(Number) : [0, 0];
console.log(`[GameEngine] Spawning player at valid spot: ${startX}, ${startY}`);
this.player = new Entity('p1', 'Barbaro', 'hero', startX, startY, '/assets/images/dungeon1/standees/barbaro.png');
if (this.onEntityUpdate) this.onEntityUpdate(this.player);
// 3. Start the Turn Sequence
this.turnManager.startGame();
}
update(deltaTime) {
// Continuous logic
}
handlePhaseChange(phase) {
console.log(`[GameEngine] Phase Changed to: ${phase}`);
}
// --- Interaction ---
onCellClick(x, y) {
if (this.turnManager.currentPhase !== GAME_PHASES.HERO) return;
console.log(`Cell Click: ${x},${y}`);
// 1. Selector Check (Click on Player?)
if (this.player.x === x && this.player.y === y) {
if (this.selectedEntityId === this.player.id) {
// Deselect
this.selectedEntityId = null;
console.log("Player Deselected");
if (this.onEntitySelect) this.onEntitySelect(this.player.id, false);
// Clear path too
this.selectedPath = [];
this._notifyPath();
} else {
// Select
this.selectedEntityId = this.player.id;
console.log("Player Selected");
if (this.onEntitySelect) this.onEntitySelect(this.player.id, true);
}
return;
}
// If nothing selected, ignore floor clicks
if (!this.selectedEntityId) return;
// 2. Check if valid Floor (Occupied Cell)
if (!this.dungeon.grid.occupiedCells.has(`${x},${y}`)) {
console.log("Invalid cell: Void");
return;
}
// 3. Logic: Path Building (Only if Selected)
// A. If clicking on last selected -> Deselect (Remove last step)
if (this.selectedPath.length > 0) {
const last = this.selectedPath[this.selectedPath.length - 1];
if (last.x === x && last.y === y) {
this.selectedPath.pop();
this._notifyPath();
return;
}
}
// B. Determine Previous Point (Player or Last Path Node)
let prevX = this.player.x;
let prevY = this.player.y;
if (this.selectedPath.length > 0) {
const last = this.selectedPath[this.selectedPath.length - 1];
prevX = last.x;
prevY = last.y;
}
// Note: Manhattan distance 1 = Adjacency (No diagonals)
const dist = Math.abs(x - prevX) + Math.abs(y - prevY);
if (dist === 1) {
// Check Path Length Limit (Speed)
if (this.selectedPath.length < this.player.stats.move) {
this.selectedPath.push({ x, y });
this._notifyPath();
} else {
console.log("Max movement reached");
}
} else {
// Restart path if clicking adjacent to player
const distFromPlayer = Math.abs(x - this.player.x) + Math.abs(y - this.player.y);
if (distFromPlayer === 1) {
this.selectedPath = [{ x, y }];
this._notifyPath();
}
}
}
onCellRightClick(x, y) {
if (this.turnManager.currentPhase !== GAME_PHASES.HERO) return;
if (!this.selectedEntityId) return; // Must satisfy selection rule
// Must be clicking the last tile of the path to confirm
if (this.selectedPath.length === 0) return;
const last = this.selectedPath[this.selectedPath.length - 1];
if (last.x === x && last.y === y) {
console.log("Confirming Move...");
this.confirmMove();
}
}
confirmMove() {
if (this.selectedPath.length === 0) return;
const target = this.selectedPath[this.selectedPath.length - 1];
const pathCopy = [...this.selectedPath];
// 1. Trigger Animation Sequence
if (this.onEntityMove) this.onEntityMove(this.player, pathCopy);
// 2. Update Logical Position
this.player.setPosition(target.x, target.y);
// 3. Cleanup
this.selectedPath = [];
this._notifyPath();
// Note: Exploration is now manual via door interaction
}
_notifyPath() {
if (this.onPathChange) this.onPathChange(this.selectedPath);
}
exploreExit(exitCell) {
console.log('[GameEngine] Exploring exit:', exitCell);
// Find this exit in pendingExits
const exit = this.dungeon.pendingExits.find(ex => ex.x === exitCell.x && ex.y === exitCell.y);
if (exit) {
// Prioritize this exit
const idx = this.dungeon.pendingExits.indexOf(exit);
if (idx > -1) {
this.dungeon.pendingExits.splice(idx, 1);
this.dungeon.pendingExits.unshift(exit);
}
// Trigger exploration
this.turnManager.triggerExploration();
this.dungeon.step();
this.turnManager.setPhase(GAME_PHASES.HERO);
} else {
console.warn('[GameEngine] Exit not found in pendingExits');
}
}
}

View File

@@ -4,7 +4,7 @@ import { CameraManager } from './view/CameraManager.js';
import { UIManager } from './view/UIManager.js';
import { MissionConfig, MISSION_TYPES } from './engine/dungeon/MissionConfig.js';
console.log("🏗️ Warhammer Quest - Manual Dungeon Construction");
// 1. Setup Mission
const mission = new MissionConfig({
@@ -65,7 +65,7 @@ renderer.onHeroFinishedMove = (x, y) => {
// 5. Connect Generator State to UI
generator.onStateChange = (state) => {
console.log(`[Main] State: ${state}`);
if (state === 'PLACING_TILE') {
ui.showPlacementControls(true);
@@ -87,7 +87,7 @@ generator.onPlacementUpdate = (preview) => {
const handleClick = (x, y, doorMesh) => {
// PRIORITY 1: Tile Placement Mode - ignore all clicks
if (generator.state === 'PLACING_TILE') {
console.log('[Main] Use placement controls to place tile');
return;
}
@@ -96,7 +96,7 @@ const handleClick = (x, y, doorMesh) => {
const doorExit = doorMesh.userData.cells[0];
if (game.isPlayerAdjacentToDoor(doorExit)) {
console.log('[Main] 🚪 Opening door and drawing tile...');
// Open door visually
renderer.openDoor(doorMesh);
@@ -109,7 +109,7 @@ const handleClick = (x, y, doorMesh) => {
console.error('[Main] Door missing exitData');
}
} else {
console.log('[Main] ⚠️ Move adjacent to the door first');
}
return;
}
@@ -127,7 +127,7 @@ renderer.setupInteraction(
);
// 7. Start
console.log("--- Starting Game ---");
game.startMission(mission);
// 8. Render Loop
@@ -140,4 +140,4 @@ const animate = (time) => {
};
animate(0);
console.log("✅ Ready - Move barbarian next to a door and click it");

View File

@@ -1,172 +0,0 @@
import { GameEngine } from './engine/game/GameEngine.js';
import { GameRenderer } from './view/GameRenderer.js';
import { CameraManager } from './view/CameraManager.js';
import { UIManager } from './view/UIManager.js';
import { DoorModal } from './view/DoorModal.js';
import { MissionConfig, MISSION_TYPES } from './engine/dungeon/MissionConfig.js';
console.log("Initializing Warhammer Quest Engine... SYSTEM: MANUAL_PLACEMENT_V2");
window.TEXTURE_DEBUG = true;
// 1. Setup Mission
const mission = new MissionConfig({
id: 'mission_1',
name: 'The First Dive',
type: MISSION_TYPES.ESCAPE,
minTiles: 6
});
// 2. Initialize Core Systems
const renderer = new GameRenderer('app');
const cameraManager = new CameraManager(renderer);
const game = new GameEngine();
// 3. Initialize UI
const ui = new UIManager(cameraManager, game);
const doorModal = new DoorModal();
// Global Access for Debugging
window.GAME = game;
window.RENDERER = renderer;
// 4. Bridge Logic & View
const generator = game.dungeon;
const originalPlaceTile = generator.grid.placeTile.bind(generator.grid);
// Override placeTile to trigger rendering
generator.grid.placeTile = (instance, variant, def) => {
originalPlaceTile(instance, variant);
const cells = generator.grid.calculateCells(variant, instance.x, instance.y);
renderer.addTile(cells, def.type, def, instance);
// Update exits visualization
setTimeout(() => {
renderer.renderExits(generator.availableExits);
}, 50);
};
// 5. Connect Generator State Changes to UI
generator.onStateChange = (state) => {
console.log(`[Main] Generator state: ${state}`);
if (state === 'PLACING_TILE') {
ui.showPlacementControls(true);
} else {
ui.showPlacementControls(false);
}
if (state === 'WAITING_DOOR') {
// Enable door selection mode
renderer.enableDoorSelection(true);
} else {
renderer.enableDoorSelection(false);
}
};
// 6. Connect Placement Updates to Renderer
generator.onPlacementUpdate = (preview) => {
if (preview) {
renderer.showPlacementPreview(preview);
ui.updatePlacementStatus(preview.isValid);
} else {
renderer.hidePlacementPreview();
}
};
// 7. Handle Door Selection
renderer.onDoorSelected = (exitPoint) => {
console.log('[Main] Door selected:', exitPoint);
generator.selectDoor(exitPoint);
};
// 8. Bridge Game Interactions (Player movement, etc.)
game.onEntityUpdate = (entity) => {
renderer.addEntity(entity);
renderer.updateEntityPosition(entity);
if (entity.id === 'p1' && !entity._centered) {
cameraManager.centerOn(entity.x, entity.y);
entity._centered = true;
}
};
game.onEntityMove = (entity, path) => {
renderer.moveEntityAlongPath(entity, path);
};
game.onEntitySelect = (entityId, isSelected) => {
renderer.toggleEntitySelection(entityId, isSelected);
};
renderer.onHeroFinishedMove = (x, y) => {
cameraManager.centerOn(x, y);
};
game.onPathChange = (path) => {
renderer.highlightCells(path);
};
// 9. Custom click handler
const handleCellClick = async (x, y, doorMesh) => {
// PRIORITY 1: Check if we're in door selection mode (dungeon building)
if (generator.state === 'WAITING_DOOR') {
if (doorMesh && doorMesh.userData.isExit) {
// This is an exit door that can be selected for expansion
const exitData = doorMesh.userData.exitData;
if (exitData) {
console.log('[Main] Door selected for expansion:', exitData);
generator.selectDoor(exitData);
}
return; // Don't process any other click logic
}
// If not clicking on a door in WAITING_DOOR mode, ignore the click
console.log('[Main] Click ignored - waiting for door selection');
return;
}
// PRIORITY 2: Normal door interaction (during gameplay with player)
if (doorMesh && doorMesh.userData.isDoor && !doorMesh.userData.isOpen) {
const player = game.player;
if (!player) {
console.log('[Main] Player not found');
return;
}
if (renderer.isPlayerAdjacentToDoor(player.x, player.y, doorMesh)) {
const confirmed = await doorModal.show('¿Quieres abrir la puerta?');
if (confirmed) {
renderer.openDoor(doorMesh);
const exitCell = doorMesh.userData.cells[0];
console.log('[Main] Opening door at exit:', exitCell);
game.exploreExit(exitCell);
}
} else {
console.log('[Main] Player is not adjacent to the door. Move closer first.');
}
} else if (x !== null && y !== null) {
game.onCellClick(x, y);
}
};
renderer.setupInteraction(
() => cameraManager.getCamera(),
handleCellClick,
(x, y) => game.onCellRightClick(x, y)
);
console.log("--- Starting Game Session ---");
game.startMission(mission);
// 10. Render Loop
const animate = (time) => {
requestAnimationFrame(animate);
game.update(time);
cameraManager.update(time);
renderer.updateAnimations(time);
renderer.render(cameraManager.getCamera());
};
animate(0);

View File

@@ -158,7 +158,7 @@ export class GameRenderer {
addEntity(entity) {
if (this.entities.has(entity.id)) return;
console.log(`[GameRenderer] Adding entity ${entity.name}`);
// Standee: Larger Size (+30%)
// Old: 0.8 x 1.2 -> New: 1.04 x 1.56
const w = 1.04;
@@ -296,11 +296,11 @@ export class GameRenderer {
const newExits = exits.filter(ex => !existingDoorCells.has(`${ex.x},${ex.y}`));
if (newExits.length === 0) {
console.log('[renderExits] No new doors to render');
return;
}
console.log(`[renderExits] Rendering ${newExits.length} new door cells`);
// Set flag for this render
this._pendingExitRender = true;
@@ -446,11 +446,11 @@ export class GameRenderer {
getTexture(path, onLoad) {
if (!this.textureCache.has(path)) {
console.log(`[TextureLoader] Starting load for: ${path}`);
const tex = this.textureLoader.load(
path,
(texture) => {
console.log(`[TextureLoader] ✓ Successfully loaded: ${path}`);
texture.needsUpdate = true;
if (onLoad) onLoad(texture);
},
@@ -478,7 +478,7 @@ export class GameRenderer {
// tileDef: The definition object (has textures, dimensions)
// tileInstance: The instance object (has x, y, rotation, id)
console.log(`[GameRenderer] Rendering Tile [${type}] ID: ${tileDef?.id} at (${tileInstance?.x}, ${tileInstance?.y}) Rot: ${tileInstance?.rotation}`);
// Draw Texture Plane (The Image) - WAIT FOR TEXTURE TO LOAD
if (tileDef && tileInstance && tileDef.textures && tileDef.textures.length > 0) {
@@ -508,8 +508,7 @@ export class GameRenderer {
const cx = tileInstance.x + (rotWidth - 1) / 2;
const cy = tileInstance.y + (rotHeight - 1) / 2;
console.log(`[GameRenderer] Dimensions (Rotated): ${rotWidth}x${rotHeight}`);
console.log(`[GameRenderer] Calculated Center: (${cx}, ${cy})`);
// 3. Use BASE dimensions from NORTH variant for the Plane
// (Since we are rotating the plane itself, we start with the un-rotated image size)
@@ -549,7 +548,7 @@ export class GameRenderer {
plane.receiveShadow = true;
this.scene.add(plane);
console.log(`[GameRenderer] ✓ Tile plane added at (${cx}, 0.01, ${-cy})`);
});
} else {
console.warn(`[GameRenderer] details missing for texture render. def: ${!!tileDef}, inst: ${!!tileInstance}`);
@@ -565,7 +564,7 @@ export class GameRenderer {
doorMesh.material.map = texture;
doorMesh.material.needsUpdate = true;
doorMesh.userData.isOpen = true;
console.log('[GameRenderer] Door opened');
});
}
@@ -721,7 +720,7 @@ export class GameRenderer {
const r = rotMap[rotation] !== undefined ? rotMap[rotation] : 0;
floatingTile.rotation.z = -r * (Math.PI / 2);
console.log(`[Preview] Rotation: ${rotation}, Center: (${cx}, ${cy})`);
floatingTile.position.set(cx, 3, -cy);
this.previewGroup.add(floatingTile);