The MUF Tutorial, by Scotfox.
Revision 2.2fb5 of 16 April 1994.
"Zen and the Art of Putting Things on Top of Other Things."
(Or, the Basics of MUF in ten megs or less.)
This is an introduction to MUF, a simple and easy programming language used to do really neat things with TinyMUCK 2.2. MUF is based on Forth.
This tutorial was designed to be read before any of the other MUF documentation. I'll assume you already know how to play TinyMUD games pretty well (you know what an object number is, you know how to @set things, etc.) and that you know how to program in at least one computer language (even BASIC will do).
Before we go on, let me show you a perfectly normal MUF program:
When you @link an action to this and use the action, it will print "Hello, world!" to your screen. Easy as pie! It'll look easier later.: hello-world (the name of the program) me @ (first argument to the function 'notify') "Hello, world!" (second argument) notify (a function which takes two arguments) ;
In order to try any of these examples on a real TinyMUCK, you must be allowed to program on it. Most mucks implement "mucker bits", which means that until a wizard gives you one, you can't create programs. Talk to your friendly neighborhood wizards to get a mucker bit. Usually they'll have some sort of policy about what sorts of programs you are allowed to write. Common restrictions are that you can't write a program to teleport yourself around the muck, spy on other characters, or do things that only wizards can do.
Before we get to the good parts, let's give you an overview of what MUF is all about. (Relax. This is easy stuff.)
A stack is just a place to store things. You put things on top of a stack one at a time (we call that "push"), and take them off the top of the stack (that's called "pop"). The most recent thing you've pushed onto a stack is the only thing you can pop; 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 12, then push the number 34, and then pop a value off the stack, you would get 34. If you were to pop another value off the stack after this, you would get 12. You can't pop anything else off then, because the stack is now empty.
There is only one stack. Things that get put onto it get left there until they get taken away, or until your program ends.
A typical stack could be represented by
which means that the MUD object #4567 is on top of the stack, the integer 123 is below it, and the string "frotz" is on the bottom of the stack. The only thing you can get to right now is #4567, but when you take that off you can get 123, and when you take that off, you can get "frotz".( "frotz" 123 #4567 )
See? Easy. And you used to think MUF was only for the elite!
would define a word named detonate_explosives.: detonate_explosives (the program text goes here) ;
Always remember to put a space between a colon and the name of a word! Everybody (and I do mean Everybody) forgets the space sometimes. If the program above began with ":detonate_explosives", it wouldn't work at all.
Parentheses are used to set aside comments. Everything inside parentheses is ignored by the muck. Thus the detonate_explosives word above is a perfectly valid program, but if run as shown it would do absolutely nothing, because all it contains is a comment -- it doesn't produce any output or modify the stack in any way.
In MUF, there are three types of constant values (things you can put on the stack):
If you were to run this word by itself, you wouldn't see anything happening. It would, however, create a stack which looks like this:: a-visit-to-the-zoo "Wolves" "Lynxes" "Bats" "Foxes" ;
In the above stack, "Wolves" is the value on the bottom. "Foxes" is the value on top of the stack, and would be the next value retrieved by any stack operations.( "Wolves" "Lynxes" "Bats" "Foxes" )
Indentation and line breaks in MUF programs are arbitrary and just make the program more readable to people. You could put each MUF operator on a separate line, or the whole program on one line. Your style is your choice. Thus a-visit-to-the-zoo could be written as follows:
But endlessly pushing values onto the stack is boring. Fortunately, there are 'operators' in MUF that can modify the stack: typically they take some values off the top of the stack, do some sort of calculation with these values, then put a new value on top of the stack.: a-visit-to-the-zoo "Foxes" "Bats" "Lynxes" "Wolves" ;
The "+" operator 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 -- i). What's inside those parentheses 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 uses two integers, and leaves one when it's done ("i" means "integer").
Notice that math in MUF works in reverse Polish notation, where you'd add two and two by saying "2 2 +". (This is the same way a lot of expensive HP calculators do it.)
The letters used here to tell what kind of data a stack object can be are:
Here are short descriptions of the procedures listed above so you can get the hang of how they work:
(i1 i2 -- i)
: add_some_stuff 2 3 + ;will leave the integer 5 on the stack when it is finished. The word
: add_some_more_stuff 2 3 4 5 + + + ;will put 14 onto the stack. Right before "add_some_more_stuff" reaches the "+ + +" line, the stack looks like (2 3 4 5). The first + changes the stack to look like (2 3 9). The next makes it (2 12), and the final plus leaves (14).
(i1 i2 -- i)
: subtract_arbitrary_things 10 7 - ;will put the number 3 on top of the stack.
(x y -- y x)
: swap_stuff_around 1 5 2 (The stack is now 1 5 2) swap (Now the stack is 1 2 5) 3 "Three, sir!" (Now it's 1 2 5 3 "Three, sir!") swap "Boom!" (And now it's 1 2 5 "Three, sir!" 3 "Boom!") ;(`rot' and `pick' let you get at items deeper in the stack. Look them up in your MUF manual.)
(x --)
: needless_popping_waste "Immanuel Kant" "Heideggar" pop "David Hume" "Schoppenhauer" "Hegel" pop pop ;would leave the stack looking like ("Immanuel Kant" "David Hume").
(-- i)
: feel_lucky_punk? random random random ;would put three random numbers onto the stack. Often you'll need a andom number in a certain range; for instance, rolling dice will get you numbers from 1 to 6. Here's how you can do that:
: roll-die random (fetch a random number) 6 % (% is the 'modulo' operator; random mod 6 gets you a number between 0 and 5) 1 + (add 1 to it to get a number between 1 and 6) ;
In MUCK, you create a "program" object with the @program command. For example, "@prog test.muf" will create an object named test.muf that will contain whatever MUF program you write, and you'll be put into the built-in editor so you can type your code. (More details on this later.) You'll usually put several words into a program, then link an action (also known as an "exit") to the program. Using the action will run the very last word you defined in your program (sometimes people call it "main" for emphasis, but you don't have to). This "last word" can use words that you defined earlier in your program, as described a few paragraphs down.
Another other way that MUF programs can be used is in the desc, succ, fail, and drop properties of a MUCK object. If you write a program whose program object number is #6800, say, and you describe yourself as:
@desc me = @6800
then whoever looks at you would run the program. (Yes, that's an
at-sign, not a pound-sign.)
Similarly, "@failing an_object = @6800" would
run the program whenever someone tried but failed to pick up the object,
and so forth. (This doesn't work in ofails, osuccs, or odrops.)
Theoretically then you could get away with only having one word (a "word" is a "subroutine" or a "procedure", remember) in your program, but you'll find it's handy to break whatever the program does into a few smaller subtasks, each of which you'll write a separate word to handle.
You've seen how the "+" operator takes two integers and puts their sum on the stack. Well, if you find yourself adding three integers a lot, you could write a word to handle it:
: add-three (i1 i2 i3 -- i) + + ;Pretty simple, but hey, it's an example. Notice that your new word expects some values to be on the stack before it begins? Well, if your 'main word' goes like this:
: frobnitz ... add-three ... ;when it comes to the "add-three" operator, it'll give the stack (remember, there's only one stack!) to the "add-three" word. "add-three" will modify it and give it back to "frobnitz", which picks up where it left off. Thus before the call to "add-three", the stack might look like ("Hey, you!" 5 4 "What?" 3 2 1), and after it returns from the call to "add-three", the stack would be ("Hey, you!" 5 4 "What?" 6).
Thus you can see how "taking arguments" in MUF means "grabbing values from the top of the stack", and "returning a value" means "leaving things on top of the stack when you're through".
The only other important thing to mention at this point is that you can have your programs take arguments. If you create a program named "frotz.muf", then link the action "frotz" to it, typing "frotz the troll" will run the program as usual, except that the stack will start off containing the string "the troll" rather than being empty. Think about this for a bit; it's how the popular custom 'page' program works, for example. If a program is called from an action, it will always be given a string on top of the stack to begin with; even if here you just typed "frotz" alone, the string "" (an empty string) would be placed on top of the stack for "frotz.muf" to handle.
If you use the program in a desc, succ, fail, or drop property, then whatever follows the program number will be put onto the stack:
@desc me = @6800 Your description here...
would run the program #6800 with
"Your description here..." on the
stack to start out with every time someone looked at you. This is how
look-notify, multiline-description,
morpher, and other programs are done.
Now that we have the basics down, let's do something useful!
There are two kinds of variables:
For technical reasons, you should always use local variables instead of global ones. Just put 'lvar <name>' into your program before the first use of the variable, then any word in that specific program object can use it.
- global
- Defined with 'var <name>'.
- local
- Defined with 'lvar <name>'
Variables are of no specific type; that is, you can assign an integer to a variable at one point in your code, then assign a string to the same variable a few lines later.
The following operators are important when dealing with variables:
(x v --)
lvar answer : multiply-and-store 6 9 * answer ! ;will put the value 42 into the variable "answer", which can then be used anywhere in the program (that is, in any word in the same program object).
(v -- x)
(The difference between "x" and "x @" is like the difference between "x" and "^x" in Pascal, "x" and "*x" in C, and "x" and "(x)" in Lisp. If that last sentence doesn't mean anything to you, then ignore it.)
The program:
var ren var stimpy : more_silly_manipulation 1 ren ! 2 stimpy ! ren @ stimpy @ + ;will return the value 3 on top of the stack. But if you make a mistake and enter the last line as:
ren stimpy +then that will be *wrong*, and will give you a completely wrong number.
In MUF, there are a few variables which are predefined and available for use at all times (without requiring "var" declarations): You'll probably use two of them a lot: "me @" will return the object number of the player who is using this MUF program, while "loc @" will return the object number of the room he is in.
(Object numbers, also known as database references or dbrefs, are the third kind of constant value after integers and strings. The value #123 is a dbref. You can convert integers to dbrefs with the "dbref (i -- d)" operator; saying "123 dbref" is the same thing as saying "#123".)
Another useful word to know is:
(d -- s)
And now that you know all about "me @", another MUF function becomes useful. Its synopsis is:
(d s --)
: whoami me @ (my dbref) "Your name is " me @ name (my name as a string) strcat (concatenate "Your name is" and my name into one string) notify ;would print "Your name is Joe" on a line by itself to my screen. (Again, on a Fuzzball MUCK, having only Mucker level 1 permission means that you can only 'notify' a person in the same room as you.)
Hurrah! Finally we did a program that begins to do something useful!
(i1 i2 -- i)
: nonequals 2 3 = ;returns 0. In MUF, any nonzero integer means "true", while zero means "false". See dbcmp to compare database references.
(i --)
( --)
"If/then" isn't hard to figure out if you keep in mind that MUF does everything backwards compared to "normal" languages such as C or Pascal. Remember that, in other languages, if/then looks like this:
MUF does it like this:if <test> then <statement> <program continues...>
"if" takes the top item off the stack; if it is zero or the empty string "", then everything up to the word "then" is ignored. Anything else makes it keep going.<test> if <statement> then <program continues...>
For example, the word:
: test-computer 2 3 = if me @ "Your computer is broken!" notify then me @ "Finished the test." notify ;will first test if 2 = 3; if so, the = operator will leave a 1 on top of the stack. "if" will grab this 1 and then print "Your computer is broken!" to you. If, however, 2 is not equal to 3, the = will leave a 0 on top of the stack to indicate falsehood, and the "if" will grab the 0 and skip the bit about broken computers. In either case, the code will finish by printing "Finished the test" to you.
If you feel like getting fancier, you could use "else" too. Remember that procedural languages have it this way:
MUF does it like this:if <test> then <statement> else <other-statement> <rest-of-program>
which may look a bit odd, but hey, it works (and it makes sense, if you think about it). Now you can do:<test> if <statement> else <other-statement> then <rest-of-program>
: better-test 2 3 = if me @ "Your computer is broken!" notify else me @ "Your computer works just fine." notify then me @ "Finished the test." notify ;And if you're really smooth, you might notice that MUF has nothing like the "case" or "switch" statement of other languages to decide between plenty of choices, so you can simulate it with lots of 'if/else/then's. Here's how it works, with two new MUF operators and a Ginsu knife tossed in for a limited time only:
(s1 s2 -- s)
(x -- x x)
: roll-die me @ "You rolled a " random 6 % 1 + (Get a random number mod 6 and add 1) (to get a number in the range 1-6.) dup 1 = if (We have to duplicate the number, because = eats it.) "one!" else dup 2 = if "two!" else dup 3 = if "three!" else dup 4 = if "four!" else dup 5 = if "five!" else "six!" then then then then then (we always need a 'then' for every 'if') (The stack now looks like:) (#123 "You rolled a " 1 "one!") (for example) swap pop (get rid of the integer in there) strcat notify (put "You rolled a " and "one!" together,) (then tell me what I rolled) ;
(d -- d')
(d -- d')
(d1 d2 -- )
: find #123 (Substitute here the number of whatever object you want to find.) dup (Make a copy of the dbref; we're about to use the copy.) name (Fetch the object's name -- better than hardcoding it here!) " is currently in: " strcat (If #123 is "foo", we now have the stack:) (#123 "foo is currently in: ") swap (Bring the dbref back to the top of the stack.) location (Where is it?) dup name (Make two copies of that room number,) (and turn one into the name of the room.) (If it's in a bar, #456, we have:) ("foo is currently in: " #456 "bar") (Now to find out who owns the room.) swap (Bring the room number to the top of the stack.) " (" swap (Put an open-paren before the room number.) owner (Turn the room number into the number of its owner.) name (... and get the owner's name.) "'s room)." (If Baz owns the room, the stack is now:) ("foo is currently in: " "bar" " (" "Baz" "'s room.") strcat strcat strcat strcat (Turn it all into one string.) me @ (We're going to tell me where it is...) swap (... but "notify" wants my dbref to come before the string.) notify (And there it is!) ;Note that this program uses no variables (except for the universally defined "me" variable.)
Want to try it out? Type "@prog find.muf", then type "i" to enter insert mode, and type in the program just as you see it here. (You can leave out the comments!) Type a period on a line by itself to exit insert mode. Then make sure it's entered correctly.
Commands you can use in the editor:
<from> <to> list
<from> <to> delete
<line> insert
When it compiles fine, type "q" to quit the editor. (You can modify the program later with "@edit find.muf".)
Create an action on yourself so you can always find whatever it is whose number you put into the program. First create the action with "@action find = me", then "@link find = find.muf". Now, whenever you want to know where that thing is, just type "find"!
If you want to see your program actually going every step of the way, then "@set find.muf = D". The D flag for programs means DEBUG, and the top of the stack will be shown to you as each operator in the program is executed. Just remember to set it !D when you're finished debugging!
If you want to view your program without having to enter the editor, just @list it. To let other people @list your program too, @set it = L (which stands for LIST_OK). Also, your programs must be set L for other people to be able to use them.
When someone uses a program, the program has all the privileges of that person -- in other words, it can modify that person's objects, but not the objects of someone else unless a wizard is the person using the program. If your program needs to modify your own objects even when another person is using it, then you'll need to let other people run your program as you -- @set the program SETUID, which gets its name from the Unix `setuid' bit on executable files.
Good luck, and happy muffing!
[ More information on Forth can be found in the comp.lang.forth Forth FAQ. ]