gt:             return "kfactor: " + startIndex + " " + endIndex + " " + value;
context: /*
   * JOGRE (Java Online Gaming Real-time Engine) - API
   * Copyright (C) 2004  Bob Marks (marksie531@yahoo.com)
   * http://jogre.sourceforge.org
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   */
  package net.olemartin.tools.rating;
  
  import net.olemartin.domain.PlayerResult;
  
  import java.util.HashMap;
  import java.util.StringTokenizer;
  
  /**
   * JOGRE's implementation of the ELO rating system.  The following is an
   * example of how to use the Elo Rating System.
   * <code>
   * EloRatingSystem elo = new EloRatingSystem();
   * int userRating = 1600;
   * int opponentRating = 1650;
   * int newUserRating = elo.getNewRating(userRating, opponentRating, WIN);
   * int newOpponentRating = elo.getNewRating(opponentRating, userRating, LOSS);
   * </code>
   *
   * @author Garrett Lehman (gman)
   */
  public class EloRatingSystem {
  
      public KFactor[] kFactors = {};
  
      // List of singletons are stored in this HashMap
      private static HashMap<String, EloRatingSystem> ratingSystems = null;
  
      private EloRatingSystem() {
  
          // Read k factor in from server properties
          String kFactorStr = "0-4000=64";
  
          // Split each of the kFactor ranges up (kfactor1,factor2, etc)
          StringTokenizer st1 = new StringTokenizer(kFactorStr, ",");
          kFactors = new KFactor[st1.countTokens()];
  
          int index = 0;
          while (st1.hasMoreTokens()) {
              String kfr = st1.nextToken();
  
              // Split the range from the value (range=value)
              StringTokenizer st2 = new StringTokenizer(kfr, "=");
              String range = st2.nextToken();
  
              // Retrieve value
              double value = Double.parseDouble(st2.nextToken());
  
              // Retrieve start end index from the range
              st2 = new StringTokenizer(range, "-");
              int startIndex = Integer.parseInt(st2.nextToken());
              int endIndex = Integer.parseInt(st2.nextToken());
  
              // Add kFactor to range
              kFactors[index++] = new KFactor(startIndex, endIndex, value);
          }
      }
  
      /**
       * Return instance of an ELO rating system.
       *
       * @param game Game to key of.
       * @return ELO rating system for specified game.
       */
      public static synchronized EloRatingSystem getInstance(String game) {
          if (ratingSystems == null) {
              ratingSystems = new HashMap<>();
          }
  
          // Retrieve rating system
          EloRatingSystem ratingSystem = ratingSystems.get(game);
  
          // If null then create new one and add to hash keying off the game
          if (ratingSystem == null) {
              ratingSystem = new EloRatingSystem();
              ratingSystems.put(game, ratingSystem);
  
              return ratingSystem;
          } else {
              return ratingSystem;
          }
      }
  
      public int getNewRating(int rating, int opponentRating, PlayerResult result) {
          switch (result) {
              case WIN:
                  return getNewRating(rating, opponentRating, 1.0);
              case LOSS:
                  return getNewRating(rating, opponentRating, 0.0);
              case DRAW:
                  return getNewRating(rating, opponentRating, 0.5);
          }
          return -1;              // no score this time.
      }
  
      /**
       * Get new rating.
       *
       * @param rating         Rating of either the current player or the average of the
       *                       current team.
       * @param opponentRating Rating of either the opponent player or the average of the
       *                       opponent team or teams.
       * @param score          Score: 0=Loss 0.5=Draw 1.0=Win
       * @return the new rating
       */
      private int getNewRating(int rating, int opponentRating, double score) {
          double kFactor = getKFactor(rating);
          double expectedScore = getExpectedScore(rating, opponentRating);
  
          return calculateNewRating(rating, score, expectedScore, kFactor);
      }
  
      /**
       * Calculate the new rating based on the ELO standard formula.
       * newRating = oldRating + constant * (score - expectedScore)
       *
       * @param oldRating     Old Rating
       * @param score         Score
       * @param expectedScore Expected Score
       * @return the new rating of the player
       */
      private int calculateNewRating(int oldRating, double score, double expectedScore, double kFactor) {
          return oldRating + (int) (kFactor * (score - expectedScore));
      }
  
      /**
       * This is the standard chess constant.  This constant can differ
       * based on different games.  The higher the constant the faster
       * the rating will grow.  That is why for this standard chess method,
       * the constant is higher for weaker players and lower for stronger
       * players.
       *
       * @param rating Rating
       * @return Constant
       */
      private double getKFactor(int rating) {
          // Return the correct k factor.
          for (KFactor kFactor : kFactors) {
              if (rating >= kFactor.getStartIndex() && rating <= kFactor.getEndIndex()) {
                  return kFactor.value;
              }
          }
          return 32;
      }
  
      /**
       * Get expected score based on two players.  If more than two players
       * are competing, then opponentRating will be the average of all other
       * opponent's ratings.  If there is two teams against each other, rating
       * and opponentRating will be the average of those players.
       *
       * @param rating         Rating
       * @param opponentRating Opponent(s) rating
       * @return the expected score
       */
      private double getExpectedScore(int rating, int opponentRating) {
          return 1.0 / (1.0 + Math.pow(10.0, ((double) (opponentRating - rating) / 400.0)));
      }
  
      /**
       * Small inner class data structure to describe a KFactor range.
       */
      public class KFactor {
  
          private int startIndex, endIndex;
          private double value;
  
          public KFactor(int startIndex, int endIndex, double value) {
              this.startIndex = startIndex;
              this.endIndex = endIndex;
              this.value = value;
          }
  
          public int getStartIndex() {
              return startIndex;
          }
  
          public int getEndIndex() {
              return endIndex;
          }
  
          public double getValue() {
              return value;
          }
  
          public String toString() {
  