// 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;
        }