This is the thirteenth 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 first half of the tenth part in Trystan's series. All the code for this part can be found at the part 10a tag of the jsrogue repository. At the time of writing, I am still using the d4ea2ab commit for rot.js.

In this post we are going to add the notion of items to our game! We're also going to make it so that items get generated throughout the floors of our cave. In the next part of this post, we're going to make it so the player can actually pick up items and carry them in an inventory!

Demo Link

The results after this post can be seen here.

assets/item.js

We first have to create the base class for items. To start out simple, an Item will be a Glyph with a name. We won't be adding a position to the item itself as items don't generally have logic based on their position. Instead we will just keep track of where all the items are in the Map class.

1
2
3
4
5
6
7
8
9
Game.Item = function(properties) {
    properties = properties || {};
    // Call the glyph's construtor with our set of properties
    Game.Glyph.call(this, properties);
    // Instantiate any properties from the passed object
    this._name = properties['name'] || '';
};
// Make items inherit all the functionality from glyphs
Game.Item.extend(Game.Glyph);

assets/map.js

We now need to modify maps to be able to hold items. Trystan's tutorial added the limitation that there could only be one item per map location, but I think it will be an interesting challenge to make it so that there can be multiple items at a given tile cell. To implement this, we will be using a hashmap where the key is a location and the value is an array of all the items that are at that tile. We will need to be able to get all items at a given tile as well as to update the set of items at a given tile, taking care to remove the key from the hashmap if there is no longer any items.

Let's first modify our constructor to create this hashmap:

1
2
3
4
5
6
7
8
9
Game.Map = function(tiles, player) {
    // ...
    // Create a table which will hold the entities
    this._entities = {};
    // Create a table which will hold the items
    this._items = {};
    // Create the engine and scheduler
    // ...
};

We now need a function for getting and setting tile items at a given location. Note that for convenience's sake we will also provide a function which adds a single item to a given location as well as to a random location.

 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
32
Game.Map.prototype.getItemsAt = function(x, y, z) {
    return this._items[x + ',' + y + ',' + z];
};

Game.Map.prototype.setItemsAt = function(x, y, z, items) {
    // If our items array is empty, then delete the key from the table.
    var key = x + ',' + y + ',' + z;
    if (items.length === 0) {
        if (this._items[key]) {
            delete this._items[key];
        }
    } else {
        // Simply update the items at that key
        this._items[key] = items;
    }
};

Game.Map.prototype.addItem = function(x, y, z, item) {
    // If we already have items at that position, simply append the item to the 
    // list of items.
    var key = x + ',' + y + ',' + z;
    if (this._items[key]) {
        this._items[key].push(item);
    } else {
        this._items[key] = [item];
    }
};

Game.Map.prototype.addItemAtRandomPosition = function(item, z) {
    var position = this.getRandomFloorPosition(z);
    this.addItem(position.x, position.y, position.z, item);
};

assets/screens.js

Once we've got map items, we'll want to be able to render them! Since we now have a fast way of determining whether there are items or entities at a given position, we can easily refactor our rendering code to simply iterate through all visible tiles and render an item or entity instead of a tile (at locations that are within the field of vision).

Our rendering code now looks like this:

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Game.Screen.playScreen = {
    // ...
    render: function(display) {
        // ...
        // Render the explored map cells
        for (var x = topLeftX; x < topLeftX + screenWidth; x++) {
            for (var y = topLeftY; y < topLeftY + screenHeight; y++) {
                if (map.isExplored(x, y, currentDepth)) {
                    // Fetch the glyph for the tile and render it to the screen
                    // at the offset position.
                    var glyph = this._map.getTile(x, y, currentDepth);
                    var foreground = glyph.getForeground();
                    // If we are at a cell that is in the field of vision, we need
                    // to check if there are items or entities.
                    if (visibleCells[x + ',' + y]) {
                        // Check for items first, since we want to draw entities
                        // over items.
                        var items = map.getItemsAt(x, y, currentDepth);
                        // If we have items, we want to render the top most item
                        if (items) {
                            glyph = items[items.length - 1];
                        }
                        // Check if we have an entity at the position
                        if (map.getEntityAt(x, y, currentDepth)) {
                            glyph = map.getEntityAt(x, y, currentDepth);
                        }
                        // Update the foreground color in case our glyph changed
                        foreground = glyph.getForeground();
                    } else {
                        // Since the tile was previously explored but is not 
                        // visible, we want to change the foreground color to
                        // dark gray.
                        foreground = 'darkGray';
                    }
                    display.draw(
                        x - topLeftX,
                        y - topLeftY,
                        glyph.getChar(), 
                        foreground, 
                        glyph.getBackground());
                }
            }
        }
        // Get the messages in the player's queue and render them
        // ...
    },
    // ...
};

Our rendering code is now much cleaner as we just have one centralized place which renders the contents of a given tile.

assets/repository.js

Similarly to our entities, we're going to want a way to generate random items on our map. In order to keep it maintainable, we're going to use a Repository object inspired by Ondrej Zara's The Royal Wedding. A repository will contain a set of named templates using a JS object internally. Using entities as an example a repository would have a "fungus" key which would have the Fungus entity template, a "newt" key which would have the Newt entity tempate, and so on. The power of the repository comes when we want to create objects based on these templates. We could simply do something like repo.create('fungus') and it would give us a new Entity based on the Fungus template. We can also do things like generate an entity basd on a random template.

 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
32
33
// A repository has a name and a constructor. The constructor is used to create
// items in the repository.
Game.Repository = function(name, ctor) {
    this._name = name;
    this._templates = {};
    this._ctor = ctor;
};

// Define a new named template.
Game.Repository.prototype.define = function(name, template) {
    this._templates[name] = template;
};


// Create an object based on a template.
Game.Repository.prototype.create = function(name) {
    // Make sure there is a template with the given name.
    var template = this._templates[name];

    if (!template) {
        throw new Error("No template named '" + name + "' in repository '" +
            this._name + "'");
    }

    // Create the object, passing the template as an argument
    return new this._ctor(template);
};

// Create an object based on a random template
Game.Repository.prototype.createRandom = function() {
    // Pick a random key and create an object based off of it.
    return this.create(Object.keys(this._templates).random());
};

assets/entities.js

We will now refactor our entities to use this repository pattern. We will create a central entities repository and define our templates there. Note that for now we will keep the PlayerTemplate out of the repository, since we wouldn't a Player to be created randomly on the map.

 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
32
33
34
35
// Player template
Game.PlayerTemplate = {
    // ...
};

// Create our central entity repository
Game.EntityRepository = new Game.Repository('entities', Game.Entity);

Game.EntityRepository.define('fungus', {
    name: 'fungus',
    character: 'F',
    foreground: 'green',
    maxHp: 10,
    mixins: [Game.Mixins.FungusActor, Game.Mixins.Destructible]
});

Game.EntityRepository.define('bat', {
    name: 'bat',
    character: 'B',
    foreground: 'white',
    maxHp: 5,
    attackValue: 4,
    mixins: [Game.Mixins.WanderActor, 
             Game.Mixins.Attacker, Game.Mixins.Destructible]
});

Game.EntityRepository.define('newt', {
    name: 'newt',
    character: ':',
    foreground: 'yellow',
    maxHp: 3,
    attackValue: 2,
    mixins: [Game.Mixins.WanderActor, 
             Game.Mixins.Attacker, Game.Mixins.Destructible]
});

assets/items.js

Similarly to our entity repository, we're going to want to create an items repository with some base templates. For now, we're going to create an apple (%) and a rock (*).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Game.ItemRepository = new Game.Repository('items', Game.Item);

Game.ItemRepository.define('apple', {
    name: 'apple',
    character: '%',
    foreground: 'red'
});

Game.ItemRepository.define('rock', {
    name: 'rock',
    character: '*',
    foreground: 'white'
});

assets/map.js

We're almost done! But we're not actually spawning any items on the map, and we also need to fix up our entity spawning. Let's modify our Map constructor to use the two repositories we just made.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Game.Map = function(tiles, player) {
    // ...
    // Add the player
    this.addEntityAtRandomPosition(player, 0);
    // Add random entities and items to each floor.
    for (var z = 0; z < this._depth; z++) {
        // 15 entities per floor
        for (var i = 0; i < 15; i++) {
            // Add a random entity
            this.addEntityAtRandomPosition(Game.EntityRepository.createRandom(), z);
        }
        // 10 items per floor
        for (var i = 0; i < 15; i++) {
            // Add a random entity
            this.addItemAtRandomPosition(Game.ItemRepository.createRandom(), z);
        }
    }
    // Setup the explored array
    // ...
};

index.html

Finally, we need to add our new files to the index page.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
    <head>
        <title>Javascript Roguelike</title>
    </head>
    <body>
        <script src="assets/rot.min.js"></script>
        <script src="assets/sprintf.min.js"></script>
        <script src="assets/game.js"></script>
        <script src="assets/screens.js"></script>
        <script src="assets/glyph.js"></script>
        <script src="assets/tile.js"></script>
        <script src="assets/builder.js"></script>
        <script src="assets/map.js"></script>
        <script src="assets/entity.js"></script>
        <script src="assets/item.js"></script>
        <script src="assets/repository.js"></script>
        <script src="assets/entities.js"></script>
        <script src="assets/items.js"></script>
    </body>
</html>

Conclusion

Our cave now has some random items laying around on the floor! We've done a lot of cleaning up in this post as well and it should now be much more maintainble to generate a random entity or item. The next part is going to be big as it will introduce an inventory to the game and we will be able to actually pick up the items we find!

I hope you enjoyed this post! Remember that all the code for this part can be found at the part 10a tag of the jsrogue repository. As always please feel free to post any comments whether questions, clarifications or criticism!

Thanks for reading,

Dominic

Next Part

Part 10b - Inventory and Item Management