[ Updated by ChupChup for the release of TinyMUCK 2.2. ]
Zen in the Art of the Towers of Hanoi (or The Basics of MUF in 10 megs or less.)
This is an introduction to MUF, a dialect/subset of forth used to do really really neat things with TinyMuck 2.2. This intro was designed to be read before any of the other MUF information; it (hopefully) should supply you with a fair idea of what MUF is all about. It's written at a non-programmingstud level, all the better for understanding.
All MUF programs work by performing operations on a stack.
For all you non-programmer types, a stack is just a tool used to store information. Information is manipulated by "pushing" things onto the stack and "popping" things off. The last thing you've placed on a stack is always the next thing you would take off if you tried; it's like piling objects on top of each other, when the only thing you can remove >from the pile is the thing on top. For example, if you were to push the number 23, then push the number 42, and then pop a value off the stack, you would get 42. If you were to pop another value off the stack after this, you would get 23. If you were to try and pop another value, you would get an error, since the stack would now be empty. (This is a "stack underflow.")
The basic procedural unit in MUF is called the word. A word is simply a sequence of instructions. In program text, a word always starts with a colon, then the word's name. A semicolon marks the end of a word. For example:
(text of word here)
would define a word called detonate_explosives.
Parentheses are used to delineate program comments; everything inside comments is ignored by the computer. The detonate_explosives word above, if run as shown, would do absolutely nothing.
Indentation in MUF is arbitrary and serves to make the program more readable to people.
In MUF, there are three types of constant values: integers, strings, and database references. Each of these types is stored and retrieved from the stack as a single unit (The string "Hello, Sailor!", for example, would be stored on the stack in its entirety: "Hello, Sailor!"; it would not be stored byte-by-byte or word-by-word or in any other such silly way.) To push a constant onto the stack, you only need to state its value. The following is a completely legitimate procedure:
"Old Man" "I'm" 37 "What?" 37 "Not old!"
However, run by itself, it wouldn't do anything visible to the user. It would, however, create a stack which looks like this:
("Old Man" "I'm" 37 "What?" 37 "Not old!")
In the above stack, "Old Man" is the value on the bottom. "Not old!" is the value on top of the stack, and would be the next value retrieved.
Placement of values on the same line of a program is arbitrary. Since each value represents something being put on a stack, the word
"Semprini?" "All right, wot's all this then!"
is the same as:
"Semprini?" "All right, wot's all this then!" ;
Functions which are available in the standard MUF library take values from the top of the stack, do things with them, and usually leave something new back on top of the stack. The words +, -, swap, pop, and random provide good examples of this and are discussed here.
The + routine takes the top two integers from the stack, adds them together, and leaves the result on top of the stack. In order to easily describe what functions like this do, a certain stack notation is used: for +, this would be (i1 i2 -- z). What's inside those parenthesis is a sort of "Before and After" synopsis; the things to the left of the double-dash are the "before", and those to the right are the "after". (i1 i2 -- i) says that the function in question takes two integers away and leaves one. The letters used here to tell what kind of data a stack object can be are: i for integer, d for database object, s for string, v for variable, and x or y to mean something that can be more than one type.
Here are short descriptions of the procedures listed above so you can get the hang of how they work:
+ (i1 i2 -- i)
Adds i1 and i2 together. The word
2 3 +
will return 5. The word
2 3 4 5 + + +
will return 14. When add_some_more_stuff first reaches the "+ + +" line, the stack looks like:
(2 3 4 5).
The first + changes the stack to look like:
(2 3 9).
The next causes:
The final plus returns:
10 7 -
will return 3 on top of the stack.
swap (x y -- y x)
Switches the top two things on the stack. This is useful for when you want to know the value of x but want to save y for later use.
1 5 2 swap 3 "Three, sir!" swap "Boom!"
will, before it gets to the first swap, create a stack of (1 5 2). After the swap, the stack looks like (1 2 5). It then accumulates another 3 and a string constant, to look like (1 2 5 3 "Three, sir!") It swaps the last two again and adds another string, so the stack looks like: (1 2 5 "Three, sir!" 3 "Boom!").
pop (x --)
Throws away the value on top of the stack. As shown in the stack diagram, it returns nothing but takes something, and so decreases the stack's total size. Useful when you really really want to get to the next thing on the stack so bad you don't care what's on top. The word:
"Immanuel Kant" "Heideggar" pop "David Hume" "Schoppenhauer" "Hegel" pop pop
would leave the stack looking like ("Immanuel Kant" "David Hume").
random (-- i)
Doesn't even look at the stack, but piles a really really random integer on top. The word:
random random random
would return a stack of three random numbers.
Because of the way the stack works, variables aren't as necessary in MUF as they are in other languages, but they can be used to simplify stack-handling operations. To declare a variable, you simply add the line "var <name>" at the beginning of your program. Variables are of no specific type; a variable which holds an integer can turn around the next second and hold a string if it's feeling haughty enough.
The following words are important when dealing with variables:
! (x v --)
Set variable v to hold value x. The program:
6 9 * answer !
will give the variable "answer" the value 42. This is the same as:
6 9 * answer !
@ (v -- x)
This word (pronounced "fetch") retrieves the value of a variable and puts it on the stack. You should remember this since a common mistake among beginning MUF programmers is to forget to put fetch symbols in their programs. The word
by itself stands for the variable "garply", while the expression
stands for the value of that same variable. If you're familiar with Lisp, this is analogous to the difference between garply and (garply).
10 biggles ! 24 fang ! biggles @ fang @ +
will return the value 34 on top of the stack. The program:
10 biggles ! 24 fang ! biggles fang +
is *wrong*. For reasons I won't go into now, since this guide was written at the last moment and at great expense, the above word will return the value 7 on top of the stack.
In MUF, there are two variables which are predefined and available for use at all times. These variables are "me" and "loc", where "me" holds the player's database reference, and loc holds the player's location's database reference.
(Database references were mentioned before as the third type of constant, then sort of ignored till now. For the sake of completeness, I will introduce the word
dbref (i -- d)
Where i is an integer and d is a database reference, dbref converts between the two types. The line
will return item #2032 in the Muck database. This is useful since there are lots of functions that operate on database references that won't work on integers. [If you want to declare something in one of your programs as being a dbref instead of an integer, you should just put a # in front. For example, 69 means the integer 69, while #69 means object number 69. You could say '69 dbref' instead of '#69', but it would be a little slower and a little harder to read.]
Me @ will return the player's item reference, while loc @ will return the room they are in. Trigger @ returns the item that triggered the current program, whether it is a player, exit, room, whatever. A useful word to know is:
name (d -- s)
Where d is a db reference and s is a string, name returns the name of item x.
Now that you know about me @, another Muck function becomes useful. Its synopsis is:
notify (d s --)
When d is a player, notify prints string s out to user d. The program
me @ "There is someone behind the next column waiting to jump you." notify
would print said message on the user's screen.
Before you can really start writing neat stuff in Muck, there are two more things you should know about. One is = and the other is the "if/then" setup.
2 3 =
If/then could be written up with a synopsis, but it would be sort of complicated and probably a lie also. The way it works is this: If pulls an item off the stack. If the item is 0, it skips over the program after the IF and resumes execution at the THEN. If the item is not 0, the program will execute everything in between.
The naming of this construction as if/then can be somewhat confusing. It
certainly doesn't work quite like the if/then of normal languages, and the
THEN actually being called THEN is sort of confusing. As nearly as I can
tell, if/then is a sort of forth-creators' joke. It does not mean
"IF the previous is true THEN do this." like it does in most languages.
Rather, it means "IF the previous is true do this; THEN go on with the rest
of the program." Remarkably silly.
2 3 = if me @ "Your computer is broken!" notify then me @ "Done executing this word." notify
will always print "Done executing this word." to the user, and will print "Your computer is broken!" if something is really screwy with the math and it actually thinks 2 = 3. Getting a bit more sophisticated, one can write something like:
"Your computer works fine." 2 3 = if pop "Your computer is broken. Sorry. Truth hurts." then me @ swap notify
When word_up is called, "Your computer works fine." gets put on the stack. If your computer actually works, 2 is *not* equal to 3, so that right after the = the stack looks like:
("Your computer works fine." 0)
The IF reads the 0 and skips all the way down to the THEN. The SWAP in the last line is used since the NOTIFY word wants its parameters in the opposite order of where they would be.
If your computer is broken, right after the =, the stack looks like:
("Your computer works fine." 1)
The IF reads this 1 and decides to keep executing. It then gets to the POP which gets rid of the filthy lie about well-working computers and replaces it with the painful truth.
Ok, so you've been reading this whole thing so far, and you really want to use this stuff to do something interesting. The following program does something interesting, and uses the function
strcat (s1 s2 -- s)
Concatenate strings s1 and s2, returning the result.
it also uses
location (d -- d')
Takes db reference d and returns d', the db reference for its location.
dup (x -- x x)
Duplicate the top of the stack.
dbcmp (d1 d2 -- )
Works just like =, except operates on db references instead of integers.
#2032 (2032 is Celia's object number. ) dup (Make 2 copies; we're about to use 1. ) name (Celia might change her name in the future, so) (instead of using "Celia" here we just look up) (her name. ) " is currently in " strcat (Attach name to sentence so far ) swap (Flip the sentence back so we can get at) (Celia's dbref again. ) (Celia's dbref is now at top of stack. ) location (Where is Celia? ) name (What is the name of the place she is in?) "." strcat strcat me @ swap notify (Tell the player where Celia is.) #2055 (Celia's hardsuit is #2055. ) location #2032 (Celia again ) dbcmp (Has she got her hardsuit with her?) if me @ "Watch out-- she's wearing her hardsuit!" notify then
Note that this program uses no variables (except for the universally defined ME variable.)
In Muck, this program would be attached to, say, a homing device or a magic staff. Now, if Boomer ever wants to find Celia, he can, and he'll even know if she's defenseless or she's got her armor.
Without the comments and spaced out like you might see normally, this program looks like:
#2032 dup name " is currently in " strcat swap location name "." strcat strcat (Now we know where she is.) me @ swap notify #2055 location #2032 dbcmp if me @ "Watch out-- she's wearing her hardsuit!" notify then
Words can also be called by other words; to do this, you treat your other words just like library functions when you use them. When you have more than one word in the same program, the word which is listed *last* is the one executed, and all the ones listed before it are subroutines. The above program could be rewritten:
celia-identity dup name " is currently in " strcat swap location name "." strcat strcat (Now we know where she is.) me @ swap notify #2055 location celia-identity dbcmp (Using celia-identity and spacing the ) (commands like this makes this bit a ) (little easier to understand. ) if me @ "Watch out-- she's wearing her hardsuit!" notify then
Oodles and oodles of other neat MUF library routines are available, too numerous to be detailed in an introduction such as this. A complete list, as well as sample code, is available from such spiffy ftp sites as prince.white.toronto.edu and belch.berkeley.edu.
If you're interested in seeing more sample code, write to me for program listings for the Pan Galactic Gargle Blaster, walkie-talkies, and several useful library routines.
Stinglai "Two Sheds" Ka'abi
Mail to: email@example.com
"We are too proud to fight." --Woodrow Wilson 1856-1924 "Violence never settles anything." --Genghis Khan 1162-1227 "The mice voted to bell the cat." --Aesop c. 620-c. 560 B.C.
Coming next week:
"Life, the Universe, and INTERCAL:" A most excellent introduction to that programming language.