First off, here's the code:
main.m
#import <Foundation/Foundation.h>
#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 <= numberOfRowsAndColumns && column > 0 && column <= numberOfRowsAndColumns) {
[board setPlayer:currentPlayer atRow:(row-1) column:(column-1)];
validInputEntered = YES;
}
}
//Show the board
[board display];
} while(!board.winner);
NSLog(@"Congrats %@! You won.", [board winner].name);
}
return 0;
}
PSBoard.h
#import <Foundation/Foundation.h>
@class PSPlayer;
@interface PSBoard : NSObject
@property (nonatomic) NSUInteger rows;
@property (nonatomic) NSUInteger columns;
@property (nonatomic, strong) PSPlayer *winner;
-(instancetype)initWithRows:(NSUInteger)rows columns:(NSUInteger)columns players:(NSArray *)players;
-(PSPlayer *)playerAtRow:(NSUInteger)row column:(NSUInteger)column;
-(void)setPlayer:(PSPlayer *)player atRow:(NSUInteger)row column:(NSUInteger)column;
-(void)display;
-(PSPlayer *)playerUp;
@end
PSBoard.m
#import "PSBoard.h"
#import "PSPlayer.h"
@interface PSBoard ()
@property (nonatomic, strong) NSMutableArray *internalBoardRepresentation;
@property (nonatomic, strong) NSArray *players;
@property (nonatomic, strong) PSPlayer *oldPlayerUp;
@end
@implementation PSBoard
-(instancetype)initWithRows:(NSUInteger)rows columns:(NSUInteger)columns players:(NSArray *)players {
if(self = [super init]) {
self.rows = rows;
self.columns = columns;
self.players = players;
self.internalBoardRepresentation = [[NSMutableArray alloc] initWithCapacity:rows];
PSPlayer *null = [[PSPlayer alloc] initWithSymbol:PSBoardSymbolNone name:nil];
for(NSUInteger row = 0; row < rows; row++) {
NSMutableArray *currentColumn = [NSMutableArray array];
for(NSUInteger column = 0; column < columns; column++) {
[currentColumn addObject:null];
}
[self.internalBoardRepresentation addObject:currentColumn];
}
self.oldPlayerUp = players[0];
}
return self;
}
-(PSPlayer *)playerAtRow:(NSUInteger)row column:(NSUInteger)column {
return self.internalBoardRepresentation[row][column];
}
-(void)setPlayer:(PSPlayer *)player atRow:(NSUInteger)row column:(NSUInteger)column {
self.internalBoardRepresentation[row][column] = player;
[self checkForWinner];
}
-(void)checkForWinner {
NSUInteger numberOfPiecesInARowToWin = MAX(self.rows, self.columns);
//Check horizontal lines
for(NSUInteger row = 0; row < self.rows; row++) {
PSPlayer *playerInFirstColumn = [self playerAtRow:row column:0];
NSUInteger playerPiecesInRow = 0;
for(NSUInteger column = 0; column < self.columns; column++) {
if([[self playerAtRow:row column:column] isEqualTo:playerInFirstColumn]) {
playerPiecesInRow++;
}
}
if(playerPiecesInRow >= numberOfPiecesInARowToWin && playerInFirstColumn.symbol != PSBoardSymbolNone) {
self.winner = playerInFirstColumn;
return;
}
}
//Check vertical lines
for(NSUInteger column = 0; column < self.columns; column++) {
PSPlayer *playerInFirstRow = [self playerAtRow:0 column:column];
NSUInteger playerPiecesInColumn = 0;
for(NSUInteger row = 0; row < self.rows; row++) {
if([[self playerAtRow:row column:column] isEqualTo:playerInFirstRow]) {
playerPiecesInColumn++;
}
}
if(playerPiecesInColumn >= numberOfPiecesInARowToWin && playerInFirstRow.symbol != PSBoardSymbolNone) {
self.winner = playerInFirstRow;
return;
}
}
//Check top left to bottom right diagonal
PSPlayer *playerInFirstSlotOfLeftDiagonal = [self playerAtRow:0 column:0];
NSUInteger playerPiecesInLeftDiagonal = 0;
for(NSUInteger row = 0, column = 0; row < self.rows; column++, row++) {
if([[self playerAtRow:row column:column] isEqualTo:playerInFirstSlotOfLeftDiagonal]) {
playerPiecesInLeftDiagonal++;
}
}
if(playerPiecesInLeftDiagonal >= numberOfPiecesInARowToWin && playerInFirstSlotOfLeftDiagonal.symbol != PSBoardSymbolNone) {
self.winner = playerInFirstSlotOfLeftDiagonal;
return;
}
//Check bottom left to top right diagonal
PSPlayer *playerInFirstSlotOfRightDiagonal = [self playerAtRow:self.rows-1 column:0];
NSUInteger playerPiecesInRightDiagonal = 0;
for(NSInteger row = self.rows-1, column = 0; row >= 0; row--, column++) {
if([[self playerAtRow:row column:column] isEqualTo:playerInFirstSlotOfRightDiagonal]) {
playerPiecesInRightDiagonal++;
}
}
if(playerPiecesInRightDiagonal >= numberOfPiecesInARowToWin && playerInFirstSlotOfRightDiagonal.symbol != PSBoardSymbolNone) {
self.winner = playerInFirstSlotOfRightDiagonal;
return;
}
}
-(void)display {
NSMutableString *displayString = [NSMutableString stringWithFormat:@"\n"];
for(NSUInteger row = 0; row < self.rows; row++) {
NSMutableString *rowDisplayString = [[NSMutableString alloc] init];
NSString *innerFillerString = (row == self.rows-1) ? @" " : @"_";
for(NSUInteger column = 0; column < self.columns; column++) {
NSString *columnSeparator = (column == self.columns-1) ? @" " : @"|";
NSString *playerSymbol = ([self playerAtRow:row column:column].symbolStringRepresentation);
if(playerSymbol.length == 0) {
playerSymbol = innerFillerString;
}
[rowDisplayString appendString:[NSString stringWithFormat:@"%@%@%@%@", innerFillerString, playerSymbol, innerFillerString, columnSeparator]];
}
[displayString appendString:[NSString stringWithFormat:@"%@\n", rowDisplayString]];
[rowDisplayString setString:@""];
}
NSLog(@"%@", displayString);
}
-(PSPlayer *)playerUp {
PSPlayer *nextPlayerUp = self.players[1-([self.players indexOfObjectIdenticalTo:self.oldPlayerUp])];
PSPlayer *previousPlayerUp = self.oldPlayerUp;
self.oldPlayerUp = nextPlayerUp;
return previousPlayerUp;
}
@end
PSPlayer.h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, PSBoardSymbol) {
PSBoardSymbolX = 0,
PSBoardSymbolO,
PSBoardSymbolNone
};
@interface PSPlayer : NSObject
-(instancetype)initWithSymbol:(PSBoardSymbol)symbol name:(NSString *)name;
@property (nonatomic) PSBoardSymbol symbol;
@property (nonatomic) NSString *symbolStringRepresentation;
@property (nonatomic, strong) NSString *name;
@end
PSPlayer.m
#import "PSPlayer.h"
@implementation PSPlayer
-(instancetype)initWithSymbol:(PSBoardSymbol)symbol name:(NSString *)name{
if(self = [super init]) {
self.symbol = symbol;
self.symbolStringRepresentation = (symbol == PSBoardSymbolO) ? @"O" : ((symbol == PSBoardSymbolX) ? @"X" : @"");
self.name = name;
}
return self;
}
@end
PSInputHandler.h
#import <Foundation/Foundation.h>
@interface PSInputHandler : NSObject
+(NSString *)getString;
+(NSInteger)getInteger;
@end
PSInputHandler.m
#import "PSInputHandler.h"
@implementation PSInputHandler
+(NSInteger)getInteger {
int temp;
scanf("%i", &temp);
return (NSInteger)temp;
}
+(NSString *)getString {
char input[256];
scanf("%s", input);
return [NSString stringWithUTF8String:input];
}
@end
So my questions are:
- In the
PSInputHandler.m
class, I wasn't so sure about how to get input from the command line. I read thatfgets()
is a potential alternative toscanf()
, but is there any reason for me to use one over the other? - The method in
PSBoard.m
that checks for a winner,checkForWinner
, is very long. Is there a simplified design I can use to shorten it? - I struggled to name the
playerUp
method, which returns the player whose turn it is. Is there a more suitable name? - When the user inputs which row and column to place an X or O in, I made it so that the coordinates they enter are from 1 to the number of rows and not 0 to the number of rows minus one (like with zero-based array indexing). Is this more user-friendly, or should I change it to zero-based indexing?
- In
PSPlayer.m
, I use nested ternary operators. Is this too hard to understand? Should I change it to if statements?- When getting the user's input for how many rows and columns to use, which I expect to be an integer, how can I sanitize the input so that the program doesn't crash when a string (for example) is inputted?
Any other critique welcome!