3 Commits

Author SHA1 Message Date
6fe693c2fc Merge pull request 'advanced_mapping' (#1) from advanced_mapping into master
Reviewed-on: #1
2025-12-29 01:12:12 +00:00
c8cc35772f feat: Implement advanced tile mapping system with abstract deck
- Created TileDefinitions.js with centralized tile definitions
- Implemented abstract deck system (8 rooms 4x4, 4 rooms 4x6, 12 corridors, 10 L-shapes, 8 T-junctions)
- Added connection validation (type compatibility, exit direction, walkability alignment)
- Implemented corridor orientation filtering (EW/NS matching)
- Added exhaustive L/T variant selection with random choice
- Updated corridor definitions with EW and NS orientations
- Fixed ASSETS.tiles references throughout main.js
- Known issue: L/T offset alignment needs further debugging
2025-12-29 02:09:34 +01:00
83dc2b0234 Feat: Implement Event System, Exploration Phase, and Collision Detection 2025-12-28 22:38:45 +01:00
25 changed files with 1396 additions and 239 deletions

View File

@@ -2,6 +2,47 @@
Este documento sirve para llevar un control diario del desarrollo, decisiones técnicas y nuevas funcionalidades implementadas en el proyecto.
## [2025-12-29] - Sistema Avanzado de Mapeo de Tiles
### Funcionalidades Implementadas
- **TileDefinitions.js:** Nuevo módulo centralizado con definiciones de todas las tiles (rooms, corridors, L-shapes, T-junctions).
- Cada tile incluye: dimensiones, tipo, imagen, matriz de walkability, y exits.
- Matriz de walkability: 0 = no pisable, 1-8 = pisable con capa/altura, 9 = escaleras.
- **Sistema de Deck Abstracto:**
- El deck ahora contiene tipos abstractos (e.g., 'L', 'corridor') en lugar de tiles específicas.
- Composición: 8 rooms 4x4, 4 rooms 4x6, 12 corridors, 10 L-shapes, 8 T-junctions.
- Cuando se dibuja un tipo, el sistema selecciona aleatoriamente entre las variantes que encajan.
- **Validación de Conexiones:**
- `canConnectTiles()`: Verifica compatibilidad de tipos, dirección de salidas, y alineación de walkability.
- Reglas de conexión: Rooms ↔ Rooms/Corridors, Corridors ↔ Rooms/Corridors/L/T, L/T ↔ Corridors.
- Validación de dirección: Si sales por N, la nueva tile debe tener salida S.
- **Alineación de Walkability:**
- `validateWalkabilityAlignment()`: Maneja tiles de diferentes tamaños (corridor 2x6 vs room 4x4).
- Prueba offset 0 primero, luego offset 2 (ancho del corridor) si es necesario.
- Sistema de offset para desplazar L-shapes y T-junctions y alinear áreas pisables.
- **Filtrado de Orientación:**
- Corridors se filtran por orientación: puertas E/W requieren corridors EW, puertas N/S requieren corridors NS.
- Selección exhaustiva: Cuando se dibuja una L o T, se prueban todas las variantes antes de descartar.
### Cambios Técnicos
- Modificado `DungeonDecks.js` para usar sistema de deck abstracto.
- Actualizado `exploreRoom()` en `main.js` para trabajar con tipos abstractos y seleccionar variantes concretas.
- Nuevas funciones: `validateWalkabilityAlignment()`, `canConnectTiles()`, `getEdgeCells()`, `shouldPlaceDoor()`.
- Actualizado `renderRoom()` para usar `room.tileDef` en lugar de `ASSETS.tiles`.
### Problemas Conocidos
- **Offset de L/T:** La alineación de L-shapes y T-junctions todavía presenta desplazamientos incorrectos en algunos casos.
- **Frecuencia de L/T:** Aunque se aumentó la cantidad en el deck, las L y T solo aparecen cuando se conectan desde corridors, limitando su frecuencia.
### Próximos Pasos
- Depurar y corregir el cálculo del offset para L-shapes y T-junctions.
- Revisar la lógica de aplicación del offset según la dirección de conexión (N/S vs E/W).
- Considerar ajustar las reglas de conexión para permitir más variedad en la generación.
## [2025-12-28] - Fase 1: Arquitectura Híbrida y Servidor
### Infraestructura

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1013 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

141
src/dungeon/DungeonDecks.js Normal file
View File

@@ -0,0 +1,141 @@
import { ROOMS, CORRIDORS, L_SHAPES, T_JUNCTIONS } from './TileDefinitions.js';
/**
* DungeonDeck - Manages the deck of dungeon tiles
*
* New approach: Deck contains abstract tile types, not specific tiles
* When a type is drawn, we select a fitting variant from available options
*/
export class DungeonDeck {
constructor() {
this.deck = [];
this.discardPile = [];
this.shuffleDeck();
}
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' },
// 4 rooms 4x6 (abstract)
{ 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 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 T-junctions (abstract) - increased from 4
{ type: 'T' },
{ type: 'T' },
{ type: 'T' },
{ type: 'T' },
{ type: 'T' },
{ type: 'T' },
{ type: 'T' },
{ type: 'T' }
];
// Fisher-Yates shuffle
for (let i = this.deck.length - 1; i > 0; i--) {
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");
}
drawCard() {
if (this.deck.length === 0) {
console.warn("Deck empty! Reshuffling discards...");
if (this.discardPile.length === 0) {
console.error("No cards left!");
return null;
}
this.deck = [...this.discardPile];
this.discardPile = [];
this.shuffleDeck();
}
const card = this.deck.pop();
this.discardPile.push(card);
return card;
}
/**
* Draw a compatible abstract tile type
* @param {string} originTileType - Type of the origin tile ('room', 'corridor', 'L', 'T')
* @param {number} maxAttempts - Maximum number of cards to try
* @returns {object|null} - Abstract tile card or null if none found
*/
drawCompatibleCard(originTileType, maxAttempts = 10) {
const validTypes = this.getCompatibleTypes(originTileType);
for (let i = 0; i < maxAttempts && this.deck.length > 0; i++) {
const card = this.drawCard();
// Check if this abstract type is compatible
if (validTypes.includes(card.type)) {
return card;
}
// Put incompatible card back at the bottom of the deck
this.deck.unshift(card);
this.discardPile.pop();
}
console.warn(`Could not find compatible tile for ${originTileType} after ${maxAttempts} attempts`);
return null;
}
/**
* Get compatible tile types for a given origin type
* @param {string} originType - Type of the origin tile
* @returns {string[]} - Array of compatible abstract tile types
*/
getCompatibleTypes(originType) {
const connectionRules = {
'room': ['room_4x4', 'room_4x6', 'corridor'],
'corridor': ['room_4x4', 'room_4x6', 'corridor', 'L', 'T'],
'L': ['corridor'],
'T': ['corridor']
};
return connectionRules[originType] || [];
}
}
export const dungeonDeck = new DungeonDeck();

41
src/dungeon/EventDeck.js Normal file
View File

@@ -0,0 +1,41 @@
export const EVENTS = [
{
id: 'ev_nothing',
title: 'Silencio',
description: 'La mazmorra está en calma... sospechosamente tranquila.',
type: 'NADA'
},
{
id: 'ev_monster_1',
title: '¡Emboscada!',
description: '¡Monstruos surgen de las sombras!',
type: 'MONSTRUO',
count: 2
},
{
id: 'ev_trap_1',
title: 'Trampa de Pinchos',
description: 'Un click resuena bajo tus pies. ¡Pinchos surgen del suelo!',
type: 'TRAMPA',
damage: 1
},
{
id: 'ev_wind',
title: 'Viento Helado',
description: 'Una corriente de aire apaga vuestras antorchas.',
type: 'PELIGRO'
}
];
export class EventDeck {
constructor() {
}
drawCard() {
// Simple random draw for now
const randIndex = Math.floor(Math.random() * EVENTS.length);
return EVENTS[randIndex];
}
}
export const eventDeck = new EventDeck();

View File

@@ -0,0 +1,431 @@
/**
* TileDefinitions.js
*
* Defines all dungeon tiles with their properties:
* - Dimensions (width x height in cells)
* - Walkability matrix (0 = not walkable, 1-8 = walkable layer/height, 9 = stairs)
* - Tile type (room, corridor, L, T)
* - Exit points
* - Image path
*/
// ============================================================================
// ROOMS (4x4 and 4x6)
// ============================================================================
const ROOM_4X4_NORMAL = {
id: 'room_4x4_normal',
tileType: 'room',
width: 4,
height: 4,
image: '/assets/images/dungeons/room_4x4_normal.png',
walkability: [
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]
],
exits: ['N', 'S', 'E', 'W']
};
const ROOM_4X4_CIRCLE = {
id: 'room_4x4_circle',
tileType: 'room',
width: 4,
height: 4,
image: '/assets/images/dungeons/room_4x4_circle.png',
walkability: [
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]
],
exits: ['N', 'S', 'E', 'W']
};
const ROOM_4X4_SKELETON = {
id: 'room_4x4_skeleton',
tileType: 'room',
width: 4,
height: 4,
image: '/assets/images/dungeons/room_4x4_squeleton.png',
walkability: [
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]
],
exits: ['N', 'S', 'E', 'W']
};
const ROOM_4X6_ALTAR = {
id: 'room_4x6_altar',
tileType: 'room',
width: 4,
height: 6,
image: '/assets/images/dungeons/room_4x6_altar.png',
walkability: [
[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: ['N', 'S', 'E', 'W']
};
const ROOM_4X6_TOMB = {
id: 'room_4x6_tomb',
tileType: 'room',
width: 4,
height: 6,
image: '/assets/images/dungeons/room_4x6_tomb.png',
walkability: [
[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: ['N', 'S', 'E', 'W']
};
// Example room with stairs (2 levels)
const ROOM_4X6_STAIRS = {
id: 'room_4x6_stairs',
tileType: 'room',
width: 4,
height: 6,
image: '/assets/images/dungeons/room_4x6_altar.png', // Using altar image as placeholder
walkability: [
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 9, 9, 1], // Stairs connecting level 1 and 2
[1, 2, 2, 1],
[2, 2, 2, 2],
[2, 2, 2, 2]
],
exits: ['N', 'S']
};
// ============================================================================
// L-SHAPES (4x4 with 2-tile rows)
// ============================================================================
const L_NE = {
id: 'L_NE',
tileType: 'L',
width: 4,
height: 4,
image: '/assets/images/dungeons/L_NE.png',
walkability: [
[1, 1, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 1],
[1, 1, 1, 1]
],
exits: ['N', 'E']
};
const L_SE = {
id: 'L_SE',
tileType: 'L',
width: 4,
height: 4,
image: '/assets/images/dungeons/L_SE.png',
walkability: [
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 0, 0],
[1, 1, 0, 0]
],
exits: ['S', 'E']
};
const L_WS = {
id: 'L_WS',
tileType: 'L',
width: 4,
height: 4,
image: '/assets/images/dungeons/L_WS.png',
walkability: [
[1, 1, 1, 1],
[1, 1, 1, 1],
[0, 0, 1, 1],
[0, 0, 1, 1]
],
exits: ['W', 'S']
};
const L_WN = {
id: 'L_WN',
tileType: 'L',
width: 4,
height: 4,
image: '/assets/images/dungeons/L_WN.png',
walkability: [
[0, 0, 1, 1],
[0, 0, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]
],
exits: ['W', 'N']
};
// ============================================================================
// T-JUNCTIONS (4x6 or 6x4 with 2-tile rows)
// ============================================================================
const T_NES = {
id: 'T_NES',
tileType: 'T',
width: 4,
height: 6,
image: '/assets/images/dungeons/T_NES.png',
walkability: [
[1, 1, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 0, 0],
[1, 1, 0, 0]
],
exits: ['N', 'E', 'S']
};
const T_WNE = {
id: 'T_WNE',
tileType: 'T',
width: 6,
height: 4,
image: '/assets/images/dungeons/T_WNE.png',
walkability: [
[0, 0, 1, 1, 0, 0],
[0, 0, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1]
],
exits: ['W', 'N', 'E']
};
const T_WSE = {
id: 'T_WSE',
tileType: 'T',
width: 6,
height: 4,
image: '/assets/images/dungeons/T_WSE.png',
walkability: [
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1],
[0, 0, 1, 1, 0, 0],
[0, 0, 1, 1, 0, 0]
],
exits: ['W', 'S', 'E']
};
const T_WNS = {
id: 'T_WNS',
tileType: 'T',
width: 4,
height: 6,
image: '/assets/images/dungeons/T_WNS.png',
walkability: [
[0, 0, 1, 1],
[0, 0, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[0, 0, 1, 1],
[0, 0, 1, 1]
],
exits: ['W', 'N', 'S']
};
// ============================================================================
// CORRIDORS (2x6 or 6x2 with 2-tile rows)
// ============================================================================
// Corridor 1 - East-West (horizontal)
const CORRIDOR1_EW = {
id: 'corridor1_EW',
tileType: 'corridor',
width: 6,
height: 2,
image: '/assets/images/dungeons/corridor1_EW.png',
walkability: [
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1]
],
exits: ['E', 'W']
};
// Corridor 1 - North-South (vertical)
const CORRIDOR1_NS = {
id: 'corridor1_NS',
tileType: 'corridor',
width: 2,
height: 6,
image: '/assets/images/dungeons/corridor1_NS.png',
walkability: [
[1, 1],
[1, 1],
[1, 1],
[1, 1],
[1, 1],
[1, 1]
],
exits: ['N', 'S']
};
// Corridor 2 - East-West (horizontal)
const CORRIDOR2_EW = {
id: 'corridor2_EW',
tileType: 'corridor',
width: 6,
height: 2,
image: '/assets/images/dungeons/corridor2_EW.png',
walkability: [
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1]
],
exits: ['E', 'W']
};
// Corridor 2 - North-South (vertical)
const CORRIDOR2_NS = {
id: 'corridor2_NS',
tileType: 'corridor',
width: 2,
height: 6,
image: '/assets/images/dungeons/corridor2_NS.png',
walkability: [
[1, 1],
[1, 1],
[1, 1],
[1, 1],
[1, 1],
[1, 1]
],
exits: ['N', 'S']
};
// Corridor 3 - East-West (horizontal)
const CORRIDOR3_EW = {
id: 'corridor3_EW',
tileType: 'corridor',
width: 6,
height: 2,
image: '/assets/images/dungeons/corridor3_EW.png',
walkability: [
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1]
],
exits: ['E', 'W']
};
// Corridor 3 - North-South (vertical)
const CORRIDOR3_NS = {
id: 'corridor3_NS',
tileType: 'corridor',
width: 2,
height: 6,
image: '/assets/images/dungeons/corridor3_NS.png',
walkability: [
[1, 1],
[1, 1],
[1, 1],
[1, 1],
[1, 1],
[1, 1]
],
exits: ['N', 'S']
};
// ============================================================================
// TILE COLLECTIONS
// ============================================================================
export const TILE_DEFINITIONS = {
// Rooms
room_4x4_normal: ROOM_4X4_NORMAL,
room_4x4_circle: ROOM_4X4_CIRCLE,
room_4x4_skeleton: ROOM_4X4_SKELETON,
room_4x6_altar: ROOM_4X6_ALTAR,
room_4x6_tomb: ROOM_4X6_TOMB,
room_4x6_stairs: ROOM_4X6_STAIRS,
// L-shapes
L_NE: L_NE,
L_SE: L_SE,
L_WS: L_WS,
L_WN: L_WN,
// T-junctions
T_NES: T_NES,
T_WNE: T_WNE,
T_WSE: T_WSE,
T_WNS: T_WNS,
// Corridors
corridor1_EW: CORRIDOR1_EW,
corridor1_NS: CORRIDOR1_NS,
corridor2_EW: CORRIDOR2_EW,
corridor2_NS: CORRIDOR2_NS,
corridor3_EW: CORRIDOR3_EW,
corridor3_NS: CORRIDOR3_NS
};
// Collections by type for easy filtering
export const ROOMS = [
ROOM_4X4_NORMAL,
ROOM_4X4_CIRCLE,
ROOM_4X4_SKELETON,
ROOM_4X6_ALTAR,
ROOM_4X6_TOMB,
ROOM_4X6_STAIRS
];
export const L_SHAPES = [
L_NE,
L_SE,
L_WS,
L_WN
];
export const T_JUNCTIONS = [
T_NES,
T_WNE,
T_WSE,
T_WNS
];
export const CORRIDORS = [
CORRIDOR1_EW,
CORRIDOR1_NS,
CORRIDOR2_EW,
CORRIDOR2_NS,
CORRIDOR3_EW,
CORRIDOR3_NS
];
// Helper function to get tile definition by ID
export function getTileDefinition(tileId) {
return TILE_DEFINITIONS[tileId] || null;
}
// Helper function to get tiles by type
export function getTilesByType(tileType) {
switch (tileType) {
case 'room':
return ROOMS;
case 'L':
return L_SHAPES;
case 'T':
return T_JUNCTIONS;
case 'corridor':
return CORRIDORS;
default:
return [];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
export const PHASES = {
POWER: 'POWER', // Roll for events
HERO: 'HERO', // Player actions
EXPLORATION: 'EXPLORATION', // Room reveal
MONSTER: 'MONSTER', // AI actions
END: 'END' // Cleanup
};
class TurnManager extends EventTarget {
constructor() {
super();
this.currentTurn = 1;
this.currentPhase = PHASES.POWER;
this.isCombat = false;
}
startTurn() {
this.currentPhase = PHASES.POWER;
this.dispatchEvent(new CustomEvent('phaseChange', { detail: this.currentPhase }));
console.log(`--- TURN ${this.currentTurn} START ---`);
// Simulating Power Phase trigger
this.processPowerPhase();
}
processPowerPhase() {
// Winds of Magic Roll (1d6)
const roll = Math.floor(Math.random() * 6) + 1;
console.log(`Winds of Magic Roll: ${roll}`);
let message = `Vientos de Magia: ${roll}`;
this.dispatchEvent(new CustomEvent('message', { detail: message }));
if (roll === 1) {
console.log("EVENTO INESPERADO!");
this.dispatchEvent(new CustomEvent('eventTriggered', {
detail: {
source: 'POWER_PHASE',
event: { title: 'Evento Inesperado', description: 'Algo se mueve en la oscuridad...', type: 'MISTERIO' }
}
}));
// In a real game, we'd draw from a specific Event Deck here
} else {
console.log("¡Poder obtenido!");
}
// Auto-advance to Hero Phase after a brief pause to show the roll
setTimeout(() => this.setPhase(PHASES.HERO), 2000);
}
setPhase(phase) {
this.currentPhase = phase;
this.dispatchEvent(new CustomEvent('phaseChange', { detail: this.currentPhase }));
console.log(`Phase changed to: ${phase}`);
if (phase === PHASES.END) {
this.endTurn();
}
}
endTurn() {
console.log(`--- TURN ${this.currentTurn} END ---`);
this.currentTurn++;
this.startTurn();
}
}
export const turnManager = new TurnManager();