/**
   * Takes a table of nodes and adds a weighted score to each node score in the table. Nodes
   * touching hexes with better numbers get better scores. Also numbers that the player isn't
   * touching yet are better than ones that the player is already touching.
   *
   * @param nodes the table of nodes with scores: Hashtable<Integer,Integer> . Contents will
   *     be modified by the scoring.
   * @param player the player that we are doing the rating for, or <tt>null</tt>; will give a bonus
   *     to numbers the player isn't already touching
   * @param weight a number that is multiplied by the score
   */
  protected void bestSpotForNumbers(
      Hashtable<Integer, Integer> nodes, SOCPlayer player, int weight) {
    final int[] numRating = SOCNumberProbabilities.INT_VALUES;
    final SOCPlayerNumbers playerNumbers = (player != null) ? player.getNumbers() : null;
    final SOCBoard board = game.getBoard();

    // 80 is highest practical score (40 if player == null)
    final int maxScore = (player != null) ? 80 : 40;

    int oldScore;
    Enumeration<Integer> nodesEnum = nodes.keys();

    while (nodesEnum.hasMoreElements()) {
      final Integer node = nodesEnum.nextElement();

      // log.debug("BSN - looking at node "+Integer.toHexString(node.intValue()));
      oldScore = nodes.get(node).intValue();

      int score = 0;
      Enumeration<Integer> hexesEnum = board.getAdjacentHexesToNode(node.intValue()).elements();

      while (hexesEnum.hasMoreElements()) {
        final int hex = hexesEnum.nextElement().intValue();
        final int number = board.getNumberOnHexFromCoord(hex);
        score += numRating[number];

        if ((number != 0) && (playerNumbers != null) && !playerNumbers.hasNumber(number)) {
          /** add a bonus for numbers that the player doesn't already have */

          // log.debug("ADDING BONUS FOR NOT HAVING "+number);
          score += numRating[number];
        }

        // log.debug(" -- -- Adding "+numRating[board.getNumberOnHexFromCoord(hex)]);
      }

      /*
       * normalize score and multiply by weight
       * 80 is highest practical score (40 if player == null)
       * lowest score is 0
       */
      final int nScore = ((score * 100) / maxScore) * weight;
      final Integer finalScore = new Integer(nScore + oldScore);
      nodes.put(node, finalScore);

      // log.debug("BSN -- put node "+Integer.toHexString(node.intValue())+" with old score
      // "+oldScore+" + new score "+nScore);
    }
  }
  /**
   * Takes a table of nodes and adds a weighted score to each node score in the table. Nodes
   * touching hexes with better numbers get better scores.
   *
   * @param nodes the table of nodes with scores: Hashtable<Integer,Integer>
   * @param weight a number that is multiplied by the score
   */
  protected void bestSpotForNumbers(Hashtable nodes, int weight, SOCGame game) {
    int[] numRating = SOCNumberProbabilities.INT_VALUES;
    SOCBoard board = game.getBoard();
    int oldScore;
    Enumeration nodesEnum = nodes.keys(); // <Integer>

    while (nodesEnum.hasMoreElements()) {
      Integer node = (Integer) nodesEnum.nextElement();

      // log.debug("BSN - looking at node "+Integer.toHexString(node.intValue()));
      oldScore = ((Integer) nodes.get(node)).intValue();

      int score = 0;
      Enumeration hexesEnum =
          SOCBoard.getAdjacentHexesToNode(node.intValue()).elements(); // <Integer>

      while (hexesEnum.hasMoreElements()) {
        int hex = ((Integer) hexesEnum.nextElement()).intValue();
        score += numRating[board.getNumberOnHexFromCoord(hex)];

        // log.debug(" -- -- Adding "+numRating[board.getNumberOnHexFromCoord(hex)]);
      }

      /*
       * normalize score and multiply by weight
       * 40 is highest practical score
       * lowest score is 0
       */
      int nScore = ((score * 100) / 40) * weight;
      Integer finalScore = new Integer(nScore + oldScore);
      nodes.put(node, finalScore);

      // log.debug("BSN -- put node "+Integer.toHexString(node.intValue())+" with old score
      // "+oldScore+" + new score "+nScore);
    }
  }
  /** 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;
  }