// MinimaxPlayer.java /** * * @author Sean Bridges * @version 1.0 * * The MinimaxPlayer uses the minimax algorithm to determine what move it should * make. Looks ahead depth moves. The number of moves will be on the order * of n^depth, where n is the number of moves possible, and depth is the number * of moves the engine is searching. Because of this the minimax player periodically * polls the thread that calls getMove(..) to see if it was interrupted. It the thread * is intertupted, the player returns null after it checks. */ public class MinimaxPlayer extends DefaultPlayer { //---------------------------------------------- //instance variables //the number of levels minimax will look ahead private int depth = 1; private Player minPlayer; //---------------------------------------------- //constructors /** Creates new MinimaxPlayer */ public MinimaxPlayer(String name, int number, Player minPlayer) { super(name, number); this.minPlayer = minPlayer; } //---------------------------------------------- //instance methods /** * Get the number of levels that the Minimax Player is currently looking * ahead. */ public int getDepth() { return depth; } /** * Set the number of levels that the Minimax Player will look ahead * when getMove is next called */ public void setDepth(int anInt) { depth = anInt; } /** Passed a copy of the board, asked what move it would like to make. * * The MinimaxPlayer periodically polls the thread that makes this call to * see if it is interrupted. If it is the player returns null. * * Looks ahead depth moves. */ public Move getMove(Board b) { MinimaxCalculator calc = new MinimaxCalculator(b,this,minPlayer); return calc.calculateMove(depth); } }//end MinimaxPlayer /** * The MinimaxCalculator does the actual work of finding the minimax move. * A new calculator should be created each time a move is to be made. * A calculator should only be used once. */ final class MinimaxCalculator { //------------------------------------------------------- //instance variables //the number of moves we have tried private int moveCount = 0; private long startTime; private Player minPlayer; private Player maxPlayer; private Board board; private final int MAX_POSSIBLE_STRENGTH; private final int MIN_POSSIBLE_STRENGTH; //------------------------------------------------------- //constructors MinimaxCalculator(Board b, Player max, Player min) { board = b; maxPlayer = max; minPlayer = min; MAX_POSSIBLE_STRENGTH = board.getBoardStats().getMaxStrength(); MIN_POSSIBLE_STRENGTH = board.getBoardStats().getMinStrength(); } //------------------------------------------------------- //instance methods /** * Calculate the move to be made. */ public Move calculateMove(int depth) { startTime = System.currentTimeMillis(); if(depth == 0) { System.out.println("Error, 0 depth in minumax player"); Thread.dumpStack(); return null; } Move[] moves = board.getPossibleMoves(maxPlayer); int maxStrength = MIN_POSSIBLE_STRENGTH; int maxIndex = 0; for(int i = 0; i < moves.length; i++) { if(board.move(moves[i])) { moveCount++; int strength = expandMinNode(depth -1, maxStrength); if(strength > maxStrength) { maxStrength = strength; maxIndex = i; } board.undoLastMove(); }//end if move made //if the thread has been interrupted, return immediately. if(Thread.currentThread().isInterrupted()) { return null; } }//end for all moves long stopTime = System.currentTimeMillis(); System.out.print("MINIMAX: Number of moves tried:" + moveCount); System.out.println(" Time:" + (stopTime - startTime) + " milliseconds"); return moves[maxIndex]; } /** * A max node returns the max score of its descendents. * parentMinimum is the minumum score that the parent has already encountered. * if we find a score that is higher than this, we will return that score * immediately rather than continue to expand the tree, since * the min node above us only cares if we are lower than its current * min score. */ private int expandMaxNode(int depth, int parentMinimum) { //base step if(depth == 0 || board.isGameOver()) { return board.getBoardStats().getStrength(maxPlayer); } //recursive step Move[] moves = board.getPossibleMoves(maxPlayer); int maxStrength = MIN_POSSIBLE_STRENGTH; for(int i = 0; i < moves.length; i++) { if(board.move(moves[i])) { moveCount++; int strength = expandMinNode(depth -1, maxStrength); if(strength > parentMinimum) { board.undoLastMove(); return strength; } if(strength > maxStrength) { maxStrength = strength; } board.undoLastMove(); }//end if move made }//end for all moves return maxStrength; }//end expandMaxNode /** * The min node chooses the smallest of its descendents. * parentMaximum is the maximum that the parent max node has already found, * if we find something smaller than this, return immediatly, since the * parent max node will choose the greatest value it can find. */ private int expandMinNode(int depth, int parentMaximum) { //base step if(depth == 0 || board.isGameOver()) { return board.getBoardStats().getStrength(maxPlayer); } //recursive step Move[] moves = board.getPossibleMoves(minPlayer); int minStrength = MAX_POSSIBLE_STRENGTH; for(int i = 0; i < moves.length; i++) { if(board.move(moves[i])) { moveCount++; int strength = expandMaxNode(depth -1, minStrength); if(strength < parentMaximum) { board.undoLastMove(); return strength; } if(strength < minStrength) { minStrength = strength; } board.undoLastMove(); }//end if move made }//end for all moves return minStrength; }//end expandMaxNode }