using System; using System.Collections.Generic; using System.Numerics; using static MinesweeperControl.MinesweeperGame; namespace MinesweeperSolver { public class SolutionCounter { private readonly int[][] SMALL_COMBINATIONS = new int[][] { new int[] { 1 }, new int[] { 1, 1 }, new int[] { 1, 2, 1 }, new int[] { 1, 3, 3, 1 }, new int[] { 1, 4, 6, 4, 1 }, new int[] { 1, 5, 10, 10, 5, 1 }, new int[] { 1, 6, 15, 20, 15, 6, 1 }, new int[] { 1, 7, 21, 35, 35, 21, 7, 1 }, new int[] { 1, 8, 28, 56, 70, 56, 28, 8, 1 } }; private readonly List witnessed; private readonly List prunedWitnesses = new List(); // a subset of allWitnesses with equivalent witnesses removed private readonly List boxes = new List(); private readonly List boxWitnesses = new List(); private bool[] mask; private readonly Dictionary boxLookup = new Dictionary(); // a lookup which finds which box a tile belongs to (an efficiency enhancement) //private readonly List deadCandidates = new List(); private List workingProbs = new List(); private readonly List heldProbs = new List(); private readonly List edgeStore = new List(); // stores independent edge analysis until we know we have to merge them private readonly int minesLeft; private readonly int tilesLeft; private readonly int tilesOffEdge; private readonly int minTotalMines; private readonly int maxTotalMines; private int recursions; // used to find the range of mine counts which offer the 2.5% - 97.5% weighted average private int edgeMinesMin; private int edgeMinesMax; private int edgeMinesMinLeft; private int edgeMinesMaxLeft; private int mineCountUpperCutoff; private int mineCountLowerCutoff; private bool truncatedProbs = false; // this gets set when the number of held probabilies exceeds the permitted threshold private BigInteger finalSolutionCount = 0; private BigInteger solutionCountMultiplier = 1; private int clearCount = 0; private readonly SolverInfo information; public SolutionCounter(SolverInfo information, List allWitnesses, List allWitnessed, int squaresLeft, int minesLeft) { this.information = information; this.witnessed = allWitnessed; // constraints in the game this.minesLeft = minesLeft; this.tilesLeft = squaresLeft; this.tilesOffEdge = squaresLeft - allWitnessed.Count; // squares left off the edge and unrevealed this.minTotalMines = minesLeft - this.tilesOffEdge; //we can't use so few mines that we can't fit the remainder elsewhere on the board this.maxTotalMines = minesLeft; this.mineCountUpperCutoff = minesLeft; this.mineCountLowerCutoff = minTotalMines; information.Write("Tiles off edge " + tilesOffEdge); //this.boxProb = []; // the probabilities end up here // generate a BoxWitness for each witness tile and also create a list of pruned witnesses for the brute force search int pruned = 0; foreach (SolverTile wit in allWitnesses) { BoxWitness boxWit = new BoxWitness(information, wit); // if the witness is a duplicate then don't store it bool duplicate = false; foreach (BoxWitness w in this.boxWitnesses) { if (w.Equivalent(boxWit)) { //if (boardState.getWitnessValue(w) - boardState.countAdjacentConfirmedFlags(w) != boardState.getWitnessValue(wit) - boardState.countAdjacentConfirmedFlags(wit)) { // boardState.display(w.display() + " and " + wit.display() + " share unrevealed squares but have different mine totals!"); // validWeb = false; //} duplicate = true; break; } } if (!duplicate) { this.prunedWitnesses.Add(boxWit); } else { pruned++; } this.boxWitnesses.Add(boxWit); // all witnesses are needed for the probability engine } information.Write("Pruned " + pruned + " witnesses as duplicates"); information.Write("There are " + this.boxWitnesses.Count + " Box witnesses"); // allocate each of the witnessed squares to a box int uid = 0; foreach (SolverTile tile in this.witnessed) { // for each adjacent tile see if it is a witness int count = 0; //foreach (SolverTile adjTile in information.GetAdjacentTiles(tile)) { // if (information.GetWitnesses().Contains(adjTile)) { // count++; // } //} // count how many adjacent witnesses the tile has foreach (SolverTile tile1 in allWitnesses) { if (tile.IsAdjacent(tile1)) { count++; } } // see if the witnessed tile fits any existing boxes bool found = false; foreach (Box box in this.boxes) { if (box.Fits(tile, count)) { box.Add(tile); boxLookup.Add(tile, box); // add this to the lookup found = true; break; } } // if not found create a new box and store it if (!found) { Box box = new Box(this.boxWitnesses, tile, uid++); this.boxes.Add(box); boxLookup.Add(tile, box); // add this to the lookup } } // calculate the min and max mines for each box foreach (Box box in this.boxes) { box.Calculate(this.minesLeft); //console.log("Box " + box.tiles[0].asText() + " has min mines = " + box.minMines + " and max mines = " + box.maxMines); } // Report how many boxes each witness is adjacent to foreach (BoxWitness boxWit in this.boxWitnesses) { information.Write("Witness " + boxWit.GetTile().AsText() + " is adjacent to " + boxWit.GetBoxes().Count + " boxes and has " + boxWit.GetMinesToFind() + " mines to find"); } } // calculate a probability for each un-revealed tile on the board public void Process() { this.mask = new bool[this.boxes.Count]; // create an initial solution of no mines anywhere ProbabilityLine held = new ProbabilityLine(this.boxes.Count); held.SetSolutionCount(1); this.heldProbs.Add(held); // add an empty probability line to get us started this.workingProbs.Add(new ProbabilityLine(this.boxes.Count)); NextWitness nextWitness = FindFirstWitness(); while (nextWitness != null) { // mark the new boxes as processed - which they will be soon foreach (Box box in nextWitness.GetNewBoxes()) { this.mask[box.GetUID()] = true; } this.workingProbs = MergeProbabilities(nextWitness); nextWitness = FindNextWitness(nextWitness); } CalculateBoxProbabilities(); } // take the next witness details and merge them into the currently held details private List MergeProbabilities(NextWitness nw) { List newProbs = new List(); foreach (ProbabilityLine pl in this.workingProbs) { int missingMines = nw.GetBoxWitness().GetMinesToFind() - (int)CountPlacedMines(pl, nw); if (missingMines < 0) { //console.log("Missing mines < 0 ==> ignoring line"); // too many mines placed around this witness previously, so this probability can't be valid } else if (missingMines == 0) { //console.log("Missing mines = 0 ==> keeping line as is"); newProbs.Add(pl); // witness already exactly satisfied, so nothing to do } else if (nw.GetNewBoxes().Count == 0) { //console.log("new boxes = 0 ==> ignoring line since nowhere for mines to go"); // nowhere to put the new mines, so this probability can't be valid } else { List result = DistributeMissingMines(pl, nw, missingMines, 0); newProbs.AddRange(result); } } //if (newProbs.length == 0) { // console.log("Returning no lines from merge probability !!"); //} return newProbs; } // counts the number of mines already placed private BigInteger CountPlacedMines(ProbabilityLine pl, NextWitness nw) { BigInteger result = 0; foreach (Box b in nw.GetOldBoxes()) { result = result + pl.GetMineBoxCount(b.GetUID()); } return result; } // this is used to recursively place the missing Mines into the available boxes for the probability line private List DistributeMissingMines(ProbabilityLine pl, NextWitness nw, int missingMines, int index) { //console.log("Distributing " + missingMines + " missing mines to box " + nw.newBoxes[index].uid); this.recursions++; if (this.recursions % 100000 == 0) { information.Write("Solution Counter recursision = " + recursions); } List result = new List(); // if there is only one box left to put the missing mines we have reach the end of this branch of recursion if (nw.GetNewBoxes().Count - index == 1) { // if there are too many for this box then the probability can't be valid if (nw.GetNewBoxes()[index].GetMaxMines() < missingMines) { //console.log("Abandon (1)"); return result; } // if there are too few for this box then the probability can't be valid if (nw.GetNewBoxes()[index].GetMinMines() > missingMines) { //console.log("Abandon (2)"); return result; } // if there are too many for this game then the probability can't be valid if (pl.GetMineCount() + missingMines > this.maxTotalMines) { //console.log("Abandon (3)"); return result; } // otherwise place the mines in the probability line pl.SetMineBoxCount(nw.GetNewBoxes()[index].GetUID(), new BigInteger(missingMines)); pl.SetMineCount(pl.GetMineCount() + missingMines); result.Add(pl); //console.log("Distribute missing mines line after " + pl.mineBoxCount); return result; } // this is the recursion int maxToPlace = Math.Min(nw.GetNewBoxes()[index].GetMaxMines(), missingMines); for (int i = nw.GetNewBoxes()[index].GetMinMines(); i <= maxToPlace; i++) { ProbabilityLine npl = ExtendProbabilityLine(pl, nw.GetNewBoxes()[index], i); List r1 = DistributeMissingMines(npl, nw, missingMines - i, index + 1); result.AddRange(r1); } return result; } // create a new probability line by taking the old and adding the mines to the new Box private ProbabilityLine ExtendProbabilityLine(ProbabilityLine pl, Box newBox, int mines) { //console.log("Extended probability line: Adding " + mines + " mines to box " + newBox.uid); //console.log("Extended probability line before" + pl.mineBoxCount); ProbabilityLine result = new ProbabilityLine(this.boxes.Count); result.SetMineCount(pl.GetMineCount() + mines); result.CopyMineBoxCount(pl); result.SetMineBoxCount(newBox.GetUID(), new BigInteger(mines)); //console.log("Extended probability line after " + result.mineBoxCount); return result; } // here we store the information we have found about this independent edge for later use private void StoreProbabilities() { List crunched = CrunchByMineCount(this.workingProbs); //Console.WriteLine("Edge has " + crunched.Count + " probability lines after consolidation"); edgeStore.Add(new EdgeStore(crunched, this.mask)); // store the existing data workingProbs.Clear(); // and get a new list for the next independent edge this.workingProbs.Add(new ProbabilityLine(this.boxes.Count)); // add a new starter probability line this.mask = new bool[boxes.Count]; } // this combines newly generated probabilities with ones we have already stored from other independent sets of witnesses private void CombineProbabilities() { List result = new List(); if (this.workingProbs.Count == 0) { information.Write("working probabilites list is empty!!"); return; } // see if we can find a common divisor BigInteger hcd = workingProbs[0].GetSolutionCount(); foreach (ProbabilityLine pl in workingProbs) { hcd = BigInteger.GreatestCommonDivisor(hcd, pl.GetSolutionCount()); } foreach (ProbabilityLine pl in heldProbs) { hcd = BigInteger.GreatestCommonDivisor(hcd, pl.GetSolutionCount()); } information.Write("Greatest Common Divisor is " + hcd); solutionCountMultiplier = solutionCountMultiplier * hcd; int mineCountMin = workingProbs[0].GetMineCount() + heldProbs[0].GetMineCount(); // shrink the window edgeMinesMinLeft = edgeMinesMinLeft - workingProbs[0].GetMineCount(); edgeMinesMaxLeft = edgeMinesMaxLeft - workingProbs[workingProbs.Count - 1].GetMineCount(); foreach (ProbabilityLine pl in workingProbs) { BigInteger plSolCount = pl.GetSolutionCount() / hcd; foreach (ProbabilityLine epl in heldProbs) { // if the mine count can never reach the lower cuttoff then ignore it if (pl.GetMineCount() + epl.GetMineCount() + edgeMinesMaxLeft < this.mineCountLowerCutoff) { continue; } // if the mine count will always be pushed beyonf the upper cuttoff then ignore it if (pl.GetMineCount() + epl.GetMineCount() + edgeMinesMinLeft > this.mineCountUpperCutoff) { continue; } ProbabilityLine newpl = new ProbabilityLine(this.boxes.Count); newpl.SetMineCount(pl.GetMineCount() + epl.GetMineCount()); BigInteger eplSolCount = epl.GetSolutionCount() / hcd; newpl.SetSolutionCount(pl.GetSolutionCount() * eplSolCount); for (int k = 0; k < this.boxes.Count; k++) { BigInteger w1 = pl.GetMineBoxCount(k) * eplSolCount; BigInteger w2 = epl.GetMineBoxCount(k) * plSolCount; newpl.SetMineBoxCount(k, w1 + w2); } result.Add(newpl); } } //Console.WriteLine("Solution multiplier is " + solutionCountMultiplier); this.heldProbs.Clear(); // if result is empty this is an impossible position if (result.Count == 0) { Console.WriteLine("Impossible position encountered"); return; } if (result.Count == 1) { heldProbs.AddRange(result); return; } // sort into mine order result.Sort(); // and combine them into a single probability line for each mine count int mc = result[0].GetMineCount(); ProbabilityLine npl = new ProbabilityLine(this.boxes.Count); npl.SetMineCount(mc); int startMC = mc; foreach (ProbabilityLine pl in result) { if (pl.GetMineCount() != mc) { this.heldProbs.Add(npl); mc = pl.GetMineCount(); npl = new ProbabilityLine(this.boxes.Count); npl.SetMineCount(mc); } npl.SetSolutionCount(npl.GetSolutionCount() + pl.GetSolutionCount()); for (int j = 0; j < this.boxes.Count; j++) { npl.SetMineBoxCount(j, npl.GetMineBoxCount(j) + pl.GetMineBoxCount(j)); } } this.heldProbs.Add(npl); } private List CrunchByMineCount(List target) { //if (target.Count == 0) { // return target; //} // sort the solutions by number of mines target.Sort(); List result = new List(); int mc = target[0].GetMineCount(); ProbabilityLine npl = new ProbabilityLine(this.boxes.Count); npl.SetMineCount(mc); foreach (ProbabilityLine pl in target) { if (pl.GetMineCount() != mc) { result.Add(npl); mc = pl.GetMineCount(); npl = new ProbabilityLine(this.boxes.Count); npl.SetMineCount(mc); } MergeLineProbabilities(npl, pl); } //if (npl.GetMineCount() >= minTotalMines) { result.Add(npl); //} //information.Write("Probability line has " + npl.GetMineCount() + " mines"); return result; } // calculate how many ways this solution can be generated and roll them into one private void MergeLineProbabilities(ProbabilityLine npl, ProbabilityLine pl) { BigInteger solutions = 1; for (int i = 0; i < this.boxes.Count; i++) { solutions = solutions * (BigInteger)SMALL_COMBINATIONS[this.boxes[i].GetTiles().Count][(int)pl.GetMineBoxCount(i)]; } npl.SetSolutionCount(npl.GetSolutionCount() + solutions); for (int i = 0; i < this.boxes.Count; i++) { if (this.mask[i]) { // if this box has been involved in this solution - if we don't do this the hash gets corrupted by boxes = 0 mines because they weren't part of this edge npl.SetMineBoxCount(i, npl.GetMineBoxCount(i) + pl.GetMineBoxCount(i) * solutions); } } } // return any witness which hasn't been processed private NextWitness FindFirstWitness() { BoxWitness excluded = null; foreach (BoxWitness boxWit in this.boxWitnesses) { if (!boxWit.IsProcessed()) { return new NextWitness(boxWit); } else if (!boxWit.IsProcessed()) { excluded = boxWit; } } if (excluded != null) { return new NextWitness(excluded); } return null; } // look for the next witness to process private NextWitness FindNextWitness(NextWitness prevWitness) { // flag the last set of details as processed prevWitness.GetBoxWitness().SetProcessed(true); foreach (Box newBox in prevWitness.GetNewBoxes()) { newBox.SetProcessed(true); } int bestTodo = 99999; BoxWitness bestWitness = null; // and find a witness which is on the boundary of what has already been processed foreach (Box b in this.boxes) { if (b.IsProcessed()) { foreach (BoxWitness w in b.GetBoxWitnesses()) { if (!w.IsProcessed()) { int todo = 0; foreach (Box b1 in w.GetBoxes()) { if (!b1.IsProcessed()) { todo++; } } if (todo == 0) { // prioritise the witnesses which have the least boxes left to process return new NextWitness(w); } else if (todo < bestTodo) { bestTodo = todo; bestWitness = w; } } } } } if (bestWitness != null) { return new NextWitness(bestWitness); } else { information.Write("Ending independent edge"); } //independentGroups++; // since we have calculated all the mines in an independent set of witnesses we can crunch them down and store them for later // store the details for this edge StoreProbabilities(); // get an unprocessed witness NextWitness nw = FindFirstWitness(); if (nw != null) { information.Write("Starting a new independent edge"); } // If there is nothing else to process then either do the local clears or calculate the probabilities if (nw == null) { edgeStore.Sort(EdgeStore.SortByLineCount); AnalyseAllEdges(); long start = DateTime.Now.Ticks; foreach (EdgeStore edgeDetails in edgeStore) { workingProbs = edgeDetails.data; CombineProbabilities(); } information.Write("Combined all edges in " + (DateTime.Now.Ticks - start) + " ticks"); } // return the next witness to process return nw; } // take a look at what edges we have and show some information - trying to get some ideas on how we can get faster good guesses private void AnalyseAllEdges() { //Console.WriteLine("Number of tiles off the edge " + tilesOffEdge); //Console.WriteLine("Number of mines to find " + minesLeft); this.edgeMinesMin = 0; this.edgeMinesMax = 0; foreach (EdgeStore edge in edgeStore) { int edgeMinMines = edge.data[0].GetMineCount(); int edgeMaxMines = edge.data[edge.data.Count - 1].GetMineCount(); edgeMinesMin = edgeMinesMin + edgeMinMines; edgeMinesMax = edgeMinesMax + edgeMaxMines; } information.Write("Min mines on all edges " + edgeMinesMin + ", max " + edgeMinesMax); this.edgeMinesMaxLeft = this.edgeMinesMax; // these values are used in the merge logic to reduce the number of lines need to keep this.edgeMinesMinLeft = this.edgeMinesMin; this.mineCountLowerCutoff = this.edgeMinesMin; this.mineCountUpperCutoff = Math.Min(this.edgeMinesMax, this.minesLeft); // can't have more mines than are left // comment this out when doing large board analysis return; // the code below reduces the range of mine count values to just be the 'significant' range List store = new List(); List store1 = new List(); ProbabilityLine init = new ProbabilityLine(0); init.SetSolutionCount(1); store.Add(init); // combine all the edges to determine the relative weights of the mine count foreach (EdgeStore edgeDetails in edgeStore) { foreach (ProbabilityLine pl in edgeDetails.data) { BigInteger plSolCount = pl.GetSolutionCount(); foreach (ProbabilityLine epl in store) { if (pl.GetMineCount() + epl.GetMineCount() <= this.maxTotalMines) { ProbabilityLine newpl = new ProbabilityLine(0); newpl.SetMineCount(pl.GetMineCount() + epl.GetMineCount()); BigInteger eplSolCount = epl.GetSolutionCount(); newpl.SetSolutionCount(pl.GetSolutionCount() * eplSolCount); store1.Add(newpl); } } } store.Clear(); // sort into mine order store1.Sort(); int mc = store1[0].GetMineCount(); ProbabilityLine npl = new ProbabilityLine(0); npl.SetMineCount(mc); foreach (ProbabilityLine pl in store1) { if (pl.GetMineCount() != mc) { store.Add(npl); mc = pl.GetMineCount(); npl = new ProbabilityLine(0); npl.SetMineCount(mc); } npl.SetSolutionCount(npl.GetSolutionCount() + pl.GetSolutionCount()); } store.Add(npl); store1.Clear(); } BigInteger total = 0; int mineValues = 0; foreach (ProbabilityLine pl in store) { if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed total = total + mult * pl.GetSolutionCount(); mineValues++; } } //this.mineCountLowerCutoff = this.edgeMinesMin; //this.mineCountUpperCutoff = Math.Min(this.edgeMinesMax, this.minesLeft); // can't have more mines than are left BigInteger soFar = 0; foreach (ProbabilityLine pl in store) { if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed soFar = soFar + mult * pl.GetSolutionCount(); double perc = Combination.DivideBigIntegerToDouble(soFar, total, 6) * 100; //Console.WriteLine("Mine count " + pl.GetMineCount() + " has solution count " + pl.GetSolutionCount() + " multiplier " + mult + " running % " + perc); //Console.WriteLine("Mine count " + pl.GetMineCount() + " has solution count " + pl.GetSolutionCount() + " has running % " + perc); if (mineValues > 30 && perc < 2.5) { this.mineCountLowerCutoff = pl.GetMineCount(); } if (mineValues > 30 && perc > 97.5) { this.mineCountUpperCutoff = pl.GetMineCount(); break; } } } information.Write("Significant range " + this.mineCountLowerCutoff + " - " + this.mineCountUpperCutoff); //this.edgeMinesMaxLeft = this.edgeMinesMax; //this.edgeMinesMinLeft = this.edgeMinesMin; return; // below here are experimental ideas on getting a good guess on very large boards int midRangeAllMines = (this.mineCountLowerCutoff + this.mineCountUpperCutoff) / 2; BigInteger[] tally = new BigInteger[boxes.Count]; double[] probability = new double[boxes.Count]; foreach (EdgeStore edgeDetails in edgeStore) { int sizeRangeEdgeMines = (edgeDetails.data[edgeDetails.data.Count - 1].GetMineCount() - edgeDetails.data[0].GetMineCount()) / 2; int start = (this.mineCountLowerCutoff - edgeDetails.data[0].GetMineCount() + this.mineCountUpperCutoff - edgeDetails.data[edgeDetails.data.Count - 1].GetMineCount()) / 2; //int start = midRangeAllMines - sizeRangeEdgeMines; //BigInteger mult = Combination.Calculate(this.minesLeft - start, this.tilesOffEdge); BigInteger totalTally = 0; foreach (ProbabilityLine pl in edgeDetails.data) { BigInteger mult = Combination.Calculate(this.minesLeft - start - pl.GetMineCount(), this.tilesOffEdge); totalTally += mult * pl.GetSolutionCount(); for (int i = 0; i < boxes.Count; i++) { if (edgeDetails.mask[i]) { BigInteger work = pl.GetMineBoxCount(i) * mult; tally[i] += work; } } //mult = mult * (this.tilesOffEdge - start) / (start + 1); //start++; } for (int i = 0; i < boxes.Count; i++) { if (edgeDetails.mask[i]) { probability[i] = Combination.DivideBigIntegerToDouble(tally[i], totalTally, 6) / boxes[i].GetTiles().Count; } } int minIndex = -1; for (int i = 0; i < boxes.Count; i++) { if (edgeDetails.mask[i]) { if (minIndex == -1 || probability[i] < probability[minIndex]) { minIndex = i; } } } if (minIndex != -1) { information.Write("Best guess is " + boxes[minIndex].GetTiles()[0].AsText() + " with " + (1 - probability[minIndex])); } else { information.Write("No Guess found"); } } } // here we expand the localised solution to one across the whole board and // sum them together to create a definitive probability for each box private void CalculateBoxProbabilities() { //long start = DateTime.Now.Ticks; if (truncatedProbs) { Console.WriteLine("probability line combining was truncated"); } information.Write("Solution count multiplier is " + this.solutionCountMultiplier); BigInteger[] tally = new BigInteger[this.boxes.Count]; // total game tally BigInteger totalTally = 0; // outside a box tally BigInteger outsideTally = 0; //console.log("There are " + this.heldProbs.length + " different mine counts on the edge"); bool[] emptyBox = new bool[boxes.Count]; for (int i = 0; i < emptyBox.Length; i++) { emptyBox[i] = true; } int linesProcessed = 0; // calculate how many mines foreach (ProbabilityLine pl in this.heldProbs) { //console.log("Mine count is " + pl.mineCount + " with solution count " + pl.solutionCount + " mineBoxCount = " + pl.mineBoxCount); if (pl.GetMineCount() >= this.minTotalMines) { // if the mine count for this solution is less than the minimum it can't be valid linesProcessed++; //console.log("Mines left " + this.minesLeft + " mines on PL " + pl.mineCount + " squares left = " + this.squaresLeft); BigInteger mult = SolverMain.Calculate(this.minesLeft - pl.GetMineCount(), this.tilesOffEdge); //# of ways the rest of the board can be formed information.Write("Mines in solution " + pl.GetMineCount() + " solution count " + pl.GetSolutionCount() + " multiplier " + mult); outsideTally = outsideTally + mult * new BigInteger(this.minesLeft - pl.GetMineCount()) * (pl.GetSolutionCount()); // this is all the possible ways the mines can be placed across the whole game totalTally = totalTally + mult * (pl.GetSolutionCount()); for (int i = 0; i < emptyBox.Length; i++) { if (pl.GetMineBoxCount(i) != 0) { emptyBox[i] = false; } } } } // determine how many clear squares there are if (totalTally > 0) { for (int i = 0; i < emptyBox.Length; i++) { if (emptyBox[i]) { clearCount = clearCount + boxes[i].GetTiles().Count; } } } this.finalSolutionCount = totalTally * solutionCountMultiplier; information.Write("Game has " + this.finalSolutionCount + " candidate solutions"); } public BigInteger GetSolutionCount() { return this.finalSolutionCount; } public int getClearCount() { return this.clearCount; } public int GetSolutionCountMagnitude() { return (int)Math.Floor(BigInteger.Log10(this.finalSolutionCount)); } public SolverInfo GetSolverInfo() { return this.information; } } }