This is the eleventh 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 second part of the eight part in Trystan's series. All the code for this part can be found at the part 8b tag of the jsrogue repository. At the time of writing, I am still using the d4ea2ab commit for rot.js. Note that I have done some aesthethic cleanup to the code between part 8a and this part. This has not changed functionality and merely fixed some style issues such as not ending all declarations with semicolons.

In the last part we added a field of vision to limit what the hero would see at any given point in time. In this post we will be making it so that our hero remembers the parts of the cave that have been previously visited. If a tile has been visited before but is not currently in the field of vision, it will appear however any entity that may be on the tile will not! These tile will also appear in gray in order to show the current field of vision.

Demo Link

The results after this post can be seen here.

assets/map.js

In order to keep track of what has been explored, we will have a 3D array of booleans representing the world. If a given coordinate is set to true, then it has appeared in the player's field of vision before and is therefore considered to be 'explored'. We will keep this data in the map.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Game.Map = function(tiles, player) {
    // ...
    // Setup the explored array
    this._explored = new Array(this._depth);
    this._setupExploredArray();
};

Game.Map.prototype._setupExploredArray = function() {
    for (var z = 0; z < this._depth; z++) {
        this._explored[z] = new Array(this._width);
        for (var x = 0; x < this._width; x++) {
            this._explored[z][x] = new Array(this._height);
            for (var y = 0; y < this._height; y++) {
                this._explored[z][x][y] = false;
            }
        }
    }
};

So we've got our array to keep track of what's been explored, but there's no way to update it! Let's add a function which will allow us to update the explored state for a given tile as well as a getter for the explored state.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Game.Map.prototype.setExplored = function(x, y, z, state) {
    // Only update if the tile is within bounds
    if (this.getTile(x, y, z) !== Game.Tile.nullTile) {
        this._explored[z][x][y] = state;
    }
};

Game.Map.prototype.isExplored = function(x, y, z) {
    // Only return the value if within bounds
    if (this.getTile(x, y, z) !== Game.Tile.nullTile) {
        return this._explored[z][x][y];
    } else {
        return false;
    }
};

Our map is now ready to keep track of what the player's seen!

assets/screens.js

We want to update our explored state every time a new cell is explored. For the sake of simplicity, we will simply mark every tile in our field of vision as explored even if it's already been marked before. The perfect place to do this is in the callback that was passed to the FOV's compute method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Game.Screen.playScreen = {
    // ...
    render: function(display) {
        // ...
        // This object will keep track of all visible map cells
        var visibleCells = {};
        // Store this._map and player's z to prevent losing it in callbacks
        var map = this._map;
        var currentDepth = this._player.getZ();
        // Find all visible cells and update the object
        map.getFov(currentDepth).compute(
            this._player.getX(), this._player.getY(), 
            this._player.getSightRadius(), 
            function(x, y, radius, visibility) {
                visibleCells[x + "," + y] = true;
                // Mark cell as explored
                map.setExplored(x, y, currentDepth, true);
            });
        // Iterate through all visible map cells
        // ...
    },
    // ...
};

Finally we want to update our tile rendering to render tiles that have been explored rather than only render tiles that are currently in the field of vision. We also want to make it render the tile in dark gray if the tile is explored but not in the field of vision.

 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
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 tile = this._map.getTile(x, y, currentDepth);
                    // The foreground color becomes dark gray if the tile has been
                    // explored but is not visible
                    var foreground = visibleCells[x + ',' + y] ?
                        tile.getForeground() : 'darkGray';
                    display.draw(
                        x - topLeftX,
                        y - topLeftY,
                        tile.getChar(), 
                        foreground, 
                        tile.getBackground());
                }
            }
        }
        // Render the entities
        // ...
    },
    // ...
};

Conclusion

That's it for our basic field of vision! Our hero now remembers what's been seen before in the cave without revealing what hasn't been seen yet! This makes exploring much more enjoyable and rewarding! The next post will work on the enemy AI! We will finally start populating the cave with more interesting enemies that actually move around and attack our hero! So make sure to stick around for that post!

I hope you enjoyed this post! Remember that all the code for this part can be found at the part 8b 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 9 - Wandering Monsters