private double HeuristicEval(BoardStates player, OthelloGame game) { //Based of features of the board that humans have identified. //Hints of evaluation from any source I could find //idealy these could me optimized using a genetic algorithm, //but that is a different project const int searchableDepthOverride = 2; //override min-max in favor of complete evaluation const int endGame = 20; //<20 moves is endgame const int midGame = 40; // 20 moves in is midgame double value = 0; int empty = game.GetPieceCount(BoardStates.empty); if (game.GameComplete) { return(CompleteEval(player, game)); } else if (empty < searchableDepthOverride) { return(MinimaxAlphaBeta(game, searchableDepthOverride, int.MinValue, int.MaxValue, player)); } value += coinDiffWeight * Math.Pow((game.GetPieceCount(player) - game.GetPieceCount(~player) + empty - coinDiffOffset), coinDiffPower); value += cornerDiffWeight * Math.Pow((game.GetCornerCount(player) - game.GetCornerCount(~player) + empty - cornerDiffOffset), cornerDiffPower); value += nearCornerDiffWeight * Math.Pow((game.GetAdjCornerCount(player) - game.GetAdjCornerCount(~player) + empty - nearCornerDiffOffset), nearCornerDiffPower); value += avalibleMoveDiffWeight * Math.Pow((game.GetPossiblePlayList(player).Count() - game.GetPossiblePlayList(~player).Count() + empty - avalibleMoveDiffOffset), avalibleMoveDiffPower); value += nonTurnableCoinDiffWeight * Math.Pow((game.GetSafePeiceCountEstimation(player) - game.GetSafePeiceCountEstimation(~player) + empty - nonTurnableCoinDiffOffset), nonTurnableCoinDiffPower); value += ControlledCornerDiffWeight * Math.Pow((game.GetControlledCorners(player) - game.GetControlledCorners(~player) + empty - ControlledCornerDiffOffset), ControlledCornerDiffPower); return(value); }
private byte[] PredictBestMove(int depth, OthelloGame game, BoardStates player) { byte[] bestMove = new byte[] { byte.MaxValue, byte.MaxValue }; List <byte[]> moves = game.GetPossiblePlayList(); double bestScore = int.MinValue + 1; if (game.GetPieceCount(BoardStates.empty) > 58)//first two moves, don't compute { return(OpeningMove(player, game)); } else if (moves.Count == 1) //don't compute if there is only 1 move { return(moves[0]); } foreach (byte[] move in moves) { OthelloGame testGame = game.DeepCopy(); testGame.MakeMove(move); double thisScore = MinimaxAlphaBeta(testGame, depth - 1, double.MinValue, double.MaxValue, player); if (thisScore > bestScore) { bestScore = thisScore; bestMove = move; } } return(bestMove); }
private static byte[] OpeningMove(BoardStates player, OthelloGame game) {//avoid computation for first move - only one symmetric option //randomly select perpendicular or diagonal for second move - parallel //has been shown to be much worse //SPECIFIC TO 8x8 BOARDS byte[][] firstMoves = new byte[4][] { new byte[] { 2, 3 }, new byte[] { 3, 2 }, new byte[] { 4, 5 }, new byte[] { 5, 4 } }; if (game.GetPieceCount(BoardStates.empty) == 60) { Random rndGen = new Random(); int rand = (int)Math.Ceiling(rndGen.NextDouble() * 4); switch (rand) { case 1: return(firstMoves[0]); case 2: return(firstMoves[1]); case 3: return(firstMoves[2]); case 4: return(firstMoves[3]); default: throw new Exception("OpeningMove has faulted with random number generation"); } } if (game.GetPieceCount(BoardStates.empty) == 59) { List <byte[]> moves = game.GetPossiblePlayList(); Random rndGen = new Random(); byte rand = (byte)Math.Ceiling(rndGen.NextDouble() * 2); switch (rand) { case 1: //diagonal return(moves[0]); case 2: //perpendicular return(moves[0]); default: throw new Exception("Opening move has faulted with random number generation"); } } return(new byte[] { byte.MaxValue, byte.MaxValue }); }
private double MinimaxAlphaBeta(OthelloGame board, int depth, double a, double b, BoardStates player)// bool isMaxPlayer) { // The heart of our AI. Minimax algorithm with alpha-beta pruning to speed up computation. // Higher search depths = greater difficulty. //from oliverzh200/reversi https://github.com/oliverzh2000/reversi if (depth == 0 || board.GameComplete) { return(HeuristicEval(player, board)); } double bestScore = double.MinValue; List <byte[]> validMoves = board.GetPossiblePlayList(); if (validMoves.Count > 0) { foreach (byte[] move in validMoves) { OthelloGame childBoard = board.DeepCopy(); childBoard.MakeMove(move); double nodeScore = MinimaxAlphaBeta(childBoard, depth - 1, a, b, player); bestScore = Math.Max(bestScore, nodeScore); a = Math.Max(bestScore, a); if (b <= a) //Prune { break; } } } else { return(MinimaxAlphaBeta(board, depth, a, b, player)); } return(bestScore); }
private byte[] PredictBestMove(int depth, OthelloGame game, BoardStates player) { byte[] bestMove = new byte[] { byte.MaxValue, byte.MaxValue }; List <byte[]> moves = game.GetPossiblePlayList(); double bestScore = int.MinValue + 1; foreach (byte[] move in moves) { OthelloGame testGame = game.DeepCopy(); testGame.MakeMove(move); double thisScore = MinimaxAlphaBeta(testGame, depth - 1, double.MinValue, double.MaxValue, player); if (thisScore > bestScore) { bestScore = thisScore; bestMove = move; } } if ((bestMove[0] == byte.MaxValue || bestMove[1] == byte.MaxValue) && moves.Count > 0) {//All moves are valued at -inf, return one of em return(moves[0]); } return(bestMove); }