36

Logo Adventure for C64 Terrapin Logo

 5 years ago
source link: https://www.tuicool.com/articles/hit/QjYNBbb
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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.

TO ADVENTURE
NODRAW
PR [WELCOME TO LOGO ADVENTURE]
PR [WRITTEN BY DON HOPKINS]
PR []
PR [TYPE HELP FOR HELP]
PR []
INIT
LOOK
END

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:

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
END

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

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 1
INITITEMS :ITEMS 1
END

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.

RfqmEz3.png!web

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 .

TO INITITEMS :I :F
IF :I = [] STOP
TEST :F = 1
IFT DEFINE LAST FIRST :I LPUT LPUT WORD "" LAST FIRST :I [OP SETIT] [[]]
IFF DEFINE LAST FIRST :I []
INITITEMS BF :I :F
END
TO SETIT :THING
MAKE "IT :THING
OP :THING
END

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 (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
(LAST (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.

TO CMD
PR []
PRINT1 "COMMAND
TOPLEVEL
END

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:

TO N
 MOVEDIR 1
END

TO E
 MOVEDIR 2
END
TO S
 MOVEDIR 3
END
TO W
 MOVEDIR 4
END
TO MOVEDIR :DIR
 MAKE "TRYMOVE ITEM :DIR ITEM :RNUM :RMOVES
 TEST :TRYMOVE = 0
 IFT PR [YOU CAN'T GO THAT WAY.]
 IFT CMD
 PR "OK.
 MAKE "RNUM :TRYMOVE
 LOOK
END
TO INVENT
 PITEMS - 1
 CMD
END

TO LOOK
 PR ITEM :RNUM :RNAMES
 PITEMS :RNUM
 CMD
END
TO PITEMS :LOC
 PITEMS2 :LOC :ITEMS
END
TO PITEMS2 :LOC :I
 IF :I = [] STOP
 IF FIRST FIRST :I = :LOC PRINT LAST FIRST :I
 PITEMS2 :LOC BF :I
END
TO IT
 OP :IT
END

TO EVERYTHING
 OP "EVERYTHING
END
TO GET :ITEM
 TEST :ITEM = "EVERYTHING
 IFT GETALLITEMS :ITEMS
 IF IHAVE? :ITEM ( PR [YOU ALREADY HAVE] PERIOD :ITEM ) CMD
 IF NOT HERE? :ITEM SEENO :ITEM
 PUTITEM :ITEM ( - 1 )
 PR SE :ITEM "TAKEN.
 CMD
END
TO TAKE :THING
 GET :THING
END
TO GETALL
 GETALLITEMS :ITEMS
END
TO GETALLITEMS :I
 IF :I = [] CMD
 TEST :RNUM = ITEMLOC LAST FIRST :I
 IFT PUTITEM LAST FIRST :I ( - 1 )
 IFT PR SE LAST FIRST :I "TAKEN.
 GETALLITEMS BF :I
END
TO DROP :ITEM
 TEST :ITEM = "EVERYTHING
 IFT DROPALLITEMS :ITEMS
 IF NOT IHAVE? :ITEM PR SE [YOU'RE NOT CARRYING THE] WORD :ITEM "! CMD
 PUTITEM :ITEM :RNUM PR SE :ITEM "DROPPED.
 CMD
END
TO DROPALL
 DROPALLITEMS :ITEMS
END

TO DROPALLITEMS :I
 IF :I = [] CMD
 TEST ITEMLOC LAST FIRST :I = ( - 1 )
 IFT PUTITEM LAST FIRST :I :RNUM
 IFT PR SE LAST FIRST :I "DROPPED.
 DROPALLITEMS BF :I
END
TO HERE? :ITEM
 LOCAL "LOC
 MAKE "LOC ITEMLOC :ITEM
 OP ANYOF - 1 = :LOC :RNUM = :LOC
END
TO ITEMLOC :ITEM
 OP ITEMLOC2 :ITEM :ITEMS
END
TO ITEMLOC2 :ITEM :I
 IF :I = [] OP 0
 IF LAST FIRST :I = :ITEM OP FIRST FIRST :I
 OP ITEMLOC2 :ITEM BF :I
END

TO PUTITEM :ITEM :LOC
 MAKE "ITEMS PUTITEM2 :ITEM :LOC :ITEMS
END
TO PUTITEM2 :ITEM :LOC :LIST
 IF :LIST = [] OP []
 IF LAST FIRST :LIST = :ITEM OP FPUT FPUT :LOC BF FIRST :LIST BF :LIST
 OP FPUT FIRST :LIST PUTITEM2 :ITEM :LOC BF :LIST
END
TO SEENO :I
 PR SE [I SEE NO] SE :I "HERE!
 CMD
END

TO IHAVE? :ITEM
 OP - 1 = ITEMLOC :ITEM
END

TO PERIOD :WORD
 OP WORD :WORD ".
END
TO WAVE :ITEM
 IF NOT IHAVE? :ITEM PR SE [YOU ARE HOLDING NO] PERIOD :ITEM CMD
 IF NOT :ITEM = "WAND NOTHING
 IF ALLOF NOT :RNUM = 4 NOT :RNUM = 5 PR [NOTHING HAPPENS.] CMD
 PR [POOF! THE SCENE CHANGES!]
 IF :RNUM = 4 MAKE "RNUM 5 ELSE MAKE "RNUM 4
 LOOK
END

TO FIX :ITEM
 IF IHAVE? :ITEM PR [YOU HAVE TO DROP IT TO FIX IT!] CMD
 IF NOT HERE? :ITEM SEENO :ITEM
 IF NOT :ITEM = "MACHINE PR [YOU CAN'T FIX THAT!] CMD
 IF NOT ITEMLOC "WAND = 0 PR [THE MACHINE IS NOT BROKEN!] CMD
 IF NOT IHAVE? "SCREWDRIVER PR [YOU DON'T HAVE THE PROPPER TOOLS TO] PR [FIX IT] CMD
 PR [YOU FIX THE MACHINE WITH YOUR TRUSTY]
 PR [SCREWDRIVER. UPON BEING FIXED, THE]
 PR [MACHINE STARTS UP AND PRODUCES A WAND!]
 PUTITEM "WAND 4
 CMD
END

TO EXAMINE :ITEM
 IF NOT HERE? :ITEM ( PR [I SEE NO] :ITEM [HERE!] ) CMD
 IF :ITEM = "WAND PR [IT BEARS A FADED INSCRIPTION:] PR ["WAVE ME AND YOU'LL BE GLAD."] CMD
 IF NOT :ITEM = "MACHINE PR SE [I SEE NOTHING SPECIAL ABOUT THE] PERIOD :ITEM CMD
 IF NOT 0 = ITEMLOC "WAND PR [IT SEEMS TO BEAR THE MARKS OF A HASTY] PR [REPAIR JOB.] CMD
 PR [IT IS BROKEN! YOU COULD FIX IT WITH]
 PR [THE RIGHT TOOL.]
 CMD
END

TO SCORE
 PR ( SE [YOUR SCORE IS] SCORE2 :ITEMS [POINTS.] )
 CMD
END
TO SCORE2 :LIST
 IF :LIST = [] OP 0
 IF NOT FIRST FIRST :LIST = - 1 OP SCORE2 BF :LIST
 OP ( ITEM 2 FIRST :LIST ) + SCORE2 BF :LIST
END
TO NOTHING
 PR [NOTHING HAPPENS.]
 CMD
END

TO GAME
 OP "ADVSAVE
END
TO DONE
 IF NOT :RNUM = 5 NOTHING
 LOCAL "SCORE MAKE "SCORE SCORE2 :ITEMS
 IF :SCORE = 0 NOTHING
 PR SE [YOUR SCORE IS] :SCORE
 IF :SCORE = 550 PR [PERFECT!] ELSE PRINT [THERE'S MORE TREASURE, THOUGH.] DONE
END


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK