Skip to content

Commit

Permalink
Early 50/50 checking (#24)
Browse files Browse the repository at this point in the history
* Early 50/50 checking

* Find 50/50 loops

* Add early 50/50 setting

* Always show dead tiles

* Disable full BFDA for hint refresh

* No pseudos

* Use full probability

* Ignore dead tiles

* Modern bulk run

---------

Co-authored-by: Wannes Boeykens <wannes.boeykens@student.kuleuven.be>
  • Loading branch information
nineteendo and Wannes Boeykens authored Jan 29, 2025
1 parent 5bc3f91 commit 97a94a7
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 56 deletions.
6 changes: 3 additions & 3 deletions Minesweeper/client/BruteForceAnalysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class BruteForceAnalysis {
alive.zeroSolutions = valueCount[0];
living.push(alive);
} else {
console.log(BruteForceGlobal.allTiles[i].asText() + " is dead with value " + minValue);
this.writeToConsole(BruteForceGlobal.allTiles[i].asText() + " is dead with value " + minValue);
this.deadTiles.push(BruteForceGlobal.allTiles[i]); // store the dead tiles
}

Expand Down Expand Up @@ -242,7 +242,7 @@ class BruteForceAnalysis {
//solver.display("first best move is " + loc.display());
const prob = 1 - (bestLiving.mineCount / this.currentNode.getSolutionSize());

console.log("mines = " + bestLiving.mineCount + " solutions = " + this.currentNode.getSolutionSize());
this.writeToConsole("mines = " + bestLiving.mineCount + " solutions = " + this.currentNode.getSolutionSize());
for (let i = 0; i < bestLiving.children.length; i++) {
if (bestLiving.children[i] == null) {
//solver.display("Value of " + i + " is not possible");
Expand All @@ -255,7 +255,7 @@ class BruteForceAnalysis {
} else {
probText = bestLiving.children[i].getProbability();
}
console.log("Value of " + i + " leaves " + bestLiving.children[i].getSolutionSize() + " solutions and winning probability " + probText + " (work size " + bestLiving.children[i].work + ")");
this.writeToConsole("Value of " + i + " leaves " + bestLiving.children[i].getSolutionSize() + " solutions and winning probability " + probText + " (work size " + bestLiving.children[i].work + ")");
}

const action = new Action(loc.getX(), loc.getY(), prob, ACTION_CLEAR);
Expand Down
72 changes: 44 additions & 28 deletions Minesweeper/client/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,10 @@ async function startup() {
board.setStarted();
}

//bulkRun(21, 12500); // seed '21' Played 12500 won 5192
//bulkRun(321, 10000); // seed 321 played 10000 won 4142
//bulkRun(21, 12500, false); // seed '21' Played 12500 won 5192
//bulkRun(321, 10000, false); // seed 321 played 10000 won 4142
//bulkRun(0, 1000, false); // classic: seed '0' Won 424/1000 (42.40%)
//bulkRun(0, 1000, true); // modern: seed '0' Won 546/1000 (54.60%)

showMessage("Welcome to minesweeper solver dedicated to Annie");
}
Expand Down Expand Up @@ -428,6 +430,7 @@ function propertiesClose() {

BruteForceGlobal.PRUNE_BF_ANALYSIS = document.getElementById("pruneBruteForce").checked;

SolverGlobal.EARLY_FIFTY_FIFTY_CHECKING = document.getElementById("early5050").checked;
SolverGlobal.CALCULATE_LONG_TERM_SAFETY = document.getElementById("useLTR").checked;
SolverGlobal.PRUNE_GUESSES = document.getElementById("pruneGuesses").checked;

Expand Down Expand Up @@ -455,6 +458,7 @@ function propertiesOpen() {

document.getElementById("pruneBruteForce").checked = BruteForceGlobal.PRUNE_BF_ANALYSIS;

document.getElementById("early5050").checked = SolverGlobal.EARLY_FIFTY_FIFTY_CHECKING;
document.getElementById("useLTR").checked = SolverGlobal.CALCULATE_LONG_TERM_SAFETY;
document.getElementById("pruneGuesses").checked = SolverGlobal.PRUNE_GUESSES;

Expand All @@ -481,6 +485,7 @@ function saveSettings() {
settings.version = SETTINGS_VERSION;
settings.pruneBruteForce = BruteForceGlobal.PRUNE_BF_ANALYSIS;
settings.pruneGuesses = SolverGlobal.PRUNE_GUESSES;
settings.early5050 = SolverGlobal.EARLY_FIFTY_FIFTY_CHECKING;
settings.useLTR = SolverGlobal.CALCULATE_LONG_TERM_SAFETY;

settings.maxAnalysisBfSolutions = BruteForceGlobal.ANALYSIS_BFDA_THRESHOLD;
Expand Down Expand Up @@ -511,6 +516,10 @@ function loadSettings() {
SolverGlobal.PRUNE_GUESSES = settings.pruneGuesses;
}

if (settings.early5050 != null) {
SolverGlobal.EARLY_FIFTY_FIFTY_CHECKING = settings.early5050;
}

if (settings.useLTR != null) {
SolverGlobal.CALCULATE_LONG_TERM_SAFETY = settings.useLTR;
}
Expand Down Expand Up @@ -996,32 +1005,41 @@ function showDownloadLink(show, url) {

}

async function bulkRun(runSeed, size) {
async function bulkRun(runSeed, size, modern) {

const options = {};
options.playStyle = PLAY_STYLE_NOFLAGS;
options.verbose = false;
options.advancedGuessing = true;
options.fullProbability = true;
options.hardcore = false;

const startTime = Date.now();

let played = 0;
let won = 0;

const rng = JSF(runSeed); // create an RNG based on the seed
const startIndex = 0;

let startIndex;
let gameType;
if (modern) {
startIndex = 93;
gameType = "zero"
} else {
startIndex = 0;
gameType = "safe"
}

while (played < size) {

played++;

const gameSeed = rng() * Number.MAX_SAFE_INTEGER;

console.log(gameSeed);
const game = new ServerGame(0, 30, 16, 99, startIndex, gameSeed, gameType);

const game = new ServerGame(0, 30, 16, 99, startIndex, gameSeed, "safe");

const board = new Board(0, 30, 16, 99, gameSeed, "safe");
const board = new Board(0, 30, 16, 99, gameSeed, gameType);

let tile = game.getTile(startIndex);

Expand Down Expand Up @@ -1053,15 +1071,17 @@ async function bulkRun(runSeed, size) {

} else { // otherwise we're trying to clear

tile = game.getTile(board.xy_to_index(action.x, action.y));
if (i == 0 || action.prob == 1) {
tile = game.getTile(board.xy_to_index(action.x, action.y));

revealedTiles = game.clickTile(tile);
revealedTiles = game.clickTile(tile);

if (revealedTiles.header.status != IN_PLAY) { // if won or lost nothing more to do
break;
}
if (revealedTiles.header.status != IN_PLAY) { // if won or lost nothing more to do
break;
}

applyResults(board, revealedTiles);
applyResults(board, revealedTiles);
}

if (action.prob != 1) { // do no more actions after a guess
break;
Expand All @@ -1071,15 +1091,16 @@ async function bulkRun(runSeed, size) {

}

console.log(revealedTiles.header.status);

if (revealedTiles.header.status == WON) {
won++;
}

const winPercentage = (won / played * 100).toFixed(2);
console.log("Won " + won + "/" + played + " (" + winPercentage + "%)");

}

console.log("Played " + played + " won " + won);
console.log("Bulk run finished in " + (Date.now() - startTime) + " milliseconds")
}

async function playAgain() {
Expand Down Expand Up @@ -1927,6 +1948,7 @@ async function replayForward(replayType) {
options.fullProbability = true;
options.advancedGuessing = false;
options.verbose = false;
options.hardcore = false;

let hints;
let other;
Expand Down Expand Up @@ -2139,6 +2161,7 @@ async function replayBackward(replayType) {
options.fullProbability = true;
options.advancedGuessing = false;
options.verbose = false;
options.hardcore = false;

let hints;
let other;
Expand Down Expand Up @@ -2267,14 +2290,10 @@ async function doAnalysis(fullBFDA) {
options.playStyle = PLAY_STYLE_NOFLAGS_EFFICIENCY;
}

if (docOverlay.value != "none") {
options.fullProbability = true;
} else {
options.fullProbability = false;
}

options.fullProbability = true;
options.guessPruning = guessAnalysisPruning;
options.fullBFDA = fullBFDA;
options.hardcore = docHardcore.checked;

const solve = await solver(board, options); // look for solutions
const hints = solve.actions;
Expand Down Expand Up @@ -3126,11 +3145,8 @@ async function handleSolver(solverStart, headerId) {
options.playStyle = PLAY_STYLE_NOFLAGS_EFFICIENCY;
}

if (docOverlay.value != "none" || docHardcore.checked) {
options.fullProbability = true;
} else {
options.fullProbability = false;
}
options.fullProbability = true;
options.hardcore = docHardcore.checked;

let hints;
let other;
Expand Down
62 changes: 59 additions & 3 deletions Minesweeper/client/solver_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const PLAY_STYLE_NOFLAGS_EFFICIENCY = 4;
class SolverGlobal {

static PRUNE_GUESSES = true; // Determines whether calculations continue after the tile can no longer be the best
static EARLY_FIFTY_FIFTY_CHECKING = true; // Determines whether 50/50 checking is done when there are safe tiles
static CALCULATE_LONG_TERM_SAFETY = true; // Switches 50/50 influence processing on or off, also most pseudo-50/50 detection

}
Expand Down Expand Up @@ -241,7 +242,7 @@ async function solver(board, options) {

// add any trivial moves we've found
if (options.fullProbability || options.playStyle == PLAY_STYLE_EFFICIENCY || options.playStyle == PLAY_STYLE_NOFLAGS_EFFICIENCY) {
console.log("Skipping trivial analysis since Probability Engine analysis is required")
writeToConsole("Skipping trivial analysis since Probability Engine analysis is required")
} else {
result.push(...trivial_actions(board, witnesses));
}
Expand Down Expand Up @@ -375,7 +376,62 @@ async function solver(board, options) {
totalSafe++;
}
showMessage("The solver has found " + totalSafe + " safe files." + formatSolutions(pe.finalSolutionsCount));
return new EfficiencyHelper(board, witnesses, witnessed, result, options.playStyle, pe, allCoveredTiles).process();
result = new EfficiencyHelper(board, witnesses, witnessed, result, options.playStyle, pe, allCoveredTiles).process()

if (!options.noGuessingMode) {
// See if there are any unavoidable 2 tile 50/50 guesses
if (SolverGlobal.EARLY_FIFTY_FIFTY_CHECKING && !options.hardcore && minesLeft > 1) {
//const unavoidable5050a = pe.checkForUnavoidable5050();
let unavoidable5050a;
if (options.playStyle == PLAY_STYLE_EFFICIENCY || options.playStyle == PLAY_STYLE_NOFLAGS_EFFICIENCY) {
unavoidable5050a = pe.checkForUnavoidable5050();
} else {
unavoidable5050a = pe.checkForUnavoidable5050OrPseudo();
}

if (unavoidable5050a != null) {

const actions = [];
for (const tile of unavoidable5050a) {
// Check if the pseudo 50/50 isn't resolved by the local clears
if (tile.probability != 0 && tile.probability != 1) {
actions.push(new Action(tile.getX(), tile.getY(), tile.probability, ACTION_CLEAR));
}
}

if (actions.length != 0) {
const returnActions = tieBreak(pe, actions, null, null, false);

const recommended = returnActions[0];
result.unshift(...returnActions);
if (recommended.prob == 0.5) {
showMessage(recommended.asText() + " is an unavoidable 50/50 guess." + formatSolutions(pe.finalSolutionsCount));
} else {
showMessage(recommended.asText() + " is an unavoidable 50/50 guess, or safe." + formatSolutions(pe.finalSolutionsCount));
}

// combine the dead tiles from the probability engine and the unavoidable 5050s
for (let deadTile of pe.deadTiles) {
let found = false;
for (let returnAction of returnActions) {
if (deadTile.isEqual(returnAction)) {
found = true;
break;
}
}
if (!found) {
deadTiles.push(deadTile);
}
}

return addDeadTiles(result, deadTiles, pe.minesFound);
}
}
}
result = addDeadTiles(result, pe.getDeadTiles(), pe.minesFound);
}

return result;
}


Expand Down Expand Up @@ -693,7 +749,7 @@ async function solver(board, options) {

// identify the dead tiles
for (let tile of deadTiles) { // show all dead tiles
if (tile.probability != 0) {
if (tile.probability != 0 && tile.probability != 1) {
const action = new Action(tile.getX(), tile.getY(), tile.probability);
action.dead = true;
result.push(action);
Expand Down
Loading

0 comments on commit 97a94a7

Please sign in to comment.