lychees's picture
Upload 569 files
87b3b3a
/**
* @class Random dungeon generator using human-like digging patterns.
* Heavily based on Mike Anderson's ideas from the "Tyrant" algo, mentioned at
* http://www.roguebasin.roguelikedevelopment.org/index.php?title=Dungeon-Building_Algorithm.
* @augments ROT.Map.Dungeon
*/
ROT.Map.Digger = 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 */
corridorLength: [3, 10], /* corridor minimum and maximum length */
dugPercentage: 0.2, /* we stop after this percentage of level area has been dug out */
timeLimit: 1000 /* we stop after this much time has passed (msec) */
}
for (var p in options) { this._options[p] = options[p]; }
this._features = {
"Room": 4,
"Corridor": 4
}
this._featureAttempts = 20; /* how many times do we try to create a feature on a suitable wall */
this._walls = {}; /* these are available for digging */
this._digCallback = this._digCallback.bind(this);
this._canBeDugCallback = this._canBeDugCallback.bind(this);
this._isWallCallback = this._isWallCallback.bind(this);
this._priorityWallCallback = this._priorityWallCallback.bind(this);
}
ROT.Map.Digger.extend(ROT.Map.Dungeon);
/**
* Create a map
* @see ROT.Map#create
*/
ROT.Map.Digger.prototype.create = function(callback) {
this._rooms = [];
this._corridors = [];
this._map = this._fillMap(1);
this._walls = {};
this._dug = 0;
var area = (this._width-2) * (this._height-2);
this._firstRoom();
var t1 = Date.now();
do {
var t2 = Date.now();
if (t2 - t1 > this._options.timeLimit) { break; }
/* find a good wall */
var wall = this._findWall();
if (!wall) { break; } /* no more walls */
var parts = wall.split(",");
var x = parseInt(parts[0]);
var y = parseInt(parts[1]);
var dir = this._getDiggingDirection(x, y);
if (!dir) { continue; } /* this wall is not suitable */
// console.log("wall", x, y);
/* try adding a feature */
var featureAttempts = 0;
do {
featureAttempts++;
if (this._tryFeature(x, y, dir[0], dir[1])) { /* feature added */
if (this._rooms.length + this._corridors.length == 2) { this._rooms[0].addDoor(x, y); } /* first room oficially has doors */
this._removeSurroundingWalls(x, y);
this._removeSurroundingWalls(x-dir[0], y-dir[1]);
break;
}
} while (featureAttempts < this._featureAttempts);
var priorityWalls = 0;
for (var id in this._walls) {
if (this._walls[id] > 1) { priorityWalls++; }
}
} while (this._dug/area < this._options.dugPercentage || priorityWalls); /* fixme number of priority walls */
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]);
}
}
}
this._walls = {};
this._map = null;
return this;
}
ROT.Map.Digger.prototype._digCallback = function(x, y, value) {
if (value == 0 || value == 2) { /* empty */
this._map[x][y] = 0;
this._dug++;
} else { /* wall */
this._walls[x+","+y] = 1;
}
}
ROT.Map.Digger.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.Digger.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);
}
ROT.Map.Digger.prototype._priorityWallCallback = function(x, y) {
this._walls[x+","+y] = 2;
}
ROT.Map.Digger.prototype._firstRoom = function() {
var cx = Math.floor(this._width/2);
var cy = Math.floor(this._height/2);
var room = ROT.Map.Feature.Room.createRandomCenter(cx, cy, this._options);
this._rooms.push(room);
room.create(this._digCallback);
}
/**
* Get a suitable wall
*/
ROT.Map.Digger.prototype._findWall = function() {
var prio1 = [];
var prio2 = [];
for (var id in this._walls) {
var prio = this._walls[id];
if (prio == 2) {
prio2.push(id);
} else {
prio1.push(id);
}
}
var arr = (prio2.length ? prio2 : prio1);
if (!arr.length) { return null; } /* no walls :/ */
var id = arr.random();
delete this._walls[id];
return id;
}
/**
* Tries adding a feature
* @returns {bool} was this a successful try?
*/
ROT.Map.Digger.prototype._tryFeature = function(x, y, dx, dy) {
var feature = null;
var total = 0;
for (var p in this._features) { total += this._features[p]; }
var random = Math.floor(ROT.RNG.getUniform()*total);
var sub = 0;
for (var p in this._features) {
sub += this._features[p];
if (random < sub) {
feature = ROT.Map.Feature[p];
break;
}
}
feature = feature.createRandomAt(x, y, dx, dy, this._options);
if (!feature.isValid(this._isWallCallback, this._canBeDugCallback)) {
// console.log("not valid");
// feature.debug();
return false;
}
feature.create(this._digCallback);
// feature.debug();
if (feature instanceof ROT.Map.Feature.Room) { this._rooms.push(feature); }
if (feature instanceof ROT.Map.Feature.Corridor) {
feature.createPriorityWalls(this._priorityWallCallback);
this._corridors.push(feature);
}
return true;
}
ROT.Map.Digger.prototype._removeSurroundingWalls = function(cx, cy) {
var deltas = ROT.DIRS[4];
for (var i=0;i<deltas.length;i++) {
var delta = deltas[i];
var x = cx + delta[0];
var y = cy + delta[1];
delete this._walls[x+","+y];
var x = cx + 2*delta[0];
var y = cy + 2*delta[1];
delete this._walls[x+","+y];
}
}
/**
* Returns vector in "digging" direction, or false, if this does not exist (or is not unique)
*/
ROT.Map.Digger.prototype._getDiggingDirection = function(cx, cy) {
var result = null;
var deltas = ROT.DIRS[4];
for (var i=0;i<deltas.length;i++) {
var delta = deltas[i];
var x = cx + delta[0];
var y = cy + delta[1];
if (x < 0 || y < 0 || x >= this._width || y >= this._width) { return null; }
if (!this._map[x][y]) { /* there already is another empty neighbor! */
if (result) { return null; }
result = delta;
}
}
/* no empty neighbor */
if (!result) { return null; }
return [-result[0], -result[1]];
}