All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ggp.base.util.statemachine.sancho.ForwardDeadReckonPropnetRuleEngine Maven / Gradle / Ivy

The newest version!

package org.ggp.base.util.statemachine.sancho;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;

import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.propnet.factory.sancho.OptimizingPolymorphicPropNetFactory;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonComponent;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonComponentFactory;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonInternalMachineState;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonInternalMachineState.InternalMachineStateIterator;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonLegalMoveInfo;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonLegalMoveSet;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonPropNet;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonPropnetFastAnimator;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonProposition;
import org.ggp.base.util.propnet.sancho.ForwardDeadReckonPropositionInfo;
import org.ggp.base.util.propnet.sancho.LearningComponent;
import org.ggp.base.util.propnet.sancho.PolymorphicAnd;
import org.ggp.base.util.propnet.sancho.PolymorphicComponent;
import org.ggp.base.util.propnet.sancho.PolymorphicConstant;
import org.ggp.base.util.propnet.sancho.PolymorphicNot;
import org.ggp.base.util.propnet.sancho.PolymorphicOr;
import org.ggp.base.util.propnet.sancho.PolymorphicPropNet;
import org.ggp.base.util.propnet.sancho.PolymorphicProposition;
import org.ggp.base.util.propnet.sancho.PolymorphicTransition;
import org.ggp.base.util.ruleengine.GameDescriptionException;
import org.ggp.base.util.ruleengine.RuleEngine;
import org.ggp.base.util.ruleengine.Translator;
import org.ggp.base.util.statemachine.MachineState;
import org.ggp.base.util.statemachine.Move;
import org.ggp.base.util.statemachine.Role;
import org.ggp.base.util.statemachine.StateMachine;
import org.ggp.base.util.statemachine.exceptions.TransitionDefinitionException;
import org.ggp.base.util.statemachine.implementation.prover.query.ProverQueryBuilder;
import org.ggp.base.util.statemachine.sancho.FactorAnalyser.FactorInfo;

import com.google.common.collect.ImmutableMap;

/**
 * A state machine.
 *
 * This class is not thread-safe.  Each instance must be accessed by a single thread.
 */
public class ForwardDeadReckonPropnetRuleEngine implements RuleEngine
{
//  private static final Logger LOGGER = LogManager.getLogger();

  /** The underlying proposition network - in various optimised forms. */

  private final ForwardDeadReckonPropnetRuleEngine                   mMaster;

  // The complete propnet.
  private ForwardDeadReckonPropNet                                     fullPropNet                     = null;

  // A propnet containing just those components required to compute the goal values when it's already known that the
  // state is terminal.
  private ForwardDeadReckonPropNet                                     goalsNet                        = null;
  private boolean                                                      useGoalNetForTerminalAndLegal   = false;
  // A propnet containing just those components required to compute the terminality of a state
  private ForwardDeadReckonPropNet                                     terminalityNet                  = null;

  // The propnet is split into two networks dependent on the proposition which changes most frequently during metagame
  // simulations.  This is commonly a "control" proposition identifying which player's turn it is in a non-simultaneous
  // game.  The X-net contains just those components required when the control proposition is true.  The O-net contains
  // just those components required when the control proposition is false.
  //
  // In games without greedy rollouts, these are set to the goalless version (below).
  private ForwardDeadReckonPropNet                                     propNetX                        = null;
  private ForwardDeadReckonPropNet                                     propNetO                        = null;

  // X- and O-nets without the goal calculation logic (which is in goalsNet).
  private ForwardDeadReckonPropNet                                     propNetXWithoutGoals            = null;
  private ForwardDeadReckonPropNet                                     propNetOWithoutGoals            = null;

  // The propnet that is currently in use (one of the X- or O- nets).
  private ForwardDeadReckonPropNet                                     propNet                         = null;
  private ForwardDeadReckonPropnetFastAnimator.InstanceInfo            propNetInstanceInfo             = null;

  private Map                      legalPropositionsX              = null;
  private Map                                            legalPropositionMovesX          = null;
  private Map                      legalPropositionsO              = null;
  private Map                                            legalPropositionMovesO          = null;
  private Map                      legalPropositions               = null;
  /** The player roles */
  int                                                                  numRoles;
  private Role[]                                                       roles;
  private ForwardDeadReckonInternalMachineState                        lastInternalSetStateX           = null;
  private ForwardDeadReckonInternalMachineState                        lastInternalSetStateO           = null;
  private ForwardDeadReckonInternalMachineState                        lastInternalSetState            = null;
  private final boolean                                                useSampleOfKnownLegals          = false;
  private GdlSentence                                                  XSentence                       = null;
  private ForwardDeadReckonPropositionCrossReferenceInfo               XSentenceInfo                   = null;
  private MachineState                                                 initialState                    = null;
  private ForwardDeadReckonProposition[]                               moveProps                       = null;
  private ForwardDeadReckonProposition[]                               previousMovePropsX              = null;
  private ForwardDeadReckonProposition[]                               previousMovePropsO              = null;
  private boolean                                                      measuringBasePropChanges        = false;
  private Map basePropChangeCounts            = new HashMap<>();
  private ForwardDeadReckonProposition[]                               chosenJointMoveProps            = null;
  private Move[]                                                       chosenMoves                     = null;
  private int[]                                                        previouslyChosenJointMovePropIdsX = null;
  private int[]                                                        previouslyChosenJointMovePropIdsO = null;
  final int[]                                                          latchedScoreRangeBuffer         = new int[2];
  private final int[]                                                  parentLatchedScoreRangeBuffer   = new int[2];
  private ForwardDeadReckonPropositionCrossReferenceInfo[]             masterInfoSet                   = null;
  private int                                                          firstBasePropIndex;
  private ForwardDeadReckonLegalMoveInfo[]                             masterLegalMoveSet              = null;
  private StateMachine                                                 validationMachine               = null;
  private RoleOrdering                                                 roleOrdering                    = null;
  private MachineState                                                 validationState                 = null;
  private int                                                          instanceId;
  private int                                                          maxInstances;
  private long                                                         metagameTimeout                 = 20000;
  private int                                                          numInstances                    = 1;
  private final Role                                                   ourRole;
  private boolean                                                      isPseudoPuzzle                  = false;
  private Set                                                  factors                         = null;
  private StateMachineFilter                                           searchFilter                    = null;
  private ForwardDeadReckonInternalMachineState                        mNonControlMask                 = null;
  private ForwardDeadReckonInternalMachineState                        mControlMask                    = null;
  public long                                                          totalNumGatesPropagated         = 0;
  public long                                                          totalNumPropagates              = 0;
  private Map mPositiveGoalLatches      = null;
  private Map mNegativeGoalLatches      = null;
  private final Map                                        mStaticGoalRanges               = new HashMap<>();
  private Set                                  mPositiveBasePropLatches        = null;
  private Set                                  mNegativeBasePropLatches        = null;
  private final Set                                       mFillerMoves                    = new HashSet<>();
  private GoalsCalculator                                              mGoalsCalculator                = null;
  private IPlayoutPolicy                                               mPlayoutPolicy                  = null;
  private Map              mRoleUnionPositiveGoalLatches   = null;

  private final TerminalResultSet                                      mResultSet                      = new TerminalResultSet();
  // A re-usable iterator over the propositions in a machine state.
  private final InternalMachineStateIterator                           mStateIterator = new InternalMachineStateIterator();
  private final RuntimeGameCharacteristics                             mGameCharacteristics;

  //  In games with negative goal latches greedy rollouts treat state transitions that lower the opponent's
  //  maximum achievable score somewhat like transitions to winning terminal states, which is to say they
  //  preferentially select them, and preferentially avoid the converse of their own max score being
  //  reduced.  The next two parameters govern how strong that preference is (0 = pref -> 100 = always)
  //  Ideally these parameters will have natural values of 0 or 100 corresponding to the mechanism being
  //  turned on or off - anything in between implies another parameter to tune that is highly likely to be
  //  game dependent.  The most natural setting would be (100,100) which would be directly analogous with
  //  the handling of decisive win terminals, however, experimentation with ELB (the canonical game for multi-player
  //  with goal latches) shows that (100,0) works a bit better.  This feels wrong, because it is equivalent to
  //  saying (in ELB) that kings will always be captured when they can be during a rollout (fine), but nothing
  //  will be done to avoid putting a king in a position where it can be immediately captured (seems wrong).
  //  This empirical preference for (100,0) over (100,100) is something I am no entirely comfortable with, but
  //  until a counter example comes along we'll just live with (note (100,100) is still WAY better than before we had
  //  the mechanism at all, so if we had to go to that due to a counter example this is still a big step forward)
  private final int                                                    latchImprovementWeight = 100;
  private final int                                                    latchWorseningAvoidanceWeight = 0;

  //  Stack variables used during playouts that are not exposed to the caller or the policy
  private int[]                                                        playoutStackMoveInitialChoiceIndex = null;
  private int[]                                                        playoutStackMoveNextChoiceIndex = null;

  /**
   * Current turn number within the overall game
   */
  volatile int                                                         mTurnNumber = 0;
  private int                                                          mLastPlayoutTurnNumber = -1;

  private class TestPropnetStateMachineStats extends Stats
  {
    private long totalResets;
    private int  numStateSettings;
    private long totalGets;
    private int  numStateFetches;
    private int  numBaseProps;
    private int  numInputs;
    private int  numLegals;

    public TestPropnetStateMachineStats(int numBaseProps,
                                        int numInputs,
                                        int numLegals)
    {
      this.numBaseProps = numBaseProps;
      this.numInputs = numInputs;
      this.numLegals = numLegals;
    }

    @Override
    public void clear()
    {
      totalResets = 0;
      numStateSettings = 0;
      totalGets = 0;
      numStateFetches = 0;
    }

    @Override
    public String toString()
    {
      StringBuilder sb = new StringBuilder();

      sb.append("#base props: " + numBaseProps);
      sb.append("\n");
      sb.append("#inputs: " + numInputs);
      sb.append("\n");
      sb.append("#legals: " + numLegals);
      sb.append("\n");
      sb.append("#state sets: " + numStateSettings);
      sb.append("\n");
      if (numStateSettings > 0)
      {
        sb.append("Average #components reset per state set: " + totalResets /
                  numStateSettings);
        sb.append("\n");
      }
      sb.append("#state gets: " + numStateFetches);
      sb.append("\n");
      if (numStateFetches > 0)
      {
        sb.append("Average #components queried per state get: " + totalGets /
                  numStateFetches);
        sb.append("\n");
      }

      return sb.toString();
    }
  }

  public class MoveWeights
  {
    public double[]  weightScore;
    private int      numSamples = 1;
    private double   total      = 0;
    private int      weightSize;
    private double[] averageScores;

    public MoveWeights(int vectorSize, int numRoles)
    {
      weightSize = vectorSize;
      weightScore = new double[vectorSize];
      averageScores = new double[numRoles];
      clear();
    }

    public void clear()
    {
      total = weightSize * 50;
      numSamples = 1;

      for (int i = 0; i < weightSize; i++)
      {
        weightScore[i] = 50;
      }
    }

    public void setWeight(int moveIndex, double weight)
    {
        weightScore[moveIndex] = weight;
    }

    public MoveWeights copy()
    {
      MoveWeights result = new MoveWeights(weightSize, averageScores.length);

      for (int i = 0; i < weightScore.length; i++)
      {
        result.weightScore[i] = weightScore[i];
      }

      result.numSamples = numSamples;
      result.total = total;

      return result;
    }

    public void addSample(double[] scores,
                          List moves)
    {
      for (ForwardDeadReckonLegalMoveInfo move : moves)
      {
        double score = scores[move.mRoleIndex];

        double oldWeight = weightScore[move.mMasterIndex];
        double newWeigth = (oldWeight * numSamples + score) / (numSamples + 1);
        weightScore[move.mMasterIndex] = newWeigth;

        total += (newWeigth - oldWeight);
      }
      numSamples++;
    }

    public void addResult(double[] scores, ForwardDeadReckonLegalMoveInfo move)
    {
      double score = scores[move.mRoleIndex];

      double oldWeight = weightScore[move.mMasterIndex];
      double newWeigth = (oldWeight * numSamples + score) / (numSamples + 1);
      weightScore[move.mMasterIndex] = newWeigth;

      total += (newWeigth - oldWeight);
    }

    public void noteSampleComplete()
    {
      numSamples++;
    }

    public void accumulate(MoveWeights other)
    {
      total = 0;

      for (int i = 0; i < weightSize; i++)
      {
        weightScore[i] = (weightScore[i] * numSamples + other.weightScore[i] *
                                                        other.numSamples) /
                         (numSamples + other.numSamples);
        total += weightScore[i];
      }

      numSamples += other.numSamples;
    }

    public double getAverage()
    {
      return total / weightSize;
    }

    public double getStdDeviation()
    {
      double var = 0;
      double mean = total / weightSize;

      for (int i = 0; i < weightSize; i++)
      {
        var += (weightScore[i] - mean) * (weightScore[i] - mean);
      }

      return Math.sqrt(var / weightSize);
    }
  }

  /**
   * @author steve
   *  Info for a single playout (config and results)
   */
  public class PlayoutInfo
  {
    //  Config params to control the playout

    public boolean      recordTrace;
    public boolean      recordTraceStates;
    /**
     * Max depth, after which cutoff and return virtual draw
     */
    public int          cutoffDepth;
    /**
     * Move weights to apply (may be null for random)
     */
    public MoveWeights  moveWeights;
    /**
     * Factor (if any) this playout is in
     */
    public Factor       factor;

    //  Result info from playout

    /**
     * Number of moves played in this playout
     */
    public int          playoutLength;
    /**
     * Average number of move choices available during this playout
     */
    public int          averageBranchingFactor;
    /**
     * Moves played - array must be instantiated by caller and be sufficient to the max cutoff depth
     * May be null if no recorded trace is required
     */
    public final ForwardDeadReckonLegalMoveInfo[] playoutTrace;
    public final ForwardDeadReckonInternalMachineState[] statesVisited;

    public PlayoutInfo(int maxDepth)
    {
      if ( maxDepth <= 0 )
      {
        recordTrace = false;
        recordTraceStates = false;
        playoutTrace = null;
        statesVisited = null;
      }
      else
      {
        playoutTrace = new ForwardDeadReckonLegalMoveInfo[maxDepth];
        statesVisited = new ForwardDeadReckonInternalMachineState[maxDepth];
        for(int i = 0; i < maxDepth; i++)
        {
          statesVisited[i] = createEmptyInternalState();
        }
      }
    }
  }

  private TestPropnetStateMachineStats stats;

  public Stats getStats()
  {
    return stats;
  }

  public ForwardDeadReckonInternalMachineState createEmptyInternalState()
  {
    return new ForwardDeadReckonInternalMachineState(masterInfoSet, firstBasePropIndex, translator);
  }

  public ForwardDeadReckonInternalMachineState createInternalState(MachineState state)
  {
    ForwardDeadReckonInternalMachineState result = createEmptyInternalState();

    for (GdlSentence s : state.getContents())
    {
      ForwardDeadReckonProposition p = (ForwardDeadReckonProposition)propNet.getBasePropositions().get(s);
      if ( p != null )
      {
        ForwardDeadReckonPropositionInfo info = p.getInfo();
        result.add(info);

        result.isXState |= (info.sentence == XSentence);
      }
    }

//    LOGGER.trace("Created internal state: " + result + " with hash " + result.hashCode());
    return result;
  }

  public ForwardDeadReckonPropositionCrossReferenceInfo[] getInfoSet()
  {
    return masterInfoSet;
  }

  /**
   * @return the master set of legal moves - i.e. information about all moves that could ever be legal.
   */
  public ForwardDeadReckonLegalMoveInfo[] getMasterLegalMoves()
  {
    return masterLegalMoveSet;
  }

  public RoleOrdering getRoleOrdering()
  {
    return roleOrdering;
  }

  private void setRoleOrdering(RoleOrdering xiRoleOrdering)
  {
    roleOrdering = xiRoleOrdering;
  }

  /**
   * @return whether the game may be treated as a puzzle (has no
   * dependence on any other role's moves)
   */
  public boolean getIsPseudoPuzzle()
  {
    return isPseudoPuzzle;
  }

  public void performSemanticAnalysis(long timeout)
  {
    findLatches(timeout);

    if (factors == null)
    {
      PartitionedChoiceAnalyser analyzer = new PartitionedChoiceAnalyser(this);
      //  If it did not factorize does it partition? (currently we do not support both
      //  at once).  Note that this analysis must be done after the propnet is crystallized
      StateMachineFilter partitionFilter = analyzer.generatePartitionedChoiceFilter();

      if (partitionFilter != null)
      {
        setBaseFilter(partitionFilter);
      }
    }
  }

  /**
   * Find latches.
   */
  // !! ARR Work in progress - will need to return something
  private void findLatches(long timeout)
  {
    // As a quick win for now, we'll keep a simple record of any propositions which latch a goal proposition (either
    // positively or negatively).
    mPositiveGoalLatches = new HashMap<>();
    mNegativeGoalLatches = new HashMap<>();
    mPositiveBasePropLatches = new HashSet<>();
    mNegativeBasePropLatches = new HashSet<>();

    for (PolymorphicProposition lGoals[] : fullPropNet.getGoalPropositions().values())
    {
      for (PolymorphicProposition lGoal : lGoals)
      {
        mPositiveGoalLatches.put(lGoal, createEmptyInternalState());
        mNegativeGoalLatches.put(lGoal, createEmptyInternalState());
      }
    }

    for (PolymorphicProposition lBaseProp : fullPropNet.getBasePropositionsArray())
    {
      if ( System.currentTimeMillis() > timeout )
      {
        mPositiveGoalLatches = null;
        mNegativeGoalLatches = null;
        return;
      }

      if (lBaseProp.getSingleInput() instanceof PolymorphicTransition)
      {
        // Assume that this base proposition is a positive latch and look for the consequences.
        Set lPositivelyLatched = new HashSet<>();
        Set lNegativelyLatched = new HashSet<>();
        findAllLatchedStatesFor(lBaseProp,
                                true,
                                (ForwardDeadReckonProposition)lBaseProp,
                                lPositivelyLatched,
                                lNegativelyLatched,
                                0);
        if (lPositivelyLatched.contains(lBaseProp))
        {
          mPositiveBasePropLatches.add(lBaseProp);
//          LOGGER.debug("Latch(+ve): " + lBaseProp);

          // If we've just latched any goal props, remember it.
          for (Entry lEntry :
                                                                                        mPositiveGoalLatches.entrySet())
          {
            PolymorphicProposition lGoal = lEntry.getKey();
            if (lPositivelyLatched.contains(lGoal))
            {
              lEntry.getValue().add(((ForwardDeadReckonProposition)lBaseProp).getInfo());
            }
          }

          for (Entry lEntry :
                                                                                        mNegativeGoalLatches.entrySet())
          {
            PolymorphicProposition lGoal = lEntry.getKey();
            if (lNegativelyLatched.contains(lGoal))
            {
              lEntry.getValue().add(((ForwardDeadReckonProposition)lBaseProp).getInfo());
            }
          }
        }

        // Assume that this base proposition is a negative latch and look for the consequences.
        lPositivelyLatched = new HashSet<>();
        lNegativelyLatched = new HashSet<>();
        findAllLatchedStatesFor(lBaseProp,
                                false,
                                (ForwardDeadReckonProposition)lBaseProp,
                                lPositivelyLatched,
                                lNegativelyLatched,
                                0);
        if (lNegativelyLatched.contains(lBaseProp))
        {
          mNegativeBasePropLatches.add(lBaseProp);
//          LOGGER.debug("Latch(-ve): " + lBaseProp);
        }
      }
    }

    // Post-process the goal latches to remove any goals for which no latches were found.
    Iterator> lIterator =
                                                                             mPositiveGoalLatches.entrySet().iterator();
    while (lIterator.hasNext())
    {
      Entry lEntry = lIterator.next();
      PolymorphicProposition lGoal = lEntry.getKey();
      ForwardDeadReckonInternalMachineState lLatches = lEntry.getValue();

      if (lLatches.size() != 0)
      {
//        LOGGER.info("Goal '" + lGoal + "' is positively latched by any of: " + lLatches);
      }
      else
      {
        lIterator.remove();
      }
    }

    if (mPositiveGoalLatches.isEmpty())
    {
//      LOGGER.info("No positive goal latches");
      mPositiveGoalLatches = null;
    }

    lIterator = mNegativeGoalLatches.entrySet().iterator();
    while (lIterator.hasNext())
    {
      Entry lEntry = lIterator.next();
      PolymorphicProposition lGoal = lEntry.getKey();
      ForwardDeadReckonInternalMachineState lLatches = lEntry.getValue();

      if (lLatches.size() != 0)
      {
//        LOGGER.info("Goal '" + lGoal + "' is negatively latched by any of: " + lLatches);
      }
      else
      {
        lIterator.remove();
      }
    }

    if (mNegativeGoalLatches.isEmpty())
    {
//      LOGGER.info("No negative goal latches");
      mNegativeGoalLatches = null;
    }
  }

  private void findAllLatchedStatesFor(PolymorphicComponent xiComponent,
                                       boolean xiForcedOutputValue,
                                       ForwardDeadReckonProposition xiOriginal,
                                       Set xiPositivelyLatched,
                                       Set xiNegativelyLatched,
                                       int xiDepth)
  {
    // Check if we've already visited this component.  (This is expected, because we do latching through transitions.)
    if ((xiPositivelyLatched.contains(xiComponent)) || (xiNegativelyLatched.contains(xiComponent)))
    {
      return;
    }

    // Record the forced value of this component.
    //
    // If this is the original component for which we're trying to determine whether or not it's latched, only consider
    // it as latched if it's latched in the next turn.  Don't consider it to be latched at depth 0 (that will always be
    // the case - this system works by setting it true in turn 0 and seeing what happens next).  Don't consider it to be
    // a latch if it doesn't happen until the turn after the next turn.  That would, for example, consider a control
    // prop to be latched in a 2-player game.
    //
    // We're basically doing a proof by induction.  We need to show that, if a base prop is true (or false) at depth n
    // then it will be true (or false) at depth n+1.  That pretty much sums up a latch.
    assert(Collections.disjoint(xiPositivelyLatched, xiNegativelyLatched));
    boolean lConsiderAsLatched = ((xiComponent != xiOriginal) || (xiDepth == 1));
    if (lConsiderAsLatched)
    {
      if (xiForcedOutputValue)
      {
        xiPositivelyLatched.add(xiComponent);
      }
      else
      {
        xiNegativelyLatched.add(xiComponent);
      }
    }
    assert(Collections.disjoint(xiPositivelyLatched, xiNegativelyLatched));

    // Check which downstream components are latched as a result.
    for (PolymorphicComponent lComp : xiComponent.getOutputs())
    {
      if (lComp instanceof PolymorphicProposition)
      {
        findAllLatchedStatesFor(lComp,
                                xiForcedOutputValue,
                                xiOriginal,
                                xiPositivelyLatched,
                                xiNegativelyLatched,
                                xiDepth);

        // If we've just negatively latched a LEGAL prop, also negatively latch the corresponding DOES prop.
        if (!xiForcedOutputValue)
        {
          // Find the matching proposition in the legal/input map.  (If we don't have a LEGAL/DOES prop in hand then we
          // won't find anything.  Also, it can't be a DOES prop in hand because they can't ever have logic leading to
          // them.  So, if we do find a match, it's the DOES prop corresponding to the LEGAL prop in hand.)
          PolymorphicProposition lDoesProp = fullPropNet.getLegalInputMap().get(lComp);
          if (lDoesProp != null)
          {
            findAllLatchedStatesFor(lComp,
                                    xiForcedOutputValue,
                                    xiOriginal,
                                    xiPositivelyLatched,
                                    xiNegativelyLatched,
                                    xiDepth + 1);
          }
        }
      }
      else if (lComp instanceof PolymorphicOr)
      {
        boolean transmitsLatchInput = true;

        if ( !xiForcedOutputValue )
        {
          //  Handle one common special-case - where an OR is with the Init proposition, which
          //  can be assumed to be false for any next state calculation and thus for latch analysis
          for(PolymorphicComponent c : lComp.getInputs())
          {
            if ( c != xiComponent && c != fullPropNet.getInitProposition() )
            {
              transmitsLatchInput = false;
              break;
            }
          }
        }
        if ( transmitsLatchInput )
        {
          // This OR gate never has true inputs other than the one being considered, and
          //  so passes the latch value through
          findAllLatchedStatesFor(lComp,
                                  xiForcedOutputValue,
                                  xiOriginal,
                                  xiPositivelyLatched,
                                  xiNegativelyLatched,
                                  xiDepth);
        }
      }
      //  Note - no need to special-case Init for the negatively latch AND case since Init always
      //  manifests via an OR
      else if ((lComp instanceof PolymorphicAnd) && (!xiForcedOutputValue))
      {
        // This AND gate will always have a false input, therefore the output will always be false.
        findAllLatchedStatesFor(lComp,
                                xiForcedOutputValue,
                                xiOriginal,
                                xiPositivelyLatched,
                                xiNegativelyLatched,
                                xiDepth);
      }
      else if (lComp instanceof PolymorphicNot)
      {
        findAllLatchedStatesFor(lComp,
                                !xiForcedOutputValue,
                                xiOriginal,
                                xiPositivelyLatched,
                                xiNegativelyLatched,
                                xiDepth);
      }
      else if (lComp instanceof PolymorphicTransition)
      {
        findAllLatchedStatesFor(lComp,
                                xiForcedOutputValue,
                                xiOriginal,
                                xiPositivelyLatched,
                                xiNegativelyLatched,
                                xiDepth + 1);
      }
    }
  }

  /**
   * @return the latched score for the specified state, or null if there isn't one.
   *
   * @param xiState - the state.
   *
   * !! ARR 1P latches?
   */
//  public Integer getLatchedScore(ForwardDeadReckonInternalMachineState xiState)
//  {
//    if (mPositiveGoalLatches != null)
//    {
//      for (Entry lEntry : mPositiveGoalLatches.entrySet())
//      {
//        if (xiState.intersects(lEntry.getValue()))
//        {
//          return Integer.parseInt(lEntry.getKey().getName().getBody().get(1).toString());
//        }
//      }
//    }
//
//    return null;
//  }

  /**
   * @param xiState - state to test for latched score in
   * @return true if all roles' scores are latched
   */
  public boolean scoresAreLatched(ForwardDeadReckonInternalMachineState xiState)
  {
    if ( mGoalsCalculator != null )
    {
      if ( mGoalsCalculator.scoresAreLatched(xiState) )
      {
        return true;
      }
    }

    if (mPositiveGoalLatches != null)
    {
      if ( mRoleUnionPositiveGoalLatches == null )
      {
        //  Allocate a working buffer for future use and calculate the role masks that imply
        //  some positively latched goal for the role
        mRoleUnionPositiveGoalLatches = new HashMap<>();

        for(Role role : getRoles())
        {
          ForwardDeadReckonInternalMachineState roleLatchMask = createEmptyInternalState();

          for(PolymorphicProposition goalProp : fullPropNet.getGoalPropositions().get(role))
          {
            ForwardDeadReckonInternalMachineState goalMask = mPositiveGoalLatches.get(goalProp);

            if ( goalMask != null )
            {
              roleLatchMask.merge(goalMask);
            }
          }

          if ( roleLatchMask.size() > 0 )
          {
            mRoleUnionPositiveGoalLatches.put(role, roleLatchMask);
          }
        }
      }

      boolean result = true;

      for(Role role : getRoles())
      {
        ForwardDeadReckonInternalMachineState roleLatchMask = mRoleUnionPositiveGoalLatches.get(role);
        if ( roleLatchMask != null )
        {
          if ( !xiState.intersects(roleLatchMask) )
          {
            result = false;
            break;
          }
        }
        else
        {
          result = false;
          break;
        }
      }

      return result;
    }

    return false;
  }

  /**
   * Get the latched range of possible scores for a given role in a given state
   * @param xiState - the state
   * @param role - the role
   * @param range - array of length 2 to contain [min,max]
   */
  public void getLatchedScoreRange(ForwardDeadReckonInternalMachineState xiState, Role role, int[] range)
  {
    assert(range.length == 2);

    if ( mGoalsCalculator != null )
    {
      if ( mGoalsCalculator.scoresAreLatched(xiState))
      {
        range[0] = mGoalsCalculator.getGoalValue(xiState, role);
        range[1] = range[0];
        return;
      }
    }

    //  Initialize to sentinel values
    range[0] = Integer.MAX_VALUE;
    range[1] = -Integer.MAX_VALUE;
    int[] staticGoalRange = null;

    if ( mPositiveGoalLatches != null || mNegativeGoalLatches != null || (staticGoalRange = mStaticGoalRanges.get(role)) == null )
    {
      //  Initialize to sentinel values
      range[0] = Integer.MAX_VALUE;
      range[1] = -Integer.MAX_VALUE;

      for(PolymorphicProposition goalProp : fullPropNet.getGoalPropositions().get(role))
      {
        ForwardDeadReckonInternalMachineState negativeMask = null;
        int latchedScore = Integer.parseInt(goalProp.getName().getBody().get(1).toString());

        if ( mPositiveGoalLatches != null )
        {
          ForwardDeadReckonInternalMachineState positiveMask = mPositiveGoalLatches.get(goalProp);
          if (positiveMask != null && xiState.intersects(positiveMask))
          {
            range[0] = latchedScore;
            range[1] = latchedScore;
            break;
          }
        }
        if ( mNegativeGoalLatches != null )
        {
          negativeMask = mNegativeGoalLatches.get(goalProp);
        }
        if ( negativeMask == null || !xiState.intersects(negativeMask))
        {
          //  This is still a possible score
          if ( latchedScore < range[0] )
          {
            range[0] = latchedScore;
          }
          if ( latchedScore > range[1] )
          {
            range[1] = latchedScore;
          }
        }
      }

      if ( mPositiveGoalLatches == null && mNegativeGoalLatches == null )
      {
        staticGoalRange = new int[2];

        staticGoalRange[0] = range[0];
        staticGoalRange[1] = range[1];

        mStaticGoalRanges.put(role, staticGoalRange);
      }
    }
    else
    {
      range[0] = staticGoalRange[0];
      range[1] = staticGoalRange[1];
    }
  }

  /**
   * @return whether there are any negative goal latches.
   */
  public boolean hasNegativelyLatchedGoals()
  {
    return (mNegativeGoalLatches != null);
  }

  /**
   * @return whether there are any positive goal latches.
   */
  public boolean hasPositivelyLatchedGoals()
  {
    return (mPositiveGoalLatches != null);
  }

  /**
   * Query whether a specified proposition is known to be a positively latched base prop
   * @param p - proposition to check
   * @return true if it latches to true
   */
  public boolean isPositivelyLatchedBaseProp(PolymorphicProposition p)
  {
    return (mPositiveBasePropLatches != null && mPositiveBasePropLatches.contains(p));
  }

  /**
   * Get a mask of all positively latched base props
   * @return
   */
  public ForwardDeadReckonInternalMachineState getPositiveBaseLatches()
  {
    if ( mPositiveBasePropLatches == null )
    {
      return null;
    }

    ForwardDeadReckonInternalMachineState result = createEmptyInternalState();

    for(PolymorphicProposition prop : mPositiveBasePropLatches)
    {
      result.add(((ForwardDeadReckonProposition)prop).getInfo());
    }

    return result;
  }

  /**
   * Get a mask of all negatively latched base props
   * @return
   */
  public ForwardDeadReckonInternalMachineState getNegativeBaseLatches()
  {
    if ( mNegativeBasePropLatches == null )
    {
      return null;
    }

    ForwardDeadReckonInternalMachineState result = createEmptyInternalState();

    for(PolymorphicProposition prop : mNegativeBasePropLatches)
    {
      result.add(((ForwardDeadReckonProposition)prop).getInfo());
    }

    return result;
  }

  /**
   * Query whether a specified proposition is known to be a negatively latched base prop
   * @param p - proposition to check
   * @return true if it latches to false
   */
  public boolean isNegativelyLatchedBaseProp(PolymorphicProposition p)
  {
    return (mNegativeBasePropLatches != null && mNegativeBasePropLatches.contains(p));
  }

  /**
   * Get the average of all goals that aren't negatively latched, for each role, in the specified state.
   *
   * @param xiState  - the state.
   * @param xoValues - output array of values (one for each role).
   */
  public void getAverageAvailableGoals(ForwardDeadReckonInternalMachineState xiState,
                                       RoleOrdering xiRoleOrdering,
                                       double[] xoValues)
  {
    int[] lNumGoalValues = new int[xoValues.length];

    // By default, all goals are available.
    // !! ARR The output of this could be pre-computed.
    for (Entry lEntry : fullPropNet.getGoalPropositions().entrySet())
    {
      int lRoleIndex = xiRoleOrdering.roleToRoleIndex(lEntry.getKey());
      for (PolymorphicProposition lProp : lEntry.getValue())
      {
        ForwardDeadReckonProposition lGoalProp = (ForwardDeadReckonProposition)lProp;
        xoValues[lRoleIndex] += lGoalProp.getGoalValue();
        lNumGoalValues[lRoleIndex]++;
      }
    }

    if (mNegativeGoalLatches != null)
    {
      for (Entry lEntry : mNegativeGoalLatches.entrySet())
      {
        // Check if this goal is negatively latched.
        if (xiState.intersects(lEntry.getValue()))
        {
          // This goal is no longer available.
          ForwardDeadReckonProposition lGoalProp = (ForwardDeadReckonProposition)lEntry.getKey();
//          LOGGER.debug("Goal not available: " + lGoalProp);
          int lRoleIndex = xiRoleOrdering.roleToRoleIndex(lGoalProp.getGoalRole());
          xoValues[lRoleIndex] -= lGoalProp.getGoalValue();
          lNumGoalValues[lRoleIndex]--;
        }
      }
    }

    for (int lii = 0; lii < xoValues.length; lii++)
    {
      assert(lNumGoalValues[lii] > 0) : "No goals remaining for " + xiRoleOrdering.roleIndexToRole(lii) +
                                        " in state " + xiState;
      xoValues[lii] /= lNumGoalValues[lii];
    }
  }

  /**
   * @return instance id of this instance
   */
  public int getInstanceId()
  {
    return instanceId;
  }

  public Set findTerminalStates(int maxResultSet, int maxDepth)
  {
    PolymorphicProposition terminal = fullPropNet.getTerminalProposition();

    return findSupportStates(terminal.getName(), maxResultSet, maxDepth);
  }

  public Set findGoalStates(Role role,
                                          int minValue,
                                          int maxResultSet,
                                          int maxDepth)
  {
    Set results = new HashSet<>();

    for (PolymorphicProposition p : fullPropNet.getGoalPropositions().get(role))
    {
      if (Integer.parseInt(p.getName().getBody().get(1).toString()) >= minValue)
      {
        results.addAll(findSupportStates(p.getName(), maxResultSet, maxDepth));
      }
    }

    return results;
  }

  private class AntecedantCursor
  {
    public Set positiveProps;
    public Set negativeProps;
    public boolean                     isPositive;

    public AntecedantCursor()
    {
      positiveProps = new HashSet<>();
      negativeProps = new HashSet<>();
      isPositive = true;
    }

    public AntecedantCursor(AntecedantCursor parent)
    {
      positiveProps = new HashSet<>(parent.positiveProps);
      negativeProps = new HashSet<>(parent.negativeProps);
      isPositive = parent.isPositive;
    }

    public boolean compatibleWith(AntecedantCursor other)
    {
      if (isPositive == other.isPositive)
      {
        for (PolymorphicProposition p : positiveProps)
        {
          if (other.negativeProps.contains(p))
          {
            return false;
          }
        }
        for (PolymorphicProposition p : other.positiveProps)
        {
          if (negativeProps.contains(p))
          {
            return false;
          }
        }
        for (PolymorphicProposition p : negativeProps)
        {
          if (other.positiveProps.contains(p))
          {
            return false;
          }
        }
        for (PolymorphicProposition p : other.negativeProps)
        {
          if (positiveProps.contains(p))
          {
            return false;
          }
        }
      }
      else
      {
        for (PolymorphicProposition p : positiveProps)
        {
          if (other.positiveProps.contains(p))
          {
            return false;
          }
        }
        for (PolymorphicProposition p : other.positiveProps)
        {
          if (positiveProps.contains(p))
          {
            return false;
          }
        }
        for (PolymorphicProposition p : negativeProps)
        {
          if (other.negativeProps.contains(p))
          {
            return false;
          }
        }
        for (PolymorphicProposition p : other.negativeProps)
        {
          if (negativeProps.contains(p))
          {
            return false;
          }
        }
      }

      return true;
    }

    public boolean compatibleWithAll(Set set)
    {
      for (AntecedantCursor c : set)
      {
        if (!compatibleWith(c))
        {
          return false;
        }
      }

      return true;
    }

    public void unionInto(AntecedantCursor c)
    {
      if (c.isPositive == isPositive)
      {
        c.positiveProps.addAll(positiveProps);
        c.negativeProps.addAll(negativeProps);
      }
      else
      {
        c.positiveProps.addAll(negativeProps);
        c.negativeProps.addAll(positiveProps);
      }
    }

    public void unionInto(Set set)
    {
      if (set.isEmpty())
      {
        set.add(this);
      }
      else
      {
        for (AntecedantCursor c : set)
        {
          unionInto(c);
        }
      }
    }
  }

  public void validateStateEquality(ForwardDeadReckonPropnetRuleEngine other)
  {
    if (!lastInternalSetState.equals(other.lastInternalSetState))
    {
//      LOGGER.warn("Last set state mismtch");
    }

    for (PolymorphicProposition p : propNet.getBasePropositionsArray())
    {
      ForwardDeadReckonProposition fdrp = (ForwardDeadReckonProposition)p;

      if (fdrp.getValue(instanceId) != fdrp.getValue(other.instanceId))
      {
//        LOGGER.warn("Base prop state mismatch on: " + p);
      }
    }
  }

  private Set addPropositionAntecedants(PolymorphicPropNet pn,
                                                          PolymorphicComponent p,
                                                          AntecedantCursor cursor,
                                                          int maxResultSet,
                                                          int maxDepth,
                                                          int depth)
  {
    if (depth >= maxDepth)
    {
      return null;
    }

    if (p instanceof PolymorphicTransition)
    {
      return null;
    }
    else if (p instanceof PolymorphicProposition)
    {
      PolymorphicProposition prop = (PolymorphicProposition)p;

      if (pn.getBasePropositions().values().contains(prop))
      {
        AntecedantCursor newCursor = new AntecedantCursor(cursor);

        if (cursor.isPositive)
        {
          if (!cursor.negativeProps.contains(p))
          {
            newCursor.positiveProps.add(prop);
            Set result = new HashSet<>();
            result.add(newCursor);
            return result;
          }
          else if (!cursor.positiveProps.contains(p))
          {
            newCursor.negativeProps.add(prop);
            Set result = new HashSet<>();
            result.add(newCursor);
            return result;
          }
          else
          {
            return null;
          }
        }
        if (!cursor.positiveProps.contains(p))
        {
          newCursor.negativeProps.add(prop);
          Set result = new HashSet<>();
          result.add(newCursor);
          return result;
        }
        else if (!cursor.negativeProps.contains(p))
        {
          newCursor.positiveProps.add(prop);
          Set result = new HashSet<>();
          result.add(newCursor);
          return result;
        }
        else
        {
          return null;
        }
      }

      return addPropositionAntecedants(pn,
                                       p.getSingleInput(),
                                       cursor,
                                       maxResultSet,
                                       maxDepth,
                                       depth + 1);
    }
    else if (p instanceof PolymorphicConstant)
    {
      if ( p.getValue() == cursor.isPositive )
      {
        Set result = new HashSet<>();
        result.add(cursor);
        return result;
      }

      return null;
    }
    else if (p instanceof PolymorphicNot)
    {
      cursor.isPositive = !cursor.isPositive;
      Set result = addPropositionAntecedants(pn,
                                                               p.getSingleInput(),
                                                               cursor,
                                                               maxResultSet,
                                                               maxDepth,
                                                               depth + 1);
      cursor.isPositive = !cursor.isPositive;

      return result;
    }
    else if (p instanceof PolymorphicAnd)
    {
      Set subResults = new HashSet<>();

      for (PolymorphicComponent c : p.getInputs())
      {
        if (subResults.size() > maxResultSet)
        {
          return null;
        }

        AntecedantCursor newCursor = new AntecedantCursor(cursor);
        Set inputResults = addPropositionAntecedants(pn,
                                                                       c,
                                                                       newCursor,
                                                                       maxResultSet,
                                                                       maxDepth,
                                                                       depth + 1);
        if (inputResults == null)
        {
          //	No positive matches in an AND that requires a positive result => failure
          if (cursor.isPositive)
          {
            return null;
          }
        }
        else
        {
          if (cursor.isPositive)
          {
            //	We require ALL inputs, so take the conditions gathered for this one and validate
            //	consistency with the current cursor, then add them into that condition set
            if (subResults.isEmpty())
            {
              subResults = inputResults;
            }
            else
            {
              Set validInputResults = new HashSet<>();

              for (AntecedantCursor cur : inputResults)
              {
                for (AntecedantCursor subResult : subResults)
                {
                  if (subResult.compatibleWith(cur))
                  {
                    AntecedantCursor combinedResult = new AntecedantCursor(subResult);
                    cur.unionInto(combinedResult);

                    validInputResults.add(combinedResult);
                  }
                }
              }

              subResults = validInputResults;
            }
          }
          else
          {
            //	This is a OR when viewed in the negative sense, so we just need one, and each such
            //	match is a new results set
            subResults.addAll(inputResults);
          }
        }
      }

      return subResults;
    }
    else if (p instanceof PolymorphicOr)
    {
      Set subResults = new HashSet<>();

      for (PolymorphicComponent c : p.getInputs())
      {
        if (subResults.size() > maxResultSet)
        {
          return null;
        }

        AntecedantCursor newCursor = new AntecedantCursor(cursor);
        Set inputResults = addPropositionAntecedants(pn,
                                                                       c,
                                                                       newCursor,
                                                                       maxResultSet,
                                                                       maxDepth,
                                                                       depth + 1);
        if (inputResults == null)
        {
          //	Any positive matches in an OR that requires a negative result => failure
          if (!cursor.isPositive)
          {
            return null;
          }
        }
        else
        {
          if (!cursor.isPositive)
          {
            //	We require ALL inputs to be negative, so take the conditions gathered for this one and validate
            //	consistency with the current cursor, then add them into that condition set
            if (subResults.isEmpty())
            {
              subResults = inputResults;
            }
            else
            {
              Set validInputResults = new HashSet<>();

              for (AntecedantCursor cur : inputResults)
              {
                for (AntecedantCursor subResult : subResults)
                {
                  if (subResult.compatibleWith(cur))
                  {
                    AntecedantCursor combinedResult = new AntecedantCursor(subResult);
                    cur.unionInto(combinedResult);

                    validInputResults.add(combinedResult);
                  }
                }
              }

              subResults = validInputResults;
            }
          }
          else
          {
            //	Any positive will do, and each such
            //	match is a new results set
            subResults.addAll(inputResults);
          }
        }
      }

      return subResults;
    }

    throw new RuntimeException("Unknown component");
  }

  public Set findSupportStates(GdlSentence queryProposition,
                                             int maxResultSet,
                                             int maxDepth)
  {
    Set result = new HashSet<>();

    PolymorphicProposition p = fullPropNet.findProposition(queryProposition);
    if (p != null)
    {
      Set cursorSet = addPropositionAntecedants(fullPropNet,
                                                                  p,
                                                                  new AntecedantCursor(),
                                                                  maxResultSet,
                                                                  maxDepth,
                                                                  0);

      if ( cursorSet != null )
      {
        for (AntecedantCursor c : cursorSet)
        {
          MachineState satisfyingState = new MachineState(new HashSet());

          for (PolymorphicProposition prop : c.positiveProps)
          {
            satisfyingState.getContents().add(prop.getName());
          }

          result.add(satisfyingState);
        }
      }
    }

    return result;
  }

  public ForwardDeadReckonPropnetRuleEngine()
  {
    maxInstances = 1;
    ourRole = null;
    mGameCharacteristics = null;
    mMaster = this;
  }

  public ForwardDeadReckonPropnetRuleEngine(int xiMaxInstances,
                                              long xiMetaGameTimeout,
                                              Role xiOurRole,
                                              RuntimeGameCharacteristics xiGameCharacteristics)
  {
    maxInstances = xiMaxInstances;
    metagameTimeout = xiMetaGameTimeout;
    ourRole = xiOurRole;
    mGameCharacteristics = xiGameCharacteristics;
    mMaster = this;
  }

  private ForwardDeadReckonPropnetRuleEngine(ForwardDeadReckonPropnetRuleEngine master, int instanceId)
  {
    mMaster = master;
    maxInstances = -1;
    this.instanceId = instanceId;
    propNetX = master.propNetX;
    propNetO = master.propNetO;
    propNetXWithoutGoals = master.propNetXWithoutGoals;
    propNetOWithoutGoals = master.propNetOWithoutGoals;
    enableGreedyRollouts = master.enableGreedyRollouts;
    goalsNet = master.goalsNet;
    terminalityNet = master.terminalityNet;
    XSentence = master.XSentence;
    XSentenceInfo = master.XSentenceInfo;
    legalPropositionMovesX = master.legalPropositionMovesX;
    legalPropositionMovesO = master.legalPropositionMovesO;
    legalPropositionsX = master.legalPropositionsX;
    legalPropositionsO = master.legalPropositionsO;
    legalPropositions = master.legalPropositions;
    initialState = master.initialState;
    firstBasePropIndex = master.firstBasePropIndex;
    roles = master.roles;
    numRoles = master.numRoles;
    fullPropNet = master.fullPropNet;
    masterInfoSet = master.masterInfoSet;
    factors = master.factors;
    mPositiveGoalLatches = master.mPositiveGoalLatches;
    mNegativeGoalLatches = master.mNegativeGoalLatches;
    ourRole = master.ourRole;
    setRoleOrdering(master.getRoleOrdering());
    totalNumMoves = master.totalNumMoves;
    if ( master.mGoalsCalculator != null )
    {
      mGoalsCalculator = master.mGoalsCalculator.createThreadSafeReference();
    }
    mRoleUnionPositiveGoalLatches = master.mRoleUnionPositiveGoalLatches;
    mGameCharacteristics = master.mGameCharacteristics;
    mControlMask = master.mControlMask;
    mNonControlMask = master.mNonControlMask;
    removeOldBasePropsBeforeAddingNew = master.removeOldBasePropsBeforeAddingNew;
    use2passBasePropSet = master.use2passBasePropSet;
    mPlayoutPolicy = (master.mPlayoutPolicy == null ? null : master.mPlayoutPolicy.cloneFor(this));

    stateBufferX1 = createEmptyInternalState();
    stateBufferX2 = createEmptyInternalState();
    stateBufferO1 = createEmptyInternalState();
    stateBufferO2 = createEmptyInternalState();
    maskStateBuffer = createEmptyInternalState();

    for(int i = 0; i < rolloutDecisionStack.length; i++)
    {
      rolloutDecisionStack[i] = new RolloutDecisionState();
    }

    moveProps = new ForwardDeadReckonProposition[numRoles];
    previousMovePropsX = new ForwardDeadReckonProposition[numRoles];
    previousMovePropsO = new ForwardDeadReckonProposition[numRoles];
    chosenJointMoveProps = new ForwardDeadReckonProposition[numRoles];
    chosenMoves = new Move[numRoles];
    previouslyChosenJointMovePropIdsX = new int[numRoles];
    previouslyChosenJointMovePropIdsO = new int[numRoles];
    isPseudoPuzzle = master.isPseudoPuzzle;

    stats = new TestPropnetStateMachineStats(fullPropNet.getBasePropositions().size(),
                                             fullPropNet.getInputPropositions().size(),
                                             fullPropNet.getLegalPropositions().get(getRolesArray()[0]).length);
  }

  public ForwardDeadReckonPropnetRuleEngine createInstance()
  {
    if (numInstances >= maxInstances)
    {
      throw new RuntimeException("Too many instances");
    }

    ForwardDeadReckonPropnetRuleEngine result = new ForwardDeadReckonPropnetRuleEngine(this, numInstances++);

    return result;
  }

  public void initialize(List description)
  {
    // Log the GDL so that we can play again as required.
    StringBuffer lGDLString  = new StringBuffer();
    lGDLString.append("GDL\n");
    for (Gdl element : description)
    {
      lGDLString.append(element);
      lGDLString.append('\n');
    }
//    LOGGER.debug(lGDLString.toString());

    setRandomSeed(1);

    try
    {
      //validationMachine = new ProverStateMachine();
      //validationMachine.initialize(description);

      fullPropNet = (ForwardDeadReckonPropNet)OptimizingPolymorphicPropNetFactory.create(
                                                                               description,
                                                                               new ForwardDeadReckonComponentFactory());
      fullPropNet.renderToFile("propnet_001.dot");

      OptimizingPolymorphicPropNetFactory.removeAnonymousPropositions(fullPropNet);
      fullPropNet.renderToFile("propnet_012_AnonRemoved.dot");
//      LOGGER.debug("Num components after anon prop removal: " + fullPropNet.getComponents().size());

      OptimizingPolymorphicPropNetFactory.removeUnreachableBasesAndInputs(fullPropNet);
      fullPropNet.renderToFile("propnet_014_UnreachablesRemoved.dot");

      isPseudoPuzzle = OptimizingPolymorphicPropNetFactory.removeIrrelevantBasesAndInputs(fullPropNet, ourRole, mFillerMoves);
      fullPropNet.renderToFile("propnet_016_IrrelevantRemoved.dot");
//      LOGGER.debug("Num components after unreachable removal: " + fullPropNet.getComponents().size());

      OptimizingPolymorphicPropNetFactory.removeRedundantConstantsAndGates(fullPropNet, false);
      fullPropNet.renderToFile("propnet_018_RedundantRemoved.dot");
//      LOGGER.debug("Num components after first pass redundant components removal: " +
//                   fullPropNet.getComponents().size());

      OptimizingPolymorphicPropNetFactory.refactorLargeGates(fullPropNet);
      fullPropNet.renderToFile("propnet_020_BeforeLargeFanout.dot");

      OptimizingPolymorphicPropNetFactory.refactorLargeFanouts(fullPropNet);
      fullPropNet.renderToFile("propnet_030_AfterLargeFanout.dot");
//      LOGGER.debug("Num components after large gate refactoring: " + fullPropNet.getComponents().size());

      OptimizingPolymorphicPropNetFactory.removeDuplicateLogic(fullPropNet);
//      LOGGER.debug("Num components after duplicate removal: " + fullPropNet.getComponents().size());

      OptimizingPolymorphicPropNetFactory.optimizeInputSets(fullPropNet);
//      LOGGER.debug("Num components after input set optimization: " + fullPropNet.getComponents().size());

      OptimizingPolymorphicPropNetFactory.optimizeInvertedInputs(fullPropNet);
//      LOGGER.debug("Num components after inverted input optimization: " + fullPropNet.getComponents().size());

      OptimizingPolymorphicPropNetFactory.removeRedundantConstantsAndGates(fullPropNet);
//      LOGGER.debug("Num components after further removal of redundant components: " +
//                   fullPropNet.getComponents().size());

      // Ensure that no propositions apart from strict input props (base, does, init) have any outputs, as this is
      // assumed by the fast animator.  Accordingly we re-wire slightly such that if any such do exist we replace their
      // output connection by one from their input (which they anyway just directly forward, so this also removes a
      // small propagation step).
      OptimizingPolymorphicPropNetFactory.removeNonBaseOrDoesPropositionOutputs(fullPropNet);

      fullPropNet.renderToFile("propnet_040_Reduced.dot");
      roles = fullPropNet.getRoles();
      numRoles = roles.length;
      roleOrdering = new RoleOrdering(this, ourRole);
      setRoleOrdering(roleOrdering);

      moveProps = new ForwardDeadReckonProposition[numRoles];
      previousMovePropsX = new ForwardDeadReckonProposition[numRoles];
      previousMovePropsO = new ForwardDeadReckonProposition[numRoles];
      chosenJointMoveProps = new ForwardDeadReckonProposition[numRoles];
      chosenMoves = new Move[numRoles];
      previouslyChosenJointMovePropIdsX = new int[numRoles];
      previouslyChosenJointMovePropIdsO = new int[numRoles];
      stats = new TestPropnetStateMachineStats(fullPropNet.getBasePropositions().size(),
                                               fullPropNet.getInputPropositions().size(),
                                               fullPropNet.getLegalPropositions().get(getRolesArray()[0]).length);
      //	Assess network statistics
      int numInputs = 0;
      int numMultiInputs = 0;
      int numMultiInputComponents = 0;

      for (PolymorphicComponent c : fullPropNet.getComponents())
      {
        int n = c.getInputs().size();

        numInputs += n;

        if (n > 1)
        {
          numMultiInputComponents++;
          numMultiInputs += n;
        }
      }

      int numComponents = fullPropNet.getComponents().size();
//      LOGGER.debug("Num components: " + numComponents + " with an average of " + numInputs / numComponents + " inputs.");
//      LOGGER.debug("Num multi-input components: " +
//                   numMultiInputComponents +
//                   " with an average of " +
//                   (numMultiInputComponents == 0 ? "N/A" : numMultiInputs /
//                                                           numMultiInputComponents) +
//                   " inputs.");

      int numGoals = 0;
      for(PolymorphicProposition[] goals : fullPropNet.getGoalPropositions().values())
      {
        numGoals += goals.length;
      }
      assert(numGoals>0);
      masterInfoSet = new ForwardDeadReckonPropositionCrossReferenceInfo[fullPropNet.getBasePropositions().size() + numGoals + 1];
      int index = 0;

      for(PolymorphicProposition[] goals : fullPropNet.getGoalPropositions().values())
      {
        for(PolymorphicProposition prop : goals)
        {
          ForwardDeadReckonPropositionCrossReferenceInfo info = new ForwardDeadReckonPropositionCrossReferenceInfo();

          info.sentence = prop.getName();
          info.fullNetProp = (ForwardDeadReckonProposition)prop;
          info.xNetProp = (ForwardDeadReckonProposition)prop;
          info.oNetProp = (ForwardDeadReckonProposition)prop;
          info.goalsNetProp = (ForwardDeadReckonProposition)prop;
          info.index = index;

          masterInfoSet[index++] = info;

          ((ForwardDeadReckonProposition)prop).setInfo(info);
        }
      }

      {
        ForwardDeadReckonProposition prop = (ForwardDeadReckonProposition)fullPropNet.getTerminalProposition();
        ForwardDeadReckonPropositionCrossReferenceInfo info = new ForwardDeadReckonPropositionCrossReferenceInfo();

        info.sentence = prop.getName();
        info.fullNetProp = prop;
        info.xNetProp = prop;
        info.oNetProp = prop;
        info.goalsNetProp = prop;
        info.terminalityNetProp = prop;
        info.index = index;

        masterInfoSet[index++] = info;

        prop.setInfo(info);
      }

      assert(index == numGoals+1);
      firstBasePropIndex = index;

      for (Entry e : fullPropNet.getBasePropositions().entrySet())
      {
        ForwardDeadReckonProposition prop = (ForwardDeadReckonProposition)e.getValue();
        ForwardDeadReckonPropositionCrossReferenceInfo info = new ForwardDeadReckonPropositionCrossReferenceInfo();

        info.sentence = e.getKey();
        info.fullNetProp = prop;
        info.xNetProp = prop;
        info.oNetProp = prop;
        info.goalsNetProp = prop;
        info.terminalityNetProp = prop;
        info.index = index;

        masterInfoSet[index++] = info;

        prop.setInfo(info);
        basePropChangeCounts.put(info, 0);
      }

      fullPropNet.crystalize(masterInfoSet, firstBasePropIndex, null, maxInstances);
      masterLegalMoveSet = fullPropNet.getMasterMoveList();

      // Try to factor the game.  But...
      // - Don't do it if we know there's only 1 factor.
      // - Allow no more than half the remaining time.
      // - Don't do it if we've previously timed out whilst factoring this game and we don't have at least 25% more time
      //   now.
      long factorizationAnalysisTimeout = (metagameTimeout - System.currentTimeMillis()) / 2;

      if (mGameCharacteristics != null)
      {
        FactorAnalyser factorAnalyser = new FactorAnalyser(this);
        FactorInfo lFactorInfo = factorAnalyser.run(factorizationAnalysisTimeout, mGameCharacteristics);

        factors = lFactorInfo.mFactors;
        if (factors != null)
        {
//          LOGGER.info("Game factorizes into " + factors.size() + " factors");
        }

        mControlMask = createEmptyInternalState();

        if (lFactorInfo.mControlSet != null)
        {
          for (PolymorphicProposition p : lFactorInfo.mControlSet)
          {
            ForwardDeadReckonPropositionInfo info = ((ForwardDeadReckonProposition)p).getInfo();
            mControlMask.add(info);
          }
        }
        mNonControlMask = new ForwardDeadReckonInternalMachineState(mControlMask);
        mNonControlMask.invert();
      }

      for (ForwardDeadReckonPropositionInfo info : masterInfoSet)
      {
        ForwardDeadReckonPropositionCrossReferenceInfo crInfo = (ForwardDeadReckonPropositionCrossReferenceInfo)info;

        crInfo.xNetPropId = crInfo.xNetProp.id;
        crInfo.oNetPropId = crInfo.oNetProp.id;
      }

      stateBufferX1 = createEmptyInternalState();
      stateBufferX2 = createEmptyInternalState();
      stateBufferO1 = createEmptyInternalState();
      stateBufferO2 = createEmptyInternalState();
      maskStateBuffer = createEmptyInternalState();

      for(int i = 0; i < rolloutDecisionStack.length; i++)
      {
        rolloutDecisionStack[i] = new RolloutDecisionState();
      }

      fullPropNet.reset(false);
      ForwardDeadReckonProposition initProp = (ForwardDeadReckonProposition)fullPropNet.getInitProposition();
      if ( initProp != null && initProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
      {
        fullPropNet.animator.getInstanceInfo(0).changeComponentValueTo(initProp.id, true);
      }
      propNet = fullPropNet;
      propNetInstanceInfo = propNet.animator.getInstanceInfo(0);
      initialState = getInternalStateFromBase(createEmptyInternalState()).getMachineState();
      fullPropNet.reset(true);

      measuringBasePropChanges = true;

      try
      {
        for (int i = 0; i < 10; i++)
        {
          performDepthCharge(initialState, null);
        }
      }
      catch (GameDescriptionException lEx)
      {
//        LOGGER.warn("Exception performing depth charges", lEx);
      }
      measuringBasePropChanges = false;

      int highestCount = 0;
      for (Entry e : basePropChangeCounts.entrySet())
      {
        if (e.getValue() > highestCount)
        {
          highestCount = e.getValue();
          XSentence = e.getKey().sentence;
        }
      }

      basePropChangeCounts = null;
      lastInternalSetState = null;
      lastGoalState = null;
      propNet = null;

      for(int i = 0; i < previousMovePropsO.length; i++)
      {
        previousMovePropsO[i] = null;
      }

      propNetX = new ForwardDeadReckonPropNet(fullPropNet, new ForwardDeadReckonComponentFactory());
      propNetO = new ForwardDeadReckonPropNet(fullPropNet, new ForwardDeadReckonComponentFactory());
      goalsNet = new ForwardDeadReckonPropNet(fullPropNet, new ForwardDeadReckonComponentFactory());
      terminalityNet = new ForwardDeadReckonPropNet(fullPropNet, new ForwardDeadReckonComponentFactory());
      propNetX.RemoveInits();
      propNetO.RemoveInits();

      if (XSentence != null)
      {
        GdlSentence OSentence = null;

//        LOGGER.info("Reducing with respect to XSentence: " + XSentence);
        OptimizingPolymorphicPropNetFactory.fixBaseProposition(propNetX, XSentence, true);

        //	If the reduced net always transitions it's own hard-wired sentence into the opposite state
        //	it may be part of a pivot whereby control passes between alternating propositions.  Check this
        //	Do we turn something else on unconditionally?
        for (Entry e : propNetX.getBasePropositions().entrySet())
        {
          PolymorphicComponent input = e.getValue().getSingleInput();

          if (input instanceof PolymorphicTransition)
          {
            PolymorphicComponent driver = input.getSingleInput();

            if (driver instanceof PolymorphicConstant && driver.getValue())
            {
              //	Found a suitable candidate
              OSentence = e.getKey();
              break;
            }
          }
        }

        if (OSentence != null)
        {
//          LOGGER.debug("Possible OSentence: " + OSentence);
          OptimizingPolymorphicPropNetFactory.fixBaseProposition(propNetO, OSentence, true);

          //	Does this one turn the original back on?
          PolymorphicProposition originalPropInSecondNet = propNetO.getBasePropositions().get(XSentence);
          if (originalPropInSecondNet != null)
          {
            PolymorphicComponent input = originalPropInSecondNet.getSingleInput();

            if (input instanceof PolymorphicTransition)
            {
              PolymorphicComponent driver = input.getSingleInput();

              if (!(driver instanceof PolymorphicConstant) || !driver.getValue())
              {
                //	Nope - doesn't work
                OSentence = null;
//                LOGGER.debug("Fails to recover back-transition to " + XSentence);
              }
            }
          }

          if (OSentence != null)
          {
            //	So if we set the first net's trigger condition to off in the second net do we find
            //	the second net's own trigger is always off?
            OptimizingPolymorphicPropNetFactory.fixBaseProposition(propNetO, XSentence, false);

            PolymorphicProposition OSentenceInSecondNet = propNetO.getBasePropositions().get(OSentence);
            if (OSentenceInSecondNet != null)
            {
              PolymorphicComponent input = OSentenceInSecondNet.getSingleInput();

              if (input instanceof PolymorphicTransition)
              {
                PolymorphicComponent driver = input.getSingleInput();

                if (!(driver instanceof PolymorphicConstant) || driver.getValue())
                {
                  //	Nope - doesn't work
//                  LOGGER.info("Fails to recover back-transition remove of " + OSentence);
                  OSentence = null;
                }

                //	Finally, if we set the OSentence off in the first net do we recover the fact that
                //	the XSentence always moves to off in transitions from the first net?
                if (OSentence != null)
                {
                  OptimizingPolymorphicPropNetFactory.fixBaseProposition(propNetX, OSentence, false);

                  PolymorphicProposition XSentenceInFirstNet = propNetX.getBasePropositions().get(XSentence);
                  if (XSentenceInFirstNet != null)
                  {
                    input = XSentenceInFirstNet.getSingleInput();

                    if (input instanceof PolymorphicTransition)
                    {
                      driver = input.getSingleInput();

                      if (!(driver instanceof PolymorphicConstant) ||
                          driver.getValue())
                      {
                        //	Nope - doesn't work
//                        LOGGER.debug("Fails to recover removal of " + XSentence);
                        OSentence = null;
                      }
                    }
                  }
                }
              }
            }
            else
            {
              OSentence = null;
            }
          }
        }

        if (OSentence == null)
        {
//          LOGGER.debug("Reverting OSentence optimizations");
          //	Failed - best we can do is simply drive the XSentence to true in one network
          propNetX = new ForwardDeadReckonPropNet(fullPropNet, new ForwardDeadReckonComponentFactory());
          propNetO = new ForwardDeadReckonPropNet(fullPropNet, new ForwardDeadReckonComponentFactory());
          propNetX.RemoveInits();
          propNetO.RemoveInits();
          OptimizingPolymorphicPropNetFactory.fixBaseProposition(propNetX, XSentence, true);
          OptimizingPolymorphicPropNetFactory.fixBaseProposition(propNetO, XSentence, false);
        }
        //OptimizingPolymorphicPropNetFactory.fixBaseProposition(propNetO, XSentence, false);
        propNetX.renderToFile("propnet_050_ReducedX.dot");
        propNetO.renderToFile("propnet_060_ReducedO.dot");
//        LOGGER.debug("Num components remaining in X-net: " + propNetX.getComponents().size());
//        LOGGER.debug("Num components remaining in O-net: " + propNetO.getComponents().size());
      }

      propNetXWithoutGoals = new ForwardDeadReckonPropNet(propNetX, new ForwardDeadReckonComponentFactory());
      propNetOWithoutGoals = new ForwardDeadReckonPropNet(propNetO, new ForwardDeadReckonComponentFactory());
      propNetXWithoutGoals.RemoveGoals();
      propNetOWithoutGoals.RemoveGoals();
      OptimizingPolymorphicPropNetFactory.minimizeNetwork(propNetXWithoutGoals);
      OptimizingPolymorphicPropNetFactory.minimizeNetwork(propNetOWithoutGoals);
      propNetXWithoutGoals.renderToFile("propnet_070_XWithoutGoals.dot");
      propNetOWithoutGoals.renderToFile("propnet_080_OWithoutGoals.dot");

      terminalityNet.RemoveAllButTerminal();
      goalsNet.RemoveAllButGoals();

      goalsNet.renderToFile("propnet_090_GoalsReduced.dot");

//      LOGGER.info("Num components in goal-less X-net: " + propNetXWithoutGoals.getComponents().size());
//      LOGGER.info("Num components in goal-less O-net: " + propNetOWithoutGoals.getComponents().size());
//      LOGGER.info("Num components in goal net:        " + goalsNet.getComponents().size());
//      LOGGER.info("Num components in terminality net: " + terminalityNet.getComponents().size());

      //masterInfoSet = new ForwardDeadReckonPropositionCrossReferenceInfo[fullPropNet
      //    .getBasePropositions().size()];
      //index = 0;
      finalizePropositionCrossReferenceInfo();

      propNetX.crystalize(masterInfoSet, firstBasePropIndex, masterLegalMoveSet, maxInstances);
      propNetO.crystalize(masterInfoSet, firstBasePropIndex, masterLegalMoveSet, maxInstances);
      goalsNet.crystalize(masterInfoSet, firstBasePropIndex, masterLegalMoveSet, maxInstances);
      terminalityNet.crystalize(masterInfoSet, firstBasePropIndex, masterLegalMoveSet, maxInstances);

      for(ForwardDeadReckonPropositionInfo info : masterInfoSet)
      {
        ForwardDeadReckonPropositionCrossReferenceInfo crInfo = (ForwardDeadReckonPropositionCrossReferenceInfo)info;

        crInfo.xNetPropId = crInfo.xNetProp.id;
        crInfo.oNetPropId = crInfo.oNetProp.id;
      }

      terminalityNet.reset(true);
      goalsNet.reset(true);
      //	Force calculation of the goal set while we're single threaded
      goalsNet.getGoalPropositions();

      //  Set move factor info
      if ( factors != null )
      {
        //  Moves with no dependencies (typically a noop) can appear in multiple factors, but
        //  should be tagged as factor-free
        setMoveInfoForPropnet(propNetX);
        setMoveInfoForPropnet(propNetO);
      }

//      stateBufferX1 = new ForwardDeadReckonInternalMachineState(masterInfoSet);
//      stateBufferX2 = new ForwardDeadReckonInternalMachineState(masterInfoSet);
//      stateBufferO1 = new ForwardDeadReckonInternalMachineState(masterInfoSet);
//      stateBufferO2 = new ForwardDeadReckonInternalMachineState(masterInfoSet);

      propNetX.reset(true);
      propNetO.reset(true);
      //	Force calculation of the goal set while we're single threaded
      propNetX.getGoalPropositions();
      propNetO.getGoalPropositions();

      propNet = propNetX;
      propNetInstanceInfo = propNet.animator.getInstanceInfo(instanceId);
      legalPropositions = legalPropositionsX;

      totalNumMoves = fullPropNet.getMasterMoveList().length;

    }
    catch (InterruptedException e)
    {
      // TODO: handle exception
    }
  }

  private void finalizePropositionCrossReferenceInfo()
  {
    //  Cross-reference the base propositions of the various networks
    for (Entry e : fullPropNet.getBasePropositions().entrySet())
    {
      ForwardDeadReckonProposition oProp = (ForwardDeadReckonProposition)propNetO
          .getBasePropositions().get(e.getKey());
      ForwardDeadReckonProposition xProp = (ForwardDeadReckonProposition)propNetX
          .getBasePropositions().get(e.getKey());
      ForwardDeadReckonProposition goalsProp = (ForwardDeadReckonProposition)goalsNet
          .getBasePropositions().get(e.getKey());
      ForwardDeadReckonProposition terminalityProp = (ForwardDeadReckonProposition)terminalityNet
          .getBasePropositions().get(e.getKey());
      ForwardDeadReckonProposition fullNetPropFdr = (ForwardDeadReckonProposition)e.getValue();
      ForwardDeadReckonPropositionCrossReferenceInfo info = (ForwardDeadReckonPropositionCrossReferenceInfo)fullNetPropFdr.getInfo();

      info.xNetProp = xProp;
      info.oNetProp = oProp;
      info.goalsNetProp = goalsProp;
      info.terminalityNetProp = terminalityProp;

      xProp.setInfo(info);
      oProp.setInfo(info);
      if (goalsProp != null)
      {
        goalsProp.setInfo(info);
      }
      if (terminalityProp != null)
      {
        terminalityProp.setInfo(info);
      }

      if (e.getKey().equals(XSentence))
      {
        XSentenceInfo = info;
      }
    }

    //  Cross-reference the goal propositions of the various networks
    for(Entry e : fullPropNet.getGoalPropositions().entrySet())
    {
      for(PolymorphicProposition prop : e.getValue())
      {
        ForwardDeadReckonPropositionInfo info = ((ForwardDeadReckonProposition)prop).getInfo();
        if ( info != null )
        {
          for(PolymorphicProposition p : propNetO.getGoalPropositions().get(e.getKey()))
          {
            if ( p.getName() == prop.getName() )
            {
              ((ForwardDeadReckonPropositionCrossReferenceInfo)info).oNetProp = (ForwardDeadReckonProposition)p;
              ((ForwardDeadReckonProposition)p).setInfo(info);
              break;
            }
          }

          for(PolymorphicProposition p : propNetX.getGoalPropositions().get(e.getKey()))
          {
            if ( p.getName() == prop.getName() )
            {
              ((ForwardDeadReckonPropositionCrossReferenceInfo)info).xNetProp = (ForwardDeadReckonProposition)p;
              ((ForwardDeadReckonProposition)p).setInfo(info);
              break;
            }
          }

          for(PolymorphicProposition p : goalsNet.getGoalPropositions().get(e.getKey()))
          {
            if ( p.getName() == prop.getName() )
            {
              ((ForwardDeadReckonPropositionCrossReferenceInfo)info).goalsNetProp = (ForwardDeadReckonProposition)p;
              ((ForwardDeadReckonProposition)p).setInfo(info);
              break;
            }
          }
        }
      }
    }

    //  Cross-reference the terminal propositions of the various networks
    {
      ForwardDeadReckonProposition prop = (ForwardDeadReckonProposition)fullPropNet.getTerminalProposition();
      ForwardDeadReckonProposition oProp = (ForwardDeadReckonProposition)propNetO.getTerminalProposition();
      ForwardDeadReckonProposition xProp = (ForwardDeadReckonProposition)propNetX.getTerminalProposition();
      ForwardDeadReckonProposition goalsProp = (ForwardDeadReckonProposition)goalsNet.getTerminalProposition();
      ForwardDeadReckonProposition terminalityProp = (ForwardDeadReckonProposition)terminalityNet.getTerminalProposition();
      ForwardDeadReckonPropositionCrossReferenceInfo info = (ForwardDeadReckonPropositionCrossReferenceInfo)prop.getInfo();

      assert(info != null);

      info.oNetProp = oProp;
      info.xNetProp = xProp;
      info.goalsNetProp = goalsProp;
      info.terminalityNetProp = terminalityProp;

      oProp.setInfo(info);
      xProp.setInfo(info);
      goalsProp.setInfo(info);
      terminalityProp.setInfo(info);
    }
  }

  public void optimizeStateTransitionMechanism(long timeout)
  {
    ForwardDeadReckonInternalMachineState initialInternalState = createInternalState(initialState);
    Role firstRole = getRolesArray()[0];
    int withTrueCount = 0;
    int withFalseCount = 0;
    long totalTime = (timeout - System.currentTimeMillis());

    //  Perform some measurements to see if the state machine runs faster if we reset
    //  base props removed between states first or add ones set first (this varies
    //  significantly between games).  We budget 2 seconds (1 second of simulation for each choice)
    //  at the end of state machine initialization for this.  The random seed is set to the same
    //  value at the start of each set of simulations to ensure the same games are played, and thus
    //  the same state transitions occur
    removeOldBasePropsBeforeAddingNew = false;

    PlayoutInfo playoutInfo = new PlayoutInfo(-1);
    playoutInfo.cutoffDepth = 1000;

    setRandomSeed(100);
    long startWithFalse = System.currentTimeMillis();
    while(System.currentTimeMillis() < startWithFalse + totalTime/2)
    {
      getDepthChargeResult(initialInternalState, playoutInfo);
      withFalseCount++;
    }

    removeOldBasePropsBeforeAddingNew = true;

    setRandomSeed(100);
    long startWithTrue = System.currentTimeMillis();
    while(System.currentTimeMillis() < startWithTrue + totalTime/2)
    {
      getDepthChargeResult(initialInternalState, playoutInfo);
      withTrueCount++;
    }

//    LOGGER.info("Iterations in " + totalTime/2 + " ms with/without removal before adding: " + withTrueCount + "/" + withFalseCount);
    if ( (Math.abs(withTrueCount-withFalseCount)*100)/Math.max(withTrueCount, withFalseCount) < 4 )
    {
      use2passBasePropSet = false;
//      LOGGER.info("Speed improvement insufficient to justify 2-pass prop setting");
    }
    else
    {
      use2passBasePropSet = true;
      if ( withFalseCount > withTrueCount )
      {
        removeOldBasePropsBeforeAddingNew = false;
//        LOGGER.info("Speed improvement of " + (100*(withFalseCount-withTrueCount))/withFalseCount + "% adding new base props before removing old");
      }
      else
      {
//        LOGGER.info("Speed improvement of " + (100*(withTrueCount-withFalseCount))/withTrueCount + "% removing old base props before adding new");
      }
    }
  }

  private void setMoveInfoForPropnet(ForwardDeadReckonPropNet pn)
  {
    //  Moves with no dependencies (typically a noop) can appear in multiple factors, but
    //  should be tagged as factor-free
    Set multiFactorMoves = new HashSet<>();

    ForwardDeadReckonLegalMoveInfo[] factoredIndexMoveList = fullPropNet.getMasterMoveList();
    for(ForwardDeadReckonLegalMoveInfo info : pn.getMasterMoveList())
    {
      if ( info != null )
      {
        if ( factors != null )
        {
          for(Factor factor : factors)
          {
            if ( factor.getMoveInfos().contains(factoredIndexMoveList[info.mMasterIndex]))
            {
              if ( info.mFactor != null )
              {
                multiFactorMoves.add(info);
              }
              info.mFactor = factor;
            }
          }
        }

        if ( info.mInputProposition != null && mFillerMoves.contains(info.mInputProposition.getName()))
        {
          info.mIsVirtualNoOp = true;
        }
      }
    }

    if ( factors != null )
    {
      for(ForwardDeadReckonLegalMoveInfo info : multiFactorMoves)
      {
        info.mFactor = null;
      }
    }
  }

  public void Optimize()
  {
    for (PolymorphicComponent c : propNet.getComponents())
    {
      ((LearningComponent)c).Optimize();
    }
  }

  /**
   * @return the underlying full propnet.
   */
  public ForwardDeadReckonPropNet getFullPropNet()
  {
    return fullPropNet;
  }

  /**
   * @return the underlying goal propnet.
   */
  public ForwardDeadReckonPropNet getGoalPropNet()
  {
    return goalsNet;
  }

  /**
   * @return the underlying goal-less X-net.
   */
  public ForwardDeadReckonPropNet getXPropNet()
  {
    return propNetXWithoutGoals;
  }

  /**
   * @return the underlying goal-less O-net.
   */
  public ForwardDeadReckonPropNet getOPropNet()
  {
    return propNetOWithoutGoals;
  }

  /**
   * Return a search filter for use with this state machine when performing higher level goal search.
   *
   * @return filter to use
   */
  public StateMachineFilter getBaseFilter()
  {
    if (searchFilter == null)
    {
      searchFilter = new NullStateMachineFilter();
    }

    return searchFilter;
  }

  /**
   * Note a new turn has started of the specified number (within the game)
   * @param turn
   */
  public void noteTurnNumber(int turn)
  {
    mTurnNumber = turn;
  }

  private void setBaseFilter(StateMachineFilter filter)
  {
    searchFilter = filter;
  }

  /**
   * Get a state mask for the non-control propositions
   * @return null if unknown else state mask
   */
  public ForwardDeadReckonInternalMachineState getNonControlMask()
  {
    return mNonControlMask;
  }

  /**
   * @return number of roles in the game
   */
  @Override
public int getNumRoles()
  {
    return numRoles;
  }

  /**
   * Get a state mask for the control propositions
   * @return null if unknown else state mask
   */
  public ForwardDeadReckonInternalMachineState getControlMask()
  {
    return mControlMask;
  }

//  private void setBasePropositionsFromState(MachineState state)
//  {
//    setBasePropositionsFromState(createInternalState(state));
//  }

  private ForwardDeadReckonInternalMachineState stateBufferX1 = null;
  private ForwardDeadReckonInternalMachineState stateBufferX2 = null;
  private ForwardDeadReckonInternalMachineState stateBufferO1 = null;
  private ForwardDeadReckonInternalMachineState stateBufferO2 = null;

  private boolean use2passBasePropSet               = true;
  private boolean removeOldBasePropsBeforeAddingNew = true;

  private void makeBasePropChangesMeasured(ForwardDeadReckonInternalMachineState nextInternalSetState)
  {
    InternalMachineStateIterator lIterator = mStateIterator;

    lIterator.reset(lastInternalSetState);
    while (lIterator.hasNext())
    {
      ForwardDeadReckonPropositionInfo info = lIterator.next();
      ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
      int propId = (propNet == propNetX ? infoCr.xNetPropId : infoCr.oNetPropId);
      if ( propId != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
      {
        if (nextInternalSetState.contains(info))
        {
          propNetInstanceInfo.changeComponentValueTo(propId, true);
        }
        else
        {
          propNetInstanceInfo.changeComponentValueTo(propId, false);
        }
      }

      basePropChangeCounts.put(infoCr,
                               basePropChangeCounts.get(infoCr) + 1);
    }
  }

  private void makeBasePropChangesUnmeasured(ForwardDeadReckonInternalMachineState nextInternalSetState)
  {
    //  The following code is a bit baroque, in the interests of extracting the last possible bit of performance, as
    //  this routine is a significant hotspot in games with large state.  The gist is:
    //  1) Iteration uses low level methods for more direct access
    //  2) Set are done before resets, or visa-versa (depending on statistical checks made during metagaming)
    //  3) Two minimize the overhead of state bitmap enumeration the first pass notes the range ends for the second
    //     pass
    //  4) The first pass is split into two sections to minimize the execution of logic specific to identifying the
    //     second pass range endpoints
    if ( propNet == propNetX)
    {
      assert(propNetInstanceInfo == propNetX.animator.getInstanceInfo(instanceId));

      if ( !use2passBasePropSet )
      {
        int index = lastInternalSetState.contents.nextSetBit(lastInternalSetState.firstBasePropIndex);

        while(index != -1)
        {
          ForwardDeadReckonPropositionCrossReferenceInfo infoCr = masterInfoSet[index];
          if ( infoCr.xNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
          {
            propNetInstanceInfo.changeComponentValueTo(infoCr.xNetProp.id, nextInternalSetState.contents.fastGet(index));
          }

          index = lastInternalSetState.contents.nextSetBit(index+1);
        }
      }
      else
      {
        int index = lastInternalSetState.contents.nextSetBit(0);
        int firstIndex = -1;
        int lastIndex = -1;

        while(index != -1)
        {
          boolean needsSetting = nextInternalSetState.contents.fastGet(index);
          if ( removeOldBasePropsBeforeAddingNew != needsSetting )
          {
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = masterInfoSet[index];
            if ( infoCr.xNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
            {
              propNetInstanceInfo.changeComponentValueTo(infoCr.xNetProp.id, needsSetting);
            }
          }
          else
          {
            firstIndex = index;
            lastIndex = index;
            index = lastInternalSetState.contents.nextSetBit(index+1);
            break;
          }

          index = lastInternalSetState.contents.nextSetBit(index+1);
        }
        while(index != -1)
        {
          boolean needsSetting = nextInternalSetState.contents.fastGet(index);
          if ( removeOldBasePropsBeforeAddingNew != needsSetting )
          {
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = masterInfoSet[index];
            if ( infoCr.xNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
            {
              propNetInstanceInfo.changeComponentValueTo(infoCr.xNetProp.id, needsSetting);
            }
          }
          else
          {
            lastIndex = index;
          }

          index = lastInternalSetState.contents.nextSetBit(index+1);
        }

        if ( firstIndex != -1 )
        {
          do
          {
            if ( removeOldBasePropsBeforeAddingNew == nextInternalSetState.contents.fastGet(firstIndex) )
            {
              ForwardDeadReckonPropositionCrossReferenceInfo infoCr = masterInfoSet[firstIndex];
              if ( infoCr.xNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
              {
                propNetInstanceInfo.changeComponentValueTo(infoCr.xNetProp.id, removeOldBasePropsBeforeAddingNew);
              }
            }

            if ( firstIndex == lastIndex )
            {
              break;
            }
            firstIndex = lastInternalSetState.contents.nextSetBit(firstIndex+1);
          } while(true);
        }
      }
    }
    else
    {
      assert(propNetInstanceInfo == propNetO.animator.getInstanceInfo(instanceId));

      if ( !use2passBasePropSet )
      {
        int index = lastInternalSetState.contents.nextSetBit(lastInternalSetState.firstBasePropIndex);

        while(index != -1)
        {
          ForwardDeadReckonPropositionCrossReferenceInfo infoCr = masterInfoSet[index];
          if ( infoCr.oNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
          {
            propNetInstanceInfo.changeComponentValueTo(infoCr.oNetProp.id, nextInternalSetState.contents.fastGet(index));
          }

          index = lastInternalSetState.contents.nextSetBit(index+1);
        }
      }
      else
      {
        int index = lastInternalSetState.contents.nextSetBit(0);
        int firstIndex = -1;
        int lastIndex = -1;

        while(index != -1)
        {
          boolean needsSetting = nextInternalSetState.contents.fastGet(index);
          if ( removeOldBasePropsBeforeAddingNew != needsSetting )
          {
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = masterInfoSet[index];
            if ( infoCr.oNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
            {
              propNetInstanceInfo.changeComponentValueTo(infoCr.oNetProp.id, needsSetting);
            }
          }
          else
          {
            firstIndex = index;
            lastIndex = index;
            index = lastInternalSetState.contents.nextSetBit(index+1);
            break;
          }

          index = lastInternalSetState.contents.nextSetBit(index+1);
        }
        while(index != -1)
        {
          boolean needsSetting = nextInternalSetState.contents.fastGet(index);
          if ( removeOldBasePropsBeforeAddingNew != nextInternalSetState.contents.fastGet(index) )
          {
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = masterInfoSet[index];
            if ( infoCr.oNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
            {
              propNetInstanceInfo.changeComponentValueTo(infoCr.oNetProp.id, needsSetting);
            }
          }
          else
          {
            lastIndex = index;
          }

          index = lastInternalSetState.contents.nextSetBit(index+1);
        }

        if ( firstIndex != -1 )
        {
          do
          {
            if ( removeOldBasePropsBeforeAddingNew == nextInternalSetState.contents.fastGet(firstIndex) )
            {
              ForwardDeadReckonPropositionCrossReferenceInfo infoCr = masterInfoSet[firstIndex];
              if ( infoCr.oNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
              {
                propNetInstanceInfo.changeComponentValueTo(infoCr.oNetProp.id, removeOldBasePropsBeforeAddingNew);
              }
            }

            if ( firstIndex == lastIndex )
            {
              break;
            }
            firstIndex = lastInternalSetState.contents.nextSetBit(firstIndex+1);
          } while(true);
        }
      }
    }
  }

  private void makeBasePropChangesWithReset()
  {
    InternalMachineStateIterator lIterator = mStateIterator;

    lIterator.reset(lastInternalSetState);
    while (lIterator.hasNext())
    {
      ForwardDeadReckonPropositionInfo s = lIterator.next();
      ForwardDeadReckonPropositionCrossReferenceInfo sCr = (ForwardDeadReckonPropositionCrossReferenceInfo)s;
      int compId = (propNet == propNetX ? sCr.xNetProp.id : sCr.oNetProp.id);

      if ( compId != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId )
      {
        propNetInstanceInfo.changeComponentValueTo(compId, true);
      }
    }
  }

  private void setBasePropositionsFromState(ForwardDeadReckonInternalMachineState state)
  {
    if (lastInternalSetState != null)
    {
      if (!lastInternalSetState.equals(state))
      {
        ForwardDeadReckonInternalMachineState nextInternalSetState;

        lastInternalSetState.xor(state);
        if (propNet == propNetX)
        {
          nextInternalSetState = (lastInternalSetState == stateBufferX1 ? stateBufferX2 : stateBufferX1);
        }
        else
        {
          nextInternalSetState = (lastInternalSetState == stateBufferO1 ? stateBufferO2 : stateBufferO1);
        }
        assert(nextInternalSetState != state);
        nextInternalSetState.copy(state);

        if (!measuringBasePropChanges)
        {
          makeBasePropChangesUnmeasured(nextInternalSetState);
        }
        else
        {
          makeBasePropChangesMeasured(nextInternalSetState);
        }

        lastInternalSetState = nextInternalSetState;
      }
    }
    else
    {
      lastInternalSetState = new ForwardDeadReckonInternalMachineState(state);

      makeBasePropChangesWithReset();
    }
  }

//  @Override
//  public boolean isTerminal(MachineState state)
//  {
//    ForwardDeadReckonInternalMachineState internalState = createInternalState(state);
//    return isTerminal(internalState);
//  }

  /**
   * Computes if the state is terminal. Should return the value of the terminal
   * proposition for the state.
   */
  @Override
public boolean isTerminal(ForwardDeadReckonInternalMachineState state)
  {
    setPropNetUsage(state);
    setBasePropositionsFromState(state);

    return isTerminal();
  }

  public boolean isTerminalDedicated(ForwardDeadReckonInternalMachineState state)
  {
    if ( factors != null )
    {
      return isTerminal(state);
    }

    setTerminalityNetBasePropsFromState(state);

    return terminalityNet.getActiveBaseProps(instanceId).contains(((ForwardDeadReckonProposition)fullPropNet.getTerminalProposition()).getInfo());
  }

  public boolean isTerminal()
  {
    if ( factors != null && !hasAvailableMoveForAllRoles(propNet) )
    {
      return true;
    }

    return isTerminalUnfactored();
  }

  private boolean isTerminalUnfactored()
  {
    boolean result = propNet.getActiveBaseProps(instanceId).contains(((ForwardDeadReckonProposition)fullPropNet.getTerminalProposition()).getInfo());

    if (validationMachine != null)
    {
      if (validationMachine.isTerminal(validationState) != result)
      {
//        LOGGER.warn("Terminality mismatch");
      }
    }
    return result;
  }

//  public int getGoal(MachineState state, Role role)
//  {
//    ForwardDeadReckonInternalMachineState internalState = createInternalState(state);
//    return getGoal(internalState, role);
//  }

  public int getGoal(Role role)
  {
    return getGoal((ForwardDeadReckonInternalMachineState)null, role);
  }

  /**
   * Returns the initial state. The initial state can be computed by only
   * setting the truth value of the INIT proposition to true, and then
   * computing the resulting state.
   */
  @Override
  public ForwardDeadReckonInternalMachineState getInitialState()
  {
//    LOGGER.trace("Initial state: " + initialState);
    return createInternalState(initialState);
  }

//  public List getLegalMoves(MachineState state, Role role)
//  {
//    ForwardDeadReckonInternalMachineState internalState = createInternalState(state);
//
//    return getLegalMovesCopy(internalState, role);
//  }

  /**
   * Computes the legal moves for role in state.
   */
  public List getLegalMovesCopy(ForwardDeadReckonInternalMachineState state, Role role)
  {
    List result;

    ForwardDeadReckonLegalMoveSet moveSet = getLegalMoveSet(state);

    result = new LinkedList<>();
    for (ForwardDeadReckonLegalMoveInfo moveInfo : moveSet.getContents(role))
    {
      result.add(moveInfo.mMove);
    }

    return result;
  }

//  /**
//   * @return the legal moves in the specified state for the specified role.
//   *
//   * @param state - the state.
//   * @param role  - the role.
//   *
//   * WARNING: This version of the function returns a collection backed by a pre-allocated array.  It is only suitable
//   *          for immediate use and not to be stored.
//   */
//  public Collection getLegalMoves(ForwardDeadReckonInternalMachineState state,
//                                                                  Role role)
//  {
//    Collection lResult = getLegalMoveSet(state).getContents(role);
//    assert(lResult.size() > 0);
//    return lResult;
//  }

  /**
   * @return the legal moves in the specified state as a ForwardDeadReckonLegalMoveSet.
   *
   * @param state - the state.
    *
   * WARNING: This version of the function returns a collection backed by a pre-allocated array.  It is only suitable
   *          for immediate use and not to be stored.
   */
  public ForwardDeadReckonLegalMoveSet getLegalMoveSet(ForwardDeadReckonInternalMachineState state)
  {
    setPropNetUsage(state);
    setBasePropositionsFromState(state);
    return propNet.getActiveLegalProps(instanceId);
  }

//  /**
//   * @return whether a specified move is legal for a role in a state.
//   *
//   * @param state - the state.
//   * @param role  - the role.
//   * @param move  - the proposed move.
//   *
//   * @throws MoveDefinitionException if the GDL is malformed.
//   */
//  public boolean isLegalMove(MachineState state, Role role, Move move) throws MoveDefinitionException
//  {
//    setPropNetUsage(state);
//    setBasePropositionsFromState(state);
//
//    Map inputProps = propNet.getInputPropositions();
//
//    GdlSentence moveSentence = ProverQueryBuilder.toDoes(role, move);
//    PolymorphicProposition moveInputProposition = inputProps.get(moveSentence);
//    PolymorphicProposition legalProp = propNet.getLegalInputMap().get(moveInputProposition);
//    if (legalProp != null)
//    {
//      return ((ForwardDeadReckonComponent)legalProp.getSingleInput()).getValue(instanceId);
//    }
//
//    throw new MoveDefinitionException(state, role);
//  }

//  private void setPropNetUsage(MachineState state)
//  {
//    setPropNetUsage(createInternalState(state));
//  }

  private void setPropNetUsage(ForwardDeadReckonInternalMachineState state)
  {
    if (XSentence != null)
    {
      if (state.isXState)
      {
        if (propNet != propNetX)
        {
          propNet = propNetX;
          propNetInstanceInfo = propNet.animator.getInstanceInfo(instanceId);
          legalPropositions = legalPropositionsX;

          lastInternalSetStateO = lastInternalSetState;
          lastInternalSetState = lastInternalSetStateX;
        }
      }
      else
      {
        if (propNet != propNetO)
        {
          propNet = propNetO;
          propNetInstanceInfo = propNet.animator.getInstanceInfo(instanceId);
          legalPropositions = legalPropositionsO;

          lastInternalSetStateX = lastInternalSetState;
          lastInternalSetState = lastInternalSetStateO;
        }
      }
    }
  }


  @Override
  public ForwardDeadReckonInternalMachineState getNextState(ForwardDeadReckonInternalMachineState internalState,
          List jointMoves) throws GameDescriptionException {
//      return createInternalState(getNextState(state.getMachineState(), translator.getMoveObjects(jointMoves)));
//      ForwardDeadReckonInternalMachineState internalState = createInternalState(state);

      setPropNetUsage(internalState);

      ForwardDeadReckonInternalMachineState internalResult = createEmptyInternalState();
      ForwardDeadReckonLegalMoveInfo[] internalMoves = new ForwardDeadReckonLegalMoveInfo[jointMoves.size()];

      Map inputProps = propNet.getInputPropositions();
      Map legalInputMap = propNet.getLegalInputMap();
      int moveRawIndex = 0;

      for (GdlSentence moveSentence : toDoes2(jointMoves))
      {
        ForwardDeadReckonProposition moveInputProposition = (ForwardDeadReckonProposition)inputProps.get(moveSentence);
        ForwardDeadReckonLegalMoveInfo moveInfo;

        if (moveInputProposition != null)
        {
          ForwardDeadReckonProposition legalProp = (ForwardDeadReckonProposition)legalInputMap.get(moveInputProposition);

          moveInfo = propNet.getMasterMoveList()[legalProp.getInfo().index];
        }
        else
        {
          moveInfo = new ForwardDeadReckonLegalMoveInfo();

          moveInfo.mIsPseudoNoOp = true;
        }

        int internalMoveIndex = (roleOrdering == null ? moveRawIndex : roleOrdering.rawRoleIndexToRoleIndex(moveRawIndex));
        internalMoves[internalMoveIndex] = moveInfo;
        moveRawIndex++;
      }

      getNextState(internalState, null, internalMoves, internalResult);

      return getInternalStateFromBase(createEmptyInternalState());
  }

//  /**
//   * Computes the next state given state and the list of moves.
//   */
////  @Override
//  public MachineState getNextState(MachineState state, List moves)
//  {
//    //RuntimeOptimizedComponent.getCount = 0;
//    //RuntimeOptimizedComponent.dirtyCount = 0;
//    ForwardDeadReckonInternalMachineState internalState = createInternalState(state);
//
//    setPropNetUsage(internalState);
//
//    ForwardDeadReckonInternalMachineState internalResult = createEmptyInternalState();
//    ForwardDeadReckonLegalMoveInfo[] internalMoves = new ForwardDeadReckonLegalMoveInfo[moves.size()];
//
//    Map inputProps = propNet.getInputPropositions();
//    Map legalInputMap = propNet.getLegalInputMap();
//    int moveRawIndex = 0;
//
//    for (GdlSentence moveSentence : toDoes(moves))
//    {
//      ForwardDeadReckonProposition moveInputProposition = (ForwardDeadReckonProposition)inputProps.get(moveSentence);
//      ForwardDeadReckonLegalMoveInfo moveInfo;
//
//      if (moveInputProposition != null)
//      {
//        ForwardDeadReckonProposition legalProp = (ForwardDeadReckonProposition)legalInputMap.get(moveInputProposition);
//
//        moveInfo = propNet.getMasterMoveList()[legalProp.getInfo().index];
//      }
//      else
//      {
//        moveInfo = new ForwardDeadReckonLegalMoveInfo();
//
//        moveInfo.mIsPseudoNoOp = true;
//      }
//
//      int internalMoveIndex = (roleOrdering == null ? moveRawIndex : roleOrdering.rawRoleIndexToRoleIndex(moveRawIndex));
//      internalMoves[internalMoveIndex] = moveInfo;
//      moveRawIndex++;
//    }
//
//    getNextState(internalState, null, internalMoves, internalResult);
//
//    MachineState result = getInternalStateFromBase(createEmptyInternalState()).getMachineState();
//
//    return result;
//  }

  /**
   * Get the next state given the current state and a set of moves.  Write the resulting state directly into the
   * supplied new state buffer.
   *
   * @param state       - the original state.
   * @param factor      - the factor.
   * @param moves       - the moves to make from the original state - in INTERNAL ordering
   * @param xbNewState  - the buffer into which the new state is written.
   */
  public void getNextState(ForwardDeadReckonInternalMachineState state,
                           Factor factor,
                           ForwardDeadReckonLegalMoveInfo[] moves,
                           ForwardDeadReckonInternalMachineState xbNewState)
  {
    assert(xbNewState != null);

    setPropNetUsage(state);

    int movesCount = 0;
    int nonNullMovesCount = 0;

    for (ForwardDeadReckonLegalMoveInfo move : moves)
    {
      ForwardDeadReckonProposition moveProp = move.mIsPseudoNoOp ? null : move.mInputProposition;
      moveProps[movesCount++] = moveProp;
      if ( moveProp != null )
      {
        nonNullMovesCount++;
      }
    }
    setBasePropositionsFromState(state);

    for (int i = 0; i < movesCount; i++)
    {
      ForwardDeadReckonProposition moveProp =  moveProps[i];
      ForwardDeadReckonProposition previousMoveProp = (propNet == propNetX ? previousMovePropsX[i] : previousMovePropsO[i]);

      if ( previousMoveProp != moveProp )
      {
        if ( propNet == propNetX )
        {
          previousMovePropsX[i] = moveProps[i];
        }
        else
        {
          previousMovePropsO[i] = moveProps[i];
        }

        if ( moveProp != null )
        {
          propNetInstanceInfo.changeComponentValueTo(moveProp.id, true);
        }
        if ( previousMoveProp != null  )
        {
          propNetInstanceInfo.changeComponentValueTo(previousMoveProp.id, false);
        }
      }
    }

    propNet.getActiveBaseProps(instanceId).markDirty();
    getInternalStateFromBase(xbNewState);

    if ( nonNullMovesCount == 0 )
    {
      ForwardDeadReckonInternalMachineState nonControlMask;
      ForwardDeadReckonInternalMachineState controlMask;

      if ( factor != null )
      {
        nonControlMask = factor.getStateMask(true);
        controlMask = factor.getInverseStateMask(true);
      }
      else
      {
        nonControlMask = getNonControlMask();
        controlMask = getControlMask();
      }

      if ( controlMask != null )
      {
        //  Hack - re-impose the base props from the starting state.  We need to do it this
        //  way in order for the non-factor turn logic (control prop, step, etc) to generate
        //  correctly, but then make sure we have not changed any factor-specific base props
        //  which can happen because no moves were played (consider distinct clauses on moves)
        ForwardDeadReckonInternalMachineState basePropState = new ForwardDeadReckonInternalMachineState(state);

        basePropState.intersect(nonControlMask);
        xbNewState.intersect(controlMask);
        xbNewState.merge(basePropState);
      }
    }
  }

  private boolean transitionToNextStateFromChosenMove()
  {
    if (validationMachine != null)
    {
      List moves = new LinkedList<>();

      for (Move move : chosenMoves)
      {
        moves.add(move);
      }
      try
      {
        validationMachine.getNextState(validationState, moves);
      }
      catch (TransitionDefinitionException e)
      {
        e.printStackTrace();
      }
    }

    int index = 0;
    for (ForwardDeadReckonProposition moveProp : chosenJointMoveProps)
    {
      int previousChosenMoveId;

      if (propNet == propNetX)
      {
        previousChosenMoveId = previouslyChosenJointMovePropIdsX[index];
      }
      else
      {
        previousChosenMoveId = previouslyChosenJointMovePropIdsO[index];
      }

      int movePropId = -1;
      if (moveProp != null)
      {
        movePropId = moveProp.id;
      }

      if (previousChosenMoveId != movePropId)
      {
        if ( movePropId != -1 )
        {
          propNetInstanceInfo.changeComponentValueTo(movePropId, true);
        }
        if ( previousChosenMoveId != -1 )
        {
          propNetInstanceInfo.changeComponentValueTo(previousChosenMoveId, false);
        }
      }
      if (propNet == propNetX)
      {
        previouslyChosenJointMovePropIdsX[index++] = movePropId;
      }
      else
      {
        previouslyChosenJointMovePropIdsO[index++] = movePropId;
      }
    }

    propagateCalculatedNextState();

    return true;
  }

  /* Already implemented for you */
  @Override
  public List getRoles() {
      return Arrays.asList(roles);
  }
  public Role[] getRolesArray()
  {
    return roles;
  }

  /* Helper methods */

//  /**
//   * The Input propositions are indexed by (does ?player ?action). This
//   * translates a list of Moves (backed by a sentence that is simply ?action)
//   * into GdlSentences that can be used to get Propositions from
//   * inputPropositions. and accordingly set their values etc. This is a naive
//   * implementation when coupled with setting input values, feel free to change
//   * this for a more efficient implementation.
//   *
//   * @param moves
//   * @return
//   */
//  private List toDoes(Move[] moves)
//  {
//    List doeses = new ArrayList<>(moves.length);
//    Map roleIndices = getRoleIndices();
//
//    for (Role lRole : roles)
//    {
//      int index = roleIndices.get(lRole);
//      doeses.add(ProverQueryBuilder.toDoes(lRole, moves[index]));
//    }
//    return doeses;
//  }

  /**
   * The Input propositions are indexed by (does ?player ?action). This
   * translates a list of Moves (backed by a sentence that is simply ?action)
   * into GdlSentences that can be used to get Propositions from
   * inputPropositions. and accordingly set their values etc. This is a naive
   * implementation when coupled with setting input values, feel free to change
   * this for a more efficient implementation.
   *
   * @param moves
   * @return
   */
  private List toDoes(List moves)
  {
    List doeses = new ArrayList<>(moves.size());
    Map roleIndices = getRoleIndices();

    for (Role lRole : roles)
    {
      int index = roleIndices.get(lRole);
      doeses.add(ProverQueryBuilder.toDoes(lRole, moves.get(index)));
    }
    return doeses;
  }

  /**
   * The Input propositions are indexed by (does ?player ?action). This
   * translates a list of Moves (backed by a sentence that is simply ?action)
   * into GdlSentences that can be used to get Propositions from
   * inputPropositions. and accordingly set their values etc. This is a naive
   * implementation when coupled with setting input values, feel free to change
   * this for a more efficient implementation.
   *
   * @param moves
   * @return
   */
  private List toDoes2(List moves)
  {
    List doeses = new ArrayList<>(moves.size());
    Map roleIndices = getRoleIndices();

    for (Role lRole : roles)
    {
      int index = roleIndices.get(lRole);
      doeses.add(ProverQueryBuilder.toDoes(lRole, moves.get(index).mMove));
    }
    return doeses;
  }

  private GdlSentence toDoes(GdlTerm move, int roleIndex)
  {
      Role lRole = roles[roleIndex];
      return GdlPool.getRelation(GdlPool.DOES, new GdlTerm[] { lRole.getName(), move });
  }

  private void propagateCalculatedNextState()
  {
    ForwardDeadReckonInternalMachineState transitionTo = propNet.getActiveBaseProps(instanceId);

    boolean targetIsXNet = transitionTo.contains(XSentenceInfo);
    if (propNet == propNetX)
    {
      if (!targetIsXNet)
      {
        propNet = propNetO;
        propNetInstanceInfo = propNet.animator.getInstanceInfo(instanceId);
        lastInternalSetStateX = lastInternalSetState;
        lastInternalSetState = lastInternalSetStateO;

        legalPropositions = legalPropositionsO;
      }
    }
    else
    {
      if (targetIsXNet)
      {
        propNet = propNetX;
        propNetInstanceInfo = propNet.animator.getInstanceInfo(instanceId);
        lastInternalSetStateO = lastInternalSetState;
        lastInternalSetState = lastInternalSetStateX;

        legalPropositions = legalPropositionsX;
      }
    }

    transitionTo.isXState = targetIsXNet;

    setBasePropositionsFromState(transitionTo);
  }

  private ForwardDeadReckonInternalMachineState getInternalStateFromBase(ForwardDeadReckonInternalMachineState xbState)
  {
    assert(xbState != propNet.getActiveBaseProps(instanceId));

    xbState.copy(propNet.getActiveBaseProps(instanceId));
    xbState.isXState = (XSentenceInfo != null && xbState.contains(XSentenceInfo));

    return xbState;
  }


//  private Map> recentLegalMoveSetsList = new HashMap<>();

//  public Move getRandomMove(MachineState state, Role role)
//      throws MoveDefinitionException
//  {
//    if (useSampleOfKnownLegals)
//    {
//      int choiceSeed = getRandom(100);
//      final int tryPreviousPercentage = 80;
//      List previouslyAvailableMoves = null;
//      boolean preferNew = false;
//
//      if (choiceSeed < tryPreviousPercentage &&
//          recentLegalMoveSetsList.keySet().contains(role))
//      {
//        previouslyAvailableMoves = recentLegalMoveSetsList.get(role);
//        Move result = previouslyAvailableMoves.get(getRandom(previouslyAvailableMoves.size()));
//
//        if (isLegalMove(state, role, result))
//        {
//          return result;
//        }
//      }
//      else if (choiceSeed > 100 - tryPreviousPercentage / 2)
//      {
//        preferNew = true;
//      }
//
//      List legals = getLegalMoves(state, role);
//      List candidates;
//
//      if (preferNew && previouslyAvailableMoves != null)
//      {
//        candidates = new LinkedList<>();
//
//        for (Move move : legals)
//        {
//          if (!previouslyAvailableMoves.contains(move))
//          {
//            candidates.add(move);
//          }
//        }
//      }
//      else
//      {
//        candidates = legals;
//      }
//
//      if (legals.size() > 1)
//      {
//        recentLegalMoveSetsList.put(role, legals);
//      }
//
//      return candidates.get(getRandom(candidates.size()));
//    }
//    List legals = getLegalMoves(state, role);
//
//    int randIndex = getRandom(legals.size());
//    return legals.get(randIndex);
//  }

  private class RolloutDecisionState
  {
    public RolloutDecisionState()
    {
      // TODO Auto-generated constructor stub
    }

    //  The following arrays may be null or point to a pre-allocated buffers
    public ForwardDeadReckonLegalMoveInfo[] chooserMoves;
    public boolean[]                        propProcessed;
    public int                              numChoices;
    //  The following are pre-allocated buffers used repeatedly to avoid GC
    //  It is expanded as necessary but never shrunk
    private ForwardDeadReckonLegalMoveInfo[] chooserMovesBuffer;
    private boolean[]                       propProcessedBuffer;
    public final ForwardDeadReckonProposition[]   nonChooserProps = new ForwardDeadReckonProposition[numRoles];
    public int                              chooserIndex;
    public int                              baseChoiceIndex;
    public int                              nextChoiceIndex;
    public int                              rolloutSeq;
    public int                              maxAchievableOpponentScoreTotal;
    final ForwardDeadReckonInternalMachineState   state = createEmptyInternalState();
    Role                                    choosingRole;

    void  clearMoveChoices()
    {
      numChoices = -1;
      chooserMoves = null;
      propProcessed = null;
    }

    void  setNumMoveChoices(int numMoveChoices)
    {
      if ( chooserMovesBuffer == null || chooserMovesBuffer.length < numMoveChoices )
      {
        //  Allow extra so we don't have to repeatedly expand
        chooserMovesBuffer = new ForwardDeadReckonLegalMoveInfo[numMoveChoices*2];
        propProcessedBuffer = new boolean[numMoveChoices*2];
      }

      numChoices = numMoveChoices;
      chooserMoves = chooserMovesBuffer;
      propProcessed = propProcessedBuffer;
    }
  }

  /**
   * The maximum possible path length.  Game of "Connect 4 Larger" could run to 400.  We'll give a bit of room for
   * bigger games.
   */
  public static final int TREE_PATH_MAX_PATH_LEN = 512;

  private final RolloutDecisionState[] rolloutDecisionStack = new RolloutDecisionState[TREE_PATH_MAX_PATH_LEN];
  private int                    rolloutStackDepth;
  private int                    rolloutSeq           = 0;

  private int                    totalRoleoutChoices;
  private int                    totalRoleoutNodesExamined;

  private void doRecursiveGreedyRoleout(TerminalResultSet results,
                                        Factor factor,
                                        MoveWeights moveWeights,
                                        ForwardDeadReckonLegalMoveInfo[] playedMoves,
                                        int cutoffDepth)
  {
    Move hintMove = null;
    int hintMoveDepth = -1;

    do
    {
      Move winningMove = transitionToNextStateInGreedyRollout(results,
                                                              factor,
                                                              hintMove,
                                                              moveWeights,
                                                              playedMoves,
                                                              rolloutStackDepth);
      if (winningMove != null)
      {
        hintMove = winningMove;
        hintMoveDepth = rolloutStackDepth;

        //	Next player had a 1 move forced win.  Pop the stack and choose again at this level unless deciding player was
        //	the same as for this node
        //	TODO - this doesn't handle well games in which the same player gets to play multiple times
        if (rolloutStackDepth > 0 &&
            rolloutDecisionStack[rolloutStackDepth - 1].nextChoiceIndex != rolloutDecisionStack[rolloutStackDepth - 1].baseChoiceIndex)
        {
          if (rolloutDecisionStack[rolloutStackDepth].chooserIndex != rolloutDecisionStack[rolloutStackDepth - 1].chooserIndex)
          {
            rolloutDecisionStack[rolloutStackDepth].chooserMoves = null;

            RolloutDecisionState poppedState = rolloutDecisionStack[--rolloutStackDepth];

            setPropNetUsage(poppedState.state);
            setBasePropositionsFromState(poppedState.state);
          }
          else
          {
            if (!isTerminal() && !scoresAreLatched(lastInternalSetState))
            {
              if (rolloutStackDepth++ >= hintMoveDepth)
              {
                hintMove = null;
              }
            }
            else
            {
              results.considerResult(rolloutDecisionStack[rolloutStackDepth++].choosingRole);
              break;
            }
          }
        }
        else
        {
          if (!isTerminal() && !scoresAreLatched(lastInternalSetState))
          {
            if (rolloutStackDepth++ >= hintMoveDepth)
            {
              hintMove = null;
            }
          }
          else
          {
            results.considerResult(rolloutDecisionStack[rolloutStackDepth++].choosingRole);
            break;
          }
        }
      }
      else if (!isTerminal() && !scoresAreLatched(lastInternalSetState))
      {
        if (rolloutStackDepth++ >= hintMoveDepth)
        {
          hintMove = null;
        }
      }
      else if (rolloutDecisionStack[rolloutStackDepth].nextChoiceIndex != rolloutDecisionStack[rolloutStackDepth].baseChoiceIndex)
      {
        //	Having recorded the potential terminal state continue to explore another
        //	branch given that this terminality was not a forced win for the deciding player
        RolloutDecisionState decisionState = rolloutDecisionStack[rolloutStackDepth];

        setPropNetUsage(decisionState.state);
        setBasePropositionsFromState(decisionState.state);
      }
      else if ( rolloutStackDepth > 0 &&
                (rolloutDecisionStack[rolloutStackDepth].chooserIndex == -1 ||
                 rolloutDecisionStack[rolloutStackDepth].chooserIndex == rolloutDecisionStack[rolloutStackDepth-1].chooserIndex) )
      {
        //  No choice lead to a win.  If there was a choice at the previous level and the
        //  result is bad for that player should pop and retry
        if ( rolloutDecisionStack[rolloutStackDepth - 1].nextChoiceIndex != rolloutDecisionStack[rolloutStackDepth - 1].baseChoiceIndex)
        {
          if (rolloutDecisionStack[rolloutStackDepth - 1].chooserIndex != -1)
          {
            int lScoreForChoosingRole = getGoal(roles[rolloutDecisionStack[rolloutStackDepth - 1].chooserIndex]);

            getLatchedScoreRange(lastInternalSetState, roles[rolloutDecisionStack[rolloutStackDepth - 1].chooserIndex], parentLatchedScoreRangeBuffer);
            if ( lScoreForChoosingRole == parentLatchedScoreRangeBuffer[0] )
            {
              rolloutDecisionStack[rolloutStackDepth].chooserMoves = null;

              RolloutDecisionState poppedState = rolloutDecisionStack[--rolloutStackDepth];

              setPropNetUsage(poppedState.state);
              setBasePropositionsFromState(poppedState.state);
              continue;
            }
          }
        }

        rolloutStackDepth++;
        break;
      }
      else
      {
        rolloutStackDepth++;
        break;
      }
    }
    while (cutoffDepth > rolloutStackDepth);
  }

  private double recursiveGreedyRollout(TerminalResultSet results,
                                        Factor factor,
                                        MoveWeights moveWeights,
                                        ForwardDeadReckonLegalMoveInfo[] playedMoves,
                                        int cutoffDepth)
  {
    rolloutSeq++;
    rolloutStackDepth = 0;
    totalRoleoutChoices = 0;
    totalRoleoutNodesExamined = 0;

    doRecursiveGreedyRoleout(results, factor, moveWeights, playedMoves, cutoffDepth);

    if (totalRoleoutNodesExamined > 0)
    {
      return totalRoleoutChoices / totalRoleoutNodesExamined;
    }
    return 0;
  }

  private Set terminatingMoveProps             = new HashSet<>();
  public long                               numRolloutDecisionNodeExpansions = 0;
  public double                             greedyRolloutEffectiveness       = 0;
  private int                               terminalCheckHorizon             = 500; //  Effectively infinite by default

  public int getNumTerminatingMoveProps()
  {
    return terminatingMoveProps.size();
  }

  public void clearTerminatingMoveProps()
  {
    terminatingMoveProps.clear();
  }

  public void setTerminalCheckHorizon(int horizon)
  {
    terminalCheckHorizon = horizon;
  }

  private Move transitionToNextStateInGreedyRollout(TerminalResultSet results,
                                                    Factor factor,
                                                    Move hintMove,
                                                    MoveWeights moveWeights,
                                                    ForwardDeadReckonLegalMoveInfo[] playedMoves,
                                                    int moveIndex)
  {
    //		ProfileSection methodSection = new ProfileSection("TestPropnetStateMachine.transitionToNextStateInGreedyRollout");
    //		try
    //		{
    ForwardDeadReckonLegalMoveSet activeLegalMoves = propNet.getActiveLegalProps(instanceId);
    int index = 0;
    boolean simultaneousMove = false;
    int maxChoices = 0;
    ForwardDeadReckonLegalMoveInfo choicelessMoveInfo = null;

    RolloutDecisionState decisionState = rolloutDecisionStack[rolloutStackDepth];
    if (decisionState.rolloutSeq != rolloutSeq)
    {
      decisionState.rolloutSeq = rolloutSeq;
      decisionState.clearMoveChoices();
    }

    if (decisionState.chooserMoves == null)
    {
      decisionState.choosingRole = null;
      decisionState.chooserIndex = -1;
      decisionState.baseChoiceIndex = -1;
      decisionState.nextChoiceIndex = -1;
      decisionState.maxAchievableOpponentScoreTotal = -1;
      totalRoleoutNodesExamined++;

      for (Role role : getRoles())
      {
        ForwardDeadReckonLegalMoveSet moves = activeLegalMoves;
        int numChoices = StateMachineFilterUtils.getFilteredSize(null, moves, role, factor, false);

        if (numChoices > maxChoices)
        {
          maxChoices = numChoices;
        }

        if (numChoices > 1)
        {
          totalRoleoutChoices += numChoices;
          if (decisionState.choosingRole == null)
          {
            if (!simultaneousMove)
            {
              decisionState.choosingRole = role;
              decisionState.setNumMoveChoices(numChoices);
            }
          }
          else
          {
            int rand = getRandom(decisionState.numChoices);

            ForwardDeadReckonLegalMoveInfo info = decisionState.chooserMoves[rand];
            decisionState.nonChooserProps[decisionState.chooserIndex] = info.mInputProposition;
            if (playedMoves != null)
            {
              playedMoves[moveIndex] = info;
            }

            decisionState.choosingRole = null;
            decisionState.clearMoveChoices();
            simultaneousMove = true;
          }
        }

        if (simultaneousMove)
        {
          int rand = getRandom(numChoices);

          Iterator itr = moves.getContents(role).iterator();
          for (int iMove = 0; iMove < numChoices; iMove++)
          {
            // Get next move for this factor
            ForwardDeadReckonLegalMoveInfo info = StateMachineFilterUtils.nextFilteredMove(factor, itr);

            if (rand-- <= 0)
            {
              decisionState.nonChooserProps[index++] = info.mInputProposition;
              //chosenJointMoveProps[index++] = info.inputProposition;
              if (playedMoves != null)
              {
                playedMoves[moveIndex] = info;
              }
              break;
            }
          }
        }
        else
        {
          int chooserMoveIndex = 0;
          Iterator itr = moves.getContents(role).iterator();
          for (int iMove = 0; iMove < numChoices; iMove++)
          {
            // Get next move for this factor
            ForwardDeadReckonLegalMoveInfo info = StateMachineFilterUtils.nextFilteredMove(factor, itr);

            if (decisionState.choosingRole == role)
            {
              if (chooserMoveIndex == 0)
              {
                decisionState.chooserIndex = index++;
              }
              decisionState.chooserMoves[chooserMoveIndex++] = info;
            }
            else
            {
              decisionState.nonChooserProps[index++] = info.mInputProposition;
              if (info.mInputProposition != null || choicelessMoveInfo == null)
              {
                choicelessMoveInfo = info;
              }
              break;
            }
          }
        }
      }
    }

    if (simultaneousMove)
    {
      for(int i = 0; i < chosenJointMoveProps.length; i++)
      {
        chosenJointMoveProps[i] = decisionState.nonChooserProps[i];
      }

      transitionToNextStateFromChosenMove();

      if (isTerminal() || scoresAreLatched(lastInternalSetState))
      {
        results.considerResult(null);
      }
    }
    else if (decisionState.chooserIndex != -1)
    {
      int choiceIndex;
      boolean preEnumerate = hasNegativelyLatchedGoals();
      int numTerminals = 0;

      if (decisionState.baseChoiceIndex == -1)
      {
        double total = 0;

        decisionState.state.copy(lastInternalSetState);
        decisionState.maxAchievableOpponentScoreTotal = 0;

        for(Role role : getRoles())
        {
          if ( !role.equals(decisionState.choosingRole))
          {
            getLatchedScoreRange(decisionState.state, role, latchedScoreRangeBuffer);

            decisionState.maxAchievableOpponentScoreTotal += latchedScoreRangeBuffer[1];
          }
        }

        for (int i = 0; i < decisionState.numChoices; i++)
        {
          ForwardDeadReckonLegalMoveInfo chooserMove = decisionState.chooserMoves[i];
          if (moveWeights != null)
          {
            total += moveWeights.weightScore[chooserMove.mMasterIndex];
          }
          if (!preEnumerate && terminatingMoveProps.contains(chooserMove.mInputProposition))
          {
            preEnumerate = true;
            numRolloutDecisionNodeExpansions++;
            if (moveWeights == null)
            {
              break;
            }
          }
        }

        if (moveWeights == null)
        {
          decisionState.baseChoiceIndex = getRandom(decisionState.numChoices);
        }
        else
        {
          total = getRandom((int)total);
        }

        for (int i = 0; i < decisionState.numChoices; i++)
        {
          decisionState.propProcessed[i] = false;
          if (decisionState.baseChoiceIndex == -1)
          {
            total -= moveWeights.weightScore[decisionState.chooserMoves[i].mMasterIndex];
            if (total <= 0)
            {
              decisionState.baseChoiceIndex = i;
            }
          }
        }

        choiceIndex = decisionState.baseChoiceIndex;
      }
      else
      {
        choiceIndex = decisionState.nextChoiceIndex;
      }

      for (int roleIndex = 0; roleIndex < getRolesArray().length; roleIndex++)
      {
        if (roleIndex != decisionState.chooserIndex)
        {
          chosenJointMoveProps[roleIndex] = decisionState.nonChooserProps[roleIndex];
        }
      }

      boolean transitioned = false;

      getLatchedScoreRange(lastInternalSetState, decisionState.choosingRole, parentLatchedScoreRangeBuffer);

      //	If we're given a hint move to check for a win do that first
      //	the first time we look at this node
      if (hintMove != null && decisionState.numChoices > 1)
      {
        if (decisionState.baseChoiceIndex == choiceIndex)
        {
          for (int i = 0; i < decisionState.numChoices; i++)
          {
            if (decisionState.chooserMoves[i].mMove == hintMove)
            {
              chosenJointMoveProps[decisionState.chooserIndex] = decisionState.chooserMoves[i].mInputProposition;

              transitionToNextStateFromChosenMove();

              if (isTerminal() || scoresAreLatched(lastInternalSetState))
              {
                numTerminals++;

                if (getGoal(decisionState.choosingRole) == parentLatchedScoreRangeBuffer[1])
                {
                  if (playedMoves != null)
                  {
                    assert(decisionState.chooserMoves[i] != null);
                    playedMoves[moveIndex] = decisionState.chooserMoves[i];
                  }
                  greedyRolloutEffectiveness++;
                  //	If we have a choosable win stop searching
                  return hintMove;
                }

                results.considerResult(decisionState.choosingRole);

                decisionState.propProcessed[i] = true;
              }
              else if ( hasNegativelyLatchedGoals() )
              {
                int newMaxAchievableOpponentScoreTotal = 0;
                for(Role role : getRoles())
                {
                  if ( !role.equals(decisionState.choosingRole))
                  {
                    getLatchedScoreRange(lastInternalSetState, role, latchedScoreRangeBuffer);

                    newMaxAchievableOpponentScoreTotal += latchedScoreRangeBuffer[1];
                  }
                }

                if ( newMaxAchievableOpponentScoreTotal < decisionState.maxAchievableOpponentScoreTotal )
                {
                  if ( getRandom(100) < latchImprovementWeight )
                  {
                    if (playedMoves != null)
                    {
                      assert(decisionState.chooserMoves[i] != null);
                      playedMoves[moveIndex] = decisionState.chooserMoves[i];
                    }

                    decisionState.nextChoiceIndex = decisionState.baseChoiceIndex;
                    if ( getRandom(100) < latchWorseningAvoidanceWeight )
                    {
                      return hintMove;
                    }
                    return null;
                  }
                }
              }

              transitioned = true;
              break;
            }
          }
        }
        else
        {
          //	Not the first time we've looked at this node
          hintMove = null;
        }
      }

      //	First time we visit the node try them all.  After that if we're asked to reconsider
      //	just take the next one from the last one we chose
      int remainingMoves = (decisionState.baseChoiceIndex == choiceIndex &&
                            preEnumerate ? decisionState.numChoices
                                        : 1);
      int choice = -1;
      int lastTransitionChoice = -1;

      for (int i = remainingMoves-1; i >= 0; i--)
      {
        choice = (i + choiceIndex) % decisionState.numChoices;

        //	Don't re-process the hint move that we looked at first unless this is the specific requested
        //  move
        if ( i > 0 )
        {
          if (decisionState.propProcessed[choice] ||
              hintMove == decisionState.chooserMoves[choice].mMove ||
              (preEnumerate && !terminatingMoveProps.contains(decisionState.chooserMoves[choice].mInputProposition)))
          {
            continue;
          }
        }

        if (transitioned)
        {
          setPropNetUsage(decisionState.state);
          setBasePropositionsFromState(decisionState.state);
        }

        chosenJointMoveProps[decisionState.chooserIndex] = decisionState.chooserMoves[choice].mInputProposition;
        lastTransitionChoice = choice;

        transitionToNextStateFromChosenMove();

        transitioned = true;

        if (isTerminal() || scoresAreLatched(lastInternalSetState))
        {
          numTerminals++;

          if ( rolloutStackDepth <= terminalCheckHorizon )
          {
            terminatingMoveProps
                .add(decisionState.chooserMoves[choice].mInputProposition);
          }

          if (getGoal(decisionState.choosingRole) == parentLatchedScoreRangeBuffer[1])
          {
            if (playedMoves != null)
            {
              assert(decisionState.chooserMoves[choice] != null);
              playedMoves[moveIndex] = decisionState.chooserMoves[choice];
            }
            if (preEnumerate)
            {
              greedyRolloutEffectiveness++;
            }

            //	If we have a choosable win stop searching
            return decisionState.chooserMoves[choice].mMove;
          }

          results.considerResult(decisionState.choosingRole);
          decisionState.propProcessed[choice] = true;
        }
        else if ( hasNegativelyLatchedGoals() )
        {
          int newMaxAchievableOpponentScoreTotal = 0;
          for(Role role : getRoles())
          {
            if ( !role.equals(decisionState.choosingRole))
            {
              getLatchedScoreRange(lastInternalSetState, role, latchedScoreRangeBuffer);

              newMaxAchievableOpponentScoreTotal += latchedScoreRangeBuffer[1];
            }
          }

          if ( newMaxAchievableOpponentScoreTotal < decisionState.maxAchievableOpponentScoreTotal )
          {
            if ( getRandom(100) < latchImprovementWeight )
            {
              if (playedMoves != null)
              {
                assert(decisionState.chooserMoves[choice] != null);
                playedMoves[moveIndex] = decisionState.chooserMoves[choice];
              }
              decisionState.nextChoiceIndex = (choiceIndex+1)%decisionState.numChoices;
              if ( getRandom(100) < latchWorseningAvoidanceWeight )
              {
                return decisionState.chooserMoves[choice].mMove;
              }
              return null;
            }
          }
        }
      }

      if ( !transitioned )
      {
        chosenJointMoveProps[decisionState.chooserIndex] = decisionState.chooserMoves[choice].mInputProposition;
        lastTransitionChoice = choice;

        transitionToNextStateFromChosenMove();
      }

      if (playedMoves != null)
      {
        assert(decisionState.chooserMoves[lastTransitionChoice] != null);
        playedMoves[moveIndex] = decisionState.chooserMoves[lastTransitionChoice];
      }

      decisionState.nextChoiceIndex = lastTransitionChoice;
      do
      {
        decisionState.nextChoiceIndex = (decisionState.nextChoiceIndex + 1) %
                                        decisionState.numChoices;
        if (!decisionState.propProcessed[decisionState.nextChoiceIndex] ||
            decisionState.nextChoiceIndex == decisionState.baseChoiceIndex)
        {
          break;
        }
      }
      while (decisionState.nextChoiceIndex != choiceIndex);

      if (preEnumerate && numTerminals > 0)
      {
        greedyRolloutEffectiveness += (decisionState.numChoices - numTerminals) /
                                      decisionState.numChoices;
      }
    }
    else
    {
      for (int roleIndex = 0; roleIndex < numRoles; roleIndex++)
      {
        chosenJointMoveProps[roleIndex] = decisionState.nonChooserProps[roleIndex];
      }
      transitionToNextStateFromChosenMove();

      if (playedMoves != null)
      {
        assert(choicelessMoveInfo != null);
        playedMoves[moveIndex] = choicelessMoveInfo;
      }
      if (isTerminal() || scoresAreLatched(lastInternalSetState))
      {
        results.considerResult(decisionState.choosingRole);
      }
    }

    return null;
    //        }
    //		finally
    //		{
    //			methodSection.exitScope();
    //		}
  }

  private boolean hasAvailableMoveForAllRoles(ForwardDeadReckonPropNet net)
  {
    for (int roleIndex = 0; roleIndex < numRoles; roleIndex++)
    {
      Collection moves = net.getActiveLegalProps(instanceId).getContents(roleIndex);

      if (moves.isEmpty())
      {
        return false;
      }
    }

    return true;
  }

  private int transitionToRandomJointMove(StateMachineFilter factor,
                                    ForwardDeadReckonLegalMoveInfo[] playedMoves,
                                    ForwardDeadReckonInternalMachineState[] statesVisited)
  {
		int result = 0;
    int numChoices;
    int choiceIndex = -1;
    int choosingRole = -1;
    boolean choiceSeen = false;
    boolean acceptableChoice = true;
    ForwardDeadReckonLegalMoveSet activeLegalMoves = getLegalMoveSet(lastInternalSetState);
    int startingDepth = rolloutDepth;
    boolean subtreeFullySearched = false;

    if ( mPlayoutPolicy != null )
    {
      mPlayoutPolicy.noteCurrentState(statesVisited == null ? lastInternalSetState : statesVisited[rolloutDepth], activeLegalMoves, factor, rolloutDepth, playedMoves, statesVisited);
    }

    do
    {
      int index = 0;
      int numChooserChoices = 1;
      ForwardDeadReckonLegalMoveInfo chooserChoice = null;
      boolean policySelectedMove = false;

      for (int roleIndex = 0; roleIndex < numRoles; roleIndex++)
      {
        ForwardDeadReckonLegalMoveSet moves = activeLegalMoves;
        ForwardDeadReckonLegalMoveInfo chosen = null;
        Iterator itr;

        if ( mPlayoutPolicy != null && playedMoves != null )
        {
          chosen = mPlayoutPolicy.selectMove(roleIndex);
        }

        if ( chosen == null )
        {
          if ( factor == null )
          {
            numChoices = moves.getNumChoices(roleIndex);
            itr = moves.getContents(roleIndex).iterator();
          }
          else
          {
            //  In a factored game the terminal logic can sometimes span the factors in a way we don't
            //  cleanly cater for currently, so use lack of legal moves as a proxy for terminality.
            if (moves.getNumChoices(roleIndex) == 0)
            {
              return 0;
            }

            numChoices = StateMachineFilterUtils.getFilteredSize(null, moves, roleIndex, factor, false);

            itr = moves.getContents(roleIndex).iterator();
          }

          if (numChoices > result)
          {
            result = numChoices;
          }

          int moveIndex;
          if ( numChoices > 1 )
          {
            if ( choosingRole != -1 && choosingRole != roleIndex )
            {
              //  Multiple roles have choices so this must be a simultaneous move game
              //  which we do not currently support playout policies in, and for which we
              //  must independently select moves for each role
              choosingRole = -1;
              choiceIndex = -1;
            }
            else
            {
              choosingRole = roleIndex;
              numChooserChoices = numChoices;
            }

            if ( choiceIndex == -1 )
            {
              choiceIndex = getRandom(numChoices);

              if ( playoutStackMoveInitialChoiceIndex != null )
              {
                playoutStackMoveInitialChoiceIndex[rolloutDepth] = choiceIndex;
              }
            }
            assert(playoutStackMoveInitialChoiceIndex == null || playoutStackMoveInitialChoiceIndex[rolloutDepth] < numChoices);
            assert(choiceIndex < numChoices);

            moveIndex = choiceIndex;
          }
          else
          {
            moveIndex = 0;
          }

          for (int iMove = 0; iMove < numChoices; iMove++)
          {
            ForwardDeadReckonLegalMoveInfo info;

            // Get next move for this factor
            info = StateMachineFilterUtils.nextFilteredMove(factor, itr);

            if (moveIndex == iMove)
            {
              chosen = info;
              if ( roleIndex == choosingRole )
              {
                chooserChoice = chosen;
              }
              break;
            }
          }
        }
        else
        {
          assert(moves.getContents(roleIndex).contains(chosen));
          policySelectedMove = true;
          numChoices = 2; //  Arbitrary number > 1
        }

        assert(chosen != null);
        if (validationMachine != null)
        {
          chosenMoves[index] = chosen.mMove;
        }
        ForwardDeadReckonProposition chosenMoveProp = chosen.mIsPseudoNoOp ? null : chosen.mInputProposition;
        chosenJointMoveProps[index++] = chosenMoveProp;
        if (playedMoves != null &&
            (numChoices > 1 ||
             (!choiceSeen &&
              ((chosenMoveProp != null && !chosen.mIsPseudoNoOp && !chosen.mIsVirtualNoOp) ||
               roleIndex == numRoles-1))))
        {
          playedMoves[rolloutDepth] = chosen;
          choiceSeen = true;
        }
      }

      transitionToNextStateFromChosenMove();

      //  Do we have a non-default policy that has not already selected an explicit move
      acceptableChoice = true;
      if ( !policySelectedMove && mPlayoutPolicy != null && playoutStackMoveInitialChoiceIndex != null && statesVisited != null && choosingRole != -1 )
      {
        assert(numChooserChoices > playoutStackMoveInitialChoiceIndex[rolloutDepth]);
        choiceIndex = (choiceIndex+1)%numChooserChoices;
        playoutStackMoveNextChoiceIndex[rolloutDepth] = choiceIndex;

        acceptableChoice = subtreeFullySearched ||
                           (mPlayoutPolicy.isAcceptableMove(chooserChoice, choosingRole) &&
                            mPlayoutPolicy.isAcceptableState(lastInternalSetState, choosingRole));

        if ( !acceptableChoice )
        {
          //LOGGER.info("Reject move " + chooserChoice + " at depth " + rolloutDepth);

          if ( choiceIndex == playoutStackMoveInitialChoiceIndex[rolloutDepth] )
          {
            while( choiceIndex == playoutStackMoveInitialChoiceIndex[rolloutDepth] )
            {
              if ( rolloutDepth > 0 &&
                   !subtreeFullySearched &&
                   mPlayoutPolicy.popStackOnAllUnacceptableMoves(startingDepth-rolloutDepth) )
              {
                //  Pop back up the stack
                rolloutDepth--;

                //LOGGER.info("Pop back to depth " + rolloutDepth);
                //System.out.println("Popped state is: " + statesVisited[rolloutDepth]);
                setPropNetUsage(statesVisited[rolloutDepth]);
                setBasePropositionsFromState(statesVisited[rolloutDepth]);
                activeLegalMoves = getLegalMoveSet(lastInternalSetState);
                choiceIndex = playoutStackMoveNextChoiceIndex[rolloutDepth];
                assert(choiceIndex < activeLegalMoves.getNumChoices(0));
                mPlayoutPolicy.noteCurrentState(statesVisited[rolloutDepth], activeLegalMoves, factor, rolloutDepth, playedMoves, statesVisited);
              }
              else
              {
                //LOGGER.info("Cannot pop any further");
                //  All possibilities have been explored so just play next path down to
                //  next required move depth (startingDepth chosen)
                subtreeFullySearched = true;
                //rolloutDepth = startingDepth;
                setPropNetUsage(statesVisited[rolloutDepth]);
                setBasePropositionsFromState(statesVisited[rolloutDepth]);
                activeLegalMoves = getLegalMoveSet(lastInternalSetState);
                choiceIndex = playoutStackMoveNextChoiceIndex[rolloutDepth];
                break;
              }
            }
          }
          else
          {
            setPropNetUsage(statesVisited[rolloutDepth]);
            setBasePropositionsFromState(statesVisited[rolloutDepth]);
            activeLegalMoves = getLegalMoveSet(lastInternalSetState);
          }
        }
        else
        {
          //LOGGER.info("Accept move " + chooserChoice + " at depth " + rolloutDepth);
        }
      }
//      if ( numRoles == 1 && statesVisited != null && getNonControlMask() != null && choiceIndex != (startingRand+result-1)%result )
//      {
//        for(int i = 0; i <= rolloutDepth; i++)
//        {
//          maskStateBuffer.copy(lastInternalSetState);
//          maskStateBuffer.xor(statesVisited[i]);
//          maskStateBuffer.intersect(getNonControlMask());
//          if ( maskStateBuffer.size() == 0 )
//          {
//            acceptableChoice = false;
//            break;
//          }
//        }
//
//        if ( !acceptableChoice )
//        {
//          setPropNetUsage(statesVisited[rolloutDepth]);
//          setBasePropositionsFromState(statesVisited[rolloutDepth]);
//        }
//      }

      if ( rolloutDepth < startingDepth && acceptableChoice )
      {
        //  If we popped a move we need to continue down again until we fill in
        //  moves down to the expected starting level
        acceptableChoice = false;
        rolloutDepth++;
        choiceIndex = -1;
        assert(statesVisited != null);
        //System.out.println("Recording new state for depth " + rolloutDepth + ": " + lastInternalSetState);
        statesVisited[rolloutDepth].copy(lastInternalSetState);

        if(isTerminal())
        {
          break;
        }

        activeLegalMoves = getLegalMoveSet(lastInternalSetState);
        mPlayoutPolicy.noteCurrentState(statesVisited[rolloutDepth], activeLegalMoves, factor, rolloutDepth, playedMoves, statesVisited);
      }
    } while(!acceptableChoice);

    return result;
  }

  private class TerminalResultSet
  {
    int                                          mChoosingRoleIndex = -1;
    public int                                   mScoreForChoosingRole = -1;
    public ForwardDeadReckonInternalMachineState mState;

    public void considerResult(Role choosingRole)
    {
      if (mChoosingRoleIndex == -1)
      {
        // We haven't yet recorded the choosing role.  Do so now - if there is one.  There won't be one if either no
        // roles have a choice or more than one role has a choice in this state.
        if (choosingRole == null)
        {
          return;
        }

        for (mChoosingRoleIndex = 0; !roles[mChoosingRoleIndex].equals(choosingRole); mChoosingRoleIndex++) {/* Spin */}
      }

      // Would this result be chosen over the previous (if any)?
      int lScoreForChoosingRole = getGoal(roles[mChoosingRoleIndex]);
      if (mState == null || (lScoreForChoosingRole > mScoreForChoosingRole))
      {
        mScoreForChoosingRole = lScoreForChoosingRole;
        if (mState == null)
        {
          mState = new ForwardDeadReckonInternalMachineState(lastInternalSetState);
        }
        else
        {
          mState.copy(lastInternalSetState);
        }
      }
    }

    public void reset()
    {
      mChoosingRoleIndex = -1;
      mScoreForChoosingRole = -1;
    }
  }

  private int     rolloutDepth;
  private boolean enableGreedyRollouts = true;
  private boolean greedyRolloutsDisabledPersistently = false;
  private ForwardDeadReckonInternalMachineState maskStateBuffer = null;

  /**
   * @return whether greedy rollouts are enabled
   */
  public boolean getIsGreedyRollouts()
  {
    return enableGreedyRollouts;
  }

  /**
   * Whether to en/disable use of greedy rollouts (defaults to enabled for a newly constructed
   * state machine)
   * @param enabled       New state
   * @param persistently  Disablement may be persistent after which no further changes are allowed
   *                      This will perform irrevocable propnet optimizations for disabled greedy rollouts
   */
  public void enableGreedyRollouts(boolean enabled, boolean persistently)
  {
    //  Legal transitions check
    assert( !enabled || !greedyRolloutsDisabledPersistently );

    if ( enabled )
    {
      enableGreedyRollouts = true;
    }
    else
    {
      enableGreedyRollouts = false;

      if ( !persistently || greedyRolloutsDisabledPersistently )
      {
        return;
      }

      //  Switch to goalless networks
      propNetO = propNetOWithoutGoals;
      propNetX = propNetXWithoutGoals;

      if (instanceId == 0)
      {
        finalizePropositionCrossReferenceInfo();  //  Onto the goalless variants now

        propNetXWithoutGoals.crystalize(masterInfoSet, firstBasePropIndex, masterLegalMoveSet, maxInstances);
        propNetOWithoutGoals.crystalize(masterInfoSet, firstBasePropIndex, masterLegalMoveSet, maxInstances);

        for(ForwardDeadReckonPropositionInfo info : masterInfoSet)
        {
          ForwardDeadReckonPropositionCrossReferenceInfo crInfo = (ForwardDeadReckonPropositionCrossReferenceInfo)info;

          crInfo.xNetPropId = crInfo.xNetProp.id;
          crInfo.oNetPropId = crInfo.oNetProp.id;
        }

        propNetXWithoutGoals.reset(true);
        propNetOWithoutGoals.reset(true);

        //  Set move factor info and virtual-noop info
        setMoveInfoForPropnet(propNetXWithoutGoals);
        setMoveInfoForPropnet(propNetOWithoutGoals);
      }

      propNet = propNetX;
      propNetInstanceInfo = propNet.animator.getInstanceInfo(instanceId);
      lastInternalSetState = null;
      lastInternalSetStateX = null;
      lastInternalSetStateO = null;

      for(int i = 0; i < numRoles; i++)
      {
        previousMovePropsX[i] = null;
        previousMovePropsO[i] = null;
      }
    }
  }

  private int totalNumMoves = 0;

  public MoveWeights createMoveWeights()
  {
    return new MoveWeights(totalNumMoves, getRolesArray().length);
  }

  public ForwardDeadReckonInternalMachineState getCurrentState()
  {
    return lastInternalSetState;
  }

  public void getDepthChargeResult(ForwardDeadReckonInternalMachineState state,
                                   PlayoutInfo info)
  {
    rolloutDepth = 0;
    boolean lUseGreedyRollouts = enableGreedyRollouts && (numRoles <= 2);

    for (int i = 0; i < numRoles; i++)
    {
      ForwardDeadReckonProposition xProp = previousMovePropsX[i];
      ForwardDeadReckonProposition oProp = previousMovePropsO[i];

      if ( xProp != null )
      {
        previousMovePropsX[i] = null;
        propNetX.animator.getInstanceInfo(instanceId).changeComponentValueTo(xProp.id, false);
      }
      if ( oProp != null )
      {
        previousMovePropsO[i] = null;
        propNetO.animator.getInstanceInfo(instanceId).changeComponentValueTo(oProp.id, false);
      }
    }

    if (validationMachine != null)
    {
      validationState = state.getMachineState();
    }
    setPropNetUsage(state);
    setBasePropositionsFromState(state);
    for (int i = 0; i < numRoles; i++)
    {
      previouslyChosenJointMovePropIdsX[i] = -1;
      previouslyChosenJointMovePropIdsO[i] = -1;
    }
    if (!lUseGreedyRollouts)
    {
      int totalChoices = 0;

      if ( mPlayoutPolicy != null && info.statesVisited != null )
      {
        if ( mMaster.mTurnNumber != mLastPlayoutTurnNumber )
        {
          mPlayoutPolicy.noteNewTurn();
          mLastPlayoutTurnNumber = mMaster.mTurnNumber;
        }
        mPlayoutPolicy.noteNewPlayout();
        if ( mPlayoutPolicy.requiresMoveHistory() )
        {
          info.recordTrace = true;
        }
        if ( mPlayoutPolicy.requiresStateHistory() )
        {
          info.recordTraceStates = true;
          if ( playoutStackMoveInitialChoiceIndex == null )
          {
            playoutStackMoveInitialChoiceIndex = new int[info.playoutTrace.length];
            playoutStackMoveNextChoiceIndex = new int[info.playoutTrace.length];
          }
        }
      }

      if ( info.recordTraceStates )
      {
        info.statesVisited[0].copy(state);
      }

      while (!isTerminal() && !scoresAreLatched(lastInternalSetState) && (mPlayoutPolicy == null || !mPlayoutPolicy.terminatePlayout()))
      {
        int numChoices = transitionToRandomJointMove(info.factor, info.playoutTrace, info.statesVisited);
        totalChoices += numChoices;
        rolloutDepth++;
        if ( info.recordTraceStates )
        {
          info.statesVisited[rolloutDepth].copy(lastInternalSetState);
        }
        if ( rolloutDepth > info.cutoffDepth )
        {
          break;
        }
      }

      if ( mPlayoutPolicy != null )
      {
        mPlayoutPolicy.noteCompletePlayout(rolloutDepth, info.playoutTrace, info.statesVisited);
      }

      info.playoutLength = rolloutDepth;
      if ( rolloutDepth > 0 )
      {
        info.averageBranchingFactor = (totalChoices + rolloutDepth / 2) / rolloutDepth;
      }
      else
      {
        info.averageBranchingFactor = 0;
      }
    }
    else
    {
      double branchingFactor = 0;

      if ( !isTerminal() )
      {
        mResultSet.reset();
        branchingFactor = recursiveGreedyRollout(mResultSet,
                                                 info.factor,
                                                 info.moveWeights,
                                                 info.playoutTrace,
                                                 info.cutoffDepth);

        if (mResultSet.mChoosingRoleIndex != -1)
        {
          setPropNetUsage(mResultSet.mState);
          setBasePropositionsFromState(mResultSet.mState);
        }

        rolloutDepth = rolloutStackDepth;
      }

      assert(rolloutStackDepth>0);
      info.playoutLength = rolloutStackDepth;
      info.averageBranchingFactor = (int)(branchingFactor + 0.5);
    }
    for (int i = 0; i < numRoles; i++)
    {
      int xId = previouslyChosenJointMovePropIdsX[i];
      int oId = previouslyChosenJointMovePropIdsO[i];

      if ( xId != -1)
      {
        propNetX.animator.getInstanceInfo(instanceId).changeComponentValueTo(xId, false);
      }
      if ( oId != -1)
      {
        propNetO.animator.getInstanceInfo(instanceId).changeComponentValueTo(oId, false);
      }
    }
  }

  public Set getFactors()
  {
    return factors;
  }

  public Set getBasePropositions()
  {
    return fullPropNet.getBasePropositions().keySet();
  }

  private ForwardDeadReckonInternalMachineState lastGoalState = null;

  public void setGoalsCalculator(GoalsCalculator calculator)
  {
    mGoalsCalculator = calculator;
  }

  /**
   * @param playoutPolicy - policy to be used for playouts made with this state machine instance
   */
  public void setPlayoutPolicy(IPlayoutPolicy playoutPolicy)
  {
//    LOGGER.info("Setting playout policy to " + playoutPolicy);
    mPlayoutPolicy = playoutPolicy;
  }

  public IPlayoutPolicy getPlayoutPolicy()
  {
    return mPlayoutPolicy;
  }

  private void setGoalNetBasePropsFromState(ForwardDeadReckonInternalMachineState state)
  {
    InternalMachineStateIterator lIterator = mStateIterator;
    ForwardDeadReckonPropnetFastAnimator.InstanceInfo instanceInfo = goalsNet.animator.getInstanceInfo(instanceId);

    if (lastGoalState != null)
    {
      if (!lastGoalState.equals(state))
      {
        lastGoalState.xor(state);

        if ( removeOldBasePropsBeforeAddingNew )
        {
          lIterator.reset(lastGoalState);
          while (lIterator.hasNext())
          {
            ForwardDeadReckonPropositionInfo info = lIterator.next();
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
            if ( infoCr.goalsNetProp != null && infoCr.goalsNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId && !state.contains(info) )
            {
              instanceInfo.changeComponentValueTo(infoCr.goalsNetProp.id, false);
            }
          }
          lIterator.reset(lastGoalState);
          while (lIterator.hasNext())
          {
            ForwardDeadReckonPropositionInfo info = lIterator.next();
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
            if ( infoCr.goalsNetProp != null && infoCr.goalsNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId && state.contains(info) )
            {
              instanceInfo.changeComponentValueTo(infoCr.goalsNetProp.id, true);
            }
          }
        }
        else
        {
          lIterator.reset(lastGoalState);
          while (lIterator.hasNext())
          {
            ForwardDeadReckonPropositionInfo info = lIterator.next();
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
            if ( infoCr.goalsNetProp != null && infoCr.goalsNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId && state.contains(info) )
            {
              instanceInfo.changeComponentValueTo(infoCr.goalsNetProp.id, true);
            }
          }
          lIterator.reset(lastGoalState);
          while (lIterator.hasNext())
          {
            ForwardDeadReckonPropositionInfo info = lIterator.next();
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
            if ( infoCr.goalsNetProp != null && infoCr.goalsNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId && !state.contains(info) )
            {
              instanceInfo.changeComponentValueTo(infoCr.goalsNetProp.id, false);
            }
          }
        }

        lastGoalState.copy(state);
      }
    }
    else
    {
      lIterator.reset(state);
      while (lIterator.hasNext())
      {
        ForwardDeadReckonPropositionInfo s = lIterator.next();
        ForwardDeadReckonPropositionCrossReferenceInfo sCr = (ForwardDeadReckonPropositionCrossReferenceInfo)s;
        if (sCr.goalsNetProp != null && sCr.goalsNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId)
        {
          if (state.contains(sCr))
          {
            instanceInfo.changeComponentValueTo(sCr.goalsNetProp.id, true);
          }
        }
      }

      lastGoalState = new ForwardDeadReckonInternalMachineState(state);
    }
  }

  private ForwardDeadReckonInternalMachineState lastTerminalityNetState = null;

  private void setTerminalityNetBasePropsFromState(ForwardDeadReckonInternalMachineState state)
  {
    InternalMachineStateIterator lIterator = mStateIterator;
    ForwardDeadReckonPropnetFastAnimator.InstanceInfo instanceInfo = terminalityNet.animator.getInstanceInfo(instanceId);

    if (lastTerminalityNetState != null)
    {
      if (!lastTerminalityNetState.equals(state))
      {
        lastTerminalityNetState.xor(state);

        if ( removeOldBasePropsBeforeAddingNew )
        {
          lIterator.reset(lastTerminalityNetState);
          while (lIterator.hasNext())
          {
            ForwardDeadReckonPropositionInfo info = lIterator.next();
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
            if ( infoCr.terminalityNetProp != null && infoCr.terminalityNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId && !state.contains(info) )
            {
              instanceInfo.changeComponentValueTo(infoCr.terminalityNetProp.id, false);
            }
          }
          lIterator.reset(lastTerminalityNetState);
          while (lIterator.hasNext())
          {
            ForwardDeadReckonPropositionInfo info = lIterator.next();
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
            if ( infoCr.terminalityNetProp != null && infoCr.terminalityNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId && state.contains(info) )
            {
              instanceInfo.changeComponentValueTo(infoCr.terminalityNetProp.id, true);
            }
          }
        }
        else
        {
          lIterator.reset(lastTerminalityNetState);
          while (lIterator.hasNext())
          {
            ForwardDeadReckonPropositionInfo info = lIterator.next();
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
            if ( infoCr.terminalityNetProp != null && infoCr.terminalityNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId && state.contains(info) )
            {
              instanceInfo.changeComponentValueTo(infoCr.terminalityNetProp.id, true);
            }
          }
          lIterator.reset(lastTerminalityNetState);
          while (lIterator.hasNext())
          {
            ForwardDeadReckonPropositionInfo info = lIterator.next();
            ForwardDeadReckonPropositionCrossReferenceInfo infoCr = (ForwardDeadReckonPropositionCrossReferenceInfo)info;
            if ( infoCr.terminalityNetProp != null && infoCr.terminalityNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId && !state.contains(info) )
            {
              instanceInfo.changeComponentValueTo(infoCr.terminalityNetProp.id, false);
            }
          }
        }

        lastTerminalityNetState.copy(state);
      }
    }
    else
    {
      lIterator.reset(state);
      while (lIterator.hasNext())
      {
        ForwardDeadReckonPropositionInfo s = lIterator.next();
        ForwardDeadReckonPropositionCrossReferenceInfo sCr = (ForwardDeadReckonPropositionCrossReferenceInfo)s;
        if (sCr.terminalityNetProp != null && sCr.terminalityNetProp.id != ForwardDeadReckonPropnetFastAnimator.notNeededComponentId)
        {
          if (state.contains(sCr))
          {
            instanceInfo.changeComponentValueTo(sCr.terminalityNetProp.id, true);
          }
        }
      }

      lastTerminalityNetState = new ForwardDeadReckonInternalMachineState(state);
    }
  }

  public int getGoal(ForwardDeadReckonInternalMachineState state, Role role)
  {
    if ( mGoalsCalculator != null )
    {
      return mGoalsCalculator.getGoalValue(state == null ? lastInternalSetState : state, role);
    }

    ForwardDeadReckonPropNet net;

    if (enableGreedyRollouts)
    {
      if (state != null)
      {
        setPropNetUsage(state);
        setBasePropositionsFromState(state);
      }

      net = propNet;
    }
    else
    {
      net = goalsNet;

      if (state == null)
      {
        state = lastInternalSetState;
      }

      setGoalNetBasePropsFromState(state);

//      if (lastGoalState == null)
//      {
//        for (PolymorphicProposition p : net.getBasePropositionsArray())
//        {
//          net.setProposition(instanceId, (ForwardDeadReckonProposition)p, false);
//          //((ForwardDeadReckonProposition)p).setValue(false, instanceId);
//        }
//
//        lIterator.reset(state);
//        while (lIterator.hasNext())
//        {
//          ForwardDeadReckonPropositionInfo s = lIterator.next();
//          ForwardDeadReckonPropositionCrossReferenceInfo scr = (ForwardDeadReckonPropositionCrossReferenceInfo)s;
//          if (scr.goalsNetProp != null)
//          {
//            net.setProposition(instanceId, scr.goalsNetProp, true);
//            //scr.goalsNetProp.setValue(true, instanceId);
//          }
//        }
//
//        if (lastGoalState == null)
//        {
//          lastGoalState = new ForwardDeadReckonInternalMachineState(state);
//        }
//        else
//        {
//          lastGoalState.copy(state);
//        }
//      }
//      else if (!state.equals(lastGoalState))
//      {
//        if (nextGoalState == null)
//        {
//          nextGoalState = new ForwardDeadReckonInternalMachineState(state);
//        }
//        else
//        {
//          nextGoalState.copy(state);
//        }
//
//        lastGoalState.xor(state);
//
//        lIterator.reset(lastGoalState);
//        while (lIterator.hasNext())
//        {
//          ForwardDeadReckonPropositionInfo info = lIterator.next();
//          ForwardDeadReckonProposition goalsNetProp = ((ForwardDeadReckonPropositionCrossReferenceInfo)info).goalsNetProp;
//          if (goalsNetProp != null)
//          {
//            if (nextGoalState.contains(info))
//            {
//              net.setProposition(instanceId, goalsNetProp, true);
//              //goalsNetProp.setValue(true, instanceId);
//            }
//            else
//            {
//              net.setProposition(instanceId, goalsNetProp, false);
//              //goalsNetProp.setValue(false, instanceId);
//            }
//          }
//        }
//
//        lastGoalState.copy(nextGoalState);
//      }


    }

    //  HACK - for factored games we might be determining terminality
    //  based on factor termination through lack of legal moves, but not
    //  actually have a complete game state that registers as terminal.
    //  In such cases the goal network may also rely on global terminality
    //  and we attempt to best-guess the actual goal in this case.  Specifically
    //  if the game is terminal in a factor but not globally terminal, and all
    //  roles report the same score, we ASSUME it should be a normalized draw
    //  with all scoring 50.
    if ( factors != null && !isTerminalUnfactored() && isTerminal() )
    {
      int observedResult = -1;
      boolean goalDifferentiated = false;
      boolean roleSeen = false;

      for(Role r : getRoles())
      {
        int value = extractRoleGoal(net, role);

        if ( observedResult == -1 )
        {
          observedResult = value;
        }
        else if ( observedResult != value )
        {
          if ( roleSeen )
          {
            break;
          }

          observedResult = value;
          goalDifferentiated = true;
        }

        if ( role.equals(r) )
        {
          roleSeen = true;
          if ( goalDifferentiated )
          {
            break;
          }
        }
      }

      if ( !goalDifferentiated )
      {
        observedResult = 50;
      }

      return observedResult;
    }

    return extractRoleGoal(net, role);
  }

  private int extractRoleGoal(ForwardDeadReckonPropNet net, Role role)
  {
    PolymorphicProposition[] goalProps = net.getGoalPropositions().get(role);
    int result = 0;

    for (PolymorphicProposition p : goalProps)
    {
      if ( net.getActiveBaseProps(instanceId).contains(((ForwardDeadReckonProposition)p).getInfo()) )
      {
        result = Integer.parseInt(p.getName().getBody().get(1).toString());
        break;
      }
    }

    return result;
  }

  private Random randomGen = new Random();

  public void setRandomSeed(long seed)
  {
    randomGen.setSeed(seed);
  }

  protected int getRandom(int n)
  {
    return randomGen.nextInt(n);
  }

  /**
   * Returns goals from a terminal state derived from repeatedly making random joint moves
   * until reaching the end of the game.
   *
   * @param theDepth an integer array, the 0th element of which will be set to
   * the number of state changes that were made to reach a terminal state.
   */
  public void performDepthCharge(MachineState state, final int[] theDepth) throws GameDescriptionException {
      performDepthCharge(createInternalState(state), theDepth);
  }

  /**
   * Returns goals from a terminal state derived from repeatedly making random joint moves
   * until reaching the end of the game.
   *
   * @param theDepth an integer array, the 0th element of which will be set to
   * the number of state changes that were made to reach a terminal state.
   */
  public void performDepthCharge(ForwardDeadReckonInternalMachineState state, final int[] theDepth) throws GameDescriptionException {
      int nDepth = 0;
      while(!isTerminal(state)) {
          nDepth++;
          state = getNextState(state, getRandomJointMove(state));
      }
      if(theDepth != null)
          theDepth[0] = nDepth;
//      return getGoals(state);
  }

  private Map roleIndices = null;
  /**
   * Returns a mapping from a role to the index of that role, as in
   * the list returned by {@link #getRoles()}. This may be a faster
   * way to check the index of a role than calling {@link List#indexOf(Object)}
   * on that list.
   */
  public Map getRoleIndices()
  {
      if (roleIndices == null) {
          ImmutableMap.Builder roleIndicesBuilder = ImmutableMap.builder();
          List roles = getRoles();
          for (int i = 0; i < roles.size(); i++) {
              roleIndicesBuilder.put(roles.get(i), i);
          }
          roleIndices = roleIndicesBuilder.build();
      }

      return roleIndices;
  }

  @Override
  public int getGoal(ForwardDeadReckonInternalMachineState state, int roleIndex) throws GameDescriptionException {
      return getGoal(state, roles[roleIndex]);
  }

  @Override
  public List getLegalMoves(ForwardDeadReckonInternalMachineState state, int roleIndex) throws GameDescriptionException {
      Role role = roles[roleIndex];
      List result;

      ForwardDeadReckonLegalMoveSet moveSet = getLegalMoveSet(state);

      result = new LinkedList<>();
      for (ForwardDeadReckonLegalMoveInfo moveInfo : moveSet.getContents(role))
      {
        result.add(moveInfo);
      }

      return result;
  }

  private final Translator translator = new Translator() {
    @Override
    public GdlTerm getGdlMove(ForwardDeadReckonLegalMoveInfo move) {
        return move.mMove.getContents();
    }

    @Override
    public ForwardDeadReckonLegalMoveInfo getNativeMove(ForwardDeadReckonInternalMachineState state, int roleIndex, GdlTerm move) {

        setPropNetUsage(state);

        Map inputProps = propNet.getInputPropositions();
        Map legalInputMap = propNet.getLegalInputMap();

        GdlSentence moveSentence = toDoes(move, roleIndex);
        ForwardDeadReckonProposition moveInputProposition = (ForwardDeadReckonProposition)inputProps.get(moveSentence);
        ForwardDeadReckonLegalMoveInfo moveInfo;

        if (moveInputProposition != null)
        {
            ForwardDeadReckonProposition legalProp = (ForwardDeadReckonProposition)legalInputMap.get(moveInputProposition);

            moveInfo = propNet.getMasterMoveList()[legalProp.getInfo().index];
        }
        else
        {
            moveInfo = new ForwardDeadReckonLegalMoveInfo();

            moveInfo.mIsPseudoNoOp = true;
        }
        moveInfo.mMove = new Move(move);
        return moveInfo;
    }

    @Override
    public Set getGdlState(ForwardDeadReckonInternalMachineState state) {
        return state.getMachineState().getContents();
    }

    @Override
    public ForwardDeadReckonInternalMachineState getNativeState(Set state) {
        return createInternalState(new MachineState(state));
    }
  };

  @Override
  public Translator getTranslator() {
      return translator;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy