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

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

Learn how to make a board game for iOS!

Welcome back to the final part of our 2-part iPad Board Game App Tutorial Series!

In the first part of the series, you learned how to create the game model, perform unit testing, add the game views, and add some basic game logic.

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.

This tutorial picks up where you left things off in the last part. If you don’t have it already, here is project where we left things off last time.

Let’s get back to making a board game!

Who’s on First? – Keeping Score

Reversi is a competitive game, so you’ll need to add a scoring mechanism to the game. Each player’s score is simply the number of pieces each player has on the board. Sounds easy enough!

Open up SHCBoard.h and add the following:

// counts the number of cells with the given state
- (NSUInteger) countCellsWithState:(BoardCellState)state;

Then open up SHCBoard.m and implement the methods as follows:

- (NSUInteger) countCellsWithState:(BoardCellState)state
{
    int count = 0;
    for (int row = 0; row < 8; row++)
    {
        for (int col = 0; col < 8; col++)
        {
            if ([self cellStateAtColumn:col andRow:row] == state)
            {
                count++;
            }
        }
    }
    return count;
}

The countCellsWithState method simply loops over every row and column in the board, counting cells with the given state.

Now you need to make use of this new method. Open up SHCReversiBoard.m and locate makeMoveToColumn:andRow:, adding the following lines to the end of the method:

_whiteScore = [self countCellsWithState:BoardCellStateWhitePiece];
_blackScore = [self countCellsWithState:BoardCellStateBlackPiece];

This will update the whiteScore and blackScore properties every time a move is made. Updating the property won’t automatically update the score value displayed to the players – you still need to notify the view that the scores have changed.

This sounds like another job for the multicasting delegate that you used in Part 1 of the tutorial. That delegate is coming in pretty handy, isn’t it? :]

Highlight the ‘Model’ group in the project and create a new file with the iOS\Cocoa Touch\Objective-C protocol template, named SHCReversiBoardDelegate and add the following method to the newly created protocol under the @protocol line:

// indicates that the game state has changed
- (void) gameStateChanged;

Open up SHCReversiBoard.h and add a property that multicasts to this delegate:

// multicasts game state changes
@property (readonly) SHCMulticastDelegate* reversiBoardDelegate;

Following the same pattern that you have already used in SHCBoard, open up SHCReversiBoard.m and add the protocol import:

#import "SHCReversiBoardDelegate.h"

Add an instance variable further down in the same file, right after @implementation and next to the current _boardNavigationFunctions variable:

id<SHCReversiBoardDelegate> _delegate;

Finally, create the multicasting delegate within commonInit by adding the following lines to the end of the method:

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

You don’t want to keep your delegates in the dark, so when a player makes a move, the delegate should be informed that the game state has changed.

Add the following lines to the end of makeMoveToColumn:andRow: (just after the lines you added earlier to update the score):

if ([_delegate respondsToSelector:@selector(gameStateChanged)]) {
    [_delegate gameStateChanged];
}

The SHCViewController has the labels that need to be updated when the score changes, so open up the SHCViewController.h to import and adopt this delegate protocol:

#import "SHCReversiBoardDelegate.h"
 
@interface SHCViewController : UIViewController <SHCReversiBoardDelegate>

Within SHCViewController.mlocate the viewDidLoad method and add the following to the end of the method:

[self gameStateChanged];
[_board.reversiBoardDelegate addDelegate:self];

And finally add the delegate method implementation somewhere within the file:

- (void)gameStateChanged
{
    _whiteScore.text = [NSString stringWithFormat:@"%d", _board.whiteScore];
    _blackScore.text = [NSString stringWithFormat:@"%d", _board.blackScore];
}

The delegate method ensures that whenever the game state changes, the labels are updated to reflect the current score. You will also notice that gameStateChanged is called initially in viewDidLoad, so that the labels reflect the state of the game when it starts.

If you recall, the starting position in Reversi is that each player has two pieces on the board — so therefore each player starts with 2 points.

Build and run your app!

Play a game against yourself and see the scoring mechanism in action, as below:

So, how did you do playing against yourself? I sure hope you won. :]

The game screens look quite nice, but the game is lacking a dynamic feel. The next section shows you how to add a bit of visual flair by using some subtle animations as the pieces are manipulated on the screen.

37 Pieces of Flair – Adding Game Animation

Open up SHCBoardSquare.m and locate the update method. Currently this method simply shows or hides the white and black playing piece images based on the cell state.

Replace update with the following code:

// updates the UI state
- (void)update
{
    BoardCellState state = [_board cellStateAtColumn:_column andRow:_row];
 
    [UIView animateWithDuration:0.5f animations:^{
        _whiteView.alpha = state == BoardCellStateWhitePiece ? 1.0 : 0.0;
        _whiteView.transform = state == BoardCellStateWhitePiece ? CGAffineTransformIdentity : CGAffineTransformMakeTranslation(0, -20);
        _blackView.alpha = state == BoardCellStateBlackPiece ? 1.0 : 0.0;
        _blackView.transform = state == BoardCellStateBlackPiece ? CGAffineTransformIdentity : CGAffineTransformMakeTranslation(0, 20);
 
    }];
}

This improved method changes both the positioning of the game piece as well as the alpha value — or transparency value — of the playing piece when the state of the cell changes.

Build and run to see the animations in action!

Now have some nicely animated pieces, but you shouldn’t just let the player keep playing forever. You need some end-game logic to check for wins or losses.

Game Over, Man – Handling the End Game

Well, there’s good news — and bad news — about the end-game condition.

The bad news is that in Reversi determining the end-game condition is not as simple as checking whether or not the board is full of pieces. The game really ends when neither player can play a piece that would result in one or more of their opponent’s pieces being flipped.

The good news is that this is a very easy condition to check. Open SHCReversiBoard.m and add the following methods:

- (BOOL) hasGameFinished
{
    return ![self canPlayerMakeAMove:BoardCellStateBlackPiece] &&
        ![self canPlayerMakeAMove:BoardCellStateWhitePiece];
}
 
- (BOOL) canPlayerMakeAMove:(BoardCellState) state
{
    // test all the board locations to see if a move can be made
    for (int row = 0; row < 8; row++)
    {
        for (int col = 0; col < 8; col++)
        {
            if ([self isValidMoveToColumn:col andRow:row forState:state])
            {
                return YES;
            }
        }
    }
    return NO;
}

The canPlayerMakeAMove method simply checks every square on the board to see if it would be a valid move for the given player. The hasGameFinished method does this check for both players.

To put the methods above in action, add a property to SHCReversiBoard.h that will reflect the game state:

// indicates whether the game has finished
@property (readonly) BOOL gameHasFinished;

Now head back to SHCReversiBoard.m and add the following line to update the state of property makeMoveToColumn:andRow: just before the line where the scores are updated:

_gameHasFinished = [self hasGameFinished];

The last rule to implement is that if one player cannot make a move, play switches back to the opponent.

Add the following method to SHCReversiBoard.m:

- (void) switchTurns
{
    // switch players
    BoardCellState nextMoveTemp = [self invertState:self.nextMove];
 
    // only switch play if this player can make a move
    if ([self canPlayerMakeAMove:nextMoveTemp])
    {
        _nextMove = nextMoveTemp;
    }
}

Here you add a check to make sure that the player can make a move before switching turns.

To make use of this method, find the makeMoveToColumn:andRow: method in SHCReversiBoard.m and locate the line that switches from one player’s turn to the other:

_nextMove = [self invertState:_nextMove];

Replace that code with the following:

[self switchTurns];

This ensures that before switching turns, a check is made to determine if the player can actually make a move.

Finally, you will want to display something to the user once the game has finished, rather than have them staring at a board where they can’t make a move.

Open up SHCViewController.m and add the following code to gameStateChanged:

_gameOverImage.hidden = !_board.gameHasFinished;

This will display the game-over image when the game ends.

Build and run your app. You can give the completed game a try, although you’ll still have to play against yourself:

You’ll notice that when the game is finished, the message at the bottom of the screen tells them to “tap to restart”. You should probably deliver on that promise!

InsideSHCViewController.m, add the following code to the end of viewDidLoad:

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

Then add the following method, which will start a new game if the current game really has finished:

- (void)restartGame:(UITapGestureRecognizer*)recognizer
{
    if (_board.gameHasFinished)
    {
        [_board setToInitialState];
        [self gameStateChanged];
    }
}

You can now play over and over and over again to your heart’s content!

However, after a few games it’s apparent that playing against yourself always guarantees a win. If you’re losing against yourself, then you’ve got bigger issues at hand! :]

Time to add a computer opponent to this game!

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 8×8 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.

Robot Redux – Making the Computer a Little Smarter

The current technique that the computer opponent employs is not a bad one. However, it fails to take into consideration what its opponent might do on their next turn, which a good Reversi player would do. A high-scoring move by the computer might still be countered by an even better move by his opponent on their next turn.

To improve the computer’s chance of success — and to make the human player sweat a little more! — you will implement a multiple-level look ahead model, instead of the current model which only looks at the next turn to determine the optimum move.

The computer currently looks for the next move that provides the maximum score for the computer. As an example, if the computer determines that there are three different moves that it can make, then it will pick the one with the highest score as illustrated below:

However, if you look at the next turn, the opponent (i.e. you!) will be trying to minimize the exact same score that the computer is trying to maximize. In the example below, if you look at what happens on the next turn, the situation will be a bit different:

With the one-step look ahead model, the computer would have picked the move that resulted in a score of ‘5’, i.e. the move marked ‘A’. However, on the next turn, a skilled human opponent would pick the move that gives the minimum score for the computer, and thus would select move ‘B’.

You can see that by looking ahead to the next round of moves, it’s easy to determine that ‘C’ is the move that will result in the best score, with the expectation that the next move after that will be ‘D’.

But why stop there? The computer, it its infinite wisdom and computing power, can continue to analyze the tree of potential moves and game states, even all the way to the end of the game! In comparison, a human competitor will struggle to look more than a few steps ahead.

The approach described here is a minimax algorithm where one player seeks to maximize their game score, while their opponent seeks to minimize this same score.

Time to put all this theory into practice and improve the computer’s chances of winning!

Open up SHCComputerOpponent.h and change the existing init method to add another argument:

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

Looking ahead to the very end of the game will take a lot of processing time, as there are many, many possible game states to analyze. Therefore, the depth argument will be used to specify how many steps to evaluate when looking for the optimum position.

Open up SHCComputerOpponent.m and add an instance variable that stores the given depth:

NSInteger _maxDepth;

Replace the init method with the following code to use the new depth parameter:

- (id)initWithBoard:(SHCReversiBoard *)board color:(BoardCellState)computerColor maxDepth:(NSInteger)depth
{
    if (self = [super init])
    {
        _board = board;
        _computerColor = computerColor;
        _maxDepth = depth;
        [_board.reversiBoardDelegate addDelegate:self];
    }
    return self;
}

Before diving into the minimax logic, you’ll need to add some utility methods. At the top of SHCComputerOpponent.m add the definition for the following struct immediately under the #import lines:

typedef struct
{
    NSInteger column;
    NSInteger row;
} Move;

This will make it easier to pass moves between methods.

Next, add the following method to SHCComputerOpponent.m in the implementation section:

// returns an array of valid next moves for the given board
- (NSArray*) validMovesForBoard:(SHCReversiBoard*) board
{
    NSMutableArray* moves = [[NSMutableArray alloc] init];
 
    for (NSUInteger row = 0; row < 8; row++)
    {
        for (NSUInteger col = 0; col < 8; col++)
        {
            if ([board isValidMoveToColumn:col andRow:row])
            {
                Move move = {.column = col, .row = row};
                [moves addObject:[NSValue valueWithBytes:&move objCType:@encode(Move)]];
            }
        }
    } 
 
    return moves;
}

This method returns an array of all the possible next moves for the given board state. Notice that because Move is a struct, it needs to be boxed within an NSValue object since NSArrays and NSMutableArrays can only store objects.

Replace the previous simplistic implementation of makeNextMove with the following:

// Determines which move to make
- (void)makeNextMove
{
    Move bestMove = {.column = -1, .row = -1};
    NSInteger bestScore = NSIntegerMin;
 
    // check every valid move for this particular board, then select the one with the best 'score'
    NSArray* moves = [self validMovesForBoard:_board];
    for(NSValue* moveValue in moves)
    {
        Move nextMove;
        [moveValue getValue:&nextMove];
 
        // clone the current board and make this move
        SHCReversiBoard* testBoard = [_board copyWithZone:nil];
        [testBoard makeMoveToColumn:nextMove.column andRow:nextMove.row];
 
        // determine the score for this move
        NSInteger scoreForMove = [self scoreForBoard:testBoard depth:1];
 
        // pick the best
        if (scoreForMove > bestScore || bestScore == NSIntegerMin)
        {
            bestScore = scoreForMove;
            bestMove.row = nextMove.row;
            bestMove.column = nextMove.column;
        }
    }
 
    if (bestMove.column != -1 && bestMove.row != -1)
    {
        [_board makeMoveToColumn:bestMove.column andRow:bestMove.row];
    }
}

The improved makeNextMove method iterates over all of the possible next moves, determines the score for each with scoreForBoard, then picks the best move.

Still working in SHCComputerOpponent.m, add the following code to scoreForBoard, where everything comes together:

// Computes the score for the given board
- (NSInteger)scoreForBoard:(SHCReversiBoard*)board depth:(NSInteger)depth
{
    // if we have reached the maximum search depth, then just compute the
    // score of the current board state
    if (depth >= _maxDepth)
    {
        return _computerColor == BoardCellStateWhitePiece ?
            board.whiteScore - board.blackScore :
            board.blackScore - board.whiteScore;
    }
 
    NSInteger minMax = NSIntegerMin;
 
    // check every valid next move for this particular board
    NSArray* moves = [self validMovesForBoard:board];
    for(NSValue* moveValue in moves)
    {
        // Extract the Move struct from the NSValue box
        Move nextMove;
        [moveValue getValue:&nextMove];
 
        // clone the current board and make the move
        SHCReversiBoard* testBoard = [_board copyWithZone:nil];
        [testBoard makeMoveToColumn:nextMove.column andRow:nextMove.row];
 
        // compute the score for this board with a recursive call
        NSInteger score = [self scoreForBoard:testBoard depth: depth+1];
 
        // pick the best score
        if (depth % 2 == 0)
        {
            if (score > minMax || minMax == NSIntegerMin)
            {
                minMax = score;
            }
        }
        else
        {
            if (score < minMax || minMax == NSIntegerMin)
            {
                minMax = score;
            }
        }
    }
 
    return minMax;
}

The scoreForBoard:depth: method uses a recursive minimax algorithm to determine the score for a given board. The method is called from makeNextMove with a depth of 1 to start off.

If the maximum search depth has been exceeded (depth >= _maxDepth), the score is simply the difference in black points vs. white points for the current game state as previously computed.

If the search depth has not been exceeded, then all potential next moves are considered. The highest or lowest score is selected based on whether the algorithm is looking for a min value or a max value.

The recursion can be seen in the code just after the board has been cloned and a move has been made, where the score is calculated by calling scoreForBoard on itself and passing a depth of depth+1.

Through this recursive approach, the minmax value is ‘bubbled up’ for each move, and the selected move is the one that gives the best result for the given depth.

There’s only one final step required to put this new and improved computer player into action!

Open up SHCViewController.m, locate the line in the viewDidLoad method where the computer player is created, and replace that line with the following:

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

This creates a computer player that searches the tree to a depth of ‘5’.

Build and run your app!

See if you can still beat the computer at this level! Many have tried and failed — are you “the one”? :]

Where To Go From Here?

If you are interested in taking this application further, investigate Alpha-beta pruning. Alpha-beta pruning is an optimization to minimax, allowing it to search much deeper into the game tree without incurring as much computational overhead..

For a bit of fun, create two computer players by duplicating the line of code above in SHCViewController.m and watch your iPad play itself! In addition to being entertaining, it’s also a great way to test that computer players with an increased depth of tree search actually play better than ones with a shallow tree depth.

By watching the two computer players, you will notice that the computer will decide on its move very quickly in the first few and the last few moves of the game. At these points, there are fewer potential moves for the computer to analyze, so it makes up its mind a lot more quickly!

Another great enhancement would allow the user to change the difficulty level. Higher values of maxDepth will make the computer smarter — and make the game more challenging!

If you wanted to see the completed project in all its glory, you can download the completed project here. Alternately, you can get the code from the project’s GitHub repository, which has commits for each ‘build and run’ step of the tutorial.

I hope you enjoyed this tutorial. 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

11 Comments

  • Great tutorial and I may have an idea for an app based on this with some variations. My question is one of patents and copyrights. If I make a game with the Reversi logic am I not setting myself up for a lawsuit for patent violation?
    avi8tor
  • Thats a nice tutorial. I like that you encourage unit testing.
    The AI part is nice too. To copy the board is much better than my past approach to write a undo function.
    zeiteisen
  • avi8tor wrote:Great tutorial and I may have an idea for an app based on this with some variations. My question is one of patents and copyrights. If I make a game with the Reversi logic am I not setting myself up for a lawsuit for patent violation?


    Well, I'm not going to come after you with my lawyers ;-)

    Although, I am guessing that you are referring to Pressman Toy Crop who market the game under the name Othello.

    As long as you steer clear of registered trademarks, I very much doubt you would ever have any legal issues with publishing a Reversi game. From searching the internet, it would appear that it was patented in 1888 - this patent would have lapsed long ago.

    Colin E.
    ColinEberhardt
  • We made Reverse It and had never legel issues.
    zeiteisen
  • Thank you for this tutorial. I actually understand most of it, I would never have been able to code it from scratch. any hint on how to set a difficulty?
    kurpaige
  • Most ALL the tutorials on this site are great. This one, though, illustrates strategic coding techniques that I would have expected not to be publicly shared due to the value. For example, your use of blocks implementing the "navigation" function is a great application for this tool.

    Your encouragement for the use of unit testing is, embarrassingly, a topic I've never even taken an interest... but no more.

    Much appreciation for the sharing of some great application development strategies.
    tflawlis
  • kurpaige wrote:Thank you for this tutorial. I actually understand most of it, I would never have been able to code it from scratch. any hint on how to set a difficulty?


    Glad you liked it! Computer players often have their difficulty set by one of two methods, either:

    1) The depth to which they can search the game tree is limited. i.e. an 'easy' player would perform a tree search to a depth of 2, 'medium' to 4 and 'hard' to '6'

    or

    2) The amount of time the computer player has to analyse moves is limited, i.e. an 'easy' player might be allowed to analyse moves for 0.5 seconds, 'medium' for 2 seconds and 'hard' for 10 seconds.

    The timed version, while a little harder to write, is probably the best. If you just limit by depth, moves at the start and end of the game, where the game tree is small, will be made quite rapidly. However, in the middle of the game the computer player will be quite slow because there are a great many moves to analyse.

    Hope that helps, Colin E.
    ColinEberhardt
  • tflawlis wrote:Most ALL the tutorials on this site are great. This one, though, illustrates strategic coding techniques that I would have expected not to be publicly shared due to the value. For example, your use of blocks implementing the "navigation" function is a great application for this tool


    Wow - great praise! glad you liked it, and thanks for your comments.

    Colin E.
    ColinEberhardt
  • In the copy of the tutorial posted on the site (I haven't looked at your completed code or repo), it seems to me there is an extraneous _ in scoreForBoard:depth: in SHCComputerOpponent.m. At the point where you clone a board to create testBoard, you are cloning the current live board each time. Unless I haven't yet had enough coffee this morning to parse this out, that would fix it so that the search doesn't actually play out several moves forward the way it should. I think the line should read:

    Code: Select all
    SHCReversiBoard *testBoard = [board copyWithZone:nil]; // no underscore here!


    Thanks for a great look at a cool game design with some interesting techniques thrown in for good measure!
    rcritz
  • I know this is not supposed to be a production ready game, but here are a few thoughts that could greatly improve the gameplay:

    1/ either disable the landscape rotation of the device, or handle it.
    2/ if it's the player's turn, tell it. If the machine is thinking, tell it. There is no way to see the difference, and you don't even notify the player that he has the blacks.
    3/ Limit the time the IA spends walking the tree, or the number of nodes explored. It's really not funny to wait 35 seconds, and even less funny to wonder if it's your turn, the machine turn, or if the application doesn't respond for another reason.
    4/ The IA really is too naive. Give more weight to the edges and even more to the corners.
    5/ The assets seems to have been upscaled. Create graphics at their final resolution, I can see the pixels in the circles that display the current score.

    Good tutorial though, these are just details.
    alecail
  • I'm enjoying these tutorials a lot and I'm an aspiring game developer, though new to programming. I'm grasping most of the tutorial, but I don't recognize the -> you used in the copyWithZone method.
    Ex.
    - (id)copyWithZone:(NSZone *)zone
    {
    SHCReversiBoard* board = [super copyWithZone:zone];
    board->_nextMove = _nextMove;
    board->_whiteScore = _whiteScore;
    board->_blackScore = _blackScore;

    Is this like dot syntax for properties of copies?

    Thanks!

    - GGL2
    GamerGeek-Level 2

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!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

Suggest a Tutorial - Past Results

Hang Out With Us!

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


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Kirill Muzykov
  • Jake Gundersen

... 53 total!

Update Team

... 14 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

... 4 total!