Files
BH2023-Minesweeper/info/Minesweeper/MineSweeperSolver/src/minesweeper/solver/LongTermRiskHelperOld.java
2023-09-28 20:23:18 +08:00

570 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.Arrays;
import java.util.List;
import minesweeper.solver.utility.Logger.Level;
import minesweeper.structure.Area;
import minesweeper.structure.Location;
public class LongTermRiskHelperOld {
private boolean[][] PATTERNS = new boolean[][] {{true, false, false, true}, {false, true, true, false}}; // 2 ways to make a 50/50 in a box
private class Risk5050 {
private Location poi;
private List<Location> area;
private List<Location> livingArea = new ArrayList<>();
private Risk5050(Location poi, Location... locs) {
this.poi = poi;
this.area = Arrays.asList(locs);
for (Location loc: locs) {
if (!deadLocations.contains(loc)) {
livingArea.add(loc);
}
}
}
}
private final static BigDecimal HALF = new BigDecimal("0.5");
private final BoardState board;
private final WitnessWeb wholeEdge;
private final ProbabilityEngineModel currentPe;
private final Area deadLocations;
private final List<Location> fifty;
private BigDecimal currentLongTermSafety;
private Risk5050 worstBox5050;
private BigDecimal box5050Safety = BigDecimal.ONE;
private BigDecimal twoTileSafety;
private List<Risk5050> risk5050s = new ArrayList<>();
public LongTermRiskHelperOld(BoardState board, WitnessWeb wholeEdge, ProbabilityEngineModel pe) {
this.board = board;
this.wholeEdge = wholeEdge;
this.currentPe = pe;
this.deadLocations = pe.getDeadLocations();
this.fifty = currentPe.getFiftyPercenters();
// sort into location order
fifty.sort(null);
}
public void findRisks() {
checkFor2Tile5050();
//checkForBox5050();
this.currentLongTermSafety = this.box5050Safety.multiply(this.twoTileSafety);
}
public void checkFor2Tile5050() {
BigDecimal longTermSafety = BigDecimal.ONE;
for (int i=0; i < fifty.size(); i++) {
Location tile1 = fifty.get(i);
Location tile2 = null;
Location info = null;
Risk5050 risk = null;
for (int j=i+1; j < fifty.size(); j++) {
tile2 = fifty.get(j);
// tile2 is below tile1
if (tile1.x == tile2.x && tile1.y == tile2.y - 1) {
info = checkVerticalInfo(tile1, tile2);
if (info == null) { // try extending it
Location tile3 = getFifty(tile2.x, tile2.y + 2);
Location tile4 = getFifty(tile2.x, tile2.y + 3);
if (tile3 != null && tile4 != null) {
info = checkVerticalInfo(tile1, tile4);
if (info != null) {
risk = new Risk5050(info, tile1, tile2, tile3, tile4);
}
}
} else {
risk = new Risk5050(info, tile1, tile2);
}
break;
}
// tile 2 is right of tile1
if (tile1.x == tile2.x - 1 && tile1.y == tile2.y) {
info = checkHorizontalInfo(tile1, tile2);
if (info == null) { // try extending it
Location tile3 = getFifty(tile2.x + 2, tile2.y);
Location tile4 = getFifty(tile2.x + 3, tile2.y);
if (tile3 != null && tile4 != null) {
info = checkHorizontalInfo(tile1, tile4);
if (info != null) {
risk = new Risk5050(info, tile1, tile2, tile3, tile4);
}
}
} else {
risk = new Risk5050(info, tile1, tile2);
}
break;
}
}
// if the 2 fifties form a pair with only 1 remaining source of information
if (risk != null) {
risk5050s.add(risk); // store the positions of interest
BigDecimal safety = BigDecimal.ONE.subtract(BigDecimal.ONE.subtract(currentPe.getProbability(info)).multiply(HALF));
board.getLogger().log(Level.INFO, "Seed %d - %s %s has 1 remaining source of information - tile %s %f", board.getSolver().getGame().getSeed(), tile1, tile2, info, safety);
longTermSafety = longTermSafety.multiply(safety);
}
}
if (longTermSafety.compareTo(BigDecimal.ONE) != 0) {
board.getLogger().log(Level.INFO, "Seed %d - Total long term safety %f", board.getSolver().getGame().getSeed(), longTermSafety);
}
this.twoTileSafety = longTermSafety;
}
private boolean isFifty(int x, int y) {
return (getFifty(x, y) != null);
}
private Location getFifty(int x, int y) {
for (Location loc: fifty) {
if (loc.x == x && loc.y == y) {
return loc;
}
}
return null;
}
public List<Location> get5050Breakers() {
List<Location> breakers = new ArrayList<>();
if (board.getSolver().preferences.considerLongTermSafety()) {
for (Risk5050 risk: risk5050s) {
breakers.addAll(risk.livingArea);
breakers.add(risk.poi);
}
}
return breakers;
}
public BigDecimal getLongTermSafety() {
return this.currentLongTermSafety;
}
public BigDecimal getLongTermSafety(Location candidate, ProbabilityEngineModel pe) {
BigDecimal longTermSafety = null;
if (board.getSolver().preferences.considerLongTermSafety()) {
// if there is a possible box 50/50 then see if we are breaking it, otherwise use that as the start safety
if (worstBox5050 != null) {
if (worstBox5050.poi.equals(candidate) || pe.getProbability(worstBox5050.poi).compareTo(BigDecimal.ONE) == 0) {
//board.getLogger().log(Level.INFO, "%s has broken 50/50", candidate);
longTermSafety = BigDecimal.ONE;
} else {
for (Location loc: worstBox5050.area) {
if (loc.equals(candidate)) {
//board.getLogger().log(Level.INFO, "%s has broken 50/50", candidate);
longTermSafety = BigDecimal.ONE;
break;
}
}
}
if (longTermSafety == null) {
longTermSafety = this.box5050Safety;
}
} else {
longTermSafety = BigDecimal.ONE;
}
for (Risk5050 risk: this.risk5050s) {
BigDecimal safety = null;
// is the candidate part of the 50/50 - if so it is being broken
for (Location loc: risk.area) {
if (loc.equals(candidate)) {
safety = BigDecimal.ONE;
break;
}
}
if (safety == null) {
if (risk.poi.equals(candidate)) {
safety = BigDecimal.ONE;
} else {
safety = BigDecimal.ONE.subtract(BigDecimal.ONE.subtract(pe.getProbability(risk.poi)).multiply(HALF));
}
}
longTermSafety = longTermSafety.multiply(safety);
}
} else {
longTermSafety = BigDecimal.ONE;
}
return longTermSafety;
}
// returns the location of the 1 tile which can still provide information, or null
private Location checkVerticalInfo(Location tile1, Location tile2) {
Location info = null;
final int top = tile1.y - 1;
final int bottom = tile2.y + 1;
final int left = tile1.x - 1;
if (isPotentialInfo(left, top)) {
if (board.isRevealed(left, top)) {
return null;
} else {
info = new Location(left, top);
}
}
if (isPotentialInfo(left + 1, top)) {
if (board.isRevealed(left + 1, top)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left + 1, top);
}
if (isPotentialInfo(left + 2, top)) {
if (board.isRevealed(left + 2, top)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left + 2, top);
}
if (isPotentialInfo(left, bottom)) {
if (board.isRevealed(left, bottom)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left, bottom);
}
if (isPotentialInfo(left + 1, bottom)) {
if (board.isRevealed(left + 1, bottom)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left + 1, bottom);
}
if (isPotentialInfo(left + 2, bottom)) {
if (board.isRevealed(left + 2, bottom)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left + 2, bottom);
}
return info;
}
// returns the location of the 1 tile which can still provide information, or null
private Location checkHorizontalInfo(Location tile1, Location tile2) {
Location info = null;
final int top = tile1.y - 1;
final int left = tile1.x - 1;
final int right = tile2.x + 1;
if (isPotentialInfo(left, top)) {
if (board.isRevealed(left, top)) {
return null;
} else {
info = new Location(left, top);
}
}
if (isPotentialInfo(left, top + 1)) {
if (board.isRevealed(left, top + 1)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left, top + 1);
}
if (isPotentialInfo(left, top + 2)) {
if (board.isRevealed(left, top + 2)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left, top + 2);
}
if (isPotentialInfo(right, top)) {
if (board.isRevealed(right, top)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(right, top);
}
if (isPotentialInfo(right, top + 1)) {
if (board.isRevealed(right, top + 1)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(right, top + 1);
}
if (isPotentialInfo(right, top + 2)) {
if (board.isRevealed(right, top + 2)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(right, top + 2);
}
return info;
}
private void checkForBox5050() {
// box 2x2
Location[] tiles = new Location[4];
BigDecimal maxProbability = BigDecimal.ZERO;
Risk5050 worst5050 = null;
List<Location> mines = new ArrayList<>();
List<Location> noMines = new ArrayList<>();
for (int i=0; i < board.getGameWidth() - 1; i++) {
for (int j=0; j < board.getGameHeight() - 1; j++) {
// need 4 hidden tiles
if (!board.isUnrevealed(i, j) || !board.isUnrevealed(i, j + 1) || !board.isUnrevealed(i + 1, j) || !board.isUnrevealed(i + 1, j + 1)) {
continue;
}
tiles[0] = new Location(i, j);
Location info = checkBoxInfo(tiles[0]);
// need the corners to be flags or off the board
if (info == null) {
continue; // this skips the rest of the logic below this in the for-loop
}
tiles[1] = new Location(i + 1, j);
tiles[2] = new Location(i, j + 1);
tiles[3] = new Location(i + 1, j + 1);
BigInteger solutions = BigInteger.ZERO;
for (int k = 0; k < PATTERNS.length; k++) {
mines.clear();
noMines.clear();
mines.add(info); // the missing mine
// allocate each position as a mine or noMine
for (int l = 0; l < 4; l++) {
if (PATTERNS[k][l]) {
mines.add(tiles[l]);
} else {
noMines.add(tiles[l]);
}
}
// see if the position is valid
SolutionCounter counter = board.getSolver().validatePosition(wholeEdge, mines, noMines, Area.EMPTY_AREA);
// if it is then mark each mine tile as risky
if (counter.getSolutionCount().signum() != 0) {
board.getLogger().log(Level.DEBUG, "Pattern %d is valid with %d solutions", k, counter.getSolutionCount());
solutions = solutions.add(counter.getSolutionCount());
} else {
board.getLogger().log(Level.DEBUG, "Pattern %d is not valid", k);
}
}
BigDecimal probability = new BigDecimal(solutions).divide(new BigDecimal(this.currentPe.getSolutionCount()), 6, RoundingMode.HALF_UP);
board.getLogger().log(Level.INFO, "%s %s %s %s is box 50/50 %f of the time", tiles[0], tiles[1], tiles[2], tiles[3], probability);
if (probability.compareTo(maxProbability) > 0) {
maxProbability = probability;
worst5050 = new Risk5050(info, tiles[0], tiles[1], tiles[2], tiles[3]);
board.getLogger().log(Level.INFO, "%s %s %s %s is box 50/50 is new worst 50/50", tiles[0], tiles[1], tiles[2], tiles[3]);
}
}
}
this.worstBox5050 = worst5050;
this.box5050Safety = BigDecimal.ONE.subtract(maxProbability.multiply(HALF));
}
// returns the location of the 1 tile which can still provide information for a 2x2 box, or null
private Location checkBoxInfo(Location tileTopLeft) {
Location info = null;
final int top = tileTopLeft.y - 1;
final int left = tileTopLeft.x - 1;
if (isPotentialInfo(left, top)) {
if (board.isRevealed(left, top)) {
return null;
} else {
info = new Location(left, top);
}
}
if (isPotentialInfo(left, top - 3)) {
if (board.isRevealed(left, top - 3)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left, top - 3);
}
if (isPotentialInfo(left + 3, top)) {
if (board.isRevealed(left + 3, top)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left + 3, top);
}
if (isPotentialInfo(left + 3, top + 3)) {
if (board.isRevealed(left + 3, top + 3)) { // info is certain
return null;
} else {
if (info != null) { // more than 1 tile giving possible info
return null;
}
}
info = new Location(left + 3, top + 3);
}
return info;
}
// returns whether there information to be had at this location; i.e. on the board and either unrevealed or revealed
private boolean isPotentialInfo(int x, int y) {
if (x < 0 || x >= board.getGameWidth() || y < 0 || y >= board.getGameHeight()) {
return false;
}
if (board.isConfirmedMine(x, y) || isMineInPe(x, y)) {
return false;
} else {
return true;
}
}
private boolean isMineInPe(int x, int y) {
for (Location loc: this.currentPe.getMines()) {
if (loc.x == x && loc.y == y) {
return true;
}
}
return false;
}
}