- Project page
- Tutorial[1]
- Repo[2]
Part 4 adds field of view using libtcod’s fov library function. I used ROT.js’s library function instead. One difference is that ROT.js offers light levels, from 0% to 100% instead of False and True. That means instead of an explored boolean, I store the highest amount of light a tile has ever seen.
Instead of storing a lit and dark color in the entity, I wrote a function that smoothly interpolates between lit and dark colors.
Field of view has to look up each tile. My Table class does lookups by checking every tile, a “linear scan”:
findTileAt(x, y) {
for (let tile of table.rows) {
if (row.location.x === x && row.location.y === y) return tile;
}
}
It works, but it’s slow, taking up to 50 milliseconds per turn on my very fast machine. The conventional way to do this is to use a 2D array for fast lookups:
findTileAt(x, y) {
return tiles[x][y];
}
Much faster! But in this project I wanted to use the table data structure as much as possible. This is a good time to add indexing to the Table class. An index is a data structure that makes lookups much faster. I’m going to use a hash table, which works well for equality tests. There are other types including bitmaps (useful for intersection/union), trees (useful for range queries), and spatial (useful for location queries; see the Spatial Partition[3] chapter in Bob Nystrom’s book, Game Programming Patterns).
findTileAt(x, y) {
let rows = table.index.location.tilesAt(x, y);
assert(rows.length === 1);
return rows[0];
}
This hash table lookup runs much faster than the linear scan but not as fast as the array lookup. That’s ok for this project. The gain in generality was worth the loss of speed. With the index, field of view took around 1 millisecond.
Indexing is tricky to get correct, but once it’s written it’s useful for lots of things. I don’t write unit test for everything. I focus my efforts on core data structures and algorithms. I added some unit tests for the Table class and asked an LLM to suggest even more tests.
Here’s the game at the end of Part 4: