package minesweeper.solver; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import Monitor.AsynchMonitor; import minesweeper.solver.constructs.CandidateLocation; import minesweeper.solver.constructs.Square; import minesweeper.solver.constructs.Witness; import minesweeper.solver.iterator.WitnessWebIterator; import minesweeper.solver.utility.Logger.Level; import minesweeper.structure.Location; public class BruteForce { //private final static BigDecimal ZERO_THRESHOLD = new BigDecimal("0.25"); private final WitnessWeb web; private final Solver solver; private final BoardState boardState; private final int mines; private final BigInteger maxIterations; private final int bfMaxSolutions; private CrunchResult crunchResult; private boolean hasRun = false; private boolean certainClear = false; //private final List zeroLocations = new ArrayList<>(); private final List results = new ArrayList<>(); private final String scope; private BigInteger iterations; private BruteForceAnalysisModel bruteForceAnalysis; public BruteForce(Solver solver, BoardState boardState, WitnessWeb web, int mines, BigInteger maxIterations, int bfMaxSolutions, String scope) { this.solver = solver; this.boardState = boardState; this.maxIterations = maxIterations; this.bfMaxSolutions = bfMaxSolutions; this.scope = scope; this.web = web; this.mines = mines; } public void process() { solver.logger.log(Level.INFO, "Brute force on %d Squares with %d mines", web.getSquares().size(), mines); // if we have no mines to place then everything must be a clear if (mines == 0 ) { solver.logger.log(Level.INFO, "brute force but already found all the mines - clear all the remaining squares"); for (Square squ: web.getSquares()) { results.add(new CandidateLocation(squ.x, squ.y, BigDecimal.ONE, boardState.countAdjacentUnrevealed(squ), boardState.countAdjacentConfirmedFlags(squ))); } iterations = BigInteger.ONE; hasRun = true; return; } // now doing this logic 'just in time' rather than always at witness web generation web.generateIndependentWitnesses(); // and crunch the result if we have something to check against if (web.getPrunedWitnesses().size() >= 0) { iterations = web.getIterations(mines); if (iterations.compareTo(maxIterations) <= 0) { //display("Brute Force about to process " + iterations + " iterations"); WitnessWebIterator[] iterators = buildParallelIterators(mines, iterations); this.bruteForceAnalysis = new BruteForceAnalysis(solver, iterators[0].getLocations(), bfMaxSolutions, scope, solver.bfdaStartLocations()); crunchResult = crunchParallel(web.getSquares(), web.getPrunedWitnesses(), true, iterators); // if there are too many to process then don't bother if (this.bruteForceAnalysis != null && this.bruteForceAnalysis.tooMany()) { this.bruteForceAnalysis = null; } int actIterations = 0; for (WitnessWebIterator i: iterators) { actIterations = actIterations + i.getIterations(); } solver.logger.log(Level.DEBUG, "Expected iterations %d Actual iterations %d", iterations, actIterations); solver.logger.log(Level.INFO, "Found %d candidate solutions in the %s", crunchResult.bigGoodCandidates, scope); certainClear = findCertainClear(crunchResult); if (certainClear) { this.bruteForceAnalysis = null; } hasRun = true; //TODO zero additional mines calculater - do we want it? /* if (crunchResult.bigGoodCandidates.signum() != 0) { BigInteger hwm = BigInteger.ZERO; int best = -1; for (int i=0; i < crunchResult.getSquare().size(); i++) { Location loc = crunchResult.getSquare().get(i); int adjacentMines = boardState.countAdjacentConfirmedFlags(loc); BigDecimal prob = new BigDecimal(crunchResult.bigDistribution[i][adjacentMines]).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP); if (prob.compareTo(ZERO_THRESHOLD) >= 0) { SuperLocation zl = new SuperLocation(loc.x, loc.y, boardState.countAdjacentUnrevealed(loc), adjacentMines); zl.setProbability(prob); zeroLocations.add(zl); } if (crunchResult.bigDistribution[i][adjacentMines].compareTo(hwm) > 0) { hwm = crunchResult.bigDistribution[i][adjacentMines]; best = i; } } if (best != -1) { BigDecimal prob = new BigDecimal(hwm).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP); boardState.display("Location " + crunchResult.getSquare().get(best).display() + " is a 'zero additional mines' with probability " + prob); } } */ } else { if (maxIterations.signum() != 0) { solver.logger.log(Level.INFO, "Brute Force too large with %d iterations", iterations); } } } else { solver.logger.log(Level.INFO, "Brute Force not performed since there are no witnesses"); } } // break a witness web search into a number of non-overlapping iterators private WitnessWebIterator[] buildParallelIterators(int mines, BigInteger totalIterations) { solver.logger.log(Level.DEBUG, "Building parallel iterators"); //WitnessWebIterator[] result1 = new WitnessWebIterator[1]; //result1[0] = new WitnessWebIterator(web, mines); //return result1; solver.logger.log(Level.DEBUG, "Non independent iterations %d", web.getNonIndependentIterations(mines)); // if there is only one cog then we can't lock it,so send back a single iterator if (web.getIndependentWitnesses().size() == 1 && web.getIndependentMines() >= mines || totalIterations.compareTo(Solver.PARALLEL_MINIMUM) < 0 || web.getPrunedWitnesses().size() == 0 || solver.preferences.isSingleThread()) { solver.logger.log(Level.DEBUG, "Only a single iterator will be used"); WitnessWebIterator[] result = new WitnessWebIterator[1]; result[0] = new WitnessWebIterator(web, mines); return result; } int witMines = web.getIndependentWitnesses().get(0).getMines(); int squares = web.getIndependentWitnesses().get(0).getSquares().size(); BigInteger bigIterations = Solver.combination(witMines, squares); int iter = bigIterations.intValue(); solver.logger.log(Level.DEBUG, "The first cog has %d iterations, so parallel processing is possible", iter); WitnessWebIterator[] result = new WitnessWebIterator[iter]; for (int i=0; i < iter; i++) { result[i] = new WitnessWebIterator(web, mines, i); // create a iterator with a lock first got at position i } return result; } // process the iterators in parallel private CrunchResult crunchParallel(List square, List witness, boolean calculateDistribution, WitnessWebIterator... iterator) { solver.logger.log(Level.DEBUG, "At parallel iterator processing"); Cruncher[] crunchers = new Cruncher[iterator.length]; for (int i=0; i < iterator.length; i++) { crunchers[i] = new Cruncher(boardState, iterator[i].getLocations(), witness, iterator[i], false, bruteForceAnalysis); } AsynchMonitor monitor = new AsynchMonitor(crunchers); monitor.setMaxThreads(Solver.CORES); try { monitor.startAndWait(); } catch (Exception ex) { solver.logger.log(Level.ERROR, "Parallel processing caused an error: %s", ex.getMessage()); ex.printStackTrace(); } CrunchResult[] results = new CrunchResult[crunchers.length]; for (int i=0; i < crunchers.length; i++) { results[i] = crunchers[i].getResult(); } CrunchResult result = CrunchResult.bigMerge(results); return result; } private boolean findCertainClear(CrunchResult output) { // if there were no good candidates then there is nothing to check if (output.bigGoodCandidates.signum() == 0) { return false; } // check the tally information to see if we have a square where a // mine is never present for (int i=0; i < output.bigTally.length; i++) { if (output.bigTally[i].signum() == 0) { return true; } } return false; } /* // do the tally check using the BigInteger values private List checkBigTally(CrunchResult output) { List result = new ArrayList<>(); // if there were no good candidates then there is nothing to check if (output.bigGoodCandidates.compareTo(BigInteger.ZERO) == 0) { return result; } // check the tally information to see if we have a square where a // mine is always present or never present for (int i=0; i < output.bigTally.length; i++) { // if there is always a mine here then odds of clear is zero if (output.bigTally[i].compareTo(output.bigGoodCandidates) == 0) { int x = output.getSquare().get(i).x; int y = output.getSquare().get(i).y; results.add(new CandidateLocation(x, y, BigDecimal.ZERO, boardState.countAdjacentUnrevealed(x, y), boardState.countAdjacentConfirmedFlags(x, y))); // if never a mine then odds of clear is one } else if (output.bigTally[i].compareTo(BigInteger.ZERO) == 0) { int x = output.getSquare().get(i).x; int y = output.getSquare().get(i).y; results.add(new CandidateLocation(x, y, BigDecimal.ONE, boardState.countAdjacentUnrevealed(x, y), boardState.countAdjacentConfirmedFlags(x, y))); certainClear = true; } } return result; } */ /* public List getBestSolutions(BigDecimal freshhold) { if (crunchResult == null) { return results; } if (!results.isEmpty()) { return results; } List candidates = new ArrayList<>(); boolean ignoreBad = true; if (crunchResult.getMaxCount() <= 1) { ignoreBad = false; solver.display("No candidates provide additional information"); } // Calculate the probability of a mine being in the square and store in a list for (int i=0; i < crunchResult.bigTally.length; i++) { BigDecimal mine = new BigDecimal(crunchResult.bigTally[i]).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP); BigDecimal notMine = BigDecimal.ONE.subtract(mine); Location l = crunchResult.getSquare().get(i); // ignore candidates that yield no info, unless none do or they are certainties if (crunchResult.getBigCount()[i] > 1 || !ignoreBad || notMine.compareTo(BigDecimal.ZERO) == 0 || notMine.compareTo(BigDecimal.ONE) == 0) { candidates.add(new CandidateLocation(l.x, l.y, notMine, boardState.countAdjacentUnrevealed(l), boardState.countAdjacentConfirmedFlags(l), crunchResult.getBigCount()[i])); } else { solver.display(l.display() + " clear probability " + notMine + " discarded because it reveals no further information"); } //candidates.add(new CandidateLocation(l.x, l.y, notMine, boardState.countAdjacentUnrevealed(l), boardState.countAdjacentConfirmedFlags(l))); } // sort the candidates into descending order by probability Collections.sort(candidates, CandidateLocation.SORT_BY_PROB_FLAG_FREE); BigDecimal hwm = candidates.get(0).getProbability(); BigDecimal tolerence; if (hwm.compareTo(BigDecimal.ONE) == 0) { tolerence = hwm; } else { tolerence = hwm.multiply(freshhold); } for (CandidateLocation cl: candidates) { if (cl.getProbability().compareTo(tolerence) >= 0) { results.add(cl); } else { break; } } boardState.display("Best Guess: " + candidates.size() + " candidates, " + results.size() + " passed tolerence at " + tolerence); return results; } */ public boolean hasRun() { return this.hasRun; } public boolean hasCertainClear() { return this.certainClear; } public CrunchResult getCrunchResult() { return this.crunchResult; } public BigInteger getSolutionCount() { if (crunchResult == null) { return BigInteger.ONE; } return crunchResult.bigGoodCandidates; } public BigInteger getIterations() { return this.iterations; } public int getTileCount() { return web.getSquares().size(); } public BruteForceAnalysisModel getBruteForceAnalysis() { return bruteForceAnalysis; } /** * Set the probability for the probabilityLocation being satisfied * @param list */ /* public List setProbabilities(List list) { if (!hasRun) { return list; } List output = new ArrayList<>(); for (ProbabilityLocation pl: list) { for (int i=0; i < crunchResult.getSquare().size(); i++) { if (crunchResult.getSquare().get(i).equals(pl)) { // get the values which are good for this location int[] adjFlagsRequired = pl.getAdjacentFlagsRequired(); // count the number of solutions which have those values BigInteger count = BigInteger.ZERO; for (int j = 0; j < adjFlagsRequired.length; j++) { count = count.add(crunchResult.bigDistribution[i][adjFlagsRequired[j]]); } // work out the % chance of it happening or if zero chance discard the location if (count.signum() != 0) { BigDecimal prob = new BigDecimal(count).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP); pl.setProbability(prob); boardState.display(pl.display() + " has probability " + prob); output.add((T) pl); } else { boardState.display(pl.display() + " has probability zero and is being discarded"); } break; } } } return output; } */ /** * Returns the probability that this square is not a mine */ public BigDecimal getProbability(int x, int y) { Location l = this.boardState.getLocation(x,y); if (crunchResult == null) { // this can happen if there are no mines left to find, so everything is a clear for (Location loc: web.getSquares()) { // if the mouse is hovering over one of the brute forced squares if (loc.equals(l)) { return BigDecimal.ONE; } } return BigDecimal.ZERO; } for (int i=0; i < crunchResult.getSquare().size(); i++) { if (crunchResult.getSquare().get(i).equals(l)) { BigDecimal prob = new BigDecimal(crunchResult.bigTally[i]).divide(new BigDecimal(crunchResult.bigGoodCandidates), Solver.DP, RoundingMode.HALF_UP); return BigDecimal.ONE.subtract(prob); } } return BigDecimal.ZERO; } }