/** place a road attached to the last initial settlement */
  public int planInitRoad(SOCGame game, SOCPlayer ourPlayerData, SOCRobotClient client) {
    int settlementNode = ourPlayerData.getLastSettlementCoord();
    Hashtable twoAway = new Hashtable(); // <Integer,Integer>

    log.debug("--- placeInitRoad");

    /** look at all of the nodes that are 2 away from the last settlement and pick the best one */
    SOCBoard board = game.getBoard();
    for (int facing = 1; facing <= 6; ++facing) {
      // each of 6 directions: NE, E, SE, SW, W, NW
      int tmp = board.getAdjacentNodeToNode2Away(settlementNode, facing);
      if ((tmp != -9) && ourPlayerData.isPotentialSettlement(tmp))
        twoAway.put(new Integer(tmp), new Integer(0));
    }

    scoreNodesForSettlements(twoAway, 3, 5, 10, game, ourPlayerData);

    log.debug("Init Road for " + client.getNickname());

    /**
     * create a dummy player to calculate possible places to build taking into account where other
     * players will build before we can.
     */
    SOCPlayer dummy = new SOCPlayer(ourPlayerData.getPlayerNumber(), game);

    if (game.getGameState() == SOCGame.START1B) {
      /** do a look ahead so we don't build toward a place where someone else will build first. */
      int numberOfBuilds = numberOfEnemyBuilds(game);
      log.debug(
          "Other players will build "
              + numberOfBuilds
              + " settlements before I get to build again.");

      if (numberOfBuilds > 0) {
        /** rule out where other players are going to build */
        Hashtable allNodes = new Hashtable(); // <Integer.Integer>
        final int minNode = board.getMinNode();

        for (int i = minNode; i <= SOCBoard.MAXNODE; i++) {
          if (ourPlayerData.isPotentialSettlement(i)) {
            log.debug("-- potential settlement at " + Integer.toHexString(i));
            allNodes.put(new Integer(i), new Integer(0));
          }
        }

        /** favor spots with the most high numbers */
        bestSpotForNumbers(allNodes, 100, game);

        /** favor spots near good ports */
        /** check 3:1 ports */
        Vector miscPortNodes = board.getPortCoordinates(SOCBoard.MISC_PORT);
        bestSpot2AwayFromANodeSet(board, allNodes, miscPortNodes, 5);

        /** check out good 2:1 ports */
        for (int portType = SOCBoard.CLAY_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
          /**
           * if the chances of rolling a number on the resource is better than 1/3, then it's worth
           * looking at the port
           */
          if (resourceEstimates[portType] > 33) {
            Vector portNodes = board.getPortCoordinates(portType);
            int portWeight = (resourceEstimates[portType] * 10) / 56;
            bestSpot2AwayFromANodeSet(board, allNodes, portNodes, portWeight);
          }
        }

        /*
         * create a list of potential settlements that takes into account
         * where other players will build
         */
        Vector psList = new Vector(); // <Integer>

        for (int j = minNode; j <= SOCBoard.MAXNODE; j++) {
          if (ourPlayerData.isPotentialSettlement(j)) {
            log.debug("- potential settlement at " + Integer.toHexString(j));
            psList.addElement(new Integer(j));
          }
        }

        dummy.setPotentialSettlements(psList);

        for (int builds = 0; builds < numberOfBuilds; builds++) {
          BoardNodeScorePair bestNodePair = new BoardNodeScorePair(0, 0);
          Enumeration nodesEnum = allNodes.keys(); // <Integer>

          while (nodesEnum.hasMoreElements()) {
            Integer nodeCoord = (Integer) nodesEnum.nextElement();
            final int score = ((Integer) allNodes.get(nodeCoord)).intValue();
            log.debug("NODE = " + Integer.toHexString(nodeCoord.intValue()) + " SCORE = " + score);

            if (bestNodePair.getScore() < score) {
              bestNodePair.setScore(score);
              bestNodePair.setNode(nodeCoord.intValue());
            }
          }

          /** pretend that someone has built a settlement on the best spot */
          dummy.updatePotentials(new SOCSettlement(ourPlayerData, bestNodePair.getNode(), null));

          /** remove this spot from the list of best spots */
          allNodes.remove(new Integer(bestNodePair.getNode()));
        }
      }
    }

    /** Find the best scoring node */
    BoardNodeScorePair bestNodePair = new BoardNodeScorePair(0, 0);
    Enumeration cenum = twoAway.keys(); // <Integer>

    while (cenum.hasMoreElements()) {
      Integer coord = (Integer) cenum.nextElement();
      final int score = ((Integer) twoAway.get(coord)).intValue();

      log.debug(
          "Considering " + Integer.toHexString(coord.intValue()) + " with a score of " + score);

      if (dummy.isPotentialSettlement(coord.intValue())) {
        if (bestNodePair.getScore() < score) {
          bestNodePair.setScore(score);
          bestNodePair.setNode(coord.intValue());
        }
      } else {
        log.debug("Someone is bound to ruin that spot.");
      }
    }

    // Reminder: settlementNode == ourPlayerData.getLastSettlementCoord()
    final int destination = bestNodePair.getNode(); // coordinate of future settlement
    // 2 nodes away from settlementNode
    final int roadEdge // will be adjacent to settlementNode
        = board.getAdjacentEdgeToNode2Away(settlementNode, destination);

    dummy.destroyPlayer();

    return roadEdge;
  }
  /** figure out where to place the two settlements */
  public int planInitialSettlements(SOCGame game, SOCPlayer ourPlayerData) {
    log.debug("--- planInitialSettlements");

    int[] rolls;
    Enumeration hexes; // Integers
    int speed;
    boolean allTheWay;
    firstSettlement = 0;
    secondSettlement = 0;

    int bestSpeed = 4 * SOCBuildingSpeedEstimate.DEFAULT_ROLL_LIMIT;
    SOCBoard board = game.getBoard();
    SOCResourceSet emptySet = new SOCResourceSet();
    SOCPlayerNumbers playerNumbers = new SOCPlayerNumbers(board.getBoardEncodingFormat());
    int probTotal;
    int bestProbTotal;
    boolean[] ports = new boolean[SOCBoard.WOOD_PORT + 1];
    SOCBuildingSpeedEstimate estimate = new SOCBuildingSpeedEstimate();
    int[] prob = SOCNumberProbabilities.INT_VALUES;

    bestProbTotal = 0;

    for (int firstNode = board.getMinNode(); firstNode <= SOCBoard.MAXNODE; firstNode++) {
      if (ourPlayerData.isPotentialSettlement(firstNode)) {
        Integer firstNodeInt = new Integer(firstNode);

        //
        // this is just for testing purposes
        //
        log.debug("FIRST NODE -----------");
        log.debug("firstNode = " + board.nodeCoordToString(firstNode));

        StringBuffer sb = new StringBuffer();
        sb.append("numbers:[");
        playerNumbers.clear();
        probTotal = 0;
        hexes = SOCBoard.getAdjacentHexesToNode(firstNode).elements();

        while (hexes.hasMoreElements()) {
          Integer hex = (Integer) hexes.nextElement();
          int number = board.getNumberOnHexFromCoord(hex.intValue());
          int resource = board.getHexTypeFromCoord(hex.intValue());
          playerNumbers.addNumberForResource(number, resource, hex.intValue());
          probTotal += prob[number];
          sb.append(number + " ");
        }

        sb.append("]");
        log.debug(sb.toString());
        sb = new StringBuffer();
        sb.append("ports: ");

        for (int portType = SOCBoard.MISC_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
          if (board.getPortCoordinates(portType).contains(firstNodeInt)) {
            ports[portType] = true;
          } else {
            ports[portType] = false;
          }

          sb.append(ports[portType] + "  ");
        }

        log.debug(sb.toString());
        log.debug("probTotal = " + probTotal);
        estimate.recalculateEstimates(playerNumbers);
        speed = 0;
        allTheWay = false;

        try {
          speed +=
              estimate.calculateRollsFast(emptySet, SOCGame.SETTLEMENT_SET, 300, ports).getRolls();
          speed += estimate.calculateRollsFast(emptySet, SOCGame.CITY_SET, 300, ports).getRolls();
          speed += estimate.calculateRollsFast(emptySet, SOCGame.CARD_SET, 300, ports).getRolls();
          speed += estimate.calculateRollsFast(emptySet, SOCGame.ROAD_SET, 300, ports).getRolls();
        } catch (CutoffExceededException e) {
        }

        rolls = estimate.getEstimatesFromNothingFast(ports, 300);
        sb = new StringBuffer();
        sb.append(" road: " + rolls[SOCBuildingSpeedEstimate.ROAD]);
        sb.append(" stlmt: " + rolls[SOCBuildingSpeedEstimate.SETTLEMENT]);
        sb.append(" city: " + rolls[SOCBuildingSpeedEstimate.CITY]);
        sb.append(" card: " + rolls[SOCBuildingSpeedEstimate.CARD]);
        log.debug(sb.toString());
        log.debug("speed = " + speed);

        //
        // end test
        //
        for (int secondNode = firstNode + 1; secondNode <= SOCBoard.MAXNODE; secondNode++) {
          if ((ourPlayerData.isPotentialSettlement(secondNode))
              && (!board.getAdjacentNodesToNode(secondNode).contains(firstNodeInt))) {
            log.debug("firstNode = " + board.nodeCoordToString(firstNode));
            log.debug("secondNode = " + board.nodeCoordToString(secondNode));

            Integer secondNodeInt = new Integer(secondNode);

            /** get the numbers for these settlements */
            sb = new StringBuffer();
            sb.append("numbers:[");
            playerNumbers.clear();
            probTotal = 0;
            hexes = SOCBoard.getAdjacentHexesToNode(firstNode).elements();

            while (hexes.hasMoreElements()) {
              Integer hex = (Integer) hexes.nextElement();
              int number = board.getNumberOnHexFromCoord(hex.intValue());
              int resource = board.getHexTypeFromCoord(hex.intValue());
              playerNumbers.addNumberForResource(number, resource, hex.intValue());
              probTotal += prob[number];
              sb.append(number + " ");
            }

            sb.append("] [");
            hexes = SOCBoard.getAdjacentHexesToNode(secondNode).elements();

            while (hexes.hasMoreElements()) {
              Integer hex = (Integer) hexes.nextElement();
              int number = board.getNumberOnHexFromCoord(hex.intValue());
              int resource = board.getHexTypeFromCoord(hex.intValue());
              playerNumbers.addNumberForResource(number, resource, hex.intValue());
              probTotal += prob[number];
              sb.append(number + " ");
            }

            sb.append("]");
            log.debug(sb.toString());

            /** see if the settlements are on any ports */
            sb = new StringBuffer();
            sb.append("ports: ");

            for (int portType = SOCBoard.MISC_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
              if ((board.getPortCoordinates(portType).contains(firstNodeInt))
                  || (board.getPortCoordinates(portType).contains(secondNodeInt))) {
                ports[portType] = true;
              } else {
                ports[portType] = false;
              }

              sb.append(ports[portType] + "  ");
            }

            log.debug(sb.toString());
            log.debug("probTotal = " + probTotal);

            /** estimate the building speed for this pair */
            estimate.recalculateEstimates(playerNumbers);
            speed = 0;
            allTheWay = false;

            try {
              speed +=
                  estimate
                      .calculateRollsFast(emptySet, SOCGame.SETTLEMENT_SET, bestSpeed, ports)
                      .getRolls();

              if (speed < bestSpeed) {
                speed +=
                    estimate
                        .calculateRollsFast(emptySet, SOCGame.CITY_SET, bestSpeed, ports)
                        .getRolls();

                if (speed < bestSpeed) {
                  speed +=
                      estimate
                          .calculateRollsFast(emptySet, SOCGame.CARD_SET, bestSpeed, ports)
                          .getRolls();

                  if (speed < bestSpeed) {
                    speed +=
                        estimate
                            .calculateRollsFast(emptySet, SOCGame.ROAD_SET, bestSpeed, ports)
                            .getRolls();
                    allTheWay = true;
                  }
                }
              }
            } catch (CutoffExceededException e) {
              speed = bestSpeed;
            }

            rolls = estimate.getEstimatesFromNothingFast(ports, bestSpeed);
            sb = new StringBuffer();
            sb.append(" road: " + rolls[SOCBuildingSpeedEstimate.ROAD]);
            sb.append(" stlmt: " + rolls[SOCBuildingSpeedEstimate.SETTLEMENT]);
            sb.append(" city: " + rolls[SOCBuildingSpeedEstimate.CITY]);
            sb.append(" card: " + rolls[SOCBuildingSpeedEstimate.CARD]);
            log.debug(sb.toString());
            log.debug("allTheWay = " + allTheWay);
            log.debug("speed = " + speed);

            /** keep the settlements with the best speed */
            if (speed < bestSpeed) {
              firstSettlement = firstNode;
              secondSettlement = secondNode;
              bestSpeed = speed;
              bestProbTotal = probTotal;
              log.debug("bestSpeed = " + bestSpeed);
              log.debug("bestProbTotal = " + bestProbTotal);
            } else if ((speed == bestSpeed) && allTheWay) {
              if (probTotal > bestProbTotal) {
                log.debug("Equal speed, better prob");
                firstSettlement = firstNode;
                secondSettlement = secondNode;
                bestSpeed = speed;
                bestProbTotal = probTotal;
                log.debug("firstSettlement = " + Integer.toHexString(firstSettlement));
                log.debug("secondSettlement = " + Integer.toHexString(secondSettlement));
                log.debug("bestSpeed = " + bestSpeed);
                log.debug("bestProbTotal = " + bestProbTotal);
              }
            }
          }
        }
      }
    }

    /** choose which settlement to place first */
    playerNumbers.clear();
    hexes = SOCBoard.getAdjacentHexesToNode(firstSettlement).elements();

    while (hexes.hasMoreElements()) {
      int hex = ((Integer) hexes.nextElement()).intValue();
      int number = board.getNumberOnHexFromCoord(hex);
      int resource = board.getHexTypeFromCoord(hex);
      playerNumbers.addNumberForResource(number, resource, hex);
    }

    Integer firstSettlementInt = new Integer(firstSettlement);

    for (int portType = SOCBoard.MISC_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
      if (board.getPortCoordinates(portType).contains(firstSettlementInt)) {
        ports[portType] = true;
      } else {
        ports[portType] = false;
      }
    }

    estimate.recalculateEstimates(playerNumbers);

    int firstSpeed = 0;
    int cutoff = 100;

    try {
      firstSpeed +=
          estimate.calculateRollsFast(emptySet, SOCGame.SETTLEMENT_SET, cutoff, ports).getRolls();
    } catch (CutoffExceededException e) {
      firstSpeed += cutoff;
    }

    try {
      firstSpeed +=
          estimate.calculateRollsFast(emptySet, SOCGame.CITY_SET, cutoff, ports).getRolls();
    } catch (CutoffExceededException e) {
      firstSpeed += cutoff;
    }

    try {
      firstSpeed +=
          estimate.calculateRollsFast(emptySet, SOCGame.CARD_SET, cutoff, ports).getRolls();
    } catch (CutoffExceededException e) {
      firstSpeed += cutoff;
    }

    try {
      firstSpeed +=
          estimate.calculateRollsFast(emptySet, SOCGame.ROAD_SET, cutoff, ports).getRolls();
    } catch (CutoffExceededException e) {
      firstSpeed += cutoff;
    }

    playerNumbers.clear();
    hexes = SOCBoard.getAdjacentHexesToNode(secondSettlement).elements();

    while (hexes.hasMoreElements()) {
      int hex = ((Integer) hexes.nextElement()).intValue();
      int number = board.getNumberOnHexFromCoord(hex);
      int resource = board.getHexTypeFromCoord(hex);
      playerNumbers.addNumberForResource(number, resource, hex);
    }

    Integer secondSettlementInt = new Integer(secondSettlement);

    for (int portType = SOCBoard.MISC_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
      if (board.getPortCoordinates(portType).contains(secondSettlementInt)) {
        ports[portType] = true;
      } else {
        ports[portType] = false;
      }
    }

    estimate.recalculateEstimates(playerNumbers);

    int secondSpeed = 0;

    try {
      secondSpeed +=
          estimate
              .calculateRollsFast(emptySet, SOCGame.SETTLEMENT_SET, bestSpeed, ports)
              .getRolls();
    } catch (CutoffExceededException e) {
      secondSpeed += cutoff;
    }

    try {
      secondSpeed +=
          estimate.calculateRollsFast(emptySet, SOCGame.CITY_SET, bestSpeed, ports).getRolls();
    } catch (CutoffExceededException e) {
      secondSpeed += cutoff;
    }

    try {
      secondSpeed +=
          estimate.calculateRollsFast(emptySet, SOCGame.CARD_SET, bestSpeed, ports).getRolls();
    } catch (CutoffExceededException e) {
      secondSpeed += cutoff;
    }

    try {
      secondSpeed +=
          estimate.calculateRollsFast(emptySet, SOCGame.ROAD_SET, bestSpeed, ports).getRolls();
    } catch (CutoffExceededException e) {
      secondSpeed += cutoff;
    }

    if (firstSpeed > secondSpeed) {
      int tmp = firstSettlement;
      firstSettlement = secondSettlement;
      secondSettlement = tmp;
    }

    log.debug(
        board.nodeCoordToString(firstSettlement)
            + ":"
            + firstSpeed
            + ", "
            + board.nodeCoordToString(secondSettlement)
            + ":"
            + secondSpeed);
    return firstSettlement;
  }
  /** figure out where to place the second settlement */
  public int planSecondSettlement(SOCGame game, SOCPlayer ourPlayerData) {
    log.debug("--- planSecondSettlement");

    int bestSpeed = 4 * SOCBuildingSpeedEstimate.DEFAULT_ROLL_LIMIT;
    SOCBoard board = game.getBoard();
    SOCResourceSet emptySet = new SOCResourceSet();
    SOCPlayerNumbers playerNumbers = new SOCPlayerNumbers(board.getBoardEncodingFormat());
    boolean[] ports = new boolean[SOCBoard.WOOD_PORT + 1];
    SOCBuildingSpeedEstimate estimate = new SOCBuildingSpeedEstimate();
    int probTotal;
    int bestProbTotal;
    int[] prob = SOCNumberProbabilities.INT_VALUES;
    int firstNode = firstSettlement;
    Integer firstNodeInt = new Integer(firstNode);

    bestProbTotal = 0;
    secondSettlement = -1;

    for (int secondNode = board.getMinNode(); secondNode <= SOCBoard.MAXNODE; secondNode++) {
      if ((ourPlayerData.isPotentialSettlement(secondNode))
          && (!board.getAdjacentNodesToNode(secondNode).contains(firstNodeInt))) {
        Integer secondNodeInt = new Integer(secondNode);

        /** get the numbers for these settlements */
        StringBuffer sb = new StringBuffer();
        sb.append("numbers: ");
        playerNumbers.clear();
        probTotal = 0;

        Enumeration hexes = SOCBoard.getAdjacentHexesToNode(firstNode).elements(); // Integers

        while (hexes.hasMoreElements()) {
          final int hex = ((Integer) hexes.nextElement()).intValue();
          int number = board.getNumberOnHexFromCoord(hex);
          int resource = board.getHexTypeFromCoord(hex);
          playerNumbers.addNumberForResource(number, resource, hex);
          probTotal += prob[number];
          sb.append(number + " ");
        }

        hexes = SOCBoard.getAdjacentHexesToNode(secondNode).elements();

        while (hexes.hasMoreElements()) {
          final int hex = ((Integer) hexes.nextElement()).intValue();
          int number = board.getNumberOnHexFromCoord(hex);
          int resource = board.getHexTypeFromCoord(hex);
          playerNumbers.addNumberForResource(number, resource, hex);
          probTotal += prob[number];
          sb.append(number + " ");
        }

        /** see if the settlements are on any ports */
        sb.append("ports: ");

        for (int portType = SOCBoard.MISC_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
          if ((board.getPortCoordinates(portType).contains(firstNodeInt))
              || (board.getPortCoordinates(portType).contains(secondNodeInt))) {
            ports[portType] = true;
          } else {
            ports[portType] = false;
          }

          sb.append(ports[portType] + "  ");
        }

        log.debug(sb.toString());
        log.debug("probTotal = " + probTotal);

        /** estimate the building speed for this pair */
        estimate.recalculateEstimates(playerNumbers);

        int speed = 0;

        try {
          speed +=
              estimate
                  .calculateRollsFast(emptySet, SOCGame.SETTLEMENT_SET, bestSpeed, ports)
                  .getRolls();

          if (speed < bestSpeed) {
            speed +=
                estimate
                    .calculateRollsFast(emptySet, SOCGame.CITY_SET, bestSpeed, ports)
                    .getRolls();

            if (speed < bestSpeed) {
              speed +=
                  estimate
                      .calculateRollsFast(emptySet, SOCGame.CARD_SET, bestSpeed, ports)
                      .getRolls();

              if (speed < bestSpeed) {
                speed +=
                    estimate
                        .calculateRollsFast(emptySet, SOCGame.ROAD_SET, bestSpeed, ports)
                        .getRolls();
              }
            }
          }
        } catch (CutoffExceededException e) {
          speed = bestSpeed;
        }

        log.debug(
            Integer.toHexString(firstNode) + ", " + Integer.toHexString(secondNode) + ":" + speed);

        /** keep the settlements with the best speed */
        if ((speed < bestSpeed) || (secondSettlement < 0)) {
          firstSettlement = firstNode;
          secondSettlement = secondNode;
          bestSpeed = speed;
          bestProbTotal = probTotal;
          log.debug("firstSettlement = " + Integer.toHexString(firstSettlement));
          log.debug("secondSettlement = " + Integer.toHexString(secondSettlement));

          int[] rolls = estimate.getEstimatesFromNothingFast(ports);
          sb = new StringBuffer();
          sb.append("road: " + rolls[SOCBuildingSpeedEstimate.ROAD]);
          sb.append(" stlmt: " + rolls[SOCBuildingSpeedEstimate.SETTLEMENT]);
          sb.append(" city: " + rolls[SOCBuildingSpeedEstimate.CITY]);
          sb.append(" card: " + rolls[SOCBuildingSpeedEstimate.CARD]);
          log.debug(sb.toString());
          log.debug("bestSpeed = " + bestSpeed);
        } else if (speed == bestSpeed) {
          if (probTotal > bestProbTotal) {
            firstSettlement = firstNode;
            secondSettlement = secondNode;
            bestSpeed = speed;
            bestProbTotal = probTotal;
            log.debug("firstSettlement = " + Integer.toHexString(firstSettlement));
            log.debug("secondSettlement = " + Integer.toHexString(secondSettlement));

            int[] rolls = estimate.getEstimatesFromNothingFast(ports);
            sb = new StringBuffer();
            sb.append("road: " + rolls[SOCBuildingSpeedEstimate.ROAD]);
            sb.append(" stlmt: " + rolls[SOCBuildingSpeedEstimate.SETTLEMENT]);
            sb.append(" city: " + rolls[SOCBuildingSpeedEstimate.CITY]);
            sb.append(" card: " + rolls[SOCBuildingSpeedEstimate.CARD]);
            log.debug(sb.toString());
            log.debug("bestSpeed = " + bestSpeed);
          }
        }
      }
    }
    return secondSettlement;
  }
  /**
   * this figures out how many rolls it would take this player to get the target set of resources
   * given a starting set
   *
   * @param startingResources the starting resources
   * @param targetResources the target resources
   * @param cutoff throw an exception if the total speed is greater than this
   * @param ports a list of port flags
   * @return the number of rolls and our resources when the target is reached. If {@link
   *     SOCResourceSet#contains(SOCResourceSet) startingResources.contains(targetResources)},
   *     returns 0 rolls and a {@code null} resource set.
   * @throws CutoffExceededException if estimate more than {@code cutoff} turns to obtain {@code
   *     targetResources}
   */
  protected SOCResSetBuildTimePair calculateRollsAccurate(
      SOCResourceSet startingResources, SOCResourceSet targetResources, int cutoff, boolean[] ports)
      throws CutoffExceededException {
    D.ebugPrintln("calculateRollsAccurate");
    D.ebugPrintln("  start: " + startingResources);
    D.ebugPrintln("  target: " + targetResources);

    SOCResourceSet ourResources = startingResources.copy();
    int rolls = 0;

    @SuppressWarnings("unchecked")
    Hashtable<SOCResourceSet, Float>[] resourcesOnRoll = new Hashtable[2];
    resourcesOnRoll[0] = new Hashtable<SOCResourceSet, Float>();
    resourcesOnRoll[1] = new Hashtable<SOCResourceSet, Float>();

    int lastRoll = 0;
    int thisRoll = 1;

    resourcesOnRoll[lastRoll].put(ourResources, new Float(1.0));

    boolean targetReached = ourResources.contains(targetResources);
    SOCResourceSet targetReachedResources = null;
    float targetReachedProb = (float) 0.0;

    while (!targetReached) {
      if (D.ebugOn) {
        D.ebugPrintln("roll: " + rolls);
        D.ebugPrintln("resourcesOnRoll[lastRoll]:");

        Enumeration<SOCResourceSet> roltEnum = resourcesOnRoll[lastRoll].keys();

        while (roltEnum.hasMoreElements()) {
          SOCResourceSet rs = roltEnum.nextElement();
          Float prob = resourcesOnRoll[lastRoll].get(rs);
          D.ebugPrintln("---- prob:" + prob);
          D.ebugPrintln("---- rsrcs:" + rs);
          D.ebugPrintln();
        }

        D.ebugPrintln("targetReachedProb: " + targetReachedProb);
        D.ebugPrintln("===================================");
      }

      rolls++;

      if (rolls > cutoff) {
        D.ebugPrintln(
            "startingResources="
                + startingResources
                + "\ntargetResources="
                + targetResources
                + "\ncutoff="
                + cutoff
                + "\nourResources="
                + ourResources);
        throw new CutoffExceededException();
      }

      //
      //  get our resources for the roll
      //
      for (int diceResult = 2; diceResult <= 12; diceResult++) {
        SOCResourceSet gainedResources = resourcesForRoll[diceResult];
        float diceProb = SOCNumberProbabilities.FLOAT_VALUES[diceResult];

        //
        //  add the resources that we get on this roll to
        //  each set of resources that we got on the last
        //  roll and multiply the probabilities
        //
        Enumeration<SOCResourceSet> lastResourcesEnum = resourcesOnRoll[lastRoll].keys();

        while (lastResourcesEnum.hasMoreElements()) {
          SOCResourceSet lastResources = lastResourcesEnum.nextElement();
          Float lastProb = resourcesOnRoll[lastRoll].get(lastResources);
          SOCResourceSet newResources = lastResources.copy();
          newResources.add(gainedResources);

          float newProb = lastProb.floatValue() * diceProb;

          if (!newResources.contains(targetResources)) {
            //
            // do any possible trading with the bank/ports
            //
            for (int giveResource = SOCResourceConstants.CLAY;
                giveResource <= SOCResourceConstants.WOOD;
                giveResource++) {
              if ((newResources.getAmount(giveResource) - targetResources.getAmount(giveResource))
                  > 1) {
                //
                // find the ratio at which we can trade
                //
                int tradeRatio;

                if (ports[giveResource]) {
                  tradeRatio = 2;
                } else if (ports[SOCBoard.MISC_PORT]) {
                  tradeRatio = 3;
                } else {
                  tradeRatio = 4;
                }

                //
                // get the target resources
                //
                int numTrades =
                    (newResources.getAmount(giveResource) - targetResources.getAmount(giveResource))
                        / tradeRatio;

                // D.ebugPrintln("))) ***");
                // D.ebugPrintln("))) giveResource="+giveResource);
                // D.ebugPrintln("))) tradeRatio="+tradeRatio);
                // D.ebugPrintln("))) newResources="+newResources);
                // D.ebugPrintln("))) targetResources="+targetResources);
                // D.ebugPrintln("))) numTrades="+numTrades);
                for (int trades = 0; trades < numTrades; trades++) {
                  //
                  // find the most needed resource by looking at
                  // which of the resources we still need takes the
                  // longest to aquire
                  //
                  int mostNeededResource = -1;

                  for (int resource = SOCResourceConstants.CLAY;
                      resource <= SOCResourceConstants.WOOD;
                      resource++) {
                    if (newResources.getAmount(resource) < targetResources.getAmount(resource)) {
                      if (mostNeededResource < 0) {
                        mostNeededResource = resource;
                      } else {
                        if (rollsPerResource[resource] > rollsPerResource[mostNeededResource]) {
                          mostNeededResource = resource;
                        }
                      }
                    }
                  }

                  //
                  // make the trade
                  //
                  // D.ebugPrintln("))) want to trade "+tradeRatio+" "+giveResource+" for a
                  // "+mostNeededResource);
                  if ((mostNeededResource != -1)
                      && (newResources.getAmount(giveResource) >= tradeRatio)) {
                    // D.ebugPrintln("))) trading...");
                    newResources.add(1, mostNeededResource);

                    if (newResources.getAmount(giveResource) < tradeRatio) {
                      System.err.println("@@@ rsrcs=" + newResources);
                      System.err.println("@@@ tradeRatio=" + tradeRatio);
                      System.err.println("@@@ giveResource=" + giveResource);
                      System.err.println("@@@ target=" + targetResources);
                    }

                    newResources.subtract(tradeRatio, giveResource);

                    // D.ebugPrintln("))) newResources="+newResources);
                  }

                  if (newResources.contains(targetResources)) {
                    break;
                  }
                }

                if (newResources.contains(targetResources)) {
                  break;
                }
              }
            }
          }

          //
          //  if this set of resources is already in the list
          //  of possible outcomes, add this probability to
          //  that one, else just add this to the list
          //
          Float probFloat = resourcesOnRoll[thisRoll].get(newResources);
          float newProb2 = newProb;

          if (probFloat != null) {
            newProb2 = probFloat.floatValue() + newProb;
          }

          //
          //  check to see if we reached our target
          //
          if (newResources.contains(targetResources)) {
            D.ebugPrintln("-----> TARGET HIT *");
            D.ebugPrintln("newResources: " + newResources);
            D.ebugPrintln("newProb: " + newProb);
            targetReachedProb += newProb;

            if (targetReachedResources == null) {
              targetReachedResources = newResources;
            }

            if (targetReachedProb >= 0.5) {
              targetReached = true;
            }
          } else {
            resourcesOnRoll[thisRoll].put(newResources, new Float(newProb2));
          }
        }
      }

      //
      //  copy the resourcesOnRoll[thisRoll] table to the
      //  resourcesOnRoll[lastRoll] table and clear the
      //  resourcesOnRoll[thisRoll] table
      //
      int tmp = lastRoll;
      lastRoll = thisRoll;
      thisRoll = tmp;
      resourcesOnRoll[thisRoll].clear();
    }

    if (D.ebugOn) {
      float probSum = (float) 0.0;
      D.ebugPrintln("**************** TARGET REACHED ************");
      D.ebugPrintln("targetReachedResources: " + targetReachedResources);
      D.ebugPrintln("targetReachedProb: " + targetReachedProb);
      D.ebugPrintln("roll: " + rolls);
      D.ebugPrintln("resourcesOnRoll[lastRoll]:");

      Enumeration<SOCResourceSet> roltEnum = resourcesOnRoll[lastRoll].keys();

      while (roltEnum.hasMoreElements()) {
        SOCResourceSet rs = roltEnum.nextElement();
        Float prob = resourcesOnRoll[lastRoll].get(rs);
        probSum += prob.floatValue();
        D.ebugPrintln("---- prob:" + prob);
        D.ebugPrintln("---- rsrcs:" + rs);
        D.ebugPrintln();
      }

      D.ebugPrintln("probSum = " + probSum);
      D.ebugPrintln("===================================");
    }

    return (new SOCResSetBuildTimePair(targetReachedResources, rolls));
  }
  /**
   * Plan and place a road attached to our most recently placed initial settlement, in game states
   * {@link SOCGame#START1B START1B}, {@link SOCGame#START2B START2B}, {@link SOCGame#START3B
   * START3B}. Also sets {@link #getPlannedInitRoadDestinationNode()}.
   *
   * <p>Road choice is based on the best nearby potential settlements, and doesn't directly check
   * {@link SOCPlayer#isPotentialRoad(int) ourPlayerData.isPotentialRoad(edgeCoord)}. If the server
   * rejects our road choice, then {@link
   * SOCRobotBrain#cancelWrongPiecePlacementLocal(SOCPlayingPiece)} will need to know which
   * settlement node we were aiming for, and call {@link SOCPlayer#clearPotentialSettlement(int)
   * ourPlayerData.clearPotentialSettlement(nodeCoord)}. The {@link
   * SOCRobotBrain#lastStartingRoadTowardsNode} field holds this coordinate.
   *
   * @return road edge adjacent to initial settlement node
   */
  public int planInitRoad() {
    // TODO handle ships here

    final int settlementNode = ourPlayerData.getLastSettlementCoord();

    /**
     * Score the nearby nodes to build road towards: Key = coord Integer; value = Integer score
     * towards "best" node.
     */
    Hashtable<Integer, Integer> twoAway = new Hashtable<Integer, Integer>();

    log.debug("--- placeInitRoad");

    /** look at all of the nodes that are 2 away from the last settlement, and pick the best one */
    final SOCBoard board = game.getBoard();

    for (int facing = 1; facing <= 6; ++facing) {
      // each of 6 directions: NE, E, SE, SW, W, NW
      int tmp = board.getAdjacentNodeToNode2Away(settlementNode, facing);
      if ((tmp != -9) && ourPlayerData.canPlaceSettlement(tmp))
        twoAway.put(new Integer(tmp), new Integer(0));
    }

    scoreNodesForSettlements(twoAway, 3, 5, 10);

    log.debug("Init Road for " + ourPlayerData.getName());

    /**
     * create a dummy player to calculate possible places to build taking into account where other
     * players will build before we can.
     */
    SOCPlayer dummy = new SOCPlayer(ourPlayerData.getPlayerNumber(), game);

    if ((game.getGameState() == SOCGame.START1B)
        || (game.isGameOptionSet(SOCGameOption.K_SC_3IP)
            && (game.getGameState() == SOCGame.START2B))) {
      /** do a look ahead so we don't build toward a place where someone else will build first. */
      final int numberOfBuilds = numberOfEnemyBuilds();
      log.debug(
          "Other players will build "
              + numberOfBuilds
              + " settlements before I get to build again.");

      if (numberOfBuilds > 0) {
        /** rule out where other players are going to build */
        Hashtable<Integer, Integer> allNodes = new Hashtable<Integer, Integer>();

        {
          Iterator<Integer> psi = ourPlayerData.getPotentialSettlements().iterator();
          while (psi.hasNext()) allNodes.put(psi.next(), Integer.valueOf(0));
          // log.debug("-- potential settlement at " + Integer.toHexString(next));
        }

        /** favor spots with the most high numbers */
        bestSpotForNumbers(allNodes, null, 100);

        /** favor spots near good ports */
        /** check 3:1 ports */
        Vector<Integer> miscPortNodes = board.getPortCoordinates(SOCBoard.MISC_PORT);
        bestSpot2AwayFromANodeSet(board, allNodes, miscPortNodes, 5);

        /** check out good 2:1 ports */
        for (int portType = SOCBoard.CLAY_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
          /**
           * if the chances of rolling a number on the resource is better than 1/3, then it's worth
           * looking at the port
           */
          if (resourceEstimates[portType] > 33) {
            Vector<Integer> portNodes = board.getPortCoordinates(portType);
            final int portWeight = (resourceEstimates[portType] * 10) / 56;
            bestSpot2AwayFromANodeSet(board, allNodes, portNodes, portWeight);
          }
        }

        /*
         * create a list of potential settlements that takes into account
         * where other players will build
         */
        Vector<Integer> psList = new Vector<Integer>();

        psList.addAll(ourPlayerData.getPotentialSettlements());
        // log.debug("- potential settlement at " + Integer.toHexString(j));

        dummy.setPotentialAndLegalSettlements(psList, false, null);

        for (int builds = 0; builds < numberOfBuilds; builds++) {
          BoardNodeScorePair bestNodePair = new BoardNodeScorePair(0, 0);
          Enumeration<Integer> nodesEnum = allNodes.keys();

          while (nodesEnum.hasMoreElements()) {
            final Integer nodeCoord = nodesEnum.nextElement();
            final int score = allNodes.get(nodeCoord).intValue();
            log.debug("NODE = " + Integer.toHexString(nodeCoord.intValue()) + " SCORE = " + score);

            if (bestNodePair.getScore() < score) {
              bestNodePair.setScore(score);
              bestNodePair.setNode(nodeCoord.intValue());
            }
          }

          /** pretend that someone has built a settlement on the best spot */
          dummy.updatePotentials(new SOCSettlement(ourPlayerData, bestNodePair.getNode(), null));

          /** remove this spot from the list of best spots */
          allNodes.remove(new Integer(bestNodePair.getNode()));
        }
      }
    }

    /** Find the best scoring node */
    BoardNodeScorePair bestNodePair = new BoardNodeScorePair(0, 0);
    Enumeration<Integer> cenum = twoAway.keys();

    while (cenum.hasMoreElements()) {
      final Integer coordInt = cenum.nextElement();
      final int coord = coordInt.intValue();
      final int score = twoAway.get(coordInt).intValue();

      log.debug("Considering " + Integer.toHexString(coord) + " with a score of " + score);

      if (dummy.canPlaceSettlement(coord)) {
        if (bestNodePair.getScore() < score) {
          bestNodePair.setScore(score);
          bestNodePair.setNode(coord);
        }
      } else {
        log.debug("Someone is bound to ruin that spot.");
      }
    }

    // Reminder: settlementNode == ourPlayerData.getLastSettlementCoord()
    plannedRoadDestinationNode = bestNodePair.getNode(); // coordinate of future settlement
    // 2 nodes away from settlementNode
    final int roadEdge // will be adjacent to settlementNode
        = board.getAdjacentEdgeToNode2Away(settlementNode, plannedRoadDestinationNode);

    dummy.destroyPlayer();

    return roadEdge;
  }
  /**
   * figure out where to place the second settlement
   *
   * @return {@link #secondSettlement}, or -1 if none
   */
  public int planSecondSettlement() {
    log.debug("--- planSecondSettlement");

    int bestSpeed = 4 * SOCBuildingSpeedEstimate.DEFAULT_ROLL_LIMIT;
    final SOCBoard board = game.getBoard();
    SOCResourceSet emptySet = new SOCResourceSet();
    SOCPlayerNumbers playerNumbers = new SOCPlayerNumbers(board);
    boolean[] ports = new boolean[SOCBoard.WOOD_PORT + 1];
    SOCBuildingSpeedEstimate estimate = new SOCBuildingSpeedEstimate();
    int probTotal;
    int bestProbTotal;
    final int[] prob = SOCNumberProbabilities.INT_VALUES;
    final int firstNode = firstSettlement;

    bestProbTotal = 0;
    secondSettlement = -1;

    final int[] ourPotentialSettlements = ourPlayerData.getPotentialSettlements_arr();
    if (ourPotentialSettlements == null) return -1; // Should not occur

    for (int i = 0; i < ourPotentialSettlements.length; ++i) {
      final int secondNode = ourPotentialSettlements[i];
      // assert: ourPlayerData.isPotentialSettlement(secondNode)

      if (board.isNodeAdjacentToNode(secondNode, firstNode))
        continue; // <-- too close to firstNode to build --

      /** get the numbers for these settlements */
      StringBuffer sb = new StringBuffer();
      sb.append("numbers: ");
      playerNumbers.clear();
      probTotal = playerNumbers.updateNumbersAndProbability(firstNode, board, prob, sb);
      probTotal += playerNumbers.updateNumbersAndProbability(secondNode, board, prob, sb);

      /** see if the settlements are on any ports */
      // sb.append("ports: ");

      Arrays.fill(ports, false);
      int portType = board.getPortTypeFromNodeCoord(firstNode);
      if (portType != -1) ports[portType] = true;
      portType = board.getPortTypeFromNodeCoord(secondNode);
      if (portType != -1) ports[portType] = true;

      // log.debug(sb.toString());
      log.debug("probTotal = " + probTotal);

      /** estimate the building speed for this pair */
      estimate.recalculateEstimates(playerNumbers);

      int speed = 0;

      try {
        speed +=
            estimate
                .calculateRollsAndRsrcFast(emptySet, SOCGame.SETTLEMENT_SET, bestSpeed, ports)
                .getRolls();

        if (speed < bestSpeed) {
          speed +=
              estimate
                  .calculateRollsAndRsrcFast(emptySet, SOCGame.CITY_SET, bestSpeed, ports)
                  .getRolls();

          if (speed < bestSpeed) {
            speed +=
                estimate
                    .calculateRollsAndRsrcFast(emptySet, SOCGame.CARD_SET, bestSpeed, ports)
                    .getRolls();

            if (speed < bestSpeed) {
              speed +=
                  estimate
                      .calculateRollsAndRsrcFast(emptySet, SOCGame.ROAD_SET, bestSpeed, ports)
                      .getRolls();
            }
          }
        }

        // because of addition, speed might be as much as (bestSpeed - 1) + bestSpeed
      } catch (CutoffExceededException e) {
        speed = bestSpeed;
      }

      log.debug(
          Integer.toHexString(firstNode) + ", " + Integer.toHexString(secondNode) + ":" + speed);

      /** keep the settlements with the best speed */
      if ((speed < bestSpeed) || (secondSettlement < 0)) {
        firstSettlement = firstNode;
        secondSettlement = secondNode;
        bestSpeed = speed;
        bestProbTotal = probTotal;
        log.debug("firstSettlement = " + Integer.toHexString(firstSettlement));
        log.debug("secondSettlement = " + Integer.toHexString(secondSettlement));

        int[] rolls = estimate.getEstimatesFromNothingFast(ports);
        sb = new StringBuffer();
        sb.append("road: " + rolls[SOCBuildingSpeedEstimate.ROAD]);
        sb.append(" stlmt: " + rolls[SOCBuildingSpeedEstimate.SETTLEMENT]);
        sb.append(" city: " + rolls[SOCBuildingSpeedEstimate.CITY]);
        sb.append(" card: " + rolls[SOCBuildingSpeedEstimate.CARD]);
        log.debug(sb.toString());
        log.debug("bestSpeed = " + bestSpeed);
      } else if (speed == bestSpeed) {
        if (probTotal > bestProbTotal) {
          firstSettlement = firstNode;
          secondSettlement = secondNode;
          bestSpeed = speed;
          bestProbTotal = probTotal;
          log.debug("firstSettlement = " + Integer.toHexString(firstSettlement));
          log.debug("secondSettlement = " + Integer.toHexString(secondSettlement));

          int[] rolls = estimate.getEstimatesFromNothingFast(ports);
          sb = new StringBuffer();
          sb.append("road: " + rolls[SOCBuildingSpeedEstimate.ROAD]);
          sb.append(" stlmt: " + rolls[SOCBuildingSpeedEstimate.SETTLEMENT]);
          sb.append(" city: " + rolls[SOCBuildingSpeedEstimate.CITY]);
          sb.append(" card: " + rolls[SOCBuildingSpeedEstimate.CARD]);
          log.debug(sb.toString());
          log.debug("bestSpeed = " + bestSpeed);
        }
      }
    } // for (i in ourPotentialSettlements[])

    return secondSettlement;
  }
  /**
   * figure out where to place the two settlements
   *
   * @return {@link #firstSettlement}, or 0 if no potential settlements for our player
   */
  public int planInitialSettlements() {
    log.debug("--- planInitialSettlements");

    int speed;
    boolean allTheWay;
    firstSettlement = 0;
    secondSettlement = 0;

    int bestSpeed = 4 * SOCBuildingSpeedEstimate.DEFAULT_ROLL_LIMIT;
    SOCBoard board = game.getBoard();
    SOCResourceSet emptySet = new SOCResourceSet();
    SOCPlayerNumbers playerNumbers = new SOCPlayerNumbers(board);
    int probTotal;
    int bestProbTotal;
    boolean[] ports = new boolean[SOCBoard.WOOD_PORT + 1];
    SOCBuildingSpeedEstimate estimate = new SOCBuildingSpeedEstimate();
    final int[] prob = SOCNumberProbabilities.INT_VALUES;

    bestProbTotal = 0;

    final int[] ourPotentialSettlements = ourPlayerData.getPotentialSettlements_arr();
    if (ourPotentialSettlements == null) return 0; // Should not occur

    for (int i = 0; i < ourPotentialSettlements.length; ++i) {
      final int firstNode = ourPotentialSettlements[i];
      // assert: ourPlayerData.isPotentialSettlement(firstNode)

      final Integer firstNodeInt = new Integer(firstNode);

      //
      // this is just for testing purposes
      //
      log.debug("FIRST NODE -----------");
      log.debug("firstNode = " + board.nodeCoordToString(firstNode));

      StringBuffer sb = new StringBuffer();
      sb.append("numbers:[");

      playerNumbers.clear();
      probTotal = playerNumbers.updateNumbersAndProbability(firstNode, board, prob, sb);

      sb.append("]");
      log.debug(sb.toString());
      sb = new StringBuffer();
      sb.append("ports: ");

      for (int portType = SOCBoard.MISC_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
        ports[portType] = (board.getPortCoordinates(portType).contains(firstNodeInt));

        sb.append(ports[portType] + "  ");
      }

      log.debug(sb.toString());
      log.debug("probTotal = " + probTotal);
      estimate.recalculateEstimates(playerNumbers);
      speed = 0;
      allTheWay = false;

      try {
        speed +=
            estimate
                .calculateRollsAndRsrcFast(emptySet, SOCGame.SETTLEMENT_SET, 300, ports)
                .getRolls();
        speed +=
            estimate.calculateRollsAndRsrcFast(emptySet, SOCGame.CITY_SET, 300, ports).getRolls();
        speed +=
            estimate.calculateRollsAndRsrcFast(emptySet, SOCGame.CARD_SET, 300, ports).getRolls();
        speed +=
            estimate.calculateRollsAndRsrcFast(emptySet, SOCGame.ROAD_SET, 300, ports).getRolls();
      } catch (CutoffExceededException e) {
      }

      if (D.ebugOn) {
        final int[] rolls = estimate.getEstimatesFromNothingFast(ports, 300);
        sb = new StringBuffer();
        sb.append(" road: " + rolls[SOCBuildingSpeedEstimate.ROAD]);
        sb.append(" stlmt: " + rolls[SOCBuildingSpeedEstimate.SETTLEMENT]);
        sb.append(" city: " + rolls[SOCBuildingSpeedEstimate.CITY]);
        sb.append(" card: " + rolls[SOCBuildingSpeedEstimate.CARD]);
        log.debug(sb.toString());
        log.debug("speed = " + speed);
      }

      //
      // end test
      //

      //
      // calculate pairs of first and second settlement together
      //

      for (int j = 1 + i; j < ourPotentialSettlements.length; ++j) {
        final int secondNode = ourPotentialSettlements[j];
        // assert: ourPlayerData.isPotentialSettlement(secondNode)

        if (board.isNodeAdjacentToNode(secondNode, firstNode))
          continue; // <-- too close to firstNode to build --

        log.debug("firstNode = " + board.nodeCoordToString(firstNode));
        log.debug("secondNode = " + board.nodeCoordToString(secondNode));

        /** get the numbers for these settlements */
        sb = new StringBuffer();
        sb.append("numbers:[");

        playerNumbers.clear();
        probTotal = playerNumbers.updateNumbersAndProbability(firstNode, board, prob, sb);

        sb.append("] [");

        probTotal += playerNumbers.updateNumbersAndProbability(secondNode, board, prob, sb);

        sb.append("]");
        log.debug(sb.toString());

        /** see if the settlements are on any ports */
        // sb = new StringBuffer();
        // sb.append("ports: ");

        Arrays.fill(ports, false);
        int portType = board.getPortTypeFromNodeCoord(firstNode);
        if (portType != -1) ports[portType] = true;
        portType = board.getPortTypeFromNodeCoord(secondNode);
        if (portType != -1) ports[portType] = true;

        // log.debug(sb.toString());
        log.debug("probTotal = " + probTotal);

        /** estimate the building speed for this pair */
        estimate.recalculateEstimates(playerNumbers);
        speed = 0;
        allTheWay = false;

        try {
          speed +=
              estimate
                  .calculateRollsAndRsrcFast(emptySet, SOCGame.SETTLEMENT_SET, bestSpeed, ports)
                  .getRolls();

          if (speed < bestSpeed) {
            speed +=
                estimate
                    .calculateRollsAndRsrcFast(emptySet, SOCGame.CITY_SET, bestSpeed, ports)
                    .getRolls();

            if (speed < bestSpeed) {
              speed +=
                  estimate
                      .calculateRollsAndRsrcFast(emptySet, SOCGame.CARD_SET, bestSpeed, ports)
                      .getRolls();

              if (speed < bestSpeed) {
                speed +=
                    estimate
                        .calculateRollsAndRsrcFast(emptySet, SOCGame.ROAD_SET, bestSpeed, ports)
                        .getRolls();
                allTheWay = true;
              }
            }
          }

          // because of addition, speed might be as much as (bestSpeed - 1) + bestSpeed
        } catch (CutoffExceededException e) {
          speed = bestSpeed;
        }

        if (D.ebugOn) {
          final int[] rolls = estimate.getEstimatesFromNothingFast(ports, bestSpeed);
          sb = new StringBuffer();
          sb.append(" road: " + rolls[SOCBuildingSpeedEstimate.ROAD]);
          sb.append(" stlmt: " + rolls[SOCBuildingSpeedEstimate.SETTLEMENT]);
          sb.append(" city: " + rolls[SOCBuildingSpeedEstimate.CITY]);
          sb.append(" card: " + rolls[SOCBuildingSpeedEstimate.CARD]);
          log.debug(sb.toString());
          log.debug("allTheWay = " + allTheWay);
          log.debug("speed = " + speed);
        }

        /** keep the settlements with the best speed */
        if (speed < bestSpeed) {
          firstSettlement = firstNode;
          secondSettlement = secondNode;
          bestSpeed = speed;
          bestProbTotal = probTotal;
          log.debug("bestSpeed = " + bestSpeed);
          log.debug("bestProbTotal = " + bestProbTotal);
        } else if ((speed == bestSpeed) && allTheWay) {
          if (probTotal > bestProbTotal) {
            log.debug("Equal speed, better prob");
            firstSettlement = firstNode;
            secondSettlement = secondNode;
            bestSpeed = speed;
            bestProbTotal = probTotal;
            log.debug("firstSettlement = " + Integer.toHexString(firstSettlement));
            log.debug("secondSettlement = " + Integer.toHexString(secondSettlement));
            log.debug("bestSpeed = " + bestSpeed);
            log.debug("bestProbTotal = " + bestProbTotal);
          }
        }
      } // for (j past i in ourPotentialSettlements[])
    } // for (i in ourPotentialSettlements[])

    /** choose which settlement to place first */
    playerNumbers.clear();
    playerNumbers.updateNumbers(firstSettlement, board);

    final Integer firstSettlementInt = new Integer(firstSettlement);

    for (int portType = SOCBoard.MISC_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
      ports[portType] = (board.getPortCoordinates(portType).contains(firstSettlementInt));
    }

    estimate.recalculateEstimates(playerNumbers);

    int firstSpeed = 0;
    final int cutoff = 100;

    firstSpeed += estimate.calculateRollsFast(emptySet, SOCGame.SETTLEMENT_SET, cutoff, ports);
    firstSpeed += estimate.calculateRollsFast(emptySet, SOCGame.CITY_SET, cutoff, ports);
    firstSpeed += estimate.calculateRollsFast(emptySet, SOCGame.CARD_SET, cutoff, ports);
    firstSpeed += estimate.calculateRollsFast(emptySet, SOCGame.ROAD_SET, cutoff, ports);

    playerNumbers.clear();
    playerNumbers.updateNumbers(secondSettlement, board);

    final Integer secondSettlementInt = new Integer(secondSettlement);

    for (int portType = SOCBoard.MISC_PORT; portType <= SOCBoard.WOOD_PORT; portType++) {
      ports[portType] = (board.getPortCoordinates(portType).contains(secondSettlementInt));
    }

    estimate.recalculateEstimates(playerNumbers);

    int secondSpeed = 0;

    secondSpeed += estimate.calculateRollsFast(emptySet, SOCGame.SETTLEMENT_SET, bestSpeed, ports);
    secondSpeed += estimate.calculateRollsFast(emptySet, SOCGame.CITY_SET, bestSpeed, ports);
    secondSpeed += estimate.calculateRollsFast(emptySet, SOCGame.CARD_SET, bestSpeed, ports);
    secondSpeed += estimate.calculateRollsFast(emptySet, SOCGame.ROAD_SET, bestSpeed, ports);

    if (firstSpeed > secondSpeed) {
      int tmp = firstSettlement;
      firstSettlement = secondSettlement;
      secondSettlement = tmp;
    }

    log.debug(
        board.nodeCoordToString(firstSettlement)
            + ":"
            + firstSpeed
            + ", "
            + board.nodeCoordToString(secondSettlement)
            + ":"
            + secondSpeed);
    return firstSettlement;
  }