465 lines
15 KiB
Java
465 lines
15 KiB
Java
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<SuperLocation> zeroLocations = new ArrayList<>();
|
|
|
|
private final List<CandidateLocation> 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> square, List<Witness> 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<CandidateLocation> checkBigTally(CrunchResult output) {
|
|
|
|
List<CandidateLocation> 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<CandidateLocation> getBestSolutions(BigDecimal freshhold) {
|
|
|
|
if (crunchResult == null) {
|
|
return results;
|
|
}
|
|
|
|
if (!results.isEmpty()) {
|
|
return results;
|
|
}
|
|
|
|
List<CandidateLocation> 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 <T extends ProbabilityLocation> List<T> setProbabilities(List<T> list) {
|
|
|
|
if (!hasRun) {
|
|
return list;
|
|
}
|
|
|
|
List<T> 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;
|
|
|
|
}
|
|
|
|
}
|