public TurnResult PlayTurn(Turn turn) { TurnResult turnResult = GameState.PlayTurn(turn); PlayedTurns.Add(Tuple.Create(turn, turnResult)); Board.LastTurn = turn; return(turnResult); }
public void Progress() { TurnPlay?.Invoke(this, new GameEventArgs(Game, null)); int inTurnPlayerIndex = Game.GameState.InTurnPlayerIndex; Turn turn = GameClients[inTurnPlayerIndex].GetTurn(Game); TurnResult turnResult = Game.PlayTurn(turn); TurnPlayed?.Invoke(this, new TurnEventArgs(Game, turn, turnResult)); }
/// <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(); } }
public void UndoTurn(Turn turn, TurnResult turnResult, bool doChecks = true) { MiniMax.UndoTurnCount++; AlphaBetaSearch.UndoTurnCount++; InTurnPlayerIndex = 1 - InTurnPlayerIndex; WinningPlayerIndex = null; Vector movedPosition = turn.OriginalPosition.Add(turn.Move); Piece piece = Board[movedPosition.X, movedPosition.Y]; if (turnResult.CapturedPieceType != null) { Piece capturedPiece = CapturedPieces.Pop(); if (doChecks) { if (!capturedPiece.IsCaptured) { throw new Exception("Huh"); } if (capturedPiece.PlayerIndex != 1 - InTurnPlayerIndex) { throw new Exception("huh"); } if (capturedPiece.PieceType != turnResult.CapturedPieceType) { throw new ArgumentException("Bad TurnResult.CapturedPieceType"); } if (!capturedPiece.Position.Equals(movedPosition)) { throw new ArgumentException("Bad CapturedPiece Position mismatch"); } } capturedPiece.IsCaptured = false; Board[movedPosition.X, movedPosition.Y] = capturedPiece; } else { Board[movedPosition.X, movedPosition.Y] = null; } piece.Position = turn.OriginalPosition; Board[turn.OriginalPosition.X, turn.OriginalPosition.Y] = piece; Card tmp = GameCards[MiddleCardIndex]; GameCards[MiddleCardIndex] = GameCards[PlayerCardIndices[InTurnPlayerIndex][turn.CardIndex]]; GameCards[PlayerCardIndices[InTurnPlayerIndex][turn.CardIndex]] = tmp; }
/// <summary> /// Returns all neighbouring states and whether or not the move to the neighbouring state is winning or not. /// Changes Game.GameState during loop. /// </summary> private IEnumerable <Tuple <Turn, bool> > GetNeighbours() { GetNeighboursCount++; foreach (Turn turn in Game.GetValidTurns()) { TurnResult turnResult = null; try { turnResult = Game.GameState.PlayTurn(turn, DoChecks); if (turnResult.GameIsFinished) { yield return(Tuple.Create(turn, true)); } else { yield return(Tuple.Create(turn, false)); } } finally { // roll back Game.GameState.UndoTurn(turn, turnResult, DoChecks); } } }
/// <summary> /// Returns all neighbouring states and whether or not the move to the neighbouring state is winning or not and the neighbouring gameStateId. /// Changes Game.GameState during loop. /// Return neighbours in order of score of evaluation function if DoPrune is true. /// </summary> private IEnumerable <Tuple <Turn, bool, long?> > GetNeighbours(bool isMaxNode, int movesDone) { GetNeighboursCount++; if (movesDone == MaxMoves - 1 || !DoPrune) { // we don't want to calculate the evaluation score of all leave nodes (to sort on them) // because that's slower than traversing them unsorted and then (due to ABS pruning) NOT calculating the evaluation score of some of the leaves at all. // so, getting neighbours for the almost-leave-nodes is different than for the other nodes. foreach (Turn turn in Game.GetValidTurns()) { TurnResult turnResult = null; try { turnResult = Game.GameState.PlayTurn(turn, DoChecks); if (turnResult.GameIsFinished) { yield return(Tuple.Create(turn, true, (long?)null)); } else { yield return(Tuple.Create(turn, false, (long?)null)); } } finally { // roll back Game.GameState.UndoTurn(turn, turnResult, DoChecks); } } } else { List <Tuple <Turn, double, long?> > turnsWithScore = new List <Tuple <Turn, double, long?> >(); foreach (Turn turn in Game.GetValidTurns()) { TurnResult turnResult = null; try { turnResult = Game.GameState.PlayTurn(turn, DoChecks); long? gameStateId = GetUniqueIdentifier(Game.GameState); // don't calculate gameStateId again in the ABS min/max loop, so pass it on. double score = GetEvaluationScore(gameStateId.Value); turnsWithScore.Add(Tuple.Create(turn, score, gameStateId)); } finally { // roll back Game.GameState.UndoTurn(turn, turnResult, DoChecks); } } if (isMaxNode) { turnsWithScore.Sort((tup1, tup2) => - 1 * tup1.Item2.CompareTo(tup2.Item2)); // descending by score for max nodes. } else { turnsWithScore.Sort((tup1, tup2) => 1 * tup1.Item2.CompareTo(tup2.Item2)); // ascending by score for min nodes. } foreach (var tup in turnsWithScore) { Turn turn = tup.Item1; TurnResult turnResult = null; long? gameStateId = tup.Item3; try { turnResult = Game.GameState.PlayTurn(turn, DoChecks); if (turnResult.GameIsFinished) { yield return(Tuple.Create(turn, true, gameStateId)); } else { yield return(Tuple.Create(turn, false, gameStateId)); } } finally { // roll back Game.GameState.UndoTurn(turn, turnResult, DoChecks); } } } }
public TurnEventArgs(Game game, Turn turn, TurnResult turnResult) { Game = game; Turn = turn; TurnResult = turnResult; }