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); }
public static BoardStates GetCurrentLeader(OthelloGame game) { BoardStates leader = BoardStates.empty; leader = game.GetPieceCount(BoardStates.white) > game.GetPieceCount(BoardStates.black) ? BoardStates.white : BoardStates.black; return(leader); }
public static void TestMinMax(OthelloGame _myGame, int minimaxDepth = 3) { const int testCount = 100; object wonGamesLock = new object(); int wonGames = 0; object tieGamesLock = new object(); int tieGames = 0; var stopwatch = Stopwatch.StartNew(); //Parallel.For(0, testCount, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },index => //{ for (int index = 0; index < testCount; index++) {//non-parallel for loop to debug BoardStates player = (index % 2 == 0) ? BoardStates.black : BoardStates.white; OthelloGame testGame = new OthelloGame(); MinMaxAgent othelloAgent = new MinMaxAgent(2); RandomAgent randAgent = new RandomAgent(); while (!testGame.GameComplete) { if (testGame.WhosTurn == player) { testGame.MakeMove(othelloAgent.MakeMove(testGame, player)); } else { testGame.MakeMove(randAgent.MakeMove(testGame, ~player)); } } if (testGame.GameComplete)//just gotta check { if (testGame.FinalWinner == player) { lock (wonGamesLock) { wonGames++; } } else if (testGame.FinalWinner == BoardStates.empty) { lock (tieGamesLock) { tieGames++; } } Console.WriteLine("Finished Game " + index + ", " + testGame.FinalWinner.ToString() + " won " + testGame.GetPieceCount(testGame.FinalWinner) + " to " + testGame.GetPieceCount(OthelloGame.OpposingPlayer(testGame.FinalWinner)));; } else { throw new Exception("MiniMax Testing didn't complete a game"); } } //}); stopwatch.Stop(); Console.WriteLine("Won " + wonGames + " / " + testCount + " games, " + ((double)wonGames / testCount) * 100 + " %"); Console.WriteLine("Tied " + tieGames + " / " + testCount + " games, " + ((double)tieGames / testCount) * 100 + " %"); Console.WriteLine("Lost " + (testCount - wonGames - tieGames) + " / " + testCount + " games, " + ((double)(testCount - wonGames - tieGames) / testCount) * 100 + " %"); Console.WriteLine("Elapsed time for games : {0}", stopwatch.Elapsed); }
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 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); }