How to Develop an iPad Board Game App: Part 1/2

In this tutorial, you’ll learn how to create a more laid-back kind of game: the classic board game. Specifically, you will create the time-honoured classic Reversi for the iPad, using plain UIKit. By Colin Eberhardt.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Revers-I, Revers-You - Adding User Interaction

Currently each of the 64 SHCBoardSquare instances is associated with a specific row and column, and sets its initial state accordingly. You will expand this concept to allow the SHCBoard to notify the individual views of state changes.

Highlight the ‘Model’ group in the project and create a new file with the iOS\Cocoa Touch\Objective-C protocol template and name it SHCBoardDelegate.

Add the following code to SHCBoardDelegate:

#import "BoardCellState.h"

@protocol SHCBoardDelegate <NSObject>

- (void)cellStateChanged:(BoardCellState)state forColumn:(int)column andRow:(int) row;

@end

This delegate can be used to notify the view when the state of a cell changes. This mimics the delegate pattern used by many of the Apple UI controls.

However, there is a small problem with this. Your class exposes a delegate property that conforms to the above protocol, but this only allows for a single observer. However, you have 64 cells who are all interested in state changes. What to do?

Fortunately I came up with a a simple solution> to this problem a little while back, by designing a class that allows you to multicast to multiple delegates. The class is called SHCMulticastDelegate - the one I mentioned earlier in this tutorial.

The starter project includes this class for you. If you want to find out more about how this class works, and why it is useful, check out this post.

Open up SHCBoard.h and add the following import:

#import "SHCMulticastDelegate.h"

As well, add a new property to hold the delegate just beneath the @interface line:

// multicasts changes in cell state. Each delegate is informed of changes in state of individual cells.
@property (readonly) SHCMulticastDelegate* boardDelegate;

Open up the implementation SHCBoard.m and add the following import to the top of the file:

#import "SHCBoardDelegate.h"

Additionally, add the following instance variable just beneath the _board variable:

id<SHCBoardDelegate> _delegate;

Then create an instance of the multicasting delegate within the existing init method at the end of the current "if" block by inserting the following code:

_boardDelegate = [[SHCMulticastDelegate alloc] init];
_delegate = (id)_boardDelegate;

The code above prevents Xcode from complaining about the fact that SHCMulticastDelegate does not adopt the SHCBoardDelegate protocol. The (id) cast is basically telling Xcode – “trust me, I know what I am doing!”

The reason that the multicast delegate is being assigned to an instance variable that conforms to the SHCBoardDelegate property is because the multicasting implementation is entirely generic – which allows it to multicast delegates which are defined by any protocol.

Now that you have this delegate at your disposal, put it to use. Update the two methods that change the board state, clearBoard and setCellState:forColumn:andRow, as follows:

- (void)setCellState:(BoardCellState)state forColumn:(NSInteger)column andRow:(NSInteger)row
{
    [self checkBoundsForColumn:column andRow:row];
    _board[column][row] = state;
    [self informDelegateOfStateChanged:state forColumn:column andRow:row];
}

- (void)clearBoard
{
    memset(_board, 0, sizeof(NSInteger) * 8 * 8);
    [self informDelegateOfStateChanged:BoardCellStateEmpty forColumn:-1 andRow:-1];
}

Next, add the following method:

-(void)informDelegateOfStateChanged:(BoardCellState) state forColumn:(NSInteger)column andRow:(NSInteger) row
{
    if ([_delegate respondsToSelector:@selector(cellStateChanged:forColumn:andRow:)]) {
        [_delegate cellStateChanged:state forColumn:column andRow:row];
    }
}

In the code above, the multicast delegate is used just like a regular delegate. First, you need to check whether it responds to the given selector, and then send the message.

Note: the clearBoard call uses a ‘special’ row and column coordinate of (-1, -1), which is used to indicate that every cell has changed, rather than sending 64 separate notifications.

Now that you're sending messages to the delegate, it's time to add the code that will react to the cellStateChanged:forColumn:andRow: message.

Open up SHCBoardSquare.h, and import and adopt the delegate protocol by adding the code below:

#import "SHCBoardDelegate.h"

@interface SHCBoardSquare : UIView <SHCBoardDelegate>

Next, open up SHCBoardSquare.m and add the implementation for the single delegate method by inserting the following code:

- (void)cellStateChanged:(BoardCellState)state forColumn:(NSInteger)column andRow:(NSInteger)row
{
    if ((column == _column && row == _row) ||
        (column == -1 && row == -1))
    {
        [self update];
    }
}

The above code checks whether the _column and _row instance variables match the coordinates of the cell that has been updated. If so, the square updates its visual state. Again, the ‘special’ (-1,-1) coordinate updates all squares.

Finally, add the following code to the initWithFrame method at the end of the ‘if’ block:

[_board.boardDelegate addDelegate:self];

This ensures that when each square is created, it is added to the multi-casting delegate; in this manner, all 64 squares will be informed of state changes. With this code in place, any change to the board state will be immediately reflected in the UI. This will come in handy very shortly.

You've done a fair bit of coding, but there's a bit more to do before you can build and run. Time to break out those code ninja skills and add some game logic!

Following the Rules - Adding Game Logic

Open up SHCReversiBoard.h and add the following property and methods:

// indicates the player who makes the next move
@property (readonly) BoardCellState nextMove;

// Returns whether the player who's turn it is can make the given move
- (BOOL) isValidMoveToColumn:(NSInteger)column andRow:(NSInteger) row;

// Makes the given move for the player who is currently taking their turn
- (void) makeMoveToColumn:(NSInteger) column andRow:(NSInteger)row;

One of the main functions of the class SHCReversiBoard is to enforce the rules of the game. The above methods and properties will ensure that the players take turns, and are only able to place their playing pieces at valid locations on the board. Otherwise, total anarchy would result, and no one wants that!

Open up SHCReversiBoard.m and add the following code to setInitialState:

// Black gets the first turn
_nextMove = BoardCellStateBlackPiece;

Further down the file add the following method implementations:

-(BOOL)isValidMoveToColumn:(NSInteger)column andRow:(NSInteger)row
{
    // check the cell is empty
    if ([super cellStateAtColumn:column andRow:row] != BoardCellStateEmpty)
        return NO;
    
    return YES;
}

- (void)makeMoveToColumn:(NSInteger)column andRow:(NSInteger)row
{
    // place the playing piece at the given location
    [self setCellState:self.nextMove forColumn:column andRow:row];
    
    _nextMove = [self invertState:_nextMove];
}

- (BoardCellState) invertState: (BoardCellState)state
{
    if (state == BoardCellStateBlackPiece)
        return BoardCellStateWhitePiece;
    
    if (state == BoardCellStateWhitePiece)
        return BoardCellStateBlackPiece;
    
    return BoardCellStateEmpty;
}

A valid move can be made on any empty square, so the check performed in isValidMoveToColumn:andRow: is fairly straightforward and uncomplicated.

When a player makes a move, the setCellState:forColumn:andRow method is called using the multicasting delegate notification as described previously. Finally, the nextMove property is updated so that the other player may take a turn.

Note that nextMove was set up as a read-only property, so you can update it by accessing the backing instance variable _nextMove directly.

It feels like a long time since you've been able to build and run your app, doesn't it? Hang tight — there's just one more thing to implement: the handling of the tap gesture on the board.

Colin Eberhardt

Contributors

Colin Eberhardt

Author

Over 300 content creators. Join our team.