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

In this second and final part of the series, you will keep score, add some nice animations, and add a computer opponent. In the end, you’ll have a complete and fun game. By Colin Eberhardt.

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

I, Robot - Adding a Computer Player

This game works just great if you have a friend to play with, but there are times where you won't have anyone to play against, like those long solo coding sessions that go late into the night!

A computer opponent should be just the thing!

All right, robot — just because you're good at chess doesn't mean you'll be any good at Reversi!

The computer opponent will use a very simple "look-ahead" algorithm that will check every possible next move and determine which move would have the highest score.

The easiest way to achieve this is to ‘clone’ the current SHCReversiBoard and try each possible turn to determine how it affects the score. The benefit to this approach is that it won't make any changes to or affect the actual live playing board!

Open up SHCBoard.h and adopt the NSCopying protocol:

@interface SHCBoard : NSObject<NSCopying>

Then open up SHCBoard.m and add an implementation for copyWithZone as follows:

- (id)copyWithZone:(NSZone *)zone
{
    SHCBoard* board = [[[self class] allocWithZone:zone] init];
    memcpy(board->_board, _board, sizeof(NSUInteger) * 8 * 8);
    board->_boardDelegate = [[SHCMulticastDelegate alloc] init];
    board->_delegate = (id)_boardDelegate;
    return board;
}

This makes a copy of the state of the board, using the C memcpy function to copy the 2D array containing the 8x8 playing field.

Next, open up SHCReversiBoard.h and add the following protocol:

@interface SHCReversiBoard : SHCBoard<NSCopying>

Then, add the following copyWithZone implementation to SHCReversiBoard.m:

- (id)copyWithZone:(NSZone *)zone
{
    SHCReversiBoard* board = [super copyWithZone:zone];
    board->_nextMove = _nextMove;
    board->_whiteScore = _whiteScore;
    board->_blackScore = _blackScore;
    return board;
}

The code above simply uses the superclass implementation from SHCBoard and then copies the additional instance variables.

Now you have a board that can clone itself! Now you have everything you need to bring your computer player to life.

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

Open up SHCComputerOpponent.h and replace the contents with the following:

#import <Foundation/Foundation.h>
#import "SHCReversiBoardDelegate.h"
#import "SHCReversiBoard.h"

/** A simple computer opponent. */
@interface SHCComputerOpponent : NSObject<SHCReversiBoardDelegate>

- (id) initWithBoard:(SHCReversiBoard*)board color:(BoardCellState)computerColor;

@end

This defines a single initialization method that associates the computer player object with a given board and color.

Open up SHCComputerOpponent.m and replace the implementation with the following:

@implementation SHCComputerOpponent
{
    SHCReversiBoard* _board;
    BoardCellState _computerColor;
}

- (id)initWithBoard:(SHCReversiBoard *)board color:(BoardCellState)computerColor
{
    if (self = [super init])
    {
        _board = board;
        _computerColor = computerColor;
        
        // listen to game state changes in order to know when to make a move
        [_board.reversiBoardDelegate addDelegate:self];
    }
    return self;
}
@end

The instance variables in the code above will store the board and piece color passed in through the init method. The object also registers itself as a delegate, so it will be informed when the game state changes — that is, when a player takes their turn.

At this point the compiler will be complaining that SHCComputerOpponent is an ‘incomplete implementation’ because it does not implement the methods defined by SHCReversiBoardDelegate. Add these two methods to complete the implementation:

- (void)gameStateChanged
{
    if (_board.nextMove == _computerColor)
    {
        // pause 1 second, then make a move
        [self performSelector:@selector(makeNextMove) withObject:nil afterDelay:1.0];
    }
}

// Determines which move to make
- (void)makeNextMove
{
    NSInteger bestScore = NSIntegerMin;
    NSInteger bestRow, bestColumn;;
    
    // check every possible move, then select the one with the best 'score'
    for (NSInteger row = 0; row < 8; row++)
    {
        for (NSInteger col = 0; col < 8; col++)
        {
            if ([_board isValidMoveToColumn:col andRow:row])
            {
                // clone the current board
                SHCReversiBoard* testBoard = [_board copyWithZone:nil];
                
                // make this move
                [testBoard makeMoveToColumn:col andRow:row];
                
                // compute the score - i.e. the difference in black and white score
                int score = _computerColor == BoardCellStateWhitePiece ?
                                testBoard.whiteScore - testBoard.blackScore :
                                testBoard.blackScore - testBoard.whiteScore;
                
                // record the best score
                if (score > bestScore)
                {
                    bestScore = score;
                    bestRow = row;
                    bestColumn = col;
                }
            }
        }
    }
    
    if (bestScore > NSIntegerMin)
    {
        [_board makeMoveToColumn:bestColumn andRow:bestRow];
    }
}

Okay, that's a fair chunk of code. However, take it piece by piece, and it will be easy to see what's going on.

The gameStateChanged method checks whether it is the computer’s turn, and if so, pauses for one second before making a move. The reason for this pause is purely cosmetic — it just makes it appear that the computer is thinking! It's the little things like this that really add "polish" to your app.

The makeNextMove method is a bit more complex. It iterates through each cell in the board to find all valid moves. When it finds a valid move, it clones the board using the NSCopying protocol that was added earlier and tries out the move and determines the resulting score. In this manner, the computer player will be able to look-ahead at every possible next move and evaluate the outcome.

For Reversi, finding the "best" move out of all possible moves is fairly simple. The goal of the game is to occupy as many cells on the board as possible, so the "best" move is the one that leaves the board in a state with as many of the player's pieces as possible.

The makeNextMove method keeps track of the move that would provide the best score, then once all permutations have been checked, makes this "best" move on the "real" board.

Note: the NSCopying protocol has proved to be really useful in this process – it allows you to perform ‘what if’ style analysis, by making moves on a cloned board, then throwing away the board copy when done. Again, these ‘what if’ moves have no effect on the real playing board!

To put the computer player into action, open up SHCViewController.m and add the following import:

#import "SHCComputerOpponent.h"

A few lines further down, add an instance variable for the computer opponent next to the existing instance variable:

SHCComputerOpponent* _computer;

And finally within viewDidLoad, add the following to the end of the method:

_computer = [[SHCComputerOpponent alloc] initWithBoard:_board color:BoardCellStateWhitePiece];

This initializes the computer opponent, passing in the game state and his color.

Build and run your app, and play a game against your computer opponent. You control the black pieces and get the first move. See if you can kick your competitor's shiny metal butt! :]

The current implementation of the computer opponent doesn't present much of a challenge, to be honest. It only looks one move ahead, and can easily be outwitted.

A quick tip to beat the computer: the edges and the corners of the board are the most valuable squares to occupy, as they are harder for your opponent to surround and ‘flip’. Control these areas and you'll almost certainly win against the computer!

To round off this tutorial, you'll create a slightly more competent computer opponent that should present more of a challenge.

Colin Eberhardt

Contributors

Colin Eberhardt

Author

Over 300 content creators. Join our team.