public SolverAction GetNextMove()
        {
            LivingLocation bestLiving = getBestLocation(currentNode);

            if (bestLiving == null)
            {
                return(null);
            }

            SolverTile loc = this.locations[bestLiving.index];

            //solver.display("first best move is " + loc.display());
            double prob = 1 - bestLiving.mineCount / currentNode.GetSolutionSize();

            while (!loc.IsHidden())
            {
                int value = loc.GetValue();

                currentNode = bestLiving.children[value];
                bestLiving  = getBestLocation(currentNode);
                if (bestLiving == null)
                {
                    return(null);
                }
                prob = 1 - ((double)bestLiving.mineCount) / currentNode.GetSolutionSize();

                loc = this.locations[bestLiving.index];
            }

            solver.Write("mines = " + bestLiving.mineCount + " solutions = " + currentNode.GetSolutionSize());
            for (int i = 0; i < bestLiving.children.Length; i++)
            {
                if (bestLiving.children[i] == null)
                {
                    //solver.display("Value of " + i + " is not possible");
                    continue; //ignore this node but continue the loop
                }

                String probText;
                if (bestLiving.children[i].bestLiving == null)
                {
                    probText = (100 / (bestLiving.children[i].GetSolutionSize())) + "%";
                }
                else
                {
                    probText = bestLiving.children[i].GetProbability() * 100 + "%";
                }
                solver.Write("Value of " + i + " leaves " + bestLiving.children[i].GetSolutionSize() + " solutions and winning probability " + probText + " (work size " + bestLiving.children[i].work + ")");
            }

            //String text = " (solve " + (currentNode.GetProbability() * 100) + "%)";
            SolverAction action = new SolverAction(loc, ActionType.Clear, 0.5);

            expectedMove = loc;

            return(action);
        }
            /**
             * Calculate the number of winning lines if this move is played at this position
             * Used at top of the game tree
             */
            public int GetWinningLines(LivingLocation move)
            {
                //if we can never exceed the cutoff then no point continuing
                if (SolverMain.PRUNE_BF_ANALYSIS && this.GetSolutionSize() - move.mineCount <= this.winningLines)
                {
                    move.pruned = true;
                    return(0);
                }

                int winningLines = GetWinningLines(1, move, this.winningLines);

                if (winningLines > this.winningLines)
                {
                    this.winningLines = winningLines;
                }

                return(winningLines);
            }
        /**
         * Builds a top of tree node based on the solutions provided
         */
        private Node buildTopNode(SolutionTable solutionTable)
        {
            Node result = new Node(this, locations.Count);

            result.startLocation = 0;
            result.endLocation   = solutionTable.GetSize();

            List <LivingLocation> living = new List <LivingLocation>();

            for (short i = 0; i < locations.Count; i++)
            {
                int value;

                int[] valueCount   = ResetValues();
                int   mines        = 0;
                int   maxSolutions = 0;
                byte  count        = 0;
                sbyte minValue     = 0;
                sbyte maxValue     = 0;

                for (int j = 0; j < result.GetSolutionSize(); j++)
                {
                    if (solutionTable.Get(j)[i] != Cruncher.BOMB)
                    {
                        value = solutionTable.Get(j)[i];
                        //values[value] = true;
                        valueCount[value]++;
                    }
                    else
                    {
                        mines++;
                    }
                }

                for (sbyte j = 0; j < valueCount.Length; j++)
                {
                    if (valueCount[j] > 0)
                    {
                        if (count == 0)
                        {
                            minValue = j;
                        }
                        maxValue = j;
                        count++;
                        if (maxSolutions < valueCount[j])
                        {
                            maxSolutions = valueCount[j];
                        }
                    }
                }
                if (count > 1)
                {
                    LivingLocation alive = new LivingLocation(this, i);
                    alive.mineCount     = mines;
                    alive.count         = count;
                    alive.minValue      = minValue;
                    alive.maxValue      = maxValue;
                    alive.maxSolutions  = maxSolutions;
                    alive.zeroSolutions = valueCount[0];
                    living.Add(alive);
                }
                else
                {
                    solver.Write(locations[i].AsText() + " is dead with value " + minValue);
                }
            }

            living.Sort();
            //Collections.sort(living);

            result.livingLocations = living;

            return(result);
        }
            /**
             * this generates a list of Location that are still alive, (i.e. have more than one possible value) from a list of previously living locations
             * Index is the move which has just been played (in terms of the off-set to the position[] array)
             */
            public void DetermineLivingLocations(List <LivingLocation> liveLocs, int index)
            {
                List <LivingLocation> living = new List <LivingLocation>(liveLocs.Count);

                foreach (LivingLocation live in liveLocs)
                {
                    if (live.index == index)    // if this is the same move we just played then no need to analyse it - definitely now non-living.
                    {
                        continue;
                    }

                    int value;

                    int[] valueCount   = bfa.ResetValues();
                    int   mines        = 0;
                    int   maxSolutions = 0;
                    byte  count        = 0;
                    sbyte minValue     = 0;
                    sbyte maxValue     = 0;

                    for (int j = startLocation; j < endLocation; j++)
                    {
                        value = bfa.allSolutions.Get(j)[live.index];
                        if (value != Cruncher.BOMB)
                        {
                            //values[value] = true;
                            valueCount[value]++;
                        }
                        else
                        {
                            mines++;
                        }
                    }

                    // find the new minimum value and maximum value for this location (can't be wider than the previous min and max)
                    for (sbyte j = live.minValue; j <= live.maxValue; j++)
                    {
                        if (valueCount[j] > 0)
                        {
                            if (count == 0)
                            {
                                minValue = j;
                            }
                            maxValue = j;
                            count++;
                            if (maxSolutions < valueCount[j])
                            {
                                maxSolutions = valueCount[j];
                            }
                        }
                    }
                    if (count > 1)
                    {
                        LivingLocation alive = new LivingLocation(bfa, live.index);
                        alive.mineCount     = mines;
                        alive.count         = count;
                        alive.minValue      = minValue;
                        alive.maxValue      = maxValue;
                        alive.maxSolutions  = maxSolutions;
                        alive.zeroSolutions = valueCount[0];
                        living.Add(alive);
                    }
                }

                living.Sort();
                //Collections.sort(living);

                this.livingLocations = living;
            }
            /**
             * Calculate the number of winning lines if this move is played at this position
             * Used when exploring the game tree
             */
            public int GetWinningLines(int depth, LivingLocation move, int cutoff)
            {
                int result = 0;

                bfa.processCount++;
                if (bfa.processCount > SolverMain.BRUTE_FORCE_ANALYSIS_MAX_NODES)
                {
                    return(0);
                }

                int notMines = this.GetSolutionSize() - move.mineCount;

                move.BuildChildNodes(this);

                foreach (Node child in move.children)
                {
                    if (child == null)
                    {
                        continue;  // continue the loop but ignore this entry
                    }

                    int maxWinningLines = result + notMines;

                    // if the max possible winning lines is less than the current cutoff then no point doing the analysis
                    if (SolverMain.PRUNE_BF_ANALYSIS && maxWinningLines <= cutoff)
                    {
                        move.pruned = true;
                        return(0);
                    }


                    if (child.fromCache)    // nothing more to do, since we did it before
                    {
                        this.work++;
                    }
                    else
                    {
                        child.DetermineLivingLocations(this.livingLocations, move.index);
                        this.work++;

                        if (child.GetLivingLocations().Count == 0)    // no further information ==> all solution indistinguishable ==> 1 winning line

                        {
                            child.winningLines = 1;
                        }
                        else      // not cached and not terminal node, so we need to do the recursion

                        {
                            foreach (LivingLocation childMove in child.GetLivingLocations())
                            {
                                // if the number of safe solutions <= the best winning lines then we can't do any better, so skip the rest
                                if (child.GetSolutionSize() - childMove.mineCount <= child.winningLines)
                                {
                                    break;
                                }

                                // now calculate the winning lines for each of these children
                                int winningLines = child.GetWinningLines(depth + 1, childMove, child.winningLines);
                                if (child.winningLines < winningLines || (child.bestLiving != null && child.winningLines == winningLines && child.bestLiving.mineCount < childMove.mineCount))
                                {
                                    child.winningLines = winningLines;
                                    child.bestLiving   = childMove;
                                }

                                // if there are no mines then this is a 100% safe move, so skip any further analysis since it can't be any better
                                if (childMove.mineCount == 0)
                                {
                                    break;
                                }
                            }

                            // no need to hold onto the living location once we have determined the best of them
                            child.livingLocations = null;

                            //if (depth > solver.preferences.BRUTE_FORCE_ANALYSIS_TREE_DEPTH) {  // stop holding the tree beyond this depth
                            //	child.bestLiving = null;
                            //}

                            // add the child to the cache if it didn't come from there and it is carrying sufficient winning lines
                            if (child.work > 30)
                            {
                                child.work      = 0;
                                child.fromCache = true;
                                bfa.cacheSize++;
                                bfa.cache.Add(child.position, child);
                            }
                            else
                            {
                                this.work = this.work + child.work;
                            }
                        }
                    }

                    if (depth > SolverMain.BRUTE_FORCE_ANALYSIS_TREE_DEPTH)    // stop holding the tree beyond this depth
                    {
                        child.bestLiving = null;
                    }

                    // store the aggregate winning lines
                    result = result + child.winningLines;

                    notMines = notMines - child.GetSolutionSize();  // reduce the number of not mines
                }

                return(result);
            }