app/dungeons/generators/layouts/ConnectedRoomLayoutGenerator.js
import BasicRoomGenerators from '../rooms/BasicRoomGenerators.js';
import Dungeon from '../../Dungeon.js';
import Tiles from '../../../tiles/Tiles.js';
const NORTH = 'NORTH';
const EAST = 'EAST';
const SOUTH = 'SOUTH';
const WEST = 'WEST';
const OPPOSITES = {
[NORTH]: SOUTH,
[EAST]: WEST,
[SOUTH]: NORTH,
[WEST]: EAST
};
function buildRoom(prng) {
const generator = Random.picker(BasicRoomGenerators)(prng);
return {
generator,
width: Random.integer(generator.minWidth, generator.maxWidth)(prng),
height: Random.integer(generator.minHeight, generator.maxHeight)(prng),
hallLength: Random.integer(generator.minHallLength, generator.maxHallLength)(prng)
};
}
function getEdges(room) {
return [{
side: NORTH,
y: room.y,
x1: room.x,
x2: room.x + room.width
}, {
side: EAST,
x: room.x + room.width,
y1: room.y,
y2: room.y + room.height
}, {
side: SOUTH,
y: room.y + room.height,
x1: room.x,
x2: room.x + room.width
}, {
side: WEST,
x: room.x,
y1: room.y,
y2: room.y + room.height
}];
}
function positionRoom(prng, room, edge) {
switch(edge.side) {
case NORTH:
room.y = edge.y - room.hallLength - room.height;
room.x = Random.integer(edge.x1 - room.width + 1, edge.x2)(prng);
break;
case EAST:
room.x = edge.x + room.hallLength;
room.y = Random.integer(edge.y1 - room.height + 1, edge.y2 - 1)(prng);
break;
case SOUTH:
room.y = edge.y + room.hallLength;
room.x = Random.integer(edge.x1 - room.width + 1, edge.x2)(prng);
break;
case WEST:
room.x = edge.x - room.hallLength - room.width;
room.y = Random.integer(edge.y1 - room.height + 1, edge.y2 - 1)(prng);
break;
}
}
function getHall(prng, room, edge) {
if(typeof edge.y === 'number') { // NORTH or SOUTH
const minX = Math.max(room.x, edge.x1);
const maxX = Math.min(room.x + room.width, edge.x2);
const lowerX = Random.integer(minX, maxX - 1)(prng);
const upperX = Random.integer(lowerX + 1, maxX)(prng);
return {
x1: lowerX,
y1: (edge.side === NORTH) ? room.y + room.height : edge.y,
x2: upperX,
y2: (edge.side === NORTH) ? edge.y : room.y,
direction: 'y'
};
} else {
const minY = Math.max(room.y, edge.y1);
const maxY = Math.min(room.y + room.height, edge.y2);
const lowerY = Random.integer(minY, maxY - 1)(prng);
const upperY = Random.integer(lowerY + 1, maxY)(prng);
return {
x1: (edge.side === EAST) ? edge.x : room.x,
y1: lowerY,
x2: (edge.side === EAST) ? room.x : edge.x,
y2: upperY,
direction: 'x'
};
}
}
function intersects(room1, room2) {
return !(room1.x >= room2.x + room2.width ||
room2.x >= room1.x + room1.width ||
room1.y >= room2.y + room2.height ||
room2.y >= room1.y + room1.height);
}
function shiftLayout(rooms, halls) {
const minX = rooms.map(({x})=>x).reduce((a, b)=>Math.min(a, b));
const maxX = rooms.map(({x, width})=>x+width).reduce((a, b)=>Math.max(a, b));
const minY = rooms.map(({y})=>y).reduce((a, b)=>Math.min(a, b));
const maxY = rooms.map(({y, height})=>y+height).reduce((a, b)=>Math.max(a, b));
return {
width: maxX - minX + 2,
height: maxY - minY + 2,
rooms: rooms.map(({x, y, width, height}) => ({
x: x - minX + 1,
y: y - minY + 1,
width,
height
})),
halls: halls.map(({x1, y1, x2, y2}) => ({
x1: x1 - minX + 1,
x2: x2 - minX + 1,
y1: y1 - minY + 1,
y2: y2 - minY + 1
}))
};
}
function print({width, height, rooms, halls}) {
const dungeon = new Array(height).fill(0).map(()=>new Array(width).fill('#'));
rooms.forEach(({x, y, width, height}) => {
const x2 = x + width;
const y2 = y + height;
for(let i = x; i < x2; i++) {
for(let j = y; j < y2; j++) {
dungeon[j][i] = ' ';
}
}
});
halls.forEach(({x1, x2, y1, y2}) => {
for(let i = x1; i < x2; i++) {
for(let j = y1; j < y2; j++) {
dungeon[j][i] = ' ';
}
}
});
console.log(dungeon.map(row=>row.join('')).join('\n'));
}
export default {
generate: function(prng, options = {
numRooms: 10,
minRoomDimension: 3,
maxRoomDimension: 10
}) {
let rooms = [];
let openEdges = [];
let halls = [];
let room = buildRoom(prng);
room.x = 0;
room.y = 0;
rooms.push(room);
openEdges = openEdges.concat(getEdges(room));
while(rooms.length < options.numRooms) {
const roomIndex = Random.integer(0, openEdges.length - 1)(prng);
let edge = openEdges.splice(roomIndex, 1)[0];
room = buildRoom(prng);
positionRoom(prng, room, edge);
if(!rooms.some((room2)=>intersects(room, room2))) {
rooms.push(room);
let hall = getHall(prng, room, edge);
halls.push(hall);
openEdges = openEdges.concat(getEdges(room).filter(
(newEdge)=>newEdge.side !== OPPOSITES[edge.side])
);
}
}
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
rooms.forEach(function(room) {
const {x, y, width, height} = room;
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x + width);
maxY = Math.max(maxY, y + height);
});
let width = maxX - minX + 2;
let height = maxY - minY + 2;
// Shift rooms and halls so that
// top-most room is at y=1 and
// left-most room is at x=1
rooms.forEach(function(room) {
room.x -= (minX - 1);
room.y -= (minY - 1);
});
halls.forEach(function(hall) {
hall.x1 -= (minX - 1);
hall.y1 -= (minY - 1);
hall.x2 -= (minX - 1);
hall.y2 -= (minY - 1);
});
var dungeon = new Dungeon(width, height);
for(var x = 0; x < width; x++) {
for(var y = 0; y < height; y++) {
dungeon.setTile(new Tiles.WallTile(x, y), x, y);
}
}
let key = 0;
rooms.forEach(function(room) {
key++;
const roomWidth = room.width;
const roomHeight = room.height;
const roomX = room.x;
const roomY = room.y;
room.generator.fill(prng, dungeon, {
x1: roomX,
y1: roomY,
width: roomWidth,
height: roomHeight
});
const startX = Math.max(roomX - 1, 0);
const endX = Math.min(roomX + roomWidth + 1, width);
const startY = Math.max(roomY - 1, 0);
const endY = Math.min(roomY + roomHeight + 1, height);
for(let x = startX; x < endX; x++) {
for(let y = startY; y < endY; y++) {
dungeon.getTile(x, y).setRoomKey(key);
}
}
});
halls.forEach(function({x1, y1, x2, y2, direction}) {
for(let x = x1; x < x2; x++) {
for(let y = y1; y < y2; y++) {
dungeon.setTile(new Tiles.Tile(x, y), x, y);
}
}
if(Random.bool(.4)(prng)) {
const doorX = Random.integer(x1, x2 - 1)(prng);
const doorY = Random.integer(y1, y2 - 1)(prng);
if(direction === 'x') {
for(let y = y1; y < y2; y++) {
dungeon.setTile(new Tiles.WallTile(doorX, y), doorX, y);
}
} else {
for(let x = x1; x < x2; x++) {
dungeon.setTile(new Tiles.WallTile(x, doorY), x, doorY);
}
}
dungeon.setTile(new Tiles.DoorTile(doorX, doorY), doorX, doorY);
}
});
return dungeon;
}
};