// 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(); }
// 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(); }
// 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; }
// 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); }
// 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(); }
// 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(); }