/// <summary> /// Function to evaluate the value of the state (Evaluation function) /// </summary> /// <param name="state">The state to be evaluated</param> /// <returns>The value of the state</returns> public int Evaluate(CheckersGameState state) { PieceColor winner = state.winner; //white is winner, if ai is white, then return max_int else return min_int if (winner == PieceColor.White) { return((state.aiColor == PieceColor.White) ? MAX_INT : MIN_INT); } //Find out that white is winner, if ai is white, then return max_int else return min_int else if (winner == PieceColor.Black) { return((state.aiColor == PieceColor.White) ? MIN_INT : MAX_INT); } //return the custom designed evaluation function //The states are evaluated by the difference in pieces left weighted by the number of turns that has occured //Every 4 turns the weight of difference in pieces goes up else { if (state.aiColor == PieceColor.White) { return((int)(state.whiteGamePieces.Count - state.blackGamePieces.Count) * (int)(1 + state.numTurnsPassed * 0.25)); } else { return((int)(state.blackGamePieces.Count - state.whiteGamePieces.Count) * (int)(1 + state.numTurnsPassed * 0.25)); } } }
/// <summary> /// Function called to initialize a new checkers game /// </summary> public void initComponent() { isMousePressed = false; didAiMove = false; currentState = new CheckersGameState(PieceColor.None); blackButton = new Rectangle(25, 640, 150, 40); whiteButton = new Rectangle(200, 640, 150, 40); }
/// <summary> /// Function that starts a new game for the player given the player color /// </summary> /// <param name="playerColor">Color of the player</param> public void newGame(PieceColor playerColor) { Console.WriteLine("========================================================" + "\n NEW GAME " + "\n========================================================"); iterativeDepth = 0; currentState = new CheckersGameState(playerColor); }
/// <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> /// Function that will determine all possible moves for the piece /// </summary> /// <param name="gameBoard">The gameboard</param> /// <param name="gameState">The current state of the game</param> /// <param name="noShow">Whether or not to mark the board</param> /// <returns>Type of move the piece has (NONE, MOVE, JUMP)</returns> public TileStatus determineMoves(CheckersBoard gameBoard, CheckersGameState gameState, bool noShow = false) { TileStatus foundMove = TileStatus.NONE; possibleMoves.Clear(); Vector2 tilePosition = new Vector2(position.X, position.Y); List <GameMove> jumps = getJumps(tilePosition, gameBoard, gameState); //Look for any jumps by the piece if (jumps.Count != 0) { foundMove = TileStatus.JUMP; possibleMoves = jumps; gameState.jumpExists = true; //Reset that the piece was just jumped over //Necessary for multi-jumps if (color == PieceColor.Black) { foreach (CheckersPiece piece in gameState.whiteGamePieces) { piece.justJumpedOver = false; } } else if (color == PieceColor.White) { foreach (CheckersPiece piece in gameState.blackGamePieces) { piece.justJumpedOver = false; } } } //If no jump was found in the context of the turn find possible regular moves for the piece else if (gameState.jumpExists == false) { List <GameMove> regularMoves = getRegularMoves(tilePosition, gameBoard, gameState); if (regularMoves.Count != 0) { foundMove = TileStatus.MOVE; possibleMoves = regularMoves; } } if (noShow == false) { gameBoard.clearMarkings(); if (foundMove != TileStatus.NONE) { foreach (GameMove move in possibleMoves) { gameBoard.getTileAt(move.destinationPosition).setStatus(foundMove); } } } return(foundMove); }
/// <summary> /// Main alpha-beta search algorithm /// </summary> /// <param name="state">The current state of the game</param> /// <returns>The value for the best next move</returns> public int AlphaBetaSearch(CheckersGameState state) { //Initialize all the variables to be used in the algorithm int alpha = MIN_INT; int beta = MAX_INT; nodes = 0; maxDepth = 0; maxPruned = 0; minPruned = 0; startTime = DateTime.Now; int value = MaxValue(state, alpha, beta, 0); endTime = DateTime.Now; return(value); }
/// <summary> /// Function handles the updating of checkers piece. /// This function is called whenever a successful move is done. /// This function will update the piece's position on the board, and change who's turn it is. /// </summary> /// <param name="gameState">The gamestate to update</param> public void Update(CheckersGameState gameState) { if (destination != Vector2.Zero) { position = destination; destination = Vector2.Zero; if (gameState.turnColor == PieceColor.Black) { gameState.changePlayerTurn(PieceColor.White); } else { gameState.changePlayerTurn(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 will find all regular moves for this piece /// A regular move is a non-jump move /// </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 regular moves</returns> public List <GameMove> getRegularMoves(Vector2 tilePosition, CheckersBoard gameBoard, CheckersGameState gameState) { List <GameMove> regularMoves = new List <GameMove>(); //Below is the logic for if the checkers piece is black if (color == PieceColor.Black) { //Get the two possible tiles a black piece could move to BoardTile topLeftTile = gameBoard.getTileAt((int)tilePosition.X - 1, (int)tilePosition.Y - 1); BoardTile topRightTile = gameBoard.getTileAt((int)tilePosition.X + 1, (int)tilePosition.Y - 1); //If the tile exists on the board, add that tile as a possible move if (topLeftTile != null && topLeftTile.getOccupiedStatus() == PieceColor.None) { regularMoves.Add(new GameMove(this, topLeftTile.position)); } if (topRightTile != null && topRightTile.getOccupiedStatus() == PieceColor.None) { regularMoves.Add(new GameMove(this, topRightTile.position)); } } //Below is the logic for if the checkers piece is white if (color == PieceColor.White) { //Get the two possible tiles a black piece could move to BoardTile bottomLeftTile = gameBoard.getTileAt((int)tilePosition.X - 1, (int)tilePosition.Y + 1); BoardTile bottomRightTile = gameBoard.getTileAt((int)tilePosition.X + 1, (int)tilePosition.Y + 1); //If the tile exists on the board, add that tile as a possible move if (bottomLeftTile != null && bottomLeftTile.getOccupiedStatus() == PieceColor.None) { regularMoves.Add(new GameMove(this, bottomLeftTile.position)); } if (bottomRightTile != null && bottomRightTile.getOccupiedStatus() == PieceColor.None) { regularMoves.Add(new GameMove(this, bottomRightTile.position)); } } return(regularMoves); }
/// <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); }