I love Terrapin Logo! I got away with not having to write a parser, by simply using the Logo top-level read-eval-print loop as the parser, and defining Logo words like LOOK, N, S, E, W, TAKE, EXAMINE, etc. So it’s really easy to cheat by examining and modifying the state of the world, but that helps you learn Logo!

The ADVENTURE word starts the game by switching to text mode (NODRAW), printing an introduction, setting up all the data structures by calling INIT, and printing a description of the current room by calling LOOK. First it sets [ADVENTURE] to be the startup function, so it starts the game automatically when you load it.



NODRAW











INIT

LOOK

END



"STARTUP [ADVENTURE] TO ADVENTURE PR [WELCOME TO LOGO ADVENTURE] PR [WRITTEN BY DON HOPKINS] PR [] PR [TYPE HELP FOR HELP] PR []INITLOOK MAKE "STARTUP [ADVENTURE]

A good place to start our walk through the code would be the HELP word, which just prints out a bunch of helpful tips and wishes you luck, which should give you an idea of what’s to come:



































CMD

END TO HELP PR [TO MOVE, TYPE] PR [N, S, E, W] PR [FOR NORTH, SOUTH, EAST, WEST] PR [] PR [TYPE LOOK TO SEE WHAT ROOM YOU] PR [ARE IN. YOU CAN GET AND DROP ITEMS.] PR [INVENT SHOWS YOUR INVENTORY.] PR [THE WORD "IT" MEANS THE LAST THING YOU] PR [REFERRED TO.] PR [] PR [THERE ARE SOME SPECIAL THINGS YOU CAN] PR [DO, LIKE SAYING EXAMINE SOMETHING.] PR [] PR [TYPE SCORE TO SEE YOUR SCORE, AND] PR [DONE TO QUIT.] PR [GOOD LUCK!]CMD

The INIT word sets up our model of the Adventure universe, which is very simple: some rooms, some items, and a player.











INITITEMS :ITEMS 1

END TO INIT MAKE "ITEMS [[1 0 SWORD] [1 0 HATCHET] [1 0 SHIELD] [2 100 GOLD] [2 100 DIAMOND] [2 50 AMULET] [3 0 SCREWDRIVER] [4 0 MACHINE] [0 100 WAND] [5 200 CROWN]] MAKE "RMOVES [[0 2 3 0] [0 0 4 1] [1 4 0 0] [2 0 0 3] [0 0 0 0]] MAKE "RNAMES [[YOU ARE IN THE WEAPON SHOP.] [THIS IS THE VAULT.] [THIS ROOM IS THE TOOLSHED.] [THIS IS THE ALTAR ROOM.] [YOU ARE IN A SECRET INCANTING ROOM.]] MAKE "RNUM 1INITITEMS :ITEMS 1

Rooms are numbered from 1 (to match 1-based Logo list indexes). The information about each room is stored in lists, indexed by the room number.

The RNAMES list is simply a list of room names, which are lists of words describing the room. The RMOVES list contains a list of four numbers per room. Those numbers tell the room number you can move to in each direction (in the order [North, East, South, West]), or 0 if you can’t move in that direction.

It’s possible to define one-way or crooked doors this way, but if you want a normal two-way door, each room should point back and forth to one another in opposite directions. But if you really want to make a twisty little maze of passages, then knock yourself out! But please give your players a lot of items to drop behind so they can map our your mazes.

The ITEMS list contains a bunch of lists describing the room number, score (a number) and name of each item (a string). The first thing in the list is the number of the room containing the item, or -1 if the item is in the player’s inventory, or 0 for nowhere. The second thing in the list is the item score: how many points you get for having that item in your inventory. The third thing in the list is the item name.

The most important but simplest part of the model is the player’s room number, RNUM. The N, S, E and W words move from room to room by changing the player’s room number by calling MOVEDIR (and waving the wand transports you to the secret incanting room by magically changing RNUM).

Theoretically, if the room number were 0, the player would be nowhere at all, and if it were -1, the player would be carrying themselves in their own inventory! But don’t worry: that could never happen, unless you used magic or hacked into the system, which is pretty easy since the system is a top-level Logo interpreter.

Finally INIT calls INITITEMS passing it the list of ITEMS as :I and the number 1 as :F. This recursively defines a bunch of magical functions referring to each of the items by name, which also remember the last item you referred to as the pronoun IT.

Those magical functions accomplish two things: We can refer to items by their name without typing additional quotes (by defining a function named for each item), and it automatically remembers the last item we referred to in the global variable IT, by calling the word SETIT.

Now we’re doing some real fun list processing and function definition! The :I parameter is the list of items to recurse over, and the :F parameter controls whether we should initialize the items (1) or clean them up (0). The expression FIRST :I is the current item, and LAST FIRST :I is the name of the current item.

The first thing a recursive function should usually do is check to see if it’s done. So IF :I = [] STOP means stop if we are at the end of the list of items.

The next thing it does is to check if the :F parameter is equal to 1 or not. If that is true (IFT), it defines a magical function, and if not (IFF), it cleans up that function definition.

Finally INITITEMS recurses on itself to process the rest of the list, BF :I, which means (don’t giggle) “but first”, that is: all elements of :I but the first.

To explain what the magical functions do, I will give an example: When the item’s name is “SWORD”, it will define a function named SWORD with no parameters, whose body is [OP SETIT “SWORD], which outputs the result of calling SETIT with the parameter “SWORD”. The SETIT word sets the global variable IT to “SWORD” and returns “SWORD”. So we can say GET SWORD, then say DROP IT.

It’s helpful to know the definition of LPUT, which takes two parameters “thing” and “list”, and returns a copy of “list” with “thing” appended to the end. And also WORD, which takes two things and concatenates them into a word, which is just used in this case to convert a string to a word (like a LISP symbol). And DEFINE takes two arguments: the name of a function, and a list. The first element of the list is the list of parameters (an empty list in our case). The subsequent elements are list expressions to evaluate ([OP SETIT “SWORD] in our case).

Let’s unpack those expressions in the IFT clause by inserting some indentation and parenthesis and comments to make it look like LISP (since Logo is essentially LISP without parens, whose parser necessarily knows the number of parameters each function takes), and linking to the Logo function documentation.

IFT

(DEFINE

(LAST (

(

(

(

"" ; this just converts the string to a word

(LAST (

[

[[]]))) ; empty function with zero parameters to append to FIRST :I)) ; function name to define is the item name LPUT ; function body is a list of lists LPUT ; expression is a list like [OP SETIT "SWORD] WORD ; concatenate the two parameters to get word "SWORD"" ; this just converts the string to a word FIRST :I))) ; the word is the item name OP SETIT]) ; output result of SETIT with item name appended[[]]))) ; empty function with zero parameters to append to

Ugh! But if you think that’s hard to understand, take a look at some of the FORTH code I was writing at the time!

Finally, if the :F parameter to INITITEMS is not 1, then it executes the IFF clause which removes the function definition, by going DEFINE LAST FIRST :I []. This doesn’t actually ever get called, apparently, but there you go.

The final sneaky trick to integrate the game with the Logo top level interpreter is the CMD word, which prints a “COMMAND” prompt, and jumps to the Logo TOPLEVEL to read and interpret a command from the player. Each Adventure word can finally call CMD at the end to show a prompt to the player.

My coffee is wearing off now so I’m going to take a break explaining things for now, but you can read the rest of the program here: