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