Roguelike games are a venerable genre of computer games that have entertained hackers for roughly 30 years, and had a major influence on gaming classics like Diablo and Torchlight. Roguelikes followed a somewhat unique evolutionary path in the world of computer games. Unlike commercial games that strive to be unique in a very large ocean of predatory competitors, roguelikes are more a labour of love. Each new generation was based on the games that came before, adding a handful (or more) of features that the author felt was missing from the generation before. This layering of new features is much like the way ancient cities such as Rome have built upon themselves, century after century. In a way, playing these earlier games is like an archaeological dig, giving us a peek at the evolution of the genre.
It was in this spirit that I sought to compile Moria, one of the most popular games that made up the second generation of roguelikes.
This is a hands-on blog posting. I will be walking through the steps I took to compile umoria 4.8.7, a Unix port of the original Moria from 1987. The instructions should be valid for virtually any Linux installation. You will need a copy of the source code for umoria 4.8.7, which I have mirrored here.
First, consult the INSTALL file for the original compilation instructions. The INSTALL file indicates that config.h needs to have a line uncommented to define the system being built for, and the path to a handful of game files needs to be updated. The original file paths are
#define MORIA_HOU "/usrb/vopata/mm/Moria_hours" #define MORIA_MOR "/usrb/vopata/mm/Moria_news" #define MORIA_MAS "/usrb/vopata/mm/Character" #define MORIA_TOP "/usrb/vopata/mm/Highscores" #define MORIA_HLP "/usrb/vopata/mm/Helpfile"
I changed these, for the purposes of quickly making things work, to
#define MORIA_HOU "Moria_hours" #define MORIA_MOR "Moria_news" #define MORIA_MAS "Character" #define MORIA_TOP "Highscores" #define MORIA_HLP "Helpfile"
When it comes to uncommenting the line for the appropriate system, there obviously isn't one for Linux. The closest match would be Unix System V, so uncomment the define for SYS_V
< /* #define SYS_V */ --- > #define SYS_V
As per the INSTALL file, run make, and check the results so far. Everything goes fine until the signals.c file.
$ make ... cc -O -c -o signals.o signals.c signals.c:32: error: conflicting types for ‘signal’ /usr/include/signal.h:101: note: previous declaration of ‘signal’ was here signals.c: In function ‘init_signals’: signals.c:55: error: ‘SIGEMT’ undeclared (first use in this function) signals.c:55: error: (Each undeclared identifier is reported only once signals.c:55: error: for each function it appears in.) make: *** [signals.o] Error 1
The first error points to line 32, and a conflicting type for 'signal'. My best guess is that signal.h on old Unix systems did not actually declare a type called 'signal', leaving it up to the developers. On a Linux system, however, signal.h does have a declaration for 'signal', making this one a duplicate.
The second error here is that Linux doesn't have a signal for emulator trap, SIGEMT. The easiest course of action is to just comment out the whole line that refers to it. Looking at the nearby lines, there's also a reference to SIGSYS, which also doesn't exist in Linux. That line can be commented out too.
$ make cc -O -c -o signals.o signals.c signals.c: In function ‘init_signals’: signals.c:49: warning: passing argument 2 of ‘signal’ from incompatible pointer type /usr/include/signal.h:101: note: expected ‘__sighandler_t’ but argument is of type ‘int (*)()’ ...
There's still something wrong in signals.c. The issue would likely be obvious to someone familiar with the differences with signal handling between Unix and Linux, but luckily for the rest of us, the comment on line 29 sheds some light on the issue. In modern (all?) Linux, signal and suspend_handler need to be declared as void instead of int. The three declarations on lines 33 to 35 need to be changed, and the three definitions on lines 67, 140, and 174 also need to be changed (And while you're here, marvel at the archaic way the return types for these functions are specified). signals.c should finally compile.
$ make cc -O -c -o signals.o signals.c cc -O -c -o moria1.o moria1.c moria1.c: In function ‘remove’: moria1.c:389: error: argument ‘item_val’ doesn’t match prototype /usr/include/stdio.h:155: error: prototype declaration moria1.c: In function ‘unwear’: moria1.c:498: warning: passing argument 1 of ‘remove’ makes pointer from integer without a cast /usr/include/stdio.h:155: note: expected ‘const char *’ but argument is of type ‘int’ moria1.c: In function ‘wear’: moria1.c:652: warning: passing argument 1 of ‘remove’ makes pointer from integer without a cast /usr/include/stdio.h:155: note: expected ‘const char *’ but argument is of type ‘int’ make: *** [moria1.o] Error 1
The errors show that there's a function 'remove' defined in moria1.c. The issue is that remove is also defined in later versions of stdio.h. Since there's no namespace support in C, the fix is to rename 'remove' in moria1.c to something like 'remove_item'. There's only two calls to that function in the code (lines 498 and 652), making it a pretty quick fix.
And that's it. One last call to make, then the shiny new old executable can be run:
If you'd prefer to dive right in, you can download the updated source on Github.