versión inicial del juego

This commit is contained in:
2025-12-30 23:24:58 +01:00
commit 7dbc77e75a
34 changed files with 1589 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
export const DIRECTIONS = {
NORTH: 'N',
SOUTH: 'S',
EAST: 'E',
WEST: 'W'
};
export const TILE_TYPES = {
ROOM: 'room',
CORRIDOR: 'corridor',
JUNCTION: 'junction',
OBJECTIVE_ROOM: 'objective_room'
};

View File

@@ -0,0 +1,109 @@
import { TILES } from './TileDefinitions.js';
export class DungeonDeck {
constructor() {
this.cards = [];
this.discards = [];
// We don't initialize automatically anymore
}
/**
* Constructs the deck according to the specific Warhammer Quest rules.
* Rulebook steps:
* 1. Take 6 random Dungeon Cards (Bottom pool).
* 2. Add Objective Room card to Bottom pool.
* 3. Shuffle Bottom pool (7 cards).
* 4. Take 6 random Dungeon Cards (Top pool).
* 5. Stack Top pool on Bottom pool.
* Total: 13 cards.
*
* @param {string} objectiveTileId - ID of the objective/exit room.
*/
generateMissionDeck(objectiveTileId) {
this.cards = [];
// 1. Create a "Pool" of standard dungeon tiles (Rooms & Corridors)
// We replicate the physical deck distribution first
let pool = [];
const composition = [
{ id: 'room_dungeon', count: 6 },
// Objective room is special, handled separately
{ id: 'corridor_straight', count: 7 },
{ id: 'corridor_steps', count: 1 },
{ id: 'corridor_corner', count: 1 },
{ id: 'junction_t', count: 3 }
];
composition.forEach(item => {
const tileDef = TILES.find(t => t.id === item.id);
if (tileDef) {
for (let i = 0; i < item.count; i++) {
pool.push(tileDef);
}
}
});
// Helper to pull random cards
const drawRandom = (source, count) => {
const drawn = [];
for (let i = 0; i < count; i++) {
if (source.length === 0) break;
const idx = Math.floor(Math.random() * source.length);
drawn.push(source[idx]);
source.splice(idx, 1); // Remove from pool
}
return drawn;
};
// --- Step 1 & 2: Bottom Pool (6 Random + Objective) ---
const bottomPool = drawRandom(pool, 6);
// Add Objective Card
const objectiveDef = TILES.find(t => t.id === objectiveTileId);
if (objectiveDef) {
bottomPool.push(objectiveDef);
} else {
console.error("Objective Tile ID not found:", objectiveTileId);
// Fallback: Add a generic room if objective missing?
}
// --- Step 3: Shuffle Bottom Pool ---
this.shuffleArray(bottomPool);
// --- Step 4: Top Pool (6 Random) ---
const topPool = drawRandom(pool, 6);
// Note: No shuffle explicitly needed for Top Pool if drawn randomly,
// but shuffling ensures random order of the 6 drawn.
this.shuffleArray(topPool);
// --- Step 5: Stack (Top on Bottom) ---
// Array[0] is the "Top" card (first to be drawn)
this.cards = [...topPool, ...bottomPool];
console.log(`Deck Generated: ${this.cards.length} cards.`);
}
shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
draw() {
if (this.cards.length === 0) {
return null; // Deck empty
}
return this.cards.shift(); // Take from top
}
// Useful for Campaign logic: Insert a specific card at position
insertCard(tileId, position = 0) {
const tileDef = TILES.find(t => t.id === tileId);
if (tileDef) {
this.cards.splice(position, 0, tileDef);
}
}
}

View File

@@ -0,0 +1,257 @@
import { DIRECTIONS } from './Constants.js';
import { GridSystem } from './GridSystem.js';
import { DungeonDeck } from './DungeonDeck.js';
import { TILES } from './TileDefinitions.js';
export class DungeonGenerator {
constructor() {
this.grid = new GridSystem();
this.deck = new DungeonDeck();
this.pendingExits = []; // Array of global {x, y, direction}
this.placedTiles = [];
this.isComplete = false;
}
startDungeon(missionConfig) {
// 1. Prepare Deck (Rulebook: 13 cards, 6+1+6)
// We need an objective tile ID from the config
const objectiveId = missionConfig.type === 'quest' ? 'room_objective' : 'room_dungeon'; // Fallback for now
this.deck.generateMissionDeck(objectiveId);
// 2. Rulebook Step 4: "Flip the first card. This is the entrance."
const startCard = this.deck.draw();
if (!startCard) {
console.error("Deck is empty on start!");
return;
}
// 3. Place the Entry Tile at (0,0)
// We assume rotation NORTH by default for the first piece
const startInstance = {
id: `tile_0_${startCard.id}`,
defId: startCard.id,
x: 0,
y: 0,
rotation: DIRECTIONS.NORTH
};
if (this.grid.canPlace(startCard, 0, 0, DIRECTIONS.NORTH)) {
this.grid.placeTile(startInstance, startCard);
this.placedTiles.push(startInstance);
this.addExitsToQueue(startInstance, startCard);
console.log(`Dungeon started with ${startCard.name}`);
} else {
console.error("Failed to place starting tile (Grid collision at 0,0?)");
}
}
step() {
if (this.isComplete) return false;
if (this.pendingExits.length === 0) {
console.log("No more exits available. Dungeon generation stopped.");
this.isComplete = true;
return false;
}
// Rulebook: Draw next card
const card = this.deck.draw();
if (!card) {
console.log("Deck empty. Dungeon complete.");
this.isComplete = true;
return false;
}
// Try to fit the card on any pending exit
// We prioritize the "current" open exit? Rulebook implies expanding from the explored edge.
// For a generator, we treat it as a queue (BFS) or stack (DFS). Queue is better for "bushy" dungeons.
// Let's try to fit the card onto the FIRST valid exit in our queue
let placed = false;
// Iterate through copy of pending exits to avoid modification issues during loop
// (Though we usually just pick ONE exit to explore per turn in the board game)
// In the board game, you pick an exit and "Explore" it.
// Let's pick the first available exit.
const targetExit = this.pendingExits.shift();
console.log(`Attempting to place ${card.name} at exit ${targetExit.x},${targetExit.y} (${targetExit.direction})`);
// We need to rotate the new card so ONE of its exits connects to 'targetExit'
// Connection rule: New Tile Exit be Opposed to Target Exit.
// Target: NORTH -> New Tile must present a SOUTH exit to connect.
const requiredInputDirection = this.getOppositeDirection(targetExit.direction);
// Find which exit on the CANDIDATE card can serve as the input
// (A tile might have multiple potential inputs, e.g. a 4-way corridor)
for (const candidateExit of card.exits) {
// calculatedRotation: What rotation does the TILE need so that 'candidateExit' points 'requiredInputDirection'?
// candidateExit.direction (Local) + TileRotation = requiredInputDirection
const rotation = this.calculateRequiredRotation(candidateExit.direction, requiredInputDirection);
// Now calculate where the tile top-left (x,y) must be so that the exits match positions.
const position = this.calculateTilePosition(targetExit, candidateExit, rotation);
if (this.grid.canPlace(card, position.x, position.y, rotation)) {
// Success! Place it.
const newInstance = {
id: `tile_${this.placedTiles.length}_${card.id}`,
defId: card.id,
x: position.x,
y: position.y,
rotation: rotation
};
this.grid.placeTile(newInstance, card);
this.placedTiles.push(newInstance);
// Add NEW exits, but...
// CRITICAL: The exit we just used to enter is NOT an exit anymore. It's the connection.
this.addExitsToQueue(newInstance, card, targetExit); // Pass the source to exclude it
placed = true;
break; // Stop looking for fits for this card
}
}
if (!placed) {
console.log(`Could not fit ${card.name} at selected exit. Discarding.`);
// In real game: Discard card.
// Put the exit back? Rulebook says "If room doesn't fit, nothing is placed".
// Does the exit remain open? Yes, usually.
this.pendingExits.push(targetExit); // Return exit to queue to try later?
// Or maybe discard it?
// "If you cannot place the room... the passage is a dead end." (Some editions)
// Let's keep it open for now, maybe next card fits.
}
return true; // Step done
}
// --- Helpers ---
getOppositeDirection(dir) {
switch (dir) {
case DIRECTIONS.NORTH: return DIRECTIONS.SOUTH;
case DIRECTIONS.SOUTH: return DIRECTIONS.NORTH;
case DIRECTIONS.EAST: return DIRECTIONS.WEST;
case DIRECTIONS.WEST: return DIRECTIONS.EAST;
}
}
calculateRequiredRotation(localDir, targetGlobalDir) {
// e.g. Local=NORTH needs to become Global=EAST.
// N(0) -> E(1). Diff +1 (90 deg).
// Standard mapping: N=0, E=1, S=2, W=3
const dirs = [DIRECTIONS.NORTH, DIRECTIONS.EAST, DIRECTIONS.SOUTH, DIRECTIONS.WEST];
const localIdx = dirs.indexOf(localDir);
const targetIdx = dirs.indexOf(targetGlobalDir);
// (Local + Rotation) % 4 = Target
// Rotation = (Target - Local + 4) % 4
const diff = (targetIdx - localIdx + 4) % 4;
return dirs[diff];
}
calculateTilePosition(targetExitGlobal, candidateExitLocal, rotation) {
// We know the Global Coordinate of the connection point (targetExitGlobal)
// We know the Local Coordinate of the matching exit on the new tile (candidateExitLocal)
// We need 'startX, startY' of the new tile.
// First, transform the local exit to a rotated offset
// We reuse GridSystem logic logic ideally, but let's do math here
let offsetX, offsetY;
// Replicating GridSystem.getGlobalPoint simple logic for vector only
// If we treat candidateExitLocal as a vector from (0,0)
const lx = candidateExitLocal.x;
const ly = candidateExitLocal.y;
switch (rotation) {
case DIRECTIONS.NORTH: offsetX = lx; offsetY = ly; break;
case DIRECTIONS.SOUTH: offsetX = -lx; offsetY = -ly; break;
case DIRECTIONS.EAST: offsetX = ly; offsetY = -lx; break;
case DIRECTIONS.WEST: offsetX = -ly; offsetY = lx; break;
}
// GlobalExit = TilePos + RotatedOffset
// TilePos = GlobalExit - RotatedOffset
// Wait, 'targetExitGlobal' is the cell just OUTSIDE the previous tile?
// Or the cell OF the previous tile's exit?
// Usually targetExit is "The cell where the connection happens".
// In GridSystem, exits are defined AT the edge.
// Let's assume targetExitGlobal is the coordinate OF THE EXIT CELL on the previous tile.
// So the new tile's matching exit cell must OVERLAP this one? NO.
// They must be adjacent.
// Correction: Tiles must connect *adjacent* to each other.
// If TargetExit is at (10,10) facing NORTH, the New Tile must attach at (10,11).
let connectionPointX = targetExitGlobal.x;
let connectionPointY = targetExitGlobal.y;
// Move 1 step in the target direction to find the "Anchor Point" for the new tile
switch (targetExitGlobal.direction) {
case DIRECTIONS.NORTH: connectionPointY += 1; break;
case DIRECTIONS.SOUTH: connectionPointY -= 1; break;
case DIRECTIONS.EAST: connectionPointX += 1; break;
case DIRECTIONS.WEST: connectionPointX -= 1; break;
}
// Now align the new tile such that its candidate exit lands on connectionPoint
return {
x: connectionPointX - offsetX,
y: connectionPointY - offsetY
};
}
addExitsToQueue(tileInstance, tileDef, excludeSourceExit = null) {
// Calculate all global exits for this placed tile
for (const exit of tileDef.exits) {
const globalPoint = this.grid.getGlobalPoint(exit.x, exit.y, tileInstance);
const globalDir = this.grid.getRotatedDirection(exit.direction, tileInstance.rotation);
// If this is the exit we just entered through, skip it
// Logic: connection is adjacent.
// A simpler check: if we just connected to (X,Y), don't add an exit at (X,Y).
// But we calculated 'connectionPoint' as the place where the NEW tile's exit is.
// Check adjacency to excludeSource?
// Or better: excludeSourceExit is the "Previous Tile's Exit".
// The "Entrance" on the new tile connects to that.
// We should just not add the exit that was used as input.
// How to identify it?
// We calculated it in the main loop.
// Let's simplify: Add ALL exits.
// The logic later will filter out exits that point into occupied cells?
// Yes, checking collision also checks if the target cell is free.
// But we don't want to list "Backwards" exits.
// Optimization: If the cell immediate to this exit is already occupied, don't add it.
// This handles the "Entrance" naturally (it points back to the previous tile).
let neighborX = globalPoint.x;
let neighborY = globalPoint.y;
switch (globalDir) {
case DIRECTIONS.NORTH: neighborY += 1; break;
case DIRECTIONS.SOUTH: neighborY -= 1; break;
case DIRECTIONS.EAST: neighborX += 1; break;
case DIRECTIONS.WEST: neighborX -= 1; break;
}
const neighborKey = `${neighborX},${neighborY}`;
if (!this.grid.occupiedCells.has(neighborKey)) {
this.pendingExits.push({
x: globalPoint.x,
y: globalPoint.y,
direction: globalDir
});
}
}
}
}

View File

@@ -0,0 +1,184 @@
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 MAGIC MATH FUNCTION.
* Converts a simplified abstract tile (width/length) into actual grid coordinates.
* Handles the Rotation logic (N, S, E, W).
* NOW SUPPORTS: Matrix Layouts (0 = Empty).
*/
getGlobalCells(tileDef, startX, startY, rotation) {
const cells = [];
const layout = tileDef.layout;
// Safety check: if no layout, fallback to full rectangle (optional, but good for stability)
// usage: const w = tileDef.width; const l = tileDef.length;
if (!layout) {
console.error("Tile definition missing layout. ID:", tileDef?.id);
console.warn("Invalid tileDef object:", tileDef);
return cells;
}
const numberOfRows = layout.length; // usually equals tileDef.length
// Iterate through matrix rows
for (let row = 0; row < numberOfRows; row++) {
const rowData = layout[row];
const numberOfCols = rowData.length; // usually equals tileDef.width
for (let col = 0; col < numberOfCols; col++) {
const cellValue = rowData[col];
// CRITICAL: 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).
// Matrix Row (Rows-1) is the "Bottom" (Y=0).
// So: ly = (numberOfRows - 1) - row
// lx = col
const lx = col;
const ly = (numberOfRows - 1) - row;
let gx, gy;
// Apply Rotation to the local (lx, ly) point relative to (0,0) anchor
switch (rotation) {
case DIRECTIONS.NORTH:
// Standard: +X is Right, +Y is Forward
gx = startX + lx;
gy = startY + ly;
break;
case DIRECTIONS.SOUTH:
// 180 degrees: Extension goes "Backwards" and "Leftwards" relative to pivot
gx = startX - lx;
gy = startY - ly;
break;
case DIRECTIONS.EAST:
// 90 degrees Clockwise: Width becomes "Length", Length becomes "Width"
// x' = y, y' = -x
gx = startX + ly;
gy = startY - lx;
break;
case DIRECTIONS.WEST:
// 270 degrees Clockwise (or 90 Counter-Clockwise)
// x' = -y, y' = x
gx = startX - ly;
gy = startY + lx;
break;
default:
gx = startX + lx;
gy = startY + ly;
}
// We could also store the 'cellValue' (height) if we wanted.
cells.push({ x: gx, y: gy, value: cellValue });
}
}
return cells;
}
/**
* Transforms a local point (like an exit definition) to Global Coordinates.
* Useful for calculating where an exit actually ends up on the board.
*/
getGlobalPoint(localX, localY, tileInstance) {
let gx, gy;
const startX = tileInstance.x;
const startY = tileInstance.y;
const rotation = tileInstance.rotation;
switch (rotation) {
case DIRECTIONS.NORTH:
gx = startX + localX;
gy = startY + localY;
break;
case DIRECTIONS.SOUTH:
gx = startX - localX;
gy = startY - localY;
break;
case DIRECTIONS.EAST:
gx = startX + localY;
gy = startY - localX;
break;
case DIRECTIONS.WEST:
gx = startX - localY;
gy = startY + localX;
break;
default:
gx = startX + localX;
gy = startY + localY;
}
return { x: gx, y: gy };
}
/**
* Rotates a direction (N, S, E, W) by a given amount.
* Useful for calculating which way an exit faces after the tile is rotated.
*/
getRotatedDirection(originalDirection, tileRotation) {
// N=0, E=1, S=2, W=3
const dirs = [DIRECTIONS.NORTH, DIRECTIONS.EAST, DIRECTIONS.SOUTH, DIRECTIONS.WEST];
const idx = dirs.indexOf(originalDirection);
let rotationSteps = 0;
if (tileRotation === DIRECTIONS.EAST) rotationSteps = 1;
if (tileRotation === DIRECTIONS.SOUTH) rotationSteps = 2;
if (tileRotation === DIRECTIONS.WEST) rotationSteps = 3;
const newIdx = (idx + rotationSteps) % 4;
return dirs[newIdx];
}
}

View File

@@ -0,0 +1,39 @@
import { TILE_TYPES } from './Constants.js';
export const MISSION_TYPES = {
ESCAPE: 'escape', // Objective is to find the exit
QUEST: 'quest' // Objective is to find the Objective Room
};
export class MissionConfig {
/**
* @param {Object} config - The mission configuration object
* @param {string} config.id - Unique ID
* @param {string} config.name - Display Name
* @param {string} config.type - MISSION_TYPES.ESCAPE or .QUEST
* @param {number} config.minTiles - Minimum tiles before the objective card is shuffled in
*/
constructor(config) {
this.id = config.id;
this.name = config.name;
this.type = config.type || MISSION_TYPES.ESCAPE;
// For Campaign missions: "Force valid exit room after X tiles"
// Use this to control when the "Target" card is inserted into the future deck
this.minTiles = config.minTiles || 8;
}
/**
* Determines which tile acts as the "Objective" for this mission.
* In standard missions: It's the Objective Room.
* In escape missions: It might be a specific generic room designated as the "Exit".
*/
getTargetTileType() {
if (this.type === MISSION_TYPES.QUEST) {
return TILE_TYPES.OBJECTIVE_ROOM;
} else {
// In escape missions, any Room can be the exit, usually marked specifically
return TILE_TYPES.ROOM;
}
}
}

View File

@@ -0,0 +1,148 @@
import { DIRECTIONS, TILE_TYPES } from './Constants.js';
export const TILES = [
// --- CORRIDORS (Corredores) ---
{
id: 'corridor_straight',
name: 'Corridor',
type: TILE_TYPES.CORRIDOR,
width: 2,
length: 6,
textures: ['/assets/images/dungeon1/tiles/corridor1.png', '/assets/images/dungeon1/tiles/corridor2.png', '/assets/images/dungeon1/tiles/corridor3.png'], // Visual variety
// Layout: 6 rows
layout: [
[1, 1], // y=5 (North End - Trident?)
[1, 1], // y=4
[1, 1], // y=3
[1, 1], // y=2
[1, 1], // y=1
[1, 1] // y=0 (South End - Single Input)
],
exits: [
// South End (1 direction)
{ x: 0, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 1, y: 0, direction: DIRECTIONS.SOUTH },
// North End (3 Directions: N, plus Side E/W meaning West/East in vertical)
{ x: 0, y: 5, direction: DIRECTIONS.NORTH }, // Straight Out
{ x: 1, y: 5, direction: DIRECTIONS.NORTH },
{ x: 0, y: 5, direction: DIRECTIONS.WEST },
{ x: 1, y: 5, direction: DIRECTIONS.EAST }
]
},
{
id: 'corridor_steps',
name: 'Steps',
type: TILE_TYPES.CORRIDOR,
width: 2,
length: 6,
textures: ['/assets/images/dungeon1/tiles/stairs1.png'],
// Layout includes 9 for stairs? User example used 9.
layout: [
[2, 2], // y=5 (High end)
[2, 2],
[9, 9], // Stairs
[9, 9],
[1, 1],
[1, 1] // y=0 (Low end)
],
exits: [
{ x: 0, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 1, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 0, y: 5, direction: DIRECTIONS.NORTH },
{ x: 1, y: 5, direction: DIRECTIONS.NORTH }
]
},
{
id: 'corridor_corner',
name: 'Corner',
type: TILE_TYPES.CORRIDOR,
width: 4,
length: 4,
textures: ['/assets/images/dungeon1/tiles/L.png'],
// L Shape
layout: [
[1, 1, 1, 1], // y=3
[1, 1, 1, 1], // y=2
[1, 1, 0, 0], // y=1
[1, 1, 0, 0] // y=0
],
exits: [
{ x: 0, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 1, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 3, y: 2, direction: DIRECTIONS.EAST },
{ x: 3, y: 3, direction: DIRECTIONS.EAST }
]
},
{
id: 'junction_t',
name: 'T-Junction',
type: TILE_TYPES.JUNCTION,
width: 6,
length: 4,
textures: ['/assets/images/dungeon1/tiles/T.png'],
// T-Shape
layout: [
[1, 1, 1, 1, 1, 1], // y=3
[1, 1, 1, 1, 1, 1], // y=2
[0, 0, 1, 1, 0, 0], // y=1
[0, 0, 1, 1, 0, 0] // y=0
],
exits: [
{ x: 2, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 3, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 0, y: 2, direction: DIRECTIONS.WEST },
{ x: 0, y: 3, direction: DIRECTIONS.WEST },
{ x: 5, y: 2, direction: DIRECTIONS.EAST },
{ x: 5, y: 3, direction: DIRECTIONS.EAST }
]
},
// --- ROOMS ---
{
id: 'room_dungeon',
name: 'Dungeon Room',
type: TILE_TYPES.ROOM,
width: 4,
length: 4,
textures: ['/assets/images/dungeon1/tiles/room_4x4_circle.png', '/assets/images/dungeon1/tiles/room_4x4_orange.png', '/assets/images/dungeon1/tiles/room_4x4_squeleton.png'],
layout: [
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]
],
exits: [
{ x: 1, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 2, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 1, y: 3, direction: DIRECTIONS.NORTH },
{ x: 2, y: 3, direction: DIRECTIONS.NORTH }
]
},
{
id: 'room_objective',
name: 'Objective Room',
type: TILE_TYPES.OBJECTIVE_ROOM,
width: 4,
length: 8,
textures: ['/assets/images/dungeon1/tiles/room_4x8_altar.png', '/assets/images/dungeon1/tiles/room_4x8_tomb.png'],
layout: [
[1, 1, 1, 1], // y=7
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1] // y=0
],
exits: [
{ x: 1, y: 0, direction: DIRECTIONS.SOUTH },
{ x: 2, y: 0, direction: DIRECTIONS.SOUTH }
]
}
];