rot-js
Version:
A roguelike toolkit in JavaScript
352 lines (351 loc) • 13.5 kB
JavaScript
import Map from "./map.js";
import RNG from "../rng.js";
import { DIRS } from "../constants.js";
/**
* Dungeon generator which uses the "orginal" Rogue dungeon generation algorithm. See https://github.com/Davidslv/rogue-like/blob/master/docs/references/Mark_Damon_Hughes/07_Roguelike_Dungeon_Generation.md
* @author hyakugei
*/
export default class Rogue extends Map {
constructor(width, height, options) {
super(width, height);
this.map = [];
this.rooms = [];
this.connectedCells = [];
options = Object.assign({
cellWidth: 3,
cellHeight: 3 // ie. as an array with min-max values for each direction....
}, options);
/*
Set the room sizes according to the over-all width of the map,
and the cell sizes.
*/
if (!options.hasOwnProperty("roomWidth")) {
options["roomWidth"] = this._calculateRoomSize(this._width, options["cellWidth"]);
}
if (!options.hasOwnProperty("roomHeight")) {
options["roomHeight"] = this._calculateRoomSize(this._height, options["cellHeight"]);
}
this._options = options;
}
create(callback) {
this.map = this._fillMap(1);
this.rooms = [];
this.connectedCells = [];
this._initRooms();
this._connectRooms();
this._connectUnconnectedRooms();
this._createRandomRoomConnections();
this._createRooms();
this._createCorridors();
if (callback) {
for (let i = 0; i < this._width; i++) {
for (let j = 0; j < this._height; j++) {
callback(i, j, this.map[i][j]);
}
}
}
return this;
}
_calculateRoomSize(size, cell) {
let max = Math.floor((size / cell) * 0.8);
let min = Math.floor((size / cell) * 0.25);
if (min < 2) {
min = 2;
}
if (max < 2) {
max = 2;
}
return [min, max];
}
_initRooms() {
// create rooms array. This is the "grid" list from the algo.
for (let i = 0; i < this._options.cellWidth; i++) {
this.rooms.push([]);
for (let j = 0; j < this._options.cellHeight; j++) {
this.rooms[i].push({ "x": 0, "y": 0, "width": 0, "height": 0, "connections": [], "cellx": i, "celly": j });
}
}
}
_connectRooms() {
//pick random starting grid
let cgx = RNG.getUniformInt(0, this._options.cellWidth - 1);
let cgy = RNG.getUniformInt(0, this._options.cellHeight - 1);
let idx;
let ncgx;
let ncgy;
let found = false;
let room;
let otherRoom;
let dirToCheck;
// find unconnected neighbour cells
do {
//dirToCheck = [0, 1, 2, 3, 4, 5, 6, 7];
dirToCheck = [0, 2, 4, 6];
dirToCheck = RNG.shuffle(dirToCheck);
do {
found = false;
idx = dirToCheck.pop();
ncgx = cgx + DIRS[8][idx][0];
ncgy = cgy + DIRS[8][idx][1];
if (ncgx < 0 || ncgx >= this._options.cellWidth) {
continue;
}
if (ncgy < 0 || ncgy >= this._options.cellHeight) {
continue;
}
room = this.rooms[cgx][cgy];
if (room["connections"].length > 0) {
// as long as this room doesn't already coonect to me, we are ok with it.
if (room["connections"][0][0] == ncgx && room["connections"][0][1] == ncgy) {
break;
}
}
otherRoom = this.rooms[ncgx][ncgy];
if (otherRoom["connections"].length == 0) {
otherRoom["connections"].push([cgx, cgy]);
this.connectedCells.push([ncgx, ncgy]);
cgx = ncgx;
cgy = ncgy;
found = true;
}
} while (dirToCheck.length > 0 && found == false);
} while (dirToCheck.length > 0);
}
_connectUnconnectedRooms() {
//While there are unconnected rooms, try to connect them to a random connected neighbor
//(if a room has no connected neighbors yet, just keep cycling, you'll fill out to it eventually).
let cw = this._options.cellWidth;
let ch = this._options.cellHeight;
this.connectedCells = RNG.shuffle(this.connectedCells);
let room;
let otherRoom;
let validRoom;
for (let i = 0; i < this._options.cellWidth; i++) {
for (let j = 0; j < this._options.cellHeight; j++) {
room = this.rooms[i][j];
if (room["connections"].length == 0) {
let directions = [0, 2, 4, 6];
directions = RNG.shuffle(directions);
validRoom = false;
do {
let dirIdx = directions.pop();
let newI = i + DIRS[8][dirIdx][0];
let newJ = j + DIRS[8][dirIdx][1];
if (newI < 0 || newI >= cw || newJ < 0 || newJ >= ch) {
continue;
}
otherRoom = this.rooms[newI][newJ];
validRoom = true;
if (otherRoom["connections"].length == 0) {
break;
}
for (let k = 0; k < otherRoom["connections"].length; k++) {
if (otherRoom["connections"][k][0] == i && otherRoom["connections"][k][1] == j) {
validRoom = false;
break;
}
}
if (validRoom) {
break;
}
} while (directions.length);
if (validRoom) {
room["connections"].push([otherRoom["cellx"], otherRoom["celly"]]);
}
else {
console.log("-- Unable to connect room.");
}
}
}
}
}
_createRandomRoomConnections() {
// Empty for now.
}
_createRooms() {
let w = this._width;
let h = this._height;
let cw = this._options.cellWidth;
let ch = this._options.cellHeight;
let cwp = Math.floor(this._width / cw);
let chp = Math.floor(this._height / ch);
let roomw;
let roomh;
let roomWidth = this._options["roomWidth"];
let roomHeight = this._options["roomHeight"];
let sx;
let sy;
let otherRoom;
for (let i = 0; i < cw; i++) {
for (let j = 0; j < ch; j++) {
sx = cwp * i;
sy = chp * j;
if (sx == 0) {
sx = 1;
}
if (sy == 0) {
sy = 1;
}
roomw = RNG.getUniformInt(roomWidth[0], roomWidth[1]);
roomh = RNG.getUniformInt(roomHeight[0], roomHeight[1]);
if (j > 0) {
otherRoom = this.rooms[i][j - 1];
while (sy - (otherRoom["y"] + otherRoom["height"]) < 3) {
sy++;
}
}
if (i > 0) {
otherRoom = this.rooms[i - 1][j];
while (sx - (otherRoom["x"] + otherRoom["width"]) < 3) {
sx++;
}
}
let sxOffset = Math.round(RNG.getUniformInt(0, cwp - roomw) / 2);
let syOffset = Math.round(RNG.getUniformInt(0, chp - roomh) / 2);
while (sx + sxOffset + roomw >= w) {
if (sxOffset) {
sxOffset--;
}
else {
roomw--;
}
}
while (sy + syOffset + roomh >= h) {
if (syOffset) {
syOffset--;
}
else {
roomh--;
}
}
sx = sx + sxOffset;
sy = sy + syOffset;
this.rooms[i][j]["x"] = sx;
this.rooms[i][j]["y"] = sy;
this.rooms[i][j]["width"] = roomw;
this.rooms[i][j]["height"] = roomh;
for (let ii = sx; ii < sx + roomw; ii++) {
for (let jj = sy; jj < sy + roomh; jj++) {
this.map[ii][jj] = 0;
}
}
}
}
}
_getWallPosition(aRoom, aDirection) {
let rx;
let ry;
let door;
if (aDirection == 1 || aDirection == 3) {
rx = RNG.getUniformInt(aRoom["x"] + 1, aRoom["x"] + aRoom["width"] - 2);
if (aDirection == 1) {
ry = aRoom["y"] - 2;
door = ry + 1;
}
else {
ry = aRoom["y"] + aRoom["height"] + 1;
door = ry - 1;
}
this.map[rx][door] = 0; // i'm not setting a specific 'door' tile value right now, just empty space.
}
else {
ry = RNG.getUniformInt(aRoom["y"] + 1, aRoom["y"] + aRoom["height"] - 2);
if (aDirection == 2) {
rx = aRoom["x"] + aRoom["width"] + 1;
door = rx - 1;
}
else {
rx = aRoom["x"] - 2;
door = rx + 1;
}
this.map[door][ry] = 0; // i'm not setting a specific 'door' tile value right now, just empty space.
}
return [rx, ry];
}
_drawCorridor(startPosition, endPosition) {
let xOffset = endPosition[0] - startPosition[0];
let yOffset = endPosition[1] - startPosition[1];
let xpos = startPosition[0];
let ypos = startPosition[1];
let tempDist;
let xDir;
let yDir;
let move; // 2 element array, element 0 is the direction, element 1 is the total value to move.
let moves = []; // a list of 2 element arrays
let xAbs = Math.abs(xOffset);
let yAbs = Math.abs(yOffset);
let percent = RNG.getUniform(); // used to split the move at different places along the long axis
let firstHalf = percent;
let secondHalf = 1 - percent;
xDir = xOffset > 0 ? 2 : 6;
yDir = yOffset > 0 ? 4 : 0;
if (xAbs < yAbs) {
// move firstHalf of the y offset
tempDist = Math.ceil(yAbs * firstHalf);
moves.push([yDir, tempDist]);
// move all the x offset
moves.push([xDir, xAbs]);
// move sendHalf of the y offset
tempDist = Math.floor(yAbs * secondHalf);
moves.push([yDir, tempDist]);
}
else {
// move firstHalf of the x offset
tempDist = Math.ceil(xAbs * firstHalf);
moves.push([xDir, tempDist]);
// move all the y offset
moves.push([yDir, yAbs]);
// move secondHalf of the x offset.
tempDist = Math.floor(xAbs * secondHalf);
moves.push([xDir, tempDist]);
}
this.map[xpos][ypos] = 0;
while (moves.length > 0) {
move = moves.pop();
while (move[1] > 0) {
xpos += DIRS[8][move[0]][0];
ypos += DIRS[8][move[0]][1];
this.map[xpos][ypos] = 0;
move[1] = move[1] - 1;
}
}
}
_createCorridors() {
// Draw Corridors between connected rooms
let cw = this._options.cellWidth;
let ch = this._options.cellHeight;
let room;
let connection;
let otherRoom;
let wall;
let otherWall;
for (let i = 0; i < cw; i++) {
for (let j = 0; j < ch; j++) {
room = this.rooms[i][j];
for (let k = 0; k < room["connections"].length; k++) {
connection = room["connections"][k];
otherRoom = this.rooms[connection[0]][connection[1]];
// figure out what wall our corridor will start one.
// figure out what wall our corridor will end on.
if (otherRoom["cellx"] > room["cellx"]) {
wall = 2;
otherWall = 4;
}
else if (otherRoom["cellx"] < room["cellx"]) {
wall = 4;
otherWall = 2;
}
else if (otherRoom["celly"] > room["celly"]) {
wall = 3;
otherWall = 1;
}
else {
wall = 1;
otherWall = 3;
}
this._drawCorridor(this._getWallPosition(room, wall), this._getWallPosition(otherRoom, otherWall));
}
}
}
}
}