This is the fifteenth 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 the eleventh part in Trystan's series. All the code for this part can be found at the part 11 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 a hunger system to our game. Over time our hero will grow hungrier and eventually starve if no food is consumed! In order to do this we will need to create consumable items! Monsters will sometimes drop corpses when killed and there will be consumables scattered throughout the cave! Important note about refactoring since last post: I have renamed the Game.Mixins namespace to Game.EntityMixins and have moved all the entity mixins to assets/entitymixins.js, leaving only entity templates and the repository definition in assets/entities.js. I reccomend you pull the code at the pre-part 11 tag before starting this post.

Demo Link

The results after this post can be seen here.

assets/dynamicglyph.js

In order to make items consumable, we're going to use a feature similar to the entity mixins. Rather than copying all that logic over, I figured now would be a good time to make a class called DynamicGlyph which extends Gylph, has a name property and has all the mixin logic. Both the Entity class and Item class will extend this instead of Glyph. This will allow us to create the describe* functions for both as they both have name properties and will also provide the mixin system.

 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
Game.DynamicGlyph = 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'] || '';
    // 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);
        }
    }
};
// Make dynamic glyphs inherit all the functionality from glyphs
Game.DynamicGlyph.extend(Game.Glyph);

Game.DynamicGlyph.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];
    }
};

Game.DynamicGlyph.prototype.setName = function(name) {
    this._name = name;
};

Game.DynamicGlyph.prototype.getName = function() {
    return this._name;
};

Game.DynamicGlyph.prototype.describe = function() {
    return this._name;
};
Game.DynamicGlyph.prototype.describeA = function(capitalize) {
    // Optional parameter to capitalize the a/an.
    var prefixes = capitalize ? ['A', 'An'] : ['a', 'an'];
    var string = this.describe();
    var firstLetter = string.charAt(0).toLowerCase();
    // If word starts by a vowel, use an, else use a. Note that this is not perfect.
    var prefix = 'aeiou'.indexOf(firstLetter) >= 0 ? 1 : 0;

    return prefixes[prefix] + ' ' + string;
};
Game.DynamicGlyph.prototype.describeThe = function(capitalize) {
    var prefix = capitalize ? 'The' : 'the';
    return prefix + ' ' + this.describe();
};

assets/entity.js

We can now change the entity class to inherit from DynamicGlyph, allowing for a much cleaner class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Game.Entity = function(properties) {
    properties = properties || {};
    // Call the dynamic glyph's construtor with our set of properties
    Game.DynamicGlyph.call(this, properties);
    // Instantiate any properties from the passed object
    this._x = properties['x'] || 0;
    this._y = properties['y'] || 0;
    this._z = properties['z'] || 0;
    this._map = null;
};
// Make entities inherit all the functionality from dynamic glyphs
Game.Entity.extend(Game.DynamicGlyph);
// ...

Make sure to delete the following functions:

  • Game.Entity.prototype.hasMixin
  • Game.Entity.prototype.setName
  • Game.Entity.prototype.getName

assets/item.js

Now let's change the Item class to use DynamicGlyph. This should also be much cleaner.

1
2
3
4
5
6
7
Game.Item = function(properties) {
    properties = properties || {};
    // Call the dynamic glyph's construtor with our set of properties
    Game.DynamicGlyph.call(this, properties);
};
// Make items inherit all the functionality from dynamic glyphs
Game.Item.extend(Game.DynamicGlyph);

assets/index.html

Now we have to add assets/dynamicglyph.js to our set of scripts!

1
2
3
4
5
    <!-- ... -->
    <script src="assets/glyph.js"></script>
    <script src="assets/dynamicglyph.js"></script>
    <script src="assets/tile.js"></script>
    <!-- ... -->

assets/entity.js

Before we can implement dying from starvation (or overeating!), we'll generalize the way we tell the game that an entity has died. Let's add a boolean to the entity to keep track that an entity is alive or dead, as well as a function effectively killing the player with an optional reason.

 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
Game.Entity = function(properties) {
    // ...
    this._alive = true;
};
// ...
Game.Entity.prototype.isAlive = function() {
    return this._alive;
};
Game.Entity.prototype.kill = function(message) {
    // Only kill once!
    if (!this._alive) {
        return;
    }
    this._alive = false;
    if (message) {
        Game.sendMessage(this, message);
    } else {
        Game.sendMessage(this, "You have died!");
    }

    // Check if the player died, and if so call their act method to prompt the user.
    if (this.hasMixin(Game.EntityMixins.PlayerActor)) {
        this.act();
    } else {
        this.getMap().removeEntity(this);
    }
};

assets/entitymixins.js

Now that we've added a kill method, we need to modify our PlayerActor to change how the game checks if the player is dead. We also need to change the Destructible mixin's takeDamage function to kill the entity.

 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
Game.EntityMixins.PlayerActor = {
    // ...
    act: function() {
        // Detect if the game is over
        if (!this.isAlive()) {
            Game.Screen.playScreen.setGameEnded(true);
            // Send a last message to the player
            Game.sendMessage(this, 'Press [Enter] to continue!');
        }
        // Re-render the screen
        // ...
    }
};

// ...

Game.EntityMixins.Destructible = {
    // ...
    takeDamage: function(attacker, damage) {
        this._hp -= damage;
        // If have 0 or less HP, then remove ourseles from the map
        if (this._hp <= 0) {
            Game.sendMessage(attacker, 'You kill the %s!', [this.getName()]);
            this.kill();
        }
    }
};

In order to introduce hunger to the game, let's create a mixin which will keep track of fullness points for an entity. Fullness points will be a stat which will go down over time, potentially causing the entity to die of starvation when depleted. We also want to associate a word with different ranges of fullness points to show on the player's screen, rather than showing raw fullness points directly. Rather than using traditional getters and setters, we'll expose a modifyFullnessBy function which allows us to replenish (or decrease!) fullness points. We also expose a addTurnHunger function which should be called in the relevant Actor mixin's act functions.

 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
Game.EntityMixins.FoodConsumer = {
    name: 'FoodConsumer',
    init: function(template) {
        this._maxFullness = template['maxFullness'] || 1000;
        // Start halfway to max fullness if no default value
        this._fullness = template['fullness'] || (this._maxFullness / 2);
        // Number of points to decrease fullness by every turn.
        this._fullnessDepletionRate = template['fullnessDepletionRate'] || 1;
    },
    addTurnHunger: function() {
        // Remove the standard depletion points
        this.modifyFullnessBy(-this._fullnessDepletionRate);
    },
    modifyFullnessBy: function(points) {
        this._fullness = this._fullness + points;
        if (this._fullness <= 0) {
            this.kill("You have died of starvation!");
        } else if (this._fullness > this._maxFullness) {
            this.kill("You choke and die!");
        }
    },
    getHungerState: function() {
        // Fullness points per percent of max fullness
        var perPercent = this._maxFullness / 100;
        // 5% of max fullness or less = starving
        if (this._fullness <= perPercent * 5) {
            return 'Starving';
        // 25% of max fullness or less = hungry
        } else if (this._fullness <= perPercent * 25) {
            return 'Hungry';
        // 95% of max fullness or more = oversatiated
        } else if (this._fullness >= perPercent * 95) {
            return 'Oversatiated';
        // 75% of max fullness or more = full
        } else if (this._fullness >= perPercent * 75) {
            return 'Full';
        // Anything else = not hungry
        } else {
            return 'Not Hungry';
        }
    }
};

As mentioned above, we have to change our PlayerActor to invoke the addTurnHunger function before checking if the player is dead! At the same time we will add a simple check to make sure our player is not already acting at the top of the act method. This function can be double called by kill if the player is killed during the player's turn, and will save us headaches related to clearing messages in the future.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Game.EntityMixins.PlayerActor = {
    name: 'PlayerActor',
    groupName: 'Actor',
    act: function() {
        if (this._acting) {
            return;
        }
        this._acting = true;
        this.addTurnHunger();
        // Detect if the game is over
        // ...
        this._acting = false;
    }
};

assets/entities.js

Let's add the FoodConsumer mixin to our player! The default settings should be fine for now.

1
2
3
4
5
6
7
8
// Player template
Game.PlayerTemplate = {
    // ...
    mixins: [Game.EntityMixins.PlayerActor,
             Game.EntityMixins.Attacker, Game.EntityMixins.Destructible,
             Game.EntityMixins.InventoryHolder, Game.EntityMixins.FoodConsumer,
             Game.EntityMixins.Sight, Game.EntityMixins.MessageRecipient]
};

assets/screens.js

Before going any further, let's render to hunger state to our player's screen. We will render it in the right corner of the status bar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Game.Screen.playScreen = {
    // ...
    render: function(display) {
        // ...
        // Render hunger state
        var hungerState = this._player.getHungerState();
        display.drawText(screenWidth - hungerState.length, screenHeight, hungerState);
    },
    // ...
};

assets/itemmixins.js

As I mentioned before, we will adopt the mixin system for items. This file will hold all such mixins in the Game.ItemMixins namespace. This will allow us to mix and match item features and give us much more power as we expand what our items can do. Imagine an item that is edible, equippable, and that could be thrown all at once such as a pumpkin! We will create an edible item mixin that can be consumed a specifiable number of times, each time providing a specific nutritional value. This will also be a neat example of modifying the describe function to add an adjective such as partly eaten.

 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.ItemMixins = {};

// Edible mixins
Game.ItemMixins.Edible = {
    name: 'Edible',
    init: function(template) {
        // Number of points to add to hunger
        this._foodValue = template['foodValue'] || 5;
        // Number of times the item can be consumed
        this._maxConsumptions = template['consumptions'] || 1;
        this._remainingConsumptions = this._maxConsumptions;
    },
    eat: function(entity) {
        if (entity.hasMixin('FoodConsumer')) {
            if (this.hasRemainingConsumptions()) {
                entity.modifyFullnessBy(this._foodValue);
                this._remainingConsumptions--;
            }
        }
    },
    hasRemainingConsumptions: function() {
        return this._remainingConsumptions > 0;
    },
    describe: function() {
        if (this._maxConsumptions != this._remainingConsumptions) {
            return 'partly eaten ' + Game.Item.prototype.describe.call(this);
        } else {
            return this._name;
        }
    }
};

assets/items.js

Let's modify our apple to be edible and let's add a melon (%) as well!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ...
Game.ItemRepository.define('apple', {
    name: 'apple',
    character: '%',
    foreground: 'red',
    foodValue: 50,
    mixins: [Game.ItemMixins.Edible]
});

Game.ItemRepository.define('melon', {
    name: 'melon',
    character: '%',
    foreground: 'brightGreen',
    foodValue: 35,
    consumptions: 4,
    mixins: [Game.ItemMixins.Edible]
});
// ...

assets/screens.js

We now want to create a screen which will allow the user to eat these edible items! Ideally we'd like to only show items that are edible in this screen, so we're going to need to play with our ItemListScreen from last post. Let's add an optional function to the template passed to this screen which will allow us to test each of the items received by the setup function to determine if they should be kept. This could be used to filter out all non-edible items! Ideally we'd like to be able to use this logic to decide if we should even show the screen or warn the user that they have no edible items, so let's make setup return the number of remaining items! That way we can check if 0 items are left after calling setup, and if so, warn the user that they have no edible items instead. In fact we'll be able to use this logic for trying to access the drop screen or inventory screen when there are no items!

 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
// ...
Game.Screen.ItemListScreen = function(template) {
    // Set up based on the template
    this._caption = template['caption'];
    this._okFunction = template['ok'];
    // By default, we use the identity function
    this._isAcceptableFunction = template['isAcceptable'] || function(x) {
        return x;
    }
    // Whether the user can select items at all.
    this._canSelectItem = template['canSelect'];
    // Whether the user can select multiple items.
    this._canSelectMultipleItems = template['canSelectMultipleItems'];
};

Game.Screen.ItemListScreen.prototype.setup = function(player, items) {
    this._player = player;
    // Should be called before switching to the screen.
    var count = 0;
    // Iterate over each item, keeping only the aceptable ones and counting
    // the number of acceptable items.
    var that = this;
    this._items = items.map(function(item) {
        // Transform the item into null if it's not acceptable
        if (that._isAcceptableFunction(item)) {
            count++;
            return item;
        } else {
            return null;
        }
    });
    // Clean set of selected indices
    this._selectedIndices = {};
    return count;
};
// ...

We can now create our eating screen which will only show edible items!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Game.Screen.eatScreen = new Game.Screen.ItemListScreen({
    caption: 'Choose the item you wish to eat',
    canSelect: true,
    canSelectMultipleItems: false,
    isAcceptable: function(item) {
        return item && item.hasMixin('Edible');
    },
    ok: function(selectedItems) {
        // Eat the item, removing it if there are no consumptions remaining.
        var key = Object.keys(selectedItems)[0];
        var item = selectedItems[key];
        Game.sendMessage(this._player, "You eat %s.", [item.describeThe()]);
        item.eat(this._player);
        if (!item.hasRemainingConsumptions()) {
            this._player.removeItem(key);
        }
        return true;
    }
});

Finally let's update our play screen's input handler to use this new setup logic as well as to make the key e open up the eating screen.

 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
Game.Screen.playScreen = {
    // ...
    handleInput: function(inputType, inputData) {
        // ...
                } else if (inputData.keyCode === ROT.VK_I) {
                    // Show the inventory
                    if (Game.Screen.inventoryScreen.setup(this._player, this._player.getItems())) {
                        this.setSubScreen(Game.Screen.inventoryScreen);
                    } else {
                        Game.sendMessage(this._player, "You are not carrying anything!");
                        Game.refresh();
                    }
                    return;
                } else if (inputData.keyCode === ROT.VK_D) {
                    // Show the drop screen
                    if (Game.Screen.dropScreen.setup(this._player, this._player.getItems())) {
                        this.setSubScreen(Game.Screen.dropScreen);
                    } else {
                        Game.sendMessage(this._player, "You have nothing to drop!");
                        Game.refresh();
                    }
                    return;
                } else if (inputData.keyCode === ROT.VK_E) {
                    // Show the drop screen
                    if (Game.Screen.eatScreen.setup(this._player, this._player.getItems())) {
                        this.setSubScreen(Game.Screen.eatScreen);
                    } else {
                        Game.sendMessage(this._player, "You have nothing to eat!");
                        Game.refresh();
                    }
                    return;
                } else if (inputData.keyCode === ROT.VK_COMMA) {
                // ...
    }
};

assets/repository.js

We're now ready to make our enemies drop corpses! In order to do this, the easiest way of managing items will be to have 1 base corpse template that we will simply modify at creation time. Ideally we'd be able to call Game.ItemRepository.create('corpse', {name: ...}). So let's modify our repository code to allow passing extra properties in the create call. These will be to the template, overriding any properties that were defined in the template.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Game.Repository.prototype.create = function(name, extraProperties) {
    if (!this._templates[name]) {
        throw new Error("No template named '" + name + "' in repository '" +
            this._name + "'");
    }
    // Copy the template
    var template = Object.create(this._templates[name]);
    // Apply any extra properties
    if (extraProperties) {
        for (var key in extraProperties) {
            template[key] = extraProperties[key];
        }
    }
    // Create the object, passing the template as an argument
    return new this._ctor(template);
};

Once we'll have defined this base template, we will run into an issue that the base template gets created sometimes by the createRandom call. In order to fix this, we're going to modify our define call to allow passing extra repository options, including one that states that this item should not be randomly created!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// A repository has a name and a constructor. The constructor is used to create
// items in the repository.
Game.Repository = function(name, ctor) {
    // ...
    this._randomTemplates = {};
};

// Define a new named template.
Game.Repository.prototype.define = function(name, template, options) {
    this._templates[name] = template;
    // Apply any options
    var disableRandomCreation = options && options['disableRandomCreation'];
    if (!disableRandomCreation) {
        this._randomTemplates[name] = 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._randomTemplates).random());
};

assets/items.js

Now let's create our base corpse template! A corpse will be edible, use the % character and take on the foreground color of the entity who it belongs to.

1
2
3
4
5
6
7
8
9
Game.ItemRepository.define('corpse', {
    name: 'corpse',
    character: '%',
    foodValue: 75,
    consumptions: 1,
    mixins: [Game.ItemMixins.Edible]
}, {
    disableRandomCreation: true
});

assets/entitymixins.js

Finally let's create a mixin which entities will use to signal that they potentially drop a corpse when they die.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Game.EntityMixins.CorpseDropper = {
    name: 'CorpseDropper',
    init: function(template) {
        // Chance of dropping a cropse (out of 100).
        this._corpseDropRate = template['corpseDropRate'] || 100;
    },
    tryDropCorpse: function() {
        if (Math.round(Math.random() * 100) < this._corpseDropRate) {
            // Create a new corpse item and drop it.
            this._map.addItem(this.getX(), this.getY(), this.getZ(),
                Game.ItemRepository.create('corpse', {
                    name: this._name + ' corpse',
                    foreground: this._foreground
                }));
        }
    }
};

For now let's make it so that an entity may only drop a corpse when killed through the takeDamage function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Game.EntityMixins.Destructible = {
    // ...
    takeDamage: function(attacker, damage) {
        this._hp -= damage;
        // If have 0 or less HP, then remove ourseles from the map
        if (this._hp <= 0) {
            Game.sendMessage(attacker, 'You kill the %s!', [this.getName()]);
            // If the entity is a corpse dropper, try to add a corpse
            if (this.hasMixin(Game.EntityMixins.CorpseDropper)) {
                this.tryDropCorpse();
            }
            this.kill();
        }
    }
};

assets/entities.js

And we're done! Let's just add our mixin to our entities and we should now have entities that drop corpses! They will all drop their corpse 100% of the time for now.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ...
Game.EntityRepository.define('bat', {
    // ...
    mixins: [Game.EntityMixins.WanderActor, Game.EntityMixins.CorpseDropper,
             Game.EntityMixins.Attacker, Game.EntityMixins.Destructible,
             Game.EntityMixins.CorpseDropper]
});

Game.EntityRepository.define('newt', {
    // ...
    mixins: [Game.EntityMixins.WanderActor, 
             Game.EntityMixins.Attacker, Game.EntityMixins.Destructible,
             Game.EntityMixins.CorpseDropper]
});
// ...

assets/index.html

Finally, let's add assets/itemmixins.js to the set of scripts!

1
2
3
4
5
    <!-- ... -->
    <script src="assets/item.js"></script>
    <script src="assets/itemmixins.js"></script>
    <script src="assets/repository.js"></script>
    <!-- ... -->

Conclusion

This was a lengthy post as we did lots of refactoring but we've now got a neat hunger system implemented in the game and we've also began to re-use the mixin logic for items! In the next post we'll be adding wieldable weapons and equippable armor which should give our game a much more traditional roguelike feel.

I hope you enjoyed this post! Remember that all the code for this part can be found at the part 11 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 12 - Weapons and Wearables