diff --git a/.gitignore b/.gitignore
index 8a6a96f..2137016 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
## log files
npm-debug.log
+/nbproject/private/
\ No newline at end of file
diff --git a/js/game.js b/clientjs/game.js
similarity index 100%
rename from js/game.js
rename to clientjs/game.js
diff --git a/js/rot.js b/clientjs/rot.js
similarity index 100%
rename from js/rot.js
rename to clientjs/rot.js
diff --git a/index.html b/index.html
index 3f872f5..d7f8e2d 100755
--- a/index.html
+++ b/index.html
@@ -3,9 +3,9 @@
Socket.io Test
-
-
-
+
+
+
diff --git a/js/map.js b/js/map.js
deleted file mode 100644
index dc7442e..0000000
--- a/js/map.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// map.js
-// =========
-var ROT = require('rot-js');
-
-var w = 10;
-var h = 10;
-var maparray = [];
-for(i = 0; i < w; i++) {
- maparray.push([]);
-}
-
-function createArena(w, h) {
- return new ROT.Map.Arena(w, h);
-}
-
-function printCallback(x, y, value) {
- console.log(x, y, value);
-}
-
-function arrayCallback(x, y, value) {
- maparray[x][y] = value;
- console.log(x, y, value);
-}
-
-
-module.exports = {
- maparray,
- createArena,
- printCallback,
- arrayCallback,
-};
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644
index 0000000..da97eba
--- /dev/null
+++ b/nbproject/project.properties
@@ -0,0 +1,3 @@
+files.encoding=UTF-8
+site.root.folder=
+source.folder=
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644
index 0000000..a73c380
--- /dev/null
+++ b/nbproject/project.xml
@@ -0,0 +1,9 @@
+
+
+ org.netbeans.modules.web.clientproject
+
+
+ rotsock
+
+
+
diff --git a/server.js b/server.js
index 2f869d9..45ef6a4 100755
--- a/server.js
+++ b/server.js
@@ -4,8 +4,9 @@ var http = require('http').Server(app);
var io = require('socket.io')(http);
var ROT = require('rot-js');
-var Map = require('./js/map.js')
-var Player = require('./js/player.js')
+var Map = require('./serverjs/map.js');
+var Player = require('./serverjs/player.js');
+var Glyph = require('./serverjs/glyph.js');
app.use(express.static(__dirname)); // Current directory is root
@@ -21,8 +22,7 @@ var Game = {
};
var map = Map.createArena(10, 10);
-map.create(Map.arrayCallback);
-console.log(Map.maparray);
+map.consoleDisplay();
function findWithAttr(array, attr, value) {
for(var i = 0; i < array.length; i += 1) {
@@ -52,7 +52,7 @@ io.on('connection', function(socket){
function pushMap() {
for (player in Game.Players) {
- io.to(player.id).emit("display", JSON.stringify(Map.maparray));
+ io.to(player.id).emit("display", JSON.stringify(map));
}
}
// setTimeout(pushMap, 1000);
diff --git a/serverjs/glyph.js b/serverjs/glyph.js
new file mode 100644
index 0000000..ad0ea9b
--- /dev/null
+++ b/serverjs/glyph.js
@@ -0,0 +1,27 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+Glyph = function(chr, foreground, background) {
+ // Instantiate properties to default if they weren't passed
+ this._char = chr || ' ';
+ this._foreground = foreground || 'white';
+ this._background = background || 'black';
+};
+
+// Create standard getters for glyphs
+Glyph.prototype.getChar = function(){
+ return this._char;
+}
+Glyph.prototype.getBackground = function(){
+ return this._background;
+}
+Glyph.prototype.getForeground = function(){
+ return this._foreground;
+}
+
+module.exports = {
+ Glyph,
+};
diff --git a/serverjs/map.js b/serverjs/map.js
new file mode 100644
index 0000000..90a106e
--- /dev/null
+++ b/serverjs/map.js
@@ -0,0 +1,72 @@
+// map.js
+// =========
+var ROT = require('rot-js');
+var Glyph = require('./glyph.js');
+var Tile = require('./tile.js');
+
+var w = 80;
+var h = 40;
+
+
+function Map(tiles) {
+ this._tiles = tiles;
+ // cache the width and height based
+ // on the length of the dimensions of
+ // the tiles array
+ this._width = tiles.length;
+ this._height = tiles[0].length;
+};
+
+// Standard getters
+Map.prototype.getWidth = function() {
+ return this._width;
+};
+Map.prototype.getHeight = function() {
+ return this._height;
+};
+
+// Gets the tile for a given coordinate set
+Map.prototype.getTile = function(x, y) {
+ // Make sure we are inside the bounds. If we aren't, return
+ // null tile.
+ if (x < 0 || x >= this._width || y < 0 || y >= this._height) {
+ return Tile.nullTile;
+ } else {
+ return this._tiles[x][y] || Tile.nullTile;
+ }
+};
+
+Map.prototype.consoleDisplay = function() {
+ for (var x = 0; x < this.getWidth(); x++) {
+ for (var y = 0; y < this.getHeight(); y++) {
+ console.log(this.getTile(x, y));
+ }
+ console.log('\n');
+ }
+}
+
+function createArena(w, h) {
+ var tilearray = []
+ for (var x = 0; x < w; x++) {
+ tilearray.push([]);
+ for (var y = 0; y < h; y++) {
+ tilearray[x].push(Tile.Tile.nullTile);
+ }
+ }
+ console.log(tilearray);
+ var generator = new ROT.Map.Arena(w, h);
+ generator.create(function(x, y, v) {
+ if (v === 1) {
+ tilearray[x][y] = Tile.Tile.floorTile;
+ } else {
+ tilearray[x][y] = Tile.Tile.wallTile;
+ }
+ });
+ return new Map(tilearray);
+}
+
+
+module.exports = {
+ Map,
+ createArena,
+};
diff --git a/js/oldserver.js b/serverjs/oldserver.js
similarity index 100%
rename from js/oldserver.js
rename to serverjs/oldserver.js
diff --git a/js/player.js b/serverjs/player.js
similarity index 71%
rename from js/player.js
rename to serverjs/player.js
index f7f6382..ee78f0e 100644
--- a/js/player.js
+++ b/serverjs/player.js
@@ -2,19 +2,23 @@
// ============
-function createPlayer(nick, addy, id, ip) {
+function Player(nick, addy, id, ip) {
// Setup the bookkeeping stuff
- this.nick = nick || "Anon";
- this.addy = addy || "MLaMWLvFJMai3czhrK6peenVT2S2hFgmUi";
- this.id = id;
- this.ip = ip;
- this.jointime = Date.now();
- this.x = 0;
- this.y = 0;
+ this._nick = nick || "Anon";
+ this._addy = addy || "MLaMWLvFJMai3czhrK6peenVT2S2hFgmUi";
+ this._id = id;
+ this._ip = ip;
+ this._jointime = Date.now();
+ this._x = 0;
+ this._y = 0;
return this;
};
-function processInput(key) {
+Player.prototype.getNick = function() {
+ return this._nick;
+}
+
+Player.prototype.processInput = function(key) {
if (key == "VK_K" || key == "VK_NUMPAD8" || key == "VK_UP") {
dir = ROT.DIRS["8"][0];
}
@@ -46,8 +50,7 @@ function processInput(key) {
}
module.exports = {
- createPlayer,
- processInput,
+ Player,
};
diff --git a/js/playerold.js b/serverjs/playerold.js
similarity index 100%
rename from js/playerold.js
rename to serverjs/playerold.js
diff --git a/serverjs/rot.js b/serverjs/rot.js
new file mode 100755
index 0000000..eb7af9a
--- /dev/null
+++ b/serverjs/rot.js
@@ -0,0 +1,5519 @@
+/*
+ This is rot.js, the ROguelike Toolkit in JavaScript.
+ Version 0.6~dev, generated on Mon Nov 30 10:34:42 CET 2015.
+*/
+/**
+ * Add objects for Node.js environment
+ */
+global.requestAnimationFrame = function(cb) {
+ return setTimeout(cb, 1000/60);
+};
+
+global.document = {
+ body: {
+ appendChild: function(child) {},
+ scrollLeft: 0,
+ scrollTop: 0
+ },
+ createElement: function(type) {
+ var canvas;
+ return canvas = {
+ getBoundingClientRect: function() {
+ var rect;
+ return rect = {
+ left: 0,
+ top: 0
+ };
+ },
+ getContext: function(type) {
+ var context;
+ return context = {
+ _termcolor: null,
+ beginPath: function() {},
+ canvas: canvas,
+ clearRect: function(x, y, w, h) {
+ if(this._termcolor !== null) {
+ var clearCmd = this._termcolor.clearToAnsi(this.fillStyle);
+ process.stdout.write(clearCmd);
+ }
+ },
+ drawImage: function(a, b, c, d, e, f, g, h, i) {},
+ fill: function() {},
+ fillRect: function(x, y, w, h) {
+ if(this._termcolor !== null) {
+ var clearCmd = this._termcolor.clearToAnsi(this.fillStyle);
+ process.stdout.write(clearCmd);
+ }
+ },
+ fillStyle: "#000",
+ fillText: function(chs, x, y) {},
+ font: "monospace",
+ lineTo: function(x, y) {},
+ measureText: function(ch) {
+ var result;
+ return result = {
+ width: 12
+ };
+ },
+ moveTo: function(x, y) {},
+ textAlign: "center",
+ textBaseline: "middle"
+ };
+ },
+ height: 0,
+ style: {
+ left: "100px",
+ position: "absolute",
+ top: "100px",
+ visibility: "hidden"
+ },
+ width: 0
+ };
+ },
+ documentElement: {
+ scrollLeft: 0,
+ scrollTop: 0
+ }
+};
+/**
+ * @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:
+ var isSpace = false, isPrevSpace = false, isFullWidth = false, isPrevFullWidth = false;
+ for (var i=0;i 0xff && cc < 0xff61) || (cc > 0xffdc && cc < 0xffe8) && cc > 0xffee;
+ // Current char is space, whatever full-width or half-width both are OK.
+ isSpace = (c.charCodeAt(0) == 0x20 || c.charCodeAt(0) == 0x3000);
+ // The previous char is full-width and
+ // current char is nether half-width nor a space.
+ if (isPrevFullWidth && !isFullWidth && !isSpace) { cx++; } // add an extra position
+ // The current char is full-width and
+ // the previous char is not a space.
+ if(isFullWidth && !isPrevSpace) { cx++; } // add an extra position
+ this.draw(cx++, cy, c, fg, bg);
+ isPrevSpace = isSpace;
+ isPrevFullWidth = isFullWidth;
+ }
+ break;
+
+ case ROT.Text.TYPE_FG:
+ fg = token.value || null;
+ break;
+
+ case ROT.Text.TYPE_BG:
+ bg = token.value || null;
+ break;
+
+ case ROT.Text.TYPE_NEWLINE:
+ cx = x;
+ cy++;
+ lines++
+ break;
+ }
+ }
+
+ return lines;
+}
+
+/**
+ * Timer tick: update dirty parts
+ */
+ROT.Display.prototype._tick = function() {
+ requestAnimationFrame(this._tick);
+
+ if (!this._dirty) { return; }
+
+ if (this._dirty === true) { /* draw all */
+ this._context.fillStyle = this._options.bg;
+ this._context.fillRect(0, 0, this._context.canvas.width, this._context.canvas.height);
+
+ for (var id in this._data) { /* redraw cached data */
+ this._draw(id, false);
+ }
+
+ } else { /* draw only dirty */
+ for (var key in this._dirty) {
+ this._draw(key, true);
+ }
+ }
+
+ this._dirty = false;
+}
+
+/**
+ * @param {string} key What to draw
+ * @param {bool} clearBefore Is it necessary to clean before?
+ */
+ROT.Display.prototype._draw = function(key, clearBefore) {
+ var data = this._data[key];
+ if (data[4] != this._options.bg) { clearBefore = true; }
+
+ this._backend.draw(data, clearBefore);
+}
+/**
+ * @class Abstract display backend module
+ * @private
+ */
+ROT.Display.Backend = function(context) {
+ this._context = context;
+}
+
+ROT.Display.Backend.prototype.compute = function(options) {
+}
+
+ROT.Display.Backend.prototype.draw = function(data, clearBefore) {
+}
+
+ROT.Display.Backend.prototype.computeSize = function(availWidth, availHeight) {
+}
+
+ROT.Display.Backend.prototype.computeFontSize = function(availWidth, availHeight) {
+}
+
+ROT.Display.Backend.prototype.eventToPosition = function(x, y) {
+}
+/**
+ * @class Rectangular backend
+ * @private
+ */
+ROT.Display.Rect = function(context) {
+ ROT.Display.Backend.call(this, context);
+
+ this._spacingX = 0;
+ this._spacingY = 0;
+ this._canvasCache = {};
+ this._options = {};
+}
+ROT.Display.Rect.extend(ROT.Display.Backend);
+
+ROT.Display.Rect.cache = false;
+
+ROT.Display.Rect.prototype.compute = function(options) {
+ this._canvasCache = {};
+ this._options = options;
+
+ var charWidth = Math.ceil(this._context.measureText("W").width);
+ this._spacingX = Math.ceil(options.spacing * charWidth);
+ this._spacingY = Math.ceil(options.spacing * options.fontSize);
+
+ if (this._options.forceSquareRatio) {
+ this._spacingX = this._spacingY = Math.max(this._spacingX, this._spacingY);
+ }
+
+ this._context.canvas.width = options.width * this._spacingX;
+ this._context.canvas.height = options.height * this._spacingY;
+}
+
+ROT.Display.Rect.prototype.draw = function(data, clearBefore) {
+ if (this.constructor.cache) {
+ this._drawWithCache(data, clearBefore);
+ } else {
+ this._drawNoCache(data, clearBefore);
+ }
+}
+
+ROT.Display.Rect.prototype._drawWithCache = function(data, clearBefore) {
+ var x = data[0];
+ var y = data[1];
+ var ch = data[2];
+ var fg = data[3];
+ var bg = data[4];
+
+ var hash = ""+ch+fg+bg;
+ if (hash in this._canvasCache) {
+ var canvas = this._canvasCache[hash];
+ } else {
+ var b = this._options.border;
+ var canvas = document.createElement("canvas");
+ var ctx = canvas.getContext("2d");
+ canvas.width = this._spacingX;
+ canvas.height = this._spacingY;
+ ctx.fillStyle = bg;
+ ctx.fillRect(b, b, canvas.width-b, canvas.height-b);
+
+ if (ch) {
+ ctx.fillStyle = fg;
+ ctx.font = this._context.font;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+
+ var chars = [].concat(ch);
+ 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;
+
+ /* FIXME char size computation does not respect transposed hexes */
+ 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;
+
+ if (options.transpose) {
+ var xprop = "height";
+ var yprop = "width";
+ } else {
+ var xprop = "width";
+ var yprop = "height";
+ }
+ this._context.canvas[xprop] = Math.ceil( (options.width + 1) * this._spacingX );
+ this._context.canvas[yprop] = 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 px = [
+ (x+1) * this._spacingX,
+ y * this._spacingY + this._hexSize
+ ];
+ if (this._options.transpose) { px.reverse(); }
+
+ if (clearBefore) {
+ this._context.fillStyle = bg;
+ this._fill(px[0], px[1]);
+ }
+
+ 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 {int} lowerBound The lower end of the range to return a value from, inclusive
+ * @param {int} upperBound The upper end of the range to return a value from, inclusive
+ * @returns {int} Pseudorandom value [lowerBound, upperBound], using ROT.RNG.getUniform() to distribute the value
+ */
+ getUniformInt: function(lowerBound, upperBound) {
+ var max = Math.max(lowerBound, upperBound);
+ var min = Math.min(lowerBound, upperBound);
+ return Math.floor(this.getUniform() * (max - min + 1)) + min;
+ },
+
+ /**
+ * @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 total = 0;
+
+ for (var id in data) {
+ total += data[id];
+ }
+ var random = this.getUniform()*total;
+
+ var part = 0;
+ for (var id in data) {
+ part += data[id];
+ if (random < part) { return id; }
+ }
+
+ // If by some floating-point annoyance we have
+ // random >= total, just return the last id.
+ return id;
+ },
+
+ /**
+ * 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;
+ },
+
+ /**
+ * Returns a cloned RNG
+ */
+ clone: function() {
+ var clone = Object.create(this);
+ clone.setState(this.getState());
+ return clone;
+ },
+
+ _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;
+}
+/**
+ * @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;
+}
+
+/**
+ * Make sure every non-wall space is accessible.
+ */
+ROT.Map.Cellular.prototype._completeMaze = function() {
+ var allFreeSpace = [];
+ var notConnected = {};
+ // find all free space
+ for (var x = 0; x < this._width; x++) {
+ for (var y = 0; y < this._height; y++) {
+ if (this._freeSpace(x, y)) {
+ var p = [x, y];
+ notConnected[this._pointKey(p)] = p;
+ allFreeSpace.push([x, y]);
+ }
+ }
+ }
+ var start = allFreeSpace[ROT.RNG.getUniformInt(0, allFreeSpace.length - 1)];
+
+ var key = this._pointKey(start);
+ var connected = {};
+ connected[key] = start;
+ delete notConnected[key]
+
+ // find what's connected to the starting point
+ this._findConnected(connected, notConnected, [start]);
+
+ while (Object.keys(notConnected).length > 0) {
+
+ // find two points from notConnected to connected
+ var p = this._getFromTo(connected, notConnected);
+ var from = p[0]; // notConnected
+ var to = p[1]; // connected
+
+ // find everything connected to the starting point
+ var local = {};
+ local[this._pointKey(from)] = from;
+ this._findConnected(local, notConnected, [from], true);
+
+ // connect to a connected square
+ this._tunnelToConnected(to, from, connected, notConnected);
+
+ // now all of local is connected
+ for (var k in local) {
+ var pp = local[k];
+ this._map[pp[0]][pp[1]] = 0;
+ connected[k] = pp;
+ delete notConnected[k];
+ }
+ }
+}
+
+/**
+ * Find random points to connect. Search for the closest point in the larger space.
+ * This is to minimize the length of the passage while maintaining good performance.
+ */
+ROT.Map.Cellular.prototype._getFromTo = function(connected, notConnected) {
+ var from, to, d;
+ var connectedKeys = Object.keys(connected);
+ var notConnectedKeys = Object.keys(notConnected);
+ for (var i = 0; i < 5; i++) {
+ if (connectedKeys.length < notConnectedKeys.length) {
+ var keys = connectedKeys;
+ to = connected[keys[ROT.RNG.getUniformInt(0, keys.length - 1)]]
+ from = this._getClosest(to, notConnected);
+ } else {
+ var keys = notConnectedKeys;
+ from = notConnected[keys[ROT.RNG.getUniformInt(0, keys.length - 1)]]
+ to = this._getClosest(from, connected);
+ }
+ d = (from[0] - to[0]) * (from[0] - to[0]) + (from[1] - to[1]) * (from[1] - to[1]);
+ if (d < 64) {
+ break;
+ }
+ }
+ // console.log(">>> connected=" + to + " notConnected=" + from + " dist=" + d);
+ return [from, to];
+}
+
+ROT.Map.Cellular.prototype._getClosest = function(point, space) {
+ var minPoint = null;
+ var minDist = null;
+ for (k in space) {
+ var p = space[k];
+ var d = (p[0] - point[0]) * (p[0] - point[0]) + (p[1] - point[1]) * (p[1] - point[1]);
+ if (minDist == null || d < minDist) {
+ minDist = d;
+ minPoint = p;
+ }
+ }
+ return minPoint;
+}
+
+ROT.Map.Cellular.prototype._findConnected = function(connected, notConnected, stack, keepNotConnected) {
+ while(stack.length > 0) {
+ var p = stack.splice(0, 1)[0];
+ var tests = [
+ [p[0] + 1, p[1]],
+ [p[0] - 1, p[1]],
+ [p[0], p[1] + 1],
+ [p[0], p[1] - 1]
+ ];
+ for (var i = 0; i < tests.length; i++) {
+ var key = this._pointKey(tests[i]);
+ if (connected[key] == null && this._freeSpace(tests[i][0], tests[i][1])) {
+ connected[key] = tests[i];
+ if (!keepNotConnected) {
+ delete notConnected[key];
+ }
+ stack.push(tests[i]);
+ }
+ }
+ }
+}
+
+ROT.Map.Cellular.prototype._tunnelToConnected = function(to, from, connected, notConnected) {
+ var key = this._pointKey(from);
+ var a, b;
+ if (from[0] < to[0]) {
+ a = from;
+ b = to;
+ } else {
+ a = to;
+ b = from;
+ }
+ for (var xx = a[0]; xx <= b[0]; xx++) {
+ this._map[xx][a[1]] = 0;
+ var p = [xx, a[1]];
+ var pkey = this._pointKey(p);
+ connected[pkey] = p;
+ delete notConnected[pkey];
+ }
+
+ // x is now fixed
+ var x = b[0];
+
+ if (from[1] < to[1]) {
+ a = from;
+ b = to;
+ } else {
+ a = to;
+ b = from;
+ }
+ for (var yy = a[1]; yy < b[1]; yy++) {
+ this._map[x][yy] = 0;
+ var p = [x, yy];
+ var pkey = this._pointKey(p);
+ connected[pkey] = p;
+ delete notConnected[pkey];
+ }
+}
+
+ROT.Map.Cellular.prototype._freeSpace = function(x, y) {
+ return x >= 0 && x < this._width && y >= 0 && y < this._height && this._map[x][y] != 1;
+}
+
+ROT.Map.Cellular.prototype._pointKey = function(p) {
+ return p[0] + "." + p[1];
+}
+
+/**
+ * @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 - 1 || cy >= this._height - 1) { return null; }
+
+ var result = null;
+ var deltas = ROT.DIRS[4];
+
+ for (var i=0;i this._options.timeLimit) { return null; } /* time limit! */
+
+ this._map = this._fillMap(1);
+ this._dug = 0;
+ this._rooms = [];
+ this._unconnected = [];
+ this._generateRooms();
+ if (this._rooms.length < 2) { continue; }
+ 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 false; }
+ 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(this._width, this._options["cellWidth"]);
+ }
+ if (!this._options.hasOwnProperty("roomHeight")) {
+ this._options["roomHeight"] = this._calculateRoomSize(this._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._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 = ROT.RNG.getUniformInt(0, this._options.cellWidth-1);
+ var cgy = ROT.RNG.getUniformInt(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 = ROT.RNG.getUniformInt(roomWidth[0], roomWidth[1]);
+ roomh = ROT.RNG.getUniformInt(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(ROT.RNG.getUniformInt(0, cwp-roomw)/2);
+ var syOffset = Math.round(ROT.RNG.getUniformInt(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 = ROT.RNG.getUniformInt(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 = ROT.RNG.getUniformInt(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 = ROT.RNG.getUniformInt(min, max);
+
+ var min = options.roomHeight[0];
+ var max = options.roomHeight[1];
+ var height = ROT.RNG.getUniformInt(min, max);
+
+ 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);
+ }
+
+ throw new Error("dx or dy must be 1 or -1");
+}
+
+/**
+ * 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 = ROT.RNG.getUniformInt(min, max);
+
+ var min = options.roomHeight[0];
+ var max = options.roomHeight[1];
+ var height = ROT.RNG.getUniformInt(min, max);
+
+ 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 = ROT.RNG.getUniformInt(min, max);
+
+ var min = options.roomHeight[0];
+ var max = options.roomHeight[1];
+ var height = ROT.RNG.getUniformInt(min, max);
+
+ 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 = ROT.RNG.getUniformInt(min, max);
+
+ 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 for a 360-degree circle
+ * @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;
+}
+/**
+ * @class Recursive shadowcasting algorithm
+ * Currently only supports 4/8 topologies, not hexagonal.
+ * Based on Peter Harkins' implementation of Björn Bergström's algorithm described here: http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting
+ * @augments ROT.FOV
+ */
+ROT.FOV.RecursiveShadowcasting = function(lightPassesCallback, options) {
+ ROT.FOV.call(this, lightPassesCallback, options);
+}
+ROT.FOV.RecursiveShadowcasting.extend(ROT.FOV);
+
+/** Octants used for translating recursive shadowcasting offsets */
+ROT.FOV.RecursiveShadowcasting.OCTANTS = [
+ [-1, 0, 0, 1],
+ [ 0, -1, 1, 0],
+ [ 0, -1, -1, 0],
+ [-1, 0, 0, -1],
+ [ 1, 0, 0, -1],
+ [ 0, 1, -1, 0],
+ [ 0, 1, 1, 0],
+ [ 1, 0, 0, 1]
+];
+
+/**
+ * Compute visibility for a 360-degree circle
+ * @param {int} x
+ * @param {int} y
+ * @param {int} R Maximum visibility radius
+ * @param {function} callback
+ */
+ROT.FOV.RecursiveShadowcasting.prototype.compute = function(x, y, R, callback) {
+ //You can always see your own tile
+ callback(x, y, 0, 1);
+ for(var i = 0; i < ROT.FOV.RecursiveShadowcasting.OCTANTS.length; i++) {
+ this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[i], R, callback);
+ }
+}
+
+/**
+ * Compute visibility for a 180-degree arc
+ * @param {int} x
+ * @param {int} y
+ * @param {int} R Maximum visibility radius
+ * @param {int} dir Direction to look in (expressed in a ROT.DIRS value);
+ * @param {function} callback
+ */
+ROT.FOV.RecursiveShadowcasting.prototype.compute180 = function(x, y, R, dir, callback) {
+ //You can always see your own tile
+ callback(x, y, 0, 1);
+ var previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 180 degrees
+ var nextPreviousOctant = (dir - 2 + 8) % 8; //Need to retrieve the previous two octants to render a full 180 degrees
+ var nextOctant = (dir+ 1 + 8) % 8; //Need to grab to next octant to render a full 180 degrees
+ this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[nextPreviousOctant], R, callback);
+ this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[previousOctant], R, callback);
+ this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[dir], R, callback);
+ this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[nextOctant], R, callback);
+}
+
+/**
+ * Compute visibility for a 90-degree arc
+ * @param {int} x
+ * @param {int} y
+ * @param {int} R Maximum visibility radius
+ * @param {int} dir Direction to look in (expressed in a ROT.DIRS value);
+ * @param {function} callback
+ */
+ROT.FOV.RecursiveShadowcasting.prototype.compute90 = function(x, y, R, dir, callback) {
+ //You can always see your own tile
+ callback(x, y, 0, 1);
+ var previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 90 degrees
+ this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[dir], R, callback);
+ this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[previousOctant], R, callback);
+}
+
+/**
+ * Render one octant (45-degree arc) of the viewshed
+ * @param {int} x
+ * @param {int} y
+ * @param {int} octant Octant to be rendered
+ * @param {int} R Maximum visibility radius
+ * @param {function} callback
+ */
+ROT.FOV.RecursiveShadowcasting.prototype._renderOctant = function(x, y, octant, R, callback) {
+ //Radius incremented by 1 to provide same coverage area as other shadowcasting radiuses
+ this._castVisibility(x, y, 1, 1.0, 0.0, R + 1, octant[0], octant[1], octant[2], octant[3], callback);
+}
+
+/**
+ * Actually calculates the visibility
+ * @param {int} startX The starting X coordinate
+ * @param {int} startY The starting Y coordinate
+ * @param {int} row The row to render
+ * @param {float} visSlopeStart The slope to start at
+ * @param {float} visSlopeEnd The slope to end at
+ * @param {int} radius The radius to reach out to
+ * @param {int} xx
+ * @param {int} xy
+ * @param {int} yx
+ * @param {int} yy
+ * @param {function} callback The callback to use when we hit a block that is visible
+ */
+ROT.FOV.RecursiveShadowcasting.prototype._castVisibility = function(startX, startY, row, visSlopeStart, visSlopeEnd, radius, xx, xy, yx, yy, callback) {
+ if(visSlopeStart < visSlopeEnd) { return; }
+ for(var i = row; i <= radius; i++) {
+ var dx = -i - 1;
+ var dy = -i;
+ var blocked = false;
+ var newStart = 0;
+
+ //'Row' could be column, names here assume octant 0 and would be flipped for half the octants
+ while(dx <= 0) {
+ dx += 1;
+
+ //Translate from relative coordinates to map coordinates
+ var mapX = startX + dx * xx + dy * xy;
+ var mapY = startY + dx * yx + dy * yy;
+
+ //Range of the row
+ var slopeStart = (dx - 0.5) / (dy + 0.5);
+ var slopeEnd = (dx + 0.5) / (dy - 0.5);
+
+ //Ignore if not yet at left edge of Octant
+ if(slopeEnd > visSlopeStart) { continue; }
+
+ //Done if past right edge
+ if(slopeStart < visSlopeEnd) { break; }
+
+ //If it's in range, it's visible
+ if((dx * dx + dy * dy) < (radius * radius)) {
+ callback(mapX, mapY, i, 1);
+ }
+
+ if(!blocked) {
+ //If tile is a blocking tile, cast around it
+ if(!this._lightPasses(mapX, mapY) && i < radius) {
+ blocked = true;
+ this._castVisibility(startX, startY, i + 1, visSlopeStart, slopeStart, radius, xx, xy, yx, yy, callback);
+ newStart = slopeEnd;
+ }
+ } else {
+ //Keep narrowing if scanning across a block
+ if(!this._lightPasses(mapX, mapY)) {
+ newStart = slopeEnd;
+ continue;
+ }
+
+ //Block has ended
+ blocked = false;
+ visSlopeStart = newStart;
+ }
+ }
+ if(blocked) { break; }
+ }
+}
+/**
+ * @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 = Math.round(l*255);
+ return [l, l, l];
+ } else {
+ var hue2rgb = function(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 && 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;
+}
+
+/**
+ * Remove all light sources
+ */
+ROT.Lighting.prototype.clearLights = function() {
+ this._lights = {};
+}
+
+/**
+ * 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];
+ 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= process.stdout.columns) { return; }
+ if (dy < 0 || dy >= process.stdout.rows) { return; }
+ if (dx !== this._cx || dy !== this._cy) {
+ process.stdout.write(this._termcolor.positionToAnsi(dx,dy));
+ this._cx = dx;
+ this._cy = dy;
+ }
+
+ // terminals automatically clear, but if we're clearing when we're
+ // not otherwise provided with a character, just use a space instead
+ if (clearBefore) {
+ if (!ch) {
+ ch = " ";
+ }
+ }
+
+ // if we're not clearing and not provided with a character, do nothing
+ if (!ch) { return; }
+
+ // determine if we need to change colors
+ var newColor = this._termcolor.colorToAnsi(fg,bg);
+ if (newColor !== this._lastColor) {
+ process.stdout.write(newColor);
+ this._lastColor = newColor;
+ }
+
+ // write the provided symbol to the display
+ var chars = [].concat(ch);
+ process.stdout.write(chars[0]);
+
+ // update our position, given that we wrote a character
+ this._cx++;
+ if (this._cx >= process.stdout.columns) {
+ this._cx = 0;
+ this._cy++;
+ }
+}
+
+ROT.Display.Term.prototype.computeSize = function(availWidth, availHeight) {
+ return [process.stdout.columns, process.stdout.rows];
+}
+
+ROT.Display.Term.prototype.computeFontSize = function(availWidth, availHeight) {
+ return 12;
+}
+
+ROT.Display.Term.prototype.eventToPosition = function(x, y) {
+ return [x,y]
+}
+/**
+ * @class Abstract terminal code module
+ * @private
+ */
+ROT.Display.Term.Color = function(context) {
+ this._context = context;
+}
+
+ROT.Display.Term.Color.prototype.clearToAnsi = function(bg) {
+}
+
+ROT.Display.Term.Color.prototype.colorToAnsi = function(fg, bg) {
+}
+
+ROT.Display.Term.Color.prototype.positionToAnsi = function(x, y) {
+}
+/**
+ * @class xterm terminal code module
+ * @private
+ */
+ROT.Display.Term.Xterm = function(context) {
+ ROT.Display.Term.Color.call(this, context);
+}
+ROT.Display.Term.Xterm.extend(ROT.Display.Term.Color);
+
+ROT.Display.Term.Xterm.prototype.clearToAnsi = function(bg) {
+ return "\x1b[0;48;5;"
+ + this._termcolor(bg)
+ + "m\x1b[2J";
+}
+
+ROT.Display.Term.Xterm.prototype.colorToAnsi = function(fg, bg) {
+ return "\x1b[0;38;5;"
+ + this._termcolor(fg)
+ + ";48;5;"
+ + this._termcolor(bg)
+ + "m";
+}
+
+ROT.Display.Term.Xterm.prototype.positionToAnsi = function(x, y) {
+ return "\x1b[" + (y+1) + ";" + (x+1) + "H";
+}
+
+ROT.Display.Term.Xterm.prototype._termcolor = function(color) {
+ var SRC_COLORS = 256.0;
+ var DST_COLORS = 6.0;
+ var COLOR_RATIO = DST_COLORS / SRC_COLORS;
+ var rgb = ROT.Color.fromString(color);
+ var r = Math.floor(rgb[0] * COLOR_RATIO);
+ var g = Math.floor(rgb[1] * COLOR_RATIO);
+ var b = Math.floor(rgb[2] * COLOR_RATIO);
+ return r*36 + g*6 + b*1 + 16;
+}
+/**
+ * Export to Node.js module
+ */
+for (var p in ROT) {
+ exports[p] = ROT[p];
+}
diff --git a/serverjs/tile.js b/serverjs/tile.js
new file mode 100644
index 0000000..d99843b
--- /dev/null
+++ b/serverjs/tile.js
@@ -0,0 +1,23 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+var Glyph = require('./glyph.js');
+
+Tile = function(glyph) {
+ this._glyph = glyph;
+};
+
+Tile.prototype.getGlyph = function() {
+ return this._glyph;
+};
+
+Tile.nullTile = new Tile(new Glyph.Glyph());
+Tile.floorTile = new Tile(new Glyph.Glyph('.'));
+Tile.wallTile = new Tile(new Glyph.Glyph('#', 'goldenrod'));
+
+
+module.exports = {
+ Tile,
+};