This is the sixth post in the Building a Roguelike in Javascript series. I recommend you start at the beginning unless you've been following along. This part corresponds to the fifth part in Trystan's series. All the code for this part can be found at the part 5a tag of the jsrogue repository. At the time of writing, I am still using the d4ea2ab commit for rot.js however there seemed to be an error in the rot.min.js file included in the github repository (it was missing ROT.Scheduler.Simple) so I fetched it again from the same commit. You may need to do the same. It can be obtained here.

Today's going to be an exciting post! We are going to add more entities to our cave so that the player is not alone! This is going to be a pretty significant step towards populating our cave with a variety of terrifying monsters. To keep it simple our first monster will be a fungus. This fungus won't move for the moment and we can't currently walk through it, but in the next post it will spread over time and we'll be able to clear it by walking through it! I'm warning you ahead of time - this post is quite long, but builds some pretty fundamental stuff.

Demo Link

The results after this post can be seen here

assets/entity.js

Before we do anything, we're going to add an extra field to the Entity class. We want an entity to be associated with a map. Let's first update the constructor:

1
2
3
4
5
6
7
8
9
Game.Entity = function(properties) {
    // ...
    // Instantiate any properties from the passed object
    this._name = properties['name'] || '';
    this._x = properties['x'] || 0;
    this._y = properties['y'] || 0;
    this._map = null;
    // ...
}

Now we'll also want our standard getter and setter:

1
2
3
4
5
6
Game.Entity.prototype.setMap = function(map) {
    this._map = map;
}
Game.Entity.prototype.getMap = function() {
    return this._map;
}

We are also going to add a way to group together mixins which offer a common interface. Consider the Moveable mixin we added last post. Suppose you wanted to have a ghost entity which could move through any type of cell and a molerat entity which could dig and move at the same time. Rather than creating an extremely complex and general Moveable mixin, we'd like to create a bunch of simple ones and just note that they offer the same common functionality. Once this is done, we'd like to be able to check if a mixin implements a given interface without being specific (eg. is it moveable rather than is it a ghost's moveable mixin). To do this we will add an optional groupName to mixins which acts similar to the name property but mixins implementing the same interface should have the same groupName.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var PlayerMoveableMixin = {
    name: 'PlayerMoveable',
    groupName: 'Moveable',
    tryMove: function(x, y){...}
}
var MoleratMoveableMixin = {
    name: 'MoleratMoveable',
    groupName: 'Moveable',
    tryMove: function(x, y){...}
}
var GhostMoveableMixin = {
    name: 'GhostMoveable',
    groupName: 'Moveable',
    tryMove: function(x, y){...}
}

Ideally we'd like to be able to test both specifically for a given mixin by passing either the name or the mixin itself, as well as testing generally by passing the group name:

1
2
3
4
5
6
// Testing specifically
entity.hasMixin('GhostMoveable')
entity.hasMixin(GhostMoveableMixin)

// Testing generally 
entity.hasMixin('Moveable')

To implement this we must first update the constructor to keep track of the group names as well as the mixin names:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Game.Entity = function(properties) {
    // ...
    // Create an object which will keep track what mixins we have
    // attached to this entity based on the name property
    this._attachedMixins = {};
    // Create a similar object for groups
    this._attachedMixinGroups = {};
    // Setup the object's mixins
    var mixins = properties['mixins'] || [];
    for (var i = 0; i < mixins.length; i++) {
        // Copy over all properties from each mixin as long
        // as it's not the name or the init property. We
        // also make sure not to override a property that
        // already exists on the entity.
        for (var key in mixins[i]) {
            if (key != 'init' && key != 'name' && !this.hasOwnProperty(key)) {
                this[key] = mixins[i][key];
            }
        }
        // Add the name of this mixin to our attached mixins
        this._attachedMixins[mixins[i].name] = true;
        // If a group name is present, add it
        if (mixins[i].groupName) {
            this._attachedMixinGroups[mixins[i].groupName] = true;
        }
        // Finally call the init function if there is one
        if (mixins[i].init) {
            mixins[i].init.call(this, properties);
        }
    }
};

Finally we need to update the hasMixin function to check the group names as well if we are checking with a string:

1
2
3
4
5
6
7
8
Game.Entity.prototype.hasMixin = function(obj) {
    // Allow passing the mixin itself or the name / group name as a string
    if (typeof obj === 'object') {
        return this._attachedMixins[obj.name];
    } else {
        return this._attachedMixins[obj] || this._attachedMixinGroups[obj];
    }
}

assets/map.js

We now need a way to keep track of our entities on the map. All our entities presently on the map are going to be stored in a list. We are also going to be using the ROT.Engine and ROT.Scheduler to manage our entities and make them take turns in the appropriate order. This system will be very important later on when entities start having different speeds so we will put it in the base right away. Let's update the constructor to reflect this:

1
2
3
4
5
6
7
8
Game.Map = function(tiles) {
    // ...
    // create a list which will hold the entities
    this._entities = [];
    // create the engine and scheduler
    this._scheduler = new ROT.Scheduler.Simple();
    this._engine = new ROT.Engine(this._scheduler);
};

We now want to provide a way to get the engine as well as all the entities on the map. Finally we'll need to be able to check if there is an entity at a given position.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Game.Map.prototype.getEngine = function() {
    return this._engine;
}
Game.Map.prototype.getEntities = function() {
    return this._entities;
}
Game.Map.prototype.getEntityAt = function(x, y){
    // Iterate through all entities searching for one with
    // matching position
    for (var i = 0; i < this._entities.length; i++) {
        if (this._entities[i].getX() == x && this._entities[i].getY() == y) {
            return this._entities[i];
        }
    }
    return false;
}

Before we can actually add entities to our maps, we need to look at how the scheduler actually works!

Scheduling and the Game Loop

The scheduling engine has a queue of all the entities and gives them a turn one by one. We're currently using the Simple scheduler which simply gives the entities turns in the order they were added to the queue, and allows us to mark some entities as being re-added into the queue when their turn is complete. In the future we will use a more complicated scheduler which will allow us for some entities to act faster than others.

The ROT.Engine object takes care of interacting with the scheduler and is started using start. It extracts the next entity from the scheduler and calls the act function on that entity, so we will need to define this function for all our entities that we want to be dynamic. We are going to do this with our mixin system we developed in the last post! Because of the nature of roguelikes, when it is the player's turn we want to wait until the player presses a key before we process the next turn. However the engine is continually running and so we won't be able to interact with our game if we simply start the engine! So when it is the player's turn we must lock the engine in order to wait for a key press, unlocking it when we are done.

Just to clarify, let's consider the game loop. Up until now, our game loop has consisted of rendering the screen once and then waiting for input, processing said input and then re-rendering the screen. Our new game loop will be structured like this:

  1. Process all turns until it is the player's turn
  2. Render the screen and lock the engine
  3. Wait for a player to press a key, process it and then unlock the engine
  4. Go back to step 1

assets/game.js

In order to get set up for our new game loop, there are some things we need to change. First of all, we will make it so that the screen no longer automatically renders after we call the screen's handleInput. This means screens will have to make a call to rerender the screen whenever they want the screen to be updated. Remember that switching screens will automatically render the screen, so we don't have to change anything in the menu screen.

To do this, we have to edit the code for the bindEventToScreen function in Game.init:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var Game = {
    // ...
    init: function() {
        // ...
        var bindEventToScreen = function(event) {
            window.addEventListener(event, function(e) {
                // When an event is received, send it to the
                // screen if there is one
                if (game._currentScreen !== null) {
                    // Send the event type and data to the screen
                    game._currentScreen.handleInput(event, e);
                }
            });
        }
        // ...
    },
    // ...
}

We are now going to add a helper function called refresh which will clear the screen and render the current screen to our Game namespace.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var Game = {
    // ...
    refresh: function() {
        // Clear the screen
        this._display.clear();
        // Render the screen
        this._currentScreen.render(this._display);
    },
    // ...
}

Finally we're going to update the code of our switchScreen function to use this refresh helper function to make sure we always go through the same route for re-rendering the screen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var Game = {
    // ...
    switchScreen: function(screen) {
        // If we had a screen before, notify it that we exited
        if (this._currentScreen !== null) {
            this._currentScreen.exit();
        }
        // Clear the display
        this.getDisplay().clear();
        // Update our current screen, notify it we entered
        // and then render it
        this._currentScreen = screen;
        if (!this._currentScreen !== null) {
            this._currentScreen.enter();
            this.refresh();
        }
    }
}

This now gives us more control over when we re-render the screen, and will allow us to same some rendering calls in the future! If we made it so that the screen would render after each time we handled input, than we would end up re-rendering screen twice - once after handling the input and another time after processing all the turns.

assets/entities.js

We will be creating Actor mixins which will have a sole method act. We will use the group name Actor for all our actor mixins to allow us to easily check if a given entity needs to be scheduled. Let's create our player's actor mixin first. As I mentioned before we need to refresh the screen as well as lock the engine when it is the player's turn.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Main player's actor mixin
Game.Mixins.PlayerActor = {
    name: 'PlayerActor',
    groupName: 'Actor',
    act: function() {
        // Re-render the screen
        Game.refresh();
        // Lock the engine and wait asynchronously
        // for the player to press a key.
        this.getMap().getEngine().lock();        
    }
}

Now we have to add this mixin to our player's template!

1
2
3
4
Game.PlayerTemplate = {
    // ...
    mixins: [Game.Mixins.Moveable, Game.Mixins.PlayerActor]
}

We will also be creating an Actor mixin for the fungus. Notice that both actor mixins have the same group name!

1
2
3
4
5
Game.Mixins.FungusActor = {
    name: 'FungusActor',
    groupName: 'Actor',
    act: function() { }
}

We're going to create a template for our fungus entity! They will show us a F on the screen, but feel free to play around with the template and make it look however you like! It's your game after all!

1
2
3
4
5
Game.FungusTemplate = {
    character: 'F',
    foreground: 'green',
    mixins: [Game.Mixins.FungusActor]
}

Finally we're going to change our Moveable mixin to make it so that we can't walk through a tile if an entity is present at that tile.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Game.Mixins.Moveable = {
    name: 'Moveable',
    tryMove: function(x, y, map) {
        var tile = map.getTile(x, y);
        var target = map.getEntityAt(x, y);
        // If an entity was present at the tile, then we
        // can't move there
        if (target) {
            return false;
        // Check if we can walk on the tile
        // and if so simply walk onto it
        } else if (tile.isWalkable()) {        
            // Update the entity's position
            this._x = x;
            this._y = y;
            return true;
        // Check if the tile is diggable, and
        // if so try to dig it
        } else if (tile.isDiggable()) {
            map.dig(x, y);
            return true;
        }
        return false;
    }
}

assets/map.js

We haven't actually provided a way to add an entity to our map yet! We're going to create a base function which allows us to add an entity to our map. In our function, we check if the entity is an Actor (thanks to our improvement), and if so we add them to the scheduler as well! We'll also want a method which adds an entity at a random position.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Game.Map.prototype.addEntity = function(entity) {
    // Make sure the entity's position is within bounds
    if (entity.getX() < 0 || entity.getX() >= this._width ||
        entity.getY() < 0 || entity.getY() >= this._height) {
        throw new Error('Adding entity out of bounds.');
    }
    // Update the entity's map
    entity.setMap(this);
    // Add the entity to the list of entities
    this._entities.push(entity);
    // Check if this entity is an actor, and if so add
    // them to the scheduler
    if (entity.hasMixin('Actor')) {
       this._scheduler.add(entity, true);
    }
}

Game.Map.prototype.addEntityAtRandomPosition = function(entity) {
    var position = this.getRandomFloorPosition();
    entity.setX(position.x);
    entity.setY(position.y);
    this.addEntity(entity);
}

We now have a fix to make to our function which generates a random floor position! We need to also check that there is no entity already present at the location we generated!:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Game.Map.prototype.getRandomFloorPosition = function() {
    // Randomly generate a tile which is a floor
    var x, y;
    do {
        x = Math.floor(Math.random() * this._width);
        y = Math.floor(Math.random() * this._width);
    } while(this.getTile(x, y) != Game.Tile.floorTile ||
            this.getEntityAt(x, y));
    return {x: x, y: y};
}

The last part, and arguably the most important part, is that we must add our player entity as well as some fungus to the map! We will do this in the constructor before starting the engine, and in fact will modify the constructor to accept the player entity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Game.Map = function(tiles, player) {
    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;
    // create a list which will hold the entities
    this._entities = [];
    // create the engine and scheduler
    this._scheduler = new ROT.Scheduler.Simple();
    this._engine = new ROT.Engine(this._scheduler);
    // add the player
    this.addEntityAtRandomPosition(player);
    // add random fungi
    for (var i = 0; i < 1000; i++) {
        this.addEntityAtRandomPosition(new Game.Entity(Game.FungusTemplate));
    }
};

assets/screens.js

We now need to update the enter function of the playScreen to pass our player to the map, as well as to start the engine.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Game.Screen.playScreen = {
    // ...
    enter: function() {  
        // ...
        // Create our map from the tiles and player
        this._player = new Game.Entity(Game.PlayerTemplate);
        this._map = new Game.Map(map, this._player);
        // Start the map's engine
        this._map.getEngine().start();
    }
    // ...
}

We now need to change our rendering function to render all the map's entities as opposed to just the player. You're going to want to replace the player rendering with this snippet, which renders all visible entities:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Game.Screen.playScreen = {
    // ...
    render: function() {  
        // ...
        // Render the entities
        var entities = this._map.getEntities();
        for (var i = 0; i < entities.length; i++) {
            var entity = entities[i];
            // Only render the entitiy if they would show up on the screen
            if (entity.getX() >= topLeftX && entity.getY() >= topLeftY &&
                entity.getX() < topLeftX + screenWidth &&
                entity.getY() < topLeftY + screenHeight) {
                display.draw(
                    entity.getX() - topLeftX, 
                    entity.getY() - topLeftY,    
                    entity.getChar(), 
                    entity.getForeground(), 
                    entity.getBackground()
                );
            }
        }
    }
    // ...
}

And the last step is to make our handleInput function unlock the map engine!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Game.Screen.playScreen = {
    // ...
    handleInput: function(inputType, inputData) {
        if (inputType === 'keydown') {
            // If enter is pressed, go to the win screen
            // If escape is pressed, go to lose screen
            if (inputData.keyCode === ROT.VK_RETURN) {
                Game.switchScreen(Game.Screen.winScreen);
            } else if (inputData.keyCode === ROT.VK_ESCAPE) {
                Game.switchScreen(Game.Screen.loseScreen);
            } else {
                // Movement
                if (inputData.keyCode === ROT.VK_LEFT) {
                    this.move(-1, 0);
                } else if (inputData.keyCode === ROT.VK_RIGHT) {
                    this.move(1, 0);
                } else if (inputData.keyCode === ROT.VK_UP) {
                    this.move(0, -1);
                } else if (inputData.keyCode === ROT.VK_DOWN) {
                    this.move(0, 1);
                }
                // Unlock the engine
                this._map.getEngine().unlock();
            }
        }    
    },
    // ...
}

Conclusion

Our game is now actually starting to ressemble a real game! We can walk around a cave filled with fungi! We are also set up for having many entities on a given map, and our small adjustement to the mixin system has made it much easier to create simple but powerful mixins.

I hope you enjoyed this post and that you'll stick around for the next part! I'm sorry for the length of the last two posts, I will try to keep it down in the future. I'd just hate to finish off a post with something you couldn't use! Remember that all the code for this part can be found at the part 5a tag of the jsrogue repository. Also please feel free to post any comments whether questions, clarifications or criticism!

Thanks for reading,

Dominic

Next Part

Part 5b - Attacking Spreading Fungi