Animate field when showing solution

Fixes #102.
This commit is contained in:
Florine W. Dekker 2024-05-01 22:42:28 +02:00
parent 654148d09c
commit 302d2e8847
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
5 changed files with 62 additions and 16 deletions

BIN
package-lock.json generated

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "minesweeper",
"version": "0.86.2",
"version": "0.86.3",
"description": "Just Minesweeper!",
"author": "Florine W. Dekker",
"browser": "dist/bundle.js",

View File

@ -445,6 +445,16 @@ export class Field {
this.invokeEventListeners();
}
/**
* Runs `#runUndoably` asynchronously.
*/
async runUndoablyAsync(callback: () => Promise<void>): Promise<void> {
this.history.startSequence();
await callback();
if (this.history.commitSequence())
this.invokeEventListeners();
}
/**
* Runs and stores the given action such that it can be undone.
*

View File

@ -198,12 +198,12 @@ export class Game {
this.solve = $("#solve");
this.solve.addEventListener(
"click",
(event: MouseEvent) => {
async (event: MouseEvent) => {
event.preventDefault();
if (this.field != null) {
this.statistics.solverUsages++;
Solver.solve(this.field);
await Solver.solveAnimated(this.field);
}
this.display.hintSquare = null;

View File

@ -17,8 +17,43 @@ export class Solver {
if (field.isOver) return;
if (field.hasStarted && !this.step(field.copy())) return;
field.isAutoSolving = true;
this.solveStart(field);
field.runUndoably(() => {
this.clearUserInputs(field);
while (this.step(field)) {
// Intentionally left empty
}
});
field.isAutoSolving = false;
}
/**
* Runs `#solve`, animating steps in between.
*/
static async solveAnimated(field: Field): Promise<void> {
if (field.isOver) return;
if (field.hasStarted && !this.step(field.copy())) return;
field.isAutoSolving = true;
this.solveStart(field);
await field.runUndoablyAsync(async () => {
this.clearUserInputs(field);
while (this.step(field)) {
// Timeout in between steps to create animation
await new Promise(it => setTimeout(it, 10));
}
});
field.isAutoSolving = false;
}
/**
* If the field has not started yet, clicks somewhere and starts solving.
*/
private static solveStart(field: Field): void {
if (!field.hasStarted) {
field.isAutoSolving = true;
field.runUndoably(() => {
const target = {x: Math.floor(field.width / 2), y: Math.floor(field.height / 2)};
const targetSquare = field.getSquareOrElse(target)!;
@ -26,21 +61,21 @@ export class Solver {
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));
while (this.step(field)) {
// Repeat until `step` returns false
}
});
field.isAutoSolving = false;
}
/**
* Removes all the user's inputs from the given field.
*
* @param field the field to remove the user's inputs from
* @private
*/
private static clearUserInputs(field: Field): void {
field.squareList.filter(it => it.hasFlag).forEach(it => field.toggleFlag(it.coords));
field.squareList.filter(it => it.hasMark).forEach(it => field.toggleMark(it.coords));
}
/**
* Returns `true` if and only if this solver can solve the given field.
*
@ -127,6 +162,7 @@ export class Solver {
return false;
}
/**
* Returns a suggestion for a next move based on the current state of the field.
*