Spaces:
Running
Running
/** | |
* @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); | |
} | |