// Classic Expectimax search public Move ExpectimaxAlgorithm(State state, int depth, WeightVector weights) { 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.EvaluateWithWeights(state, weights); return bestMove; } else if (state.Player == GameEngine.COMPUTER) { bestMove = new ComputerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.EvaluateWithWeights(state, weights); return bestMove; } 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 = ExpectimaxAlgorithm(resultingState, depth - 1, weights).Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } } bestMove.Score = highestScore; return bestMove; } 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); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); average += StateProbability(((ComputerMove)move).Tile) * ExpectimaxAlgorithm(resultingState, depth - 1, weights).Score; } bestMove.Score = average / moves.Count; return bestMove; } else throw new Exception(); }
private static void TestGetMoves(State state, IEnumerable <string> expected) { var moves = expected.Select(an => Move.Parse(an, state)); var succs = moves.Select(m => state.Apply(m)); var succPairs = moves.Zip(succs, (move, succ) => new SuccessorPair(move, succ)); Assert.Equal(moves, state.GetMoves(), OrderInsensitiveComparer <Move> .Instance); Assert.Equal(succPairs, state.GetSuccessors(), OrderInsensitiveComparer <SuccessorPair> .Instance); }
private Move?TryGetRandomMove(State state) { var moves = state.GetMoves().ToArray(); if (moves.Length == 0) { return(null); } return(moves[_rng.Next(0, moves.Length)]); }
// 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(); }
public void Moves(State state) { WriteLine("List of valid moves:"); WriteLine(); WriteLine(string.Join(Environment.NewLine, state.GetMoves())); }
// Simulates a game to the end (game over) based on the default policy // Returns the game over state private State SimulateGame(State state, int POLICY) { if (POLICY == RANDOM_POLICY) { while (state.GetMoves().Count != 0) { state = state.ApplyMove(state.GetRandomMove()); } return state; } else if (POLICY == BEST_EVAL_POLICY) { List<Move> moves = state.GetMoves(); while (moves.Count != 0) { // random move for computer if (state.Player == GameEngine.COMPUTER) { state = state.ApplyMove(state.GetRandomMove()); } else { // find the move that results in the best child state, based on the evaluation function State bestState = null; double bestScore = Double.MinValue; foreach (Move move in moves) { State result = state.ApplyMove(move); double score = AI.Evaluate(result); if (score > bestScore) { bestScore = score; bestState = result; } } state = bestState; } moves = state.GetMoves(); } return state; } else if (POLICY == EXPECTIMAX_POLICY) { Expectimax expectimax = new Expectimax(gameEngine, 2); while (state.GetMoves().Count != 0) { if (state.Player == GameEngine.COMPUTER) { state = state.ApplyMove(state.GetRandomMove()); } else { Move move = expectimax.ExpectimaxAlgorithm(state, 2, weights); state = state.ApplyMove(move); } } return state; } else throw new Exception(); }
// Runs a root-parallelized Monte Carlo Tree Search in the same way as the RootParallelizationMCTS, // but limited by a time limit instead of number of iterations public DIRECTION RootParallelizationMCTSTimeLimited(State rootState, int timeLimit, int numOfThreads) { ConcurrentBag<Node> allChildren = new ConcurrentBag<Node>(); int numOfChildren = rootState.GetMoves().Count; Stopwatch timer = new Stopwatch(); timer.Start(); Parallel.For(0, numOfThreads, i => { Node resultRoot = TimeLimited(rootState, timeLimit, timer); foreach (Node child in resultRoot.Children) { allChildren.Add(child); } }); timer.Stop(); List<int> totalVisits = new List<int>(4) { 0, 0, 0, 0 }; List<double> totalResults = new List<double>(4) { 0, 0, 0, 0 }; foreach (Node child in allChildren) { int direction = (int)((PlayerMove)child.GeneratingMove).Direction; totalVisits[direction] += child.Visits; totalResults[direction] += child.Results; } double best = Double.MinValue; int bestDirection = -1; for (int k = 0; k < 4; k++) { double avg = totalResults[k] / totalVisits[k]; if (avg > best) { best = avg; bestDirection = k; } } if (bestDirection == -1) return (DIRECTION)(-1); return (DIRECTION)bestDirection; }
// Recursive part of iterative deepening Expectimax with transposition table private Tuple<Move, Boolean> RecursiveTTExpectimax(State state, int depth, double timeLimit, Stopwatch timer, WeightVector weights) { 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.EvaluateWithWeights(state, weights); 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.EvaluateWithWeights(state, weights); return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) // AI's turn { // transposition table look-up long zob_hash = GetHash(state); if (transposition_table.ContainsKey(zob_hash) && transposition_table[zob_hash].depth > depth) { Move move = new PlayerMove(transposition_table[zob_hash].direction); move.Score = transposition_table[zob_hash].value; return new Tuple<Move, Boolean>(move, true); } 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 = RecursiveTTExpectimax(resultingState, depth - 1, timeLimit, timer, weights).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; // add result to transposition table TableRow row = new TableRow((short)depth, ((PlayerMove)bestMove).Direction, bestMove.Score); transposition_table.AddOrUpdate(zob_hash, row, (key, oldValue) => row); 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) * RecursiveTTExpectimax(resultingState, depth - 1, timeLimit, timer, weights).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(); }
// Standard Minimax search with no pruning Move MinimaxAlgorithm(State state, int depth) { Move bestMove; if (depth == 0 || state.IsGameOver()) { if (state.Player == GameEngine.PLAYER) { bestMove = new PlayerMove(); bestMove.Score = AI.Evaluate(state); return bestMove; } else if (state.Player == GameEngine.COMPUTER) { bestMove = new ComputerMove(); bestMove.Score = AI.Evaluate(state); return bestMove; } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) { 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 = MinimaxAlgorithm(resultingState, depth - 1).Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } } bestMove.Score = highestScore; return bestMove; } else if (state.Player == GameEngine.COMPUTER) { 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 = MinimaxAlgorithm(resultingState, depth - 1).Score; if (currentScore < lowestScore) { lowestScore = currentScore; bestMove = move; } } bestMove.Score = lowestScore; return bestMove; } else throw new Exception(); }
// Runs a parallel expectimax search to speed up search // A search is started in a separate thread for each child of the given root node // This method should only be called for the the root, where depth will always // be > 0 and player will always be GameEngine.PLAYER - the recursion is started // for the children of the root using standard Expectimax algorithm private Move ParallelExpectimax(State state, int depth, WeightVector weights) { Move bestMove = new PlayerMove(); List<Move> moves = state.GetMoves(); ConcurrentBag<Tuple<double, Move>> scores = new ConcurrentBag<Tuple<double, Move>>(); if (moves.Count == 0) { // game over return bestMove; } // create the resulting states before starting the threads List<State> resultingStates = new List<State>(); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); resultingStates.Add(resultingState); } // start a thread for each child Parallel.ForEach(resultingStates, resultingState => { double score = ExpectimaxAlgorithm(resultingState, depth - 1, weights).Score; scores.Add(new Tuple<double, Move>(score, resultingState.GeneratingMove)); }); // find the best score double highestScore = Double.MinValue; foreach (Tuple<double, Move> score in scores) { PlayerMove move = (PlayerMove)score.Item2; if (score.Item1 > highestScore) { highestScore = score.Item1; bestMove = score.Item2; } } return bestMove; }
// Recursive part of iterative deepening Expectimax private Tuple<Move, Boolean> IterativeDeepeningExpectimax(State state, int depth, double timeLimit, Stopwatch timer, WeightVector weights) { 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.EvaluateWithWeights(state, weights); 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.EvaluateWithWeights(state, weights); 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, weights).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, weights).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(); }
// Expectimax with Star2 pruning // NB: DO NOT USE - way too slow private Move Star2Expectimax(State state, double alpha, double beta, int depth, WeightVector weights) { 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 bestMove; } else if (state.Player == GameEngine.COMPUTER) { bestMove = new ComputerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.Evaluate(state); return bestMove; } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) { 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 = Star2Expectimax(resultingState, alpha, beta, depth - 1, weights).Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } alpha = Math.Max(alpha, highestScore); if (beta <= alpha) { // beta cut-off break; } } bestMove.Score = highestScore; return bestMove; } else if (state.Player == GameEngine.COMPUTER) // computer's turn (the random event node) { bestMove = new ComputerMove(); List<Cell> availableCells = state.GetAvailableCells(); List<Move> moves = state.GetAllComputerMoves(availableCells); int numSuccessors = moves.Count; double upperBound = AI.GetUpperBound(weights); double lowerBound = AI.GetLowerBound(weights); double curAlpha = numSuccessors * (alpha - upperBound); double curBeta = numSuccessors * (beta - lowerBound); double sucAlpha = Math.Max(curAlpha, lowerBound); double[] probeValues = new double[numSuccessors]; // probing phase double vsum = 0; int i = 1; foreach (Move move in moves) { curBeta += lowerBound; double sucBeta = Math.Min(curBeta, upperBound); State resultingState = state.ApplyMove(move); probeValues[i - 1] = Probe(resultingState, sucAlpha, sucBeta, depth - 1, weights); vsum += probeValues[i - 1]; if (probeValues[i - 1] >= curBeta) { vsum += lowerBound * (numSuccessors - i); bestMove.Score = vsum / numSuccessors; return bestMove; // pruning } curBeta -= probeValues[i - 1]; i++; } // search phase vsum = 0; i = 1; foreach (Move move in moves) { curAlpha += upperBound; curBeta += probeValues[i - 1]; sucAlpha = Math.Max(curAlpha, lowerBound); double sucBeta = Math.Min(curBeta, upperBound); State resultingState = state.ApplyMove(move); double score = StateProbability(((ComputerMove)move).Tile) * Star2Expectimax(resultingState, sucAlpha, sucBeta, depth - 1, weights).Score; vsum += score; if (score <= curAlpha) { vsum += upperBound * (numSuccessors - i); bestMove.Score = vsum / numSuccessors; return bestMove; // pruning } if (score >= curBeta) { vsum += lowerBound * (numSuccessors - i); bestMove.Score = vsum / numSuccessors; return bestMove; // pruning } curAlpha -= score; curBeta -= score; i++; } bestMove.Score = vsum / numSuccessors; return bestMove; } else { throw new Exception(); } }
// Expectimax search with Star1 pruning and forward pruning private Move Star1WithUnlikelyPruning(State state, double alpha, double beta, int depth, int lastSpawn, WeightVector weights) { 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.EvaluateWithWeights(state, weights); return bestMove; } else if (state.Player == GameEngine.COMPUTER) { bestMove = new ComputerMove(); // dummy action, as there will be no valid move bestMove.Score = AI.EvaluateWithWeights(state, weights); return bestMove; } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) { 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 = Star1WithUnlikelyPruning(resultingState, alpha, beta, depth - 1, lastSpawn, weights).Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } alpha = Math.Max(alpha, highestScore); if (beta <= alpha) { // beta cut-off break; } } bestMove.Score = highestScore; return bestMove; } else if (state.Player == GameEngine.COMPUTER) // computer's turn (the random event node) { bestMove = new ComputerMove(); List<Cell> availableCells = state.GetAvailableCells(); List<Move> moves = state.GetAllComputerMoves(availableCells); int numSuccessors = moves.Count; double upperBound = AI.GetUpperBound(weights); double lowerBound = AI.GetLowerBound(weights); double curAlpha = numSuccessors * (alpha - upperBound) + upperBound; double curBeta = numSuccessors * (beta - lowerBound) + lowerBound; double scoreSum = 0; int i = 1; foreach (Move move in moves) { int value = ((ComputerMove)move).Tile; if (value == 4 && lastSpawn == 4) continue; // unlikely event pruning (2 4-spawns in sequence only has 1% chance) double sucAlpha = Math.Max(curAlpha, lowerBound); double sucBeta = Math.Min(curBeta, upperBound); State resultingState = state.ApplyMove(move); double score = StateProbability(((ComputerMove)move).Tile) * Star1WithUnlikelyPruning(resultingState, sucAlpha, sucBeta, depth - 1, value, weights).Score; scoreSum += score; if (score <= curAlpha) { scoreSum += upperBound * (numSuccessors - i); bestMove.Score = scoreSum / numSuccessors; return bestMove; // pruning } if (score >= curBeta) { scoreSum += lowerBound * (numSuccessors - i); bestMove.Score = scoreSum / numSuccessors; return bestMove; // pruning } curAlpha += upperBound - score; curBeta += lowerBound - score; i++; } bestMove.Score = scoreSum / numSuccessors; return bestMove; } else throw new Exception(); }
// Recursive part of ^^ iterative deepening Expectimax with Star1, move ordering and transposition table private Tuple<Move, Boolean> RecursiveTTStar1(State state, double alpha, double beta, int depth, double timeLimit, Stopwatch timer, WeightVector weights) { 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.EvaluateWithWeights(state, weights); 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.EvaluateWithWeights(state, weights); return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) // AI's turn { DIRECTION bestDirection = (DIRECTION)(-1); bestMove = new PlayerMove(); double highestScore = Double.MinValue, currentScore = Double.MinValue; // transposition table look-up long zob_hash = GetHash(state); if (transposition_table.ContainsKey(zob_hash) && transposition_table[zob_hash].depth > depth) { Move move = new PlayerMove(transposition_table[zob_hash].direction); move.Score = transposition_table[zob_hash].value; return new Tuple<Move, Boolean>(move, true); } // move ordering - make sure we first check the move we believe to be best based on earlier searches else if (transposition_table.ContainsKey(zob_hash)) { bestDirection = transposition_table[zob_hash].direction; State resultingState = state.ApplyMove(new PlayerMove(bestDirection)); currentScore = RecursiveTTStar1(resultingState, alpha, beta, depth - 1, timeLimit, timer, weights).Item1.Score; if (currentScore > highestScore) { highestScore = currentScore; bestMove = new PlayerMove(bestDirection); } if (timer.ElapsedMilliseconds > timeLimit) { bestMove.Score = highestScore; return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false } } // now check the rest of moves List<Move> moves = state.GetMoves(); foreach (Move move in moves) { if (((PlayerMove)move).Direction != bestDirection) { State resultingState = state.ApplyMove(move); currentScore = RecursiveTTStar1(resultingState, alpha, beta, depth - 1, timeLimit, timer, weights).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; // add result to transposition table TableRow row = new TableRow((short)depth, ((PlayerMove)bestMove).Direction, bestMove.Score); transposition_table.AddOrUpdate(zob_hash, row, (key, oldValue) => row); return new Tuple<Move, Boolean>(bestMove, true); } else if (state.Player == GameEngine.COMPUTER) // computer's turn (the random event node) { bestMove = new ComputerMove(); int moveCheckedSoFar = 0; List<Cell> availableCells = state.GetAvailableCells(); List<Move> moves = state.GetAllComputerMoves(availableCells); int numSuccessors = moves.Count; double upperBound = AI.GetUpperBound(weights); double lowerBound = AI.GetLowerBound(weights); double curAlpha = numSuccessors * (alpha - upperBound) + upperBound; double curBeta = numSuccessors * (beta - lowerBound) + lowerBound; double scoreSum = 0; int i = 1; foreach (Move move in moves) { double sucAlpha = Math.Max(curAlpha, lowerBound); double sucBeta = Math.Min(curBeta, upperBound); State resultingState = state.ApplyMove(move); double score = StateProbability(((ComputerMove)move).Tile) * RecursiveTTStar1(resultingState, sucAlpha, sucBeta, depth - 1, timeLimit, timer, weights).Item1.Score; scoreSum += score; moveCheckedSoFar++; if (score <= curAlpha) { scoreSum += upperBound * (numSuccessors - i); bestMove.Score = scoreSum / numSuccessors; return new Tuple<Move,bool>(bestMove, true); // pruning } if (score >= curBeta) { scoreSum += lowerBound * (numSuccessors - i); bestMove.Score = scoreSum / numSuccessors; return new Tuple<Move, bool>(bestMove, true); // pruning } if (timer.ElapsedMilliseconds > timeLimit) { bestMove.Score = scoreSum / moveCheckedSoFar; return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false } curAlpha += upperBound - score; curBeta += lowerBound - score; i++; } bestMove.Score = scoreSum / numSuccessors; return new Tuple<Move, bool>(bestMove, true); } else throw new Exception(); }
// MAX part of Minimax (with alpha-beta pruning) Move Max(State state, int depth, double alpha, double beta) { Move bestMove = new PlayerMove(); double highestScore = Double.MinValue, currentScore = Double.MinValue; List<Move> moves = state.GetMoves(); if (depth == chosenDepth) { int numMax = moves.Count; } if (debug) logger.writeParent(state, chosenDepth - depth); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); currentScore = AlphaBeta(resultingState, depth - 1, alpha, beta).Score; if (debug) logger.writeChild(resultingState, chosenDepth - depth, currentScore); if (currentScore > highestScore) { highestScore = currentScore; bestMove = move; } alpha = Math.Max(alpha, highestScore); if (beta <= alpha) { // beta cut-off break; } } bestMove.Score = highestScore; return bestMove; }
// MIN part of Minimax (with alpha-beta pruning) Move Min(State state, int depth, double alpha, double beta) { Move bestMove = new ComputerMove(); double lowestScore = Double.MaxValue, currentScore = Double.MaxValue; List<Move> moves = state.GetMoves(); if (debug) logger.writeParent(state, chosenDepth - depth); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); currentScore = AlphaBeta(resultingState, depth - 1, alpha, beta).Score; if (debug) logger.writeChild(resultingState, chosenDepth - depth, currentScore); if (currentScore < lowestScore) { lowestScore = currentScore; bestMove = move; } beta = Math.Min(beta, lowestScore); if (beta <= alpha) break; } bestMove.Score = lowestScore; return bestMove; }
// Used by Star2 in probing phase private State PickSuccessor(State state) { List<Move> moves = state.GetMoves(); int numSuccessors = moves.Count; if (numSuccessors < 2) return state.ApplyMove(moves[0]); else { State choice = state.ApplyMove(moves[0]); double best = AI.Evaluate(choice); for (int i = 1; i < numSuccessors; i++) { State resultingState = state.ApplyMove(moves[i]); double score = AI.Evaluate(resultingState); if (score > best) { best = score; choice = resultingState; } } return choice; } }
// Runs a parallel version of iterative deepening // A search is started in a separate thread for each child of root node private Move ParallelIterativeDeepening(State state, double timeLimit) { Move bestMove = new PlayerMove(); List<Move> moves = state.GetMoves(); ConcurrentBag<Tuple<double, Move>> scores = new ConcurrentBag<Tuple<double, Move>>(); if (moves.Count == 0) { // game over return bestMove; } // create the resulting states before starting the threads List<State> resultingStates = new List<State>(); foreach (Move move in moves) { State resultingState = state.ApplyMove(move); resultingStates.Add(resultingState); } Parallel.ForEach(resultingStates, resultingState => { double score = IterativeDeepening(resultingState, timeLimit).Score; scores.Add(new Tuple<double, Move>(score, resultingState.GeneratingMove)); }); // find the best score double highestScore = Double.MinValue; foreach (Tuple<double, Move> score in scores) { PlayerMove move = (PlayerMove)score.Item2; if (score.Item1 > highestScore) { highestScore = score.Item1; bestMove = score.Item2; } } return bestMove; }
// Recursive part of iterative deepening Expectimax with star 1 pruning private Tuple<Move, Boolean> RecursiveIterativeDeepeningExpectimaxWithStar1(State state, double alpha, double beta, int depth, int timeLimit, Stopwatch timer, WeightVector weights) { 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.EvaluateWithWeights(state, weights); 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.EvaluateWithWeights(state, weights); return new Tuple<Move, Boolean>(bestMove, true); ; } else throw new Exception(); } if (state.Player == GameEngine.PLAYER) { 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 = RecursiveIterativeDeepeningExpectimaxWithStar1(resultingState, alpha, beta, depth - 1, timeLimit, timer, weights).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(); List<Cell> availableCells = state.GetAvailableCells(); List<Move> moves = state.GetAllComputerMoves(availableCells); int numSuccessors = moves.Count; double upperBound = AI.GetUpperBound(weights); double lowerBound = AI.GetLowerBound(weights); double curAlpha = numSuccessors * (alpha - upperBound) + upperBound; double curBeta = numSuccessors * (beta - lowerBound) + lowerBound; double scoreSum = 0; int i = 1; foreach (Move move in moves) { double sucAlpha = Math.Max(curAlpha, lowerBound); double sucBeta = Math.Min(curBeta, upperBound); State resultingState = state.ApplyMove(move); double score = StateProbability(((ComputerMove)move).Tile) * RecursiveIterativeDeepeningExpectimaxWithStar1(resultingState, sucAlpha, sucBeta, depth - 1, timeLimit, timer, weights).Item1.Score; scoreSum += score; if (score <= curAlpha) { scoreSum += upperBound * (numSuccessors - i); bestMove.Score = scoreSum / numSuccessors; return new Tuple<Move, Boolean>(bestMove, true); // pruning } if (score >= curBeta) { scoreSum += lowerBound * (numSuccessors - i); bestMove.Score = scoreSum / numSuccessors; return new Tuple<Move, Boolean>(bestMove, true); // pruning } if (timer.ElapsedMilliseconds > timeLimit) { bestMove.Score = scoreSum / i; return new Tuple<Move, Boolean>(bestMove, false); // recursion not completed, return false } curAlpha += upperBound - score; curBeta += lowerBound - score; i++; } bestMove.Score = scoreSum / numSuccessors; return new Tuple<Move, Boolean>(bestMove, true); } else throw new Exception(); }