private static void SokobanRandom(string levelPath, int maxDepth, int iterations, uint seed)
        {
            string[]           levels       = ReadSokobanLevels(levelPath);
            int                randomSolved = 0;
            List <IPuzzleMove> moves        = null;

            RNG.Seed(seed + threadIndex);
            MersenneTwister rng;


            for (int i = 0; i < levels.Length; i++)
            {
                RNG.Seed(seed + threadIndex);
                rng = new MersenneTwister(seed + threadIndex);
                IPuzzleState state   = new AbstractSokobanState(levels[i], RewardType.R0, useNormalizedPosition, useGoalMacro, useTunnelMacro, useGoalCut, null, rng);
                IPuzzleMove  move    = null;
                int          restart = 0;
                IPuzzleState clone   = state.Clone();
                string       solution;
                while (!state.EndState() && restart < iterations)
                {
                    moves = new List <IPuzzleMove>();
                    state = clone.Clone();
                    int count = 0;

                    while (!state.isTerminal() && count < maxDepth)
                    {
                        move = state.GetRandomMove();
                        moves.Add(move);
                        state.DoMove(move);
                        count++;
                    }

                    restart++;
                }
                if (state.EndState())
                {
                    solution = "";
                    int pushCount = 0;
                    foreach (SokobanPushMove push in moves)
                    {
                        foreach (IPuzzleMove m in push.MoveList)
                        {
                            if (m > 3)
                            {
                                pushCount++;
                            }
                            solution += m;
                        }
                    }
                    Log("Level " + (i + 1) + "\titerations: " + restart + "\tsolution length:  (moves/pushes)" + solution.Length + "/" + pushCount + "\tsolution: " + solution);
                    randomSolved++;
                }
                else
                {
                    Log("Level " + (i + 1) + "\tNo solution found");
                }
            }
            Log("Solved: " + randomSolved + "/" + levels.Length);
        }
        /// <summary>
        /// Takes in a state and returns all the states that can be reached from that state via player input.
        /// In our case, we have one transition for each color that the player can paint each area.
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        public IPuzzleState[] GetReachableStates(IPuzzleState state)
        {
            KamiState kamiState = state as KamiState; //Cast IPuzzleState instance to KamiState so that specific information can be extracted.

            if (kamiState == null)                    //If the state is not a KamiState, something has gone wrong. Print a debug message and return an empty array;
            {
                Debug.Log("State was not castable to KamiState.");
                return(new KamiState[0]);
            }

            //Make a list of states that can be reached from the current state by player input.
            //If I were optimizing I would use an array since we can calculate the exact number of transitions: (numberOfColors -1) * areas. Readability would suffer though
            List <IPuzzleState> reachableStates = new List <IPuzzleState>();

            foreach (ColorArea area in kamiState.areas)                                    //foreach area...
            {
                for (int paintColor = 0; paintColor < mNumberOfColors; paintColor++)       //foreach color that are could be painted...
                {
                    if (area.color != paintColor)                                          //An area can't be painted its current color.
                    {
                        reachableStates.Add(TransitionState(kamiState, area, paintColor)); //Call TransitionState to change color of area and check adjacent areas for merge.
                    }
                }
            }

            return(reachableStates.ToArray()); //reachableStates could be an array to begin with; more optimal but less readable.
        }
Beispiel #3
0
        public List <IPuzzleMove> GetSolution(IPuzzleState gameState)
        {
            iterations = mcts.IterationsExecuted;
            List <IPuzzleMove> solution = mcts.Solve(gameState, iterations);

            return(solution);
        }
        //Check whether the state is a solution to the puzzle.
        public bool CheckForSolution(IPuzzleState state)
        {
            KamiState kamiState = state as KamiState;

            if (kamiState == null) //If the state is not a KamiState, something has gone wrong. Print a debug message and return an empty array;
            {
                Debug.Log("State was not castable to KamiState.");
                return(false);
            }

            if (kamiState.areas.Length <= 1)
            {
                return(true); //if there is only one area remaining, the puzzle is solved.
            }
            else
            { //Later levels have discontinous areas that must be set to the same color.
                int firstColor = kamiState.areas[0].color;
                for (int i = 1; i < kamiState.areas.Length; i++)
                {
                    if (kamiState.areas[i].color != firstColor)
                    {
                        return(false); //If there is any mismatch, the state is not a solution.
                    }
                }
                return(true); //If they all match, the solution has been reached.
            }
        }
Beispiel #5
0
 public Opt_SP_UCTTreeNode(IPuzzleMove move, Opt_SP_UCTTreeNode parent, IPuzzleState state, MersenneTwister rng, bool ucb1Tuned, bool rave, double raveThreshold, bool nodeRecycling, double const_C = 1, double const_D = 20000, bool generateUntriedMoves = true)
 {
     Move               = move;
     this.parent        = parent;
     this.const_C       = const_C;
     this.const_D       = const_D;
     rnd                = rng;
     childNodes         = new List <Opt_SP_UCTTreeNode>();
     NextLRUElem        = null;
     PrevLRUElem        = null;
     SetActive          = false;
     wins               = 0;
     visits             = 0;
     squares_rewards    = 0;
     RAVEwins           = 0;
     RAVEvisits         = 0;
     squaredReward      = 0;
     topScore           = double.MinValue;
     this.ucb1Tuned     = ucb1Tuned;
     this.rave          = rave;
     this.raveThreshold = raveThreshold;
     this.nodeRecycling = nodeRecycling;
     if (generateUntriedMoves)
     {
         untriedMoves = state.GetMoves();
     }
 }
        public virtual ISPTreeNode AddChild(IPuzzleMove move, IPuzzleState state)
        {
            untriedMoves.Remove(move);
            SP_UCTTreeNode n = new SP_UCTTreeNode(move, this, state, rnd, const_C, const_D, true);

            childNodes.Add(n);
            return(n);
        }
 /// <summary>
 /// Register discovered states in stateNodes dictionary.
 /// </summary>
 /// <param name="state"></param>
 /// <param name="depth"></param>
 private void RecordState(IPuzzleState state, int depth)
 {
     if (!stateNodes.ContainsKey(state))
     { //Only create, record, and queue state for exploration if it is new.
         StateNode node = new StateNode(state, depth);
         stateNodes.Add(state, node);
         mNodesToBeExplored.Enqueue(node);
     }
 }
        private static void SokobanIDAStarTest(string levelPath, int maxCost, RewardType rewardType, int maxTableSize, bool useNormalizedPosition, bool useTunnelMacro, bool useGoalMacro, bool useGoalCut)
        {
            string[]       levels       = ReadSokobanLevels(levelPath);
            IPuzzleState[] states       = new IPuzzleState[levels.Length];
            int            solvedLevels = 0;
            Stopwatch      stopwatch    = new Stopwatch();
            long           stateInitTime;
            long           solvingTime;

            //GoalMacroWrapper.BuildMacroTree(null);
            for (int i = 0; i < states.Length; i++)
            {
                stopwatch.Restart();
                states[i] = new AbstractSokobanState(levels[i], rewardType, useNormalizedPosition, useGoalMacro, useTunnelMacro, useGoalCut, null);
                stopwatch.Stop();
                stateInitTime = stopwatch.ElapsedMilliseconds;
                IDAStarSearch idaStar = new IDAStarSearch();
                //Log("Level" + (i + 1) + ":\n" + states[i].PrettyPrint());
                stopwatch.Restart();
                List <IPuzzleMove> solution = idaStar.Solve(states[i], maxCost, maxTableSize, 700);
                stopwatch.Stop();
                solvingTime = stopwatch.ElapsedMilliseconds;
                string moves     = "";
                int    pushCount = 0;

                if (solution != null)
                {
                    foreach (IPuzzleMove m in solution)
                    {
                        //Debug.WriteLine(states[i]);
                        //Debug.WriteLine(m);
                        SokobanPushMove push = (SokobanPushMove)m;
                        foreach (IPuzzleMove basicMove in push.MoveList)
                        {
                            moves += basicMove;
                            if (basicMove.move > 3)//the move is a push move
                            {
                                pushCount++;
                            }
                        }

                        states[i].DoMove(m);
                    }
                    if (states[i].EndState())
                    {
                        solvedLevels++;
                    }
                    Log("Level " + (i + 1) + " solved: " + (states[i].EndState()) + " with " + idaStar.NodeCount + " nodes; solution length:" + moves.Count() + "/" + pushCount + " - Init Time: " + TimeFormat(stateInitTime) + " - Solving Time: " + TimeFormat(solvingTime));
                    Log("Moves: " + moves);
                    Log("Solved " + solvedLevels + "/" + (i + 1));
                    Console.Write("\rSolved " + solvedLevels + "/" + (i + 1));
                }
            }
        }
        public List <IPuzzleMove> Solve(IPuzzleState rootState, int iterations, double maxTimeInMinutes = 5)
        {
            topScore    = double.MinValue;
            bestRollout = null;
            IPuzzleMove        bestMove = Search(rootState, iterations, maxTimeInMinutes);
            List <IPuzzleMove> moves    = new List <IPuzzleMove>()
            {
                bestMove
            };

            moves.AddRange(bestRollout);
            return(moves);
        }
 public SP_UCTTreeNode(IPuzzleMove move, SP_UCTTreeNode parent, IPuzzleState state, MersenneTwister rng, double const_C = 1, double const_D = 20000, bool generateUntriedMoves = true)
 {
     this.move     = move;
     this.parent   = parent;
     this.const_C  = const_C;
     this.const_D  = const_D;
     rnd           = rng;
     childNodes    = new List <SP_UCTTreeNode>();
     wins          = 0;
     visits        = 0;
     squaredReward = 0;
     topScore      = double.MinValue;
     if (generateUntriedMoves)
     {
         untriedMoves = state.GetMoves();
     }
 }
Beispiel #11
0
 public IPuzzleMove selectMove(IPuzzleState gameState)
 {
     if (rnd.NextDouble() < inertiaProbability)
     {
         List <IPuzzleMove> moves = gameState.GetMoves();
         SokobanGameState   state = (SokobanGameState)gameState;
         foreach (IPuzzleMove m in moves)
         {
             SokobanPushMove push = (SokobanPushMove)m;
             if (push.MoveList.Count() == 0)
             {
                 return(m);
             }
         }
     }
     return(gameState.GetRandomMove());
 }
Beispiel #12
0
        public IPuzzleMove selectMove(IPuzzleState gameState)
        {
            List <IPuzzleMove> moves = gameState.GetMoves();

            if (rnd.NextDouble() <= 0.00007) //epsilon greedy
            {
                return(moves[rnd.Next(moves.Count)]);
            }
            moves.RemoveAll(item => gameState.GetBoard(SamegameGameMove.GetX(item), SamegameGameMove.GetY(item)) == selectedColor);
            if (moves.Count == 0)
            {
                moves = gameState.GetMoves();
            }
            IPuzzleMove selectedMove = moves[rnd.Next(moves.Count)];

            return(selectedMove);
        }
 public IPuzzleMove selectMove(IPuzzleState gameState)
 {
     //if (moveList != null)
     //{
     //    if (moveList.Count > 0)
     //    {
     //        IPuzzleMove move = moveList[0];
     //        moveList.RemoveAt(0);
     //        return move;
     //    }
     //    else
     //    {
     //        moveList = null;
     //        return (IPuzzleMove)(-1);
     //    }
     //}
     //else
     //{
     //    moveList = idaStar.Solve(gameState, maxNodes, tableSize, maxDepth);
     //    if (moveList.Count == 0)
     //    {
     //        return (IPuzzleMove)(-1);
     //    }
     //    IPuzzleMove move = moveList[0];
     //    moveList.RemoveAt(0);
     //    return move;
     //}
     if (RNG.NextDouble() < epsilon)
     {
         return(gameState.GetMoves()[RNG.Next(gameState.GetMoves().Count)]);
     }
     moveList = idaStar.Solve(gameState, maxNodes, tableSize, maxDepth);
     if (moveList.Count > 0)
     {
         return(moveList[0]);
     }
     else
     {
         return((IPuzzleMove)(-1));
     }
 }
        private static void SamegameIDAStarTest(string levelPath, int maxCost, int tableSize)//TODO Need a good heuristic for samegame
        {
            string[]       levels       = ReadSamegameLevels(levelPath);
            IPuzzleState[] states       = new IPuzzleState[levels.Length];
            int            solvedLevels = 0;
            double         totalScore   = 0;

            for (int i = 0; i < states.Length; i++)
            {
                states[i] = new SamegameGameState(levels[i], null, null);
                IDAStarSearch idaStar = new IDAStarSearch();
                Log("Level" + (i + 1) + ":\n" + states[i].PrettyPrint());
                List <IPuzzleMove> solution = null;
                string             moves    = "";
                while (!states[i].isTerminal())
                {
                    solution = idaStar.Solve(states[i], maxCost, tableSize, 100);
                    if (solution.Count > 0)
                    {
                        moves += solution[0];
                        states[i].DoMove(solution[0]);
                    }
                    else
                    {
                        break;
                    }
                }
                if (states[i].EndState())
                {
                    solvedLevels++;
                }
                totalScore += states[i].GetResult();
                Log("Level " + (i + 1) + " solved: " + (states[i].EndState()) + " solution length:" + moves.Count() + " Score: " + states[i].GetResult());
                Log("Moves: " + moves);
                Log("Solved " + solvedLevels + "/" + (i + 1));
                Console.Write("\rSolved " + solvedLevels + "/" + (i + 1));
            }
            Log("Total score: " + totalScore);
        }
Beispiel #15
0
        public IPuzzleMove selectMove(IPuzzleState gameState)
        {
            List <IPuzzleMove> moves    = gameState.GetMoves();
            IPuzzleMove        bestMove = null;

            if (rng.NextDouble() > epsilon)
            {
                IPuzzleState       clone     = gameState.Clone();
                double             maxReward = double.MinValue;
                List <IPuzzleMove> bestMoves = new List <IPuzzleMove>();
                foreach (IPuzzleMove move in clone.GetMoves())
                {
                    clone.DoMove(move);
                    double result = clone.GetResult();
                    if (result > maxReward)
                    {
                        bestMoves.Clear();
                        bestMoves.Add(move);
                        maxReward = result;
                        //bestMove = move;
                    }
                    else if (result == maxReward)
                    {
                        bestMoves.Add(move);
                    }
                    clone = gameState.Clone();
                }
                //return bestMoves[0];
                return(bestMoves[rng.Next(bestMoves.Count())]);
            }
            else
            {
                bestMove = gameState.GetRandomMove();
            }
            return(bestMove);
        }
Beispiel #16
0
 public IPuzzleMove selectMove(IPuzzleState gameState)
 {
     return(mcts.Search(gameState, iterations));
 }
Beispiel #17
0
 public IPuzzleMove selectMove(IPuzzleState gameState)
 {
     return(gameState.GetRandomMove());
 }
Beispiel #18
0
 public AStarNode(IPuzzleState state, IPuzzleMove move, AStarNode parent)
 {
     this.state  = state;
     this.move   = move;
     this.parent = parent;
 }
 public ISPTreeNode AddChild(ObjectPool objectPool, IPuzzleMove move, IPuzzleState state)
 {
     throw new NotImplementedException();
 }
 public BFSNodeState(IPuzzleState state, IPuzzleMove move, BFSNodeState parent)
 {
     this.state  = state;
     this.move   = move;
     this.parent = parent;
 }
 public StateNode(IPuzzleState puzzleState, int depthInGraph)
 {
     state = puzzleState;
     depth = depthInGraph;
 }
 public ISPTreeNode GenRootNode(IPuzzleState rootState)
 {
     return(new SP_UCTTreeNode(null, null, rootState, rnd, const_C, const_D, true));
 }
        public IPuzzleMove Search(IPuzzleState rootState, int iterations, double maxTimeInMinutes = 5)
        {
            IterationsForFirstSolution = -1;
            nodeCount        = 0;
            nodesEliminated  = 0;
            nodesNotExpanded = 0;
            bool looped;

            if (!search)
            {
                search = true;
            }
            double minReward = double.MaxValue;
            double maxReward = double.MinValue;

            maxDepth = 0;
            // If needed clean the pool, restore all objects in the pool to the initial value
            if (objectPool.NeedToClean)
            {
                objectPool.CleanObjectPool();
            }

            ISPTreeNode rootNode = treeCreator.GenRootNode(rootState);
            ISPTreeNode head     = null;
            ISPTreeNode tail     = null;

            HashSet <IPuzzleMove> allFirstMoves  = new HashSet <IPuzzleMove>();
            List <IPuzzleMove>    currentRollout = new List <IPuzzleMove>();

            solutionHashes = new HashSet <int>();

#if PROFILING
            long beforeMemory = GC.GetTotalMemory(false);
            long afterMemory  = GC.GetTotalMemory(false);
            long usedMemory   = afterMemory - beforeMemory;
            long averageUsedMemoryPerIteration = 0;
#endif
            int deadlocksInTree = 0;
            int currentDepth    = 0;
            for (iterationsExecuted = 0; iterationsExecuted < iterations; iterationsExecuted++)
            {
                looped = false;
                ISPTreeNode  node  = rootNode;
                IPuzzleState state = rootState.Clone();
                //Debug.WriteLine(node.TreeToString(0));
                HashSet <IPuzzleState> visitedStatesInRollout = new HashSet <IPuzzleState>()
                {
                    state.Clone()
                };

                // Clear lists of moves used for RAVE updates && best rollout
                solutionHash   = 27;
                currentRollout = new List <IPuzzleMove>();
                allFirstMoves.Clear();

                // Select
                while (!node.HasMovesToTry() && node.HasChildren())
                {
                    // UCB1-Tuned and RAVE Optimizations
                    node = node.SelectChild();
                    state.DoMove(node.Move);
                    visitedStatesInRollout.Add(state.Clone());
                    // RAVE Optimization && best rollout
                    currentRollout.Add(node.Move);
                    UpdateSolutionHash(node.Move);
                    allFirstMoves.Add(node.Move);

                    // Node Recycling Optimization
                    if (((Opt_SP_UCTTreeNode)node).NodeRecycling)
                    {
                        // Non-leaf node removed from LRU queue during playout
                        if (node.NextLRUElem != null && node.PrevLRUElem != null)
                        {
                            LRUQueueManager.LRURemoveElement(ref node, ref head, ref tail);
                        }
                    }
                }
                IPuzzleState backupState = state.Clone();

                if (!node.HasChildren() && !node.HasMovesToTry())
                {
                    deadlocksInTree++;
                }
                else
                {
                    Debug.Write("");
                }

                // Expand
                if (node.HasMovesToTry())
                {
                    IPuzzleMove move = node.SelectUntriedMove();
                    if (move != -1)
                    {
                        state.DoMove(move);

                        // Node Recycling Optimization
                        if (((Opt_SP_UCTTreeNode)node).NodeRecycling)
                        {
                            if (memoryBudget == nodeCount && head != null)
                            {
                                head.ChildRecycle();
                                nodeCount--;
                                // Change LRU queue head when it becomes a leaf node
                                if (!head.HasChildren())
                                {
                                    LRUQueueManager.LRURemoveFirst(ref head, ref tail);
                                }
                            }
                        }

                        if (visitedStatesInRollout.Contains(state))
                        {
                            if (avoidCycles)
                            {
                                while (node.GetUntriedMoves().Count > 0 && visitedStatesInRollout.Contains(state))
                                {
                                    state = backupState.Clone();
                                    move  = node.GetUntriedMoves()[RNG.Next(node.GetUntriedMoves().Count)];
                                    state.DoMove(move);
                                    node.RemoveUntriedMove(move);
                                }
                                if (!visitedStatesInRollout.Contains(state)) //found valid move
                                {
                                    node = node.AddChild(objectPool, move, state);
                                    UpdateSolutionHash(move);
                                    currentRollout.Add(move);
                                    allFirstMoves.Add(move);
                                    nodeCount++;
                                }
                                else //all moves visited
                                {
                                    nodesNotExpanded++;
                                    state = backupState;
                                }
                            }
                            else
                            {
                                nodesNotExpanded++;
                                looped = true;
                            }
                        }
                        else
                        {
                            node = node.AddChild(objectPool, move, state);
                            // RAVE Optimization && best rollout
                            UpdateSolutionHash(move);
                            currentRollout.Add(move);
                            allFirstMoves.Add(move);
                            nodeCount++;
                        }
                        visitedStatesInRollout.Add(state.Clone());
                    }
                    else
                    {
                        state.Pass();
                    }
                }
                else
                {
                    nodesNotExpanded++;
                }


                // Rollout
                while (!state.isTerminal() && !looped)
                {
                    var move = state.GetSimulationMove();
                    backupState = state.Clone();
                    if (move != -1)
                    {
                        state.DoMove(move);
                        if (visitedStatesInRollout.Contains(state))
                        {
                            if (avoidCycles)
                            {
                                state = backupState.Clone();
                                List <IPuzzleMove> availableMoves = state.GetMoves();
                                while (availableMoves.Count > 0 && visitedStatesInRollout.Contains(state))
                                { //keep trying different moves until we end up in an unvisited state
                                    state = backupState.Clone();
                                    move  = availableMoves[RNG.Next(availableMoves.Count)];
                                    availableMoves.Remove(move);
                                    state.DoMove(move);
                                }
                                if (availableMoves.Count == 0 && visitedStatesInRollout.Contains(state))//all states have already been visited
                                {
                                    break;
                                }
                            }
                            else
                            {
                                looped = true;
                            }
                        }
                        // RAVE Optimization && best rollout
                        UpdateSolutionHash(move);
                        currentRollout.Add(move);
                        allFirstMoves.Add(move);
                        visitedStatesInRollout.Add(state.Clone());
                    }
                    else //simulation ended
                    {
                        break;
                        //state.Pass();
                    }
                }

                //Keep topScore and update bestRollout
                double result = state.GetResult();
                minReward = Math.Min(result, minReward);
                maxReward = Math.Max(result, maxReward);
                if (state.EndState() && !solutionHashes.Contains(solutionHash))
                {
                    solutionHashes.Add(solutionHash);
                    solutionCount++;
                    if (iterationsForFirstSolution < 0)
                    {
                        iterationsForFirstSolution = iterationsExecuted + 1;
                    }
                }
                if (result > topScore || result == topScore && currentRollout.Count < bestRollout.Count)
                {
                    topScore    = result;
                    bestRollout = currentRollout;
                    if (state.EndState() && stopOnResult)
                    {
                        iterationsExecuted++;
                        break;
                    }
                }


                // Backpropagate
                currentDepth = 0;
                while (node != null)
                {
                    if (looped)
                    {
                        //TODO penalize score for loops?
                    }
                    ISPTreeNode parent = node.Parent;
                    //if a node is a dead end remove it from the tree
                    if (!node.HasChildren() && !node.HasMovesToTry() && !state.EndState() && useNodeElimination)
                    {
                        if (node.Parent == null)//unsolvable level. The tree has been completely explored. Return current best score
                        {
                            //SinglePlayerMCTSMain.Log("Unsolvable Level");
                            //Console.WriteLine("\nUnsolvable Level");
                            break;
                        }
                        node.Parent.RemoveChild(node);
                        nodeCount--;
                        nodesEliminated++;
                        currentDepth--;
                    }

                    // RAVE Optimization
                    node.Update(result, allFirstMoves);
                    node = parent;
                    currentDepth++;
                    // Node Recycling Optimization
                    if (((Opt_SP_UCTTreeNode)rootNode).NodeRecycling)
                    {
                        // Non-leaf node pushed back to LRU queue when updated
                        if (node != rootNode && node != null && node.HasChildren())
                        {
                            LRUQueueManager.LRUAddLast(ref node, ref head, ref tail);
                        }
                    }
                }

                maxDepth = Math.Max(maxDepth, currentDepth);

                if (!rootNode.HasChildren() && !rootNode.HasMovesToTry())
                {
                    break;
                }

                if (!search)
                {
                    search = true;
                    return(null);
                }

                #if PROFILING
                afterMemory = GC.GetTotalMemory(false);
                usedMemory  = afterMemory - beforeMemory;
                averageUsedMemoryPerIteration = usedMemory / (i + 1);

                var outStringToWrite = string.Format(" optMCTS search: {0:0.00}% [{1} of {2}] - Total used memory B(MB): {3}({4:N7}) - Average used memory per iteration B(MB): {5}({6:N7})\n",
                                                     (float)((i + 1) * 100) / (float)iterations, i + 1, iterations, usedMemory, usedMemory / 1024 / 1024, averageUsedMemoryPerIteration,
                                                     (float)averageUsedMemoryPerIteration / 1024 / 1024);
                    #if DEBUG
                if (showMemoryUsage)
                {
                    Console.Write(outStringToWrite);
                    Console.SetCursorPosition(0, Console.CursorTop);
                }
                    #endif
                #endif

                //Console.WriteLine(rootNode.TreeToString(0));
            }
            //Console.WriteLine();

            objectPool.NeedToClean = true;

            //#if DEBUG
            //    Console.WriteLine(rootNode.ChildrenToString());
            //    Console.WriteLine(rootNode.TreeToString(0));
            //#endif


            IPuzzleMove bestMove;
            if (bestRollout != null && bestRollout.Count > 0) //Remove first move from rollout so that if the topScore is not beaten we can just take the next move on the next search
            {
                bestMove = bestRollout[0];
                bestRollout.RemoveAt(0);
            }
            else
            {
                bestMove = rootNode.GetBestMove();
            }
            Debug.WriteLine(rootNode.TreeToString(0));
            Debug.WriteLine("Min Reward: " + minReward + " - Max Reward: " + maxReward);
            visits     = new List <int>();
            raveVisits = new List <int>();
            CountVisits((Opt_SP_UCTTreeNode)rootNode, visits, raveVisits);

            visits.Sort((x, y) => (x.CompareTo(y)));
            raveVisits.Sort((x, y) => (x.CompareTo(y)));
            //string visitsString = LogVisits((Opt_SP_UCTTreeNode) rootNode);
            //SinglePlayerMCTSMain.Log("Iterations: "+IterationsExecuted+" NodeCount: " + nodeCount+" "+visitsString);
            return(bestMove);
        }
        private static int[] SokobanTest(double const_C, double const_D, int iterations, int restarts, string[] levels, uint seed, bool abstractSokoban, RewardType rewardType, bool stopOnResult, double epsilonValue, bool log)
        {
            uint threadIndex = GetThreadIndex();

            RNG.Seed(seed + threadIndex);
            MersenneTwister       rng = new MersenneTwister(seed + threadIndex);
            int                   currentLevelIndex = GetTaskIndex(threadIndex);
            ISPSimulationStrategy simulationStrategy;

            simulationStrategy = new SokobanEGreedyStrategy(epsilonValue, rng);
            IPuzzleState[] states = new IPuzzleState[levels.Length];

            //SokobanMCTSStrategy player;

            //Debug.WriteLine("Solved random: "+randomSolved+"/"+levels.Length);

            //    Debug.WriteLine(restart);


            int solvedLevels = 0;

            int[]      rolloutsCount = new int[states.Length];
            Stopwatch  stopwatch     = new Stopwatch();
            long       stateInitializationTime;
            long       solvingTime;
            int        totalRollouts         = 0;
            int        totalNodes            = 0;
            List <int> visitsList            = new List <int>();
            List <int> raveVisitsList        = new List <int>();
            double     totalDepth            = 0;
            int        totalNodesEliminated  = 0;
            int        totalNodesNotExpanded = 0;

            for (int i = 0; i < states.Length; i++)
            {
                RNG.Seed(seed + threadIndex);
                rng = new MersenneTwister(seed + threadIndex);
                if (simulationType == SimulationType.EpsilonGreedy)
                {
                    simulationStrategy = new SokobanEGreedyStrategy(epsilonValue, rng);
                }
                else
                {
                    simulationStrategy = new SokobanIDAstarStrategy(maxNodes, tableSize, 200, 0.2);
                }
                if (i % SinglePlayerMCTSMain.threadIndex != threadIndex)
                {
                    continue;
                }
                stopwatch.Restart();
                if (abstractSokoban)
                {
                    states[i] = new AbstractSokobanState(levels[i], rewardType, useNormalizedPosition, useGoalMacro, useTunnelMacro, useGoalCut, simulationStrategy, rng);
                }
                else
                {
                    states[i] = new SokobanGameState(levels[i], rewardType, simulationStrategy);
                }
                stopwatch.Stop();
                stateInitializationTime = stopwatch.ElapsedMilliseconds;
                List <IPuzzleMove> moveList = new List <IPuzzleMove>();
                //player = new SokobanMCTSStrategy(rng, iterations, 600, null, const_C, const_D, stopOnResult);

                //SP_MCTSAlgorithm mcts = new SP_MCTSAlgorithm(new SP_UCTTreeNodeCreator(const_C, const_D, rng), stopOnResult);

                OptMCTSAlgorithm mcts  = new OptMCTSAlgorithm(new Opt_SP_UCTTreeNodeCreator(const_C, const_D, rng, ucb1Tuned, rave, raveThreshold, nodeRecycling), iterations, memoryBudget, stopOnResult, avoidCycles, useNodeElimination);
                string           moves = "";
                stopwatch.Restart();
                moveList = mcts.Solve(states[i], iterations);
                stopwatch.Stop();
                solvingTime = stopwatch.ElapsedMilliseconds;

                //moveList = player.GetSolution(states[i]);
                int pushCount = 0;

                foreach (IPuzzleMove m in moveList)
                {
                    if (abstractSokoban)
                    {
                        //Debug.WriteLine("Move: " + m);
                        //Debug.WriteLine(states[i]);
                        SokobanPushMove push = (SokobanPushMove)m;
                        foreach (IPuzzleMove basicMove in push.MoveList)
                        {
                            moves += basicMove;
                            if (basicMove.move > 3)//the move is a push move
                            {
                                pushCount++;
                            }
                        }
                    }
                    else
                    {
                        moves += m;
                        if (m.move > 3)//the move is a push move
                        {
                            pushCount++;
                        }
                    }
                    states[i].DoMove(m);
                }
                if (states[i].EndState())
                {
                    solvedLevels++;
                    totalRollouts += mcts.IterationsForFirstSolution;
                }
                else
                {
                    totalRollouts += mcts.IterationsExecuted;
                }
                totalDepth            += mcts.maxDepth;
                totalNodesEliminated  += mcts.nodesEliminated;
                totalNodesNotExpanded += mcts.nodesNotExpanded;
                rolloutsCount[i]       = mcts.IterationsExecuted;
                scores[i]              = rolloutsCount[i];
                solved[i]              = states[i].EndState();
                if (log)
                {
                    Log("Level " + (i + 1) + "\titerations: " + mcts.IterationsExecuted + "\titerations for first solution: " + mcts.IterationsForFirstSolution + "\ttotal solutions: " + mcts.SolutionCount + "\tbest solution length (moves/pushes): " + moves.Count() + "/" + pushCount + "\tInit Time: " + TimeFormat(stateInitializationTime) + " - Solving Time: " + TimeFormat(solvingTime) + "\tTree depth: " + mcts.maxDepth + "\tNodes: " + mcts.NodeCount + "\tNodes Eliminated: " + mcts.nodesEliminated + "\tNodes Not Expanded: " + mcts.nodesNotExpanded + "\tBest solution: " + moves);
                }
                totalNodes += mcts.NodeCount;
                visitsList.AddRange(mcts.visits);
                raveVisitsList.AddRange(mcts.raveVisits);
                Console.Write("\r                              ");
                Console.Write("\rSolved " + solvedLevels + "/" + (i + 1));
            }
            visitsList.Sort((x, y) => (x.CompareTo(y)));
            raveVisitsList.Sort((x, y) => (x.CompareTo(y)));
            double avgVisits = 0;

            foreach (int v in visitsList)
            {
                avgVisits += v;
            }
            double avgRaveVisits = 0;

            foreach (int v in raveVisitsList)
            {
                avgRaveVisits += v;
            }
            avgVisits     /= visitsList.Count;
            avgRaveVisits /= raveVisitsList.Count;

            Log("Solved " + solvedLevels + "/" + levels.Length);
            Log("Total iterations: " + totalRollouts);
            Log("Total nodes: " + totalNodes);
            Log("Nodes eliminated: " + totalNodesEliminated);
            Log("Nodes Not Expanded: " + totalNodesNotExpanded);
            Log("avg nodes:" + ((double)totalNodes) / states.Length);
            Log("avg visits: " + avgVisits);
            Log("avg raveVisits: " + avgRaveVisits);
            Log("median visits: " + (visitsList.Count % 2 == 0 ? visitsList[visitsList.Count / 2] : (visitsList[visitsList.Count / 2] + visitsList[1 + visitsList.Count / 2]) / 2));
            Log("median raveVisits: " + (raveVisitsList.Count % 2 == 0 ? raveVisitsList[raveVisitsList.Count / 2] : (raveVisitsList[raveVisitsList.Count / 2] + raveVisitsList[1 + raveVisitsList.Count / 2]) / 2));
            Log("avg depth: " + totalDepth / states.Length);
            return(rolloutsCount);
        }
        /// <summary>
        /// Returns an active object from the object pool without resetting any of its values.
        /// You will need to set its values and set it inactive again when you are done with it.
        /// </summary>
        /// <returns>ITreeNode of requested type if it is available, otherwise null.</returns>
        public Opt_SP_UCTTreeNode GetObject(IPuzzleMove move, Opt_SP_UCTTreeNode parent, IPuzzleState state, MersenneTwister rng, bool ucb1Tuned, bool rave, double raveThreshold, bool nodeRecycling, double const_C, double const_D)
        {
            //iterate through all pooled objects.
            foreach (Opt_SP_UCTTreeNode node in pooledObjects)
            {
                //look for the first one that is inactive.
                if (node.SetActive)
                {
                    continue;
                }
                //set the object to active.
                node.SetActive = true;

                //set object's values
                node.Move          = move;
                node.Parent        = parent;
                node.untriedMoves  = state.GetMoves();
                node.Rnd           = rng;
                node.Ucb1Tuned     = ucb1Tuned;
                node.Rave          = rave;
                node.RaveThreshold = raveThreshold;
                node.NodeRecycling = nodeRecycling;
                node.ConstC        = const_C;
                node.ConstD        = const_D;

                //return the object we found.
                return(node);
            }
            //if we make it this far, we obviously didn't find an inactive object.
            //so we need to see if we can grow beyond our current count.
            //if we reach the maximum size we didn't have any inactive objects.
            //we also were unable to grow, so return null as we can't return an object.
            if (maxPoolSize <= pooledObjects.Count)
            {
                return(null);
            }

            //Instantiate a new object.
            //set it to active since we are about to use it.
            Opt_SP_UCTTreeNode objNode = new Opt_SP_UCTTreeNode(move, parent, state, rng, ucb1Tuned, rave, raveThreshold, nodeRecycling, const_C, const_D)
            {
                SetActive = true
            };

            //add it to the pool of objects
            pooledObjects.Add(objNode);

            //return the object to the requestor.
            return(objNode);
        }