TicTacToe Tutorial

Quick jumps to the various sections of the tutorial

Introduction

Ok, this tutorial assumes that you already know how to connect to a moo and have programmer priveledges. Text that looks like
this
is 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 word
Stuff 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 word
in the moo. You might also want to check out the
Lambda Moo Programmer's Manual.

Some Important Preliminary Information

A MOO is object oriented. We program it by creating objects, and adding properties and verbs (methods) to those objects. Read the Programmers Manual,
Objects in the MOO Database for more information about how objects work. The key things you need to know to program objects are When you write MOO code, you want to be able to test it. There are two ways to do this, and which you use depends on whether the verb you wrote was interactive (callable from the prompt) or non-interactive (callable from other verbs). When you create a verb, it defaults either to being interactive only, or non interactive only, depending on the arguments you give to @verb (type help @verb for more info). You can make a verb both interactive and non-interactive by changing it's permissions to include the executable ("x") bit (type help @chmod for more info). In any case, if a verb is interactive, you can test it simply by typing it's name and arguments (to understand how the server knows which verb you're referring to, check out the Programming manual section on command parsing). If it is non-interactive, you can test it using the eval function (a shortcut for eval is ";", see help eval for more info). When using eval, pass it a string exactly like one that you might put in the source code for a verb. Be careful, however, that you take out all references to local variables. Also, you need to use the id number of the object whose verb you are running instead of it's name.

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.

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.

-- Given what we know about the structure of the board data, we can change or retrieve the piece at a given location ...

@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.

Since we're aiming at TicTacToe, let's call our instance TicTacToe.

@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.

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 ...

@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) + "  \"");
this.current_player = partner;
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";
.

@prop GameBoard.current_player 0 
@prop GameBoard.players {} 

@verb TicTacToe:reset this none this
@program TicTacToe:reset
this.data = {{" ", " ", " "}, {" ", " ", " "}, {" ", " ", " "}};
.
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  \"");
  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
.
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.

Customizing the standard GUI interface

Instead of just having a generic button with a bland icon on it, lets start by giving our gameboard a decent picture on the screen, one that someone might recognize. In order to do this, we need to create the icon, and put it in a directory where it can be found. If you want to use something other than the default directory specified above, give the icon a name which encodes a directory relative to that one (ie, "../../me/icons/foo.gif"). Unfortunately, because of security problems, you cannot specify an arbitrary URL. You can, however, ask Wizard or jmankoff@cc.gatech.edu to copy over an icon once you've created it.

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.

Making your own GUI interface

Instead, let's do it ourselves. In Java. But remember something: whomever you're playing with may not have a GUI running. This points out a central issue in how the GUI interface is built: it contains none of the functionality, it's just there for show, and to handle user input. We have to continue to let the game play run in the moo, and just display the results of the changes to any users viewing this with a GUI.

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.

What's required of a minimalits java<->MOO interface object?

SelfCallbackButton provides minimal defaults for most of these functions, and defaultButton covers the rest of them.

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 are ";
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
.
http://www.cc.gatech.edu/fce/domisilica/info-pages/architecture.htm#communication
jmankoff@cc.gatech.edu