/// <summary> /// This method runs the ABS algorithm parallel, in the following way: /// Only parallelize the calculation of the score of the depth=1 nodes, then take the max of those. /// To do the calculation of the depth=1 node correctly, we need to pass on the Invert=true property and set MaxMoves one lower. /// Also, we need to sort on evaluation score as usual. /// Finally, in order for the pruning to work well, we have to pass on the updated Min (alpha) as much as possible. /// Here, we have a trade-off: on the one hand we want to pass on the updated Min value immediately to the next node, but then we can't parallelize anything. /// On the other hand, if we parallelize with 8 cores and start calculating 8 nodes immediately with Min = -inf, then those calculations might all be slow, and the parallelizing overhead results in time lost instead of time won. /// After some trial, parallelizing 4 seems to have the best results. /// </summary> /// <param name="originalGame"></param> /// <returns></returns> private Turn GetTurnParallel(Game originalGame) { try { GameClientStatsCollector?.StartGetTurn(originalGame); List <Tuple <Turn, Game, double> > games = new List <Tuple <Turn, Game, double> >(); foreach (Turn turn in originalGame.GetValidTurns()) { TurnResult turnResult = null; try { turnResult = originalGame.GameState.PlayTurn(turn); var game = originalGame.Clone(); double score = Evaluator.Evaluate(game, 1 - game.GameState.InTurnPlayerIndex); // @@@ if the turn is directly winning, we are still going to try to explore the subtree and that leads to problems. games.Add(Tuple.Create(turn, game, score)); } finally { // roll back originalGame.GameState.UndoTurn(turn, turnResult); } } ConcurrentBag <Tuple <Turn, double> > bag = new ConcurrentBag <Tuple <Turn, double> >(); object dummyLock = new object(); double min = AlphaBetaSearch.GameResultLosing; var orderedGames = games.OrderByDescending(t => t.Item3).ToList(); Parallel.ForEach(orderedGames, new ParallelOptions() { MaxDegreeOfParallelism = Math.Min(4, Environment.ProcessorCount) }, tup => { Turn turn = tup.Item1; var game = tup.Item2; AlphaBetaSearch abs = new AlphaBetaSearch(game, MaxMoves - 1, Evaluator, false, DoPrune, CollectStats, DoLog, invert: true, startingMin: min); var gameResults = abs.GetGameResult(); var gameResult = gameResults.Item1; lock (dummyLock) { min = Math.Max(min, gameResult); } bag.Add(Tuple.Create(turn, gameResult)); }); var best = bag.OrderByDescending(tup => tup.Item2).First(); GameResult = best.Item2; return(best.Item1); } finally { GameClientStatsCollector?.EndGetTurn(); } }
private Turn GetTurnNormal(Game originalGame) { try { GameClientStatsCollector?.StartGetTurn(originalGame); int maxMoves = GetMaxMoves(originalGame); // MaxMoves AlphaBetaSearch abs = new AlphaBetaSearch(originalGame, maxMoves, Evaluator, false, DoPrune, CollectStats, DoLog); var gameResults = abs.GetGameResult(); if (gameResults.Item1 == AlphaBetaSearch.GameResultWinning) { Turn winningTurn = originalGame.GetDirectlyWinningTurn(); if (winningTurn != null) { return(winningTurn); } } GameResult = gameResults.Item1; EvaluationScore = abs.EvaluationScore; NodeInfos = abs.NodeInfos; return(gameResults.Item2.OrderByDescending(kvp => kvp.Value).First().Key); } finally { GameClientStatsCollector?.EndGetTurn(); } }