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

Colin Eberhardt
Learn how to make a board game for iOS!

Learn how to make a board game for iOS!

Previous gaming tutorials on this site have looked at creating beat ‘em up games, platformer games, or space shooter games – but some of us like to take life at a more relaxed pace!

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.

Reversi is a turn-based game played on an 8×8 board. Two players, represented by black and white pieces, take turns placing their pieces on the board.

By surrounding one of your opponent’s pieces with pieces of your own color, you flip, or capture, your opponent’s piece. The winner is the player with most squares covered at the end of the game.

So shall we play a game? Let’s dive into the tutorial and get started right away!

Getting Started

This game uses a number of graphical assets which are included in a starter project. Download the starter project here.

Download, then build and run this project in order to verify that you have everything setup correctly. Once the app launches, you should see the following screen:

The starter project is an iOS Single View Application, iPad only, with ARC and unit tests enabled. In order to give you a quick head start, there are already a few controls in the SHCViewController nib file with corresponding outlets.

The project also includes an interesting little utility class, SHCMulticastDelegate — but more on this later.

Nail Down that Board – Setting up the Game Board Model

What would a board game be without a board? You’ll add a class to the project that represents the playing board.

Highlight the ‘Model’ group in the project and create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the new class SHCBoard and make it a subclass of NSObject, as such:

This class will represent an 8×8 playing board. Each square can either be empty, occupied by a black piece, or occupied by a white piece. To keep track of the state of each square, you should use an enumeration.

To add an enumeration, highlight the “Model” group and create a new file with the iOS\C and C++\Header File template, and name the file BoardCellState.h, like so:

In BoardCellState.h, add the following code just below the #define directive:

typedef NS_ENUM(NSUInteger, BoardCellState) {
    BoardCellStateEmpty = 0,
    BoardCellStateBlackPiece = 1,
    BoardCellStateWhitePiece = 2
};

This will set up an enumeration, which gives you a friendly set of names like ‘BoardCellStateEmpty’ to help indicate the state of the square on the board. Otherwise, you would need to remember that 0 means empty, 1 means black, and 2 means white — and you’ve got enough to remember as it is. :]

Along with making your code easier to follow, an enumeration allows Xcode to assist you with things such as code completion and type checking. ‘BoardCellState’ is now a type of its own – just like NSInteger or CGFloat. So this helps you save time and avoid mistakes as well.

Now you need to define the interface for working with your gameboard. Open SHCBoard.h and replace everything below the standard framework import with the following code:

#import "BoardCellState.h"
 
/** An 8x8 playing board. */
@interface SHCBoard : NSObject
 
// gets the state of the cell at the given location
- (BoardCellState) cellStateAtColumn:(NSInteger)column andRow:(NSInteger)row;
 
// sets the state of the cell at the given location
- (void) setCellState:(BoardCellState)state forColumn:(NSInteger)column andRow:(NSInteger)row;
 
// clears the entire board
- (void) clearBoard;
 
@end

The interface for the playing board has three methods: one for getting the state of a cell, one for setting the state, and one more to clear the state of all cells on the board at once.

Okay, now you need to implement your board interface methods. Open up SHCBoard.m and replace the empty class implementation with the following code:

@implementation SHCBoard
{
    NSUInteger _board[8][8];
}
 
- (id)init
{
    if (self = [super init]){
        [self clearBoard];
    }
    return self;
}
 
- (BoardCellState)cellStateAtColumn:(NSInteger)column andRow:(NSInteger)row
{
    return _board[column][row];
}
 
- (void)setCellState:(BoardCellState)state forColumn:(NSInteger)column andRow:(NSInteger)row
{
    _board[column][row] = state;
}
 
- (void)clearBoard
{
    memset(_board, 0, sizeof(NSUInteger) * 8 * 8);
}
@end

The code above implements each of your board interface methods. The clearBoard method makes use of the C memset function to set each byte of the block of memory occupied by this array to zero. This way, you will be sure that your array is initialized to a known state of all zeros.

You may notice that old C-style arrays are being used to store the state of each board cell. The Cocoa framework supports 1-dimensional arrays with NSArray, but has no support for 2D arrays, so you’ll be rocking it old-school with the C-style arrays.

However, the issue with using old C-style arrays is that there is no automatic bounds-checking performed when reading or writing to the array. You can quite happily set the state of a cell at “column = 34, row = 45” and write to some random piece of memory. Uh oh!

NSArray performs automatic bounds checking and throws a NSRangeException if an out-of-bounds access occurs. But since you’re using C-style arrays, adding some custom bounds checking to the _board array is a must-have for your app.

But how would you test your bound-checking error condition? You could stick a temporary line of code in your app that will access row #45 in the app, and make sure it crashes with an exception. But that’s pretty hacky for a code ninja like you. :]

How else can you tell that the bounds checking is working?

Unit tests to the rescue!

Unit Tests – Preventing Buggy Code Since 2005

Unit tests are a set of scenarios that exercise small pieces of your code (known as the ‘units’). In Xcode, the set of unit tests is set up as a separate target. This means some bits of code can be shared between the app and the tests, and other bits of code are just standalone tests and won’t affect the app.

Sad dog image courtesy of lampelina.

To add your first unit test, select the SHCReversiGameTests folder, and add a file using the iOS\Cocoa Touch\Objective-C test case class template. Name the class SHCBoardTest. The generated source files will already have the imports required for running tests.

Open up SHCBoardTest.m and replace the contents with the following:

#import "SHCBoardTest.h"
#import "SHCBoard.h"
 
@implementation SHCBoardTest
 
- (void)test_setCellState_setWithValidCoords_cellStateIsChanged
{
    SHCBoard* board = [[SHCBoard alloc] init];
 
    // set the state of one of the cells
    [board setCellState:BoardCellStateWhitePiece forColumn:4 andRow:5];
 
    // verify
    BoardCellState retrievedState = [board cellStateAtColumn:4 andRow:5];
    STAssertEquals(BoardCellStateWhitePiece, retrievedState, @"The cell should have been white!");
}
 
- (void)test_setCellState_setWithInvalidCoords_exceptionWasThrown
{
    SHCBoard* board = [[SHCBoard alloc] init];
 
    @try {
        // set the state of a cell at an invalid coordinate
        [board setCellState:BoardCellStateBlackPiece forColumn:10 andRow:5];
 
        // if an exception was not thrown, this line will be reached
        STFail(@"An exception should have been thrown!");
    }
    @catch (NSException* e) {
 
    }
}
 
@end

The first test test_setCellState_setWithValidCoords_cellStateIsChanged checks that when the value of a cell is ‘set’, the same value is returned when you ‘get’ the cell value.

The second test test_setCellState_setWithInvalidCoords_exceptionWasThrown checks that an exception is thrown if you try to set a cell that is outside of the bounds of the board.

Note: If you are wondering about the odd method names, they follow the popular convention defined by Ray Osherove – [UnitOfWork_StateUnderTest_ExpectedBehavior].

Run your tests by selecting the Product\Test menu option. The test classes will be built and then run in the simulator. You should find that the test fails with an indication of the line that failed, as shown below:

You might be thinking, “Why on earth did I just write and run a test that I knew would fail?” That’s a good question!

This is the development principle known as test-first. By writing the test and observing that it fails, you are confirming that your test is actually working properly and adding value — essentially, you are “testing” the test.

Since your test showed that accessing the C array will not throw an out-of-bounds exception, you can now update the class to put the bounds-checking code in place.

In SHCBoard.m, replace the getter / setter implementation with the following code:

- (BoardCellState)cellStateAtColumn:(NSInteger)column andRow:(NSInteger)row
{
    [self checkBoundsForColumn:column andRow:row];
    return _board[column][row];
}
 
- (void)setCellState:(BoardCellState)state forColumn:(NSInteger)column andRow:(NSInteger)row
{
    [self checkBoundsForColumn:column andRow:row];
    _board[column][row] = state;
}
 
- (void)checkBoundsForColumn:(NSInteger)column andRow:(NSInteger)row
{
    if (column < 0 || column > 7 || row < 0 || row > 7)
        [NSException raise:NSRangeException format:@"row or column out of bounds"];
}

This adds some bounds checking to the getters and setters to avoid accessing invalid memory.

Re-run the unit tests (keyboard shortcut ⌘U), and with the bounds checking above in place, you should find that your tests pass. Great!

It’s a good idea to get into the habit of thinking up and coding tests as you work on your application’s logic. Your challenge — should you choose to accept it — is to come up with more unit tests for your code as you follow along with the tutorial.

Now, back to the gameplay logic.

Additional Game State

Now that you have a class representing the state of each square, you’ll create a class to hold the rest of the game state, such as the score for each side.

Highlight the ‘Model’ group in the project and create a new file using the iOS\Cocoa Touch\Objective-C class template. Name this new class SHCReversiBoard and make it a subclass of SHCBoard.

Open up SHCReversiBoard.h, and add the following code after the @interface line:

// the white player's score
@property (readonly) NSInteger whiteScore;
 
// the black payer's score
@property (readonly) NSInteger blackScore;
 
// sets the board to the opening positions for Reversi
- (void) setToInitialState;

You can let Xcode automatically synthesize those two properties. Open up SHCReversiBoard.m add the following method implementation (just beneath the @implementation line):

- (void)setToInitialState
{
    // clear the board
    [super clearBoard];
 
    // add initial play counters
    [super setCellState:BoardCellStateWhitePiece forColumn:3 andRow:3];
    [super setCellState:BoardCellStateBlackPiece forColumn:4 andRow:3];
    [super setCellState:BoardCellStateBlackPiece forColumn:3 andRow:4];
    [super setCellState:BoardCellStateWhitePiece forColumn:4 andRow:4];
 
    _whiteScore = 2;
    _blackScore = 2;
}

The above code sets the initial starting point of a game of Reversi: two black playing pieces and two white, occupying the centre four squares of the board.

Would you like to practice your unit testing skills? Try adding a unit test for the above method!

Now that the board is set to its initial state, you’ll need to create a View so that the players can see the gameboard on screen.

Visualizing the Board

The starter app has a background image for the entire playing field. However, you need to create a view to place on top of each board square, that will contain the appropriate image for that square (white piece, black piece, or empty).

To do this, highlight the ‘View’ group in the project and create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class SHCBoardSquare and make it a subclass of UIView.

Open up the newly created header file SHCBoardSquare.h. The board square needs to know about the board as a whole, so add one more #import as below:

#import "SHCReversiBoard.h"

Then add the following method declaration after the @interface line:

- (id) initWithFrame:(CGRect)frame column:(NSInteger)column row:(NSInteger)row board:(SHCReversiBoard*)board;

Each square will be initialized with its row and column position, and each square will also know which board it belongs to.

“Wait”, you may be saying. “I thought we only had one board here? What’s going on?”

Later on in the tutorial, you’ll be using multiple boards to implement computer opponents — but for now just leave the declaration as-is so that you don’t need to rework everything later on.

Open up SHCBoardSquare.m. Replace the implementation with the following code:

@implementation SHCBoardSquare
{
    int _row;
    int _column;
    SHCReversiBoard* _board;
    UIImageView* _blackView;
    UIImageView* _whiteView;
}
 
- (id)initWithFrame:(CGRect)frame column:(NSInteger)column row:(NSInteger)row board:(SHCReversiBoard *)board
{
    self = [super initWithFrame:frame];
    if (self) {
 
        _row = row;
        _column = column;
        _board = board;
 
        // create the views for the playing piece graphics
        UIImage* blackImage = [UIImage imageNamed: @"ReversiBlackPiece.png"];
        _blackView = [[UIImageView alloc] initWithImage: blackImage];
        _blackView.alpha = 0.0;
        [self addSubview:_blackView];
 
        UIImage* whiteImage = [UIImage imageNamed: @"ReversiWhitePiece.png"];
        _whiteView = [[UIImageView alloc] initWithImage: whiteImage];
        _whiteView.alpha = 0.0;
        [self addSubview:_whiteView];
 
        self.backgroundColor = [UIColor clearColor];
 
        [self update];
    }
    return self;
}
 
// updates the UI state
- (void)update
{
    // show / hide the images based on the cell state
    BoardCellState state = [_board cellStateAtColumn:_column andRow:_row];
    _whiteView.alpha = state == BoardCellStateWhitePiece ? 1.0 : 0.0;
    _blackView.alpha = state == BoardCellStateBlackPiece ? 1.0 : 0.0;
}
 
@end

The initWithFrame:column:row:board: method stores the various arguments that are passed to it as instance variables. It also creates two UIImageView instances: one to display a black playing piece and the other to display a white playing piece.

The update method shows or hides the black or white images depending on the state of the cell that it represents. If there is no playing piece in the square, both image views are hidden.

Okay, so that takes care of a single cell on the board. The next section shows you how to arrange these single cells into a full gameboard!

Views^2 – Views in Views

In order to render the board, you’ll need 8×8 or 64 instances of SHCBoardSquare properly positioned on screen. To keep things organized, you will set up another view that will hold the 64 squares.

Highlight the ‘View’ group in the project and create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class SHCReversiBoardView and make it a subclass of UIView.

Open up SHCReversiBoardView.h. Add the following import to the top of the file so your board view will know about boards:

#import "SHCReversiBoard.h"

Then you can add the declaration for the designated initializer underneath the @interface line, as such:

- (id)initWithFrame:(CGRect)frame andBoard:(SHCReversiBoard*) board;

Okay, now it’s time to flesh out the implementation of this method. Open up SHCReversiBoardView.m and replace the current contents with the code below:

#import "SHCReversiBoardView.h"
#import "SHCBoardSquare.h"
 
@implementation SHCReversiBoardView
 
- (id)initWithFrame:(CGRect)frame andBoard:(SHCReversiBoard *)board
{
    if (self = [super initWithFrame:frame])
    {
        float rowHeight = frame.size.height / 8.0;
        float columnWidth = frame.size.width / 8.0;
 
        // create the 8x8 cells for this board
        for (int row = 0; row < 8; row++)
        {
            for (int col = 0; col < 8; col++)
            {
                SHCBoardSquare* square = [[SHCBoardSquare alloc] initWithFrame:CGRectMake(col*columnWidth, row*rowHeight, columnWidth, rowHeight) column:col row:row board:board];
                [self addSubview:square];
            }
        }
 
        self.backgroundColor = [UIColor clearColor];
    }
    return self;
}
 
@end

In the code above, the rowHeight and columnWidth sizes are first calculated. You’ll hold on to these values to set the size and positioning of the 64 squares on the board.

Next up is a double for loop to set up the 8×8 grid. For each turn of the inner loop, a new instance of SHCBoardSquare is allocated and positioned.

Now that the view is set up, it’s time to glue everything together to create the Reversi Board and View within the application’s view controller!

Open up SHCViewController.m and add the following imports at the top of the file:

#import "SHCReversiBoard.h"
#import "SHCReversiBoardView.h"

Then, just beneath the @implementation, add the following instance variables:

@implementation SHCViewController
{
    SHCReversiBoard* _board;
}

Next, add the lines below at the end of viewDidLoad:

// create our game board
_board = [[SHCReversiBoard alloc] init];
[_board setToInitialState];
 
// create a view
SHCReversiBoardView* reversiBoard = [[SHCReversiBoardView alloc] initWithFrame:CGRectMake(88,151,600,585) andBoard:_board];
[self.view addSubview:reversiBoard];

Here you create your game board model class, as well as the view for the model.

The CGRect used as the frame for the SHCReversiBoardView has been carefully measured to align with the PNG image that is the background for this application.

Build and run to see the fruits of your labour:

Cool – your app is starting to look like a real game now!

Well, a pretty board is fun to look at, but your prospetcive players will be itching to put some pieces on the board. Fortunately, the next section lets you do just that.

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.

All Tapped Out – Gesture Recognition

Open up SHCBoardSquare.m, and add the following code at the end of initWithFrame :

// add a tap recognizer
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
                                         initWithTarget:self action:@selector(cellTapped:)];
[self addGestureRecognizer:tapRecognizer];

The code above adds a tap gesture recognizer, which results in the cellTapped method being invoked.

Add the following method implementation to SHCBoardSquare.m:

- (void)cellTapped:(UITapGestureRecognizer*)recognizer
{
    if ([_board isValidMoveToColumn:_column andRow:_row])
    {
        [_board makeMoveToColumn:_column andRow:_row];
    }
}

This checks whether a move is valid, and if so, makes the move. The SHCReversiBoard class keeps track of whose turn it is.

Okay! Now’s your chance to see this all in action — you’ve earned it! :]

Build and run the application, and tap on the empty spaces on the board. You should see alternate black and white pieces being placed, as such:

The game is starting to take shape. But there’s something not quite right about it, which you will instantly recognize if you have played Reversi before. The game just lets you place pieces wherever you like, which is contrary to the rules of the game.

It’s not quite total anarchy, but you’ll need to start implementing the rules of the game in order to maintain some semblance of order around here! :]

Devil’s in the Details – Detailed Game Logic

In Reversi, when a piece is played, it must surround one or more of your opponent’s pieces either horizontally, vertically or diagonally.

To illustrate this. consider the playing board below, with black set to play next:

The position marked ‘A’ on the board is a valid move, because it would surround a white piece to the left. However, ‘B’ is not a valid move as it would not surround any white pieces in any direction.

So in order to determine whether a move is valid or not, you need to check for surrounded pieces in eight different directions.

This sounds like a lot of work! However, the logic applied for all eight directions is exactly the same: one or more pieces must be surrounded. You can use this commonality to come up with a concise way of checking if a move is valid.

Open up SHCReversiBoard.m, and add the following at the top of the file just beneath the #import statements:

// A 'navigation' function. This takes the given row / column values and navigates in one of the 8 possible directions across the playing board.
typedef void (^BoardNavigationFunction)(NSInteger*, NSInteger*);
 
BoardNavigationFunction BoardNavigationFunctionRight = ^(NSInteger* c, NSInteger* r) {
    (*c)++;
};
 
BoardNavigationFunction BoardNavigationFunctionLeft = ^(NSInteger* c, NSInteger* r) {
    (*c)--;
};
 
BoardNavigationFunction BoardNavigationFunctionUp = ^(NSInteger* c, NSInteger* r) {
    (*r)--;
};
 
BoardNavigationFunction BoardNavigationFunctionDown = ^(NSInteger* c, NSInteger* r) {
    (*r)++;
};
 
BoardNavigationFunction BoardNavigationFunctionRightUp = ^(NSInteger* c, NSInteger* r) {
    (*c)++;
    (*r)--;
};
 
BoardNavigationFunction BoardNavigationFunctionRightDown = ^(NSInteger* c, NSInteger* r) {
    (*c)++;
    (*r)++;
};
 
BoardNavigationFunction BoardNavigationFunctionLeftUp = ^(NSInteger* c, NSInteger* r) {
    (*c)--;
    (*r)++;
};
 
BoardNavigationFunction BoardNavigationFunctionLeftDown = ^(NSInteger* c, NSInteger* r) {
    (*c)--;
    (*r)--;
};

The above code typedefs a block, BoardNavigationFunction, which takes as its arguments pointers to two integers. It is then followed by eight blocks, each of which de-reference the two arguments and either increment or decrement them. It looks a little strange, to be sure, but you’ll soon see what this is all about.

Navigate a little further down the same file and add an instance variable and init method as follows:

@implementation SHCReversiBoard
{
    BoardNavigationFunction _boardNavigationFunctions[8];
}
 
- (id)init
{
    if (self = [super init]) {
        [self commonInit];
        [self setToInitialState];
    }
    return self;
}
 
- (void)commonInit
{
    // create an array of all 8 navigation functions
    _boardNavigationFunctions[0] = BoardNavigationFunctionUp;
    _boardNavigationFunctions[1] = BoardNavigationFunctionDown;
    _boardNavigationFunctions[2] = BoardNavigationFunctionLeft;
    _boardNavigationFunctions[3] = BoardNavigationFunctionRight;
    _boardNavigationFunctions[4] = BoardNavigationFunctionLeftDown;
    _boardNavigationFunctions[5] = BoardNavigationFunctionLeftUp;
    _boardNavigationFunctions[6] = BoardNavigationFunctionRightDown;
    _boardNavigationFunctions[7] = BoardNavigationFunctionRightUp;
 
}

The above code creates an array of these navigation functions, adding each of the eight blocks defined earlier.

In order to put all those blocks to use, you now need to implement the logic that checks whether placing your piece on a specific board square would surround some of the opponent’s playing pieces.

Add the following method to SHCReversiBoard.m:

- (BOOL) moveSurroundsCountersForColumn:(NSInteger) column andRow:(NSInteger)row withNavigationFunction:(BoardNavigationFunction) navigationFunction toState:(BoardCellState) state
{
    NSInteger index = 1;
 
    // advance to the next cell
    navigationFunction(&column, &row);
 
    // while within the bounds of the board
    while(column>=0 && column<=7 && row>=0 && row<=7)
    {
        BoardCellState currentCellState = [super cellStateAtColumn:column andRow:row];
 
        // the cell that is the immediate neighbour must be of the other colour
        if (index == 1)
        {
            if(currentCellState!=[self invertState:state])
            {
                return NO;
            }
        }
        else
        {
            // if we have reached a cell of the same colour, this is a valid move
            if (currentCellState==state)
            {
                return YES;
            }
 
            // if we have reached an empty cell - fail
            if (currentCellState==BoardCellStateEmpty)
            {
                return NO;
            }
        }
 
        index++;
 
        // advance to the next cell
        navigationFunction(&column, &row);
    }
 
    return NO;
}

This method determines whether a move to a specific location on the board would surround one or more of the opponent’s pieces. This method uses the supplied navigationFunction to move from one cell to the next. Notice that because the row and column are integers, which are passed by value, the ampersand (&) operator is used to pass a pointer to the integer, allowing its value to be changed by the navigation block.

Within the while loop, the required conditions are checked: the neighbouring cell must be occupied by a piece of the opposing colour, and following cells can be either the opposing colour (in which case the while loop continues), or the player’s colour – which means that a group has been surrounded.

Note: Do you want some more practice with unit testing? The moveSurroundsCountersForColumn method is an excellent opportunity to practice your unit test skills to ensure that you have full branch coverage!

Finally, you’ll need to update the logic in isValidMoveForColumn:andRow which checks whether a board square is empty. Replace the previous implementation of the method with the following:

- (BOOL)isValidMoveToColumn:(int)column andRow:(int) row;
{
    return [self isValidMoveToColumn:column andRow:row forState:self.nextMove];
}
 
- (BOOL)isValidMoveToColumn:(int)column andRow:(int)row forState:(BoardCellState)state
{
    // check the cell is empty
    if ([super cellStateAtColumn:column andRow:row] != BoardCellStateEmpty)
        return NO;
 
    // check each direction
    for(int i=0;i<8;i++)
    {
        if ([self moveSurroundsCountersForColumn:column
                                          andRow:row
                          withNavigationFunction:_boardNavigationFunctions[i]
                                         toState:state])
        {
            return YES;
        }
    }
 
    // if no directions are valid - then this is not a valid move
    return NO;
}

The above code uses the array of navigation functions to check each in turn to see if any of the opponent’s pieces are surrounded. Note that this method has been split into two parts – one which has a state argument, and the other that uses the nextMove property. You’ll see the reason for this shortly.

Build and run your app, and tap on some of the board squares. Now you will see that the game is much fussier about where you can place the playing pieces, as in the image below:

However, the game is still a little dull – and since you haven’t yet implemented all of the rules yet, every game will result in a draw.

One of the key features of the game of Reversi is that any pieces that are surrounded by pieces of the opposite color are ‘flipped’ to the opposite color. It’s time to put this rule into action!

Add the following code to SHCReversiBoard.m:

- (void) flipOponnentsCountersForColumn:(int) column andRow:(int)row withNavigationFunction:(BoardNavigationFunction) navigationFunction toState:(BoardCellState) state
{
    // are any pieces surrounded in this direction?
    if (![self moveSurroundsCountersForColumn:column
                                       andRow:row
                       withNavigationFunction:navigationFunction
                                      toState:state])
        return;
 
    BoardCellState opponentsState = [self invertState:state];
    BoardCellState currentCellState;
 
    // flip counters until the edge of the boards is reached, or
    // a piece of the current state is reached
    do
    {
        // advance to the next cell
        navigationFunction(&column, &row);
        currentCellState = [super cellStateAtColumn:column andRow:row];
        [self setCellState:state forColumn:column  andRow:row];
    }
    while(column>=0 && column<=7 &&
          row>=0 && row<=7 &&
          currentCellState == opponentsState);
 
}

The above method shows just how useful those navigation functions are; after checking whether any pieces will be surrounded in the given direction, a short do-while loop goes about the business of flipping the opponent’s pieces.

To employ this logic when a move is made, update makeMoveToColumn:andRow: in SHCReversiBoard.m as follows:

- (void)makeMoveToColumn:(NSInteger)column andRow:(NSInteger)row
{
    // place the playing piece at the given location
    [self setCellState:self.nextMove forColumn:column  andRow:row];
 
    // check the 8 play directions and flip pieces
    for(int i=0; i<8; i++)
    {
        [self flipOponnentsCountersForColumn:column
                                      andRow:row
                      withNavigationFunction:_boardNavigationFunctions[i]
                                     toState:self.nextMove];
    }
 
    _nextMove = [self invertState:_nextMove];
 
}

This uses the array of navigation functions to flip pieces in all eight surrounding squares.

Build and run your app. You should see it behaving properly now:

Congratulations — you’ve now implemented all of the gameplay functions to play a basic game of Reversi!

Where To Go From Here?

Here is an example project of all the code you have developed in this tutorial so far.

In part two of this tutorial, you’ll be adding some code to keep score, some end-game logic to determine when the game is over, as well as a computer opponent, since playing Reversi against yourself isn’t much of a challenge. :]

Until next time, why not look at implementing a different board game, such as Go? You will find that many of the techniques that you have employed throughout this tutorial will work well for a whole range of other games.

I look forward to seeing what you will create! In the meantime, if you have any questions or comments, please join the forum discussion below.

Colin Eberhardt

Colin Eberhardt has been writing code and tutorials for many years, covering a wide range of technologies and platforms. Most recently he has turned his attention to iOS. Colin is CTO of ShinobiControls, creators of charts, grids and other powerful iOS controls.

You can check out their app, ShinobiPlay, in the App Store.

User Comments

10 Comments

  • Finally! I've been waiting for this tutorial for a long time! :D

    Thanks a lot!
    wdflu
  • Very nice! I'm sorry, but could you elaborate more on how the navigation functions work, and more about the moveSurroundsCountersForColumn: method? My C-programming kung-fu is weak as I came to Objective-C from a Ruby background, so I don't really fully comprehend typedefs and blocks.. But fantastic tutorial anyway! Learnt loads. By the way I'm @sg_gabriel here

    ------
    Get your app on the App Store in the shortest time. No designers needed.
    Beautiful iPhone design templates with reusable sliced image assets and PSD files you can customize. Focus on your coding, leave the design to us.
    http://www.getappninja.com
    Twitter: https://twitter.com/sg_gabriel
    PenguinGab
  • PenguinGab wrote:Very nice! I'm sorry, but could you elaborate more on how the navigation functions work, and more about the moveSurroundsCountersForColumn: method? My C-programming kung-fu is weak as I came to Objective-C from a Ruby background, so I don't really fully comprehend typedefs and blocks.. But fantastic tutorial anyway! Learnt loads. By the way I'm @sg_gabriel here


    Let's see if I can make it a bit more clear.

    Blocks are an Obj-C language feature that allow you to define distinct segments of code that can be passed around just like variables. A block has a 'signature'just like any other method, which details its return value and any arguments that are passed to it. The typedef is simply used to create an alias for a particular signature. This is all that typedefs do - alias things.

    The moveSurroundsCountersForColumn takes a BoardNavigationFunction, which is a block. In other words, you can pass a segment of code to this method which dictates how it moves from one cell to the next. I practice, this is one of the 8 navigation functions declared at the top of the file.

    Within moveSurroundsCountersForColumn the passed BoardNavigationFunction is invoked on each iteration. As an example, let's say BoardNavigationFunctionRight was passed to this function. When invoked, this function will increment the value of the column variable that was passed to it. This results in one move to the function moving one cell to the right. Note, this also makes use of C-style programming where the address of a variable is passed then de-referenced in order to increment / decrement it. You can probably ignore this!

    I hope this makes sense. If I were you I would read a few tutorials and just give blocks a go!

    Colin E.
    ColinEberhardt
  • Thank you for the very good tutorial!!!

    I have a question on 'moveSurroundsCountersForColumn' unit testing.
    I have no idea how I can ensure that I have full branch coverage.
    Is there any suggestions how to think about algorithms?
    nafu
  • nafu wrote:I have a question on 'moveSurroundsCountersForColumn' unit testing.
    I have no idea how I can ensure that I have full branch coverage.
    Is there any suggestions how to think about algorithms?


    Good question! in the absence of dedicated code coverage tools, this basically just takes discipline. In the past I have added comments in my code that indicate the various code paths then added unit test that refer to each of these. This makes it easier to audit your code visually in order to test for coverage.

    However, this is pretty tedious!

    If you really care about getting full coverage of branches or conditions, you are going to need tools. I've used code coverage tools within Eclipse and Visual Studio before - ones the visually highlight the code that has been covered as well as report metrics are the best.

    I haven't tried this in Xcode yet, but it does appear that it is possible:

    http://www.infinite-loop.dk/blog/2011/0 ... it-all-up/

    Regards, Colin E.
    ColinEberhardt
  • I think you have the "r" variable increment/decrement for BoardNavigationFunctionLeftUp and BoardNavigationFunctionLeftDown reversed. For BoardNavigationFunctionLeftUp, shouldn't it be (*r)--, and BoardNavigationFunctionLeftDown (*r)++? As in...

    Code: Select all

    BoardNavigationFunction BoardNavigationFunctionLeftUp = ^(NSInteger* c, NSInteger* r) {
        (*c)--;
        (*r)--;
    };

    BoardNavigationFunction BoardNavigationFunctionLeftDown = ^(NSInteger* c, NSInteger* r) {
        (*c)--;
        (*r)++;
    };
    jieuryli
  • First off: great tutorial.

    I have a question about your use of blocks.

    I tried changing the block signature from NSInteger to int, so that rather than defining it as you did:

    Code: Select all
    BoardNavigationFunction BoardNavigationFunctionRight = ^(NSInteger* c, NSInteger* r) {
        (*c)++;
    };


    I did this:

    Code: Select all
    BoardNavigationFunction BoardNavigationFunctionRight = ^(int c, int r) {
        c++;
    };


    I changed the typedef accordingly, got everything to compile and run and found that it did not work. The columns and rows do not get altered as they did with the objects. I am still new to blocks, so I wanted to know if this was a scoping issue? It actually seems to me that the int behavior makes more sense since the values passed in are supposed to be copied into the block and should not change the originals. Are you circumventing that behavior in your code by the deference?

    What am I missing?

    Thanks.

    P.S. I ran some coding tests. It looks like this has nothing to do with primitives vs. objects but rather about the fact that you are doing a deference in the block. That is what allows you to change the values of column and row. This seems to go against the idea that blocks have their own protected scope -- on the other hand it does work...:) I guess technically you are not changing the value of the parameters, they always point to the same thing, you just change the value that is being pointed at. So I think I have answered my own question.

    Thanks again!
    zenwar
  • jieuryli wrote:I think you have the "r" variable increment/decrement for BoardNavigationFunctionLeftUp and BoardNavigationFunctionLeftDown reversed.


    Ooops - well spotted! Fortunately this doesn't actually have an impact on the gameplay.

    Colin E.
    ColinEberhardt
  • zenwar wrote:I tried changing the block signature from NSInteger to int


    NSInteger and int are interchangeable, however ... you have swapped an NSInteger* for int - the first (NSInteger*) is a pointer to an integer, whereas the second (int) is an integer.

    Primitive / value types are passed by value rather than passed by reference. In order to allow the block to update the rows / columns, you have to pass a pointer to the value, which is then de-referenced and updated.

    Hope that helps!
    Colin E.
    ColinEberhardt
  • Errors when I update Unit Test code.
    I get 3 errors when I update the code in SHCBoard.m to replace the getter / setter implementation.
    The errors are:
    Undefined symbols for architecture i386:
    "_STAssertEquals", referenced from:
    -[SHCBoardTest test_setCellState_setWithValidCoords_cellStateIsChanged] in SHCBoardTest.o
    "_STFail", referenced from:
    -[SHCBoardTest test_setCellState_setWithInvalidCoords_exceptionWasThrown] in SHCBoardTest.o
    ld: symbol(s) not found for architecture i386
    clang: error: linker command failed with exit code 1 (use -v to see invocation)

    Can you please help? I'm running Xcode Version 4.5.2 (4G2008a) Thanks!
    pszerr

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in July: Facebook Pop Tech Talk!

Sign Up - July

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Jake Gundersen

... 49 total!

Update Team

  • Andy Pereira
  • Riccardo D'Antoni

Editorial Team

  • John Clem

... 23 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • David Xie
  • Myeong Hoon
  • Sungwook Yeom

... 33 total!

Subject Matter Experts

  • Richard Casey

... 4 total!