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, +};