// // NCGameModel.h // NoughtsCrosses // Version 3.0 (2015) // // Created by Terry Payne on 21/10/2011. // Updated to ARC on 14/10/2012 // Improved on 10/10/2015 // Copyright 2011/2012/2015 University of Liverpool. All rights reserved. // #import // ======================================================================================= // Usefull Macros and typedefs - see below // ======================================================================================= #define NO_CORRESPONDING_WINNING_LINE 0 #define STALEMATE -1 #define NO_OPTIMAL_POSITION_FOUND -1 typedef enum NC_CounterType { NC_Error = -1, NC_Empty = 0, NC_Nought = 1, NC_Cross = 2, } NC_CounterType; typedef enum NC_GameState { NC_GameInProgress = 0, NC_GameWon = 1, NC_GameStalemate = 2, } NC_GameState; @interface NCGameModel : NSObject // ======================================================================================= // // The NCGameModel was developed as a Model class to support the game Noughts and Crosses // It's design has been to be as general as possible, allowing for game boards of any // size (greater than 3). In addition, the AI heuristic rules for 3x3 noughts and crosses // have been included, allowing for "hints" on the optimal move for a given player. // // A small number of Enumerated Types and macros have been included to assist in using // this model. // // The macro value NO_CORRESPONDING_WINNING_LINE may be returned by the method // -(NSInteger)getWinningLine if no winning line has yet been detected; this is typically // only the case if the game state returned by -(NC_GameState)getGameState is not NC_GameWon // // Similarly, it is possible that NO_OPTIMAL_POSITION_FOUND may be returned by the method // -(NSInteger)getOptimalMoveForPlayer:(NC_CounterType)player, however, this would suggest // that there is an error in the code or memory corruption as this should not happen!!! // // The Enumerated types NC_CounterType represent counters or states in the model. They have // the values: // NC_Error - this is reserved for error conditions // NC_Empty - the counter is "empty"; i.e. there is no counter // NC_Nought - the counter is a "nought" // NC_Cross - the counter is a "cross" // // The method -(NC_GameState)getGameState will return a enumerated value of the // type NC_GameState, which represents the following states: // NC_GameInProgress - the game is in progress // NC_GameWon - the game has been won by one of the players // NC_GameStalemate - stalemate; the game cannot be won // // ======================================================================================= // Properties // ======================================================================================= // // The following determines who the current player is. Thus, an application // can use this to determine the current player. @property (nonatomic, assign) NC_CounterType currentPlayer; // Determines the current player of a game // ======================================================================================= // The second property determines if the game is over. This is a readonly property, and // is called by using the method "isGameOver". This is a standard pattern when defining // getters on boolean properties. @property (nonatomic, assign, readonly, getter=isGameOver) BOOL gameOver; // Determines when the game is over // ======================================================================================= // The third property determines if the game is not only finished, but also if there is no // winner - i.e. we have a stalemate, and is called by using the method "isStalemate" @property (nonatomic, assign, readonly, getter=isStalemate) BOOL stalemate; // Returns true if the the game is over and there is no winner // ======================================================================================= // This property returns the board size - i.e. the number of squares along one side of the board; // it corresponds to the value passed to the method initWithBoardSize:. // In a traditional 3x3 noughts and crosses game, this would be 3. @property (nonatomic, assign, readonly) NSInteger boardSize; // ======================================================================================= // This property returns the total number of positions on the board - typically n*n where n // corresponds to the board size. In a traditional 3x3 noughts and crosses game, this would be 9. @property (nonatomic, assign, readonly) NSInteger numberOfPositions; // ======================================================================================= // Factory Method - setting up the model // ======================================================================================= // A model should always be created using the following factory class: +(NCGameModel *) modelWithBoardSize:(NSInteger) n; // This initialises the new model, and specifies the size of the board. In most cases // (for traditional 3x3 noughts and crosses), this would be implemented as: // // if ((gameModel = [NCGameModel modelWithBoardSize:3])!=nil) { // ... // } // // This will also ensure that all the defualt values and internal state have been // defined. // =========================== // General Model Queries // // The following methods can be used make general queries about the model. //-(NSInteger) getNumberOfPositions; // This returns the total number of positions on the board - typically n*n where n // corresponds to the board size. // In a traditional 3x3 noughts and crosses game, this would be 9. // ======================================================================================= // Adding and checking the counters in the model // ======================================================================================= // // Methods have been provided to insert counters (i.e. nought counters or cross counters) // within an empty position, and to get the counter type at positions on the board; either // using absolute positions or using row and column positions. // // The board is modelled as a single array, where each row is modelled as a fragment in the // array, and there are boardSize fragments for each of the rows. The first element corresponds // to the position 0. Thus, a 3x3 board would have an array of 9 positions, indexed 0..8 // arranged as follows: // // 0 1 2 // 3 4 5 // 6 7 8 // // To insert a counter at a position for the current player, use the following method: -(BOOL)addCounterAtPosition:(NSInteger)pos; // The existence of counters can be determined by using the following method, which // would normally return: NC_Nought or NC_Cross if a counter is at that position, or // NC_Empty if the position is empty. -(NC_CounterType)getCounterAtPosition:(NSInteger)pos; // An example of this could be when developing a view class that displays the // counters. The code below checks the counter at a given position (i) and then // calls the method -drawCounterAtPosition:isNought: whenever a nought or cross // is detected (the counter type is passed as a bool in this case). // // countertype = [gameModel getCounterAtPosition:i]; // switch (countertype) { // case NC_Nought: // [self drawCounterAtPosition:i isNought:YES]; // break; // case NC_Cross: // [self drawCounterAtPosition:i isNought:NO]; // break; // default: // break; // } // // ////////////////////////////////////////////////////////////////////// // // HINT: The methods above would typically be used by the // // view class when painting the view // // ////////////////////////////////////////////////////////////////////// // // ======================================================================================= // Optional Method for those wanying to play with the AI heuristics // ======================================================================================= // // Note that he method below is not required for the assignment, but students may // want to exploit it if there own studies. // // =========================== // Optimal Move Method // // Finally, the method -getOptimalMoveForPlayer:: uses the AI heuristics built // into the model to determine the optimal move. This then returns a position // which can then be used by the caller, either to make recommendations, or to // determine a computer players next move. The optimal move may be different // for nought vs cross; therefore to request the correct move, pass in the // counter type as the method's argument. // The mistakes argument determines wether or not mistakes could be made // and can be used if the AI engine should be easier to play against. -(NSInteger) getOptimalMoveForPlayer:(NC_CounterType)player withMistakes:(BOOL)mistakes; @end