diff --git a/types/rot-js/index.d.ts b/types/rot-js/index.d.ts
new file mode 100644
index 0000000000..32a75dbc9f
--- /dev/null
+++ b/types/rot-js/index.d.ts
@@ -0,0 +1,722 @@
+// Type definitions for rot-js 0.6
+// Project: https://github.com/ondras/rot.js
+// Definitions by: Roger Ostrander
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+// Extensions (thanks, https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html )
+declare global {
+ interface Array {
+ random(): T;
+ randomize(): T[];
+ }
+
+ interface Number {
+ mod(n: number): number;
+ }
+
+ // Added to support `String.format`
+ interface FormatFunc {
+ (...args: any[]): string;
+ map: { [key: string]: string };
+ }
+
+ interface StringConstructor {
+ format: FormatFunc;
+ }
+
+ interface String {
+ capitalize(): string;
+ lpad(character?: string, count?: number): string;
+ rpad(character?: string, count?: number): string;
+
+ format: FormatFunc;
+ }
+}
+
+export function isSupported(): boolean;
+
+// Docs explicitly show examples of overriding these, but TS2 (and ES6)
+// say that module-level vars are constant, so here we are:
+export const DEFAULT_WIDTH: number;
+export const DEFAULT_HEIGHT: number;
+
+/** Directional constants. Ordering is important! */
+export const DIRS: {
+ "4": Array<[number, number]>;
+ "6": Array<[number, number]>;
+ "8": Array<[number, number]>;
+};
+
+/** Cancel key. */
+export const VK_CANCEL: number;
+/** Help key. */
+export const VK_HELP: number;
+/** Backspace key. */
+export const VK_BACK_SPACE: number;
+/** Tab key. */
+export const VK_TAB: number;
+/** 5 key on Numpad when NumLock is unlocked. Or on Mac, clear key which is positioned at NumLock key. */
+export const VK_CLEAR: number;
+/** Return/enter key on the main keyboard. */
+export const VK_RETURN: number;
+/** Reserved, but not used. */
+export const VK_ENTER: number;
+/** Shift key. */
+export const VK_SHIFT: number;
+/** Control key. */
+export const VK_CONTROL: number;
+/** Alt (Option on Mac) key. */
+export const VK_ALT: number;
+/** Pause key. */
+export const VK_PAUSE: number;
+/** Caps lock. */
+export const VK_CAPS_LOCK: number;
+/** Escape key. */
+export const VK_ESCAPE: number;
+/** Space bar. */
+export const VK_SPACE: number;
+/** Page Up key. */
+export const VK_PAGE_UP: number;
+/** Page Down key. */
+export const VK_PAGE_DOWN: number;
+/** End key. */
+export const VK_END: number;
+/** Home key. */
+export const VK_HOME: number;
+/** Left arrow. */
+export const VK_LEFT: number;
+/** Up arrow. */
+export const VK_UP: number;
+/** Right arrow. */
+export const VK_RIGHT: number;
+/** Down arrow. */
+export const VK_DOWN: number;
+/** Print Screen key. */
+export const VK_PRINTSCREEN: number;
+/** Ins(ert) key. */
+export const VK_INSERT: number;
+/** Del(ete) key. */
+export const VK_DELETE: number;
+/***/
+export const VK_0: number;
+/***/
+export const VK_1: number;
+/***/
+export const VK_2: number;
+/***/
+export const VK_3: number;
+/***/
+export const VK_4: number;
+/***/
+export const VK_5: number;
+/***/
+export const VK_6: number;
+/***/
+export const VK_7: number;
+/***/
+export const VK_8: number;
+/***/
+export const VK_9: number;
+/** Colon (:) key. Requires Gecko 15.0 */
+export const VK_COLON: number;
+/** Semicolon (;) key. */
+export const VK_SEMICOLON: number;
+/** Less-than (<) key. Requires Gecko 15.0 */
+export const VK_LESS_THAN: number;
+/** Equals (=) key. */
+export const VK_EQUALS: number;
+/** Greater-than (>) key. Requires Gecko 15.0 */
+export const VK_GREATER_THAN: number;
+/** Question mark (?) key. Requires Gecko 15.0 */
+export const VK_QUESTION_MARK: number;
+/** Atmark (@) key. Requires Gecko 15.0 */
+export const VK_AT: number;
+/***/
+export const VK_A: number;
+/***/
+export const VK_B: number;
+/***/
+export const VK_C: number;
+/***/
+export const VK_D: number;
+/***/
+export const VK_E: number;
+/***/
+export const VK_F: number;
+/***/
+export const VK_G: number;
+/***/
+export const VK_H: number;
+/***/
+export const VK_I: number;
+/***/
+export const VK_J: number;
+/***/
+export const VK_K: number;
+/***/
+export const VK_L: number;
+/***/
+export const VK_M: number;
+/***/
+export const VK_N: number;
+/***/
+export const VK_O: number;
+/***/
+export const VK_P: number;
+/***/
+export const VK_Q: number;
+/***/
+export const VK_R: number;
+/***/
+export const VK_S: number;
+/***/
+export const VK_T: number;
+/***/
+export const VK_U: number;
+/***/
+export const VK_V: number;
+/***/
+export const VK_W: number;
+/***/
+export const VK_X: number;
+/***/
+export const VK_Y: number;
+/***/
+export const VK_Z: number;
+/***/
+export const VK_CONTEXT_MENU: number;
+/** 0 on the numeric keypad. */
+export const VK_NUMPAD0: number;
+/** 1 on the numeric keypad. */
+export const VK_NUMPAD1: number;
+/** 2 on the numeric keypad. */
+export const VK_NUMPAD2: number;
+/** 3 on the numeric keypad. */
+export const VK_NUMPAD3: number;
+/** 4 on the numeric keypad. */
+export const VK_NUMPAD4: number;
+/** 5 on the numeric keypad. */
+export const VK_NUMPAD5: number;
+/** 6 on the numeric keypad. */
+export const VK_NUMPAD6: number;
+/** 7 on the numeric keypad. */
+export const VK_NUMPAD7: number;
+/** 8 on the numeric keypad. */
+export const VK_NUMPAD8: number;
+/** 9 on the numeric keypad. */
+export const VK_NUMPAD9: number;
+/** * on the numeric keypad. */
+export const VK_MULTIPLY: number;
+/** + on the numeric keypad. */
+export const VK_ADD: number;
+/***/
+export const VK_SEPARATOR: number;
+/** - on the numeric keypad. */
+export const VK_SUBTRACT: number;
+/** Decimal point on the numeric keypad. */
+export const VK_DECIMAL: number;
+/** / on the numeric keypad. */
+export const VK_DIVIDE: number;
+/** F1 key. */
+export const VK_F1: number;
+/** F2 key. */
+export const VK_F2: number;
+/** F3 key. */
+export const VK_F3: number;
+/** F4 key. */
+export const VK_F4: number;
+/** F5 key. */
+export const VK_F5: number;
+/** F6 key. */
+export const VK_F6: number;
+/** F7 key. */
+export const VK_F7: number;
+/** F8 key. */
+export const VK_F8: number;
+/** F9 key. */
+export const VK_F9: number;
+/** F10 key. */
+export const VK_F10: number;
+/** F11 key. */
+export const VK_F11: number;
+/** F12 key. */
+export const VK_F12: number;
+/** F13 key. */
+export const VK_F13: number;
+/** F14 key. */
+export const VK_F14: number;
+/** F15 key. */
+export const VK_F15: number;
+/** F16 key. */
+export const VK_F16: number;
+/** F17 key. */
+export const VK_F17: number;
+/** F18 key. */
+export const VK_F18: number;
+/** F19 key. */
+export const VK_F19: number;
+/** F20 key. */
+export const VK_F20: number;
+/** F21 key. */
+export const VK_F21: number;
+/** F22 key. */
+export const VK_F22: number;
+/** F23 key. */
+export const VK_F23: number;
+/** F24 key. */
+export const VK_F24: number;
+/** Num Lock key. */
+export const VK_NUM_LOCK: number;
+/** Scroll Lock key. */
+export const VK_SCROLL_LOCK: number;
+/** Circumflex (^) key. Requires Gecko 15.0 */
+export const VK_CIRCUMFLEX: number;
+/** Exclamation (!) key. Requires Gecko 15.0 */
+export const VK_EXCLAMATION: number;
+/** Double quote () key. Requires Gecko 15.0 */
+export const VK_DOUBLE_QUOTE: number;
+/** Hash (#) key. Requires Gecko 15.0 */
+export const VK_HASH: number;
+/** Dollar sign ($) key. Requires Gecko 15.0 */
+export const VK_DOLLAR: number;
+/** Percent (%) key. Requires Gecko 15.0 */
+export const VK_PERCENT: number;
+/** Ampersand (&) key. Requires Gecko 15.0 */
+export const VK_AMPERSAND: number;
+/** Underscore (_) key. Requires Gecko 15.0 */
+export const VK_UNDERSCORE: number;
+/** Open parenthesis (() key. Requires Gecko 15.0 */
+export const VK_OPEN_PAREN: number;
+/** Close parenthesis ()) key. Requires Gecko 15.0 */
+export const VK_CLOSE_PAREN: number;
+/* Asterisk (*) key. Requires Gecko 15.0 */
+export const VK_ASTERISK: number;
+/** Plus (+) key. Requires Gecko 15.0 */
+export const VK_PLUS: number;
+/** Pipe (|) key. Requires Gecko 15.0 */
+export const VK_PIPE: number;
+/** Hyphen-US/docs/Minus (-) key. Requires Gecko 15.0 */
+export const VK_HYPHEN_MINUS: number;
+/** Open curly bracket ({) key. Requires Gecko 15.0 */
+export const VK_OPEN_CURLY_BRACKET: number;
+/** Close curly bracket (}) key. Requires Gecko 15.0 */
+export const VK_CLOSE_CURLY_BRACKET: number;
+/** Tilde (~) key. Requires Gecko 15.0 */
+export const VK_TILDE: number;
+/** Comma (,) key. */
+export const VK_COMMA: number;
+/** Period (.) key. */
+export const VK_PERIOD: number;
+/** Slash (/) key. */
+export const VK_SLASH: number;
+/** Back tick (`) key. */
+export const VK_BACK_QUOTE: number;
+/** Open square bracket ([) key. */
+export const VK_OPEN_BRACKET: number;
+/** Back slash (\) key. */
+export const VK_BACK_SLASH: number;
+/** Close square bracket (]) key. */
+export const VK_CLOSE_BRACKET: number;
+/** Quote (''') key. */
+export const VK_QUOTE: number;
+/** Meta key on Linux, Command key on Mac. */
+export const VK_META: number;
+/** AltGr key on Linux. Requires Gecko 15.0 */
+export const VK_ALTGR: number;
+/** Windows logo key on Windows. Or Super or Hyper key on Linux. Requires Gecko 15.0 */
+export const VK_WIN: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_KANA: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_HANGUL: number;
+/** 英数 key on Japanese Mac keyboard. Requires Gecko 15.0 */
+export const VK_EISU: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_JUNJA: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_FINAL: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_HANJA: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_KANJI: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_CONVERT: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_NONCONVERT: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_ACCEPT: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_MODECHANGE: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_SELECT: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_PRINT: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_EXECUTE: number;
+/** Linux support for this keycode was added in Gecko 4.0. */
+export const VK_SLEEP: number;
+
+// Added to support `measure`
+export interface Size {
+ width: number;
+ height: number;
+}
+
+export namespace Text {
+ const RE_COLORS: RegExp;
+
+ /* token types */
+ const TYPE_BG: number;
+ const TYPE_FG: number;
+ const TYPE_NEWLINE: number;
+ const TYPE_TEXT: number;
+
+ function measure(str: string, maxWidth: number): Size;
+
+ // Added to support `tokenize`
+ interface Token {
+ type: number;
+ value?: string;
+ }
+
+ function tokenize(str: string, maxWidth: number): Token[];
+}
+
+// Duplication is so that we can support chaining like ROT does,
+// but also use ROT.RNG like a normal namespace
+export interface RNGable {
+ getSeed(): number;
+ setSeed(seed: number): RNGable;
+ getUniform(): number;
+ getUniformInt(lowerBound: number, upperBound: number): number;
+ getNormal(mean?: number, stddev?: number): number;
+ getPercentage(): number;
+ getWeightedValue(data: { T: number }): T;
+ getState(): [number, number, number, number];
+ setState(state: [number, number, number, number]): RNGable;
+ clone(): RNGable;
+}
+
+export namespace RNG {
+ function getSeed(): number;
+ function setSeed(seed: number): RNGable;
+ function getUniform(): number;
+ function getUniformInt(lowerBound: number, upperBound: number): number;
+ function getNormal(mean?: number, stddev?: number): number;
+ function getPercentage(): number;
+ // Can't do the following because index has to explicitly be strings
+ // or numbers, see https://github.com/Microsoft/TypeScript/issues/5683
+ // function getWeightedValue(data: {[key: T]: number}): T;
+ // Provide a type-safe version for strings, at least:
+ function getWeightedValue(data: { [key: string]: number }): string;
+ // And everything else:
+ function getWeightedValue(data: any): any;
+ function getState(): [number, number, number, number];
+ function setState(state: [number, number, number, number]): RNGable;
+ function clone(): RNGable;
+}
+
+export interface DisplayOptions {
+ width?: number;
+ height?: number;
+ transpose?: boolean;
+ fontSize?: number;
+ fontFamily?: string;
+ fontStyle?: string;
+ fg?: string;
+ bg?: string;
+ spacing?: number;
+ border?: number;
+ layout?: string;
+ forceSquareRatio?: boolean;
+ tileWidth?: number;
+ tileHeight?: number;
+ tileMap?: any;
+ tileSet?: any;
+ tileColorize?: boolean;
+ termColor?: string;
+}
+
+export class Display {
+ DEBUG: DigCallback;
+
+ constructor(options?: DisplayOptions);
+ clear(): void;
+ computeSize(availWidth: number, availHeight: number): [number, number];
+ computeFontSize(availWidth: number, availHeight: number): number;
+ draw(x: number, y: number, character: string | string[], fg?: string, bg?: string): void;
+ drawText(x: number, y: number, text: string, maxWidth?: number): number;
+ eventToPosition(e: UIEvent): [number, number] | number;
+ getContainer(): Node;
+ getOptions(): DisplayOptions;
+ setOptions(options: DisplayOptions): Display;
+}
+
+// Map callbacks
+export type DigCallback = (x: number, y: number, cellValue: number) => any;
+export type DoorCallback = (x: number, y: number) => any;
+export type CanBeDugCallback = (x: number, y: number) => boolean;
+export type IsWallCallback = (x: number, y: number) => boolean;
+
+// Map
+export class Map {
+ constructor(width: number, height: number);
+ create(callback?: DigCallback): Map;
+}
+
+export interface CellularOptions {
+ born?: number[];
+ survive?: number[];
+ topology?: number;
+ // Docs say this exists, even though it's never used:
+ connected?: boolean;
+}
+
+export interface DiggerOptions {
+ roomWidth?: [number, number];
+ roomHeight?: [number, number];
+ corridorLength?: [number, number];
+ dugPercentage?: number;
+ timeLimit?: number;
+}
+
+export interface UniformOptions {
+ roomWidth?: [number, number];
+ roomHeight?: [number, number];
+ roomDugPercentage?: number;
+ timeLimit?: number;
+}
+
+export interface RogueOptions {
+ cellWidth?: number;
+ cellHeight?: number;
+ roomWidth?: [number, number];
+ roomHeight?: [number, number];
+}
+
+export interface RoomOptions {
+ roomWidth?: [number, number];
+ roomHeight?: [number, number];
+}
+
+export interface CorridorOptions {
+ corridorLength: [number, number];
+}
+export type FeatureOptions = RoomOptions | CorridorOptions;
+
+export namespace Map {
+ class Arena extends Map { }
+ class DividedMaze extends Map { }
+ class IceyMaze extends Map {
+ constructor(width?: number, height?: number, regularity?: number);
+ }
+ class EllerMaze extends Map { }
+ class Cellular extends Map {
+ constructor(width?: number, height?: number, options?: CellularOptions);
+ randomize(probability: number): this;
+ setOptions(options: CellularOptions): void;
+ set(x: number, y: number, value: any): void;
+ serviceCallback(callback: DigCallback): void;
+ connect(callback: DigCallback, value?: number, connectionCallback?: DoorCallback): void;
+ }
+ class Dungeon extends Map {
+ getRooms(): Map.Feature.Room[];
+ getCorridors(): Map.Feature.Corridor[];
+ }
+
+ class Digger extends Dungeon {
+ constructor(width?: number, height?: number, options?: DiggerOptions);
+ }
+
+ class Uniform extends Dungeon {
+ constructor(width?: number, height?: number, options?: UniformOptions);
+ }
+
+ class Rogue extends Map {
+ constructor(width?: number, height?: number, options?: RogueOptions);
+ }
+
+ class Feature {
+ isValid(canBeDugCallback: CanBeDugCallback): boolean;
+ create(digCallback: DigCallback): void;
+ debug(): void;
+ static createRandomAt(x: number, y: number, dx: number, dy: number, options?: FeatureOptions): Feature;
+ }
+
+ namespace Feature {
+ class Room {
+ constructor(x1: number, y1: number, x2: number, y2: number, doorX?: number, doorY?: number);
+
+ static createRandom(availWidth: number, availHeight: number, options?: RoomOptions): Room;
+ static createRandomAt(x: number, y: number, dx: number, dy: number, options?: RoomOptions): Room;
+ static createRandomCenter(cx: number, cy: number, options?: RoomOptions): Room;
+
+ create(digCallback: DigCallback): void;
+ debug(): void;
+
+ addDoor(x: number, y: number): this;
+ getDoors(callback: DoorCallback): this;
+ clearDoors(): this;
+ addDoors(isWallCallback: IsWallCallback): this;
+ isValid(isWallCallback: IsWallCallback, canBeDugCallback: CanBeDugCallback): boolean;
+ getCenter(): [number, number];
+
+ getLeft(): number;
+ getRight(): number;
+ getTop(): number;
+ getBottom(): number;
+ }
+
+ class Corridor {
+ constructor(startX: number, startY: number, endX: number, endY: number);
+
+ static createRandomAt(x: number, y: number, dx: number, dy: number, options?: CorridorOptions): Corridor;
+
+ create(digCallback: DigCallback): boolean;
+ debug(): void;
+
+ isValid(isWallCallback: IsWallCallback, canBeDugCallback: CanBeDugCallback): boolean;
+ createPriorityWalls(priorityWallCallback: DoorCallback): void;
+ }
+ }
+}
+
+// FOV callbacks
+export type LightPassesCallback = (x: number, y: number) => boolean;
+export type FOVCallback = (x: number, y: number, R: number, visibility: number) => any;
+
+export interface FOVOptions {
+ topology?: number;
+}
+
+export class FOV {
+ constructor(lightPassesCallback: LightPassesCallback, options?: FOVOptions);
+ compute(x: number, y: number, R: number, callback: FOVCallback): void;
+}
+export namespace FOV {
+ class DiscreteShadowcasting extends FOV { }
+ class PreciseShadowcasting extends FOV { }
+
+ class RecursiveShadowcasting extends FOV {
+ compute180(x: number, y: number, R: number, dir: number, callback: FOVCallback): void;
+ compute90(x: number, y: number, R: number, dir: number, callback: FOVCallback): void;
+ }
+ namespace RecursiveShadowcasting {
+ const OCTANTS: Array<[number, number, number, number]>;
+ }
+}
+
+export type ColorArray = [number, number, number];
+
+export namespace Color {
+ function fromString(str: string): [number, number, number];
+ function add(color1: [number, number, number], ...colors2: Array<[number, number, number]>): [number, number, number];
+ function add_(color1: [number, number, number], ...colors2: Array<[number, number, number]>): [number, number, number];
+ function multiply(color1: [number, number, number], ...colors2: Array<[number, number, number]>): [number, number, number];
+ function multiply_(color1: [number, number, number], ...colors2: Array<[number, number, number]>): [number, number, number];
+ function interpolate(color1: [number, number, number], color2: [number, number, number], factor: number): [number, number, number];
+ function interpolateHSL(color1: [number, number, number], color2: [number, number, number], factor: number): [number, number, number];
+ function randomize(color: [number, number, number], diff: number | [number, number, number]): [number, number, number];
+ function rgb2hsl(color: [number, number, number]): [number, number, number];
+ function hsl2rgb(color: [number, number, number]): [number, number, number];
+ function toRGB(color: [number, number, number]): string;
+ function toHex(color: [number, number, number]): string;
+}
+
+// Lighting callbacks
+export type ReflectivityCallback = (x: number, y: number) => number;
+export type LightingCallback = (x: number, y: number, color: [number, number, number]) => any;
+
+export interface LightingOptions {
+ passes?: number;
+ emissionThreshold?: number;
+ range?: number;
+}
+
+export class Lighting {
+ constructor(reflectivityCallback: ReflectivityCallback, options?: LightingOptions);
+
+ setOptions(options: LightingOptions): this;
+ setFOV(fov: FOV): this;
+ setLight(x: number, y: number, color: string | [number, number, number]): this;
+ clearLights(): void;
+ reset(): this;
+ compute(lightingCallback: LightingCallback): this;
+}
+
+// Path callbacks
+export type PassableCallback = (x: number, y: number) => boolean;
+export type PathCallback = (x: number, y: number) => any;
+
+export interface PathOptions {
+ topology?: number;
+}
+
+export class Path {
+ constructor(toX: number, toY: number, passableCallback: PassableCallback, options?: PathOptions);
+
+ compute(fromX: number, fromY: number, callback: PathCallback): void;
+}
+
+export namespace Path {
+ class Dijkstra extends Path { }
+ class AStar extends Path { }
+}
+
+export interface Noise {
+ get(x: number, y: number): number;
+}
+export namespace Noise {
+ class Simplex implements Noise {
+ constructor(gradients?: number);
+ get(xin: number, yin: number): number;
+ }
+}
+
+export class EventQueue {
+ getTime(): number;
+ clear(): void;
+ add(event: T, time: number): void;
+ get(): T;
+ getEventTime(event: T): number;
+ remove(event: T): boolean;
+ remove(index: number): boolean;
+}
+
+export class Scheduler {
+ getTime(): number;
+ add(item: any, repeat: boolean, time?: number): Scheduler;
+ getTimeOf(item: any): number;
+ clear(): Scheduler;
+ remove(item: any): any;
+ next(): any;
+}
+
+export namespace Scheduler {
+ class Simple extends Scheduler { }
+ class Speed extends Scheduler { }
+ class Action extends Scheduler {
+ setDuration(time: number): Action;
+ }
+}
+
+export class Engine {
+ constructor(scheduler: Scheduler);
+ start(): Engine;
+ lock(): Engine;
+ unlock(): Engine;
+}
+
+export interface StringGeneratorOptions {
+ words?: boolean;
+ order?: number;
+ prior?: number;
+}
+
+export class StringGenerator {
+ constructor(options?: StringGeneratorOptions);
+ clear(): void;
+ generate(): string;
+ observe(str: string): void;
+ getStats(): string;
+}
diff --git a/types/rot-js/rot-js-tests.ts b/types/rot-js/rot-js-tests.ts
new file mode 100644
index 0000000000..bef1de16d3
--- /dev/null
+++ b/types/rot-js/rot-js-tests.ts
@@ -0,0 +1,1119 @@
+import * as ROT from 'rot-js';
+
+// All the documentation uses a function called `SHOW` to do results; stubbing it out here
+// so that I can copy/paste directly.
+function SHOW(...something: any[]): any {
+ console.log(something);
+}
+
+let w = 80;
+let h = 40;
+let display: ROT.Display;
+
+// Introduction / Browser Support
+
+SHOW(
+ ROT.isSupported()
+);
+
+// JS Enhancements / Array
+
+SHOW(
+ ["apples", "oranges", "zombies"].random(),
+ ["apples", "oranges", "zombies"].randomize()
+);
+
+// JS Enhancements / String
+
+SHOW(
+ "hello world".capitalize(),
+ "hello %s, this is %s".format("world", "sparta"),
+ "7".lpad("0", 3),
+ "123".rpad(".", 6)
+);
+
+// JS Enhancements / Number
+
+SHOW(
+ (15) % 7,
+ (-15) % 7,
+ (15).mod(7),
+ (-15).mod(7)
+);
+
+// JS Enhancements / (Object | Function) not included, as TS does that for us
+
+// JS Enhancements / String formatting
+SHOW(
+ String.format("%s %s", "hello", "world"),
+ "%s %s".format("hello", "world")
+);
+// ----
+
+let myObj = {
+ foo() { return "bar"; }
+};
+String.format.map['f'] = "foo";
+
+SHOW("%f".format(myObj));
+// ----
+
+class Item {
+ constructor(private name: string) { }
+
+ a() {
+ let first = self.name.charAt(0);
+ return (first.match(/[aeiouy]/i) ? "an" : "a") + " " + this.name;
+ }
+
+ the() {
+ return `the ${this.name}`;
+ }
+}
+
+String.format.map['a'] = "a";
+String.format.map['the'] = "the";
+
+let apple = new Item("apple");
+let banana = new Item("banana");
+let template = "You eat %a. %The was delicious.";
+
+SHOW(template.format(apple, apple));
+SHOW(template.format(banana, banana));
+// ----
+
+class Animal {
+ constructor(private name: string) { }
+ adjective(x: string) {
+ return `${x} ${this.name}`;
+ }
+}
+
+String.format.map['adjective'] = "adjective";
+
+let cat = new Animal("cat");
+template = "You see a %{adjective,black}.";
+
+SHOW(template.format(cat));
+
+// Keyboard Handling not included, as nothing ROT specific in there
+
+// RNG / Generating random values
+
+SHOW(
+ ROT.RNG.getUniform(),
+ ROT.RNG.getNormal(0, 10),
+ ROT.RNG.getPercentage()
+);
+// ----
+
+let canvas = document.createElement("canvas");
+canvas.width = 500;
+canvas.height = 200;
+SHOW(canvas);
+
+let ctx = canvas.getContext("2d");
+ctx!.fillStyle = "#fff";
+ctx!.fillRect(0, 0, canvas.width, canvas.height);
+ctx!.fillStyle = "#f00";
+
+let data = new Array();
+for (let i = 0; i < 40000; i++) { /* generate histogram */
+ let num = Math.round(ROT.RNG.getNormal(250, 100));
+ data[num] = (data[num] || 0) + 1;
+}
+
+for (let i = 0; i < data.length; i++) { /* plot histogram */
+ ctx!.fillRect(i, canvas.height - data[i], 1, data[i]);
+}
+
+// RNG / Working with RNG state
+let state = ROT.RNG.getState();
+SHOW(ROT.RNG.getUniform());
+
+ROT.RNG.setState(state);
+SHOW(ROT.RNG.getUniform());
+// ----
+
+let seed = ROT.RNG.getSeed();
+
+ROT.RNG.setSeed(12345);
+SHOW(
+ ROT.RNG.getUniform(),
+ ROT.RNG.getUniform()
+);
+
+ROT.RNG.setSeed(12345);
+SHOW(
+ ROT.RNG.getUniform(),
+ ROT.RNG.getUniform()
+);
+
+// RNG / Cloning a RNG
+
+let clone = ROT.RNG.clone();
+
+SHOW(ROT.RNG.getUniform());
+SHOW(clone.getUniform());
+
+ROT.RNG.setSeed(123);
+SHOW(ROT.RNG.getUniform());
+SHOW(clone.getUniform());
+
+// RNG / Picking a weighted value
+let monsters = {
+ orc: 3,
+ ogre: 1,
+ rat: 5
+};
+
+for (let i = 0; i < 20; i++) {
+ SHOW(ROT.RNG.getWeightedValue(monsters));
+}
+
+// Console Display / Creating a display
+
+display = new ROT.Display({ width: 20, height: 5 });
+SHOW(display.getContainer()); /* do not forget to append to page! */
+
+// Console Display / Configuring the display
+display = new ROT.Display({ width: 20, height: 5 });
+SHOW(display.getContainer());
+display.setOptions({
+ width: 30,
+ fontSize: 8,
+ fontStyle: "bold",
+ bg: "#a00"
+});
+
+// Console Display / Drawing individual characters
+
+display = new ROT.Display({ width: 40, height: 9 });
+SHOW(display.getContainer());
+
+display.draw(5, 4, "@");
+display.draw(15, 4, "%", "#0f0"); /* foreground color */
+display.draw(25, 4, "#", "#f00", "#009"); /* and background color */
+
+// Console Display / Drawing strings
+
+display = new ROT.Display({ width: 40, height: 20 });
+SHOW(display.getContainer());
+
+display.drawText(5, 2, "Hello world");
+
+/* last argument specifies maximum length */
+display.drawText(20, 5, "This line of text is very long.", 16);
+
+/* lines are broken at word boundaries; lines are trimmed */
+let words = ["lorem", "ipsum", "dolor", "sit", "amet"];
+let long: string[] = [];
+for (let i = 0; i < 30; i++) { long.push(words.random()); }
+let longer = long.join(" ");
+
+display.drawText(1, 10, longer, 38);
+
+// Console Display / Specifying foreground/background color in strings
+
+display = new ROT.Display({ width: 40, height: 5 });
+SHOW(display.getContainer());
+
+let str = "Goodbye %c{red}cr%b{blue}u%b{}el %c{}world";
+display.drawText(5, 2, str);
+
+// Console Display / Forced square aspect ratio
+let options: ROT.DisplayOptions = {
+ width: 20,
+ height: 8,
+ fontSize: 18,
+ forceSquareRatio: true
+};
+display = new ROT.Display(options);
+SHOW(display.getContainer());
+
+str = "Using a regular grid\n@....%b{blue}#%b{}##.%b{red}.%b{}.$$$";
+display.drawText(2, 2, str);
+
+// Console display / graphical tiles
+
+let tileSet = document.createElement("img");
+tileSet.src = "tiles.png";
+
+options = {
+ layout: "tile",
+ bg: "transparent",
+ tileWidth: 64,
+ tileHeight: 64,
+ tileSet,
+ tileMap: {
+ "@": [0, 0],
+ "#": [0, 64],
+ a: [64, 0],
+ "!": [64, 64]
+ },
+ width: 3,
+ height: 3
+};
+display = new ROT.Display(options);
+SHOW(display.getContainer());
+
+tileSet.onload = () => {
+ display.draw(1, 1, "@");
+ display.draw(0, 0, "#");
+ display.draw(0, 1, "#");
+ display.draw(1, 0, "#");
+ display.draw(0, 2, "#");
+ display.draw(2, 2, "a");
+ display.draw(2, 0, "!");
+ display.draw(2, 1, "!");
+};
+
+// Console display / graphical tiles / Multiple tiles at one place
+tileSet = document.createElement("img");
+tileSet.src = "tiles.png";
+
+options = {
+ layout: "tile",
+ bg: "transparent",
+ tileWidth: 64,
+ tileHeight: 64,
+ tileSet,
+ tileMap: {
+ "@": [0, 0],
+ "#": [0, 64]
+ },
+ width: 1,
+ height: 1
+};
+display = new ROT.Display(options);
+SHOW(display.getContainer());
+
+tileSet.onload = () => {
+ display.draw(0, 0, ["#", "@"]);
+};
+
+// Console display / graphical tiles / Colorizing tiles
+
+tileSet = document.createElement("img");
+tileSet.src = "tiles.png";
+
+options = {
+ layout: "tile",
+ tileWidth: 64,
+ tileHeight: 64,
+ tileSet,
+ tileMap: {
+ "@": [0, 0],
+ "#": [0, 64],
+ a: [64, 0],
+ "!": [64, 64]
+ },
+ width: 3,
+ height: 2,
+ tileColorize: true
+};
+display = new ROT.Display(options);
+SHOW(display.getContainer());
+
+tileSet.onload = () => {
+ display.draw(0, 0, "@", "transparent");
+ display.draw(1, 0, "@", "green", "red");
+ display.draw(2, 0, "@", "rgba(30, 200, 30, 0.5)", "red");
+ display.draw(0, 1, "#", "transparent");
+ display.draw(1, 1, "#", "white");
+ display.draw(2, 1, "#", "transparent", "rgba(250, 250, 0, 0.5)");
+};
+
+// Map creation
+let map = new ROT.Map.Arena(3, 3);
+let userCallback = (x: number, y: number, value: number) => {
+ SHOW("Value %s generated at [%s,%s]".format(value, x, y));
+};
+map.create(userCallback);
+map = new ROT.Map.Arena(10, 5);
+
+let display1 = new ROT.Display({ width: 10, height: 5, fontSize: 18 });
+SHOW(display1.getContainer());
+
+map.create((x, y, wall) => {
+ display1.draw(x, y, wall ? "#" : ".");
+});
+
+/* debugging with small font */
+let display2 = new ROT.Display({ width: 10, height: 5, fontSize: 8 });
+SHOW(display2.getContainer());
+map.create(display2.DEBUG);
+
+// Map creation / Maze / DividedMaze
+let dm = new ROT.Map.DividedMaze(w, h);
+
+for (let i = 0; i < 4; i++) {
+ display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+ SHOW(display.getContainer());
+
+ dm.create(display.DEBUG);
+}
+
+// Map creation / Maze / Icey's Maze
+
+for (let i = 0; i < 4; i++) {
+ display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+ SHOW(display.getContainer());
+
+ let maze = new ROT.Map.IceyMaze(w, h, 4 * i);
+ maze.create(display.DEBUG);
+}
+
+// Map creation / Maze / Eller's Perfect Maze
+
+let em = new ROT.Map.EllerMaze(w, h);
+
+for (let i = 0; i < 4; i++) {
+ display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+ SHOW(display.getContainer());
+
+ em.create(display.DEBUG);
+}
+
+// Map creation / Cellular
+let cellular = new ROT.Map.Cellular(w, h);
+
+/* cells with 1/2 probability */
+cellular.randomize(0.5);
+
+/* generate and show four generations */
+for (let i = 0; i < 4; i++) {
+ display = new ROT.Display({ width: w, height: h, fontSize: 4 });
+ SHOW(display.getContainer());
+ cellular.create(display.DEBUG);
+}
+// ----
+
+w = 100; h = 60;
+display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+SHOW(display.getContainer());
+
+/* custom born/survive rules */
+cellular = new ROT.Map.Cellular(w, h, {
+ born: [4, 5, 6, 7, 8],
+ survive: [2, 3, 4, 5]
+});
+
+cellular.randomize(0.9);
+
+/* generate fifty iterations, show the last one */
+for (let i = 49; i >= 0; i--) {
+ cellular.create(i ? undefined : display.DEBUG);
+}
+// ----
+
+/* create a connected map where the player can reach all non-wall sections */
+cellular = new ROT.Map.Cellular(w, h, { connected: true });
+
+/* cells with 1/2 probability */
+cellular.randomize(0.5);
+
+/* make a few generations */
+for (let i = 0; i < 4; i++) cellular.create();
+
+/* display only the final map */
+display = new ROT.Display({ width: w, height: h, fontSize: 4 });
+SHOW(display.getContainer());
+cellular.create(display.DEBUG);
+
+/* now connect the maze */
+display = new ROT.Display({ width: w, height: h, fontSize: 4 });
+SHOW(display.getContainer());
+cellular.connect(display.DEBUG);
+// ----
+
+/* create a connected map where the player can reach all non-wall sections */
+cellular = new ROT.Map.Cellular(w, h, { connected: true });
+
+/* cells with 1/2 probability */
+cellular.randomize(0.5);
+
+/* make a few generations */
+for (let i = 0; i < 4; i++) cellular.create();
+
+/* display only the final map */
+display = new ROT.Display({ width: w, height: h, fontSize: 4 });
+SHOW(display.getContainer());
+cellular.create(display.DEBUG);
+
+/* now connect the maze */
+display = new ROT.Display({ width: w, height: h, fontSize: 4 });
+SHOW(display.getContainer());
+cellular.connect(display.DEBUG, 1);
+// ----
+
+/* create a connected map where the player can reach all non-wall sections */
+cellular = new ROT.Map.Cellular(w, h, { connected: true });
+
+/* cells with 1/2 probability */
+cellular.randomize(0.5);
+
+/* make a few generations */
+for (let i = 0; i < 4; i++) cellular.create();
+
+/* display only the final map */
+display = new ROT.Display({ width: w, height: h, fontSize: 4 });
+SHOW(display.getContainer());
+cellular.create(display.DEBUG);
+
+/* now connect the maze */
+display = new ROT.Display({ width: w, height: h, fontSize: 4 });
+SHOW(display.getContainer());
+cellular.connect(display.DEBUG, 0, (from, to) => {
+ SHOW("Connection was made " + from + " to " + to);
+});
+
+// Map creation / Dungeon
+
+ROT.RNG.setSeed(1234);
+let dungeon = new ROT.Map.Digger();
+display = new ROT.Display({ fontSize: 8 });
+SHOW(display.getContainer());
+dungeon.create(display.DEBUG);
+
+let drawDoor = (x: number, y: number) => {
+ display.draw(x, y, "", "", "red");
+};
+
+let rooms = dungeon.getRooms();
+for (let i = 0; i < rooms.length; i++) {
+ let room = rooms[i];
+ SHOW("Room #%s: [%s, %s] => [%s, %s]".format(
+ (i + 1),
+ room.getLeft(), room.getTop(),
+ room.getRight(), room.getBottom()
+ ));
+
+ room.getDoors(drawDoor);
+}
+
+// Map creation / Dungeon / Digger
+dungeon = new ROT.Map.Digger(w, h);
+
+for (let i = 0; i < 4; i++) {
+ display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+ SHOW(display.getContainer());
+ dungeon.create(display.DEBUG);
+}
+
+// Map creation / Dungeon / Uniform
+
+let uniform = new ROT.Map.Uniform(w, h);
+
+for (let i = 0; i < 4; i++) {
+ display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+ SHOW(display.getContainer());
+ uniform.create(display.DEBUG);
+}
+
+// Map creation / Dungeon / Rogue
+
+let rogue = new ROT.Map.Rogue(w, h);
+display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+SHOW(display.getContainer());
+rogue.create(display.DEBUG);
+
+// FOV computation / Precise shadowcasting
+
+ROT.RNG.setSeed(12345);
+// Not supported in ES6/TS2
+// ROT.DEFAULT_WIDTH = 80;
+// ROT.DEFAULT_HEIGHT = 30;
+
+display = new ROT.Display({ fontSize: 2 });
+SHOW(display.getContainer());
+
+/* create a map */
+let data_uniform: any = {};
+new ROT.Map.Uniform().create((x: number, y: number, type: number) => {
+ data_uniform[x + "," + y] = type;
+ display.DEBUG(x, y, type);
+});
+
+/* input callback */
+let lightPasses = (x: number, y: number) => {
+ let key = x + "," + y;
+ if (key in data_uniform) { return (data_uniform[key] === 0); }
+ return false;
+};
+
+let fov = new ROT.FOV.PreciseShadowcasting(lightPasses);
+
+/* output callback */
+fov.compute(50, 22, 10, (x, y, r, visibility) => {
+ let ch = (r ? "" : "@");
+ let color = (data_uniform[x + "," + y] ? "#aa0" : "#660");
+ display.draw(x, y, ch, "#fff", color);
+});
+
+// FOV computation / Recursive shadowcasting
+
+ROT.RNG.setSeed(12345);
+const DIR_NORTH = 0;
+const DIR_WEST = 6;
+
+display = new ROT.Display({ fontSize: 12 });
+SHOW(display.getContainer());
+
+/* create a map */
+data_uniform = {};
+new ROT.Map.Uniform().create((x: number, y: number, type: number) => {
+ data_uniform[x + "," + y] = type;
+ display.DEBUG(x, y, type);
+});
+
+/* input callback */
+lightPasses = (x: number, y: number) => {
+ let key = x + "," + y;
+ if (key in data_uniform) { return (data_uniform[key] === 0); }
+ return false;
+};
+
+let fov2 = new ROT.FOV.RecursiveShadowcasting(lightPasses);
+
+/* output callback for mob with bad vision */
+fov2.compute90(50, 22, 10, DIR_WEST, (x: number, y: number, r: number, visibility: number) => {
+ let ch = (r ? "1" : "@");
+ let color = (data_uniform[x + "," + y] ? "#aa0" : "#660");
+ display.draw(x, y, ch, "#fff", color);
+});
+
+/* output callback for second mob with better vision */
+fov2.compute180(57, 14, 10, DIR_NORTH, (x: number, y: number, r: number, visibility: number) => {
+ let ch = (r ? "2" : "@");
+ let color = (data_uniform[x + "," + y] ? "#aa0" : "#660");
+ display.draw(x, y, ch, "#fff", color);
+});
+
+/* output callback for third mob with supernatural vision */
+fov2.compute(65, 5, 10, (x: number, y: number, r: number, visibility: number) => {
+ let ch = (r ? "3" : "@");
+ let color = (data_uniform[x + "," + y] ? "#aa0" : "#660");
+ display.draw(x, y, ch, "#fff", color);
+});
+
+// Color / Converting string → array
+SHOW(
+ ROT.Color.fromString("rgb(10, 128, 230)"),
+ ROT.Color.fromString("#faa"),
+ ROT.Color.fromString("#83fcc4"),
+ ROT.Color.fromString("goldenrod")
+);
+
+// Color / Converting array → string
+SHOW(
+ ROT.Color.toRGB([10, 128, 230]),
+ ROT.Color.toHex([10, 128, 230])
+);
+
+// Color / Converting between RGB and HSL
+SHOW(
+ ROT.Color.rgb2hsl([51, 102, 51]),
+ ROT.Color.hsl2rgb([0.333, 0.333, 0.3])
+);
+
+// Color / Adding and mixing colors
+SHOW( /* addition = lightening */
+ ROT.Color.add([10, 128, 230], [200, 10, 15], [30, 30, 100]),
+ ROT.Color.add_([10, 128, 230], [200, 10, 15])
+);
+SHOW( /* multiplication = darkening */
+ ROT.Color.multiply([10, 128, 230], [200, 10, 15]),
+ ROT.Color.multiply_([10, 128, 230], [200, 10, 15])
+);
+
+// Color / Interpolating between two colors
+SHOW(
+ /* computed in RGB space */
+ ROT.Color.interpolate([10, 128, 230], [30, 255, 255], 0.3),
+
+ /* computed in HSL space */
+ ROT.Color.interpolateHSL([10, 128, 230], [30, 255, 255], 0.3)
+);
+
+// Color / Creating random letiants
+SHOW(
+ ROT.Color.randomize([100, 128, 230], [30, 10, 20]),
+ ROT.Color.randomize([100, 128, 230], [30, 10, 20]),
+ ROT.Color.randomize([100, 128, 230], [30, 10, 20])
+);
+
+// Color / Lighting
+ROT.RNG.setSeed(12345);
+
+let mapData: any = {};
+let lightData: { [key: string]: [number, number, number] } = {};
+
+/* build a map */
+cellular = new ROT.Map.Cellular().randomize(0.5);
+let createCallback = (x: number, y: number, value: number) => {
+ mapData[x + "," + y] = value;
+};
+for (let i = 0; i < 4; i++) {
+ cellular.create(createCallback);
+}
+
+/* prepare a FOV algorithm */
+lightPasses = (x: number, y: number) => {
+ return (mapData[x + "," + y] === 1);
+};
+fov = new ROT.FOV.PreciseShadowcasting(lightPasses, { topology: 4 });
+
+/* prepare a lighting algorithm */
+let reflectivity = (x: number, y: number) => {
+ return (mapData[x + "," + y] === 1 ? 0.3 : 0);
+};
+let lighting = new ROT.Lighting(reflectivity, { range: 12, passes: 2 });
+lighting.setFOV(fov);
+lighting.setLight(12, 12, [240, 240, 30]);
+lighting.setLight(20, 20, [240, 60, 60]);
+lighting.setLight(45, 25, [200, 200, 200]);
+
+let lightingCallback = (x: number, y: number, color: [number, number, number]) => {
+ lightData[x + "," + y] = color;
+};
+lighting.compute(lightingCallback);
+
+/* draw the resulting mix of mapData and lightData */
+display = new ROT.Display({ fontSize: 8 });
+SHOW(display.getContainer());
+
+/* all cells are lit by ambient light; some are also lit by light sources */
+let ambientLight: ROT.ColorArray = [100, 100, 100];
+for (let id in mapData) {
+ let parts = id.split(",");
+ let x = parseInt(parts[0], 10);
+ let y = parseInt(parts[1], 10);
+
+ let baseColor: ROT.ColorArray = (mapData[id] ? [100, 100, 100] : [50, 50, 50]);
+ let light = ambientLight;
+
+ if (id in lightData) { /* add light from our computation */
+ light = ROT.Color.add(light, lightData[id]);
+ }
+
+ let finalColor = ROT.Color.multiply(baseColor, light);
+ display.draw(x, y, "@", "white", ROT.Color.toRGB(finalColor));
+}
+
+// Pathfinding / Dijkstra's algorithm
+
+ROT.RNG.setSeed(12345);
+display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+SHOW(display.getContainer());
+
+/* generate map and store its data */
+let uni_data: any = {};
+let uni_map = new ROT.Map.Uniform(w, h);
+uni_map.create((x, y, value) => {
+ uni_data[x + "," + y] = value;
+ display.DEBUG(x, y, value);
+});
+
+/* input callback informs about map structure */
+let passableCallback = (x: number, y: number) => {
+ return (uni_data[x + "," + y] === 0);
+};
+
+/* prepare path to given coords */
+let dijkstra = new ROT.Path.Dijkstra(98, 38, passableCallback);
+
+/* compute from given coords #1 */
+dijkstra.compute(8, 45, (x: number, y: number) => {
+ display.draw(x, y, "", "", "#800");
+});
+
+/* compute from given coords #2 */
+dijkstra.compute(130, 8, (x: number, y: number) => {
+ display.draw(x, y, "", "", "#800");
+});
+
+/* highlight */
+display.draw(8, 45, "", "", "#3f3");
+display.draw(130, 8, "", "", "#3f3");
+display.draw(98, 38, "", "", "#f33");
+
+// Pathfinding / A* algorithm
+
+ROT.RNG.setSeed(12345);
+display = new ROT.Display({ width: w, height: h, fontSize: 6 });
+SHOW(display.getContainer());
+
+/* generate map and store its data */
+uni_map = new ROT.Map.Uniform(w, h);
+uni_map.create((x: number, y: number, value: number) => {
+ uni_data[x + "," + y] = value;
+ display.DEBUG(x, y, value);
+});
+
+/* input callback informs about map structure */
+passableCallback = (x: number, y: number) => {
+ return (uni_data[x + "," + y] === 0);
+};
+
+/* prepare path to given coords */
+let astar = new ROT.Path.AStar(98, 38, passableCallback);
+
+/* compute from given coords #1 */
+astar.compute(8, 45, (x: number, y: number) => {
+ display.draw(x, y, "", "", "#800");
+});
+
+/* compute from given coords #2 */
+astar.compute(130, 8, (x: number, y: number) => {
+ display.draw(x, y, "", "", "#800");
+});
+
+/* highlight */
+display.draw(8, 45, "", "", "#3f3");
+display.draw(130, 8, "", "", "#3f3");
+display.draw(98, 38, "", "", "#f33");
+
+// Noise Generation
+let noise = new ROT.Noise.Simplex();
+
+display = new ROT.Display({ width: w, height: h, fontSize: 3 });
+SHOW(display.getContainer());
+
+for (let j = 0; j < h; j++) {
+ for (let i = 0; i < w; i++) {
+ let val = noise.get(i / 20, j / 20) * 255;
+
+ let r = ~~(val > 0 ? val : 0);
+ let g = ~~(val < 0 ? -val : 0);
+ display.draw(i, j, "", "", "rgb(" + r + "," + g + ",0)");
+ }
+}
+// ----
+noise = new ROT.Noise.Simplex();
+
+display = new ROT.Display({ width: w, height: h, fontSize: 12, layout: "hex" });
+SHOW(display.getContainer());
+
+for (let j = 0; j < h; j++) {
+ for (let i = j % 2; i < w; i += 2) {
+ let val = noise.get(i / 60, j / 60) * 255;
+
+ let r = ~~(val > 0 ? val : 0);
+ let g = ~~(val < 0 ? -val : 0);
+ display.draw(i, j, "", "", "rgb(" + r + "," + g + ",0)");
+ }
+}
+
+// Timing & scheduling / event queue
+
+let queue = new ROT.EventQueue();
+
+queue.add("event 1", 100); /* queued after 100 time units */
+queue.add("event 2", 10); /* queued after 10 time units */
+queue.add("event 3", 50); /* queued after 50 time units */
+
+queue.remove("event 2");
+SHOW(
+ queue.get(),
+ queue.get(),
+ queue.getTime()
+);
+
+// Timing & scheduling / Simple scheduler
+
+let scheduler = new ROT.Scheduler.Simple();
+
+/* generate some actors */
+for (let i = 0; i < 4; i++) {
+ scheduler.add(i + 1, true); /* true = recurring actor */
+}
+
+/* simulate several turns */
+let turns: any = [];
+for (let i = 0; i < 20; i++) {
+ let current = scheduler.next();
+ turns.push(current);
+}
+
+SHOW("\nGenerated order of actors:");
+SHOW(turns.join(" ") + " ...");
+
+// Timing & scheduling / Speed scheduler
+
+scheduler = new ROT.Scheduler.Speed();
+
+class Actor {
+ constructor(public speed: number, public number: number) { }
+
+ getSpeed() { return this.speed; }
+}
+
+/* generate some actors */
+for (let i = 0; i < 4; i++) {
+ let actor = new Actor(ROT.RNG.getPercentage(), i + 1);
+ scheduler.add(actor, true);
+ SHOW("Object #%s has speed %s.".format(actor.number, actor.speed));
+}
+
+/* simulate several turns */
+turns = [];
+for (let i = 0; i < 40; i++) {
+ let current = scheduler.next();
+ turns.push(current.number);
+}
+
+SHOW("\nGenerated order of turns:");
+SHOW(turns.join(" ") + " ...");
+
+// Timing & scheduling / Action-duration scheduler
+
+let act_scheduler = new ROT.Scheduler.Action();
+
+/* generate some actors */
+for (let i = 0; i < 4; i++) {
+ act_scheduler.add(i + 1, true, i); /* last argument - initial delay */
+}
+
+/* simulate several turns */
+template = "Actor %s performing action for %s time units (current time: %s)";
+for (let i = 0; i < 20; i++) {
+ let current = act_scheduler.next();
+
+ let actionDuration = Math.ceil(ROT.RNG.getUniform() * 20);
+ act_scheduler.setDuration(actionDuration);
+
+ let padded = actionDuration.toString().lpad("0");
+ SHOW(template.format(current, padded, act_scheduler.getTime()));
+}
+
+// Timing & scheduling / Engine / Asynchronous game engine
+scheduler = new ROT.Scheduler.Simple();
+let engine = new ROT.Engine(scheduler);
+let output: any = [];
+
+class Actor1 {
+ constructor(public lives: number) { }
+
+ act() {
+ output.push(".");
+ this.lives--;
+ if (!this.lives) {
+ scheduler.remove(actor1);
+ engine.lock(); /* pause execution */
+ setTimeout(unlock, 500); /* wait for 500ms */
+ }
+ }
+}
+
+/* sample actor: pauses the execution when dead */
+let actor1 = new Actor1(3);
+scheduler.add(actor1, true);
+
+class Actor2 {
+ act() {
+ output.push("@");
+ }
+}
+
+let unlock = () => { /* called asynchronously */
+ let actor2 = new Actor2();
+
+ output = [];
+ scheduler.add(actor2, false); /* add second (non-repeating) actor */
+ engine.unlock(); /* continue execution */
+ SHOW(output.join(""));
+};
+
+engine.start();
+SHOW(output.join(""));
+
+// Timing & scheduling / Engine / Promises
+
+scheduler = new ROT.Scheduler.Simple();
+engine = new ROT.Engine(scheduler);
+output = [];
+
+/* sample actor: pauses the execution when dead */
+class Actor3 {
+ constructor(public lives: number) { }
+
+ act() {
+ let done: any = null;
+ let promise = {
+ then(cb: any) { done = cb; }
+ };
+
+ output.push(".");
+ SHOW(output.join(""));
+ this.lives--;
+
+ /* if alive, wait for 500ms for next turn */
+ if (this.lives) {
+ setTimeout(() => { done(); }, 500);
+ }
+
+ return promise;
+ }
+}
+let actor3 = new Actor3(3);
+
+scheduler.add(actor3, true);
+
+engine.start();
+
+// String generator
+
+let sg = new ROT.StringGenerator();
+
+let req = new XMLHttpRequest();
+req.open("get", "java.txt", true);
+req.send();
+
+req.onreadystatechange = () => {
+ if (req.readyState !== 4) { return; }
+
+ let lines = req.responseText.split("\n");
+ while (lines.length) {
+ let line = lines.pop()!.trim();
+ if (!line) { continue; }
+ sg.observe(line);
+ }
+
+ for (let i = 0; i < 20; i++) { SHOW(sg.generate()); }
+};
+
+// Hex support / Displaying hexes
+display = new ROT.Display({ width: 8, height: 5 });
+SHOW(display.getContainer());
+
+for (let y = 0; y < 5; y++) {
+ for (let x = y % 2; x < 8; x += 2) {
+ display.draw(x, y, "•");
+ }
+}
+// ----
+
+display = new ROT.Display({ width: 8, height: 5, layout: "hex" });
+SHOW(display.getContainer());
+
+for (let y = 0; y < 5; y++) {
+ for (let x = y % 2; x < 8; x += 2) {
+ let bg = ["#333", "#666", "#999", "#ccc", "#fff"].random();
+ display.draw(x, y, "•", "#000", bg);
+ }
+}
+// ----
+
+display = new ROT.Display({
+ width: 10, height: 4, spacing: 2.5,
+ layout: "hex", transpose: true
+});
+SHOW(display.getContainer());
+
+for (let y = 0; y < 4; y++) {
+ for (let x = y % 2; x < 10; x += 2) {
+ let bg = ["#333", "#666", "#999", "#ccc", "#fff"].random();
+ display.draw(x, y, x + "," + y, "#000", bg);
+ }
+}
+
+// Hex support / Cellular dungeon generator
+
+display = new ROT.Display({ width: w, height: h, fontSize: 10, layout: "hex" });
+SHOW(display.getContainer());
+
+/* hexagonal map and rules */
+let cell_map = new ROT.Map.Cellular(w, h, {
+ topology: 6,
+ born: [4, 5, 6],
+ survive: [3, 4, 5, 6]
+});
+
+/* initialize with irregularly random values */
+for (let i = 0; i < w; i++) {
+ for (let j = 0; j < h; j++) {
+ let dx = i / w - 0.5;
+ let dy = j / h - 0.5;
+ let dist = Math.pow(dx * dx + dy * dy, 0.3);
+ if (ROT.RNG.getUniform() < dist) { cell_map.set(i, j, 1); }
+ }
+}
+
+/* generate four iterations, show the last one */
+for (let i = 4; i >= 0; i--) {
+ cell_map.create(i ? undefined : display.DEBUG);
+}
+
+// Hex support / Pathfinding
+
+ROT.RNG.setSeed(12345);
+display = new ROT.Display({ width: w, height: h, fontSize: 6, layout: "hex" });
+SHOW(display.getContainer());
+
+/* generate map and store its data */
+let cell_data: any = {};
+cell_map = new ROT.Map.Cellular(w, h, {
+ topology: 6,
+ born: [4, 5, 6],
+ survive: [3, 4, 5, 6]
+});
+cell_map.randomize(0.48);
+cell_map.create(); /* two iterations */
+cell_map.create((x: number, y: number, value: number) => {
+ cell_data[x + "," + y] = value;
+ display.DEBUG(x, y, value);
+});
+
+/* input callback informs about map structure */
+passableCallback = (x: number, y: number) => {
+ return (cell_data[x + "," + y] === 0);
+};
+
+/* prepare path to given coords */
+dijkstra = new ROT.Path.Dijkstra(120, 64, passableCallback, { topology: 6 });
+
+/* compute from given coords */
+dijkstra.compute(30, 16, (x: number, y: number) => {
+ display.draw(x, y, "", "", "#800");
+});
+
+/* highlight */
+display.draw(30, 16, "", "", "#3f3");
+display.draw(120, 64, "", "", "#f33");
+
+// Hex support / Field of view
+
+ROT.RNG.setSeed(12345);
+
+display = new ROT.Display({ fontSize: 12, layout: "hex" });
+SHOW(display.getContainer());
+
+/* generate map and store its data */
+cell_data = {};
+cell_map = new ROT.Map.Cellular(undefined, undefined, {
+ topology: 6,
+ born: [4, 5, 6],
+ survive: [3, 4, 5, 6]
+});
+cell_map.randomize(0.4);
+cell_map.create((x: number, y: number, value: number) => {
+ cell_data[x + "," + y] = value;
+ display.DEBUG(x, y, value);
+});
+
+/* input callback */
+lightPasses = (x: number, y: number) => {
+ let key = x + "," + y;
+ if (key in cell_data) { return (cell_data[key] === 0); }
+ return false;
+};
+
+fov = new ROT.FOV.PreciseShadowcasting(lightPasses, { topology: 6 });
+
+/* output callback */
+fov.compute(20, 14, 6, (x, y, r, vis) => {
+ let ch = (r ? "" : "@");
+ let color = (cell_data[x + "," + y] ? "#aa0" : "#660");
+ display.draw(x, y, ch, "#fff", color);
+});
diff --git a/types/rot-js/tsconfig.json b/types/rot-js/tsconfig.json
new file mode 100644
index 0000000000..fb42352fb7
--- /dev/null
+++ b/types/rot-js/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "lib": [
+ "es6",
+ "dom"
+ ],
+ "noImplicitAny": true,
+ "noImplicitThis": true,
+ "strictNullChecks": true,
+ "baseUrl": "../",
+ "typeRoots": [
+ "../"
+ ],
+ "types": [],
+ "noEmit": true,
+ "forceConsistentCasingInFileNames": true
+ },
+ "files": [
+ "index.d.ts",
+ "rot-js-tests.ts"
+ ]
+}
diff --git a/types/rot-js/tslint.json b/types/rot-js/tslint.json
new file mode 100644
index 0000000000..3db14f85ea
--- /dev/null
+++ b/types/rot-js/tslint.json
@@ -0,0 +1 @@
+{ "extends": "dtslint/dt.json" }