As a kid, I wanted to learn how to program to make computer games. That was the original goal, anyways. I’ve always felt there is a weird sort of magic being able to type structured sentences into a text file, ram it through a compiler (sometimes with fun, cryptic looking incantations typed into the terminal), and seeing something come to life on the other end.
Programming has always been an on again/off again sort of hobby. I think it’s more the act of making something from nothing that draws me to it then actually making something. A few years ago (actually closer to a decade, somehow) I found the Complete Roguelike Tutorial using Python and libtcod. I didn’t know Python when I started the tutorial, but the end result of that project was amazing - I had a mostly complete game. I tweaked and customized, modified and played around with that project for a while. Eventually hit a wall, probably due to my lack of knowledge with Python.
That project inspired me to try and learn C++, which I figured was a logical leap since I had a pretty decent (completely informal and entirely from books) set of skills with C. It didn’t take long for me to want to attempt another Roguelike.
That project was called Barbarian, and it was a lot of fun to work on - and probably the closest I’ve came to making a start at “the game” I wanted to make as a kid. I never really felt comfortable with my C++ knowledge though, and the project had issues I was never able to quite wrap my head around fixing. A thing most books don’t teach is how to actually “build” software, and my conclusion was that most of Barbarian’s code problems were architectural.
I think programming is like building a model out of legos. You start with little pieces, and eventually you make a part that then connects to other parts. If you’re really good, the legos you make are reusable. There’s certain (well documented) patterns and strategies to make parts of your software that if you adapt well, you can make them into anything. I went back into C programming to learn more basic skills and patterns, and again, what better place to practice all these little components then in a roguelike? A long, rambling sort of introduction to my latest project. I started working last year on a roguelike, but this time with a “clear” end goal.
The project was supposed to be simple - a dungeon dive with a set number of levels. Get to the bottom, kill the chief, get out. After that was accomplished, I could use the project as a sandbox to play with all my other ideas. I named this project Goblin Caves - the burly hero of this adventure is sent to slay a Goblin Chief, who sits on his throne at the bottom of a labyrinthine cavern.
Currently, the project runs right in the terminal and uses ncurses to render everything. One of the major goals is to make sure to not tie the game to ncurses - I may eventually use SDL to draw everything on a window instead, which would be more portable and more consistent. The entire project is hand rolled, and uses no external libraries, other than ncurses and the system stuff. I wrote all of it, and tried my best to keep things as generic as possible.
I hit a snag last year, and it was a problem I had never solved in other projects: time management. It’s no fun if you and a goblin take turns whacking at each other in lockstep - some goblins should be really fast, and some should be slow. Your player should be able to speed up and slow down - from something in the environment, a poison, a potion. When the bat swoops down from the ceiling it should be able to attack a couple of times before you get a chance to react.
The way the game loop is structured is straight out of Game Programming Patterns, basically the engine does the following things in a constant loop:
- Handle Events
- Update
- Draw
Events are (in this project, and most of my projects) things like keyboard presses, game state changes, etc. When the player presses a key, it’s read here and then interpreted in the player’s update routine. Update is the big function, and is responsible for translating the player’s keypress into an action taken by their avatar, a selection on a menu screen, or initiating a change in game state. Draw is where all of this action is then translated into glyphs that are then placed on the screen, showing the player the game.
Now, since this is a turn based game, all the entities that are not the player don’t update until after the player takes their turn. So the update/draw loop has to run until the player can take their turn again. Each “tick” through the loop checks all the actors (player/enemies) and if they can take a turn, they take it. Otherwise, they gain a set amount of energy. Then, when the player can take a turn, the update/draw loop breaks, and waits for the player’s input. Easy enough right? Well, when would animations play? What happens when the actor list is bigger, or the monsters have more complex behavior?
So, the solution is this: the update loop checks the player first, if they can take their turn it doesn’t update the rest of the actors. Player takes their turn, spends energy. Now, when update is called again, the player doesn’t have the energy to take another turn, so they are granted a unit of energy. The game then proceeds through the rest of the actors - taking turns and spending energy immediately if they have enough to act, and giving a unit of energy if they don’t. Now, if (when) animations and sound are added, update can play them immediately. Psuedocode:
engine() {
while(running) {
handle_events();
update();
draw();
}
}
update() {
if(can_take_turn(player) return;
update_monster(player); // Checks if player is dead, has moved, etc
for(i = 0; i < monster_list.length; i++) {
if(can_take_turn(monster_list[i]) {
take_turn(monster_list[i];
} else {
grant_energy(monster_list[i];
}
update_monster(monster_list[i]);
}
}
Well, that should work. Except, remember how I said I wasn’t going to tie
things to ncurses? Well, checking for keyboard input blocks the loop from
advancing with the way that routine is set up. And, for some reason I set up the
draw()
routine to call ncurses directly, instead of just changing glyphs on a
fake screen, then drawing that fake screen once a loop. A temporary work around
that is currently in place (until I fix the silliness I wrote in with ncurses)
is that update loops until the player can take their turn, then breaks and waits
for input. This causes monsters to “jump” around and not be where the player
thinks they are (since draw isn’t called at the right time). Adding draw()
into this temporary loop breaks the display of game messages…
So, that’s where I’m at with the project now - divorcing the game from ncurses. The way everything should work should be completely independent from ncurses - even though I have no intention of doing actual tiles/graphics for this game, I want the possibility to eventually have a command line switch for an SDL interface. A few more screenshots featuring my attempts at ASCII art:
Some goals I wrote for myself when I started this project:
- Keep the game limited to 4 (or 5) major data types: Monster, Tile, Effects, Pickups. Currently, the player is a monster, but eventually it might be it’s own thing.
- Saving/Loading, dungeon persistence (you can go back up stairs and return to a level you were at previously). I finished this part already, and it was really not as complicated as I previously understood. Maybe a side effect of doing this in C (and not C++)?
- Map generation. Procedural levels, destructible tiles, pre-made “chunks” (like ADOM’s vaults), dynamic field-of-view. This is the fun part, so naturally bits and pieces of it exist in some form or another. Whenever I get tired of doing something, I find myself here tweaking and adding stuff in.
- Time management - where I got stuck last year, and the thing I am currently working on.
Then there’s the long wishlist items of things I want to implement, eventually. Some highlights:
- Player abilities - spells, weapon attacks.
- Monster intelligence. Lots of ideas here, and none of them have been worked on yet.
- Procedurally generated flora
- Animations
- “Flavor” text procedurally generated, from chunks in an external file. This would be amazing to accomplish - because then the game could be easily translated to another language.
And here’s a little gameplay video, warts and all. You can see in the video that the update/draw routine isn’t quite right (players aren’t where they should be). The splatter after a goblin is smashed might need to be toned down a hair - not sure if it’s fun or excessive at this point.
The project is on Github, and I dump
all my ideas into a markdown file (ideas.md
) on there. The master
branch is
stable, and should work/build just fine on any flavor of Linux. The most recent
hare-brained ideas and work are on the dev
branch.