private int MiniMax(Move move, GridData gridData, int turn, int depth) { // The MiniMax method gives the best possible move for the current grid data. // This works by, effectively, playing each possible game. If that game results // in a CPU win, then return a positive score; if it results in a CPU loss, // then return a negative score. // The CPU and Player alternate turns. The Player is aiming to get the lowest // possible score (i.e. a CPU loss / a Player win). Therefore, when the lowest // score possible for the Player is positive, then a perfect-playing CPU will // eventually win, and that is the move it should take. // We limit the depth, in order to create a beatable CPU. eState moveState = new StatePlayerConverter().GetPlayerState(turn); gridData.PlaceMove(move, moveState); if(gridData.HasWinner()) { int winScore = 10 - depth; return IsCPUMove(turn) ? winScore: -winScore; } else if(gridData.IsStalemate() || depth == _maxDepth) { return 0; } else { // Game is neither won nor a stalemate. Therefore, we'll keep playing. int nextTurn = (turn + 1) % 2; int bestScore = -20; List<Move> nextMoves = gridData.GetPossibleMoves(); foreach(Move nextMove in nextMoves) { int score = MiniMax(nextMove, gridData.Copy(), nextTurn, depth + 1); // Player move will return a negative score if it has won. Negate it for // now so that it is positive and will be seen as the player's best move. score = IsCPUMove(nextTurn) ? score : -score; if(score > bestScore) { bestScore = score; } } // Player move has negated to find the best score. Turn it back, so that it // gives an accurate score for CPU move. return IsCPUMove(nextTurn) ? bestScore: -bestScore; } }
public CPUMoveFinder(GridData gridData, bool isBeatable) { _gridData = gridData; _maxDepth = isBeatable ? 4 : -1; }