/**
   * 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;
  }