Colin's Way Easy MOO Programming Examples

Version 1.0
February 1996

by Colin McCormick (a.k.a Snowfall)
cfm21@cam.ac.uk


0.0) Silly Foreword

This is a developing document and is likely to change at any time. It is intended for public distribution. If you have comments, questions or suggestions, please email me at colin@tripod.com. Enjoy your MOOing!

1.0) Introduction

This document is intended to be a series of examples of MOO programming projects, to help teach new programmers basic techniques and good style (what I call good, anyway). It is a sequel to Colin's Way Easy Guide to MOO Programming and those unfamiliar with MOO programming would do well to read that document before this one. 2.0) The Examples The first example (Rubber Bouncy Ball) is long-winded and proceeds step-by-step. Skip to the next example if you think you've got the basics down.

All examples will be outlined assuming that one is using a LambdaMOO-derived core database. If you're not sure where your database is from, it's probably LambdaMOO-derived, but you would do well to ask a wizard if the examples below don't work on your MOO.

To begin ...


EXAMPLE A: The Rubber Bouncy Ball

Let's build a rubber bouncy ball. The first thing we've got to do is figure out what we want to be able to do to the rubber bouncy ball, and how we want it to respond. What does one do to rubber bouncy balls in real life? One bounces them, clearly. So our rubber bouncy ball will have a verb called bounce, and we will eventually be able to type bounce ball and see something happen.

The problem is, what? Well, clearly the ball should at least tell us that it just bounced. And to be more interesting for other people, it should also tell everyone else in the room that it just bounced. We'll keep the complexity level there for the moment, since we can always add interesting other effects when we perfect this much.

Now that we've got a plan, let's make and describe the ball. The ball should essentially be a "thing" (as opposed to a container, or a note, etc. More on generics later.) This means we can use $thing as a parent, as one usually does by default. So type:

	@create $thing named "Rubber Bouncy Ball",ball
Remember, @create will create an object that is a child of its direct object argument (in this case, $thing) and name it its indirect object argument (Rubber Bouncy Ball,ball). If a comma (,) appears in the indirect object argument, the new object is named the string before the comma, and the string after the comma is added as an alias. (You may use here any number of strings separated by commas: the first string is always the name and the rest are all aliases.) If you want to use a name that has spaces in it, surround the entire name with double quotes (").

Remember to describe your ball:

	@describe ball as "This explosively red ball reminds you of
firetrucks.  It looks like you could bounce it."
(You can always change the description by typing @notedit ball.description.)

Okay, onto the bounce verb. To create the verb in the first place, you need to type:

	@verb ball:bounce this none none
This creates a verb on ball called bounce that expects only one argument, the name (or alias) of the object on which it is saved, i.e. ball (or Rubber bouncy ball.) Thus, we will be able to type bounce ball and this verb will run.

To start programming the verb, type:

	@edit ball:bounce
Note you are taken to the Verb Editor (see the Way Easy Guide for info on how to use the Verb Editor.) Recall we wish to have the ball tell both us and people in the room that it has just bounced. So the verb should look like:
	player:tell("You bounce ", this.name, ".  It's fun!");
	player.location:announce(player.name, " bounces ", this.name,".
		It looks like fun!");
The line player:tell(...); calls the verb tell on the object player, which in this as in every verb, is the person who called the verb (right now, that's you.) This is the most important verb you'll ever know: it's the way to make the MOO tell somebody (player) something (You bounce ... etc.)

The line player.location:announce(...); calls the verb announce on the room (.location) in which player (the bouncer) is standing. announce has the very useful feature that it tells its argument to everyone in the room except for the player who calls it (or who called the verb that calls it.) This avoids having the bouncer player see both

You bounce...
and
Colin bounces ... 
for instance. (See player.location:announce_all() and player.location:announce_all_but() for variants on :announce().)

Recall that this is the object on which the verb is saved (the ball.) Using this.name rather than just putting in the Rubber bouncy ball may seem silly now, but it allows you greater flexibility later; if you create a "pudgy baby" someday, you can copy your bounce verb directly to it without any changes. Flexibility is a good habit to get into.

We now need some refinements, because this is dull. Let's say that every time you bounce the ball, there's a 10% chance that you'll fail to catch it again. (Since the current version of bounce doesn't move the ball at all, and we assume the bouncer started with the ball in his/her possession, the implication is that you bounced it and caught it; i.e., it remains in your possession at the end of the verb's execution.) Additionally, if someone other than you bounces the ball, they've got a 20% chance of dropping it (it's your ball, after all.) So our new bounce verb might look like:

	player:tell("You bounce ", this.name, ".");
	player.location:announce(player.name, " bounces ", this.name, ".");
	drop_chance = random(10);
	if (player == this.owner && drop_chance <= 1)
		player:tell("You didn't catch ", this.name, "!");
		this:moveto(player.location);
	elseif (player != this.owner && drop_chance <= 2)
		player:tell("You didn't catch ", this.name, "!");
		this:moveto(player.location);
	else
	player:tell("You successfully catch ", this.name, ".");
	endif
This may look horrendously long, but there are only two new verbs in it: random() and this:moveto(). random(x) returns a random integer between 1 and x, and we will use this a lot. Every object has a verb :moveto() on it, and it is the proper way to move objects around -- if all the permissions check out (more on that later), this:moveto(Y) will move "this" (the ball) to Y, where Y is some object number (in this case, the room in which the player is standing, i.e. the floor.)

(Note also that we use an if-elseif-else-endif structure, as explained in the Way Easy Guide.)

If the person bouncing the ball is its owner (that's you) and our random number from 1 to 10 turns out to be 1, the bouncer will fail to catch the ball after bouncing, and it will "fall" into (be moved to) player.location. If someone other than the owner bounces the ball, this occurs for a random number of one or two.

Unfortunately, all this adds a lot of extra lines to the code, and we haven't even put in the lines to inform others in the room that the bouncer didn't catch the ball -- that would be two more lines! There must be a better way to organize the logic of the verb -- and that is precisely ...

Exercise 1

Rewrite the latest version of bounce so that it takes no more than 11 lines, including informing others in the room if the bouncer has dropped the ball. If you can do it in 9, you're excellent. If you can do it in fewer, you're better than I am.

The Moral

New Things

EXAMPLE 2: The Chalkboard

Next, we'll build a chalkboard. People will be able to write message on chalkboard and read chalkboard.

	@create $thing named Chalkboard,board
	@describe board as "Black, and covered in chalk dust."
	@verb board:write any on this
	@verb board:read this none none
The any argument, remember, will allow any string to be acceptable as an argument. Note, however, that if the string contains spaces it's best to surround it with double quotes ("). Thus
	write Hello down there! on board
will confuse the MOO, whereas
	write "Hello down there!" on board"
won't.

Right, we've got to store the text of the messages somewhere, and the best place (the only place) is in a property. We'll use a list of strings:

	@property board.messages {}
The list is obviously empty at the moment (no messages yet!)

We'll want to have write add a message to this list, and read will display them. So:

	@edit board:write

	new_message = dobjstr;
	this.messages = {@this.messages, new_message};
	player:tell("You write \"", new_message, "\" on ", this.name, ".");
	player.location:announce(player.name, " writes \"", new_message, "\" 
		on ", this.name, ".");
The '@' operator is kind of tricky. It can only be used in front of a list, and it "explodes" the list -- breaks it up into collection of its elements. The expression {@this.messages, new_message} first breaks up the current list of messages into a collection of messages, puts new_message (the new one) at the back of the collection, and then, because of the {} brackets around the whole thing, regroups them into a proper list. This is the standard method of adding an element to a list. (See also: setadd().) Now for read.
	@edit board:read

	player:tell("These messages are written on ", this.name, ":");
	for msg in (this.messages)
		player:tell(msg);
	endfor
	player.location:announce(player.name, " reads the messages on ",
		this.name, ".");

The use of for..endfor construction is outlined in the Way Easy Guide.

It might be nice to have the board record who wrote what. We will replace the first line of our current write verb with:

	new_message = dobjstr + " -- " + player.name ;
Strings may be added together (concatenated) with the plus (+) sign. This expression will set new_message equal to Hi folks! -- Colin, and that will be the message saved in the .messages property.

The board should be erasable, but not by everyone. Let's create an eraser object to go with it, and put an erase verb on it.

	@create $thing named Eraser
	@describe eraser as "This chalky blackness reminds you of school."
	@verb eraser:erase any with this
We're going to need to know the object number (#X) of the chalkboard in order to do this verb. That's no problem -- just type
	@contents me
This produces a list of the objects inside you (i.e. that you are holding) with their object numbers. Now we're ready. The eraser's erase verb might look like:
	@edit eraser:erase

	if (#X.location == player.location)
		#X.messages = {};
		player:tell("You erase ", #X.name, ".");
	else
		player:tell("But ", #X.name, " isn't here!");
	endif
This verb won't let you erase the board unless you're in the same room as it, which makes sense. And since the MOO won't know what erase chalkboard with eraser means unless you're holding the eraser, only the person with the eraser can erase the board.

There's another way to restrict erasing the board to people holding the eraser. It involves putting the erase verb on the chalkboar, and it is ...

Exercise 2

Redo the erase verb so that it is stored on the chalkboard rather than the eraser object. The syntax can now be simplified to erase board. The board's erase verb should check to see if the player is holding the eraser (it must know the eraser's number for this -- remember @contents) before it erases itself. Hint: all objects have a .contents property, that tells what they contain/are holding. This, plus the in test (see Way Easy Guide, should be about all you need.)

The Moral

New Things

EXAMPLE 3: Todd, The Talking Parrot

Enough of this inanimate stuff, let's build something alive! Next up on the example agenda is Todd, the incredible talking parrot. If you poke, prod, harrass or otherwise vex Todd, he'll say any one of a number of unhappy phrases. Do it enough, and he'll fly away. But if you pet, soothe or compliment Todd, he'll calm down and say something nice ... unless you do it too much!

	@create $thing named "Todd the talking parrot",todd
	@describe todd as "Astonishing.  Verbose.  Loud."
	@verb todd:poke this none none
	@addalias prod,harrass,vex to todd:poke
	@verb todd:pet this none none
	@addalias soothe,compliment this none none
The @addalias verb adds its direct object argument (with possible comma explosion, as in the bouncy ball example) as aliases to the verb named by its indirect object argument. Thus, typing poke todd and prod todd will call the same verb.

Todd's going to have a list of unhappy phrases he might say, a list of happy phrases he might say, and a "grumpiness" property that will record how ready he is to fly off. We'll use a list of strings for the phrases and an integer (starting with 0) for the grumpiness.

	@prop todd.unhappy_phrases {}
	@prop todd.happy_phrases {}
	@prop todd.grumpiness 0
@prop is equivalent to @property.

We'll want to add some phrases to Todd's vocabulary first, and we'll do this by using the Note Editor, which works in a similar way to the Verb Editor. (Type look while there to see a list of commands.) Each line we type in and save in the Note Editor will be recorded as one string in todd.phrases, making it easy to add new phrases to Todd's vocabulary.

	@notedit todd.unhappy_phrases
	...
	@notedit todd.happy_phrases
Now on to making Todd talk. His poke/prod/vex/harrass verb:
	player:tell("You ", verb, " ", this.name, "!");
	player.location:announce(player.name, " ", verb, "s ", this.name,
		"!");
	phrase = this.unhappy_phrases[random(length(this.unhappy_phrases))];
	player.location:announce_all(this.name, " squawks \"", phrase,
		"\".);
The variable verb is always the name of the verb that was used to call the verb; this is important here, because our verb has multiple aliases. If one poked Todd, it wouldn't be good to see
You prod Todd.
The expression length(this.phrases) returns the length (number of elements) of the property todd.phrases. Thus random(length(this.unhappy_phrases) gives us a random number between 1 and the number of unhappy phrases Todd has at his disposal, and we select one by this.unhappy_phrases[that number]. This allows us to add new phrases to Todd's vocabulary without having to change the poke/prod/vex/harrass verb itself. Note we use :announce_all() in the last line, so that the player calling the verb can also see what Todd says.

Now for his pet/soothe/compliment verb:

	player:tell("You ", verb, " Todd.");
	player.location:announce(player.name, " ", verb, "s ", this.name,
		".");
	phrase = this.happy_phrases[random(length(this.happy_phrases))];
	player.location:announce_all(this.name, " squawks \"", phrase, 
		"\".");
After this point, we'd like to make Todd get more or less grumpy, depending on whether he's being poked/etc or patted/etc. We'll add to the end of todd:poke:
	this.grumpiness = this.grumpiness + 1;
... and to the end of the todd:pat:
	this.grumpiness = this.grumpiness - 1;
These lines will increase and decrease Todd's grumpiness level. This should have some effect. If Todd's grumpiness ever gets over 10, say, he'll fly off in a rage. Plus, if he's petted too much, he'll also fly off into a rage. Rather than writing "flying off in a rage" code into both the poke/etc and the pet/etc verbs, let's make just one verb, fly_off_in_rage(), which we can call from either poke/etc or pet/etc.:
	@verb todd:fly_off_in_rage this none this
We've used a strange set of arguments for this verb. That's okay, because we don't intend to call it from the command line; instead, it's to be called from within the poke/etc and prod/etc verbs. Setting the arguments to this none this makes the verb executable, which allows it to be called from within a verb, rather than just from the command line. (See verb_info().) To the end of todd:poke, we'll add:
	if (this.grumpiness >= 10)
		this:fly_off_in_rage();
	endif
To the end of todd:pet, we'll add:
	if (this.grumpiness <= -10)
		this:fly_off_in_rage();
	endif
We've just called the :fly_off_in_rage() verb from within the poke or the prod verb. We pass it no arguments (the parentheses () are empty) although we could if we needed to (we don't in this case.) :fly_off_in_rage() might look like:
	player:tell(this.name, " gets very upset with you, and flies off 
		in a rage!");
	player.location:announce(this.name, " gets very upset with ",
		player.name, " and flies off in a rage!");
	this:moveto(#-1);
	fork(10)
		player.location:announce_all(this.name, " returns,
			looking calmer.");
		this.grumpiness = 0;
	endfork
All kinds of crazy stuff going on in here. First of all, we move Todd the parrot #-1: a very strange location; #-1 is sometimes called "nowhere" or "The Void". It's not a "real" location in the virtual reality, but it's a useful place to put things that you don't want lying around. Todd storms off to "nowhere" so that you can't see him anymore in the room. However, we fork off a set of our code to be executed 10 seconds later (see the Way Easy Guide) so that Todd will return, calmer, in 10 seconds.

Exercise 3

With a careful use of fork() and random(), one can imitate spontaneous animal actions. Give Todd a :say_something_random() verb that has a random chance of being called from within his poke/etc and pet/etc verbs a random number of seconds after Todd is poked/etc or petted/etc. This verb will make Todd "spontaneously" say something a while after he's been poked/petted/etc.

The Moral

New Things

Further Example Ideas

The rest of the examples will be done as essentially no more than suggestions of things to build, with a brief discussion of possibilities for how to program them. Let me stress that experimentation is the key to learning MOO code -- there's almost nothing you can do that will damage the MOO, and if you wind up destroying some of your own objects (or yourself, though that is very tough to do) your wizards should be able to put things right in short order.

Ideas

Some last ideas ...

The End

Well, that's about it. Suggestions and comments are welcome -- write to me at colin@tripod.com. Enjoy programming!

Back to HereMOO