Neohack progress report, Game version O.1, Engine version 0.21.
Being chased by wolves is normally kind of a downer, unless you happen to be hungry and carrying a gun. I mean, there isn’t much meat on ’em but they come right up to you…. Aw, who am I kidding?! I grew up with guns and near wild land, but I’ve been living in a very civilized area for about 20 years and I don’t even own guns any more. Where nobody I know has any wild land I could go hunting on, nor any problems with predators taking cattle or sheep, nor with pests getting into their feed storage etc, and there’s essentially no danger from wildlife, there’s just no need for guns. If you wanna talk about urban home defense, there’s a huge range of options from baseball bats, mattocks, pickaxes, shovels, etc. all the way up to swords. And anyway I’m not talking here about real wolves.
These wolves – the ones I’m actually happy about – are simulations in the game Neohack. They are represented by little brown Unicode ‘w’s on a terminal screen, chasing after a protagonist whose sign is ‘@’. The reason they make me happy is because they mean I’ve been successful in debugging the module interface so that now it’s possible to add monsters to Neohack without touching any of the engine code.
The module interface was something I wrote in bits and chunks over a long-ish time, but had never really put into use before. There is a fair amount of code in it, and up until this weekend, it was mostly not debugged. I had hoped to work on the inventory system this weekend, but debugging the module interface took longer than I expected.
So, briefly, I will explain how it works. Neohack itself works by initializing a few structures, then putting a game-start event on the schedule, then repeatedly pulling events off the schedule and running them until the schedule is empty. That would be a fairly short run, except that many of the events that go onto the schedule, when executed, put more events on the schedule before they finish.
The schedule itself is a classic Data-Directed structure, because the events are function closures (which you may call ‘promises’ instead depending on your programming language background). Each event is a pointer to a function of a particular type, and a bundle of arguments, assembled like a call frame but not yet called. The actual call happens when the schedule says its time has come.
A ‘sed’ script built into the makefile does simple text pattern matching on the source files to find all the function prototypes for the event functions. It manipulates them into lists, which are then #included in various places where they can be read as C syntax for function prototypes, function pointer constants (ie, the names of routines), Integer constants (ie, indices into an array of function pointers) etc.
Some functions have names that end in ‘INIT’ and the module interface starts up when the game-start event puts an event for each such function into the schedule. Each module can therefore have an ‘INIT’ event, and rely on it getting called when the game starts. That allows each module to insert things into data structures where other parts of the game will pick it up.
The structures that the ‘wolf’ module is interested in are the ones that Neohack uses to define creatures. The goal is for a stack of attributes that defines ‘wolf’ to have a place in the ‘kinds’ table, which will allow a ‘Make-critter’ event to make new wolves, and for a ‘Make-critter’ event having as an argument that entry in the ‘kinds’ table to have a place and probability in the ‘Natural Animals’ table. The ‘Natural Animals’ table is one which the map generator uses to generate creatures when populating its maps. The ‘Natural Animals’ table is one of many generation tables. Others produce things like doors, treasures, different kinds of monsters, tools, food, and so on.
So Action_Wolf_INIT does the following things; First, it defines an attribute stack for wolves. Neohack has generic actors, whose actions, interactions, and capabilities are all defined by attributes. An actor can have any number of attributes. The attributes that define wolves give them things like a speed, a reference to the AI routine they use, a map representation (the brown ‘w’), a size and mass for modeling in-game interactions, and so on. Then it adds the attribute stack, with the name ‘wolf’ and the additional information that it is a ‘species’ kind, to the kinds table, so from now on that ‘kind’ will refer to a species having that attribute stack. Then it creates an event (using the predefined event routine ‘Action_Make_Critter’) which will make two wolves, and puts that event into the ‘Natural Animals’ generation table, with an oddment, in this case, of 1000, which corresponds to a probility of 1000 out of however many oddments the Natural Animals table has when everything has eventually been added to it. And that is absolutely everything that Action_Wolf_INIT needs to do.
The map generator gets called after all the INIT events have run. It calls the ‘Natural Animals’ table to get an event, makes a copy of the event, adds a ‘to location’ argument to that copy so that the created critters will appear in a definite location on the map, and then puts the copy into the schedule as a new event.
When the event is pulled off the schedule, Action_Make_Critter is finally called. It creates actors, copies the requested attribute stack from the ‘kinds’ table into those actors making them wolves, and places each actor at the closest walkable, unoccupied tile to the ‘to location’ still available in the map when that actor is created. Then if the new actor has a ‘speed’ attribute Action_Make_Critter updates the ‘speed’ attribute so they don’t get any actions before the current game time, and schedules their first AI action.
I found lots of bugs while finally putting all this machinery into use. I probably built too much functionality without testing, which made debugging a bit harder, but I did manage to sort it all out. Maybe in another post I’ll talk about the debugging infrastructure I’ve built into it (it keeps a ‘trace’ logfile so you can see what it was trying to do just before it crashed).
But anyway, now wolves are chasing me, and that makes me happy.