thisis stuff you type in the moo. Stuff in bold next to moo text is comments, don't type it. Anytime you don't recognize the first word in a line (the verb), type
help wordStuff in italics is also often stuff you can type in the moo, and anytime I refer to the help command, I mean the help function that you access by typing
help wordin the moo. You might also want to check out the Lambda Moo Programmer's Manual.
object identifier or name.property name(note the ".").
object identifier or name:verb name(note the ":").
Before you start programming, I highly recommend that you get a decent client. See the FAQ on clients for more detials. Thanks to Jonathan we have a few more choices -- another FAQ and two suggestions for windoze users: zmud and pueblo.
-- Given what we know about the structure of the board data, we can
change or retrieve the piece at a given location ...
Since we're aiming at TicTacToe, let's call our instance
TicTacToe.
Well, you should be able to figure out how to test move_piece
at this point. Go back and check your code for it to see what it's
arguments are, or better yet, type help GameBoard:move_piece,
and that commenting mechanism I referred to earlier should do it's
thing. Then call it non-interactively, and make sure you've
actually put something to move at one of your indeces, and finally,
use show to see that it worked.
All this is kind of clunky, though, and doesn't make for great
gameplay. Maybe it's time to write a function which lets you start the
game, and a function which lets you move ...
Lets start by defining what A GameBoard might look like..
Even though our final aim is a tic tac toe game, let's think ahead and
acknowledge that we may want to build many different types of
games. So we'll use the standard OO strategy and create a more generic
object which can encapsulate functionality which isn't specific to
TicTacToe.
Create the GameBoard: This object will be in charge of holding data
and drawing the current state of the board.
@create $thing named GameBoard:GameBoard ($thing refers to a
generic class which
represents "things" (as
opposed to "containers" or
"players" or "rooms"). The
"$something" syntax is a
shortcut to properties
defined on #0, the 0th
object. Try @display #0 to
get a list of other object
which you can refer to in
this fashion.)
Set up some properties: To draw it, we need some information about
it. The GameBoard data is a list of lists (each list represents one
row). The elements of the row lists are pieces. In fancier version, we
might allow pieces to be arbitrary objects, or lists (if they have
height greater than 1), but for now they are strings.
@prop GameBoard.num_rows 3
@prop GameBoard.num_cols 3
@prop GameBoard.data {} ({} is of type LIST. See the
Programming Manual, section on
Moo Value Types
for more inforamiton.)
-- what to draw between pieces
@prop GameBoard.vert_sep "|"
@prop GameBoard.hor_sep "-"
-- the maximum width and height of a piece
@prop GameBoard.max_piece_width 1
@prop GameBoard.max_piece_height 1
Set up some verbs to give us nicer access to those
properties.
@verb GameBoard:set_piece this none this this none this
refer to the arguments to the
verb set_piece. For more
information on verb
arguments, type help @verb
@program GameBoard:set_piece
"args are (piece, row, col)"; This line is a
comment. It's more than just
useful to someone reading the
source for this function: any
user can now type help GameBoard:set_piece
and any comments at the
beginning of the function
will be printed out.
piece = args[1]; New variables are created on the
fly just by setting them ...
row = args[2]; args is a list of the
arguments. args[num] refers to an
element of that list
col = args[3];
this.data[row][col] = piece; this refers to the current
object. For an explanation of
variables which are automatically
defined in a function, such
as this and args (there are a couple of
other useful ones), see
Programming Manual, Naming Values in Verbs
Remember that this.data is also a list
.
@verb GameBoard:get_piece this none this
@program GameBoard:set_piece
"args are (row, col)";
row = args[1];
col = args[2];
return this.data[row][col];
.
@verb GameBoard:move_piece this none this
@program GameBoard:move_piece
"args are (from_row, from_col, to_row, to_col)";
"Default is to switch two pieces. Override this function to change that.";
{from_row, from_col, to_row, to_col} = args; Remember how arguments
are a list? Well this
is one really nice, funky feature for accessing the
elements of a list. -- basically, the list gets parsed
for you. See the
Programming Manual, Spreading List Elements Among Variables
for more details ...
piece = this:get_piece(from_row, from_col); Here's an example of
how one might use a non-
interactive function.
see the introduction
to this document for
more info on interactive
vs non-interactive functions
this:set_piece(this:get_piece(to_row, to_col), from_row, from_col);
this:set_piece(piece, to_row, to_col);
.
-- Given what we know about the structure of the board data, we can
draw the board (return a list of strings which represent each row of
the drawing in this case)
@verb GameBoard:draw_board this none this
@program GameBoard:draw_board
across = $string_utils:space(((this.max_piece_width * this.num_cols) + this.num_cols) + 1, this.hor_sep);
$string_utils is one
of those core objects which
are defined on #0. To find
out more about it, use
help $string_utils or help
$string_utils:verbName. It's
*very* useful. To find out
about other utility objects,
try help core-index. help
index also contains lots
of interesting stuff.
ret = {across};
for r in (this.data) for more information
about for loops
and other Moo Language Statements,
see the Programming
Manual.
line = this.vert_sep;
for e in (r)
line = (line + e) + this.vert_sep;
endfor Note that statements
general end with an
endStatementName.
ret = {@ret, line, across}; This is a good example
of some of the less
intuitive but very useful
operations
which can be done on lists.
endfor
return ret;
.
Making A Specific Game (TicTacToe)
Testing out the gameboard ...: Since this object is completely
generic, we haven't yet been able to test out how well our code
works. Let's make an instance of it and test things out by hand
quickly.
@create GameBoard named TicTacToe:TicTacToe
Ok, now we basically have an exact duplicate of the GameBoard. What do
we do with it? Lets customize it to look like a TicTacToe Board...
;TicTacToe.data = {{" ", " ", " "}, {" ", " ", " "}, {" ", " ", " "}}
Aha! the first use of
the eval function ... if
you don't know what I'm
talking about, go back
and read the beginning
of this tutorial (ie, the
section titled
Some Important Preliminary Information
Now that we've got it, lets see if any of our previous functions
actually work. First, lets test the function which prints out the
current board:
;#504:draw_board() Note that we use the
object number, not the
name TicTacToe.
Substitute in your
TicTacToe's number
instead here.
=> {"-------", "| | | |", "-------", "| | | |", "-------", "| | | |", "-------"}
Well, those results might not have been quite what you expected, but
just to test their validity, try rearranging them slightly (something
we need to write a function to do for us):
{"-------",
"| | | |",
"-------",
"| | | |",
"-------",
"| | | |",
"-------"}
Lets go ahead and write a function which we can run interactively,
which will "pretty print" the board -- call it "show":
@verb TicTacToe:show this none none The first argument to
the function should be
the board to show.
@program TicTacToe:show
res = this:draw_board();
player:tell_lines(res);
.
Does it work? Try show TicTacToe
Now that we can look at the board, lets test out the other functions
we wrote: get_piece, set_piece, and move_piece
;#504:set_piece("O", 1, 1)
=> 0 This is the return value for the
verb. Since we didn't specify one (go
back and look at the code), it defaults
to zero.
To check if it worked, we can use get_piece (remember to call
this non interactively, using eval, and pass it the same indeces we
used to set the piece) or show. Try both.
@verb TicTacToe:start this with any
@program TicTacToe:start
"arguments are (this with opponent)"
partner = $string_utils:match_player(args[3]); args[3] is just a
string, we have to
convert it to the data
type we want, and in this
case, we have to first
figure out which
object it *really* refers to ...
if (partner == $failed_match) This is some error
checking. It points
out some useful
programming conventions: first of all, $string_utils:match_player
tries to return a useful return value (a value
which tells us something about what went
wrong, if anything.) Second of all, we then
pass that information on to the user in an
understandable format. Since the moo uses a
semi-natural language interface, the users
often have to guess at the best syntax of a
verb. We're helping them out when we explain
what they did wrong.
player:notify(args[3] + " is not a valid partner. Please name a player who is logged in.");
return;
endif
this.players = {player, partner}; We need somewhere
to keep track of who's
playing ... and who's
turn it is. We'll make a
few new properties
(current_player, and players
... see below)
this:reset(); Oops this function
doesn't exist yet
... better write it
(see below)
partner:notify(((args[3] + ": start by saying \"move ") + this.name) + "
Try it out. Notice that the way we wrote the function, we're
always letting your opponent start the game. You could, alternatively,
make it random. You probably don't want to write a random number
generator from scratch, so this would be a good time to type help
random in order to see whether or not there's already something
useful in the moo. I'll leave the rest of this problem as an excersize
to the reader.
Now that we know how start a game, we need to provide a decent
mechanism for each player to make a move. The following function is
one solution to the problem.
@verb TicTacToe:move this any none
@program TicTacToe:move
"args are (this row col)";
some error checking ...
if (((length(args) != 4) || (!tonum(args[3]))) || (!tonum(args[4])))
player:notify("Try \"move TictacToe to
Ok, we're set! You can now play a game with someone if you wish. One
thing you might want to account for ... they can cheat (and so can
you). A better implementation of the move function might check to make
sure someone hasn't already put a piece in the spot chosen.
Creating a GUI Interface
At the very minimum, when you create an object in the moo, you should
give it a reasonable icon for use in the GUI. If you type drop
TicTacToe right now, load
up the GUI interface , and go to the room
where you dropped the TicTacToe board, you should see it's default
representation (pretty boring, huh?).
There are three properties defined on every moo object which dictate
it's representation. They are listed below. You can look at them with
the @display command. When you create your own interface, you will
tell the GUI where and what it is by changing these properties:
>@display TicTacToe.type type Domisilica-Wizard (#2) r c "Object" >@display TicTacToe.icon icon Domisilica-Wizard (#2) r c "defaultButton.gif" >@display TicTacToe.action action Domisilica-Wizard (#2) r c "PALplate.lib.defaultButton"The first property, type, tells the GUI what sort of beast it's looking at. The only two types currently valid are "URL" and "Object", but one could imagine adding others, such as "Sound". The second property, icon, has different meanings depending on the type. If the type is "URL", the GUI puts a button on the screen with the specified icon as it's picture. If the type is "Object", it passes the icon as an argument to the java class specified in the third property, action. action is the property which tells the GUI what to do with this moo object. If type is "Object", action should be a java class. If the type is "URL", the action should be a URL (ie, "http://foo.bar.com/...").
Images are loaded from a subdirectory of the place where the HTML for the GUI is loaded called "images". Classes are expected to be in your classpath.
For demo purposes, I've made an icon called "TicTacToe.gif". Try using it. Or if your curious, you can be silly and use any of the other gif files in /net/www-int/projects/cyberfridge/images (the directory from which all this stuff is being loaded). This isn't very useful, though, since TicTacToe is an interactive game, and you can't do much that's interactive with an icon.
There's one other way you could customize the GUI interface: have it point at a URL. For example, you might know about a networked, multi-user version of TicTacToe already in existance somewhere on the web. If you wanted, you could even write your own java applet or web page for this purpose. So you set the action property to that URL, and if somone clicks on the icon with the mouse, that URL will be loaded in the bottom frame of the GUI interface. The only problem with this solution is that no-one in the moo will be able to play the game with you.
Let's start by looking at the default class which is currently in TicTacToe.action. It should be "PALplate.lib.defaultButton". This class is actually a little more complex than a simple icon -- it's a button which can be pressed. Right now, if someone presses on it, it doesn't actually do anything, though.
In order to get a better understanding of what's going on in defaultButton, we need to take a look at it's parent, PALplate.interactors.SelfCallBackButton. This is just a child of sub_arctic's button class which recieves it's own callback when it is pressed. SelfCallbackButton is actually an abstract class because it doesn't implement the callback function, among other things. It's child, defaultButton, is an example of one of the most minimalist interfaces to a MOO object possible.
The defaultButton class has a constructor which takes no arguments because when it's instantiated (on the fly) by the GUI, it's not possible to pass it any arguments (this is a limitation of java, and anyway, consider that the GUI class which is instantiating defaultButton doesn't know anything about it except it's name. How could it know what arguments to pass to it's constructor?). Well, it doesn't know
@verb TicTacToe:show this any none rxd @program TicTacToe:show if (is_player(toobj(args[1]))) target = args[1]; else target = player; endif connection_type = $client_options:get(player.client_options, "connection_type"); if (connection_type == "direct" || !connection_type) res = this:draw_board(); target:tell_lines(res); else target:tell("#@#" + args[2] + " " + $string_utils:from_value(this:draw_board(), 2 + this.max_piece_height)); endif .
@verb TicTacToe:start this with any @program TicTacToe:start partner = $string_utils:match_player(args[3]); if (partner == $failed_match) player:notify(args[3] + " is not a valid partner. Please name a player who is logged in."); return; endif this.players = {player, partner}; ctype1 = $client_options:get(partner.client_options, "connection_type"); ctype2 = $client_options:get(player.client_options, "connection_type"); this:reset(); if ((ctype1 == "direct") || (!ctype1)) partner:notify(((args[3] + ": start by saying \"move ") + this.name) + "|
\""); this.current_player = partner; else partner:notify("#@#" + args[4]); endif if ((ctype2 == "direct") || (!ctype2)) player:notify(args[3] + " will begin the game."); "don't need an else case here 'cause we only need to respond to the gui going first"; endif .
@verb TicTacToe:move this to any @program TicTacToe:move "args arehttp://www.cc.gatech.edu/fce/domisilica/info-pages/architecture.htm#communication"; if (((length(args) != 4) || (!tonum(args[3]))) || (!tonum(args[4]))) player:notify("Try \"move TictacToe to |
\""); return; endif if (player != this.current_player) player:notify("It's not your turn!"); return; else if (this.players[1] == player) this.current_player = this.players[2]; this:set_piece("X", tonum(args[3]), tonum(args[4])); else this.current_player = this.players[1]; this:set_piece("O", tonum(args[3]), tonum(args[4])); endif this:show(this.current_player); this.current_player:notify("Your turn ..."); this:show(player); endif .