// 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"); } } }
// 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 static SolverActionHeader FindActions(SolverInfo information) { long time1 = DateTime.Now.Ticks; List <SolverAction> actions; if (information.GetGameStatus() == GameStatus.Lost) { information.Write("Game has been lost already - no valid moves"); return(new SolverActionHeader()); } else if (information.GetGameStatus() == GameStatus.Won) { information.Write("Game has been won already - no valid moves"); return(new SolverActionHeader()); } else if (information.GetGameStatus() == GameStatus.NotStarted) { information.Write("Game has not yet started - currently unable to provide help"); return(new SolverActionHeader()); } // are we walking down a brute force deep analysis tree? BruteForceAnalysis lastBfa = information.GetBruteForceAnalysis(); if (lastBfa != null) // yes { SolverTile expectedMove = lastBfa.GetExpectedMove(); if (expectedMove != null && expectedMove.IsHidden()) // the expected move wasn't played ! { information.Write("The expected Brute Force Analysis move " + expectedMove.AsText() + " wasn't played"); information.SetBruteForceAnalysis(null); } else { SolverAction move = lastBfa.GetNextMove(); if (move != null) { information.Write("Next Brute Force Deep Analysis move is " + move.AsText()); actions = new List <SolverAction>(1); actions.Add(move); return(BuildActionHeader(information, actions)); } } } actions = FindTrivialActions(information); long time2 = DateTime.Now.Ticks; information.Write("Finding Trivial Actions took " + (time2 - time1) + " ticks"); // if we have some actions from the trivial search use them if (actions.Count > 0) { return(BuildActionHeader(information, actions)); } if (information.GetTilesLeft() == information.GetDeadTiles().Count) { information.Write("All tiles remaining are dead"); // when all the tiles are dead return the first one (they are all equally good or bad) foreach (SolverTile guess in information.GetDeadTiles()) { actions.Add(new SolverAction(guess, ActionType.Clear, 0.5)); // not all 0.5 safe though !! return(BuildActionHeader(information, actions)); } } // get all the witnessed tiles List <SolverTile> witnesses = new List <SolverTile>(information.GetWitnesses()); HashSet <SolverTile> witnessedSet = new HashSet <SolverTile>(); foreach (SolverTile witness in witnesses) { foreach (SolverTile adjTile in information.GetAdjacentTiles(witness)) { if (adjTile.IsHidden()) { witnessedSet.Add(adjTile); } } } List <SolverTile> witnessed = new List <SolverTile>(witnessedSet); int livingTilesLeft = information.GetTilesLeft(); int livingMinesLeft = information.GetMinesLeft(); int offEdgeTilesLeft = information.GetTilesLeft() - witnessed.Count; //information.Write("Excluded tiles " + information.GetExcludedTiles().Count + " out of " + information.GetTilesLeft()); //information.Write("Excluded witnesses " + information.GetExcludedWitnesses().Count); //information.Write("Excluded mines " + information.GetExcludedMineCount() + " out of " + information.GetMinesLeft()); // if there are no living mines but some living tiles then the living tiles can be cleared if (livingMinesLeft == 0 && livingTilesLeft > 0) { information.Write("There are no living mines left - all living tiles must be clearable"); for (int x = 0; x < information.description.width; x++) { for (int y = 0; y < information.description.height; y++) { SolverTile tile = information.GetTile(x, y); if (tile.IsHidden() && !tile.IsMine()) { actions.Add(new SolverAction(tile, ActionType.Clear, 1)); } } } return(BuildActionHeader(information, actions)); } SolutionCounter solutionCounter = new SolutionCounter(information, witnesses, witnessed, livingTilesLeft, livingMinesLeft); solutionCounter.Process(); information.Write("Solution counter says " + solutionCounter.GetSolutionCount() + " solutions and " + solutionCounter.getClearCount() + " clears"); //ProbabilityEngine pe = new ProbabilityEngine(information, witnesses, witnessed, information.GetTilesLeft(), information.GetMinesLeft()); ProbabilityEngine pe = new ProbabilityEngine(information, witnesses, witnessed, livingTilesLeft, livingMinesLeft); pe.Process(); long time3 = DateTime.Now.Ticks; information.Write("Probability Engine took " + (time3 - time2) + " ticks"); // have we found any local clears which we can use List <SolverTile> localClears = pe.GetLocalClears(); if (localClears.Count > 0) { foreach (SolverTile tile in localClears) // place each local clear into an action { actions.Add(new SolverAction(tile, ActionType.Clear, 1)); } information.Write("The probability engine has found " + localClears.Count + " safe Local Clears"); // add any mines to it List <SolverTile> minesFound = pe.GetMinesFound(); foreach (SolverTile tile in minesFound) // place each mine found into an action { information.MineFound(tile); actions.Add(new SolverAction(tile, ActionType.Flag, 0)); } information.Write("The probability engine has found " + minesFound.Count + " mines"); return(BuildActionHeader(information, actions)); } if (pe.GetBestEdgeProbability() == 1) { actions = pe.GetBestCandidates(1); information.Write("The probability engine has found " + actions.Count + " safe Clears"); // add any mines to it List <SolverTile> minesFound = pe.GetMinesFound(); foreach (SolverTile tile in minesFound) // place each mine found into an action { information.MineFound(tile); actions.Add(new SolverAction(tile, ActionType.Flag, 0)); } information.Write("The probability engine has found " + minesFound.Count + " mines"); return(BuildActionHeader(information, actions)); } // dead edge (all tiles on the edge are dead and there is only one mine count value) if (pe.GetDeadEdge().Count != 0) { SolverTile tile = pe.GetDeadEdge()[0]; information.Write("Probability engine has found a dead area, guessing at " + tile.AsText()); double probability = Combination.DivideBigIntegerToDouble(BigInteger.One, pe.GetDeadEdgeSolutionCount(), 6); actions.Add(new SolverAction(tile, ActionType.Clear, probability)); return(BuildActionHeader(information, actions)); } // Isolated edges found (all adjacent tiles are also on the edge and there is only one mine count value) if (pe.GetOutcome() == ProbabilityEngine.Outcome.ISOLATED_EDGE) { information.Write("Probability engine has found an isolated area"); Cruncher cruncher = pe.getIsolatedEdgeCruncher(); // determine all possible solutions cruncher.Crunch(); // determine best way to solver them BruteForceAnalysis bfa = cruncher.GetBruteForceAnalysis(); bfa.process(); // if after trying to process the data we can't complete then abandon it if (!bfa.IsComplete()) { information.Write("Abandoned the Brute Force Analysis after " + bfa.GetNodeCount() + " steps"); bfa = null; } else // otherwise try and get the best long term move { information.Write("Built probability tree from " + bfa.GetSolutionCount() + " solutions in " + bfa.GetNodeCount() + " steps"); SolverAction move = bfa.GetNextMove(); if (move != null) { information.SetBruteForceAnalysis(bfa); // save the details so we can walk the tree information.Write("Brute Force Analysis: " + move.AsText()); actions.Add(move); return(BuildActionHeader(information, actions)); } else if (bfa.GetAllDead()) { SolverTile tile = cruncher.getTiles()[0]; information.Write("Brute Force Analysis has decided all tiles are dead on the Isolated Edge, guessing at " + tile.AsText()); double probability = Combination.DivideBigIntegerToDouble(BigInteger.One, bfa.GetSolutionCount(), 6); actions.Add(new SolverAction(tile, ActionType.Clear, probability)); return(BuildActionHeader(information, actions)); } else { information.Write("Brute Force Analysis: no move found!"); } } } // after this point we know the probability engine didn't return any certain clears. But there are still some special cases when everything off edge is either clear or a mine // If there are tiles off the edge and they are definitely safe then clear them all, or mines then flag them if (offEdgeTilesLeft > 0 && (pe.GetOffEdgeProbability() == 1 || pe.GetOffEdgeProbability() == 0)) { information.Write("Looking for the certain moves off the edge found by the probability engine"); bool clear; if (pe.GetOffEdgeProbability() == 1) { information.Write("All off edge tiles are clear"); clear = true; } else { information.Write("All off edge tiles are mines"); clear = false; } for (int x = 0; x < information.description.width; x++) { for (int y = 0; y < information.description.height; y++) { SolverTile tile = information.GetTile(x, y); if (tile.IsHidden() && !witnessedSet.Contains(tile)) { if (clear) { information.Write(tile.AsText() + " is clear"); actions.Add(new SolverAction(tile, ActionType.Clear, 1)); } else { information.Write(tile.AsText() + " is mine"); information.MineFound(tile); actions.Add(new SolverAction(tile, ActionType.Flag, 0)); } } } } if (actions.Count > 0) { // add any mines to it List <SolverTile> minesFound = pe.GetMinesFound(); foreach (SolverTile tile in minesFound) // place each mine found into an action { information.MineFound(tile); actions.Add(new SolverAction(tile, ActionType.Flag, 0)); } information.Write("The probability engine has found " + minesFound.Count + " mines"); return(BuildActionHeader(information, actions)); } else { Console.WriteLine("No Actions found!"); } } // these are guesses List <SolverAction> guesses = pe.GetBestCandidates(1); // we know the Probability Engine completed so hold onto the information for the gui information.SetProbabilityEngine(pe); // if there aren't many possible solutions then do a brute force search if (pe.GetSolutionCount() <= MAX_BFDA_SOLUTIONS) { //if (minesFound.Count > 0) { // information.Write("Not doing a brute force analysis because we found some mines using the probability engine"); // return BuildActionHeader(information, actions); //} // find a set of independent witnesses we can use as the base of the iteration pe.GenerateIndependentWitnesses(); BigInteger expectedIterations = pe.GetIndependentIterations() * SolverMain.Calculate(livingMinesLeft - pe.GetIndependentMines(), livingTilesLeft - pe.GetIndependentTiles()); information.Write("Expected Brute Force iterations " + expectedIterations); // do the brute force if there are not too many iterations if (expectedIterations < MAX_BRUTE_FORCE_ITERATIONS) { List <SolverTile> allCoveredTiles = new List <SolverTile>(); for (int x = 0; x < information.description.width; x++) { for (int y = 0; y < information.description.height; y++) { SolverTile tile = information.GetTile(x, y); if (tile.IsHidden() && !tile.IsMine()) { allCoveredTiles.Add(tile); } } } WitnessWebIterator[] iterators = BuildParallelIterators(information, pe, allCoveredTiles, expectedIterations); BruteForceAnalysis bfa = Cruncher.PerformBruteForce(information, iterators, pe.GetDependentWitnesses()); bfa.process(); // if after trying to process the data we can't complete then abandon it if (!bfa.IsComplete()) { information.Write("Abandoned the Brute Force Analysis after " + bfa.GetNodeCount() + " steps"); bfa = null; } else // otherwise try and get the best long term move { information.Write("Built probability tree from " + bfa.GetSolutionCount() + " solutions in " + bfa.GetNodeCount() + " steps"); SolverAction move = bfa.GetNextMove(); if (move != null) { information.SetBruteForceAnalysis(bfa); // save the details so we can walk the tree information.Write("Brute Force Analysis: " + move.AsText()); actions.Add(move); return(BuildActionHeader(information, actions)); } else { information.Write("Brute Force Analysis: no move found!"); } } } else { information.Write("Too many iterations, Brute Force not atempted"); } } if (guesses.Count == 0) // find an off edge guess { if (offEdgeTilesLeft > 0) { information.Write("getting an off edge guess"); SolverTile tile = OffEdgeGuess(information, witnessedSet); SolverAction action = new SolverAction(tile, ActionType.Clear, pe.GetOffEdgeProbability()); information.Write(action.AsText()); actions.Add(action); } else { if (information.GetDeadTiles().Count > 0) { information.Write("Finding a dead tile to guess"); SolverTile tile = null; foreach (SolverTile deadTile in information.GetDeadTiles()) // get the first dead tile { tile = deadTile; break; } SolverAction action = new SolverAction(tile, ActionType.Clear, 0.5); // probability may not be 0.5 actions.Add(action); } } } else if (guesses.Count > 1) // if we have more than 1 guess then do some tie break logic { information.Write("Doing a tie break for " + guesses.Count + " actions"); actions = DoTieBreak(guesses); } else { actions = guesses; } return(BuildActionHeader(information, actions)); }