/** * Does a depth limited minimax decision search. * * @param brd current board state * @param currDepth current depth in the search * @param mvStack new ArrayList * @return move recommendation */ private ScoredBreakthroughMove minimax( EqualBreakthroughState brd, int currDepth, ArrayList<ScoredBreakthroughMove> mvStack) { if (currDepth >= mvStack.size()) { mvStack.add(new ScoredBreakthroughMove()); } // auto grow boolean toMaximize = (brd.getWho() == GameState.Who.HOME); boolean isTerminal = terminalValue(brd, mvStack.get(currDepth)); if (isTerminal) {; } else if (currDepth == depthLimit) { mvStack.get(currDepth).set(evalBoard(brd)); } else { double bestScore = (toMaximize ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY); ScoredBreakthroughMove bestMove = mvStack.get(currDepth); if ((currDepth + 1) >= mvStack.size()) { mvStack.add(new ScoredBreakthroughMove()); } // auto grow ScoredBreakthroughMove nextMove = mvStack.get((currDepth + 1)); bestMove.set(bestScore); ArrayList<ScoredBreakthroughMove> possMvs = getPossibleMoves(brd); Collections.shuffle(possMvs); for (int i = 0; i < possMvs.size(); i++) { BreakthroughMove currMv = possMvs.get(i); // copy original occupant char curOccupant = brd.board[currMv.endingRow][currMv.endingCol]; // Make move on board brd.makeMove(currMv); // Check out worth of this move minimax(brd, currDepth + 1, mvStack); // Undo the move brd.undoMove(currMv, curOccupant); // Check out the results, relative to what we've seen before if (toMaximize && nextMove.score > bestMove.score) { bestMove.set( currMv.startRow, currMv.startCol, currMv.endingRow, currMv.endingCol, nextMove.score); } else if (!toMaximize && nextMove.score < bestMove.score) { bestMove.set( currMv.startRow, currMv.startCol, currMv.endingRow, currMv.endingCol, nextMove.score); } } } return mvStack.get(currDepth); }
/** * Is the game complete at this brd state? If it is, the evaluation values for these boards is * recorded (i.e., 0 for a draw +X, for a HOME win and -X for an AWAY win. * * @param brd Breakthrough board to be examined * @param mv where to place the score information; move is irrelevant * @return true if the brd is in a terminal state */ protected boolean terminalValue(GameState brd, ScoredBreakthroughMove mv) { GameState.Status status = brd.getStatus(); boolean isTerminal = true; if (status == GameState.Status.HOME_WIN) { mv.set(MAX_EVAL_SCORE); } else if (status == GameState.Status.AWAY_WIN) { mv.set(-MAX_EVAL_SCORE); } else if (status == GameState.Status.DRAW) { mv.set(0); } else { isTerminal = false; } return isTerminal; }
/** * Get an ArrayList of all possible moves that we can make given state. * * @param state * @return */ protected ArrayList<ScoredBreakthroughMove> getPossibleMoves(GameState state) { BreakthroughState board = (BreakthroughState) state; ArrayList<ScoredBreakthroughMove> list = new ArrayList<ScoredBreakthroughMove>(); ScoredBreakthroughMove mv = new ScoredBreakthroughMove(); int dir = state.who == GameState.Who.HOME ? +1 : -1; for (int r = 0; r < BreakthroughState.N; r++) { for (int c = 0; c < BreakthroughState.N; c++) { mv.startRow = r; mv.startCol = c; mv.endingRow = r + dir; mv.endingCol = c; if (board.moveOK(mv)) { list.add((ScoredBreakthroughMove) mv.clone()); } mv.endingRow = r + dir; mv.endingCol = c + 1; if (board.moveOK(mv)) { list.add((ScoredBreakthroughMove) mv.clone()); } mv.endingRow = r + dir; mv.endingCol = c - 1; if (board.moveOK(mv)) { list.add((ScoredBreakthroughMove) mv.clone()); } } } return list; }