/* Minimax search * top player is max and bottom is min * returns a resulting move from recursive calculations */ private Result minimax(Board board, int depth, int alpha, int beta) { if (board.gameOver() || depth == 0) { return(new Result(0, evaluate(board))); } int bestVal = int.MinValue; int bestMove = 0; if (board.whoseMove() == Position.Top) { for (int move = 7; move < 13 && alpha < beta; move++) { if (board.legalMove(move)) { Board b1 = new Board(board); b1.makeMove(move, false); Result val = minimax(b1, depth - 1, alpha, beta); if (val.getBestScore() > bestVal) { bestVal = val.getBestScore(); bestMove = move; } if (bestVal > alpha) { alpha = bestVal; } } } } else { bestVal = Int32.MaxValue; for (int move = 0; move < 6 && alpha < beta; move++) { if (board.legalMove(move)) { Board b1 = new Board(board); b1.makeMove(move, false); Result val = minimax(b1, depth - 1, alpha, beta); if (val.getBestScore() < bestVal) { bestVal = val.getBestScore(); bestMove = move; } if (bestVal < beta) { beta = bestVal; } } } } return(new Result(bestMove, bestVal)); }
public override int chooseMove(Board b) { if (b.whoseMove() == Position.Top) { for (int i=12; i>=7; i--) // try first go-again if (b.stonesAt(i) == 13-i) return i; for (int i=12; i>=7; i--) // otherwise, first if (b.stonesAt(i) > 0) return i; // available move } else { for (int i=5; i>=0; i--) if (b.stonesAt(i) == 6-i) return i; for (int i=5; i>=0; i--) if (b.stonesAt(i) > 0) return i; } return -1; // an illegal move if there aren't any legal ones }
// evaluates the board from minimax search for total stones, possible replays and captures public override int evaluate(Board b) { int score = b.stonesAt(13) - b.stonesAt(6); int totalStones = 0; //total stones on the board int playAgain = 0; //total go-agains from ending in own bin int captures = 0; //total number of possible captures int targetPit = 0; //last pit into which stones from current pit will go if (b.whoseMove() == Position.Top) { for (int i = 7; i < 13; i++) { totalStones += b.stonesAt(i); if (b.stonesAt(i) == (13 - i)) { playAgain++; } targetPit = i + b.stonesAt(i); if (targetPit < 13 && (b.stonesAt(targetPit) == 0 && b.stonesAt(12 - i) > 0)) { captures++; } } } else { for (int i = 0; i < 6; i++) { totalStones -= b.stonesAt(i); if (b.stonesAt(i) == (13 - i)) { playAgain--; } targetPit = i + b.stonesAt(i); if (targetPit < 13 && (b.stonesAt(targetPit) == 0 && b.stonesAt(12 - i) > 0)) { captures--; } } } return(score + playAgain + captures + totalStones); }
public override int chooseMove(Board b) { if (b.whoseMove() == Position.Top) { b.board[0] = 0; b.board[1] = 0; b.board[2] = 0; b.board[3] = 0; b.board[4] = 0; b.board[5] = 0; b.board[6] = 0; b.board[7] = 0; b.board[8] = 0; b.board[9] = 0; b.board[10] = 0; b.board[11] = 0; b.board[12] = 1; b.board[13] = 47; return(12); } else { b.board[0] = 0; b.board[1] = 0; b.board[2] = 0; b.board[3] = 0; b.board[4] = 0; b.board[5] = 1; b.board[6] = 47; b.board[7] = 0; b.board[8] = 0; b.board[9] = 0; b.board[10] = 0; b.board[11] = 0; b.board[12] = 0; b.board[13] = 0; return(5); } }
/*Evaluate function used when the end game is not clear * Uses heuristic found on github by previous student * Code belongs to Chan Kim ([email protected]) */ public override int evaluate(Board b) { int score = b.stonesAt(13) - b.stonesAt(6); int stonesTotal = 0; int goAgainsTotal = 0; int capturesTotal = 0; for (int i = 7; i <= 12; i++) { int priority = 0; int target = b.stonesAt(i) % (13 - i); int targetStonesAt = b.stonesAt(target + 7); if (b.whoseMove() == Position.Bottom) { stonesTotal -= b.stonesAt(i); if ((b.stonesAt(i) - (13 - i) == 0) || (b.stonesAt(i) - (13 - i)) == 13) { goAgainsTotal -= (1 + priority); } if (targetStonesAt == 0 && b.stonesAt(i) == (13 - i + target + 7)) { capturesTotal += (b.stonesAt(i) + b.stonesAt(12 - target)); } } else { stonesTotal += b.stonesAt(i); if ((b.stonesAt(i) - (13 - i) == 0) || (b.stonesAt(i) - (13 - i)) == 13) { goAgainsTotal += (1 + priority); } if (targetStonesAt == 0 && b.stonesAt(i) == (13 - i + target + 7)) { capturesTotal -= (b.stonesAt(i) + b.stonesAt(12 - target)); } } priority++; } for (int i = 0; i <= 5; i++) { int priority = 0; int target = b.stonesAt(i) % (13 - i); int targetStonesAt = b.stonesAt(target); if (b.whoseMove() == Position.Bottom) { stonesTotal += b.stonesAt(i); if ((b.stonesAt(i) - (6 - i) == 0) || (b.stonesAt(i) - (6 - i)) == 13) { goAgainsTotal -= (1 + priority); } if (targetStonesAt == 0 && b.stonesAt(i) == (13 - i + target)) { capturesTotal -= (b.stonesAt(i) + b.stonesAt(12 - target)); } } else { stonesTotal -= b.stonesAt(i); if ((b.stonesAt(i) - (6 - i) == 0) || (b.stonesAt(i) - (6 - i)) == 13) { goAgainsTotal += (1 + priority); } if (targetStonesAt == 0 && b.stonesAt(i) == (13 - i + target)) { capturesTotal += (b.stonesAt(i) + b.stonesAt(12 - target)); } } priority++; } score += stonesTotal + capturesTotal + goAgainsTotal; return(score); //int score; //if (us == Position.Top) // score = b.scoreTop() - b.scoreBot(); //else // score = b.scoreBot() - b.scoreTop(); //return score; }
// b = current board state, d = depth, w = clock, alpha and beta to keep track of max/min values for pruning private Result minimax(Board b, int d, Stopwatch w, int alpha, int beta) { // throw exception if time limit has reached if (w.ElapsedMilliseconds > getTimePerMove()) { throw new MoveTimedOutException(); } // initialize variables int bestMove = 0; int bestVal; bool gameCompleted = false; if (b.gameOver() || d == 0) { return(new Result(0, evaluate(b), b.gameOver())); } // if it's my move, maximize if (b.whoseMove() == mypos) // TOP is MAX { bestVal = Int32.MinValue; // set bestVal to lowest possible number to update later on for (int move = myfirst; move <= mylast; move++) // loop through all possible moves for this player { if (b.legalMove(move)) // if it's a legal move, make that move, look at board and see if it is best move { Board b1 = new Board(b); // duplicate board b1.makeMove(move, false); // make the move Result val = minimax(b1, d - 1, w, alpha, beta); // find its value if (val.getScore() > bestVal) // remember if best maximum { bestVal = val.getScore(); // update best move, value, and game state after this move bestMove = move; gameCompleted = val.isEndGame(); } // update alpha if (bestVal > alpha) { alpha = bestVal; } } } } // else minimize else { bestVal = Int32.MaxValue; // set bestVal to highest possible number to update later on for (int move = myfirstOP; move <= mylastOP; move++) // loop through all possible moves for this player { if (b.legalMove(move)) // if it's a legal move, make that move, look at board and see if it is best move { Board b1 = new Board(b); // duplicate board b1.makeMove(move, false); // make the move Result val = minimax(b1, d - 1, w, alpha, beta); // find its value if (val.getScore() < bestVal) // remember if best minimum { bestVal = val.getScore(); // update best move, value, and game state after this move bestMove = move; gameCompleted = val.isEndGame(); } // update beta if (bestVal < beta) { beta = bestVal; } } } } // return the Result set with best move, score, and game status return(new Result(bestMove, bestVal, gameCompleted)); }
// evaluates the board based on potential go-agains, captures, and stones each side has // and returns a score based on those values public override int evaluate(Board b) { int score = b.stonesAt(13) - b.stonesAt(6); int totalStones = 0; int goAgains = 0; int captures = 0; for (int i = 7; i <= 12; i++) { int priority = 0; int target = b.stonesAt(i) % (13 - i); int targetStonesAt = b.stonesAt(target + 7); if (b.whoseMove() == Position.Bottom) { totalStones -= b.stonesAt(i); if ((b.stonesAt(i) - (13 - i) == 0) || (b.stonesAt(i) - (13 - i)) == 13) { goAgains -= (1 + priority); } if (targetStonesAt == 0 && b.stonesAt(i) == (13 - i + target + 7)) { captures += (b.stonesAt(i) + b.stonesAt(12 - target)); } } else { totalStones += b.stonesAt(i); if ((b.stonesAt(i) - (13 - i) == 0) || (b.stonesAt(i) - (13 - i)) == 13) { goAgains += (1 + priority); } if (targetStonesAt == 0 && b.stonesAt(i) == (13 - i + target + 7)) { captures -= (b.stonesAt(i) + b.stonesAt(12 - target)); } } priority++; } for (int i = 0; i <= 5; i++) { int priority = 0; int target = b.stonesAt(i) % (13 - i); int targetStonesAt = b.stonesAt(target); if (b.whoseMove() == Position.Bottom) { totalStones += b.stonesAt(i); if ((b.stonesAt(i) - (6 - i) == 0) || (b.stonesAt(i) - (6 - i)) == 13) { goAgains -= (1 + priority); } if (targetStonesAt == 0 && b.stonesAt(i) == (13 - i + target)) { captures -= (b.stonesAt(i) + b.stonesAt(12 - target)); } } else { totalStones -= b.stonesAt(i); if ((b.stonesAt(i) - (6 - i) == 0) || (b.stonesAt(i) - (6 - i)) == 13) { goAgains += (1 + priority); } if (targetStonesAt == 0 && b.stonesAt(i) == (13 - i + target)) { captures += (b.stonesAt(i) + b.stonesAt(12 - target)); } } priority++; } score += totalStones + captures + goAgains; return(score); }
// The function minimax() calculates the best possible move that Chappie can make by // recursing as far as it can within the time limit and keeping track of what the best move and // score are as it recurses. It is also implemented with AB pruning, meaning that if it // starts to go down a path where the outcome will inevitable be worse that our best values, // it prunes off that path, therefore saving time to go down a better path. private MoveResult minimax(Board b, int d, Stopwatch w, int alpha, int beta) { // check to see if the time limit is up if (w.ElapsedMilliseconds > getTimePerMove()) { throw new MoveTimedOutException(); } // base case if (b.gameOver() || d == 0) { return(new MoveResult(0, evaluate(b), b.gameOver())); } // initialization of trackers int bestMove = 0; int bestVal; bool gameCompleted = false; // check all the the moves that top could make, and act as if it is the MAX in minimax if (b.whoseMove() == Position.Top) // TOP is MAX { // smallest possible value so that it can only get better bestVal = Int32.MinValue; for (int move = 7; move <= 12 && alpha < beta; move++) { if (b.legalMove(move)) { Board b1 = new Board(b); // duplicate board b1.makeMove(move, false); // make the move MoveResult val = minimax(b1, d - 1, w, alpha, beta); // find its value if (val.getScore() > bestVal) // remember if best { bestVal = val.getScore(); bestMove = move; // track the current condition of the game gameCompleted = val.isEndGame(); } // prune if (bestVal > alpha) { alpha = bestVal; } } } } // check all the the moves that bottom could make, and act as if it is the MIN in minimax else // BOTTOM is MIN { // lergest possible value so that it can only get better bestVal = Int32.MaxValue; for (int move = 0; move <= 5 && alpha < beta; move++) { if (b.legalMove(move)) { Board b1 = new Board(b); // duplicate board b1.makeMove(move, false); // make the move MoveResult val = minimax(b1, d - 1, w, alpha, beta); // find its value if (val.getScore() < bestVal) // remember if best { bestVal = val.getScore(); bestMove = move; // track the current condition of the game gameCompleted = val.isEndGame(); } // prune if (bestVal < beta) { beta = bestVal; } } } } return(new MoveResult(bestMove, bestVal, gameCompleted)); }
// The overriden evaluate() function checks various features of the current board, and // attempts to predict the score in order to assisst the minimax function in // choosing the best move to make. The score will generally be negative if // bottom is predicted to win, and positive if top is predicted to win. public override int evaluate(Board b) { // heuristics int score = b.stonesAt(13) - b.stonesAt(6); // necessary, tested as one of the best heuristics int stonesTotal = 0; // established that it is very good, both by tests and articles (1) int goAgainsTotal = 0; // established as accurate, it will add an extra point if a 'go-again' is possible (2) int capturesTotal = 0; // established as accurate, it adds the actual number of points that would be captured (3) int opponentWinning = 0; // not entirely accurate, the weights are subject to change (4) // TOP loop - calcuate heuristics for the top player for (int i = 7; i <= 12; i++) { // add all the stones in the top row stonesTotal += b.stonesAt(i); // add all the 'go-again's in the top row if (b.stonesAt(i) - (13 - i) == 0) { goAgainsTotal += 1; } // add all of stones that can be obtained through captures int target = i + b.stonesAt(i); if (target < 13) { int targetStones = b.stonesAt(target); if (b.whoseMove() == Position.Top) { if (targetStones == 0 && b.stonesAt(13 - target - 1) != 0) { capturesTotal += b.stonesAt(13 - target - 1); } } } } // BOTTOM loop - calcuate heuristics for the bottom player for (int i = 0; i <= 5; i++) { // subtract all the stones in the bottom row stonesTotal -= b.stonesAt(i); // add all the 'go-again's in the bottom row if (b.stonesAt(i) - (6 - i) == 0) { goAgainsTotal -= 1; } // subtract all of stones that can be obtained through captures int target = i + b.stonesAt(i); if (target < 6) { int targetStones = b.stonesAt(target); if (b.whoseMove() == Position.Bottom) { if (targetStones == 0 && b.stonesAt(13 - target - 1) != 0) { capturesTotal -= b.stonesAt(13 - target - 1); } } } } // calculate the 'closeness' of the opponent winning if (b.whoseMove() == Position.Top) { // if you are top and your opponent is close to winning, give some points to MIN if (b.stonesAt(6) > 24) { opponentWinning -= 3; } } else { // if you are bottom and your opponent is close to winning, give some points to MAX if (b.stonesAt(13) > 24) { opponentWinning += 3; } } // add up all of the heuristics and return what is believed the score will be score += stonesTotal + capturesTotal + opponentWinning + goAgainsTotal; return(score); }
/* minimaxVal with Alpha-Beta Pruning * returns evaluation if it reaches maxDepth, else calculates min or max if it is opponent's turn or my turn respectively * */ public DataWrapper minimaxVal(Board b, int d, int alpha, int beta) { //when time runs out, this evaluates to true //when the cancelled execption is thrown, the last best move will be returned in the chooseMove() try->catch statement if (cancellationToken.IsCancellationRequested) { throw new OperationCanceledException(); } //return if reached gameover or max depth if (b.gameOver() || d == 0) { return(new DataWrapper(0, evaluate(b), 0, 0)); } DataWrapper data; //look through right positions for moves int start = 0; int end = 5; if (b.whoseMove() == Position.Top) { start = 7; end = 12; } //initialize variables int bestMove = -1; int secondBestMove = -1; int bestValue = int.MinValue; int secondBestValue = int.MinValue; //loop through all possible moves for (int move = start; move <= end; move++) { //if it is a legal move, check the value of it if (b.legalMove(move)) { //create copy of board, make move, and evaluate it by recursing Board newBoard = new Board(b); newBoard.makeMove(move, false); data = minimaxVal(newBoard, d - 1, alpha, beta); //if value, move pair is best or worst, set bestValue and bestMove to it //also set second best value, move pair to previous value if (isBetterMove(b.whoseMove(), bestValue, data.getValue(), d)) { secondBestValue = bestValue; bestValue = data.getValue(); secondBestMove = bestMove; bestMove = move; //alpha beta pruning if (b.whoseMove() == this.position) { alpha = Math.Max(alpha, bestValue); } else { beta = Math.Min(beta, bestValue); } } //if alpha is greater than or equal to beta, we can skip other moves (pruning part of alpha beta pruning) if (alpha >= beta) { break; } } } //return move pairs return(new DataWrapper(bestMove, bestValue, secondBestMove, secondBestValue)); }
//Function evaulates the board and returns a score paired with a board space value public int evaluate(Board b) { int score = 0; if (b.whoseMove() == Position.Top) { //oppoDiff is used to keep the difference between the current hole and the opposite one across the board for (int i = 12; i >= 7; i--) { int lastHole = (i + b.stonesAt(i)) % 13; // CASE 1.0: If there's a go-again available, add 2 to the score if (b.stonesAt(i) == 13 - i) { score += 2; } // CASE 1.1: If there's a go-again available for the opponent, subtract 1 from the score //also use loop for... // CASE 2.0: If the holes are empty, check to see if they can be filled // ...and... //CASE 2.1: Check opponent's holes to see if they can be filled for (int j = 5; j >= 0; j--) { int lastOppoHole = (j + b.stonesAt(j)) % 13; //CASE 1.1 check... if (b.stonesAt(j) == 6 - j) { score -= 2; //Counter-attack: If this opponent hole can be filled by the stones in the current hole, //add to score if (b.stonesAt(j) < lastHole) { score += 5; } } //CASE 2.0 check... if (b.stonesAt(j) == 0) { if (lastHole >= j) { score += 5; } } //CASE 2.1 check... if (j + lastOppoHole >= i) { score -= 2; } } //If the last stone is placed within the top... if (lastHole < 13) { // CASE 3.0: Possible stone capture if (b.stonesAt(lastHole) == 0) { //if the last stone is placed in an empty hole on the top score += 1; if (b.stonesAt(getOpposite(lastHole)) != 0) { //if there are stones found in the opposite hole, add them to the score score += b.stonesAt(getOpposite(lastHole)); } } //CASE 3.1: Last stone placed will contribute to a possible capture from your opponent else { //If the opposite hole is empty and not 0 if (b.stonesAt(getOpposite(lastHole)) == 0 && getOpposite(lastHole) != 0) { for (int j = b.stonesAt(getOpposite(lastHole - 1)); j >= 0; j--) { //If any of the holes leading up to the opposite empty hole have enough stones //to put one in the empty hole and capture, subtract from the score if (b.stonesAt(j) + j == b.stonesAt(getOpposite(lastHole))) { score -= b.stonesAt(i); } } } } } } } else { for (int i = 5; i >= 0; i--) { //int lasthole is the hole that gets the last stone from hole i int lastHole = (i + b.stonesAt(i)) % 13; // CASE 1.0: If there's a go-again available, add 2 to the score if (b.stonesAt(i) == 6 - i) { score += 2; } // CASE 1.1: If there's a go-again available for the opponent, subtract 1 from the score //also use loop for... // CASE 2.0: if the holes are empty, check to see if they can be filled for (int j = 12; j >= 7; j--) { int lastOppoHole = (j + b.stonesAt(j)) % 13; if (b.stonesAt(j) == 13 - j) { score -= 2; //Counter-attack: If this opponent hole can be filled by the stones in the current hole, //add to score if (b.stonesAt(j) < lastHole) { score += 5; } } //CASE 2.0 case check... if (b.stonesAt(j) == 0) { if (lastHole >= j) { score += 5; } } //CASE 2.1 check... if (j + lastOppoHole >= i) { score -= 2; } } //If the last stone is placed within the bottom... if (lastHole < 6) { //CASE 3.0: Possible stone capture if (b.stonesAt(lastHole) == 0) { //if the last stone is placed in an empty hole on the bottom score += 1; //if there are stones found in the opposite hole, add them to the score if (b.stonesAt(getOpposite(lastHole)) != 0) { score += b.stonesAt(getOpposite(lastHole)); } } //CASE 3.1: Last stone placed will contribute to a possible capture from your opponent else { //If the opposite hole is empty and not 7 if (b.stonesAt(getOpposite(lastHole)) == 0 && getOpposite(lastHole) != 7) { for (int j = b.stonesAt(getOpposite(lastHole - 1)); j >= 0; j--) { //If any of the holes leading up to the opposite empty hole have enough stones //to put one in the empty hole and capture, subtract from the score if (b.stonesAt(j) + j == b.stonesAt(getOpposite(lastHole))) { score -= b.stonesAt(i); } } } } } } } return(score); }
//int d is depth of miniMax search. int max should be 1 for getting the max result, -1 for min. Should return moveResult object public moveResult miniMax(Board b, int d, double timeLimit, int max, Timer timer) { //Set up variables int bestVal = int.MinValue; int bestMove = 0; moveResult bestMR = new moveResult(bestMove, bestVal); //Base case: depth is 0, time is up or game is over. Returns a moveResult with the best score value and 0 move value if (d == 0 || timeOut || b.gameOver()) { bestVal = evaluate(b); //Console.WriteLine("Best score found to be " + bestVal); moveResult bestMoveResult = new moveResult(bestMove, bestVal); return(bestMoveResult); } if (b.whoseMove() == Position.Top) { for (int move = 7; move <= 12; move++) { //Console.WriteLine(""); //Recurse if the move is legal, time hasn't expired and game isn't over int val = 0; if (b.legalMove(move) && !timeOut && !b.gameOver()) { Board b1 = new Board(b); b1.makeMove(move, true); int newDepth = d - 1; //Switch the value of max to be the opposite for the recursion int mnOrMx = max * -1; //Console.WriteLine("Recursing from " + d + " to " + newDepth + "..."); val = miniMax(b1, newDepth, timeLimit, mnOrMx, timer).getScore(); //If at "max" level, prioritize and return maximum value if (max == 1) { //If there's a new max, update bestValue and bestMove if (val > bestVal) { bestVal = val; bestMove = move; } } //If at "min" level, prioritize and return minimum value if (max == -1) { //If there's a new min, update bestValue and bestMove if (val < bestVal || val != int.MinValue) { bestVal = val; bestMove = move; } } } } bestMR = new moveResult(bestMove, bestVal); return(bestMR); } else { for (int move = 0; move <= 5; move++) { //Console.WriteLine(""); //Recurse if the move is legal, time hasn't expired and game isn't over if (b.legalMove(move) && !timeOut && !b.gameOver()) { Board b1 = new Board(b); b1.makeMove(move, true); int newDepth = d - 1; //Switch the value of max to be the opposite for the recursion int mnOrMx = max * -1; // Console.WriteLine("Recursing from " + d + " to " + newDepth + "..."); int val = miniMax(b1, newDepth, timeLimit, mnOrMx, timer).getScore(); // Console.WriteLine("New Value: " + val); //If at "max" level, prioritize and return maximum value if (max == 1) { //If there's a new max, update bestValue and bestMove if (val > bestVal) { bestVal = val; bestMove = move; } } //If at "min" level, prioritize and return minimum value if (max == -1) { //If there's a new min, update bestValue and bestMove if (val < bestVal || val != int.MinValue) { bestVal = val; bestMove = move; } } } } bestMR = new moveResult(bestMove, bestVal); return(bestMR); } //Console.WriteLine("Best move: " + bestMove + " Best score: " + bestVal); bestMR = new moveResult(bestMove, bestVal); return(bestMR); }
private Result minimaxVal(Board b, int d, Stopwatch w, int alpha, int beta) { if (w.ElapsedMilliseconds > getTimePerMove()) { throw new MoveTimedOutException(); } int bestMove = 0; int bestVal; bool gameCompleted = false; if (b.gameOver() || d == 0) { return(new Result(0, evaluate(b), b.gameOver())); } if (b.whoseMove() == Position.Top) { bestVal = Int32.MinValue; for (int move = 7; move <= 12; move++) { if (b.legalMove(move)) { Board b1 = new Board(b); b1.makeMove(move, false); Result val = minimaxVal(b1, d - 1, w, alpha, beta); if (val.getScore() > bestVal) { bestVal = val.getScore(); bestMove = move; gameCompleted = val.isEndGame(); } if (bestVal > alpha) { alpha = bestVal; } } } } else { bestVal = Int32.MaxValue; for (int move = 0; move <= 5; move++) { if (b.legalMove(move)) { Board b1 = new Board(b); b1.makeMove(move, false); Result val = minimaxVal(b1, d - 1, w, alpha, beta); if (val.getScore() < bestVal) { bestVal = val.getScore(); bestMove = move; gameCompleted = val.isEndGame(); } if (bestVal < beta) { beta = bestVal; } } } } return(new Result(bestMove, bestVal, gameCompleted)); }