/// <summary> /// Returns a list of all pieces belonging to a current player that are still on the board /// </summary> public List <Piece> GetPlayersPieces(Program.Square currentPlayer) { var currentPlayersPieces = new List <Piece>(); // set playersKing to either RedKing or WhiteKing var playersKing = currentPlayer == Program.Square.Red ? Program.Square.RedKing : Program.Square.WhiteKing; // scan the board to identify squares where current player's pieces are for (var row = 0; row < Size; row++) { for (var col = 0; col < Size; col++) { // if current row,column coordinate within the board holds current player's piece if (_board[row, col] == currentPlayer) { currentPlayersPieces.Add(new Piece(currentPlayer, false, row, col)); } else if (_board[row, col] == playersKing) { currentPlayersPieces.Add(new Piece(playersKing, true, row, col)); } } } return(currentPlayersPieces); }
public Piece(Program.Square type, bool isKing, int x, int y) { Type = type; IsKing = isKing; X = x; Y = y; }
/// <summary> /// Changes the location of a piece using the move provided /// </summary> public void MovePiece(Move move) { var fromRow = move.FromRow; var fromCol = move.FromCol; var toRow = move.ToRow; var toCol = move.ToCol; Program.Square pieceType = _board[fromRow, fromCol]; // if move is more than 2 rows forwards/backwards, then it's illegal if (toRow - fromRow > 2 || toRow - fromRow < -2) { Console.WriteLine("You can't move further than 2 rows"); } else { // if the piece has reached either first or last row, it becomes a king if (toRow == 0 && pieceType == Program.Square.Red || toRow == 7 && pieceType == Program.Square.White) { SetKing(pieceType, toRow, toCol); _board[fromRow, fromCol] = Program.Square.EmptyDark; } else { // move piece and replace it with an empty dark square _board[toRow, toCol] = _board[fromRow, fromCol]; _board[fromRow, fromCol] = Program.Square.EmptyDark; } } }
/// <summary> /// Checks whether provided move is valid /// </summary> private bool IsMovePermitted(Program.Square currentPiece, Move move) { // can't move outside the board if (move.ToRow < 0 | move.ToRow > 7 | move.ToCol < 0 | move.ToCol > 7) { return(false); } // can't move onto a light square if (_board[move.ToRow, move.ToCol] == Program.Square.EmptyLight) { return(false); } if (currentPiece == Program.Square.Red) { // can't move backwards if (move.ToRow >= move.FromRow) { return(false); } // can only move to an empty dark square if (GetPieceAt(move.CoordinateTo) != Program.Square.EmptyDark) { return(false); } return(true); } if (currentPiece == Program.Square.White) { // can't move backwards if (move.ToRow <= move.FromRow) { return(false); } // can only move to an empty dark square if (GetPieceAt(move.CoordinateTo) != Program.Square.EmptyDark) { return(false); } return(true); } if (currentPiece == Program.Square.WhiteKing || currentPiece == Program.Square.RedKing) { // can only move to an empty dark square if (GetPieceAt(move.CoordinateTo) != Program.Square.EmptyDark) { return(false); } return(true); } return(false); }
/// <summary> /// Makes a regular red or white piece a king once it's reached the opposite end row of the board /// </summary> private void SetKing(Program.Square currentPlayer, int rowTo, int colTo) { if (currentPlayer == Program.Square.Red && rowTo == 0) { _board[rowTo, colTo] = Program.Square.RedKing; } if (currentPlayer == Program.Square.White && rowTo == 7) { _board[rowTo, colTo] = Program.Square.WhiteKing; } }
/// <summary> /// Returns current state of the board, ie. how pieces are positioned at this particular stage /// </summary> public Program.Square[,] GetState() { var newArr = new Program.Square[8, 8]; Array.Copy(_board, newArr, _board.Length); /* for (var row = 0; row < 8; row++) * for (var col = 0; col < 8; col++) * newArr[row, col] = _board[row, col];*/ return(newArr); }
/// <summary> /// Evaluates all possible moves that AI player can make during its current turn in order to /// find the highest score associated with one of these moves /// </summary> /// <param name="board"></param> /// <param name="state"></param> /// <param name="depth"></param> /// <param name="alpha"></param> /// <param name="beta"></param> /// <returns>Returns the highest scored out of all possible moves</returns> private double Negamax(Board board, State state, int depth, double alpha, double beta) { List <Piece> pieces = board.GetPlayersPieces(state.CurrentPlayer); HashSet <Move> legalJumps = board.GetLegalJumps(pieces); HashSet <Move> legalMoves = board.GetLegalMoves(pieces); legalMoves.UnionWith(legalJumps); // evaluate the state of the board in order to obtain the score if depth has reached zero if (depth == 0) { return(Evaluate(state)); } Program.Square currentPlayer = state.CurrentPlayer; Board boardCopy; State newState; foreach (var move in legalMoves) { boardCopy = board.DeepCopy(); if (move.IsJump) { boardCopy.DoJump(move); } else { boardCopy.MovePiece(move); } newState = new State(boardCopy.GetState(), Program.ChangeTurn(currentPlayer)); double newScore = -Negamax(boardCopy, newState, depth - 1, -beta, -alpha); // alpha-beta cut-off if (newScore >= beta) { return(newScore); } if (newScore > alpha) { alpha = newScore; } } return(alpha); }
/// <summary> /// Evaluates the state of the board provided by Negamax method by calculating the difference in number of /// both players' pieces, either from Red or from White player's perspective /// </summary> /// <param name="state"></param> /// <returns>Returns the score for a move which is the difference between the number of players' pieces /// left on the board once the move has been applied</returns> private static int Evaluate(State state) { int red = 0; int white = 0; Program.Square currentPlayer = state.CurrentPlayer; Program.Square[,] board = state.BoardState; for (var row = 0; row < 8; row++) { for (var col = 0; col < 8; col++) { if (board[row, col] == Program.Square.Red) { red++; } if (board[row, col] == Program.Square.White) { white++; } if (board[row, col] == Program.Square.RedKing) { red += 2; } if (board[row, col] == Program.Square.WhiteKing) { white += 2; } } } // return the difference between the number of red and white pieces if calculating for red player if (currentPlayer == Program.Square.Red || currentPlayer == Program.Square.RedKing) { return(red - white); } // return the difference between the number of white and red pieces if calculating for white player return(white - red); }
/// <summary> /// Retrieves the best move that AI player can perform that is associated with the highest score returned /// by Negamax method /// </summary> /// <param name="board"></param> /// <param name="state"></param> /// <returns>Returns the best move out of all legal moves for AI player</returns> public Move GetBestMove(Board board, State state) { List <Piece> pieces = board.GetPlayersPieces(state.CurrentPlayer); HashSet <Move> legalJumps = board.GetLegalJumps(pieces); HashSet <Move> legalMoves = board.GetLegalMoves(pieces); legalMoves.UnionWith(legalJumps); var bestMoves = new List <Move>(); // analyze all possible moves three turns ahead while searching for best move for this turn int depth = 3; double alpha = Double.MinValue; Program.Square currentPlayer = state.CurrentPlayer; Board boardCopy; State newState; var rand = new Random(); foreach (var move in legalMoves) { // make a deep copy of the board to perform a move without changing the original board boardCopy = board.DeepCopy(); if (move.IsJump) { boardCopy.DoJump(move); } else { boardCopy.MovePiece(move); } // create a new state of the game using copy of the original board after move has been performed newState = new State(boardCopy.GetState(), Program.ChangeTurn(currentPlayer)); // get score for the move that has been applied to copy of the board double newScore = -Negamax(boardCopy, newState, depth - 1, Double.MinValue, -alpha); //Console.WriteLine("From {0} To {1} = {2}", move.CoordinateFrom, move.CoordinateTo, newScore); // if new score is better than alpha, it becomes a new alpha (highest score found so far) if (newScore > alpha) { alpha = newScore; bestMoves.Clear(); bestMoves.Add(move); } // if score for this move is equal to score of best move found so far, add it to the list of best moves else if (newScore == alpha) { bestMoves.Add(move); } } // remove all regular moves from the list of best moves if any jumps were found if (bestMoves.Exists(other => other.IsJump)) { bestMoves.RemoveAll(notJump => !notJump.IsJump); } return(bestMoves[rand.Next(bestMoves.Count)]); }
public State(Program.Square[,] state, Program.Square currentPlayer) { BoardState = state; CurrentPlayer = currentPlayer; }
/// <summary> /// Checks whether provided jump is valid /// </summary> private bool CanJump(Piece piece, Move move) { Program.Square currentPlayer = piece.Type; Program.Square opponent; Program.Square opponentsKing; var toRow = move.ToRow; var toCol = move.ToCol; var colDirection = move.FromCol - move.ToCol; var rowDirection = move.FromRow - move.ToRow; // can't jump outside the board if (move.ToRow < 0 | move.ToRow > 7 | move.ToCol < 0 | move.ToCol > 7) { return(false); } // set opponent and opponentsKing variables to opposite colour to the currentPlayer's pieces if (currentPlayer == Program.Square.Red || currentPlayer == Program.Square.RedKing) { opponent = Program.Square.White; opponentsKing = Program.Square.WhiteKing; } else { opponent = Program.Square.Red; opponentsKing = Program.Square.RedKing; } if (currentPlayer == Program.Square.Red && !piece.HasJustJumped) { // checking jumps to the right if (colDirection == -2) { // checking if a jump to the right can be made to the row below if (GetPieceAt(new Point(toRow, toCol)) == Program.Square.EmptyDark && (GetPieceAt(new Point(toRow + 1, toCol - 1)) == opponent || GetPieceAt(new Point(toRow + 1, toCol - 1)) == opponentsKing)) { return(true); } return(false); } // checking jumps to the left if (colDirection == 2) { // checking if a jump to the left can be made to the row below if (GetPieceAt(new Point(toRow, toCol)) == Program.Square.EmptyDark && (GetPieceAt(new Point(toRow + 1, toCol + 1)) == opponent || GetPieceAt(new Point(toRow + 1, toCol + 1)) == opponentsKing)) { return(true); } return(false); } } if (currentPlayer == Program.Square.White && !piece.HasJustJumped) { // checking jumps to the right if (colDirection == -2) { // checking if a jump to the right can be made to the row above if (GetPieceAt(new Point(toRow, toCol)) == Program.Square.EmptyDark && (GetPieceAt(new Point(toRow - 1, toCol - 1)) == opponent || GetPieceAt(new Point(toRow - 1, toCol - 1)) == opponentsKing)) { return(true); } return(false); } // checking jumps to the left if (colDirection == 2) { // checking if a jump to the left can be made to the row above if (GetPieceAt(new Point(toRow, toCol)) == Program.Square.EmptyDark && (GetPieceAt(new Point(toRow - 1, toCol + 1)) == opponent || GetPieceAt(new Point(toRow - 1, toCol + 1)) == opponentsKing)) { return(true); } return(false); } } // king and regular piece that has just jumped can jump in all directions if (currentPlayer == Program.Square.RedKing || currentPlayer == Program.Square.WhiteKing || piece.HasJustJumped) { // checking jumps to the right if (colDirection == -2) { // checking jumps to the row above if (rowDirection == -2) { if (GetPieceAt(new Point(toRow, toCol)) == Program.Square.EmptyDark && (GetPieceAt(new Point(toRow - 1, toCol - 1)) == opponent || GetPieceAt(new Point(toRow - 1, toCol - 1)) == opponentsKing)) { return(true); } } // checking jumps to the row below if (rowDirection == 2) { if (GetPieceAt(new Point(toRow, toCol)) == Program.Square.EmptyDark && (GetPieceAt(new Point(toRow + 1, toCol - 1)) == opponent || GetPieceAt(new Point(toRow + 1, toCol - 1)) == opponentsKing)) { return(true); } } return(false); } // checking jumps to the left if (colDirection == 2) { // checking jumps to the row above if (rowDirection == -2) { if (GetPieceAt(new Point(toRow, toCol)) == Program.Square.EmptyDark && (GetPieceAt(new Point(toRow - 1, toCol + 1)) == opponent || GetPieceAt(new Point(toRow - 1, toCol + 1)) == opponentsKing)) { return(true); } } // checking jumps to the row below if (rowDirection == 2) { if (GetPieceAt(new Point(toRow, toCol)) == Program.Square.EmptyDark && (GetPieceAt(new Point(toRow + 1, toCol + 1)) == opponent || GetPieceAt(new Point(toRow + 1, toCol + 1)) == opponentsKing)) { return(true); } } return(false); } } return(false); }