Speed up solver, find edge cases

For real this time!
This commit is contained in:
Florine W. Dekker 2020-08-07 13:13:14 +02:00
parent fb1825b262
commit 336337eedb
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
2 changed files with 58 additions and 11 deletions

View File

@ -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",

View File

@ -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[];