// a helper method to deep clone a CheckersBoard public CheckersBoard Clone() { var clone = new CheckersBoard { TileBoard = TileBoard, Pieces = new SetOfPieces(Pieces) }; return clone; }
public AlphaBetaReturnValue MinValue(CheckersBoard board, int alphaValue, int betaValue, PieceColor color, ref int currentDepth, ref int maxDepth, int maxDepthToSearchFor) { NodesGenerated++; var v = new AlphaBetaReturnValue(PositiveInfinity, null, currentDepth + 1); var moves = board.GetAllAvailableMoves(color).ToList().OrderByDescending(mr => mr.JumpResults.Count()).ToList(); var coTest = CutoffTest(board, currentDepth, maxDepthToSearchFor); if (coTest.HasValue && coTest.Value || !moves.Any()) { v.Value = Evaluate(board, color); v.Depth = currentDepth; return v; } if (!coTest.HasValue) { return null; } for (var i = 0; i < moves.Count; i++) { var m = moves[i]; board.MovePiece(m, color); if (currentDepth == 0 && moves.Count == 1) { v.Move = m; v.Value = Evaluate(board, color); board.RevertMove(m, color); return v; } var newDepth = currentDepth; newDepth++; var retVal = MaxValue(board, alphaValue, betaValue, color == PieceColor.Black ? PieceColor.Red : PieceColor.Black, ref newDepth, ref maxDepth, maxDepthToSearchFor); if (retVal == null) return null; retVal.Move = m; board.RevertMove(m, color); if (retVal.Depth > maxDepth) maxDepth = retVal.Depth; if(retVal.Value < v.Value) { v.Value = retVal.Value; v.Move = retVal.Move; } if (v.Value <= alphaValue) { NumberOfMinPrunes++; return retVal; } betaValue = Math.Min(betaValue, v.Value); } return v; }
// the evaluation function sums up the values of the pieces for each color and the available moves // the available moves is weighted more, and the value of the piece is determined by how close the piece is to the center of the board [favors pieces that are in the center] public int Evaluate(CheckersBoard state, PieceColor color) { int pOne = 0, pTwo = 0; for (var i = 0; i < state.Pieces.AlivePlayerOnePieces.Count; i++) { var piece = state.Pieces.AlivePlayerOnePieces[i]; pOne += 3*(state.TileBoard.Height >> 1 - Math.Abs(state.TileBoard.Height >> 1 - piece.Y)) + 3*(state.TileBoard.Width >> 1 - Math.Abs(state.TileBoard.Width >> 1 - piece.X)); } for (var i = 0; i < state.Pieces.AlivePlayerTwoPieces.Count; i++) { var piece = state.Pieces.AlivePlayerTwoPieces[i]; pTwo += 3*(state.TileBoard.Height >> 1 - Math.Abs(state.TileBoard.Height >> 1 - piece.Y)) + 3*(state.TileBoard.Width >> 1 - Math.Abs(state.TileBoard.Width >> 1 - piece.X)); } pOne += state.GetAllAvailableMoves(PieceColor.Black).Count()*10; pTwo += state.GetAllAvailableMoves(PieceColor.Red).Count()*10; return (Color == PieceColor.Black ? pOne - pTwo : pTwo - pOne); }
public bool? CutoffTest(CheckersBoard state, int depth, int maxDepthToSearchFor) { if ((int)(DateTime.Now - AlphaBetaStartTime).TotalSeconds >= 60) return null; if (depth == maxDepthToSearchFor) { _depthCutoffHit = true; return true; } var result = state.GetGameResultState(Color); if (Color == PieceColor.Black) return result == GameResult.BlackWins; if (Color == PieceColor.Red) return result == GameResult.RedWins; return false; }
//a straightforward implementation of the alpha-beta search, using iterative deepening search public MoveResult AlphaBeta(CheckersBoard game, ref int maxDepth) { var iterativeDepth = _lastSuccessfulDepth; const int alpha = NegativeInfinity; const int beta = PositiveInfinity; int totalNodesGenerated = 0, totalMaxPrunes = 0, totalMinPrunes = 0; //limits itself to the difficulty level's max depth AlphaBetaReturnValue lastCompletedMove = null; while (iterativeDepth <= (int)_difficultyLevel) { ResetCounts(); maxDepth = 0; var currentDepth = 0; var copy = game.Clone(); var v = MaxValue(copy, alpha, beta, Color, ref currentDepth, ref maxDepth, iterativeDepth); if (v != null) { lastCompletedMove = v; Console.WriteLine( "\tSearch at depth {0} completed. Optimal move found with value {1}; time to find move: {2}.\n" + "\tNodes generated: {3}, max prunes: {4}, min prunes: {5}.\n", maxDepth, lastCompletedMove.Value, DateTime.Now - AlphaBetaStartTime, NodesGenerated, NumberOfMaxPrunes, NumberOfMinPrunes); //keeps a running total of the total stats for the IDS totalNodesGenerated += NodesGenerated; totalMaxPrunes += NumberOfMaxPrunes; totalMinPrunes += NumberOfMinPrunes; } else break; if (maxDepth == 0 || !_depthCutoffHit || iterativeDepth == (int)_difficultyLevel) break; if(iterativeDepth != (int)_difficultyLevel) iterativeDepth++; } _lastSuccessfulDepth = iterativeDepth; Console.WriteLine( "IDS ended. Optimal move found with value {0}; IDS stopped at depth {1}; time to find move: {2}.\n" + "Total completed IDS stats: \n\tNodes generated: {3}. \n\tMax prunes: {4}. \n\tMin prunes: {5}.\n", lastCompletedMove.Value, iterativeDepth, DateTime.Now - AlphaBetaStartTime, totalNodesGenerated, totalMaxPrunes, totalMinPrunes); return lastCompletedMove.Move; }
// begins a new game of Checkers given the players and a displayer public void Start(int numPieces, LogicDriver pOne, LogicDriver pTwo, DisplayDriver displayer, bool playerWantsToGoFirst) { Board = new CheckersBoard { TileBoard = new Board(6, 6), Pieces = new SetOfPieces() }; var pOneColor = -1;// r.Next(0, 2); // 0 = black, 1 = red if (playerWantsToGoFirst) { if (pOne.IsPlayer) { pOneColor = 0; } else if (pTwo.IsPlayer) { pOneColor = 1; } } else { pOneColor = 1; } if (!pOne.IsPlayer && !pTwo.IsPlayer) pOneColor = 0; _playerOne = pOne; _playerOne.Color = pOneColor == 0 ? PieceColor.Black : PieceColor.Red; //set up the board and assign pieces on black tiles for (var j = 0; j < Board.TileBoard.Height; j++) { for (var i = 0; i < Board.TileBoard.Width; i++) { if (Board.TileBoard.GetTile(i, j).Color == TileColor.Black) Board.Pieces.AddPiece(PieceColor.Black, i, j, PieceDirection.Down); if (Board.Pieces.AlivePlayerOnePieces.Count == numPieces) break; } if (Board.Pieces.AlivePlayerOnePieces.Count == numPieces) break; } _playerTwo = pTwo; _playerTwo.Color = _playerOne.Color == PieceColor.Red ? PieceColor.Black : PieceColor.Red; //set up the board and assign pieces on black tiles...again for (var j = Board.TileBoard.Height - 1; j >= 0; j--) { for (var i = 0; i < Board.TileBoard.Width; i++) { if (Board.TileBoard.GetTile(i, j).Color == TileColor.Black) Board.Pieces.AddPiece(PieceColor.Red, i, j, PieceDirection.Up); if (Board.Pieces.AlivePlayerTwoPieces.Count == numPieces) break; } if (Board.Pieces.AlivePlayerTwoPieces.Count == numPieces) break; } _displayer = displayer; }