/* This is rot.js, the ROguelike Toolkit in JavaScript. Version 0.5~dev, generated on Wed Dec 18 21:12:34 CET 2013. */ /** * @namespace Top-level ROT namespace */ var ROT = { /** * @returns {bool} Is rot.js supported by this browser? */ isSupported: function() { return !!(document.createElement("canvas").getContext && Function.prototype.bind); }, /** Default with for display and map generators */ DEFAULT_WIDTH: 80, /** Default height for display and map generators */ DEFAULT_HEIGHT: 25, /** Directional constants. Ordering is important! */ DIRS: { "4": [ [ 0, -1], [ 1, 0], [ 0, 1], [-1, 0] ], "8": [ [ 0, -1], [ 1, -1], [ 1, 0], [ 1, 1], [ 0, 1], [-1, 1], [-1, 0], [-1, -1] ], "6": [ [-1, -1], [ 1, -1], [ 2, 0], [ 1, 1], [-1, 1], [-2, 0] ] }, /** Cancel key. */ VK_CANCEL: 3, /** Help key. */ VK_HELP: 6, /** Backspace key. */ VK_BACK_SPACE: 8, /** Tab key. */ VK_TAB: 9, /** 5 key on Numpad when NumLock is unlocked. Or on Mac, clear key which is positioned at NumLock key. */ VK_CLEAR: 12, /** Return/enter key on the main keyboard. */ VK_RETURN: 13, /** Reserved, but not used. */ VK_ENTER: 14, /** Shift key. */ VK_SHIFT: 16, /** Control key. */ VK_CONTROL: 17, /** Alt (Option on Mac) key. */ VK_ALT: 18, /** Pause key. */ VK_PAUSE: 19, /** Caps lock. */ VK_CAPS_LOCK: 20, /** Escape key. */ VK_ESCAPE: 27, /** Space bar. */ VK_SPACE: 32, /** Page Up key. */ VK_PAGE_UP: 33, /** Page Down key. */ VK_PAGE_DOWN: 34, /** End key. */ VK_END: 35, /** Home key. */ VK_HOME: 36, /** Left arrow. */ VK_LEFT: 37, /** Up arrow. */ VK_UP: 38, /** Right arrow. */ VK_RIGHT: 39, /** Down arrow. */ VK_DOWN: 40, /** Print Screen key. */ VK_PRINTSCREEN: 44, /** Ins(ert) key. */ VK_INSERT: 45, /** Del(ete) key. */ VK_DELETE: 46, /***/ VK_0: 48, /***/ VK_1: 49, /***/ VK_2: 50, /***/ VK_3: 51, /***/ VK_4: 52, /***/ VK_5: 53, /***/ VK_6: 54, /***/ VK_7: 55, /***/ VK_8: 56, /***/ VK_9: 57, /** Colon (:) key. Requires Gecko 15.0 */ VK_COLON: 58, /** Semicolon (;) key. */ VK_SEMICOLON: 59, /** Less-than (<) key. Requires Gecko 15.0 */ VK_LESS_THAN: 60, /** Equals (=) key. */ VK_EQUALS: 61, /** Greater-than (>) key. Requires Gecko 15.0 */ VK_GREATER_THAN: 62, /** Question mark (?) key. Requires Gecko 15.0 */ VK_QUESTION_MARK: 63, /** Atmark (@) key. Requires Gecko 15.0 */ VK_AT: 64, /***/ VK_A: 65, /***/ VK_B: 66, /***/ VK_C: 67, /***/ VK_D: 68, /***/ VK_E: 69, /***/ VK_F: 70, /***/ VK_G: 71, /***/ VK_H: 72, /***/ VK_I: 73, /***/ VK_J: 74, /***/ VK_K: 75, /***/ VK_L: 76, /***/ VK_M: 77, /***/ VK_N: 78, /***/ VK_O: 79, /***/ VK_P: 80, /***/ VK_Q: 81, /***/ VK_R: 82, /***/ VK_S: 83, /***/ VK_T: 84, /***/ VK_U: 85, /***/ VK_V: 86, /***/ VK_W: 87, /***/ VK_X: 88, /***/ VK_Y: 89, /***/ VK_Z: 90, /***/ VK_CONTEXT_MENU: 93, /** 0 on the numeric keypad. */ VK_NUMPAD0: 96, /** 1 on the numeric keypad. */ VK_NUMPAD1: 97, /** 2 on the numeric keypad. */ VK_NUMPAD2: 98, /** 3 on the numeric keypad. */ VK_NUMPAD3: 99, /** 4 on the numeric keypad. */ VK_NUMPAD4: 100, /** 5 on the numeric keypad. */ VK_NUMPAD5: 101, /** 6 on the numeric keypad. */ VK_NUMPAD6: 102, /** 7 on the numeric keypad. */ VK_NUMPAD7: 103, /** 8 on the numeric keypad. */ VK_NUMPAD8: 104, /** 9 on the numeric keypad. */ VK_NUMPAD9: 105, /** * on the numeric keypad. */ VK_MULTIPLY: 106, /** + on the numeric keypad. */ VK_ADD: 107, /***/ VK_SEPARATOR: 108, /** - on the numeric keypad. */ VK_SUBTRACT: 109, /** Decimal point on the numeric keypad. */ VK_DECIMAL: 110, /** / on the numeric keypad. */ VK_DIVIDE: 111, /** F1 key. */ VK_F1: 112, /** F2 key. */ VK_F2: 113, /** F3 key. */ VK_F3: 114, /** F4 key. */ VK_F4: 115, /** F5 key. */ VK_F5: 116, /** F6 key. */ VK_F6: 117, /** F7 key. */ VK_F7: 118, /** F8 key. */ VK_F8: 119, /** F9 key. */ VK_F9: 120, /** F10 key. */ VK_F10: 121, /** F11 key. */ VK_F11: 122, /** F12 key. */ VK_F12: 123, /** F13 key. */ VK_F13: 124, /** F14 key. */ VK_F14: 125, /** F15 key. */ VK_F15: 126, /** F16 key. */ VK_F16: 127, /** F17 key. */ VK_F17: 128, /** F18 key. */ VK_F18: 129, /** F19 key. */ VK_F19: 130, /** F20 key. */ VK_F20: 131, /** F21 key. */ VK_F21: 132, /** F22 key. */ VK_F22: 133, /** F23 key. */ VK_F23: 134, /** F24 key. */ VK_F24: 135, /** Num Lock key. */ VK_NUM_LOCK: 144, /** Scroll Lock key. */ VK_SCROLL_LOCK: 145, /** Circumflex (^) key. Requires Gecko 15.0 */ VK_CIRCUMFLEX: 160, /** Exclamation (!) key. Requires Gecko 15.0 */ VK_EXCLAMATION: 161, /** Double quote () key. Requires Gecko 15.0 */ VK_DOUBLE_QUOTE: 162, /** Hash (#) key. Requires Gecko 15.0 */ VK_HASH: 163, /** Dollar sign ($) key. Requires Gecko 15.0 */ VK_DOLLAR: 164, /** Percent (%) key. Requires Gecko 15.0 */ VK_PERCENT: 165, /** Ampersand (&) key. Requires Gecko 15.0 */ VK_AMPERSAND: 166, /** Underscore (_) key. Requires Gecko 15.0 */ VK_UNDERSCORE: 167, /** Open parenthesis (() key. Requires Gecko 15.0 */ VK_OPEN_PAREN: 168, /** Close parenthesis ()) key. Requires Gecko 15.0 */ VK_CLOSE_PAREN: 169, /* Asterisk (*) key. Requires Gecko 15.0 */ VK_ASTERISK: 170, /** Plus (+) key. Requires Gecko 15.0 */ VK_PLUS: 171, /** Pipe (|) key. Requires Gecko 15.0 */ VK_PIPE: 172, /** Hyphen-US/docs/Minus (-) key. Requires Gecko 15.0 */ VK_HYPHEN_MINUS: 173, /** Open curly bracket ({) key. Requires Gecko 15.0 */ VK_OPEN_CURLY_BRACKET: 174, /** Close curly bracket (}) key. Requires Gecko 15.0 */ VK_CLOSE_CURLY_BRACKET: 175, /** Tilde (~) key. Requires Gecko 15.0 */ VK_TILDE: 176, /** Comma (,) key. */ VK_COMMA: 188, /** Period (.) key. */ VK_PERIOD: 190, /** Slash (/) key. */ VK_SLASH: 191, /** Back tick (`) key. */ VK_BACK_QUOTE: 192, /** Open square bracket ([) key. */ VK_OPEN_BRACKET: 219, /** Back slash (\) key. */ VK_BACK_SLASH: 220, /** Close square bracket (]) key. */ VK_CLOSE_BRACKET: 221, /** Quote (''') key. */ VK_QUOTE: 222, /** Meta key on Linux, Command key on Mac. */ VK_META: 224, /** AltGr key on Linux. Requires Gecko 15.0 */ VK_ALTGR: 225, /** Windows logo key on Windows. Or Super or Hyper key on Linux. Requires Gecko 15.0 */ VK_WIN: 91, /** Linux support for this keycode was added in Gecko 4.0. */ VK_KANA: 21, /** Linux support for this keycode was added in Gecko 4.0. */ VK_HANGUL: 21, /** 英数 key on Japanese Mac keyboard. Requires Gecko 15.0 */ VK_EISU: 22, /** Linux support for this keycode was added in Gecko 4.0. */ VK_JUNJA: 23, /** Linux support for this keycode was added in Gecko 4.0. */ VK_FINAL: 24, /** Linux support for this keycode was added in Gecko 4.0. */ VK_HANJA: 25, /** Linux support for this keycode was added in Gecko 4.0. */ VK_KANJI: 25, /** Linux support for this keycode was added in Gecko 4.0. */ VK_CONVERT: 28, /** Linux support for this keycode was added in Gecko 4.0. */ VK_NONCONVERT: 29, /** Linux support for this keycode was added in Gecko 4.0. */ VK_ACCEPT: 30, /** Linux support for this keycode was added in Gecko 4.0. */ VK_MODECHANGE: 31, /** Linux support for this keycode was added in Gecko 4.0. */ VK_SELECT: 41, /** Linux support for this keycode was added in Gecko 4.0. */ VK_PRINT: 42, /** Linux support for this keycode was added in Gecko 4.0. */ VK_EXECUTE: 43, /** Linux support for this keycode was added in Gecko 4.0. */ VK_SLEEP: 95 }; /** * @namespace * Contains text tokenization and breaking routines */ ROT.Text = { RE_COLORS: /%([bc]){([^}]*)}/g, /* token types */ TYPE_TEXT: 0, TYPE_NEWLINE: 1, TYPE_FG: 2, TYPE_BG: 3, /** * Measure size of a resulting text block */ measure: function(str, maxWidth) { var result = {width:0, height:1}; var tokens = this.tokenize(str, maxWidth); var lineWidth = 0; for (var i=0;i maxWidth) { /* line too long, find a suitable breaking spot */ /* is it possible to break within this token? */ var index = -1; while (1) { var nextIndex = token.value.indexOf(" ", index+1); if (nextIndex == -1) { break; } if (lineLength + nextIndex > maxWidth) { break; } index = nextIndex; } if (index != -1) { /* break at space within this one */ token.value = this._breakInsideToken(tokens, i, index, true); } else if (lastTokenWithSpace != -1) { /* is there a previous token where a break can occur? */ var token = tokens[lastTokenWithSpace]; var breakIndex = token.value.lastIndexOf(" "); token.value = this._breakInsideToken(tokens, lastTokenWithSpace, breakIndex, true); i = lastTokenWithSpace; } else { /* force break in this token */ token.value = this._breakInsideToken(tokens, i, maxWidth-lineLength, false); } } else { /* line not long, continue */ lineLength += token.value.length; if (token.value.indexOf(" ") != -1) { lastTokenWithSpace = i; } } i++; /* advance to next token */ } tokens.push({type: ROT.Text.TYPE_NEWLINE}); /* insert fake newline to fix the last text line */ /* remove trailing space from text tokens before newlines */ var lastTextToken = null; for (var i=0;i= this._context.canvas.width || y >= this._context.canvas.height) { return [-1, -1]; } return this._backend.eventToPosition(x, y); } /** * @param {int} x * @param {int} y * @param {string || string[]} ch One or more chars (will be overlapping themselves) * @param {string} [fg] foreground color * @param {string} [bg] background color */ ROT.Display.prototype.draw = function(x, y, ch, fg, bg) { if (!fg) { fg = this._options.fg; } if (!bg) { bg = this._options.bg; } this._data[x+","+y] = [x, y, ch, fg, bg]; if (this._dirty === true) { return; } /* will already redraw everything */ if (!this._dirty) { this._dirty = {}; } /* first! */ this._dirty[x+","+y] = true; } /** * Draws a text at given position. Optionally wraps at a maximum length. Currently does not work with hex layout. * @param {int} x * @param {int} y * @param {string} text May contain color/background format specifiers, %c{name}/%b{name}, both optional. %c{}/%b{} resets to default. * @param {int} [maxWidth] wrap at what width? * @returns {int} lines drawn */ ROT.Display.prototype.drawText = function(x, y, text, maxWidth) { var fg = null; var bg = null; var cx = x; var cy = y; var lines = 1; if (!maxWidth) { maxWidth = this._options.width-x; } var tokens = ROT.Text.tokenize(text, maxWidth); while (tokens.length) { /* interpret tokenized opcode stream */ var token = tokens.shift(); switch (token.type) { case ROT.Text.TYPE_TEXT: for (var i=0;i 1) { /* too wide with current aspect ratio */ boxHeight = Math.floor(boxHeight / widthFraction); } return Math.floor(boxHeight / this._options.spacing); } ROT.Display.Rect.prototype.eventToPosition = function(x, y) { return [Math.floor(x/this._spacingX), Math.floor(y/this._spacingY)]; } /** * @class Hexagonal backend * @private */ ROT.Display.Hex = function(context) { ROT.Display.Backend.call(this, context); this._spacingX = 0; this._spacingY = 0; this._hexSize = 0; this._options = {}; } ROT.Display.Hex.extend(ROT.Display.Backend); ROT.Display.Hex.prototype.compute = function(options) { this._options = options; var charWidth = Math.ceil(this._context.measureText("W").width); this._hexSize = Math.floor(options.spacing * (options.fontSize + charWidth/Math.sqrt(3)) / 2); this._spacingX = this._hexSize * Math.sqrt(3) / 2; this._spacingY = this._hexSize * 1.5; this._context.canvas.width = Math.ceil( (options.width + 1) * this._spacingX ); this._context.canvas.height = Math.ceil( (options.height - 1) * this._spacingY + 2*this._hexSize ); } ROT.Display.Hex.prototype.draw = function(data, clearBefore) { var x = data[0]; var y = data[1]; var ch = data[2]; var fg = data[3]; var bg = data[4]; var cx = (x+1) * this._spacingX; var cy = y * this._spacingY + this._hexSize; if (clearBefore) { this._context.fillStyle = bg; this._fill(cx, cy); } if (!ch) { return; } this._context.fillStyle = fg; var chars = [].concat(ch); for (var i=0;i>> 0) * this._frac; seed = (seed*69069 + 1) >>> 0; this._s1 = seed * this._frac; seed = (seed*69069 + 1) >>> 0; this._s2 = seed * this._frac; this._c = 1; return this; }, /** * @returns {float} Pseudorandom value [0,1), uniformly distributed */ getUniform: function() { var t = 2091639 * this._s0 + this._c * this._frac; this._s0 = this._s1; this._s1 = this._s2; this._c = t | 0; this._s2 = t - this._c; return this._s2; }, /** * @param {float} [mean=0] Mean value * @param {float} [stddev=1] Standard deviation. ~95% of the absolute values will be lower than 2*stddev. * @returns {float} A normally distributed pseudorandom value */ getNormal: function(mean, stddev) { do { var u = 2*this.getUniform()-1; var v = 2*this.getUniform()-1; var r = u*u + v*v; } while (r > 1 || r == 0); var gauss = u * Math.sqrt(-2*Math.log(r)/r); return (mean || 0) + gauss*(stddev || 1); }, /** * @returns {int} Pseudorandom value [1,100] inclusive, uniformly distributed */ getPercentage: function() { return 1 + Math.floor(this.getUniform()*100); }, /** * @param {object} data key=whatever, value=weight (relative probability) * @returns {string} whatever */ getWeightedValue: function(data) { var avail = []; var total = 0; for (var id in data) { total += data[id]; } var random = Math.floor(this.getUniform()*total); var part = 0; for (var id in data) { part += data[id]; if (random < part) { return id; } } return null; }, /** * Get RNG state. Useful for storing the state and re-setting it via setState. * @returns {?} Internal state */ getState: function() { return [this._s0, this._s1, this._s2, this._c]; }, /** * Set a previously retrieved state. * @param {?} state */ setState: function(state) { this._s0 = state[0]; this._s1 = state[1]; this._s2 = state[2]; this._c = state[3]; return this; }, _s0: 0, _s1: 0, _s2: 0, _c: 0, _frac: 2.3283064365386963e-10 /* 2^-32 */ } ROT.RNG.setSeed(Date.now()); /** * @class (Markov process)-based string generator. * Copied from a RogueBasin article. * Offers configurable order and prior. * @param {object} [options] * @param {bool} [options.words=false] Use word mode? * @param {int} [options.order=3] * @param {float} [options.prior=0.001] */ ROT.StringGenerator = function(options) { this._options = { words: false, order: 3, prior: 0.001 } for (var p in options) { this._options[p] = options[p]; } this._boundary = String.fromCharCode(0); this._suffix = this._boundary; this._prefix = []; for (var i=0;i this._options.order) { context = context.slice(-this._options.order); } else if (context.length < this._options.order) { context = this._prefix.slice(0, this._options.order - context.length).concat(context); } while (!(this._join(context) in this._data) && context.length > 0) { context = context.slice(1); } return context; } ROT.StringGenerator.prototype._pickRandom = function(data) { var total = 0; for (var id in data) { total += data[id]; } var random = ROT.RNG.getUniform()*total; var part = 0; for (var id in data) { part += data[id]; if (random < part) { return id; } } } /** * @class Generic event queue: stores events and retrieves them based on their time */ ROT.EventQueue = function() { this._time = 0; this._events = []; this._eventTimes = []; } /** * @returns {number} Elapsed time */ ROT.EventQueue.prototype.getTime = function() { return this._time; } /** * Clear all scheduled events */ ROT.EventQueue.prototype.clear = function() { this._events = []; this._eventTimes = []; return this; } /** * @param {?} event * @param {number} time */ ROT.EventQueue.prototype.add = function(event, time) { var index = this._events.length; for (var i=0;i time) { index = i; break; } } this._events.splice(index, 0, event); this._eventTimes.splice(index, 0, time); } /** * Locates the nearest event, advances time if necessary. Returns that event and removes it from the queue. * @returns {? || null} The event previously added by addEvent, null if no event available */ ROT.EventQueue.prototype.get = function() { if (!this._events.length) { return null; } var time = this._eventTimes.splice(0, 1)[0]; if (time > 0) { /* advance */ this._time += time; for (var i=0;i= width || y >= height) { return false; } return map[x][y]; } /** * @class Maze generator - Eller's algorithm * See http://homepages.cwi.nl/~tromp/maze.html for explanation * @augments ROT.Map */ ROT.Map.EllerMaze = function(width, height) { ROT.Map.call(this, width, height); } ROT.Map.EllerMaze.extend(ROT.Map); ROT.Map.EllerMaze.prototype.create = function(callback) { var map = this._fillMap(1); var w = Math.ceil((this._width-2)/2); var rand = 9/24; var L = []; var R = []; for (var i=0;i rand) { this._addToList(i, L, R); map[x+1][y] = 0; } /* bottom connection */ if (i != L[i] && ROT.RNG.getUniform() > rand) { /* remove connection */ this._removeFromList(i, L, R); } else { /* create connection */ map[x][y+1] = 0; } } } /* last row */ for (var i=0;i rand)) { /* dig right also if the cell is separated, so it gets connected to the rest of maze */ this._addToList(i, L, R); map[x+1][y] = 0; } this._removeFromList(i, L, R); } for (var i=0;i= this._width || x < 0 || y >= this._width) { continue; } result += (this._map[x][y] == 1 ? 1 : 0); } return result; } /** * @class Dungeon map: has rooms and corridors * @augments ROT.Map */ ROT.Map.Dungeon = function(width, height) { ROT.Map.call(this, width, height); this._rooms = []; /* list of all rooms */ this._corridors = []; } ROT.Map.Dungeon.extend(ROT.Map); /** * Get all generated rooms * @returns {ROT.Map.Feature.Room[]} */ ROT.Map.Dungeon.prototype.getRooms = function() { return this._rooms; } /** * Get all generated corridors * @returns {ROT.Map.Feature.Corridor[]} */ ROT.Map.Dungeon.prototype.getCorridors = function() { return this._corridors; } /** * @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 */ this._addDoors(); if (callback) { for (var i=0;i= 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 = ROT.RNG.getWeightedValue(this._features); feature = ROT.Map.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= 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]]; } /** * Find empty spaces surrounding rooms, and apply doors. */ ROT.Map.Digger.prototype._addDoors = function() { var data = this._map; var isWallCallback = function(x, y) { return (data[x][y] == 1); } for (var i = 0; i < this._rooms.length; i++ ) { var room = this._rooms[i]; room.clearDoors(); room.addDoors(isWallCallback); } } /** * @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._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 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=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= 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); } /** * @author hyakugei * @class Dungeon generator which uses the "orginal" Rogue dungeon generation algorithm. See http://kuoi.com/~kamikaze/GameDesign/art07_rogue_dungeon.php * @augments ROT.Map * @param {int} [width=ROT.DEFAULT_WIDTH] * @param {int} [height=ROT.DEFAULT_HEIGHT] * @param {object} [options] Options * @param {int[]} [options.cellWidth=3] Number of cells to create on the horizontal (number of rooms horizontally) * @param {int[]} [options.cellHeight=3] Number of cells to create on the vertical (number of rooms vertically) * @param {int} [options.roomWidth] Room min and max width - normally set auto-magically via the constructor. * @param {int} [options.roomHeight] Room min and max height - normally set auto-magically via the constructor. */ ROT.Map.Rogue = function(width, height, options) { ROT.Map.call(this, width, height); this._options = { cellWidth: 3, // NOTE to self, these could probably work the same as the roomWidth/room Height values cellHeight: 3 // ie. as an array with min-max values for each direction.... } for (var p in options) { this._options[p] = options[p]; } /* Set the room sizes according to the over-all width of the map, and the cell sizes. */ if (!this._options.hasOwnProperty("roomWidth")) { this._options["roomWidth"] = this._calculateRoomSize(width, this._options["cellWidth"]); } if (!this._options.hasOwnProperty["roomHeight"]) { this._options["roomHeight"] = this._calculateRoomSize(height, this._options["cellHeight"]); } } ROT.Map.Rogue.extend(ROT.Map); /** * @see ROT.Map#create */ ROT.Map.Rogue.prototype.create = function(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 (var i = 0; i < this._width; i++) { for (var j = 0; j < this._height; j++) { callback(i, j, this.map[i][j]); } } } return this; } ROT.Map.Rogue.prototype._getRandomInt = function(min, max) { return Math.floor(ROT.RNG.getUniform() * (max - min + 1)) + min; } ROT.Map.Rogue.prototype._calculateRoomSize = function(size, cell) { var max = Math.floor((size/cell) * 0.8); var min = Math.floor((size/cell) * 0.25); if (min < 2) min = 2; if (max < 2) max = 2; return [min, max]; } ROT.Map.Rogue.prototype._initRooms = function () { // create rooms array. This is the "grid" list from the algo. for (var i = 0; i < this._options.cellWidth; i++) { this.rooms.push([]); for(var j = 0; j < this._options.cellHeight; j++) { this.rooms[i].push({"x":0, "y":0, "width":0, "height":0, "connections":[], "cellx":i, "celly":j}); } } } ROT.Map.Rogue.prototype._connectRooms = function() { //pick random starting grid var cgx = this._getRandomInt(0, this._options.cellWidth-1); var cgy = this._getRandomInt(0, this._options.cellHeight-1); var idx; var ncgx; var ncgy; var found = false; var room; var otherRoom; // find unconnected neighbour cells do { //var dirToCheck = [0,1,2,3,4,5,6,7]; var dirToCheck = [0,2,4,6]; dirToCheck = dirToCheck.randomize(); do { found = false; idx = dirToCheck.pop(); ncgx = cgx + ROT.DIRS[8][idx][0]; ncgy = cgy + ROT.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) } ROT.Map.Rogue.prototype._connectUnconnectedRooms = function() { //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). var cw = this._options.cellWidth; var ch = this._options.cellHeight; var randomConnectedCell; this.connectedCells = this.connectedCells.randomize(); var room; var otherRoom; var validRoom; for (var i = 0; i < this._options.cellWidth; i++) { for (var j = 0; j < this._options.cellHeight; j++) { room = this.rooms[i][j]; if (room["connections"].length == 0) { var directions = [0,2,4,6]; directions = directions.randomize(); var validRoom = false; do { var dirIdx = directions.pop(); var newI = i + ROT.DIRS[8][dirIdx][0]; var newJ = j + ROT.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 (var 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."); } } } } } ROT.Map.Rogue.prototype._createRandomRoomConnections = function(connections) { // Empty for now. } ROT.Map.Rogue.prototype._createRooms = function() { // Create Rooms var w = this._width; var h = this._height; var cw = this._options.cellWidth; var ch = this._options.cellHeight; var cwp = Math.floor(this._width / cw); var chp = Math.floor(this._height / ch); var roomw; var roomh; var roomWidth = this._options["roomWidth"]; var roomHeight = this._options["roomHeight"]; var sx; var sy; var tx; var ty; var otherRoom; for (var i = 0; i < cw; i++) { for (var j = 0; j < ch; j++) { sx = cwp * i; sy = chp * j; if (sx == 0) sx = 1; if (sy == 0) sy = 1; roomw = this._getRandomInt(roomWidth[0], roomWidth[1]); roomh = this._getRandomInt(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++; } } var sxOffset = Math.round(this._getRandomInt(0, cwp-roomw)/2); var syOffset = Math.round(this._getRandomInt(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 (var ii = sx; ii < sx + roomw; ii++) { for (var jj = sy; jj < sy + roomh; jj++) { this.map[ii][jj] = 0; } } } } } ROT.Map.Rogue.prototype._getWallPosition = function(aRoom, aDirection) { var rx; var ry; var door; if (aDirection == 1 || aDirection == 3) { rx = this._getRandomInt(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 if (aDirection == 2 || aDirection == 4) { ry = this._getRandomInt(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]; } /*** * @param startPosition a 2 element array * @param endPosition a 2 element array */ ROT.Map.Rogue.prototype._drawCorridore = function (startPosition, endPosition) { var xOffset = endPosition[0] - startPosition[0]; var yOffset = endPosition[1] - startPosition[1]; var xpos = startPosition[0]; var ypos = startPosition[1]; var tempDist; var xDir; var yDir; var move; // 2 element array, element 0 is the direction, element 1 is the total value to move. var moves = []; // a list of 2 element arrays var xAbs = Math.abs(xOffset); var yAbs = Math.abs(yOffset); var percent = ROT.RNG.getUniform(); // used to split the move at different places along the long axis var firstHalf = percent; var 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 += ROT.DIRS[8][move[0]][0]; ypos += ROT.DIRS[8][move[0]][1]; this.map[xpos][ypos] = 0; move[1] = move[1] - 1; } } } ROT.Map.Rogue.prototype._createCorridors = function () { // Draw Corridors between connected rooms var cw = this._options.cellWidth; var ch = this._options.cellHeight; var room; var connection; var otherRoom; var wall; var otherWall; for (var i = 0; i < cw; i++) { for (var j = 0; j < ch; j++) { room = this.rooms[i][j]; for (var 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 if(otherRoom["celly"] < room["celly"]) { wall = 1; otherWall = 3; } this._drawCorridore(this._getWallPosition(room, wall), this._getWallPosition(otherRoom, otherWall)); } } } } /** * @class Dungeon feature; has own .create() method */ ROT.Map.Feature = function() {} ROT.Map.Feature.prototype.isValid = function(canBeDugCallback) {} ROT.Map.Feature.prototype.create = function(digCallback) {} ROT.Map.Feature.prototype.debug = function() {} ROT.Map.Feature.createRandomAt = function(x, y, dx, dy, options) {} /** * @class Room * @augments ROT.Map.Feature * @param {int} x1 * @param {int} y1 * @param {int} x2 * @param {int} y2 * @param {int} [doorX] * @param {int} [doorY] */ ROT.Map.Feature.Room = function(x1, y1, x2, y2, doorX, doorY) { this._x1 = x1; this._y1 = y1; this._x2 = x2; this._y2 = y2; this._doors = {}; if (arguments.length > 4) { this.addDoor(doorX, doorY); } } ROT.Map.Feature.Room.extend(ROT.Map.Feature); /** * Room of random size, with a given doors and direction */ ROT.Map.Feature.Room.createRandomAt = function(x, y, dx, dy, options) { var min = options.roomWidth[0]; var max = options.roomWidth[1]; var width = min + Math.floor(ROT.RNG.getUniform()*(max-min+1)); var min = options.roomHeight[0]; var max = options.roomHeight[1]; var height = min + Math.floor(ROT.RNG.getUniform()*(max-min+1)); if (dx == 1) { /* to the right */ var y2 = y - Math.floor(ROT.RNG.getUniform() * height); return new this(x+1, y2, x+width, y2+height-1, x, y); } if (dx == -1) { /* to the left */ var y2 = y - Math.floor(ROT.RNG.getUniform() * height); return new this(x-width, y2, x-1, y2+height-1, x, y); } if (dy == 1) { /* to the bottom */ var x2 = x - Math.floor(ROT.RNG.getUniform() * width); return new this(x2, y+1, x2+width-1, y+height, x, y); } if (dy == -1) { /* to the top */ var x2 = x - Math.floor(ROT.RNG.getUniform() * width); return new this(x2, y-height, x2+width-1, y-1, x, y); } } /** * Room of random size, positioned around center coords */ ROT.Map.Feature.Room.createRandomCenter = function(cx, cy, options) { var min = options.roomWidth[0]; var max = options.roomWidth[1]; var width = min + Math.floor(ROT.RNG.getUniform()*(max-min+1)); var min = options.roomHeight[0]; var max = options.roomHeight[1]; var height = min + Math.floor(ROT.RNG.getUniform()*(max-min+1)); var x1 = cx - Math.floor(ROT.RNG.getUniform()*width); var y1 = cy - Math.floor(ROT.RNG.getUniform()*height); var x2 = x1 + width - 1; var y2 = y1 + height - 1; return new this(x1, y1, x2, y2); } /** * Room of random size within a given dimensions */ ROT.Map.Feature.Room.createRandom = function(availWidth, availHeight, options) { var min = options.roomWidth[0]; var max = options.roomWidth[1]; var width = min + Math.floor(ROT.RNG.getUniform()*(max-min+1)); var min = options.roomHeight[0]; var max = options.roomHeight[1]; var height = min + Math.floor(ROT.RNG.getUniform()*(max-min+1)); var left = availWidth - width - 1; var top = availHeight - height - 1; var x1 = 1 + Math.floor(ROT.RNG.getUniform()*left); var y1 = 1 + Math.floor(ROT.RNG.getUniform()*top); var x2 = x1 + width - 1; var y2 = y1 + height - 1; return new this(x1, y1, x2, y2); } ROT.Map.Feature.Room.prototype.addDoor = function(x, y) { this._doors[x+","+y] = 1; return this; } /** * @param {function} */ ROT.Map.Feature.Room.prototype.getDoors = function(callback) { for (var key in this._doors) { var parts = key.split(","); callback(parseInt(parts[0]), parseInt(parts[1])); } return this; } ROT.Map.Feature.Room.prototype.clearDoors = function() { this._doors = {}; return this; } ROT.Map.Feature.Room.prototype.addDoors = function(isWallCallback) { var left = this._x1-1; var right = this._x2+1; var top = this._y1-1; var bottom = this._y2+1; for (var x=left; x<=right; x++) { for (var y=top; y<=bottom; y++) { if (x != left && x != right && y != top && y != bottom) { continue; } if (isWallCallback(x, y)) { continue; } this.addDoor(x, y); } } return this; } ROT.Map.Feature.Room.prototype.debug = function() { console.log("room", this._x1, this._y1, this._x2, this._y2); } ROT.Map.Feature.Room.prototype.isValid = function(isWallCallback, canBeDugCallback) { var left = this._x1-1; var right = this._x2+1; var top = this._y1-1; var bottom = this._y2+1; for (var x=left; x<=right; x++) { for (var y=top; y<=bottom; y++) { if (x == left || x == right || y == top || y == bottom) { if (!isWallCallback(x, y)) { return false; } } else { if (!canBeDugCallback(x, y)) { return false; } } } } return true; } /** * @param {function} digCallback Dig callback with a signature (x, y, value). Values: 0 = empty, 1 = wall, 2 = door. Multiple doors are allowed. */ ROT.Map.Feature.Room.prototype.create = function(digCallback) { var left = this._x1-1; var right = this._x2+1; var top = this._y1-1; var bottom = this._y2+1; var value = 0; for (var x=left; x<=right; x++) { for (var y=top; y<=bottom; y++) { if (x+","+y in this._doors) { value = 2; } else if (x == left || x == right || y == top || y == bottom) { value = 1; } else { value = 0; } digCallback(x, y, value); } } } ROT.Map.Feature.Room.prototype.getCenter = function() { return [Math.round((this._x1 + this._x2)/2), Math.round((this._y1 + this._y2)/2)]; } ROT.Map.Feature.Room.prototype.getLeft = function() { return this._x1; } ROT.Map.Feature.Room.prototype.getRight = function() { return this._x2; } ROT.Map.Feature.Room.prototype.getTop = function() { return this._y1; } ROT.Map.Feature.Room.prototype.getBottom = function() { return this._y2; } /** * @class Corridor * @augments ROT.Map.Feature * @param {int} startX * @param {int} startY * @param {int} endX * @param {int} endY */ ROT.Map.Feature.Corridor = function(startX, startY, endX, endY) { this._startX = startX; this._startY = startY; this._endX = endX; this._endY = endY; this._endsWithAWall = true; } ROT.Map.Feature.Corridor.extend(ROT.Map.Feature); ROT.Map.Feature.Corridor.createRandomAt = function(x, y, dx, dy, options) { var min = options.corridorLength[0]; var max = options.corridorLength[1]; var length = min + Math.floor(ROT.RNG.getUniform()*(max-min+1)); return new this(x, y, x + dx*length, y + dy*length); } ROT.Map.Feature.Corridor.prototype.debug = function() { console.log("corridor", this._startX, this._startY, this._endX, this._endY); } ROT.Map.Feature.Corridor.prototype.isValid = function(isWallCallback, canBeDugCallback){ var sx = this._startX; var sy = this._startY; var dx = this._endX-sx; var dy = this._endY-sy; var length = 1 + Math.max(Math.abs(dx), Math.abs(dy)); if (dx) { dx = dx/Math.abs(dx); } if (dy) { dy = dy/Math.abs(dy); } var nx = dy; var ny = -dx; var ok = true; for (var i=0; i y0) { i1 = 1; j1 = 0; } else { // lower triangle, XY order: (0,0)->(1,0)->(1,1) i1 = 0; j1 = 1; } // upper triangle, YX order: (0,0)->(0,1)->(1,1) // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where // c = (3-sqrt(3))/6 var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords var y1 = y0 - j1 + G2; var x2 = x0 - 1 + 2*G2; // Offsets for last corner in (x,y) unskewed coords var y2 = y0 - 1 + 2*G2; // Work out the hashed gradient indices of the three simplex corners var ii = i.mod(count); var jj = j.mod(count); // Calculate the contribution from the three corners var t0 = 0.5 - x0*x0 - y0*y0; if (t0 >= 0) { t0 *= t0; gi = indexes[ii+perms[jj]]; var grad = this._gradients[gi]; n0 = t0 * t0 * (grad[0] * x0 + grad[1] * y0); } var t1 = 0.5 - x1*x1 - y1*y1; if (t1 >= 0) { t1 *= t1; gi = indexes[ii+i1+perms[jj+j1]]; var grad = this._gradients[gi]; n1 = t1 * t1 * (grad[0] * x1 + grad[1] * y1); } var t2 = 0.5 - x2*x2 - y2*y2; if (t2 >= 0) { t2 *= t2; gi = indexes[ii+1+perms[jj+1]]; var grad = this._gradients[gi]; n2 = t2 * t2 * (grad[0] * x2 + grad[1] * y2); } // Add contributions from each corner to get the final noise value. // The result is scaled to return values in the interval [-1,1]. return 70 * (n0 + n1 + n2); } /** * @class Abstract FOV algorithm * @param {function} lightPassesCallback Does the light pass through x,y? * @param {object} [options] * @param {int} [options.topology=8] 4/6/8 */ ROT.FOV = function(lightPassesCallback, options) { this._lightPasses = lightPassesCallback; this._options = { topology: 8 } for (var p in options) { this._options[p] = options[p]; } }; /** * Compute visibility * @param {int} x * @param {int} y * @param {int} R Maximum visibility radius * @param {function} callback */ ROT.FOV.prototype.compute = function(x, y, R, callback) {} /** * Return all neighbors in a concentric ring * @param {int} cx center-x * @param {int} cy center-y * @param {int} r range */ ROT.FOV.prototype._getCircle = function(cx, cy, r) { var result = []; var dirs, countFactor, startOffset; switch (this._options.topology) { case 4: countFactor = 1; startOffset = [0, 1]; dirs = [ ROT.DIRS[8][7], ROT.DIRS[8][1], ROT.DIRS[8][3], ROT.DIRS[8][5] ] break; case 6: dirs = ROT.DIRS[6]; countFactor = 1; startOffset = [-1, 1]; break; case 8: dirs = ROT.DIRS[4]; countFactor = 2; startOffset = [-1, 1]; break; } /* starting neighbor */ var x = cx + startOffset[0]*r; var y = cy + startOffset[1]*r; /* circle */ for (var i=0;i A2[0]) { /* split into two sub-arcs */ var v1 = this._checkVisibility(A1, [A1[1], A1[1]], blocks, SHADOWS); var v2 = this._checkVisibility([0, 1], A2, blocks, SHADOWS); return (v1+v2)/2; } /* index1: first shadow >= A1 */ var index1 = 0, edge1 = false; while (index1 < SHADOWS.length) { var old = SHADOWS[index1]; var diff = old[0]*A1[1] - A1[0]*old[1]; if (diff >= 0) { /* old >= A1 */ if (diff == 0 && !(index1 % 2)) { edge1 = true; } break; } index1++; } /* index2: last shadow <= A2 */ var index2 = SHADOWS.length, edge2 = false; while (index2--) { var old = SHADOWS[index2]; var diff = A2[0]*old[1] - old[0]*A2[1]; if (diff >= 0) { /* old <= A2 */ if (diff == 0 && (index2 % 2)) { edge2 = true; } break; } } var visible = true; if (index1 == index2 && (edge1 || edge2)) { /* subset of existing shadow, one of the edges match */ visible = false; } else if (edge1 && edge2 && index1+1==index2 && (index2 % 2)) { /* completely equivalent with existing shadow */ visible = false; } else if (index1 > index2 && (index1 % 2)) { /* subset of existing shadow, not touching */ visible = false; } if (!visible) { return 0; } /* fast case: not visible */ var visibleLength, P; /* compute the length of visible arc, adjust list of shadows (if blocking) */ var remove = index2-index1+1; if (remove % 2) { if (index1 % 2) { /* first edge within existing shadow, second outside */ var P = SHADOWS[index1]; visibleLength = (A2[0]*P[1] - P[0]*A2[1]) / (P[1] * A2[1]); if (blocks) { SHADOWS.splice(index1, remove, A2); } } else { /* second edge within existing shadow, first outside */ var P = SHADOWS[index2]; visibleLength = (P[0]*A1[1] - A1[0]*P[1]) / (A1[1] * P[1]); if (blocks) { SHADOWS.splice(index1, remove, A1); } } } else { if (index1 % 2) { /* both edges within existing shadows */ var P1 = SHADOWS[index1]; var P2 = SHADOWS[index2]; visibleLength = (P2[0]*P1[1] - P1[0]*P2[1]) / (P1[1] * P2[1]); if (blocks) { SHADOWS.splice(index1, remove); } } else { /* both edges outside existing shadows */ if (blocks) { SHADOWS.splice(index1, remove, A1, A2); } return 1; /* whole arc visible! */ } } var arcLength = (A2[0]*A1[1] - A1[0]*A2[1]) / (A1[1] * A2[1]); return visibleLength/arcLength; } /** * @namespace Color operations */ ROT.Color = { fromString: function(str) { var cached, r; if (str in this._cache) { cached = this._cache[str]; } else { if (str.charAt(0) == "#") { /* hex rgb */ var values = str.match(/[0-9a-f]/gi).map(function(x) { return parseInt(x, 16); }); if (values.length == 3) { cached = values.map(function(x) { return x*17; }); } else { for (var i=0;i<3;i++) { values[i+1] += 16*values[i]; values.splice(i, 1); } cached = values; } } else if (r = str.match(/rgb\(([0-9, ]+)\)/i)) { /* decimal rgb */ cached = r[1].split(/\s*,\s*/).map(function(x) { return parseInt(x); }); } else { /* html name */ cached = [0, 0, 0]; } this._cache[str] = cached; } return cached.slice(); }, /** * Add two or more colors * @param {number[]} color1 * @param {number[]} color2 * @returns {number[]} */ add: function(color1, color2) { var result = color1.slice(); for (var i=0;i<3;i++) { for (var j=1;j 0.5 ? d / (2 - max - min) : d / (max + min)); switch(max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h, s, l]; }, /** * Converts an HSL color value to RGB. Expects 0..1 inputs, produces 0..255 outputs. * @param {number[]} color * @returns {number[]} */ hsl2rgb: function(color) { var l = color[2]; if (color[1] == 0) { l *= 255; return [l, l, l]; } else { function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } var s = color[1]; var q = (l < 0.5 ? l * (1 + s) : l + s - l * s); var p = 2 * l - q; var r = hue2rgb(p, q, color[0] + 1/3); var g = hue2rgb(p, q, color[0]); var b = hue2rgb(p, q, color[0] - 1/3); return [Math.round(r*255), Math.round(g*255), Math.round(b*255)]; } }, toRGB: function(color) { return "rgb(" + this._clamp(color[0]) + "," + this._clamp(color[1]) + "," + this._clamp(color[2]) + ")"; }, toHex: function(color) { var parts = []; for (var i=0;i<3;i++) { parts.push(this._clamp(color[i]).toString(16).lpad("0", 2)); } return "#" + parts.join(""); }, _clamp: function(num) { if (num < 0) { return 0; } else if (num > 255) { return 255; } else { return num; } }, _cache: { "black": [0,0,0], "navy": [0,0,128], "darkblue": [0,0,139], "mediumblue": [0,0,205], "blue": [0,0,255], "darkgreen": [0,100,0], "green": [0,128,0], "teal": [0,128,128], "darkcyan": [0,139,139], "deepskyblue": [0,191,255], "darkturquoise": [0,206,209], "mediumspringgreen": [0,250,154], "lime": [0,255,0], "springgreen": [0,255,127], "aqua": [0,255,255], "cyan": [0,255,255], "midnightblue": [25,25,112], "dodgerblue": [30,144,255], "forestgreen": [34,139,34], "seagreen": [46,139,87], "darkslategray": [47,79,79], "darkslategrey": [47,79,79], "limegreen": [50,205,50], "mediumseagreen": [60,179,113], "turquoise": [64,224,208], "royalblue": [65,105,225], "steelblue": [70,130,180], "darkslateblue": [72,61,139], "mediumturquoise": [72,209,204], "indigo": [75,0,130], "darkolivegreen": [85,107,47], "cadetblue": [95,158,160], "cornflowerblue": [100,149,237], "mediumaquamarine": [102,205,170], "dimgray": [105,105,105], "dimgrey": [105,105,105], "slateblue": [106,90,205], "olivedrab": [107,142,35], "slategray": [112,128,144], "slategrey": [112,128,144], "lightslategray": [119,136,153], "lightslategrey": [119,136,153], "mediumslateblue": [123,104,238], "lawngreen": [124,252,0], "chartreuse": [127,255,0], "aquamarine": [127,255,212], "maroon": [128,0,0], "purple": [128,0,128], "olive": [128,128,0], "gray": [128,128,128], "grey": [128,128,128], "skyblue": [135,206,235], "lightskyblue": [135,206,250], "blueviolet": [138,43,226], "darkred": [139,0,0], "darkmagenta": [139,0,139], "saddlebrown": [139,69,19], "darkseagreen": [143,188,143], "lightgreen": [144,238,144], "mediumpurple": [147,112,216], "darkviolet": [148,0,211], "palegreen": [152,251,152], "darkorchid": [153,50,204], "yellowgreen": [154,205,50], "sienna": [160,82,45], "brown": [165,42,42], "darkgray": [169,169,169], "darkgrey": [169,169,169], "lightblue": [173,216,230], "greenyellow": [173,255,47], "paleturquoise": [175,238,238], "lightsteelblue": [176,196,222], "powderblue": [176,224,230], "firebrick": [178,34,34], "darkgoldenrod": [184,134,11], "mediumorchid": [186,85,211], "rosybrown": [188,143,143], "darkkhaki": [189,183,107], "silver": [192,192,192], "mediumvioletred": [199,21,133], "indianred": [205,92,92], "peru": [205,133,63], "chocolate": [210,105,30], "tan": [210,180,140], "lightgray": [211,211,211], "lightgrey": [211,211,211], "palevioletred": [216,112,147], "thistle": [216,191,216], "orchid": [218,112,214], "goldenrod": [218,165,32], "crimson": [220,20,60], "gainsboro": [220,220,220], "plum": [221,160,221], "burlywood": [222,184,135], "lightcyan": [224,255,255], "lavender": [230,230,250], "darksalmon": [233,150,122], "violet": [238,130,238], "palegoldenrod": [238,232,170], "lightcoral": [240,128,128], "khaki": [240,230,140], "aliceblue": [240,248,255], "honeydew": [240,255,240], "azure": [240,255,255], "sandybrown": [244,164,96], "wheat": [245,222,179], "beige": [245,245,220], "whitesmoke": [245,245,245], "mintcream": [245,255,250], "ghostwhite": [248,248,255], "salmon": [250,128,114], "antiquewhite": [250,235,215], "linen": [250,240,230], "lightgoldenrodyellow": [250,250,210], "oldlace": [253,245,230], "red": [255,0,0], "fuchsia": [255,0,255], "magenta": [255,0,255], "deeppink": [255,20,147], "orangered": [255,69,0], "tomato": [255,99,71], "hotpink": [255,105,180], "coral": [255,127,80], "darkorange": [255,140,0], "lightsalmon": [255,160,122], "orange": [255,165,0], "lightpink": [255,182,193], "pink": [255,192,203], "gold": [255,215,0], "peachpuff": [255,218,185], "navajowhite": [255,222,173], "moccasin": [255,228,181], "bisque": [255,228,196], "mistyrose": [255,228,225], "blanchedalmond": [255,235,205], "papayawhip": [255,239,213], "lavenderblush": [255,240,245], "seashell": [255,245,238], "cornsilk": [255,248,220], "lemonchiffon": [255,250,205], "floralwhite": [255,250,240], "snow": [255,250,250], "yellow": [255,255,0], "lightyellow": [255,255,224], "ivory": [255,255,240], "white": [255,255,255] } } /** * @class Lighting computation, based on a traditional FOV for multiple light sources and multiple passes. * @param {function} reflectivityCallback Callback to retrieve cell reflectivity (0..1) * @param {object} [options] * @param {int} [options.passes=1] Number of passes. 1 equals to simple FOV of all light sources, >1 means a *highly simplified* radiosity-like algorithm. * @param {int} [options.emissionThreshold=100] Cells with emissivity > threshold will be treated as light source in the next pass. * @param {int} [options.range=10] Max light range */ ROT.Lighting = function(reflectivityCallback, options) { this._reflectivityCallback = reflectivityCallback; this._options = { passes: 1, emissionThreshold: 100, range: 10 }; this._fov = null; this._lights = {}; this._reflectivityCache = {}; this._fovCache = {}; this.setOptions(options); } /** * Adjust options at runtime * @see ROT.Lighting * @param {object} [options] */ ROT.Lighting.prototype.setOptions = function(options) { for (var p in options) { this._options[p] = options[p]; } if (options.range) { this.reset(); } return this; } /** * Set the used Field-Of-View algo * @param {ROT.FOV} fov */ ROT.Lighting.prototype.setFOV = function(fov) { this._fov = fov; this._fovCache = {}; return this; } /** * Set (or remove) a light source * @param {int} x * @param {int} y * @param {null || string || number[3]} color */ ROT.Lighting.prototype.setLight = function(x, y, color) { var key = x+","+y; if (color) { this._lights[key] = (typeof(color) == "string" ? ROT.Color.fromString(color) : color); } else { delete this._lights[key]; } return this; } /** * Reset the pre-computed topology values. Call whenever the underlying map changes its light-passability. */ ROT.Lighting.prototype.reset = function() { this._reflectivityCache = {}; this._fovCache = {}; return this; } /** * Compute the lighting * @param {function} lightingCallback Will be called with (x, y, color) for every lit cell */ ROT.Lighting.prototype.compute = function(lightingCallback) { var doneCells = {}; var emittingCells = {}; var litCells = {}; for (var key in this._lights) { /* prepare emitters for first pass */ var light = this._lights[key]; if (!(key in emittingCells)) { emittingCells[key] = [0, 0, 0]; } ROT.Color.add_(emittingCells[key], light); } for (var i=0;i this._options.emissionThreshold) { result[key] = emission; } } return result; } /** * Compute one iteration from one cell * @param {int} x * @param {int} y * @param {number[]} color * @param {object} litCells Cell data to by updated */ ROT.Lighting.prototype._emitLightFromCell = function(x, y, color, litCells) { var key = x+","+y; if (key in this._fovCache) { var fov = this._fovCache[key]; } else { var fov = this._updateFOV(x, y); } for (var fovKey in fov) { var formFactor = fov[fovKey]; if (fovKey in litCells) { /* already lit */ var result = litCells[fovKey]; } else { /* newly lit */ var result = [0, 0, 0]; litCells[fovKey] = result; } for (var i=0;i<3;i++) { result[i] += Math.round(color[i]*formFactor); } /* add light color */ } return this; } /** * Compute FOV ("form factor") for a potential light source at [x,y] * @param {int} x * @param {int} y * @returns {object} */ ROT.Lighting.prototype._updateFOV = function(x, y) { var key1 = x+","+y; var cache = {}; this._fovCache[key1] = cache; var range = this._options.range; var cb = function(x, y, r, vis) { var key2 = x+","+y; var formFactor = vis * (1-r/range); if (formFactor == 0) { return; } cache[key2] = formFactor; } this._fov.compute(x, y, range, cb.bind(this)); return cache; } /** * @class Abstract pathfinder * @param {int} toX Target X coord * @param {int} toY Target Y coord * @param {function} passableCallback Callback to determine map passability * @param {object} [options] * @param {int} [options.topology=8] */ ROT.Path = function(toX, toY, passableCallback, options) { this._toX = toX; this._toY = toY; this._fromX = null; this._fromY = null; this._passableCallback = passableCallback; this._options = { topology: 8 } for (var p in options) { this._options[p] = options[p]; } this._dirs = ROT.DIRS[this._options.topology]; if (this._options.topology == 8) { /* reorder dirs for more aesthetic result (vertical/horizontal first) */ this._dirs = [ this._dirs[0], this._dirs[2], this._dirs[4], this._dirs[6], this._dirs[1], this._dirs[3], this._dirs[5], this._dirs[7] ] } } /** * Compute a path from a given point * @param {int} fromX * @param {int} fromY * @param {function} callback Will be called for every path item with arguments "x" and "y" */ ROT.Path.prototype.compute = function(fromX, fromY, callback) { } ROT.Path.prototype._getNeighbors = function(cx, cy) { var result = []; for (var i=0;i