lychees's picture
Upload 569 files
87b3b3a
/**
* @class Dungeon generator which tries to fill the space evenly. Generates independent rooms and tries to connect them.
* @augments ROT.Map.Dungeon
*/
ROT.Map.Uniform = function(width, height, options) {
ROT.Map.Dungeon.call(this, width, height);
this._options = {
roomWidth: [3, 9], /* room minimum and maximum width */
roomHeight: [3, 5], /* room minimum and maximum height */
roomDugPercentage: 0.1, /* we stop after this percentage of level area has been dug out by rooms */
timeLimit: 1000 /* we stop after this much time has passed (msec) */
}
for (var p in options) { this._options[p] = options[p]; }
this._roomAttempts = 20; /* new room is created N-times until is considered as impossible to generate */
this._corridorAttempts = 20; /* corridors are tried N-times until the level is considered as impossible to connect */
this._connected = []; /* list of already connected rooms */
this._unconnected = []; /* list of remaining unconnected rooms */
this._digCallback = this._digCallback.bind(this);
this._canBeDugCallback = this._canBeDugCallback.bind(this);
this._isWallCallback = this._isWallCallback.bind(this);
}
ROT.Map.Uniform.extend(ROT.Map.Dungeon);
/**
* Create a map. If the time limit has been hit, returns null.
* @see ROT.Map#create
*/
ROT.Map.Uniform.prototype.create = function(callback) {
var t1 = Date.now();
while (1) {
var t2 = Date.now();
if (t2 - t1 > this._options.timeLimit) { return null; } /* time limit! */
this._map = this._fillMap(1);
this._dug = 0;
this._rooms = [];
this._unconnected = [];
this._generateRooms();
if (this._generateCorridors()) { break; }
}
if (callback) {
for (var i=0;i<this._width;i++) {
for (var j=0;j<this._height;j++) {
callback(i, j, this._map[i][j]);
}
}
}
return this;
}
/**
* Generates a suitable amount of rooms
*/
ROT.Map.Uniform.prototype._generateRooms = function() {
var w = this._width-2;
var h = this._height-2;
do {
var room = this._generateRoom();
if (this._dug/(w*h) > this._options.roomDugPercentage) { break; } /* achieved requested amount of free space */
} while (room);
/* either enough rooms, or not able to generate more of them :) */
}
/**
* Try to generate one room
*/
ROT.Map.Uniform.prototype._generateRoom = function() {
var count = 0;
while (count < this._roomAttempts) {
count++;
var room = ROT.Map.Feature.Room.createRandom(this._width, this._height, this._options);
if (!room.isValid(this._isWallCallback, this._canBeDugCallback)) { continue; }
room.create(this._digCallback);
this._rooms.push(room);
return room;
}
/* no room was generated in a given number of attempts */
return null;
}
/**
* Generates connectors beween rooms
* @returns {bool} success Was this attempt successfull?
*/
ROT.Map.Uniform.prototype._generateCorridors = function() {
var cnt = 0;
while (cnt < this._corridorAttempts) {
cnt++;
this._corridors = [];
/* dig rooms into a clear map */
this._map = this._fillMap(1);
for (var i=0;i<this._rooms.length;i++) {
var room = this._rooms[i];
room.clearDoors();
room.create(this._digCallback);
}
this._unconnected = this._rooms.slice().randomize();
this._connected = [];
if (this._unconnected.length) { this._connected.push(this._unconnected.pop()); } /* first one is always connected */
while (1) {
/* 1. pick random connected room */
var connected = this._connected.random();
/* 2. find closest unconnected */
var room1 = this._closestRoom(this._unconnected, connected);
/* 3. connect it to closest connected */
var room2 = this._closestRoom(this._connected, room1);
var ok = this._connectRooms(room1, room2);
if (!ok) { break; } /* stop connecting, re-shuffle */
if (!this._unconnected.length) { return true; } /* done; no rooms remain */
}
}
return false;
}
/**
* For a given room, find the closest one from the list
*/
ROT.Map.Uniform.prototype._closestRoom = function(rooms, room) {
var dist = Infinity;
var center = room.getCenter();
var result = null;
for (var i=0;i<rooms.length;i++) {
var r = rooms[i];
var c = r.getCenter();
var dx = c[0]-center[0];
var dy = c[1]-center[1];
var d = dx*dx+dy*dy;
if (d < dist) {
dist = d;
result = r;
}
}
return result;
}
ROT.Map.Uniform.prototype._connectRooms = function(room1, room2) {
/*
room1.debug();
room2.debug();
*/
var center1 = room1.getCenter();
var center2 = room2.getCenter();
var diffX = center2[0] - center1[0];
var diffY = center2[1] - center1[1];
if (Math.abs(diffX) < Math.abs(diffY)) { /* first try connecting north-south walls */
var dirIndex1 = (diffY > 0 ? 2 : 0);
var dirIndex2 = (dirIndex1 + 2) % 4;
var min = room2.getLeft();
var max = room2.getRight();
var index = 0;
} else { /* first try connecting east-west walls */
var dirIndex1 = (diffX > 0 ? 1 : 3);
var dirIndex2 = (dirIndex1 + 2) % 4;
var min = room2.getTop();
var max = room2.getBottom();
var index = 1;
}
var start = this._placeInWall(room1, dirIndex1); /* corridor will start here */
if (!start) { return false; }
if (start[index] >= min && start[index] <= max) { /* possible to connect with straight line (I-like) */
var end = start.slice();
var value = null;
switch (dirIndex2) {
case 0: value = room2.getTop()-1; break;
case 1: value = room2.getRight()+1; break;
case 2: value = room2.getBottom()+1; break;
case 3: value = room2.getLeft()-1; break;
}
end[(index+1)%2] = value;
this._digLine([start, end]);
} else if (start[index] < min-1 || start[index] > max+1) { /* need to switch target wall (L-like) */
var diff = start[index] - center2[index];
switch (dirIndex2) {
case 0:
case 1: var rotation = (diff < 0 ? 3 : 1); break;
case 2:
case 3: var rotation = (diff < 0 ? 1 : 3); break;
}
dirIndex2 = (dirIndex2 + rotation) % 4;
var end = this._placeInWall(room2, dirIndex2);
if (!end) { return false; }
var mid = [0, 0];
mid[index] = start[index];
var index2 = (index+1)%2;
mid[index2] = end[index2];
this._digLine([start, mid, end]);
} else { /* use current wall pair, but adjust the line in the middle (S-like) */
var index2 = (index+1)%2;
var end = this._placeInWall(room2, dirIndex2);
if (!end) { return; }
var mid = Math.round((end[index2] + start[index2])/2);
var mid1 = [0, 0];
var mid2 = [0, 0];
mid1[index] = start[index];
mid1[index2] = mid;
mid2[index] = end[index];
mid2[index2] = mid;
this._digLine([start, mid1, mid2, end]);
}
room1.addDoor(start[0], start[1]);
room2.addDoor(end[0], end[1]);
var index = this._unconnected.indexOf(room1);
if (index != -1) {
this._unconnected.splice(index, 1);
this._connected.push(room1);
}
var index = this._unconnected.indexOf(room2);
if (index != -1) {
this._unconnected.splice(index, 1);
this._connected.push(room2);
}
return true;
}
ROT.Map.Uniform.prototype._placeInWall = function(room, dirIndex) {
var start = [0, 0];
var dir = [0, 0];
var length = 0;
switch (dirIndex) {
case 0:
dir = [1, 0];
start = [room.getLeft(), room.getTop()-1];
length = room.getRight()-room.getLeft()+1;
break;
case 1:
dir = [0, 1];
start = [room.getRight()+1, room.getTop()];
length = room.getBottom()-room.getTop()+1;
break;
case 2:
dir = [1, 0];
start = [room.getLeft(), room.getBottom()+1];
length = room.getRight()-room.getLeft()+1;
break;
case 3:
dir = [0, 1];
start = [room.getLeft()-1, room.getTop()];
length = room.getBottom()-room.getTop()+1;
break;
}
var avail = [];
var lastBadIndex = -2;
for (var i=0;i<length;i++) {
var x = start[0] + i*dir[0];
var y = start[1] + i*dir[1];
avail.push(null);
var isWall = (this._map[x][y] == 1);
if (isWall) {
if (lastBadIndex != i-1) { avail[i] = [x, y]; }
} else {
lastBadIndex = i;
if (i) { avail[i-1] = null; }
}
}
for (var i=avail.length-1; i>=0; i--) {
if (!avail[i]) { avail.splice(i, 1); }
}
return (avail.length ? avail.random() : null);
}
/**
* Dig a polyline.
*/
ROT.Map.Uniform.prototype._digLine = function(points) {
for (var i=1;i<points.length;i++) {
var start = points[i-1];
var end = points[i];
var corridor = new ROT.Map.Feature.Corridor(start[0], start[1], end[0], end[1]);
corridor.create(this._digCallback);
this._corridors.push(corridor);
}
}
ROT.Map.Uniform.prototype._digCallback = function(x, y, value) {
this._map[x][y] = value;
if (value == 0) { this._dug++; }
}
ROT.Map.Uniform.prototype._isWallCallback = function(x, y) {
if (x < 0 || y < 0 || x >= this._width || y >= this._height) { return false; }
return (this._map[x][y] == 1);
}
ROT.Map.Uniform.prototype._canBeDugCallback = function(x, y) {
if (x < 1 || y < 1 || x+1 >= this._width || y+1 >= this._height) { return false; }
return (this._map[x][y] == 1);
}