How to make a simple board game in iPhone / iPod

In this tutorial I will teach you how to build a simple board game using only UIViews and Interface Builder.

The game we are going to code is the classic peg solitaire.

game main view

This version uses SQLITE to save the data.

Step 1. Creating the OneByOneSolitaire Project:

Start Xcode and choose new project from the file menu. In the new project dialog choose View-based Application.

Xcode new project window

Name the project OneByOneSolitaire.

After you choose a folder were to save your new project Xcode will create basic project with nine files, but we are only interested in the following five:

OneByOneSolitireAppDelegate.h Application delegate
OneByOneSolitaireAppDelegate.m
OneByOneSolitaireViewController.h View controler of the main view
OneByOneSolitaireViewController.m
OneByOneSolitaireViewController.xib the main view of the game

Step 2. Getting the Images:

You will need a lot of images for the game. Download this zip file with all the images for the game and add this to your Xcode project.

Images from OneByOneSolitaire

To add the images to your project you need to right click over the Resources folder and select the option Add New Group. A new folder will be add under Resources. Rename this folder to images. Right click the images folder and select Add Existing Files. Check the option Copy items into destination group’s folder (if needed).

add images to the project

All this images are needed but there are two which have an special meaning. The icon.png image it’s the 57×57 pixels image used by iOS to identify your application in the desktop. The default.png image is presented to the user just after him launch your application.

Application's icon image

icon.png

application's default image

default.png

Step 3. Adding the Board Image

In this game we will use UIImageView objects to represent the board and every peg.

The board will be hosted in the OneByOneSolitaireView which is defined in the files OneByOneSolitaireViewControler.h, OneByOneSolitaireViewControler.m and OneByOneSolitaireViewControler.xib.

We will use an UIImageView to show the board. Double click the OneByOneSolitaireViewController.xib to open Interface Builder.

Add a UIImageView and set background.png to its image property.

adding the board

Set the image position to cover all the view.

Add a UIImageView and set hollos.png to its image property. Edit Size & Position and set X: -2 Y: 38 W: 313 and H: 313.

adding the holes

Finally we need to add a toolbar and a label. Add the toolbar to the bottom and drag the label between the holes and the toolbar. Set the position and size of the label to X=20, Y=372, W=280, H=32, set the alignment to center and the color to white. Set the style of the UIToolbar to Black Opaque.

adding a toolbar and a label

Step 4. Creating the Toolbar

First we need to add two variables to our class OneByOneSolitaireViewController to enable or disable the buttons Undo and Redo according to the state of the game.

Copy this variables to the OneByOneSolitaireViewController.h just under the line @interface OneByOneSolitaireViewController : UIViewController {.

    UIBarButtonItem *cmdUndo;
    UIBarButtonItem *cmdRedo;

After the closing braket } add this three function declarations.

- (void) deal;
- (void) undo;
- (void) redo;
- (void) showSettings;

Second we need to add code to viewDidLoad. Uncomment the standard implementation of viewDidLoad in OneByOneSolitaireViewController.m and after calling super add this:

    UIToolbar *toolbar;

    // IMPORTANT: if you add the components in a different order to what was followed
    //                   in this tutorial probably you would need to change this value
    //
    toolbar = [[self.view subviews] objectAtIndex:2];  // 2 is the index of our toolbar 

    //Add buttons
    UIBarButtonItem *cmdDeal = [[UIBarButtonItem alloc] initWithTitle:@"deal"
                                                                    style:UIBarButtonItemStyleBordered
                                                                   target:self
                                                                   action:@selector(deal)];

    cmdUndo = [[UIBarButtonItem alloc]
               initWithBarButtonSystemItem:UIBarButtonSystemItemUndo
               target:self
               action:@selector(undo)];

    cmdRedo = [[UIBarButtonItem alloc]
               initWithBarButtonSystemItem:UIBarButtonSystemItemRedo
               target:self
               action:@selector(redo)];

    // i button
    UIView *view;
    view = [[UIView alloc] initWithFrame:CGRectMake(0,0,45,45)];
    UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
    infoButton.frame = CGRectMake(0, 0, 44, 44);
    [infoButton addTarget:self action:@selector(showSettings) forControlEvents:UIControlEventTouchUpInside];
    [view addSubview:infoButton];

    UIBarButtonItem *cmdSettings = [[UIBarButtonItem alloc]
                                initWithCustomView:view];
    [view release];

    //Use this to put space in between your toolbox buttons
    UIBarButtonItem *flexItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                                                                              target:nil
                                                                              action:nil];

    //Add buttons to the array
    NSArray *items = [NSArray arrayWithObjects: cmdDeal, flexItem, cmdUndo, cmdRedo, flexItem, cmdSettings, nil];

    //release buttons
    [cmdDeal release];
    [flexItem release];

    //add array of buttons to toolbar
    [toolbar setItems:items animated:NO];

At the end of the file before the @end tag add this three empty implementations.

- (void) deal {}
- (void) undo {}
- (void) redo {}
- (void) showSettings {}

At this point if we run our application in the simulator we will see this

One By One Solitaire in iPhone Simulator

Step 5. Linking the Label to the ViewControler.

To link a control from our UI to a variable in our view controller we need to declare a variable of IBOutlet type. Add this code after *cmdRedo; in OneByOneSolitaireViewController.h.

IBOutlet UILabel *lbPlayer;

The IBOutlet type is an alias of the id type but it let Xcode to identify a variable which is intended to be linked to a control in the UI.

See this video to learn how to link an IBOutlet.

Step 6. Game Concepts.

To code the logic of this game we need to know the coordinates of every hole in the board. To do this we are going to declare four C arrays which will bring us the position of every hole.

static const int posX[] = {  173, 126, 79,
/*                         */173, 126, 79,
/*               */267, 220, 173, 126, 79, 33, -14,
/*               */267, 220, 173, 126, 79, 33, -14,
/*               */267, 220, 173, 126, 79, 33, -14,
/*                         */173, 126, 79,
/*                         */173, 126, 79};

static const int posY[] = {  32, 32, 32,
/*                         */78, 78, 78,
/*               */124, 124, 124, 124, 124, 124, 124,
/*               */170, 170, 170, 170, 170, 170, 170,
/*               */216, 216, 216, 216, 216, 216, 216,
/*                         */262, 262, 262,
/*                         */308, 308, 308};

static const int cordX[] = { 4, 3, 2,
/*                         */4, 3, 2,
/*                   */6, 5, 4, 3, 2, 1, 0,
/*                   */6, 5, 4, 3, 2, 1, 0,
/*                   */6, 5, 4, 3, 2, 1, 0,
/*                         */4, 3, 2,
/*                         */4, 3, 2};

static const int cordY[] = { 0, 0, 0,
/*                         */1, 1, 1,
/*                   */2, 2, 2, 2, 2, 2, 2,
/*                   */3, 3, 3, 3, 3, 3, 3,
/*                   */4, 4, 4, 4, 4, 4, 4,
/*                         */5, 5, 5,
/*                         */6, 6, 6};

The first two arrays posX and posY give us the position. The other two arrays are used to map coordinates to positions of every hole.

Add this code to the beginning of OneByOneSolitaireViewController.m just after @implementation OneByOneSolitaireViewController.

We will need an array to hold the position of every hole to manage the moves of every peg made by the user.

Add this declaration to OneByOneSolitaireViewController.h just after IBOutlet UILabel *lbPlayer;.

    CGRect m_boxes[33];

In the viewDidLoad of OneByOneSolitaireViewController.m add this for sentence to fill the array just after [toolbar setItems:items animated:NO];

    for (int i = 0; i < 33; i++) {
        m_boxes[i].size.height = 47;
        m_boxes[i].size.width = 47;
        m_boxes[i].origin.x = posX[i];
        m_boxes[i].origin.y = posY[i];
    }

Every hole is a square of 47 by 47 pixels. We will use this array to know in which hole has the user dropped a peg after moving it.

Step 7. Adding a Database to our Application.

To save the state of the game, the user preferences and the history of every game played we will use SQLITE. To get an introduction to SQLITE on the iPhone you can read these two excellent tutorials.

http://www.switchonthecode.com/tutorials/using-sqlite-on-the-iphone

http://www.icodeblog.com/2008/08/19/iphone-programming-tutorial-creating-a-todo-list-using-sqlite-part-1/

First we need to add a reference to SQLITE in our project. The file we need to add is libsqlite3.dylib which is located in /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSilumator3.0.sdk/usr/lib.

It’s very important that you set the Reference Type property to Relative to Current SDK.

adding sqlite3 reference

Second we need to add a database file to our project. For this tutorial you can download the file from here:

http://www.crowsoft.com.ar/blog/tutorials/onebyonesolitaire/by1by1solitaire.db.zip

If you want to create the file from the scratch you can do it. These are the sql commands to create the file by yourself.

>sqlite3 by1by1solitaire.db
>CREATE TABLE IF NOT EXISTS Player (pl_id INTEGER PRIMARY KEY AUTOINCREMENT, pl_name TEXT, pl_activo INTEGER);
>CREATE TABLE IF NOT EXISTS Game (gm_id INTEGER PRIMARY KEY AUTOINCREMENT, gm_name TEXT, gm_number INTEGER, gm_activo INTEGER, pl_id INTEGER);
>CREATE TABLE IF NOT EXISTS Move (mv_id INTEGER PRIMARY KEY AUTOINCREMENT, mv_x1 INTEGER, mv_y1 INTEGER, mv_x2 INTEGER, mv_y2 INTEGER, gm_id INTEGER);
>CREATE TABLE IF NOT EXISTS Settings (st_code INTEGER PRIMARY KEY, st_value INTEGER);
>.quit

Now we need to add this database file to our project. Right click over the Resources folder and choose Add Existing Files. It’s very important you check the option Copy items into destination group’s folder (if needed) and set the property Reference Type to Relative to Enclosing Group.

adding the database file

Step 8. Adding the Database Class.

We will need to add a new class to implement some basic database functionality. Right click over the classes folder and choose Add New File. Select Objective-C class from Iphone OS – Cocoa Touch Class. Press the next button and name the file DataBase.m.

In the DataBase.h file add an import references to sqlite3:

#import "/usr/include/sqlite3.h"

After the @interface DataBase : NSObject { add these variable declarations:

    UIView *view;
    NSString *m_lastErrorMsg;
    sqlite3 *m_database;

After the closing bracket add these function declarations:

-(BOOL)openDB: (NSString *) databaseName;
-(BOOL)execute: (NSString *) sqlstmt;
-(sqlite3_stmt *)openRS: (NSString *) sqlstmt;
-(NSString *)getLastErrorMsg;
-(int)getLastPk;

In the DataBaseFile.m add this code:

-(NSString *)getLastErrorMsg {
    return m_lastErrorMsg;
}

-(BOOL)openDB: (NSString *) databaseName {
    int result = sqlite3_open([databaseName UTF8String], &m_database);
    if (result != SQLITE_OK)
    {
        sqlite3_close(m_database);
        m_lastErrorMsg = @"Failed to open database.";
        return NO;
    }
    else {
        return YES;
    }
}

-(BOOL)execute: (NSString *)sqlstmt {
    int result = sqlite3_exec(m_database,
                              [sqlstmt UTF8String],
                              NULL, NULL, NULL);
    if (result != SQLITE_OK)
    {
        sqlite3_close(m_database);
        m_lastErrorMsg = @"Failed to execute sentence";
        return NO;
    }
    else {
        return YES;
    }
}

-(sqlite3_stmt *)openRS: (NSString *) sqlstmt {
    sqlite3_stmt *rs;
    sqlite3_prepare_v2(m_database, [sqlstmt UTF8String], -1, &rs, nil);
    return rs;
}

-(int)getLastPk {
    return sqlite3_last_insert_rowid(m_database);
}

We need to add an import to DataBase.h in OneByOneSolitaireViewController.h and a new variable named m_db:

After #import <UIKit/UIKit.h> add in a new line:

#import "DataBase.h"

After CGRect m_boxes[33]; add:

    DataBase *m_db;

We need a function to install our database in the Application Document directory. Add this declaration after -(void) showSettings;:

- (void)createEditableCopyOfDatabaseIfNeeded;
- (void) showAlert: (NSString *)msg;

At the end of the viewDidLoad function of OneByOneSolitaireViewController.m add these lines:

    // init database
    m_db = [[DataBase alloc] init];

    [self createEditableCopyOfDatabaseIfNeeded];

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];

    //if (![m_db openDB:@"/bs1by1solitaire.db"]) {
    if (![m_db openDB:[documentsDirectory stringByAppendingPathComponent:@"by1by1solitaire.db"]]) {
        [self showAlert:[m_db getLastErrorMsg]];
    }

In the first line we are instantiating a new DataBase object which we will use to access the database. Our database will be located in the Documents folder of the iPhone / iPod user. The NSSearchPathForDirectoriesInDomains function returns an array which contains in the first element the path of this folder. Finally, we open the database calling the openDB method of our DataBase class.

This is the implementation of createEditableCopyOfDatabaseIfNeeded:

// Creates a writable copy of the bundled default database in the application Documents directory.
- (void)createEditableCopyOfDatabaseIfNeeded {
    // First, test for existence.
    BOOL success;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"by1by1solitaire.db"];
    success = [fileManager fileExistsAtPath:writableDBPath];
    if (success) return;
    // The writable database does not exist, so copy the default to the appropriate location.
    NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"by1by1solitaire.db"];
    success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
    if (!success) {
        NSAssert1(0, @"Failed to create writable database file with message '%@'.", [error localizedDescription]);
    }
}

This function check that the database exists in the application Documents directory. If it doesn’t exists the function make a copy of the database from the application’s bundle to the application Documents folder.

This is the implementation of showAlert:

- (void) showAlert: (NSString *)msg {
    UIAlertView *view;
    view = [[UIAlertView alloc]
            initWithTitle: @"Database Error"
            message: msg
            delegate: self
            cancelButtonTitle: @"Close" otherButtonTitles: nil];
    [view show];
    [view autorelease];
}

This function show a message ever things go wrong interacting with the database.

Step 9. Loading the User Name and His Preferences.

After opening a connection to our database we can load the name of the user and the image of the peg which he like the most. To do this we need to add this two calls:

    [self getPlayer];
    lbPlayer.text = m_pl_name;

    [self getBallImageFromDb];

We will need to add some declarations to OneByOneSolitaireViewController.h:

Three new variables (add this lines after DataBase *m_db);

    int m_pl_id;
    NSString *m_pl_name;
    int ballImage;

Two new functions

- (void) getPlayer;
- (void) getBallImageFromDb;

And a new property:

@property (assign) int ballImage;

This is the implementation of getPlayer:

- (void) getPlayer {
    sqlite3_stmt *rs;
    rs = [m_db openRS:@"SELECT pl_id, pl_name FROM Player WHERE pl_activo <> 0"];

    if (sqlite3_step(rs) == SQLITE_ROW)
    {
        m_pl_name = [[NSString alloc] initWithUTF8String:
                     (char *)sqlite3_column_text(rs, 1)];
        m_pl_id = sqlite3_column_int(rs, 0);
    }
    else {
        if (![m_db execute:[NSString stringWithFormat:@"INSERT INTO Player (pl_name, pl_activo) VALUES('%@', 1)",
                            [[UIDevice currentDevice] name]]]) {
            [self showAlert:[m_db getLastErrorMsg]];
        }
        else {
            m_pl_id = [m_db getLastPk];
            m_pl_name = [[UIDevice currentDevice] name];
        }
    }
    sqlite3_finalize(rs);
}

The function try to get the name and the id of the player from the database. If there is no player in the database yet, the function add the name of the iPhone / iPod as the default user. The name and the id are saved in m_pl_name and m_pl_id. Every new game will be associated to m_pl_id.

This is the implementation of getBallImageFromDb:

- (void) getBallImageFromDb {
    sqlite3_stmt *rs;
    rs = [m_db openRS:@"SELECT st_value FROM Settings WHERE st_code = 1"]; // 1 ballimage
    if (sqlite3_step(rs) == SQLITE_ROW)
    {
        ballImage = sqlite3_column_int(rs, 0);
    }
    else {
        ballImage = 0;
    }
    sqlite3_finalize(rs);
}

As you can see, this function query the table Settings to find which image has been selected by the user.

Finally we need to tell the compiler to implement the ballImage property. Add this line just after the declaration of static const int cordY[]

@synthesize ballImage;

Step 10. Filling the Board.

We are ready to fill the board with the 32 pegs. To do this we will call the function deal in viewDidLoad. First we need to add a new class named Ball to represent a peg. Right click over the classes folder and choose Add New File. Select Objective-C class from Iphone OS – Cocoa Touch Class. Press the next button and name the file Ball.m.

This class will inherit from UIImageView. We need to change the default inheritance from NSObject to UIImageView. Replace the code from @interface Ball : NSObject to @end with this code:

@interface Ball : UIImageView {
    int x, y;
    int cordX, cordY;
    int lastX, lastY;
    id board;
    BOOL m_bFirstCallToMoved;
    CGPoint m_location;
}

@property (assign) id board;
@property (assign) int x;
@property (assign) int y;
@property (assign) int cordX;
@property (assign) int cordY;
@property (assign) int lastX;
@property (assign) int lastY;

- (void)restoreToInitialPosition;

@end

In the Ball.m file replace from @implementation Ball to @end with this code:

// Import QuartzCore for animations
#import <QuartzCore/QuartzCore.h>

@implementation Ball

@synthesize board;
@synthesize x;
@synthesize y;
@synthesize cordX;
@synthesize cordY;
@synthesize lastX;
@synthesize lastY;

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        // Initialization code
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    // Drawing code
}

- (void)restoreToInitialPosition {

    // Bounces the placard back to the center
    CALayer *welcomeLayer = self.layer;

    // Create a keyframe animation to follow a path back to the center
    CAKeyframeAnimation *bounceAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    bounceAnimation.removedOnCompletion = NO;

    CGFloat animationDuration = 1.5;

    // Create the path for the bounces
    CGMutablePathRef thePath = CGPathCreateMutable();
    [board setBallOriginalPosition:self];
    m_location = CGPointMake(cordX+27, cordY+27);

    CGFloat midX = m_location.x;
    CGFloat midY = m_location.y;
    CGFloat originalOffsetX = self.center.x - midX;
    CGFloat originalOffsetY = self.center.y - midY;
    CGFloat offsetDivider = 4.0;

    BOOL stopBouncing = NO;

    // Start the path at the placard's current location
    CGPathMoveToPoint(thePath, NULL, self.center.x, self.center.y);
    CGPathAddLineToPoint(thePath, NULL, midX, midY);

    // Add to the bounce path in decreasing excursions from the center
    while (stopBouncing != YES) {
        CGPathAddLineToPoint(thePath, NULL, midX + originalOffsetX/offsetDivider, midY + originalOffsetY/offsetDivider);
        CGPathAddLineToPoint(thePath, NULL, midX, midY);

        offsetDivider += 4;
        animationDuration += 1/offsetDivider;
        if ((abs(originalOffsetX/offsetDivider) < 6) && (abs(originalOffsetY/offsetDivider) < 6)) {
            stopBouncing = YES;
        }
    }

    bounceAnimation.path = thePath;
    bounceAnimation.duration = animationDuration;
    CGPathRelease(thePath);

    // Create a basic animation to restore the size of the placard
    CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    transformAnimation.removedOnCompletion = YES;
    transformAnimation.duration = animationDuration;
    transformAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];

    // Create an animation group to combine the keyframe and basic animations
    CAAnimationGroup *theGroup = [CAAnimationGroup animation];

    // Set self as the delegate to allow for a callback to reenable user interaction
    theGroup.delegate = self;
    theGroup.duration = animationDuration;
    theGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];

    theGroup.animations = [NSArray arrayWithObjects:bounceAnimation, transformAnimation, nil];

    // Add the animation group to the layer
    [welcomeLayer addAnimation:theGroup forKey:@"animatePlacardViewToCenter"];

    // Set the placard view's center and transformation to the original values in preparation for the end of the animation
    self.center = m_location;
    self.transform = CGAffineTransformIdentity;
}

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
    //Animation delegate method called when the animation's finished:
    // restore the transform and reenable user interaction
    self.transform = CGAffineTransformIdentity;
    self.userInteractionEnabled = YES;
}

//------------------------
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    m_location = self.center;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];

    if (!m_bFirstCallToMoved) {
        m_bFirstCallToMoved = YES;
        m_location = self.center;
        [self.superview bringSubviewToFront:self];
    }

    // If the touch was in the placardView, move the placardView to its location
    if ([touch view] == self) {
        CGPoint location = [touch locationInView:[self superview]];
        self.center = location;
        return;
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    m_bFirstCallToMoved = NO;

    // To manage double tap bug
    if (m_location.x == 0 && m_location.y == 0) { return; }

    UITouch *touch = [touches anyObject];
    NSUInteger tapCount = [touch tapCount];

    if (tapCount == 2) {
        return;
    }

    if (self.center.x != m_location.x || self.center.y != m_location.y) {

        if (![board move:self :[touch locationInView:[board view]]]) {
            [self restoreToInitialPosition];
        }
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    // To manage double tap bug
    if (m_location.x == 0 && m_location.y == 0) { return; }

    self.center = m_location;
    self.transform = CGAffineTransformIdentity;
}

//------------------------

- (void)dealloc {
    [super dealloc];
}

@end

Be carefull to not remove the import of Ball.h at the top of the file.

Finally we will need to add a reference to QuartzCore Framework to be able to make some simple animations.

On the left side of XCode, there is a folder called Targets. Expand it. Double click on your target in that folder. On the General Tab of the info pane that pops up, click the + sign on the bottom left corner of the window. Scroll down and select QuartzCore.framework.

adding QuartzCore Framework

We will discuss the functionality of this class later. For now we only need this class to allow our project to compile.

We need to add to OneByOneSolitaireViewController.h an import to Ball.h, a new array variable declaration to hold the pegs and some more variable declarations to keep the state of the game:

After #import “DataBase.h” add in a new line:

#import "Ball.h"

After int ballImage; add:

    Ball *m_ballImages[32];

    // moves
    int m_first_move_id;
    int m_last_move_id;
    int m_last_move_x1;
    int m_last_move_y1;
    int m_last_move_x2;
    int m_last_move_y2;    

    int m_board[7][7];

In the file OneByOneSolitaireViewController.m replace the trivial implementation of the function deal with this code:

- (void) deal {

    // moves
    m_firstMove = YES;
    m_first_move_id = 0;
    m_last_move_id = 0;

    cmdUndo.enabled = NO;
    cmdRedo.enabled = NO;

    // bals
    UIImage *image = [self getImage];

    for (int i = 0; i < 16; i++) {

        Ball *ball;
        if (m_ballImages[i] == nil) {
            ball = [[Ball alloc] initWithImage:image];
            ball.userInteractionEnabled = TRUE;
            ball.board = self;
            ball.x = cordX[i];
            ball.y = cordY[i];
            ball.lastX = 0;
            ball.lastY = 0;
            m_board[ball.x][ball.y] = 1;
            m_ballImages[i] = ball;
            ball.frame = CGRectMake(posX[i], posY[i], 55, 54);
            [self.view addSubview:ball];
            [ball release];
        }
        else {
            ball = m_ballImages[i];
            ball.x = cordX[i];
            ball.y = cordY[i];
            m_board[ball.x][ball.y] = 1;
            ball.frame = CGRectMake(posX[i], posY[i], 55, 54);
            [self showBall:ball];
            ball.hidden = NO;
            [ball setImage:image];
        }
    }
    for (int i = 17; i < 33; i++) {

        Ball *ball;
        if (m_ballImages[i-1] == nil) {
            ball = [[Ball alloc] initWithImage:image];
            ball.userInteractionEnabled = TRUE;
            ball.board = self;
            ball.lastX = 0;
            ball.lastY = 0;
            ball.x = cordX[i];
            ball.y = cordY[i];
            m_board[ball.x][ball.y] = 1;
            ball.frame = CGRectMake(posX[i], posY[i], 55, 54);
            [self.view addSubview:ball];
            m_ballImages[i-1] = ball;
            [ball release];
        }
        else {
            ball = m_ballImages[i-1];
            ball.x = cordX[i];
            ball.y = cordY[i];
            m_board[ball.x][ball.y] = 1;
            ball.frame = CGRectMake(posX[i], posY[i], 55, 54);
            [self showBall:ball];
            ball.hidden = NO;
            [ball setImage:image];
        }
    }
    m_board[3][3] = 0;
}

Finally, we need to call to the function deal at the end of viewDidLoad:

    [self deal];

If everything was right we must see this image after running the application in the iPhone Simulator:

the game running with a filled board

Step 11. Managing Moves.

At this point we only need to manage move event of every UIImageView to have a first version of our game which actually could be played. We need to add the function move. If you have been looking at the Ball.m code probably you might be noticed that this class catch the events touchesBegan, touchesMove and touchesEnd which allow us to move the peg following the player’s finger. In the touchesEnd function we call the method move of the object board (which is a reference to OneByOneViewController). Here is the code of move:

- (BOOL) move:(Ball *) ball:(CGPoint) point {

    int originalX, originalY;
    originalX = ball.x;
    originalY = ball.y;

    for (int i = 0; i < 33; i++) {         if(      point.x >= m_boxes[i].origin.x
           && point.y >= m_boxes[i].origin.y
           && point.x <= m_boxes[i].origin.x + m_boxes[i].size.width
           && point.y <= m_boxes[i].origin.y + m_boxes[i].size.height
           ) {
            if (m_board[cordX[i]][cordY[i]] == 0) {
                int moveX, moveY;
                moveX = ball.x - cordX[i];
                moveY = ball.y - cordY[i];

                if ((abs(moveX) == 2 || moveX == 0) && (abs(moveY) == 2 || moveY == 0)) {
                    if (!(abs(moveX) != 0 && abs(moveY) != 0)) {
                        int x, y;
                        x = ball.x - (moveX / 2);
                        y = ball.y - (moveY / 2);
                        if (m_board[x][y] == 1) {
                            m_board[x][y] = 0;
                            for (int k = 0; k < 32; k++) {
                                if (m_ballImages[k].x == x && m_ballImages[k].y == y) {
                                    m_ballImages[k].hidden = YES;
                                    m_ballImages[k].lastX = m_ballImages[k].x;
                                    m_ballImages[k].lastY = m_ballImages[k].y;
                                    m_ballImages[k].x = 0;
                                    m_ballImages[k].y = 0;
                                    break;
                                }
                            }
                            m_board[cordX[i]][cordY[i]] = 1;
                            m_board[ball.x][ball.y] = 0;
                            ball.x = cordX[i];
                            ball.y = cordY[i];
                            ball.frame = CGRectMake(m_boxes[i].origin.x, m_boxes[i].origin.y, 55, 54);

                            return YES;
                        }
                        else {
                            return NO;
                        }
                    }
                    else {
                        return NO;
                    }
                }
                else {
                    return NO;
                }
            }
            else {
                return NO;
            }
        }
    }
    return NO;
}

We need to add this function to OneByOneSolitaireViewController.m and a declaration to OneByOneSolitaireViewController.h:

- (BOOL) move:(Ball *) ball:(CGPoint) point;

The function move returns a boolean. When the move is not valid the function returns NO. In these cases we need to restore the peg to their original position. To do this the object Ball call the method setBallOriginalPosition of the object board and then makes an animation which moves the peg to its original position. This is the code of the function:

- (void) setBallOriginalPosition:(Ball *) ball {
	int x = -1, y = -1;

	for (int i = 0; i < 33; i++) {
		if (cordX[i] == ball.x) {
			x = i;
			break;
		}
	}
	for (int i = 0; i < 33; i++) { 		if (cordY[i] == ball.y) { 			y = i; 			break; 		} 	} 	if (x >= 0 && y >= 0) {
		ball.cordX = posX[x];
		ball.cordY = posY[y];
	}
}

Add this code to the end of OneByOneSolitaireViewController.m and a declaration of this function in OneByOneSolitaireViewController.h:

- (void) setBallOriginalPosition:(Ball *) ball;

At this point the code allows us to play the game. There is a lot of functionality waiting to be coded, but for the goals of this tutorial we are finishing here. You can donwload this version of the code and the full version:

Tutorial version:

http://www.crowsoft.com.ar/blog/tutorials/onebyonesolitaire/onebyonesolitaire.zip

Full version:

http://www.crowsoft.com.ar/blog/tutorials/onebyonesolitaire/onebyonesolitaire_tutorial.zip

If you are interested in an explanation about the full version let me know by email to “javier at crowsoft dot com dot ar”.

If you have found this useful and want to help the author you can buy the game in the app store

http://itunes.apple.com/us/app/one-by-one-solitaire/id450764439?mt=8