/// <summary> /// The constructor calculates the score if this is a leaf node. Otherwise it builds the rest /// of the branch. /// </summary> /// <param name="game">The game state at this point in the path.</param> /// <param name="move">The move taken to get to this game state.</param> public MinMaxNode(Game game, Tuple <int, int> move = null) { this.Move = move; this._game = game; // If the game is over, set the scores relative to the X side. When evaluating for O later on, // we will just negate the scores as needed. if (this._game.IsXWin) { this._xScore = 1.0; } else if (this._game.IsOWin) { this._xScore = -1.0; } else if (this._game.IsTie) { this._xScore = 0.0; } else { // If we got here, it's because the game is not over. Walk the board and try each of the paths. this._children = new List <MinMaxNode>(); for (int row = 0; row < 3; row++) { for (int column = 0; column < 3; column++) { if (this._game.Board.IsEmptyAt(row, column)) { // If we can move here, then copy the game, simulate the move, and add the node to the children. Game copy = this._game.Copy(); copy.Mark(row, column); // NOTE: Due to the nature of Tic-Tac-Toe, we know that all boards of the same configuration are // identical. To optimize the performance, this step caches each board state by its serialized string // and just reuses identical boards instead of creating new ones and iterating their paths redundantly. if (MinMaxNode._useCache && MinMaxNode._nodeCache.ContainsKey(copy.Board.Serialize())) { this._children.Add(MinMaxNode._nodeCache[copy.Board.Serialize()]); } else { MinMaxNode node = new MinMaxNode(copy, Tuple.Create(row, column)); this._children.Add(node); if (MinMaxNode._useCache) { MinMaxNode._nodeCache.Add(copy.Board.Serialize(), node); } } } } } } }
public Tuple <int, int> GetMove(Game game) { MinMaxNode minMax = new MinMaxNode(game); return(minMax.GetMove(game.IsXTurn)); }