HiveBrain v1.2.0
Get Started
← Back to all entries
patternModerate

Tic Tac Toe implementation in Objective-C

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
toetictacobjectiveimplementation

Problem

First off, here's the code:

main.m

```
#import
#import "PSBoard.h"
#import "PSPlayer.h"
#import "PSInputHandler.h"

int main(int argc, const char * argv[]) {

@autoreleasepool {

NSLog(@"Enter Player 1 Name:");
NSString *playerOneName = [PSInputHandler getString];
NSLog(@"Enter Player 2 Name:");
NSString *playerTwoName = [PSInputHandler getString];

NSLog(@"How many rows and columns will you play with?");
NSUInteger numberOfRowsAndColumns = [PSInputHandler getInteger];

PSPlayer *playerOne = [[PSPlayer alloc] initWithSymbol:PSBoardSymbolX name:playerOneName];
PSPlayer *playerTwo = [[PSPlayer alloc] initWithSymbol:PSBoardSymbolO name:playerTwoName];
PSBoard *board = [[PSBoard alloc] initWithRows:numberOfRowsAndColumns columns:numberOfRowsAndColumns players:@[playerOne, playerTwo]];

do {
PSPlayer *currentPlayer = [board playerUp];
BOOL validInputEntered = NO;

//Loop until valid input is entered
while(!validInputEntered) {
//Get input coordinates
NSLog(@"%@, enter a row (1-%lu).", currentPlayer.name, (unsigned long)numberOfRowsAndColumns);
NSUInteger row = [PSInputHandler getInteger];
NSLog(@"Now enter a column (1-%lu).", (unsigned long)numberOfRowsAndColumns);
NSUInteger column = [PSInputHandler getInteger];

//Verify that nothing is already placed there
PSBoardSymbol symbolOfPlayerAtCoordinates = [board playerAtRow:row-1 column:column-1].symbol;

if((symbolOfPlayerAtCoordinates != PSBoardSymbolX && symbolOfPlayerAtCoordinates != PSBoardSymbolO) &&
row > 0 && row 0 && column <= numberOfRowsAndColumns) {
[board setPlayer:currentPlayer atRow:(row-1) column:(column-1)];
validInputEntered = YES;
}
}

//Show the b

Solution

I will update this post over the weekend as I go through your question more and come up with some examples to iterate over my points, but I thought for now, I'd answer some of the easier questions.

Question 1.

I'm not sure and cannot remember (I will try to find out). At the end of the day, you might consider implementing this with a GUI. If you're using Xcode, it's quite easy to develop a GUI for either OSX or iOS, and most of your logic is already in place. You'd just have to write the logic to hook the GUI up to the business logic.

Question 2.

One immediate thought on speeding up this process would be to use a flag to mark whether or not a row/column is a potential winner. AND, if you do find a row that's a winner, immediately return the winner.

For a row to be a winner, every piece in the row must belong to the same player, correct? So set the owner of the piece in the first box, and check every box. As soon as you get to a box that doesn't match the first box, break;. You don't need to check any more boxes in that row/column/diagonal. You can move to the next row/column/diagonal. And if you get to the end of the inner loop and haven't had to break; because you've found the winner, then you can set the winner and return; and stop checking.

So basically, refactor into something more like this:

for(NSUInteger row = 0; row < self.rows; row++) {

    PSPlayer *playerInFirstColumn = [self playerAtRow:row column:0];
    BOOL winnerFound;

    for(NSUInteger column = 0; column < self.columns; column++) {
        if(![[self playerAtRow:row column:column] isEqualTo:playerInFirstColumn]) {
            winnerFound = false;
            break;
        } else {
            winnerFound = true;
        }
    }

    if (winnerFound) {
        self.winner = playerInFirstColumn;
        return;
    }
}


This will improve performance some. You can probably still do better, but this is still a drastic improvement, especially for exceptionally large boards.

Now... the BEST performance improvement I can think of would actually mean you're running this check after every turn (which you're already doing, right?). In this case, you only need to check ONE row, ONE column, and ZERO, ONE, or TWO diagonals. And this would be a massive performance boost. You only need to check a the row the piece was played in, the column the piece was played in, and the diagonal the piece was played in. Every other row, column, and diagonal has been checked on a previous turn and a winner was not found otherwise the game would be over and we wouldn't've had this turn.

AND, even if we modified the rules to continue playing after a winner has been found, you can just use an array to keep track of each row and column and diagonal and who won that row/column/diagonal, and still only need to check the relevant rows (and only check them for the player who played the piece).

Question 3.

playerUp is probably an alright method name. Maybe activePlayer? If you feel it's not descriptive enough, don't hesitate to leave a comment explaining it. // returns the player whose turn it is

Question 4.

As a programmer, I am used to 0-based indexing systems, but your assumption is correct. Most people who use programs aren't programs and would be more comfortable with a 1-based coordinate system. Though... back to question 1... if this were given a GUI, it wouldn't matter. ;)

Question 5.

Personally, I hate the ternary operators and never use them. Whether or not they're acceptable would depend largely on who you're working with though. In this case, it's a simple one. Again, personally, I hate them and I wouldn't use it, because I never use it, but this one is simple enough that if you and everyone working on the project are comfortable with them, then go ahead and keep it.

Question 5.1.

The exact way you want to handle non-number input is an implementation detail that'd be up to you. Do you want to request another input? Do you want to just strip out the non-numbers and use the numbers that are there?

But as for actually checking the string itself, once you've got it into an NSString, it's quite easy:

NSCharacterSet *nonNumbers = [[NSCharacterSet 
    decimalDigitCharacterSet] invertedSet];

if([yourString rangeOfCharactersFromSet:nonNumbers].location == NSNotFound) {
    // string is all numbers and is good to go
} else {
    // string contains non-number characters
}

Code Snippets

for(NSUInteger row = 0; row < self.rows; row++) {

    PSPlayer *playerInFirstColumn = [self playerAtRow:row column:0];
    BOOL winnerFound;

    for(NSUInteger column = 0; column < self.columns; column++) {
        if(![[self playerAtRow:row column:column] isEqualTo:playerInFirstColumn]) {
            winnerFound = false;
            break;
        } else {
            winnerFound = true;
        }
    }

    if (winnerFound) {
        self.winner = playerInFirstColumn;
        return;
    }
}
NSCharacterSet *nonNumbers = [[NSCharacterSet 
    decimalDigitCharacterSet] invertedSet];

if([yourString rangeOfCharactersFromSet:nonNumbers].location == NSNotFound) {
    // string is all numbers and is good to go
} else {
    // string contains non-number characters
}

Context

StackExchange Code Review Q#40820, answer score: 13

Revisions (0)

No revisions yet.