rot.js is a set of JavaScript libraries for developing in browser roguelikes. rot.js is based on libtcod another roguelike library I did a tutorial for in Python a while ago. Going through that tutorial really helped illustrate a lot of OOP concepts, so I figured it would be interesting and helpful that to see how a concept I was familiar with was implemented in JavaScript.

Rouge on as it looked running on an ASCII terminal


The original, Rogue, as it looked running on an ASCII terminal

Here is a link to the tutorial and the link to the Github repo.

The game consists of a player trying to find a pineapple hidden in one of the boxes before Pedro catches the player. The level and locations of the boxes, Pedro and the player start are all randomly generated each time. Play is turn based with the player and then Pedro each moving one space until the player finds the box with the pineapple or Pedro catches the player.

Pineapple Hole
Screenshot of the game, the player is represented by the @, Pedro by the P, and the boxes by *.

Most roguelikes are more complex and have systems for combat and inventory systems. This game primarily an example of using the library. As a game it is subject to the random map layout the chances of the players winning. The further the distance the player starts from Pedro increases the amount of moves the player can make, and alternate routes to the same room allow the player to keep moving away from Pedro while checking the boxes. However since Pedro moves directly towards the player, he can be outsmarted by dancing around to move him away from blocking a route.

Since the tutorial documents all of the script in detail, I will just do a quick run down here.

index.html defines the script source for rot.min.js and game.js and loads Game.init.

<!doctype html>
<html>
	<head>
		<title>Pineapple Hole: Rot.js Tutorial Game</title>
		<script src="https://cdn.rawgit.com/ondras/rot.js/master/rot.min.js"></script>
		<script src="/game.js"></script>
	</head>
	<body onload="Game.init()">
		<h1>Pineapple Hole</h1>
	</body>
</html>

After being loaded by index.html, game.js defines the Game variable which handles setting up the display through display, creating the scheduler, and then generating and drawing the map (all through the rot.js library) and populating it with the player, Pedro and the boxes.

var Game = {
	display: null,
	map: {},
	engine: null,
	player: null,
	pedro: null,
	pineapple: null,

	init: function() {
		this.display = new ROT.Display({spacing:1.1});
		document.body.appendChild(this.display.getContainer());
		
        this._generateMap();

		var scheduler = new ROT.Scheduler.Simple();
		scheduler.add(this.player, true);
		scheduler.add(this.pedro, true);

		this.engine = new ROT.Engine(scheduler);
		this.engine.start();
	},

	_generateMap: function() {
		var digger = new ROT.Map.Digger();
		var freeCells = [];

		var digCallback = function(x, y, value) {
			if (value) { return; } /* do not store walls */
			
            var key = x+","+y;
			this.map[key] = ".";
			freeCells.push(key);
		}
		digger.create(digCallback.bind(this));

		this._generateBoxes(freeCells);
		this._drawWholeMap();

		this.player = this._createBeing(Player, freeCells);
		this.pedro = this._createBeing(Pedro, freeCells)
	},

	_createBeing: function(what, freeCells) {
		var index = Math.floor(ROT.RNG.getUniform() * 	freeCells.length);
		var key = freeCells.splice(index, 1)[0];
		var parts = key.split(",");
		var x = parseInt(parts[0]);
		var y = parseInt(parts[1]);
		return new what(x, y);
	},

	_generateBoxes: function(freeCells) {
		for (var i=0;i<10;i++) {
			var index = Math.floor(ROT.RNG.getUniform() * freeCells.length);
			var key = freeCells.splice(index, 1)[0];
			this.map[key] = "*";
			if (!i) { this.pineapple = key; } /*first box contains a pineapple */
		}
	},

	_drawWholeMap: function() {
		for (var key in this.map) {
			var parts = key.split(",");
			var x = parseInt(parts[0]);
			var y = parseInt(parts[1]);
			this.display.draw(x, y, this.map[key]);
		}
	}
};

The Player variable handles the player location, input and locking the engine between turns and checking if the player has found the box with pineapple.

var Player = function(x, y) {
	this._x = x;
	this._y = y;
	this._draw();
};

Player.prototype.getSpeed = function() { return 100; }
Player.prototype.getX = function() { return this._x; }
Player.prototype.getY = function() { return this._y; }

Player.prototype.act = function() {
	Game.engine.lock();
	/* wait for user input; do stuff when user hits a key */
	window.addEventListener("keydown", this);
};

Player.prototype.handleEvent = function(e) {
	var code = e.keyCode;
	if (code == 13 || code == 32) {
		this._checkBox();
		return;
	};

	var keyMap = {};
	keyMap[38] = 0;
	keyMap[33] = 1;
	keyMap[39] = 2;
	keyMap[34] = 3;
	keyMap[40] = 4;
	keyMap[35] = 5;
	keyMap[37] = 6;
	keyMap[36] = 7;

	/* one of the numpad directions? */
	if (!(code in keyMap)) { return; };

	/* is there a free space? */
	var dir = ROT.DIRS[8][keyMap[code]];
	var newX = this._x + dir[0];
	var newY = this._y + dir[1];
	var newKey = newX + "," + newY;
	if (!(newKey in Game.map)) { return; } /* cannot move in this direction */

	Game.display.draw(this._x, this._y, Game.map[this._x+","+this._y]);
	this._x = newX;
	this._y = newY;
	this._draw();
	window.removeEventListener("keydown", this);
	Game.engine.unlock();
};

Player.prototype._checkBox = function() {
	var key = this._x + "," + this._y;
	if (Game.map[key] != "*") {
		alert("There is no box here!");
	} else if (key == Game.pineapple) {
		alert("Woo! You found a pineapple and won the game.");
		Game.engine.lock();
		window.removeEventListener("keydown", this);
	} else {
		alert("This box is empty :(");
	};
};

Player.prototype._draw = function() {
	Game.display.draw(this._x, this._y, "@", "#ff0");
};

Last Pedro handles tracking the location of Pedro and his AI for chasing the player, and checking if he has.

var Pedro = function(x, y) {
	this._x = x;
	this._y = y;
	this._draw();
};

Pedro.prototype.getSpeed = function() { return 100; }

Pedro.prototype.act = function() {
	var x = Game.player.getX();
	var y = Game.player.getY();
	var passableCallback = function(x, y) {
		return (x+","+y in Game.map);
	}
	var astar = new ROT.Path.AStar(x, y, passableCallback, {topology:4});

	var path = [];
	var pathCallback = function(x, y) {
		path.push([x, y]);
	};
	astar.compute(this._x, this._y, pathCallback);

	path.shift();
	if (path.length == 1) {
		Game.engine.lock();
		alert("Game over - you were captured by Pedro!");
	} else {
		x = path[0][0];
		y = path[0][1];
		Game.display.draw(this._x, this._y, Game.map[this._x+","+this._y]);
		this._x = x;
		this._y = y;
		this._draw();
	}
};

Pedro.prototype._draw = function () {
	Game.display.draw(this._x, this._y, "P", "red");
};

The major difference I noticed was with Objects and Inheritance. Python uses "classical" inheritance and Javascript uses “prototypical” inheritance where any function can serve as a Constructor and Methods can be assigned to the object’s prototype. This was a big change to my way of thinking.

The this keyword was another change, even though at first it seemed simmilar to the self variable in Python.

Overall, the process was simillar and took the same steps to generate the map and get the players key input and then move the non player characters and update the map on the screen.

Here is the link to the game.

The arrow keys move the player and spacebar checks the box for pineapples.