From 336337eedb216c741f2c3226529ea81a374b4b2c Mon Sep 17 00:00:00 2001 From: "Felix W. Dekker" Date: Fri, 7 Aug 2020 13:13:14 +0200 Subject: [PATCH] Speed up solver, find edge cases For real this time! --- package.json | 2 +- src/main/js/Solver.ts | 67 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index f8383a8..1181e0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minesweeper", - "version": "0.78.3", + "version": "0.78.4", "description": "Just Minesweeper!", "author": "Felix W. Dekker", "browser": "dist/bundle.js", diff --git a/src/main/js/Solver.ts b/src/main/js/Solver.ts index e23a24d..733fded 100644 --- a/src/main/js/Solver.ts +++ b/src/main/js/Solver.ts @@ -14,6 +14,8 @@ export class Solver { * @param field the field to solve */ solve(field: Field): void { + if (field.hasWon || field.hasLost) return; + if (!field.hasStarted) { field.runUndoably(() => { field.squareList.filter(it => it.hasFlag).forEach(it => field.flag(it.coords)); @@ -30,7 +32,7 @@ export class Solver { let newFlagCount; let newCoveredCount; - this.chordAndFlagTrivial(field); + this.stepSingleSquares(field); if (field.hasWon) break; newFlagCount = field.flagCount; newCoveredCount = field.coveredNonMineCount; @@ -40,7 +42,17 @@ export class Solver { continue; } - this.step(field); + this.stepNeighboringSquares(field); + if (field.hasWon) break; + newFlagCount = field.flagCount; + newCoveredCount = field.coveredNonMineCount; + if (newFlagCount !== flagCount || newCoveredCount !== coveredCount) { + flagCount = newFlagCount; + coveredCount = newCoveredCount; + continue; + } + + this.stepAllSquares(field); if (field.hasWon) break; newFlagCount = field.flagCount; newCoveredCount = field.coveredNonMineCount; @@ -76,12 +88,15 @@ export class Solver { /** - * Automatically chords and flags all trivial squares. + * Solves the field as much as by considering just one square at a time and looking for trivial solutions. * - * @param field the field to chord and flag in + * This function is very fast but only finds trivial moves such as a square that can be chorded or a square of which + * all neighbors can be flagged. + * + * @param field the field to solve * @private */ - private chordAndFlagTrivial(field: Field): void { + private stepSingleSquares(field: Field): void { field.squareList .filter(it => !it.isCovered) .forEach(square => { @@ -92,18 +107,50 @@ export class Solver { } /** - * Solves the given field given only the information currently available, without considering the information that - * is gained from the actions performed by this function. + * Solves the field as much as possible by considering only one uncovered square and its uncovered neighbors at a + * time. + * + * This function is slower than `#stepSingleSquares` but finds some more advanced moves by effectively considering + * two squares at time. Meanwhile, this function does not look at the bigger picture so it cannot infer some more + * complicated moves. On the other hand, for some reason this function finds some edge cases that `#stepAllSquares` + * overlooks. * * @param field the field to solve + * @private */ - private step(field: Field): void { + private stepNeighboringSquares(field: Field): void { + const knowns = field.squareList + .filter(it => !it.isCovered) + .filter(it => it.getNeighborCount(it => it.isCovered && !it.hasFlag) > 0); + knowns.forEach(known => { + const system = this.matrixSolve(field, known.neighbors.filter(it => !it.isCovered).concat(known), true); + + system.forEach(target => { + if (target === undefined) return; + + const [solution, square] = target; + if (solution === 0) field.uncover(square.coords); + else if (solution === 1) field.flag(square.coords); + }); + }); + } + + /** + * Solves the field as much as possible by looking at all uncovered squares and the remaining number of mines. + * + * Because this function considers all squares in the field, it is very slow. Then again, it finds a lot of steps + * as well. + * + * @param field the field to solve + * @private + */ + private stepAllSquares(field: Field): void { if (!field.hasStarted || field.hasWon || field.hasLost) return; const knowns = field.squareList .filter(it => !it.isCovered) .filter(it => it.getNeighborCount(it => it.isCovered && !it.hasFlag) > 0); - const system = this.solveFrom(field, knowns, false); + const system = this.matrixSolve(field, knowns, false); system.forEach(target => { if (target === undefined) return; @@ -123,7 +170,7 @@ export class Solver { * all squares in the field. Enabling this option increases complexity, but may uncover some edge cases * @private */ - private solveFrom(field: Field, knowns: Square[], adjacentSquaresOnly: boolean): ([number, Square] | undefined)[] { + private matrixSolve(field: Field, knowns: Square[], adjacentSquaresOnly: boolean): ([number, Square] | undefined)[] { if (knowns.length === 0) return []; let unknowns: Square[];