// 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(); }
// 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; }
// 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(); }
// 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; }
// 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(); }
// runs minimax with alpha beta pruning Move AlphaBeta(State state, int depth, double alpha, double beta) { 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) return Max(state, depth, alpha, beta); else return Min(state, depth, alpha, beta); }
// Executes the user action by updating the board representation public int SendUserAction(PlayerMove action) { int value = -1; if (action.Direction == DIRECTION.DOWN) { value = DownPressed(); } if (action.Direction == DIRECTION.UP) { value = UpPressed(); } if (action.Direction == DIRECTION.LEFT) { value = LeftPressed(); } if (action.Direction == DIRECTION.RIGHT) { value = RightPressed(); } Reset(); return value; }
// Runs an entire game using parallelized MCTS limited by time public State RunRootParallelizationTimeLimitedMCTS(bool print, int timeLimit, int numThreads) { State rootState = null; while (true) { rootState = new State(BoardHelper.CloneBoard(this.gameEngine.board), this.gameEngine.scoreController.getScore(), GameEngine.PLAYER); if (print) { Program.PrintState(rootState); } DIRECTION result = RootParallelizationMCTSTimeLimited(rootState, timeLimit, numThreads); PlayerMove move = new PlayerMove(); move.Direction = result; if (result == (DIRECTION)(-1)) { // game over return rootState; } gameEngine.SendUserAction(move); } }
// Runs a game with the user playing private static void StartGame() { GameEngine game = new GameEngine(); bool gameOver = false; CleanConsole(); // main game loop while (!gameOver) { Console.SetCursorPosition(0, 0); Console.WriteLine("Score: " + game.scoreController.getScore() + " "); Console.WriteLine(BoardHelper.ToString(game.board)); DIRECTION direction = GetUserInput(); Move move = new PlayerMove(direction); game.SendUserAction((PlayerMove)move); if (new State(game.board, game.scoreController.getScore(), GameEngine.PLAYER).IsGameOver()) { gameOver = true; } } Console.WriteLine("Game over! Final score: " + game.scoreController.getScore()); Thread.Sleep(500); }
// 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(); }
// 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(); }
// 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(); }