parent
6d93d3394f
commit
07f028d369
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "minesweeper",
|
"name": "minesweeper",
|
||||||
"version": "0.81.8",
|
"version": "0.81.9",
|
||||||
"description": "Just Minesweeper!",
|
"description": "Just Minesweeper!",
|
||||||
"author": "Felix W. Dekker",
|
"author": "Felix W. Dekker",
|
||||||
"browser": "dist/bundle.js",
|
"browser": "dist/bundle.js",
|
||||||
|
|
|
@ -54,11 +54,7 @@ export class Field {
|
||||||
return this._deathCount;
|
return this._deathCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _wasAutoSolved: boolean = false;
|
isAutoSolving: boolean = false;
|
||||||
set wasAutoSolved(value: boolean) {
|
|
||||||
if (this._wasAutoSolved && !value) throw new Error("Cannot set wasAutoSolved to false while it is true.");
|
|
||||||
this._wasAutoSolved = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -214,14 +210,14 @@ export class Field {
|
||||||
if (square.getNeighborCount(it => it.hasMark) > 0) return;
|
if (square.getNeighborCount(it => it.hasMark) > 0) return;
|
||||||
if (square.getNeighborCount(it => it.hasFlag) !== square.getNeighborCount(it => it.hasMine)) return;
|
if (square.getNeighborCount(it => it.hasFlag) !== square.getNeighborCount(it => it.hasMine)) return;
|
||||||
|
|
||||||
this.statistics.squaresChorded++;
|
if (!this.isAutoSolving) this.statistics.squaresChorded++;
|
||||||
this.runUndoably(() => {
|
this.runUndoably(() => {
|
||||||
square.neighbors
|
square.neighbors
|
||||||
.filter(it => it.isCovered && !it.hasFlag)
|
.filter(it => it.isCovered && !it.hasFlag)
|
||||||
.forEach(it => this.uncover(it.coords));
|
.forEach(it => this.uncover(it.coords));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.hasLost) this.statistics.squaresChordedLeadingToLoss++;
|
if (!this.isAutoSolving && this.hasLost) this.statistics.squaresChordedLeadingToLoss++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -267,13 +263,13 @@ export class Field {
|
||||||
this.addAction(new Action(
|
this.addAction(new Action(
|
||||||
() => {
|
() => {
|
||||||
next.isCovered = false;
|
next.isCovered = false;
|
||||||
this.statistics.squaresUncovered++;
|
if (!this.isAutoSolving) this.statistics.squaresUncovered++;
|
||||||
|
|
||||||
if (next.hasMine) {
|
if (next.hasMine) {
|
||||||
this.timer.stop();
|
this.timer.stop();
|
||||||
this._hasLost = true;
|
this._hasLost = true;
|
||||||
this._deathCount++;
|
this._deathCount++;
|
||||||
this.statistics.minesUncovered++;
|
if (!this.isAutoSolving) this.statistics.minesUncovered++;
|
||||||
} else {
|
} else {
|
||||||
this._coveredNonMineCount--;
|
this._coveredNonMineCount--;
|
||||||
if (this.coveredNonMineCount === 0) {
|
if (this.coveredNonMineCount === 0) {
|
||||||
|
@ -283,13 +279,15 @@ export class Field {
|
||||||
this._flagCount = this.mineCount;
|
this._flagCount = this.mineCount;
|
||||||
this._hasWon = true;
|
this._hasWon = true;
|
||||||
|
|
||||||
if (!this._hasWonBefore && !this._wasAutoSolved) {
|
if (!this._hasWonBefore) {
|
||||||
this._hasWonBefore = true;
|
this._hasWonBefore = true;
|
||||||
|
if (!this.isAutoSolving) {
|
||||||
this.statistics.gamesWon++;
|
this.statistics.gamesWon++;
|
||||||
if (this.deathCount === 0) this.statistics.gamesWonWithoutLosing++;
|
if (this.deathCount === 0) this.statistics.gamesWonWithoutLosing++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
next.isCovered = true;
|
next.isCovered = true;
|
||||||
|
@ -330,7 +328,7 @@ export class Field {
|
||||||
this.addAction(new Action(
|
this.addAction(new Action(
|
||||||
() => {
|
() => {
|
||||||
square.hasFlag = !square.hasFlag;
|
square.hasFlag = !square.hasFlag;
|
||||||
if (square.hasFlag) this.statistics.squaresFlagged++;
|
if (!this.isAutoSolving && square.hasFlag) this.statistics.squaresFlagged++;
|
||||||
this._flagCount += (square.hasFlag ? 1 : -1);
|
this._flagCount += (square.hasFlag ? 1 : -1);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
@ -355,11 +353,11 @@ export class Field {
|
||||||
this.addAction(new Action(
|
this.addAction(new Action(
|
||||||
() => {
|
() => {
|
||||||
square.hasMark = !square.hasMark;
|
square.hasMark = !square.hasMark;
|
||||||
if (square.hasMark) this.statistics.squaresMarked++;
|
if (!this.isAutoSolving && square.hasMark) this.statistics.squaresMarked++;
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
square.hasMark = !square.hasMark;
|
square.hasMark = !square.hasMark;
|
||||||
if (square.hasMark) this.statistics.squaresMarked++;
|
if (!this.isAutoSolving && square.hasMark) this.statistics.squaresMarked++;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
() => true
|
() => true
|
||||||
|
@ -408,7 +406,7 @@ export class Field {
|
||||||
*/
|
*/
|
||||||
redo(amount: number | undefined = undefined): void {
|
redo(amount: number | undefined = undefined): void {
|
||||||
const redone = this.history.redo(amount);
|
const redone = this.history.redo(amount);
|
||||||
if (redone > 0) this.statistics.actionsRedone++;
|
if (!this.isAutoSolving && redone > 0) this.statistics.actionsRedone++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -420,8 +418,8 @@ export class Field {
|
||||||
const wasLost = this.hasLost;
|
const wasLost = this.hasLost;
|
||||||
|
|
||||||
const undone = this.history.undo(amount);
|
const undone = this.history.undo(amount);
|
||||||
if (undone > 0) this.statistics.actionsUndone++;
|
if (!this.isAutoSolving && undone > 0) this.statistics.actionsUndone++;
|
||||||
if (wasLost && !this.hasLost) this.statistics.lossesUndone++;
|
if (!this.isAutoSolving && wasLost && !this.hasLost) this.statistics.lossesUndone++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,60 +15,30 @@ export class Solver {
|
||||||
*/
|
*/
|
||||||
solve(field: Field): void {
|
solve(field: Field): void {
|
||||||
if (field.hasWon || field.hasLost) return;
|
if (field.hasWon || field.hasLost) return;
|
||||||
field.wasAutoSolved = true;
|
if (field.hasStarted && !this.step(field.copy())) return;
|
||||||
|
|
||||||
if (!field.hasStarted) {
|
if (!field.hasStarted) {
|
||||||
|
field.isAutoSolving = true;
|
||||||
field.runUndoably(() => {
|
field.runUndoably(() => {
|
||||||
field.squareList.filter(it => it.hasFlag).forEach(it => field.toggleFlag(it.coords));
|
const target = {x: Math.floor(field.width / 2), y: Math.floor(field.height / 2)};
|
||||||
field.squareList.filter(it => it.hasMark).forEach(it => field.toggleMark(it.coords));
|
const targetSquare = field.getSquareOrElse(target, undefined)!;
|
||||||
field.uncover({x: Math.floor(field.width / 2), y: Math.floor(field.height / 2)});
|
if (targetSquare.hasFlag) field.toggleFlag(target);
|
||||||
|
if (targetSquare.hasMark) field.toggleMark(target);
|
||||||
|
field.uncover(target);
|
||||||
});
|
});
|
||||||
|
field.isAutoSolving = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
field.isAutoSolving = true;
|
||||||
field.runUndoably(() => {
|
field.runUndoably(() => {
|
||||||
field.squareList.filter(it => it.hasFlag).forEach(it => field.toggleFlag(it.coords));
|
field.squareList.filter(it => it.hasFlag).forEach(it => field.toggleFlag(it.coords));
|
||||||
field.squareList.filter(it => it.hasMark).forEach(it => field.toggleMark(it.coords));
|
field.squareList.filter(it => it.hasMark).forEach(it => field.toggleMark(it.coords));
|
||||||
|
|
||||||
let flagCount = -1;
|
while (this.step(field)) {
|
||||||
let coveredCount = -1;
|
// Repeat until `step` returns false
|
||||||
while (true) {
|
|
||||||
let newFlagCount;
|
|
||||||
let newCoveredCount;
|
|
||||||
|
|
||||||
this.stepSingleSquares(field);
|
|
||||||
if (field.hasWon) break;
|
|
||||||
newFlagCount = field.flagCount;
|
|
||||||
newCoveredCount = field.coveredNonMineCount;
|
|
||||||
if (newFlagCount !== flagCount || newCoveredCount !== coveredCount) {
|
|
||||||
flagCount = newFlagCount;
|
|
||||||
coveredCount = newCoveredCount;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
if (newFlagCount !== flagCount || newCoveredCount !== coveredCount) {
|
|
||||||
flagCount = newFlagCount;
|
|
||||||
coveredCount = newCoveredCount;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No solver method changed anything, so stop solving
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
field.isAutoSolving = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -125,6 +95,35 @@ export class Solver {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solves in one step through the field.
|
||||||
|
*
|
||||||
|
* @param field the field to solve one step in
|
||||||
|
* @returns `true` if a step could be solved
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private step(field: Field): boolean {
|
||||||
|
let flagCount = field.flagCount;
|
||||||
|
let coveredCount = field.coveredNonMineCount;
|
||||||
|
|
||||||
|
if (field.hasWon || field.hasLost)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.stepSingleSquares(field);
|
||||||
|
if (field.hasWon || field.flagCount !== flagCount || field.coveredNonMineCount !== coveredCount)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
this.stepNeighboringSquares(field);
|
||||||
|
if (field.hasWon || field.flagCount !== flagCount || field.coveredNonMineCount !== coveredCount)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
this.stepAllSquares(field);
|
||||||
|
if (field.hasWon || field.flagCount !== flagCount || field.coveredNonMineCount !== coveredCount)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Solves the field as much as by considering just one square at a time and looking for trivial solutions.
|
* Solves the field as much as by considering just one square at a time and looking for trivial solutions.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue