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

Learn how to make a board game for iOS!

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.

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

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.

Colin Eberhardt

Contributors

Colin Eberhardt

Author

Over 300 content creators. Join our team.