private List <ProbabilityLine> CrunchByMineCount(List <ProbabilityLine> target) { //if (target.Count == 0) { // return target; //} // sort the solutions by number of mines target.Sort(); List <ProbabilityLine> result = new List <ProbabilityLine>(); 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 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(); }
// this is used to recursively place the missing Mines into the available boxes for the probability line private List <ProbabilityLine> 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 <ProbabilityLine> result = new List <ProbabilityLine>(); // 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 <ProbabilityLine> r1 = DistributeMissingMines(npl, nw, missingMines - i, index + 1); result.AddRange(r1); } return(result); }
// 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); }
// 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); }
// 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); } } }
// 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 <ProbabilityLine> store = new List <ProbabilityLine>(); List <ProbabilityLine> store1 = new List <ProbabilityLine>(); 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"); } } }
// this combines newly generated probabilities with ones we have already stored from other independent sets of witnesses private void CombineProbabilities() { List <ProbabilityLine> result = new List <ProbabilityLine>(); 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); }