\subsubsection{Idea}

The idea behind this recipe is very simple: implement an application that
allows two human players to play ``connect four'' over the network. The
program displays the board to each player, who may drop chips of his own color
in any one of the seven columns. The player who is the first to obtain four
chips along a horizontal, vertical, or diagonal line, wins the game. The
application detects a win and uses an alert panel to inform the players that
the game is over. It also detects ties. The application has three states: ``my
turn'', ``opponent's turn'' and ``game over.''

This game provides an opportunity to illustrate the use of distributed objects
(DO) in an easy-to-understand application. There are two classes: a controller
that interacts with the application object through delegation and a subclass
of {\tt NSView} that implements the board and the ``connect four''
functionality. There is one instance of the board, which the controller vends
to the network so that the opponent may invoke methods to restart or quit the
game, and record a move, all of it through a proxy. These methods are grouped
in the protocol ``connect four.'' The protocol works both ways: each player
has a proxy of his opponent's board and uses it to communicate moves and
restart or quit events to the process of his opponent. The connection is
bi-directional rather than client-server. The name chosen by the controller to
vend the board object has the form ``connectfour-user-color'', where ``user''
is the name of the user playing the game and ``color'' is the color the user
chose (red or yellow, red begins). Start the game from the command line like
this:
\begin{center}
{\tt openapp DOConnectFour.app/ red 172.16.1.150 riedel.}
\end{center}

\subsubsection{Implementation}

Start by including all the necessary headers and define the width and height
of a single square on the board. Define an enumerated datatype that represents
the contents of the board: a square is either empty or it contains a red or a
yellow chip.

\begin{lstlisting}{}
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>

#define DIMENSION    50

typedef enum {
    FIELD_BLANK=0,
    FIELD_YELLOW,
    FIELD_RED
} FIELD_STATE;
\end{lstlisting}

The next set of constants defines the number of rows and columns, the total
number of squares on the board and the dimensions of the board in pixels.

\begin{lstlisting}{}
#define BOARD_WIDTH  7
#define BOARD_HEIGHT 6
#define BOARD_SIZE (BOARD_WIDTH*BOARD_HEIGHT)

#define TOTAL_WIDTH  (BOARD_WIDTH*DIMENSION)
#define TOTAL_HEIGHT (BOARD_HEIGHT*DIMENSION)
\end{lstlisting}

The game has three states: ``my turn'', ``opponent's turn'' and ``game over.''

\begin{lstlisting}{}
typedef enum {
    STATE_LOCAL_MOVE,
    STATE_REMOTE_MOVE,
    STATE_DONE
} STATE_GAME;
\end{lstlisting}

The protocol {\tt ConnectFour} is of key importance. It defines the methods
that the application may invoke on the proxy object representing the
opponent's board. There are three methods: the first communicates to the
opponent process to restart the game, the second, to quit the game and the
third, to update the board and the state of the opponent's game to reflect a
move by the local player. The qualifier {\tt oneway void} indicates that the
application needn't wait for the invoked method to return.

\begin{lstlisting}{}
@protocol ConnectFour
- (oneway void)remoteNewGame;
- (oneway void)remoteQuit;
- (oneway void)remoteMoveAtRow:(int)row Col:(int)col Win:(BOOL)win;
@end
\end{lstlisting}

We may now define the class that represents the board. Note the first line --
it says that it implements the protocol {\tt ConnectFour}. The board stores
the state of the game in the array {\tt data}, which is {\tt BOARD\_WIDTH}
columns wide and {\tt BOARD\_HEIGHT} rows high. The board records the number
of moves and the state of the game, as well as the color of the local player
(red or yellow). Finally there is an instance variable that holds the proxy
object representing the opponent's board, which also implements the protocol
{\tt ConnectFour} (obviously, since it is an instance of the same class).

\begin{lstlisting}{}
@interface Board : NSView <ConnectFour>
{
    FIELD_STATE data[BOARD_HEIGHT][BOARD_WIDTH];

    int moves;
    STATE_GAME state;
    FIELD_STATE color;

    id <ConnectFour> remote;
}
\end{lstlisting}

The class {\tt Board} only contains a few simple methods. There is an
initializer and a method to restart the game. There is an accessor to set the
proxy object. The method {\tt winner} determines whether the local player has
a winning configuration. The most important methods are {\tt drawRect:}, which
draws the board and {\tt mouseDown:}, which responds to a click on a column,
advances the state of the game and notifies the opponent of the change.

\begin{lstlisting}{}
- initWithColor:(FIELD_STATE)col;
- newGame;
- setRemote:(id <ConnectFour>)rem;

- (BOOL)winner;

- (void)drawRect:(NSRect)aRect;

- (void)mouseDown:(NSEvent *)theEvent;

@end
\end{lstlisting}

Now start the implementation. The initializer is very simple. It computes the
frame rectangle and invokes {\tt NSView}'s method {\tt initWithFrame:}. It
records the color of the local player and restarts the game.

\begin{lstlisting}{}
@implementation Board

- initWithColor:(FIELD_STATE)col
{
    NSRect frame;

    frame.origin = NSZeroPoint;
    frame.size.width  = TOTAL_WIDTH;
    frame.size.height = TOTAL_HEIGHT;

    [super initWithFrame:frame];
    color = col;

    [self newGame];

    return self;
}
\end{lstlisting}

The method {\tt newGame} is straightforward. It iterates over the squares of
the board and resets every square to be empty.

\begin{lstlisting}{}
- newGame
{
    int row, col;

    for(row=0; row<BOARD_HEIGHT; row++){
        for(col=0; col<BOARD_WIDTH; col++){
            data[row][col] = FIELD_BLANK;
        }
    }
\end{lstlisting}

It also resets the move counter to zero and determines the initial state based
on the convention that red begins. The last step is to mark the view as
needing redisplay.

\begin{lstlisting}{}
    moves = 0;
    state = 
	(color == FIELD_RED ? 
	 STATE_LOCAL_MOVE : STATE_REMOTE_MOVE);

    [self setNeedsDisplay:YES];
    
    return self;
}
\end{lstlisting}

The setter method {\tt setRemote:} stores the proxy for the remote board in
the corresponding instance variable. Retain and release for this object are
handled by the controller.

\begin{lstlisting}{}
- setRemote:(id <ConnectFour>)rem
{
    remote = rem;
    return self;
}
\end{lstlisting}

The method that detects winning configurations iterates over every square of
the board. It checks whether the current square could be the first of a
horizontal, vertical, ascending diagonal or descending diagonal segment of
length four, and if it is, the segment's contents (chips) are checked. We have
a winner if all four are the color of the local player.

\begin{lstlisting}{}
- (BOOL)winner
{
    int row, col;
    
    for(row=0; row<BOARD_HEIGHT; row++){
        for(col=0; col<BOARD_WIDTH; col++){
\end{lstlisting}

Check horizontal segments first.

\begin{lstlisting}{}
	    if(col+3<BOARD_WIDTH){
		if(data[row][col] == color &&
		   data[row][col+1] == color &&
		   data[row][col+2] == color &&
		   data[row][col+3] == color){
		    return YES;
		}
	    }
\end{lstlisting}

Do vertical segments next.

\begin{lstlisting}{}
	    if(row+3<BOARD_HEIGHT){
		if(data[row][col] == color &&
		   data[row+1][col] == color &&
		   data[row+2][col] == color &&
		   data[row+3][col] == color){
		    return YES;
		}
	    }
\end{lstlisting}

Now try ascending diagonals.

\begin{lstlisting}{}
	    if(row+3<BOARD_HEIGHT && col+3<BOARD_WIDTH){
		if(data[row][col] == color &&
		   data[row+1][col+1] == color &&
		   data[row+2][col+2] == color &&
		   data[row+3][col+3] == color){
		    return YES;
		}
	    }
\end{lstlisting}

Check descending diagonals last and return {\tt NO} if no segment of length
four was found.

\begin{lstlisting}{}
	    if(row-3>=0 && col+3<BOARD_WIDTH){
		if(data[row][col] == color &&
		   data[row-1][col+1] == color &&
		   data[row-2][col+2] == color &&
		   data[row-3][col+3] == color){
		    return YES;
		}
	    }
        }
    }    

    return NO;
}
\end{lstlisting}

The method {\tt drawRect:} is important, yet straightforward. Start by
allocating an array of colors for indexing with the {\tt FIELD\_STATE} data
type.

\begin{lstlisting}{}
- (void)drawRect:(NSRect)aRect
{
    int index, row, col;
    NSColor *cols[3] = {
        [NSColor blackColor],
        [NSColor yellowColor],
        [NSColor redColor]
    };
\end{lstlisting}

Set the line width to $1.0$ and paint the background of the view blue.

\begin{lstlisting}{}
    PSsetlinewidth(1.0);

    [[NSColor blueColor] set];
    PSrectfill(0, 0, TOTAL_WIDTH, TOTAL_HEIGHT);
\end{lstlisting}

The grid that delinates the squares is next. It is drawn in black. Left,
right, upper and lower margins are not drawn. There are two loops: the first
draws the vertical lines of the grid and the second one draws the horizontal
ones.

\begin{lstlisting}{}
    [[NSColor blackColor] set];
    for(index=1; index<BOARD_WIDTH; index++){
        PSmoveto(index*DIMENSION, 0);
        PSlineto(index*DIMENSION, TOTAL_HEIGHT);
    }
    for(index=1; index<BOARD_HEIGHT; index++){
        PSmoveto(0, index*DIMENSION);
        PSlineto(TOTAL_WIDTH, index*DIMENSION);
    }
    PSstroke();
\end{lstlisting}

The last step is to draw the chips, or a black filled circle for empty
squares. We iterate over the data array, compute the center of the filled arc
and use the data value as an index into the array of colors. We set the color
and fill the arc.

\begin{lstlisting}{}
    for(row=0; row<BOARD_HEIGHT; row++){
        for(col=0; col<BOARD_WIDTH; col++){
            NSPoint pt = {
                col*DIMENSION+DIMENSION/2,
                row*DIMENSION+DIMENSION/2
            };
            [cols[data[row][col]] set];
            PSarc(pt.x, pt.y, DIMENSION/3, 0, 360);
            PSfill();
        }
    }
}
\end{lstlisting}

The way the board responds to clicks by the user is determined by the method
{\tt mouseDown:}. If the state of the game is not equal to ``my turn''
i.e. {\tt STATE\_LOCAL\_MOVE}, then the click is not valid, and the user hears
a beep.

\begin{lstlisting}{}
- (void)mouseDown:(NSEvent *)theEvent
{
    if(state!=STATE_LOCAL_MOVE){
        NSBeep();
        return;
    }
\end{lstlisting}

We need to know where the user has clicked and retrieve the corrdinates where
the mouse-down occurred. We convert these coordinates to view coordinates. We
have a case where the view is the content view of the window, so the
conversion is not strictly necessary, but it is the right thing to do, since a
view may be anywhere in a window.

\begin{lstlisting}{}
    NSPoint curp;
    curp = [theEvent locationInWindow];
    curp = [self convertPoint:curp fromView: nil];
\end{lstlisting}

Next we compute the column where the user clicked. We use a loop to start at
the top of the column and look for the highest square that is not empty. We
break out of the loop if we find one.

\begin{lstlisting}{}
    int row, col;
    col = curp.x/DIMENSION;
    for(row=BOARD_HEIGHT-1; row>=0; row--){
        if(data[row][col]!=FIELD_BLANK){
            break;
        }
    }
\end{lstlisting}

If the highest square that is not empty is the top square of the column, then
we know that the user clicked on a column that is full, and the application
beeps to inform the user of this fact.

\begin{lstlisting}{}
    if(row==BOARD_HEIGHT-1){
        NSBeep();
    }
\end{lstlisting}

The remaining case is the case of a valid move. We record the move in the data
array and display the view.

\begin{lstlisting}{}
    else{
        moves++; row++;
        data[row][col] = color;

	[self display];
\end{lstlisting}

It remains to check whether the move resulted in a winning configuration. We
check and record the result in {\tt win}, and communicate the state change to
the other player.

\begin{lstlisting}{}
	BOOL win = [self winner];

	[remote
	    remoteMoveAtRow:row Col:col Win:win];
\end{lstlisting}

The game goes into the ``game over'' state in the case of a winning move. We
pop up an alert panel to congratulate the user.

\begin{lstlisting}{}
        if(win){
            state = STATE_DONE;
	    NSString *msg =
		[NSString stringWithFormat:@"%@ wins!",
			  (color==FIELD_RED ? @"Red" : @"Yellow")];
            NSRunAlertPanel(@"Congratulations!", msg,
                            @"Ok", nil, nil);
        }
\end{lstlisting}

The game also goes into the ``game over'' state if there are no more free
squares available, in which case we have a tie, and alert the user.

\begin{lstlisting}{}
	else if(moves==BOARD_SIZE){
	    state = STATE_DONE;
	    NSRunAlertPanel(@"Game over.", @"Tie (red, yellow).",
                                @"Ok", nil, nil);
	}
\end{lstlisting}

If we didn't win and there is room on the board, then it must be the
opponent's turn, and we make the appropriate state change.

\begin{lstlisting}{}
	else{
	    state = STATE_REMOTE_MOVE;
	}
    }
}
\end{lstlisting}

Note that we recorded the state change before we displayed the alert panel in
the two cases where an alert was necessary. This is because the alert panel
runs a modal loop during which our board could receive events from the
opponent. The way the code is written we will not overwrite any state changes
that could occur while the alert panel is on screen.

The last section of the board class implements the protocol {\tt ConnectFour},
which we saw earlier, and which makes communication between the two players
possible. The first method ({\tt remoteNewGame}) is invoked when the opponent
decides to restart the game. We do the same, thus keeping the two boards in
sync.

\begin{lstlisting}{}
- (oneway void)remoteNewGame
{
    [self newGame];
}
\end{lstlisting}

The second method is invoked when the opponent quits the game. We are
observing the notification ``connection died'', which we stop observing when
the opponent quits. We terminate the application once this is done.

\begin{lstlisting}{}
- (oneway void)remoteQuit
{
    [[NSNotificationCenter defaultCenter]
	removeObserver:self];
    [[NSApplication sharedApplication] terminate:self];
}
\end{lstlisting}

The method {remoteMoveAtRow:Col:Win} is the most important one of the protocol
and it keeps the two boards in sync. It starts by incrementing the move
counter, records the move in the data array and displays the board.

\begin{lstlisting}{}
- (oneway void)remoteMoveAtRow:(int)row Col:(int)col Win:(BOOL)win
{
    moves++;        
    data[row][col] = 
	(color==FIELD_RED ? FIELD_YELLOW : FIELD_RED);

    [self display];
\end{lstlisting}

If the opponent reached a winning position, then we lost the game, change
state to ``game over'' and run an appropriate alert.

\begin{lstlisting}{}
    if(win){
	state = STATE_DONE;
	NSString *msg =
	    [NSString stringWithFormat:@"%@ loses.",
		      (color==FIELD_RED ? @"Red" : @"Yellow")];
	NSRunAlertPanel(@"Game over.", msg,
			@"Ok", nil, nil);
    }
\end{lstlisting}

We also go into the state ``game over'' if there is no more room on the
board. This is exactly the same as in the method {\tt mouseDown:}, and we also
run an alert panel.

\begin{lstlisting}{}
    else if(moves==BOARD_SIZE){
	state = STATE_DONE;
	NSRunAlertPanel(@"Game over.", @"Tie (red, yellow).",
			@"Ok", nil, nil);
    }
\end{lstlisting}

The last case occurs if the opponent's move wasn't a winning one and there is
room on the board. In that case it's our turn; by the way, the observation
about not changing state after the panel appears applies here as well.

\begin{lstlisting}{}
    else{
	state = STATE_LOCAL_MOVE;
    }
}

@end
\end{lstlisting}

The last part of this recipe is the controller. It responds to two
notifications: ``application finished launching'' and ``connection died.'' It
stores the window and the board in instance variables, as well as two strings
that describe the two players and a proxy object that represents the board
object of the opponent.

\begin{lstlisting}{}
@interface Controller : NSObject
{
    NSWindow *window;
    Board *board;

    NSString *localName, *remoteName;
    id <ConnectFour> remote;
}
\end{lstlisting}

The first two methods respond two the two notifications described above. The
remaining two methods are action methods that respond to clicks on the main
menu. They restart the game and terminate the application, respectively.

\begin{lstlisting}{}
- (void)applicationDidFinishLaunching:(NSNotification *)notif;
- (void)connectionDied: (NSNotification *)notif;

- newGame:(id)sender;
- terminate:(id)sender;
@end
\end{lstlisting}

It is the job of {\tt applicationDidFinishLaunching:} to build the two
components of the GUI: the main menu and the window that displays the
board. It must also read command line parameters that determine the color of
the local player and the host and username of the opponent. It first retrieves
the array of arguments and checks that we have the right number and raises an
exception otherwise.

\begin{lstlisting}{}
@implementation Controller

- (void)applicationDidFinishLaunching:(NSNotification *)notif
{
    NSProcessInfo *procInfo = [NSProcessInfo processInfo];
    NSArray *args = [procInfo arguments];
    
    if([args count]!=4){
        [NSException raise:NSInvalidArgumentException
                     format:@"args are: <color> <host> <user>"];
    }
\end{lstlisting}

The first argument gives the color of the local player. We retrieve it from
the argument array and compare it against the strings ``red'' and ``yellow''
and store the result in the variable {\tt lcolor}. We raise an exception if
the user entered an unknown color.

\begin{lstlisting}{}
    NSString *colName = [args objectAtIndex:1];
    FIELD_STATE lcolor;
    if([colName isEqualToString:@"red"]){
	lcolor = FIELD_RED;
    }
    else if([colName isEqualToString:@"yellow"]){
	lcolor = FIELD_YELLOW;
    }
    else{
        [NSException raise:NSInvalidArgumentException
                     format:@"color must be red or yellow"];
    }
\end{lstlisting}

The three parameters {\tt remoteHost}, {\tt remoteUser} and {\tt remoteColor}
describe the opponent. They are initialized from the array of arguments.

\begin{lstlisting}{}
    NSString 
	*remoteHost = [args objectAtIndex:2],
	*remoteUser = [args objectAtIndex:3],
	*remoteColor = 
	(lcolor == FIELD_RED ?
	 @"yellow" : @"red");
\end{lstlisting}

The next step is to allocate and initialize the board and the window whose
content view it will become. The window will not be resizable.

\begin{lstlisting}{}
    board  = [[Board alloc] initWithColor:lcolor];

    window = 
        [[NSWindow alloc] 
            initWithContentRect:[board frame]
            styleMask:NSTitledWindowMask		       
            backing:NSBackingStoreRetained 
            defer:NO];
\end{lstlisting}

The title of the window shows the color of the local player and the name and
host of the opponent. We make the board the content view of the window and set
the window delegate.

\begin{lstlisting}{}
    NSString *title =
	[NSString 
	    stringWithFormat:@"Connect Four: %@ vs. %@-%@",
	    colName, remoteHost, remoteUser];
    [window setTitle:title];                
    
    [window setContentView:board];
    [window setDelegate:self];
\end{lstlisting}

The variable {\tt localName} holds the name under which we will register the
local connection and make the local board available to the opponent.

\begin{lstlisting}{}
    localName =
	[NSString 
	    stringWithFormat:
		@"connectfour-%@-%@", NSUserName(), colName];
    [localName retain];
\end{lstlisting}

We retrieve the default connection, set its root object and attempt to
register it under the local name. We raise an exception if we couldn't
register under the local name.

\begin{lstlisting}{}
    NSConnection *conn = [NSConnection defaultConnection];

    [conn setRootObject:board];
    if(![conn registerName:localName]){
        [NSException raise:NSGenericException
                     format:@"couldn't register %@", localName];
    }
\end{lstlisting}

The next step is to constuct the name of the remote connection. It has the
same form as the local name. (This is important.) It contains the remote user
and color and a prefix that identifies the application.

\begin{lstlisting}{}
    remoteName =
	[NSString 
	    stringWithFormat:
		@"connectfour-%@-%@", remoteUser, remoteColor];
    [remoteName retain];
\end{lstlisting}

We now try to establish a connection. We try this sixty times, with a pause of
three seconds between each attempt, for a total of three minutes. We try to
obtain the proxy object under the remote name at the remote host. We sleep for
three seconds if we fail and then we try again. We log each attempt so that
the user sees what the application is doing.

\begin{lstlisting}{}
    #define MAXTRIES 60
    #define SLEEPSECS 3
    int tries = 0;
    do {
	remote = (id <ConnectFour>)
	    [NSConnection 
		rootProxyForConnectionWithRegisteredName:
		    remoteName host:remoteHost];
	if(remote==nil){
	    [NSThread
		sleepUntilDate: 
		    [NSDate dateWithTimeIntervalSinceNow:
				SLEEPSECS]];     
	}
        NSLog(@"trying %@ %@", remoteHost, remoteName);
    } while(remote==nil && tries++ < MAXTRIES);
\end{lstlisting}

If the variable {\tt remote} is nil after the loop has finished, then we
failed to obtain a connection and raise an exception. Otherwise we retain the
proxy object and set the corresponding instance variable of the local board
object.

\begin{lstlisting}{}
    if(remote==nil){
        [NSException raise:NSGenericException
                     format:@"couldn't connect to %@", remoteName];
    }
    [(NSObject *)remote retain];
    [board setRemote:remote];
\end{lstlisting}

We add ourselves to the default notification center as an observer of the
notification ``connection died.'' We want to be notified so that we may
terminate the application.

\begin{lstlisting}{}
    [[NSNotificationCenter defaultCenter]
	addObserver:self
	selector:@selector(connectionDied:)
	name:NSConnectionDidDieNotification
	object:[(NSDistantObject *)remote connectionForProxy]];
\end{lstlisting}

We may order the window to the front once we have registered under the local
name and obtained the proxy object of our opponent. The window is centered on
screen.

\begin{lstlisting}{}
    [window center];
    [window orderFrontRegardless];
    [window makeKeyWindow];
\end{lstlisting}

The last step is to construct the main menu, which has two entries, one to
restart the game and another one to quit the application. Now the application
is ready to go.

\begin{lstlisting}{}
    NSMenu *menu = [NSMenu new];

    [menu addItemWithTitle: @"New Game"
          action:@selector(newGame:)
          keyEquivalent:@"n"];
    [menu addItemWithTitle: @"Quit"
          action:@selector(terminate:)
          keyEquivalent:@"q"];
    [NSApp setMainMenu:menu];

    [menu display];
}
\end{lstlisting}

The next method responds to the notification that the connection died. We
don't do anything fancy here; we release the proxy object and raise an
exception.

\begin{lstlisting}{}
- (void)connectionDied: (NSNotification *)notif
{
    [(NSObject *)remote release];
    [NSException raise:NSGenericException
		 format:@"connection died: %@", remoteName];
}
\end{lstlisting}

The two action methods are last. The method {\tt newGame:} is invoked from the
menu. It communicates the state change to the opponent and resets the local
state and board.

\begin{lstlisting}{}
- newGame:(id)sender
{
    [remote remoteNewGame];
    [board newGame];
    return self;
}
\end{lstlisting}

The last method in this recipe is the method {\tt terminate:}, which is also
invoked from the menu. We do not want to receive the notification ``connection
died'' when the opponent's application quits in response to our message. Hence
we remove ourselves from the notification center. The next step is to tell the
other side to quit. We terminate the application once this is done.

\begin{lstlisting}{}
- terminate:(id)sender
{
    [[NSNotificationCenter defaultCenter]
	removeObserver:self];
    [remote remoteQuit];
    [[NSApplication sharedApplication] terminate:self];
}

@end
\end{lstlisting}

This is the end of the recipe, which is based on the DO tutorials by Nicola
Pero and Adam Fedor. We do not show the function {\tt main}, since it is
identical to what we have e.g. in the n-queens recipe.
