/// <summary> /// Constructor for a Game Move /// </summary> /// <param name="movePiece">The moving piece</param> /// <param name="destination">The destination position of the moving piece</param> public GameMove(CheckersPiece movePiece, Vector2 destination) { this.movePiece = movePiece; capturedPieces = new List <CheckersPiece>(); destinationPosition = new Vector2(destination.X, destination.Y); originalPosition = new Vector2(movePiece.position.X, movePiece.position.Y); }
/// <summary> /// This function is for handling any checker piece actions. /// Function take a checkers piece as parameter, and set it as the active game piece. /// Function will also make the piece determine all its possible moves. /// </summary> /// <param name="activePiece">A piece to do an action on</param> public void doGamePieceAction(CheckersPiece activePiece) { if (activePiece.getCaptureStatus() == false && activePiece.getColor() == turnColor) { activeGamePiece = activePiece; activePiece.determineMoves(gameBoard, this); } }
/// <summary> /// Copy constructor for a game move. /// This constructor is used for creating temporary game states. /// </summary> /// <param name="move">The move to copy</param> public GameMove(GameMove move) { movePiece = move.movePiece; capturedPieces = new List <CheckersPiece>(); foreach (CheckersPiece piece in move.capturedPieces) { capturedPieces.Add(new CheckersPiece(piece)); } destinationPosition = new Vector2(move.destinationPosition.X, move.destinationPosition.Y); originalPosition = new Vector2(move.originalPosition.X, move.originalPosition.Y); }
/// <summary> /// Copy constructor for a checkers piece. /// This constructor is used mainly for creating new temporary states for running the alpha-beta /// </summary> /// <param name="piece">The piece to copy</param> public CheckersPiece(CheckersPiece piece) { position = new Vector2(piece.position.X, piece.position.Y); color = piece.color; isCaptured = piece.isCaptured; destination = new Vector2(piece.destination.X, piece.destination.Y); possibleMoves = new List <GameMove>(); foreach (GameMove move in piece.possibleMoves) { possibleMoves.Add(new GameMove(move)); } }
/// <summary> /// Constructor for a state. /// A new game state is initialized by passing in the color the player is playing as. /// </summary> /// <param name="playerClr">The color for the player</param> public CheckersGameState(PieceColor playerClr) { gameBoard = new CheckersBoard(); whiteGamePieces = new List <CheckersPiece>(); blackGamePieces = new List <CheckersPiece>(); turnColor = PieceColor.None; winner = PieceColor.None; jumpExists = false; //If a player color is passed in, the blow will initialize all the white and black pieces. if (playerClr != PieceColor.None) { Vector2 blackPosition; Vector2 whitePosition; for (int index = 0; index < MAX_PIECES; index++) { //calculate the position for the black piece and the white piece if (index < 3) { blackPosition = new Vector2(index * 2f, 5 - (index / 3)); whitePosition = new Vector2(index * 2f + 1, index / 3); } else { blackPosition = new Vector2((index * 2f + 1) - 6, 5 - (index / 3)); whitePosition = new Vector2((index * 2f) - 6, index / 3); } //Create the pieces with the calculated positions and add them the the list CheckersPiece blackPiece = new CheckersPiece(new Vector2(blackPosition.X, blackPosition.Y), PieceColor.Black); CheckersPiece whitePiece = new CheckersPiece(new Vector2(whitePosition.X, whitePosition.Y), PieceColor.White); blackGamePieces.Add(blackPiece); whiteGamePieces.Add(whitePiece); //Set the tiles to be occupied gameBoard.getTileAt(blackPosition).occupy(PieceColor.Black); gameBoard.getTileAt(whitePosition).occupy(PieceColor.White); } //Set the player and ai colors if (playerClr == PieceColor.Black) { playerColor = PieceColor.Black; aiColor = PieceColor.White; } else { playerColor = PieceColor.White; aiColor = PieceColor.Black; } } }
/// <summary> /// Copy constructor to make a new copy of this current state /// This constructor is used to create temporary states in the ai. /// </summary> /// <param name="gameState">The state to be copied</param> public CheckersGameState(CheckersGameState gameState) { whiteGamePieces = new List <CheckersPiece>(); blackGamePieces = new List <CheckersPiece>(); moveablePieces = new List <CheckersPiece>(); gameBoard = new CheckersBoard(gameState.gameBoard); foreach (CheckersPiece piece in gameState.blackGamePieces) { CheckersPiece newPiece = new CheckersPiece(piece); //find the active game piece if (gameState.activeGamePiece != null && newPiece.position == gameState.activeGamePiece.position) { activeGamePiece = newPiece; } blackGamePieces.Add(newPiece); } foreach (CheckersPiece piece in gameState.whiteGamePieces) { CheckersPiece newPiece = new CheckersPiece(piece); //find the active game piece if (gameState.activeGamePiece != null && newPiece.position == gameState.activeGamePiece.position) { activeGamePiece = newPiece; } whiteGamePieces.Add(newPiece); } foreach (CheckersPiece piece in gameState.moveablePieces) { CheckersPiece newPiece = new CheckersPiece(piece); moveablePieces.Add(newPiece); } playerColor = gameState.playerColor; aiColor = gameState.aiColor; turnColor = gameState.turnColor; jumpExists = gameState.jumpExists; winner = gameState.winner; numTurnsPassed = gameState.numTurnsPassed; }
/// <summary> /// Function that is called whenever it is the turn of the AI to do its move /// </summary> public void doAiMove() { if (didAiMove == false) { bool onlyOneMove = false; //Check if theres only 1 moveable piece if (currentState.moveablePieces.Count == 1) { //If there only 1 moveable piece and only 1 available action, set best move to that move if (currentState.moveablePieces.First().getPossibleMoves().Count == 1) { bestMove = currentState.moveablePieces.First().getPossibleMoves().First(); onlyOneMove = true; Console.WriteLine("Only one move was available - Alpha-beta wasn't required" + "\n--------------------------------------------------------"); } } //If there is more than one move run algorithm if (onlyOneMove == false) { int bestMoveValue = AlphaBetaSearch(new CheckersGameState(currentState)); //if the algorithm determines that the AI will win or lose regardless of which move, //then make the ai do the first possible move it can if (bestMoveValue == MIN_INT || bestMoveValue == MAX_INT) { bestMove = currentState.moveablePieces.First().getPossibleMoves()[0]; } //if algorithm took 50 or more seconds to run, it has reach time cutoff if ((endTime - startTime).TotalSeconds >= 55) { Console.Write("Tree reached time cutoff of 55 seconds" + "\n--Max Depth: " + maxDepth + "\n--Cut Off Depth: " + (cutoff + iterativeDepth)); //Reached time cutoff - reduce the cutoff by 1/4 float actualCutoff = 0.75f * (cutoff + iterativeDepth); iterativeDepth = (((int)actualCutoff - cutoff) > 0) ? (int)actualCutoff - cutoff : 0; } //Else if maxDepth is equal to the cutoff depth, that means the tree reached cutoff level else if (maxDepth == cutoff + iterativeDepth) { Console.Write("Tree reached cut off" + "\n--Cut Off Depth: " + (cutoff + iterativeDepth)); //Since alpha-beta was able to finish in reasonable time (< 50s) // and the cutoff was reached, increased by 1 + (1/4)number of turns passed iterativeDepth = 1 + (int)(currentState.numTurnsPassed * 0.25); } //else the tree completed before reaching cutoff else { Console.Write("Tree completed before reaching cut off" + "\n--Max Depth: " + maxDepth + "\n--Cut Off Depth: " + (cutoff + iterativeDepth)); } Console.WriteLine("\n--Nodes Generated: " + nodes + "\n--# times pruning occured in MAX-VALUE: " + maxPruned + "\n--# times pruning occured in MIN-VALUE: " + minPruned + "\nFound Move in: " + (endTime - startTime).TotalSeconds + "seconds\n--------------------------------------------------------"); //AI is completed sleep the thread Thread.Sleep(1000); } //Do the actual move CheckersPiece movePiece = bestMove.movePiece; currentState.doGamePieceAction(currentState.getPiece(movePiece.getColor(), movePiece.position)); //Set now to when the delay for the AI to actually do the move to now didAiMove = true; aiMoveStart = DateTime.Now; } else { //If the delay for an AI to do the move has been reached do the move if ((DateTime.Now - aiMoveStart).TotalSeconds > aiTimeDelay) { if (currentState.doTileAction(currentState.gameBoard.getTileAt(bestMove.destinationPosition)) == true) { currentState.activeGamePiece.Update(currentState); didAiMove = false; } } } }
/// <summary> /// Function that handles all drawings in the game /// This function is called everytime after an update /// </summary> /// <param name="spriteBatch">Spritebatch initialized by main</param> public void Draw(SpriteBatch spriteBatch) { spriteBatch.Begin(); //Draw the board currentState.gameBoard.Draw(spriteBatch, checkerGameBoardTexture); //Draw all white pieces foreach (CheckersPiece piece in currentState.whiteGamePieces) { if (piece.getCaptureStatus() == false) { piece.Draw(spriteBatch, whitePieceTexture); } } //Draw all black pieces foreach (CheckersPiece piece in currentState.blackGamePieces) { if (piece.getCaptureStatus() == false) { piece.Draw(spriteBatch, blackPieceTexture); } } //Draw the active game piece marking CheckersPiece active = currentState.activeGamePiece; if (active != null) { if (active.getColor() == PieceColor.White) { currentState.activeGamePiece.Draw(spriteBatch, activePieceTexture2, true); } else { currentState.activeGamePiece.Draw(spriteBatch, activePieceTexture, true); } } //Draw any marked tiles BoardTile[,] tiles = currentState.gameBoard.getTiles(); for (int x = 0; x < CheckersBoard.MAX_HORIZONTAL_TILES; x++) { for (int y = 0; y < CheckersBoard.MAX_VERTICAL_TILES; y++) { if (tiles[x, y].getStatus() == TileStatus.MOVE) { tiles[x, y].Draw(spriteBatch, moveTileTexture); } else if (tiles[x, y].getStatus() == TileStatus.JUMP) { tiles[x, y].Draw(spriteBatch, jumpTileTexture); } } } //Draw the text and buttons for the UI spriteBatch.DrawString(textFont, "Select a color to start a new game", new Vector2(10, 610), Color.Bisque); spriteBatch.Draw(blackButtonTexture, blackButton, Color.White); spriteBatch.Draw(whiteButtonTexture, whiteButton, Color.White); //Below displays the player's turn, or the winner if there exists if (currentState.winner == PieceColor.None && currentState.turnColor != PieceColor.None) { String message = currentState.turnColor + " turn"; spriteBatch.DrawString(font, message, new Vector2(400, 630), Color.Bisque); } else { if (currentState.winner == PieceColor.Black) { String message = "Black Wins!"; spriteBatch.DrawString(font, message, new Vector2(385, 630), Color.Red); } if (currentState.winner == PieceColor.White) { String message = "White Wins!"; spriteBatch.DrawString(font, message, new Vector2(385, 630), Color.Blue); } } spriteBatch.End(); }
/// <summary> /// Function that will recursively find all the jump sequences possible by this piece /// </summary> /// <param name="tilePosition">The position of the tile to check jumps from</param> /// <param name="gameBoard">The gameboard</param> /// <param name="gameState">The current gamestate</param> /// <returns>A list of possible jumps</returns> public List <GameMove> getJumps(Vector2 tilePosition, CheckersBoard gameBoard, CheckersGameState gameState) { List <GameMove> jumps = new List <GameMove>(); //Get all the possible tiles that could result with a jump BoardTile topLeftTile = gameBoard.getTileAt((int)tilePosition.X - 1, (int)tilePosition.Y - 1); BoardTile topRightTile = gameBoard.getTileAt((int)tilePosition.X + 1, (int)tilePosition.Y - 1); BoardTile bottomLeftTile = gameBoard.getTileAt((int)tilePosition.X - 1, (int)tilePosition.Y + 1); BoardTile bottomRightTile = gameBoard.getTileAt((int)tilePosition.X + 1, (int)tilePosition.Y + 1); BoardTile topLeftJumpTile = gameBoard.getTileAt((int)tilePosition.X - 2, (int)tilePosition.Y - 2); BoardTile topRightJumpTile = gameBoard.getTileAt((int)tilePosition.X + 2, (int)tilePosition.Y - 2); BoardTile bottomLeftJumpTile = gameBoard.getTileAt((int)tilePosition.X - 2, (int)tilePosition.Y + 2); BoardTile bottomRightJumpTile = gameBoard.getTileAt((int)tilePosition.X + 2, (int)tilePosition.Y + 2); //Set the enemy color PieceColor enemyColor; if (this.color == PieceColor.Black) { enemyColor = PieceColor.White; } else { enemyColor = PieceColor.Black; } //The below four if-elses will basically determine if there was a piece of the enemy color was jumped over //by moving to a tile 2 diagonal tiles away. If there was a jump, it will mark the piece that it just jumped over //and then recursively look for any other following jumps from that tile. if (topLeftTile != null && topLeftTile.getOccupiedStatus() == enemyColor) { //Get the piece that was jumped over CheckersPiece adjacentPiece = gameState.getPiece(enemyColor, topLeftTile.position); if (topLeftJumpTile != null && topLeftJumpTile.getOccupiedStatus() == PieceColor.None && adjacentPiece.justJumpedOver == false) { //mark the piece as jumped over adjacentPiece.justJumpedOver = true; //Find any jumps from the new tile position List <GameMove> sequenceJumps = getJumps(topLeftJumpTile.position, gameBoard, gameState); //If there was no following jump then add the jump to the list of jump if (sequenceJumps.Count == 0) { GameMove newJump = (new GameMove(this, topLeftJumpTile.position)); newJump.capturedPieces.Add(adjacentPiece); jumps.Add(newJump); } //Else for each following jump, add the piece the first jump captured to the list of captured pieces else { foreach (GameMove jump in sequenceJumps) { //GameMove newJump = (new GameMove(this, jump.destinationPosition)); //jump.capturedPieces = jump.capturedPieces; jump.capturedPieces.Add(adjacentPiece); jumps.Add(jump); } } } } if (topRightTile != null && topRightTile.getOccupiedStatus() == enemyColor) { CheckersPiece adjacentPiece = gameState.getPiece(enemyColor, topRightTile.position); if (topRightJumpTile != null && topRightJumpTile.getOccupiedStatus() == PieceColor.None && adjacentPiece.justJumpedOver == false) { //get any jumps from that tile adjacentPiece.justJumpedOver = true; List <GameMove> sequenceJumps = getJumps(topRightJumpTile.position, gameBoard, gameState); if (sequenceJumps.Count == 0) { GameMove newJump = (new GameMove(this, topRightJumpTile.position)); newJump.capturedPieces.Add(adjacentPiece); jumps.Add(newJump); } else { foreach (GameMove jump in sequenceJumps) { GameMove newJump = (new GameMove(this, jump.destinationPosition)); newJump.capturedPieces = jump.capturedPieces; newJump.capturedPieces.Add(adjacentPiece); jumps.Add(newJump); } } } } if (bottomLeftTile != null && bottomLeftTile.getOccupiedStatus() == enemyColor) { CheckersPiece adjacentPiece = gameState.getPiece(enemyColor, bottomLeftTile.position); if (bottomLeftJumpTile != null && bottomLeftJumpTile.getOccupiedStatus() == PieceColor.None && adjacentPiece.justJumpedOver == false) { //get any jumps from that tile adjacentPiece.justJumpedOver = true; List <GameMove> sequenceJumps = getJumps(bottomLeftJumpTile.position, gameBoard, gameState); if (sequenceJumps.Count == 0) { GameMove newJump = (new GameMove(this, bottomLeftJumpTile.position)); newJump.capturedPieces.Add(adjacentPiece); jumps.Add(newJump); } else { foreach (GameMove jump in sequenceJumps) { GameMove newJump = (new GameMove(this, jump.destinationPosition)); newJump.capturedPieces = jump.capturedPieces; newJump.capturedPieces.Add(adjacentPiece); jumps.Add(newJump); } } } } if (bottomRightTile != null && bottomRightTile.getOccupiedStatus() == enemyColor) { CheckersPiece adjacentPiece = gameState.getPiece(enemyColor, bottomRightTile.position); if (bottomRightJumpTile != null && bottomRightJumpTile.getOccupiedStatus() == PieceColor.None && adjacentPiece.justJumpedOver == false) { //get any jumps from that tile adjacentPiece.justJumpedOver = true; List <GameMove> sequenceJumps = getJumps(bottomRightJumpTile.position, gameBoard, gameState); if (sequenceJumps.Count == 0) { GameMove newJump = (new GameMove(this, bottomRightJumpTile.position)); newJump.capturedPieces.Add(adjacentPiece); jumps.Add(newJump); } else { foreach (GameMove jump in sequenceJumps) { GameMove newJump = (new GameMove(this, jump.destinationPosition)); newJump.capturedPieces = jump.capturedPieces; newJump.capturedPieces.Add(adjacentPiece); jumps.Add(newJump); } } } } return(jumps); }
/// <summary> /// Function to capture a checkerpiece given the piece, and its tile position /// </summary> /// <param name="piece">The piece to be captured</param> /// <param name="tile">The tile the piece is on</param> public void Capture(CheckersPiece piece, BoardTile tile) { tile.unOccupy(); piece.isCaptured = true; }