/// <summary> /// Max value search function /// </summary> /// <param name="state">The current state of the game</param> /// <param name="alpha">highest alpha found</param> /// <param name="beta">lowest beta found</param> /// <param name="depth">depth current state is at</param> /// <returns>The best max value</returns> public int MaxValue(CheckersGameState state, int alpha, int beta, int depth) { //increase depth depth++; maxDepth = (depth > maxDepth) ? depth : maxDepth; //Evaluate the state int utilityValue = Evaluate(state); //If the state is terminal or if the cuttoff is reached, return the value if (utilityValue == MAX_INT || utilityValue == MIN_INT || depth == cutoff + iterativeDepth || (DateTime.Now - startTime).TotalSeconds > 55) { return(utilityValue); } //integer value used to determine alpha int value = MIN_INT; //A temporary gamestate to prevent anything in the actual game from changing CheckersGameState tempState; //The loop below finds every possible action from this state, //then using the temporary state, it will do the action, //and pass that state into the minValue function to find the largest possible value for alpha foreach (CheckersPiece piece in state.moveablePieces) { state.doGamePieceAction(piece); foreach (GameMove action in state.activeGamePiece.getPossibleMoves()) { tempState = new CheckersGameState(state); nodes++; if (tempState.doTileAction(tempState.gameBoard.getTileAt(action.destinationPosition)) == true) { tempState.activeGamePiece.Update(tempState); } value = Math.Max(value, MinValue(tempState, alpha, beta, depth)); //if alpha becomes greater than or equal to beta prune the tree if (value >= beta) { maxPruned++; return(value); } if (alpha < value) { alpha = value; //if we are in the actual state of the game, that means we found a new best move if (depth == 1) { bestMove = action; } } } } return(value); }
/// <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> /// 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 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); }