LambaMOO Programming Tutorial (very rough draft)

by Steven J. Owens (unless otherwise attributed)

MOO, of which LambaMOO is the first and biggest, is an interactive, multiplayer online game that includes the ability for players to build and even program new things or features into the game. The language lacks a formal name but is generally referred to as "moo code" or sometimes "moocode".

While there is a fair amount of information about LambdaMOO programming in various places, there is also a lack of a comprehensive explanation, especially of much of the programming environment. These incomplete notes are the start of an attempt to remedy that. This is just a beginning, but maybe someday it'll be an end.

Outline of Current Draft

Note: See end for draft outline of planned revision

Basic Terminology

I'm going to asume you have a basic familiarity with programming concepts, but don't get too stressed out if you don't, you'll be able to pick that up as you go along. If you're totally, completely new at this, I explain some general concepts over in this article here:

Real Basics of Programming In Java

It explains things in terms of the java programming language, some some of the syntax details will be different than for MOO, but it gets the general concepts over.

I'm going to assume you have a basic familiarity with moo concepts, but just to be clear:

You have the server and the database (colloquially "db"). The server is the foundation; it loads and runs the database, which defines the objects and their properties and verbs. Much of the code that makes the up the basic moo-ishness of things is implemented in the db, in moocode.

Note: Most people tend to associate the term "database" with a "relational database", and most relational database programs tend to keep most of their data in disk storage. The purely technical meaning of "database" is "an organized collection of information." LambdaMOO's database is not relational, it is an object database, and it is kept entirely in-memory.

MOO consists of objects. MOO objects have properties (values stored on the object and referenced in code via object.propertyname) and verbs (code stored on the object and invoked in code via object:verbname()).

The "core db" is the infrastructure that most moos start from, the initial set of objects, verbs and properties that provide handy utilities and predefined types of objects. These are all that exists when the MOO is first started up, until the users start adding custom code.

There are, generally speaking, two different versions of the core db in use. The minimal db, which is just about exactly what the name suggest, just the bare bones minimum of objects to get your MOO up and running, and the lambdacore, which has a whole bunch of additional objects and features that the lambdamoo wizards found useful.

The moo language has a number of predefined functions, which are generally referred to as functions or sometimes as "built-in functions" or "built-ins" for short.

In addition to the built-ins, there are a large number of handy little utility verbs. Since verbs have to be defined on objects, these utility verbs are organized on several objects, one object per category of verbs. They are generally referred to as "the utils" or sometimes "the $utils", because the individual grouping objects tend to be kept track of in properties on the #0 object, and you can refer to these properties by using $propertyname, like $string_utils, or $list_utils.

Speaking of object #0, object #0 is The System Object. This is used as a central place to stick values that the wizards want to keep track of on a moo wide basis, in properties on #0. You can reference these properties with #0.propertyname, but there's a shortcut, as I used above, the dollar sign, $, so you can reference the properties in your code by simply using $propertyname.

Anytime you see $ in a moo verb, mentally translate it to "#0.", i.e.:

$string_utils:english_ordinal

is really

#0.string_utils:english_ordinal

There are a great many such useful properties on #0 besides the utils, but here are the utils defined on LambdaMOO's #0 as of this writing:

There are also several verbs defined directly on #0, but most of them are system related and are not particularly relevant to your average moo coder.

Object #1 is The Root Class. All objects are descended from Object #1.

Object #-1 is "nothing" , the null object, the object that does not exist. Things that are not located anywhere in particular have their location property set to #-1. Verbs or functions that do not come up with a valid result typically return the value #-1.

There's actually a root property, $nothing, that contains the value #-1, but I'm pretty sure that $nothing does not define #-1, it merely points to it and gives you a more expressive way to use the value #-1.

Verb Invocation and Property Access

You invoke verbs with the ":" operator.

foo = object:methodname() ;

You reference properties to get the values in them with the "." operator.

foo = object.propertyname ;

You can generally chain properties for verb or property reference, assuming that each link in the chain is a valid object reference, e.g.:

player.foo.bar:xyzzy()

Lists

You define a list by enclosing a comma-separated series of elements in curly braces

foo = {"a", "b", "c"} ;

You reference a specific element in a list by using square braces and an index:

xyzzy = foo[2] ;

The trick with lists is not the syntax, it's the way lists are used in day-to-day MOO coding.

foo = {1, 2, 3}  ;
bar = {4, 5} ;
baz = {@foo, @bar} ;

baz will now contain {1, 2, 3, 4 5} ;

While you only use { and } when you're creating a list, and @ when you expand a list, the "moo way" is that you do a whole lot of list creation and expansion. The "right" way to append to a list is:

baz = {@baz, 6} ;

Other than that, you use [] for array indexing, like normal.

Eval

Eval is a really, really handy command that basically allows you to write little one-line MOOCode programs and execute them. It works just the same as say or emote, e.g.

Enter: "foo<enter>
See: You say, "Foo"

Enter: :grins toothily<enter>
See: Puff grins toothily.

Enter: ;1+1<enter>
See: =>2

Eval is handy for exploring and experimenting, and also really useful for setting property values on your own objects and directly invoking verbs.

One of the first property values you should set with eval is your eval_env property. This may already be set for you, to check type the following:

;player.eval_env<enter>

You should see:

=> "here=player.location;me=player;"

If it's not set, type the following:

;player.eval_env="here=player.location;me=player;"<enter>

Once you execute this command, you'll be able to use the eval shortcuts "me" for your player object, and "here" for your current location. This turns out to be incredibly handy.

There's also a nifty little property, player.eval_ticks. See the section below "Threads, Ticks and Tasks" for more on that.

I never really got in the habit of using multi-statement evals. "Help eval" will explain how it works. You start a multi-line eval with a double-semicolon, i.e.:

;;foo = {1, 2, 3}; bar = {4, 5};  for x in ({@foo, @bar}) me:tell(x) ; endfor<enter>

Also note that multi-line evals don't automatically print the return value of the statement, so you need to explicitly print whatever you want to see in the body of your eval statement. Generally I find when I hit that point that it's simpler/easier to just create a real verb to mess with.

Basic MOOCode Gotchas

Comments

Effectively, MOOCode doesn't have comments. MOO actually has comments, but nobody uses them and nobody, AFAIK, ever has. I can't even remember what the comment character is - // maybe?

The problem is, early MOO was designed with the idea that the users would store/maintain their own source code, and thus the compiler discarded comments, didn't bother to save them in the compiled verb bytes. When users used the in-server editing tools, which decompiled the code for the user to edit it, presto, the comments were gone. (This compiling/decompiling also lead to the happy accident that code indenting/formatting is defacto specified by the decompiler.)

As a result of this, the custom of using string literals for comments arose. E.g.:

"this is a string literal" ;

This is so ingrained that by default the help command "help objectname:verbname" will give the user any string-literal comments at the top of of the verb source.

So, current usage is to use string-literals, as follows:

"this is a MOOCode faux comment." ;

Of course, note that you need a semi-colon to terminate the line, and of course any embedded quotes must be escaped with a backslash, i.e. \"

Variable Declaration and Scoping

In the MOO world you have objects, which have verbs and properties. A verb is a method, function, subroutine, whatever. It's invoked on an object using the ":", like so:

object:verb(argument) 

Objects and data stored in properties on objects are the only persistent things in the moo world.

Within a verb, variables are dynamically declared, and exist only within the scope of the verb. If you call out to another verb and include parameters in that call, the value is passed, not the original variable (i.e. all verb arguments are pass-by-value). The second verb can modify the parameter value all it wants, but the original variable, in the first verb, will remain unchanged.

If an object reference (an object number) is passed in a verb call, you can't really do anything to the reference itself (that is, you can't do anything in the second verb to make the variable back in the first verb point to some other object), but you can use that reference to access verbs and properties on the object, thus changing the persistent object.

A Short Digression on Dynamically Typed Variables

Moo code is dynamically typed. This means that you don't have to go through a lot of bureacracy to set up a variable, yo can just throw a variable assignment in anywhere and presto! It's a variable.

foo = 3 ;

This is a lot more convenient (and fun) but it also gives you a lot more rope to hang yourself. You can be in the middle of a verb and accidentally typo for variable name as "foop" instead of "foot" (don't ask me how you typoed a "p" instead of a "t", maybe you have a weird keyboard layout) and moo will happily create the new variable "foop" and assign it, leading to all sorts of unexpected craziness.

Fortunately, hanging yourself isn't all that dangerous in the MOO world, so you'll usually get a chance to spot the problem and fix it.

Perms and Args

You can see the existing verbs on your object using the @display command (I consider this command indispensable as an object browser).

You can set the permissions using @chmod.

You can set the parser arguments using @args.

Now we get into how the args work. This immediately gets into parsing and matching, since that's the whole point of verb args. These topics deserve a section of their own, and I hope to find time to write it. However, you can't really do any moocoding without some arg basics, so here's a short primer meant to give you just sufficient info to get started.

In moo programming, "arguments" generally refers to the somewhat Zork-like command-line parser, though "@args" refers to the verb arguments. Command-line arguments consist of three elements, (after the verbname) e.g.:

And so forth. The "foo preposition bar" form gets automatically parsed by the MOO's command-line parser; it finds the verb name and argument structure that best matches, and invokes it. There are some rules for precedence - player:verbname comes first, player.location:verbname comes second, etc.

When the parser invokes the verbs, it pre-populates several special context variables that each verb starts with; argstr, dobjstr, iobjstr, and several others. If the verb name and arguments are "put any in any" and you type "put gold in treasure chest", then chest:put() is invoked with a dobjstr "gold" and an iobjstr of "treasure chest".

The parser will also try to match the dobjstr (direct object string) and iobjstr (indirect object string) and set the dobj variable and iobj variable for the verb to the matched objects.

Note that "none none none" is for verbs that aren't meant to have parameters at all. "any any any" is for verbs that expect to do some sort of custom parsing on their own.

There's also a special set of arguments for verbs meant to be invoked only by other verbs:

Generally speaking, if your verb doesn't have its args set to "this none this", you won't be able to invoke it from another verb.

In addition, such not-to-be-directly-invoked verbs must be @chmodded to +x, to allow them to be invoked. If they're not +x, the moo will report a rather confusing "verb not found" error. The reason for this is that not-to-be-directly-invoked verbs are expected (in well-designed code) to do the heavy lifting, the important and tricky stuff, and Pavel (or possibly Ghond) wanted to avoid having lots of half-done code be invokable by anybody.

Flow Control: if, for, while, suspend, fork

A simple list of instructions, one after the other, is technically a program, but you probably want some more sophisticated options than just a straight one-after-the-other list of instructions. You want things like being able to check something and execute one set of instructions or the other (if/endif), or being able to repeat an instruction some number of times (for/endfor).

Some general rules of thumb:

The if and while flow control structures have a "test". In general, this test an expression that evaluates to 0 for false or not-0 for true. Not-0 includes negative values and strings and object numbers. Basically, it includes anything but the number 0.

if (0)
  player:tell("This line never gets executed") ;
endif
if (1)
  player:tell("This line always gets executed.") ;
endif
if (player.iftest)
  player:tell("This line only gets executed if player.iftest contains a non-zero value.") ;
endif
if (1 == player.iftest)
  player:tell("This line only gets executed if player.iftest contains the value 1.") ;
endif

while (1 == player.iftest) player:tell("This line keeps getting executed until somebody sets player.iftest to something besides 1.") ; endwhile

Every flow control structure starts with a keyword (if, for, while). Almost every flow control structure (except suspend) requires an ending keyword for where the structure ends; in moocode, that's almost always the same as the beginning keyword, but it starts with "end", i.e. if/endif, for/endfor, while/endwhile, etc.

Use "if" for conditionals - test some condition, and either carry out the set of commands inside or not.


if (some expression that evaluates to 0 or not-0) "do something" ; endif

You can also use if/else for when you want have a set of instructions for when the if test results in not-true.


if (some expression that evaluates to 0 or not-0) "do something" ; else "do something else" ; endif

while (some expression that evaluates to 0 or not-0) "do something repetitive"; endwhile

for foo in (some expression that produces a list) "do something with foo" ; endfor

suspend(some number of seconds you want the task to pause) ;

fork (somenumber of seconds) "do something somenumber of seconds later, in a separate task" ; endfork "immediately after scheduling the fork, continue on with the rest of the program" ;
fork taskidvariable (somenumber of seconds) "do something somenumber of seconds later, in a separate task" ; endfork "immediately after scheduling the fork, continue on with the rest of the program" ;

The taskidvariable is optional. If it's there, it gets filled with the taskid for the task that the fork creates. Typically the next thing you do is store the taskidvariable's contents in some property, so you can later on use it to check on, or if necessary kill, the task.

for loop

For loops in moocode are kinda neat; "for x in (y)" is nice and readable. Hey, just last year Java added something similar and it only took them an extra 17 years (ooh that makes me feel all smalltakerly/LISPerly inside). The y part of "for x in (y)" can be anything that returns a list, including:

a list slice reference...

>;for x in (me.owned_objects[1..10]) me:tell(x) ; endfor
#1449
#1560
#1565
#1570
#1573
#1574
#1575
#1578
#1579
#1708
=> 0
[used 4001 ticks, 0 seconds.]

Or even just a range...

>;for x in [1..10] me:tell(x) ; endfor
1
2
3
4
5
6
7
8
9
10
=> 0
[used 3776 ticks, 0 seconds.]
suspend

Suspend is pretty straight-forward; it just pauses the execution for however many seconds.

;;suspend(20) ; me:tell("boo!") ;
...20 seconds pass...
boo!
=> 0
fork

Fork takes a little more discussion. Fork fires off a separate task. In this example I used all of the optional arguments. The somenumber of seconds part delays the new task from starting until some number of seconds in the future. Meanwhile, the rest of the verb continues on without delay.

You're allowed to set the delay at 0 if you just want to fire off the new task immediately. This is useful when you want to do some things that require time (perhaps a scripted set of comments by a robot) but meanwhile you want the rest of the verb to proceed as usual.

The body of the fork is what gets executed by the new task (I usually just put a one-line callout to some other verb in there and put all the gory stuff in the separate verb).

The taskidvariable part lets you define a new variable to hold the taskid of the new task. Of course, you have to do something with the taskid that gets put in that variable, like store it on an object property so you can kill it later with @kill, when it's sucking down all of your ticks like a great tick-sucking vortex of doom.

(But, if you messed up and forgot to save the taskid, check out the @forked command).

Here are some examples of if/for/while/forked in action. Note that I'm demonstrating all of them via eval, so each example is all in one line. In normal code you'd have each bit on a separate line. for example, the first if/else/endif example would be five lines in a normal verb (if, the body of the if, else, the body of the else, endif).

>;if(1) player:tell("foo") ; else player:tell("bar") ; endif
foo
=> 0
>
>;if(0) player:tell("foo") ; else player:tell("bar") ; endif
bar
=> 0
>
>;if(-1) player:tell("foo") ; else player:tell("bar") ; endif
foo
=> 0
>
>;; i = 3 ; while(i) me:tell("i is ", i) ; i = i -1 ; endwhile
i is 3
i is 2
i is 1
=> 0
>
>;fork taskidvar (2) me:tell("later.") ; endfork
=> 0
(two seconds pass)
later.
>

Types

Moocode isn't friendly enough to auto-convert between types for you, most of the time. You have objects references, ints, strings, floats. Since it's MOO, by definition a lot of the time you're going to be dealing wtih other people's data. Use the typeof() function to see what type a particular parameter is. Use the toliteral() function to convert it to a string you can print out (especially useful for looking at hairy lists-of-lists).

Use the various tofoo() functions to convert back and forth:

Note that typeof returns an int value, but there are several standard values that are defined in moocode. INT is 0, OBJ is 1, STR is 2, ERR is 3, etc. See "help typeof" for more info. You could just check to see if the return value is 0 or 1, etc, but it's a lot smarter to use those predefined variables. That way, when you look at a bunch of code you wrote in a drunken binge, you'll have some vague idea wtf you were intending to do.

MOOCode In The Large

Threading, Ticks and Tasks

Like just about all computers, MOO doesn't really do many things at once, it just fakes it. Unlike other systems, MOO uses a rather distinctive approach to faking it. It's not really multitasking, but it fakes it rather well, especially for a multi-user, multi-coder system.

At any given point in time, the MOO server is running only one verb invocation. This is called a task. What happens if that task gets out of hand? What if some idiot codes a verb to calculate pi to the final decimal place?

To keep this from causing problems, MOOcode has some internal cost-accounting. Each task is given a certain number of "clock ticks" at the start; last I looked I think it was 40,000 ticks. Each operation inside the task costs some number of ticks, arbitrarily decided by the original developers of MOO, based on how expensive they thought that operation would be. If the task uses up all of the ticks, the verb invocation dies with an error about being out of ticks.

Ticks get renewed over time, so verbs that have to do a whole lot of stuff will use suspend() to pause for some period of time and let their ticks recharge. $command_utils:suspend_if_needed(10) is often used for this, to only suspend() if the verb is in danger of running out of ticks. Of course, suspend_if_needed() burns up ticks in itself, so it's wise to always give it an argument of at least 5, more like 10 seconds for the pause.

There's also a player-wide tick allocation, so if a particular player has lots of verbs that have heavy tick usage, the performance impact will fall more on that player, and less on the rest of the MOO.

If you want to learn more about just what costs how many ticks, do:

;me.eval_env=1

And now the examples I gave you in the Flow Control section will produce output like:

>;if(1) player:tell("foo") ; else player:tell("bar") ; endif
foo
=> 0
[used 377 ticks, 0 seconds.]
>;if(0) player:tell("foo") ; else player:tell("bar") ; endif
bar
=> 0
[used 382 ticks, 0 seconds.]
>;if(-1) player:tell("foo") ; else player:tell("bar") ; endif
foo
=> 0
[used 377 ticks, 0 seconds.]

Note that there are a lot of odd variables that go into actual tick costs, so they will tend to vary a little from invocation to invocation. Don't get hung up on that facet.

Core Classes

The real trick to moocoding is, just as with Java, learning the APIs. With MOOCode, learning how the core infrastructure objects hook into each other (and hence how you can code an object and drop it into this system of interacting objects) is critical.

The @classes command displays a list of the "important" classes for one of about five or six subsets that the wizards have decided are worth keeping track of. One of the most important is the Generics, and the key generics are:

You should probably also study up on object #1, The Root Class, to see what verbs you can expect to see on all objects, since The Root Class is the grandaddy object from whom all objects are descended.

While these classes aren't the only important classes, they account for the fundamental MOO experience. The vast majority of player-created objects in the MOO database are descendants of $player, $room, $exit or $thing.

Overview

There are help entries for $room ("help $room") and $exit ("help $exit") but not for $player and $thing. Here's a quick overview of all four.

Okay, so players are roughly self-explanatory, but the real essence of a player is that player objects have incoming telnet connections attached to them, and will feed incoming text to the MOO parser, and feed the resulting output text back into the outgoing telnet. Outgoing text usually reaches the player object via player:tell() and usually is passed to the outgoing telnet connection when player:tell() calls player:notify().

In addition, players are generally descendants of various classes that provide useful basic commands like say, emote, etc, and for other MOO features that players care about, like the in-MOO mail system, @who, some old features like setting line length and linewrapping (mostly irrelevant now due to decent MUD client programs) and so forth.

Rooms are locations; like any MOO object they have a contents property containing a list of objects they contain, but they also have lots of verbs oriented towards players who are contained in them. Players, and rooms both work closely with the MOO parser to create the whole MOO experience, both in terms of making the verbs work and in terms of creating the sense of space and place.

Room:look() is the verb that the parser invokes if you simply type "look" with no arguments. room:look() calls room:look_self() to return text describing the room. Room:look_self() calls room:description() and room:tell_contents() and sends th results to player:tell(). Room:description() defaults to just returning the text in the property room.description, and room:tell_contents() defaults to just listing the objects in the property room.contents, but they can and do often work differently.

There are some rooms, for example, that dynamically compose the description from objects that in the room that are designed to work with such rooms, and then the room leaves any such objects out of the room:tell_contents() output.

Rooms also have two properties, room.entrances and room.exits. Both properties contain a list of $exits; an entrance is just an exit from some other room that has this room as a destination. When you enter a command, if the MOO parser can't find a verb whose name and arguments match the command, it then tries to match an exit. If it does, it calls the exit's :invoke() verb, which then moves you from that room to the destination room.

Finally we have $thing, which provides the basic features you expect to find in well, a thing; something that is neither intrinsically a part of the room, nor an autonomous entity like a player, nor an abstraction for movement like an exit. You can pick up a $thing using its get verb. You can drop a thing using its drop verb, and so forth. Things are kind of obvious and kind of minimal, but you have to start somewhere. A lot of stuff in LambdamOO is ultimately a deescendant of $thing.

Detailed Examples

The two examples that come to mind are also probably the two most common cases as well as fundamental to that sense of there-ness that makes MOO work, which are: sight/sound and location.

Sight and Sound in MOOCode

Sight/sound is pretty straight-forward. I lump them together because really, they're just lines of text that are being sent to the player's network connection.

Everything has a :tell() verb, starting with #1:tell(), which just does nothing. This makes it very convenient to code verbs that emit text and just send it to all objects in the room.

The player object has a special verb, player:notify(), that sends a line of text to the player's network connection. player:notify() is so special that nobody actually invokes it directly; it's almost only ever used by player:tell() (and occasionally by player:tell_lines(), see note below). In fact, player:notify() has a special permission check in its code to prevent it from being called from anything else but verbs on the player.

player:tell(), at its simplest, relays lines to player:notify(). More complicated versions of :tell() do checking for unwanted messages or attempts to "spoof" the player.

(Note: spoofing is when you send text to a player with the intent to deceive, usually by having the text not identify its source or pretending to be output that resulted from a player command; it's generally considered rude on its own, and when done for malicious purposes extremely rude and/or socially objectionable).

Note: player:tell_lines() works much like player:tell() except it can take a list of strings as an argument; it, in turn, calls player:notify_lines(), which loops through the list and calls player:notify() with each individual line.

Okay, so now we know what happens to a line of text after it gets to the player. This is the mechanism that everything in the MOO uses to send text to your player object's network connection. When you look at the room, for example, the room's verbs in turn call player:tell() to feed you descriptions, a list of what's in the room, etc. This is a fairly common practice and is considered normal.

Location in MOOCode

Location in MOO is based on two special object properties: "object.location" and "object.contents". All objects in MOO have these two properties built into them. Every object always has a location value (always a single object number), and every object has a contents value (always a list, possibly empty). The server itself inherently keeps track of these, so every object can only have one location at a time, and will only (and can only) be in a single other object.contents list.

The built-in function, "move()" updates three different object properties in one atomic step, so they can never get out of sync:

However, you shouldn't be messing with move(), instead you'll use object:moveto(#destination).

When you move an object, the object's :moveto() verb may have some special checks coded in it to prevent you moving it. Most players don't like being moved around by random people. See "help locking"; also, a lot of older players use a player class that has a "sessile" property that, when set to 1, keeps anything from moving them. These days players mostly just use @refuse moves to protect themselves from idiots.

For the most part, objects are moved into/out of rooms, and into/out of players (the stuff you're "carrying"). There are also generic container objects - things that you can put stuff into and get stuff out of. And, when all else fails, you can probably expect to find your missing object off in #-1, which appears to be where the server sticks anything when it can't figure out where to put it.

For the most part what happens when you move something is that the following verbs are called in something vaguely like this order:

When you are in a room and you issue the "look" command, the parser finds $room:look(), which in turn calls $room:look_self(). look_self() is the generic verb for an object to assemble what a player sees when the object is looked at. As is often the case in MOOCode, the look_self verb doesn't just return a description, it in turn calls player:tell().

What follows is pretty much the same process for most objects:

As it turns out, room:description() is not part of $room:look_self(). I'm not sure where that get added to look_self() (though the :description() verb is defined on the Root Class, #1).

Outline of Planned Revision


See original (unformatted) article

Feedback

Verification Image:
Subject:
Your Email Address:
Confirm Address:
Please Post:
Copyright: By checking the "Please Post" checkbox you agree to having your feedback posted on notablog if the administrator decides it is appropriate content, and grant compilation copyright rights to the administrator.
Message Content: