// Starts the time limited Monte Carlo Tree Search and returns the best child node // resulting from the search public Node TimeLimitedMCTS(State rootState, int timeLimit) { Stopwatch timer = new Stopwatch(); Node bestNode = null; while (bestNode == null && !rootState.IsGameOver()) { timer.Start(); Node rootNode = TimeLimited(rootState, timeLimit, timer); bestNode = FindBestChild(rootNode.Children); timeLimit += 10; timer.Reset(); } return bestNode; }
// recursive part of the minimax algorithm when used in iterative deepening search // checks at each recursion if timeLimit has been reached // if is has, it cuts of the search and returns the best move found so far, along with a boolean indicating that the search was not fully completed private Tuple<Move, Boolean> IterativeDeepeningAlphaBeta(State state, int depth, double alpha, double beta, double timeLimit, Stopwatch timer) { Move bestMove; if (depth == 0 || state.IsGameOver()) { if (state.Player == GameEngine.PLAYER) { bestMove = new PlayerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return new Tuple<Move, Boolean>(bestMove, true); } else if (state.Player == GameEngine.COMPUTER) { bestMove = new ComputerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) // AI's turn { bestMove = new PlayerMove(); double highestScore = Double.MinValue, currentScore = Double.MinValue; List<Move> moves = state.GetMoves(); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); currentScore = IterativeDeepeningAlphaBeta(resultingState, depth - 1, alpha, beta, timeLimit, timer).Item1.Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } alpha = Math.Max(alpha, highestScore); if (beta <= alpha) { // beta cut-off break; } if (timer.ElapsedMilliseconds > timeLimit) { bestMove.Score = highestScore; return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false } } bestMove.Score = highestScore; return new Tuple<Move, Boolean>(bestMove, true); } else if (state.Player == GameEngine.COMPUTER) // computer's turn (the random event node) { bestMove = new ComputerMove(); double lowestScore = Double.MaxValue, currentScore = Double.MaxValue; List<Move> moves = state.GetMoves(); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); currentScore = IterativeDeepeningAlphaBeta(resultingState, depth - 1, alpha, beta, timeLimit, timer).Item1.Score; if (currentScore < lowestScore) { lowestScore = currentScore; bestMove = move; } beta = Math.Min(beta, lowestScore); if (beta <= alpha) break; if (timer.ElapsedMilliseconds > timeLimit) { bestMove.Score = lowestScore; return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false } } bestMove.Score = lowestScore; return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); }
// Recursive part of iterative deepening Expectimax private Tuple<Move, Boolean> IterativeDeepeningExpectimax(State state, int depth, double timeLimit, Stopwatch timer) { Move bestMove; if (depth == 0 || state.IsGameOver()) { if (state.Player == GameEngine.PLAYER) { bestMove = new PlayerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return new Tuple<Move, Boolean>(bestMove, true); } else if (state.Player == GameEngine.COMPUTER) { bestMove = new ComputerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) // AI's turn { bestMove = new PlayerMove(); double highestScore = Double.MinValue, currentScore = Double.MinValue; List<Move> moves = state.GetMoves(); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); currentScore = IterativeDeepeningExpectimax(resultingState, depth - 1, timeLimit, timer).Item1.Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } if (timer.ElapsedMilliseconds > timeLimit) { bestMove.Score = highestScore; return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false } } bestMove.Score = highestScore; return new Tuple<Move, Boolean>(bestMove, true); } else if (state.Player == GameEngine.COMPUTER) // computer's turn (the random event node) { bestMove = new ComputerMove(); // return the weighted average of all the child nodes's scores double average = 0; List<Cell> availableCells = state.GetAvailableCells(); List<Move> moves = state.GetAllComputerMoves(availableCells); int moveCheckedSoFar = 0; foreach (Move move in moves) { State resultingState = state.ApplyMove(move); average += StateProbability(((ComputerMove)move).Tile) * IterativeDeepeningExpectimax(resultingState, depth - 1, timeLimit, timer).Item1.Score; moveCheckedSoFar++; if (timer.ElapsedMilliseconds > timeLimit) { bestMove.Score = average / moveCheckedSoFar; return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false } } bestMove.Score = average / moves.Count; return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); }