// Returns the best move from some particular state for the player which has the turn, within the time given // Returns random move if not enough time is given // pre: startState != null && !startState.isFinal() public UTTTMove FindBestMove(UTTTState startState, int msGiven) { if (startState == null || startState.IsFinal()) { throw new ArgumentException("TimedSearcher.FindBestMove: Invalid startState passed."); } // stopwatch keeps track of how much time we have left Stopwatch sw = new Stopwatch(); sw.Start(); // iterative deepening UTTTMove bestMove = null; double bestScore = 0; TimedAlphaBetaWIP timedAB = new TimedAlphaBetaWIP(sw, msGiven, startState); for (int depthLeft = 1; sw.ElapsedMilliseconds < msGiven; depthLeft++) { List <UTTTMove> newMoveList = new List <UTTTMove>(); TimedAlphaBetaWIP.AlphaBetaIterationResult res = timedAB.computeNextIteration(); if (res.outOfTime) { break; } else { bestMove = res.bestMove; bestScore = res.bestMoveValue; } } // if no time to find any move, return random move if (bestMove == null) { bestMove = startState.GetPossibleMoves().First(); } sw.Stop(); Console.Error.WriteLine(bestScore); return(bestMove); }
// state : The root of the (sub)-tree this search expands // alpha : Value used for AlphaBeta pruning - maximum value maximizing player can definitely get // beta : Value used for AlphaBeta pruning - minimum value minimizing player can definitely get // depthLeft : How many more depths we are allowed to explore private double AlphaBetaSearch(UTTTState state, double alpha, double beta, int depthLeft) { // throw an exception if our player is forced to stop if (sw.ElapsedMilliseconds > msGiven) { throw new OutOfTimeException(); } // if final depth reached, then return the value of this leaf if (depthLeft == 0 || state.IsFinal()) { return(state.Evaluate()); } // if not final depth, then generate all possible branches List <UTTTMove> moves = state.GetPossibleMoves(); // if (moves.Count == 0) { throw new OutOfTimeException(); } // if only a single move is possible, don't decrease depth if (moves.Count == 1) { depthLeft++; } // if the state has been seen before, then continue // tracing the best move of the previous iteration by putting the old move // in the first position to be evaluated. This leads to better pruning. StateRecord rec = null; statesSeen.TryGetValue(state.GetHashCode(), out rec); if (rec != null) { if (rec.iteration == iteration) { return(rec.value); } if (rec.bestMove != null) { FindCopyAndSwap(rec.bestMove, moves, moves.First()); } statesSeen.Remove(state.GetHashCode()); } else { rec = new StateRecord(); } // keep track of the best move // update the StateRecord in the dictionary after we have finished analyzing the state Boolean min = state.MinimizingHasTurn(); UTTTMove bestMove = null; foreach (UTTTMove move in moves) { state.DoMove(move); double result = AlphaBetaSearch(state, alpha, beta, depthLeft - 1); state.UndoMove(move); if ((min && result < beta) || (!min && result > alpha)) { bestMove = move; if (min) { beta = result; } else { alpha = result; } } if (alpha >= beta) { // update record and return double res = (alpha + beta) / 2; rec.Update(res, bestMove, state, statesSeen, iteration); return(res); } } // update record and return if (min) { rec.Update(beta, bestMove, state, statesSeen, iteration); return(beta); } else { rec.Update(alpha, bestMove, state, statesSeen, iteration); return(alpha); } }