How To

Mammoth Cave
     
    You are standing at the end of a road before a small brick building. 
    Around you is a forest. A small stream flows out of the building 
    and down a gully.

    Sound familiar? If you’re an old-school gamer, you’ve probably seen these words hundreds of times. They’re the starting point for Colossal Cave Adventure, the first major computer game. It was created in 1977 by Willie Crowther and Don Woods and quickly became a phenomenon in those pre-GUI, pre-Internet years. What helped the game spread from one data center to another was the fact that the source code was freely available.

    The last version of the code from the original authors was written in 1995, and if you’re like us, you're hearing this story and wondering where that code is now. With the encouragement and permission of the authors, open source pioneer Eric S. Raymond has ported the code to his GitLab account. In this article, we’ll show you how to build and run that code.

    Before we get started, though, a brief promo for more awesome things: this article and the associated video and Docker image were inspired by the Command Line Heroes podcast, Season 2, Episode 1.

    This episode of the podcast covers Colossal Cave Adventure along with more info on the history of gaming. Check out the show, available at your favorite podcasteria. (A big thanks to Saron and the Command Line Heroes gang for telling us the story and piquing our interest in the code.)

    Let's get started.

    Cloning the repo and building the code

    The code lives at https://gitlab.com/esr/open-adventure

     

    From here, of course, you need to clone the repo:

    git clone https://gitlab.com/esr/open-adventure.git

    Once you've cloned the repo, switch to that directory and run make...which will almost certainly fail because there are a couple of libraries that may not be installed on your machine. 

    doug@dtidwell-mac:~/Developer/open-adventure $ make
    ./make_dungeon.py
    Traceback (most recent call last):
      File "./make_dungeon.py", line 13, in <module>
        import sys, yaml
    ImportError: No module named yaml
    make: *** [dungeon.h] Error 1
    

    The crucial libraries are the Python YAML library and libedit. To complicate things, the way you install those libraries is different on Linux, the Mac, and Windows.

    Linux

    We'll tackle Linux first. Use the install manager for your distro to install the package python-yaml:

    [doug@rhel-7 open-adventure]$ sudo yum -y install python-yaml
    Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager
    Resolving Dependencies
    --> Running transaction check
    ---> Package PyYAML.x86_64 0:3.10-11.el7 will be installed
    --> Finished Dependency Resolution
    
    Dependencies Resolved
    
    ========================================================================================================================
     Package                 Arch                    Version                      Repository                           Size
    ========================================================================================================================
    Installing:
     PyYAML                  x86_64                  3.10-11.el7                  rhel-7-server-rpms                  153 k
    
    Transaction Summary
    ========================================================================================================================
    Install  1 Package
    
    Total download size: 153 k
    Installed size: 630 k
    Downloading packages:
    PyYAML-3.10-11.el7.x86_64.rpm                                                                    | 153 kB  00:00:00     
    Running transaction check
    Running transaction test
    Transaction test succeeded
    Running transaction
      Installing : PyYAML-3.10-11.el7.x86_64                                                                            1/1 
      Verifying  : PyYAML-3.10-11.el7.x86_64                                                                            1/1 
    
    Installed:
      PyYAML.x86_64 0:3.10-11.el7                                                                                           
    
    Complete!
    

    Next, install the package libedit-devel:

    [doug@rhel-7 open-adventure]$ sudo yum -y install libedit-devel
    Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager
    Resolving Dependencies
    --> Running transaction check
    ---> Package libedit-devel.x86_64 0:3.0-12.20121213cvs.el7 will be installed
    --> Finished Dependency Resolution
    
    Dependencies Resolved
    
    ========================================================================================================================
     Package                 Arch             Version                           Repository                             Size
    ========================================================================================================================
    Installing:
     libedit-devel           x86_64           3.0-12.20121213cvs.el7            rhel-7-server-optional-rpms            33 k
    
    Transaction Summary
    ========================================================================================================================
    Install  1 Package
    
    Total download size: 33 k
    Installed size: 52 k
    Downloading packages:
    libedit-devel-3.0-12.20121213cvs.el7.x86_64.rpm                                                  |  33 kB  00:00:00     
    Running transaction check
    Running transaction test
    Transaction test succeeded
    Running transaction
      Installing : libedit-devel-3.0-12.20121213cvs.el7.x86_64                                                          1/1 
      Verifying  : libedit-devel-3.0-12.20121213cvs.el7.x86_64                                                          1/1 
    
    Installed:
      libedit-devel.x86_64 0:3.0-12.20121213cvs.el7                                                                         
    

    With those in place, you should be able to type make and build the code: 

    [doug@rhel-7 open-adventure]$ make
    ./make_dungeon.py
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline    -c main.c
    main.c: In function ‘do_command’:
    main.c:1152:9: warning: implicit declaration of function ‘strncasecmp’ [-Wimplicit-function-declaration]
             if (strncasecmp(command.word[0].raw, "west", sizeof("west")) == 0) {
             ^
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline    -c init.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline    -c actions.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline    -c score.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline    -c misc.c
    misc.c: In function ‘get_motion_vocab_id’:
    misc.c:362:13: warning: implicit declaration of function ‘strncasecmp’ [-Wimplicit-function-declaration]
                 if (strncasecmp(word, motions[i].words.strs[j], TOKLEN) == 0 && (strlen(word) > 1 ||
                 ^
    misc.c: In function ‘get_vocab_metadata’:
    misc.c:457:5: warning: implicit declaration of function ‘strcasecmp’ [-Wimplicit-function-declaration]
         if (strcasecmp(word->raw, game.zzword) == 0) {
         ^
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline    -c saveresume.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all  -c dungeon.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all  -o advent main.o init.o actions.o score.o misc.o saveresume.o dungeon.o  -ledit -ltinfo  
    

    You'll get a couple of warnings, which you'll probably ignore. (Hey, the code compiled, didn’t it?) You should now have an executable named advent.

    Mac

    On the Mac, it's a little more complicated. For the Python YAML library, just type sudo -H pip install PyYAML

    doug@dtidwell-mac:~/Developer/open-adventure $ sudo -H pip install PyYAML
    Collecting PyYAML
      Using cached https://files.pythonhosted.org/packages/9e/a3/1d13970c3f36777c583f136c136f804d70f500168edc1edea6daa7200769/PyYAML-3.13.tar.gz
    Installing collected packages: PyYAML
      Running setup.py install for PyYAML ... done
    Successfully installed PyYAML-3.13
    

    For libedit, if you use MacPorts, sudo port install libedit does the trick. Ignore any warning error messages that come up:

    doug@dtidwell-mac:~/Developer/open-adventure $ sudo port install libedit
    Warning: port definitions are more than two weeks old, consider updating them by running 'port selfupdate'.
    Warning: xcodebuild exists but failed to execute
    Warning: Xcode does not appear to be installed; most ports will likely fail to build.
    --->  Computing dependencies for libedit
    --->  Cleaning libedit
    --->  Scanning binaries for linking errors
    --->  No broken files found.
    --->  No broken ports found.
    

    If you use Homebrew, type brew install libedit:

    doug@dtidwell-mac:~/Developer/CLH/S2E1 $ brew install libedit
    Updating Homebrew...
    Ignoring path homebrew-cask/
    To restore the stashed changes to /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask run:
      'cd /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask && git stash pop'
    ==> Auto-updated Homebrew!
    Updated 2 taps (homebrew/core, homebrew/cask).
    ==> New Formulae
    skopeo                                   ucloud
    ==> Updated Formulae
    ghostscript ✔       double-conversion   libphonenumber      pdfsandwich
    kubernetes-cli ✔    eslint              libspectre          phpunit
    annie               folly               lnav                pqiv
    apache-drill        fonttools           logentries          ripgrep
    at-spi2-atk         grunt-cli           mdcat               terragrunt
    atk                 imagemagick         media-info          webpack
    atomicparsley       imagemagick@6       mps-youtube         xmount
    bwfmetaedit         kustomize           msgpack             youtube-dl
    cmdshelf            libimobiledevice    nano
    ==> Deleted Formulae
    llvm@3.7
    
    ==> Downloading https://homebrew.bintray.com/bottles/libedit-20180525-3.1.high_s
    Already downloaded: /Users/doug/Library/Caches/Homebrew/downloads/44dfc51536ebc0eede0404570dae31f5d2812eb16b2eaf3ac33ad31eb8f7714f--libedit-20180525-3.1.high_sierra.bottle.tar.gz
    ==> Pouring libedit-20180525-3.1.high_sierra.bottle.tar.gz
    ==> Caveats
    libedit is keg-only, which means it was not symlinked into /usr/local,
    because macOS already provides this software and installing another version in
    parallel can cause all kinds of trouble.
    
    For compilers to find libedit you may need to set:
      export LDFLAGS="-L/usr/local/opt/libedit/lib"
      export CPPFLAGS="-I/usr/local/opt/libedit/include"
    
    For pkg-config to find libedit you may need to set:
      export PKG_CONFIG_PATH="/usr/local/opt/libedit/lib/pkgconfig"
    
    ==> Summary
    🍺  /usr/local/Cellar/libedit/20180525-3.1: 53 files, 510KB
    

    Be aware that whichever tool you use, you'll have to add the directory with the libedit.pc file to your PKG_CONFIG_PATH environment variable. In the screen capture above, Homebrew gives you the command to use. Simply cut and paste the export command. Be aware that MacPorts installs libedit in a different directory (/opt/local/lib/pkgconfig), so set the variable accordingly. 

    With those tedious steps behind you, running make should work. The warning messages you'll get are different, but the carelessness with which you ignore them should be the same. 

    doug@dtidwell-mac:~/Developer/open-adventure $ make
    ./make_dungeon.py
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/local/Cellar/libedit/20180525-3.1/include -I/usr/local/Cellar/libedit/20180525-3.1/include/editline  -c main.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/local/Cellar/libedit/20180525-3.1/include -I/usr/local/Cellar/libedit/20180525-3.1/include/editline  -c init.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/local/Cellar/libedit/20180525-3.1/include -I/usr/local/Cellar/libedit/20180525-3.1/include/editline  -c actions.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/local/Cellar/libedit/20180525-3.1/include -I/usr/local/Cellar/libedit/20180525-3.1/include/editline  -c score.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/local/Cellar/libedit/20180525-3.1/include -I/usr/local/Cellar/libedit/20180525-3.1/include/editline  -c misc.c
    misc.c:150:18: warning: passing an object that undergoes default argument
          promotion to 'va_start' has undefined behavior [-Wvarargs]
        va_start(ap, blank);
                     ^
    misc.c:142:62: note: parameter of type 'bool' is declared here
    void pspeak(vocab_t msg, enum speaktype mode, int skip, bool blank, ...)
                                                                 ^
    1 warning generated.
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/local/Cellar/libedit/20180525-3.1/include -I/usr/local/Cellar/libedit/20180525-3.1/include/editline  -c saveresume.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all  -c dungeon.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all  -o advent main.o init.o actions.o score.o misc.o saveresume.o dungeon.o  -L/usr/local/Cellar/libedit/20180525-3.1/lib -ledit

    Windows

    Finally, if you’re using Windows, you’ll need to go to the Cygwin website and click the Install link: 

    The Cygwin website

    To keep it simple, just install everything from the Devel and Python packages.

    Installing Cygwin packages

    With that done, just type make and you’re all set:

    doug@DOUGTIDWELL4497 ~/open-adventure
    $ make
    ./make_dungeon.py
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline  -c main.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline  -c init.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline  -c actions.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline  -c score.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline  -c misc.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all -I/usr/include/editline  -c saveresume.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all  -c dungeon.c
    cc -std=c99 -D_DEFAULT_SOURCE -DVERSION=\"1.4\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all  -o advent main.o init.o actions.o score.o misc.o saveresume.o dungeon.o  -ledit -lcurses
    

    Notice that the Windows/Cygwin ports of gcc and make didn’t generate any errors at all.

    Playing the game

    Now you’re ready to play the game! Brace yourselves: Colossal Cave Adventure combines the visual appeal of the command line with all the excitement of typing. Run the advent executable to get started. First you’re asked if you want instructions. Type yes to get some background and see the words that inspired a generation of gamers:  

    doug@dtidwell-mac:~/Developer/open-adventure$ advent
    
    Welcome to Adventure!!  Would you like instructions?
    
    > y
    
    Somewhere nearby is Colossal Cave, where others have found fortunes in
    treasure and gold, though it is rumored that some who enter are never
    seen again. Magic is said to work in the cave. I will be your eyes
    and hands. Direct me with commands of 1 or 2 words. I should warn
    you that I look at only the first five letters of each word, so you'll
    have to enter "northeast" as "ne" to distinguish it from "north".
    You can type "help" for some general hints.  For information on how
    to end your adventure, scoring, etc., type "info".
                     - - -
    This program was originally developed by Willie Crowther.  Most of the
    features of the current program were added by Don Woods.
    
    You are standing at the end of a road before a small brick building.
    Around you is a forest.  A small stream flows out of the building and
    down a gully.
    

    Exploring the building seems like a reasonable place to start. You can type enter building to make things explicit, or just type the word enter:

    > enter
    
    You are inside a building, a well house for a large spring.
    
    There are some keys on the ground here.
    
    There is a shiny brass lamp nearby.
    
    There is food here.
    
    There is a bottle of water here.
    

    All of those things sound useful, so go ahead and take everything:

    > get keys
    
    OK
    
    > get lamp
    
    OK
    
    > get food
    
    OK
    
    > get water
    
    OK
    

    At any time, you can use the inventory command to see what you’re carrying:

    > inventory
    
    You are currently holding the following:
    Set of keys
    Brass lantern
    Tasty food
    Small bottle
    Water in the bottle
    
    

    Notice that the command lists the water in the bottle separate from the bottle itself. So maybe the bottle is useful whether it has any water or not. Hmmm. And the food, whatever it is, is tasty. So we’ve got that going for us, which is nice.

    Pro tip: As explained in the instructions, Colossal Cave Adventure only looks at the first five characters of a command. That means you can type inven to do the same thing:

    > inven
    
    You are currently holding the following:
    Set of keys
    Brass lantern
    Tasty food
    Small bottle
    Water in the bottle
    

    This shortcut allows you to see your inventory 44% faster. You’re welcome.

    Another tip: Let’s just say that maybe it’s not a good idea to pick up everything in the building. Or maybe it is. You’ll have to play the game to find out.

    Now that you’re loaded up with provisions, type leave to go back outside.

    > leave
    
    You're in front of building.

    Now you can start exploring the world. Type west to, well, go west:

    > west
    
    You have walked up a hill, still in the forest.  The road slopes back
    down the other side of the hill.  There is a building in the distance.
    

    (You can also just type w.)

    One of the great things about the game is that it requires your active imagination. For example, the narrative here suggests that the building in the distance is the one you just visited. It’s surprisingly compelling, especially considering that it’s a command-line game originally written 40+ years ago. To keep track of things, most players grab a sheet of paper and draw a map of the world as they go.

    Typing quit and yes takes you out of the game. Unless you’ve explored the game beyond what we’ve done here, you’ll get a snarky message about your pathetic score:

    > quit
    
    Do you really want to quit now?
    
    > y
    
    OK
    
    You scored 32 out of a possible 430, using 11 turns.
    
    You are obviously a rank amateur.  Better luck next time.
    
    To achieve the next higher rating, you need 14 more points.
    

    Our gift to you: a Docker image

    As an added bonus, we’ve created a Docker image at quay.io that contains the game and its source code:

    That image lets you play the game without building the code yourself. If you have Docker installed and running on your machine, docker pull quay.io/rhdp/open-adventure downloads the image. Create a container from it, SSH into the container, run ./advent, and off you go.

    Summary

    Colossal Cave Adventure had an enormous impact on gamers and gaming. Forty years later, you can still see what all the fuss was/is about. Like any good game, it’s complicated and demands your concentration. If you’ve gone through the steps here (or just pulled the Docker image), you can follow the same treacherous paths as many thousands of gamers before you. Enjoy the adventure!