diff --git a/package.json b/package.json index 7302d7d..f900abd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minesweeper", - "version": "0.81.8", + "version": "0.81.9", "description": "Just Minesweeper!", "author": "Felix W. Dekker", "browser": "dist/bundle.js", diff --git a/src/main/js/Field.ts b/src/main/js/Field.ts index 544b159..0b5aaa0 100644 --- a/src/main/js/Field.ts +++ b/src/main/js/Field.ts @@ -54,11 +54,7 @@ export class Field { return this._deathCount; } - private _wasAutoSolved: 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; - } + isAutoSolving: boolean = false; /** @@ -214,14 +210,14 @@ export class Field { if (square.getNeighborCount(it => it.hasMark) > 0) return; if (square.getNeighborCount(it => it.hasFlag) !== square.getNeighborCount(it => it.hasMine)) return; - this.statistics.squaresChorded++; + if (!this.isAutoSolving) this.statistics.squaresChorded++; this.runUndoably(() => { square.neighbors .filter(it => it.isCovered && !it.hasFlag) .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( () => { next.isCovered = false; - this.statistics.squaresUncovered++; + if (!this.isAutoSolving) this.statistics.squaresUncovered++; if (next.hasMine) { this.timer.stop(); this._hasLost = true; this._deathCount++; - this.statistics.minesUncovered++; + if (!this.isAutoSolving) this.statistics.minesUncovered++; } else { this._coveredNonMineCount--; if (this.coveredNonMineCount === 0) { @@ -283,10 +279,12 @@ export class Field { this._flagCount = this.mineCount; this._hasWon = true; - if (!this._hasWonBefore && !this._wasAutoSolved) { + if (!this._hasWonBefore) { this._hasWonBefore = true; - this.statistics.gamesWon++; - if (this.deathCount === 0) this.statistics.gamesWonWithoutLosing++; + if (!this.isAutoSolving) { + this.statistics.gamesWon++; + if (this.deathCount === 0) this.statistics.gamesWonWithoutLosing++; + } } } } @@ -330,7 +328,7 @@ export class Field { this.addAction(new Action( () => { square.hasFlag = !square.hasFlag; - if (square.hasFlag) this.statistics.squaresFlagged++; + if (!this.isAutoSolving && square.hasFlag) this.statistics.squaresFlagged++; this._flagCount += (square.hasFlag ? 1 : -1); }, () => { @@ -355,11 +353,11 @@ export class Field { this.addAction(new Action( () => { square.hasMark = !square.hasMark; - if (square.hasMark) this.statistics.squaresMarked++; + if (!this.isAutoSolving && square.hasMark) this.statistics.squaresMarked++; }, () => { square.hasMark = !square.hasMark; - if (square.hasMark) this.statistics.squaresMarked++; + if (!this.isAutoSolving && square.hasMark) this.statistics.squaresMarked++; return true; }, () => true @@ -408,7 +406,7 @@ export class Field { */ redo(amount: number | undefined = undefined): void { 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 undone = this.history.undo(amount); - if (undone > 0) this.statistics.actionsUndone++; - if (wasLost && !this.hasLost) this.statistics.lossesUndone++; + if (!this.isAutoSolving && undone > 0) this.statistics.actionsUndone++; + if (!this.isAutoSolving && wasLost && !this.hasLost) this.statistics.lossesUndone++; } diff --git a/src/main/js/Solver.ts b/src/main/js/Solver.ts index 37a19d8..6d511da 100644 --- a/src/main/js/Solver.ts +++ b/src/main/js/Solver.ts @@ -15,60 +15,30 @@ export class Solver { */ solve(field: Field): void { if (field.hasWon || field.hasLost) return; - field.wasAutoSolved = true; + if (field.hasStarted && !this.step(field.copy())) return; if (!field.hasStarted) { + field.isAutoSolving = true; field.runUndoably(() => { - 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.uncover({x: Math.floor(field.width / 2), y: Math.floor(field.height / 2)}); + const target = {x: Math.floor(field.width / 2), y: Math.floor(field.height / 2)}; + const targetSquare = field.getSquareOrElse(target, undefined)!; + if (targetSquare.hasFlag) field.toggleFlag(target); + if (targetSquare.hasMark) field.toggleMark(target); + field.uncover(target); }); + field.isAutoSolving = false; } + field.isAutoSolving = true; field.runUndoably(() => { field.squareList.filter(it => it.hasFlag).forEach(it => field.toggleFlag(it.coords)); field.squareList.filter(it => it.hasMark).forEach(it => field.toggleMark(it.coords)); - let flagCount = -1; - let coveredCount = -1; - 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; + while (this.step(field)) { + // Repeat until `step` returns false } }); + 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. *