gt:             rawScore += HUGE_NEGATIVE;
context: package controllers.singlePlayer.sampleMCTS;
  
  import java.util.Random;
  
  import core.ArcadeMachine;
  import core.game.StateObservation;
  import ontology.Types;
  import tools.ElapsedCpuTimer;
  import tools.Utils;
  
  public class SingleTreeNode
  {
      private final double HUGE_NEGATIVE = -10000000.0;
      private final double HUGE_POSITIVE =  10000000.0;
      public double epsilon = 1e-6;
      public double egreedyEpsilon = 0.05;
      public StateObservation state;
      public SingleTreeNode parent;
      public SingleTreeNode[] children;
      public double totValue;
      public int nVisits;
      public Random m_rnd;
      private int m_depth;
      protected double[] bounds = new double[]{Double.MAX_VALUE, -Double.MAX_VALUE};
      public int num_actions;
      Types.ACTIONS[] actions;
  
      public int ROLLOUT_DEPTH = 10;
      public double K = Math.sqrt(2);
  
      //public static int totalIters = 0;
  
      public SingleTreeNode(Random rnd, int num_actions, Types.ACTIONS[] actions) {
          this(null, null, rnd, num_actions, actions);
      }
  
      public SingleTreeNode(StateObservation state, SingleTreeNode parent, Random rnd, int num_actions, Types.ACTIONS[] actions) {
          this.state = state;
          this.parent = parent;
          this.num_actions = num_actions;
          this.m_rnd = rnd;
          this.actions = actions;
          children = new SingleTreeNode[num_actions];
          totValue = 0.0;
          if(parent != null)
              m_depth = parent.m_depth+1;
          else
              m_depth = 0;
  
      }
  
  
      public void mctsSearch(ElapsedCpuTimer elapsedTimer) {
  
          double avgTimeTaken = 0;
          double acumTimeTaken = 0;
          long remaining = elapsedTimer.remainingTimeMillis();
          int numIters = 0;
  
          int remainingLimit = 5;
          while(remaining > 2*avgTimeTaken && remaining > remainingLimit){
              ElapsedCpuTimer elapsedTimerIteration = new ElapsedCpuTimer();
              SingleTreeNode selected = treePolicy();
              double delta = selected.rollOut();
              backUp(selected, delta);
  
              numIters++;
              acumTimeTaken += (elapsedTimerIteration.elapsedMillis()) ;
  
              avgTimeTaken  = acumTimeTaken/numIters;
              remaining = elapsedTimer.remainingTimeMillis();
              //System.out.println(elapsedTimerIteration.elapsedMillis() + " --> " + acumTimeTaken + " (" + remaining + ")");
          }
          //System.out.println("-- " + numIters + " -- ( " + avgTimeTaken + ")");
          //totalIters = numIters;
  
          //ArcadeMachine.performance.add(numIters);
      }
  
      public SingleTreeNode treePolicy() {
  
          SingleTreeNode cur = this;
  
          while (!cur.state.isGameOver() && cur.m_depth < ROLLOUT_DEPTH)
          {
              if (cur.notFullyExpanded()) {
                  return cur.expand();
  
              } else {
                  SingleTreeNode next = cur.uct();
                  //SingleTreeNode next = cur.egreedy();
                  cur = next;
              }
          }
  
          return cur;
      }
  
  
      public SingleTreeNode expand() {
  
          int bestAction = 0;
          double bestValue = -1;
  
          for (int i = 0; i < children.length; i++) {
              double x = m_rnd.nextDouble();
              if (x > bestValue && children[i] == null) {
                  bestAction = i;
                  bestValue = x;
              }
          }
  
          StateObservation nextState = state.copy();
          nextState.advance(actions[bestAction]);
  
          SingleTreeNode tn = new SingleTreeNode(nextState, this, this.m_rnd, num_actions, actions);
          children[bestAction] = tn;
          return tn;
  
      }
  
      public SingleTreeNode uct() {
  
          SingleTreeNode selected = null;
          double bestValue = -Double.MAX_VALUE;
          for (SingleTreeNode child : this.children)
          {
              double hvVal = child.totValue;
              double childValue =  hvVal / (child.nVisits + this.epsilon);
  
  
              childValue = Utils.normalise(childValue, bounds[0], bounds[1]);
  
              double uctValue = childValue +
                      K * Math.sqrt(Math.log(this.nVisits + 1) / (child.nVisits + this.epsilon));
  
              // small sampleRandom numbers: break ties in unexpanded nodes
              uctValue = Utils.noise(uctValue, this.epsilon, this.m_rnd.nextDouble());     //break ties randomly
  
              // small sampleRandom numbers: break ties in unexpanded nodes
              if (uctValue > bestValue) {
                  selected = child;
                  bestValue = uctValue;
              }
          }
  
          if (selected == null)
          {
              throw new RuntimeException("Warning! returning null: " + bestValue + " : " + this.children.length);
          }
  
          return selected;
      }
  
      public SingleTreeNode egreedy() {
  
  
          SingleTreeNode selected = null;
  
          if(m_rnd.nextDouble() < egreedyEpsilon)
          {
              //Choose randomly
              int selectedIdx = m_rnd.nextInt(children.length);
              selected = this.children[selectedIdx];
  
          }else{
              //pick the best Q.
              double bestValue = -Double.MAX_VALUE;
              for (SingleTreeNode child : this.children)
              {
                  double hvVal = child.totValue;
                  hvVal = Utils.noise(hvVal, this.epsilon, this.m_rnd.nextDouble());     //break ties randomly
                  // small sampleRandom numbers: break ties in unexpanded nodes
                  if (hvVal > bestValue) {
                      selected = child;
                      bestValue = hvVal;
                  }
              }
  
          }
  
  
          if (selected == null)
          {
              throw new RuntimeException("Warning! returning null: " + this.children.length);
          }
  
          return selected;
      }
  
  
      public double rollOut()
      {
          StateObservation rollerState = state.copy();
          int thisDepth = this.m_depth;
  
          while (!finishRollout(rollerState,thisDepth)) {
  
              int action = m_rnd.nextInt(num_actions);
              rollerState.advance(actions[action]);
              thisDepth++;
          }
  
          double delta = value(rollerState);
  
          if(delta < bounds[0])
              bounds[0] = delta;
  
          if(delta > bounds[1])
              bounds[1] = delta;
  
          return delta;
      }
  
      public double value(StateObservation a_gameState) {
  
          boolean gameOver = a_gameState.isGameOver();
          Types.WINNER win = a_gameState.getGameWinner();
          double rawScore = a_gameState.getGameScore();
  
          if(gameOver && win == Types.WINNER.PLAYER_LOSES)
  