Compare commits
1 Commits
main
...
solve-with
Author | SHA1 | Date |
---|---|---|
Florine W. Dekker | 30e3467598 |
Binary file not shown.
|
@ -160,7 +160,7 @@ export class Field {
|
|||
* @param square the square from to move mines away from
|
||||
* @private
|
||||
*/
|
||||
private clearMines(square: Square): void {
|
||||
private async moveMinesAwayFrom(square: Square): Promise<void> {
|
||||
const swapAction = (source: Square, target: Square) => new Action(
|
||||
() => {
|
||||
source.hasMine = false;
|
||||
|
@ -174,21 +174,20 @@ export class Field {
|
|||
() => true
|
||||
);
|
||||
|
||||
this.runUndoably(() => {
|
||||
await this.runUndoably(async () => {
|
||||
if (square.hasMine) {
|
||||
const target = this.squareList.find(it => !it.hasMine && it !== square)!;
|
||||
this.addAction(swapAction(square, target));
|
||||
await this.addAction(swapAction(square, target));
|
||||
}
|
||||
|
||||
square.neighbors
|
||||
.filter(it => it.hasMine)
|
||||
.forEach(it => {
|
||||
const target = this.squareList
|
||||
.find(it => !it.hasMine && it !== square && square.neighbors.indexOf(it) < 0);
|
||||
const eligibleNeighbors = square.neighbors.filter(it => it.hasMine);
|
||||
for (const neighbors of eligibleNeighbors) {
|
||||
const target = this.squareList
|
||||
.find(it => !it.hasMine && it !== square && square.neighbors.indexOf(it) < 0);
|
||||
|
||||
if (target !== undefined)
|
||||
this.addAction(swapAction(it, target));
|
||||
});
|
||||
if (target !== undefined)
|
||||
await this.addAction(swapAction(neighbors, target));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -214,7 +213,7 @@ export class Field {
|
|||
*
|
||||
* @param coords the coordinates of the square to chord
|
||||
*/
|
||||
chord(coords: { x: number, y: number }): void {
|
||||
async chord(coords: { x: number, y: number }): Promise<void> {
|
||||
const square = this.squares[coords.y][coords.x];
|
||||
|
||||
if (square === undefined) throw new Error(`Cannot chord undefined square at (${coords}).`);
|
||||
|
@ -223,10 +222,11 @@ export class Field {
|
|||
if (square.getNeighborCount(it => it.hasFlag) !== square.getNeighborCount(it => it.hasMine)) return;
|
||||
|
||||
if (!this.isAutoSolving) this.statistics.squaresChorded++;
|
||||
this.runUndoably(() => {
|
||||
square.neighbors
|
||||
.filter(it => it.isCovered && !it.hasFlag)
|
||||
.forEach(it => this.uncover(it.coords));
|
||||
await this.runUndoably(async () => {
|
||||
const eligibleNeighbors = square.neighbors.filter(it => it.isCovered && !it.hasFlag);
|
||||
for (const neighbor of eligibleNeighbors) {
|
||||
await this.uncover(neighbor.coords);
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.isAutoSolving && this.hasLost) this.statistics.squaresChordedLeadingToLoss++;
|
||||
|
@ -237,31 +237,50 @@ export class Field {
|
|||
*
|
||||
* @param coords the coordinates of the square to uncover
|
||||
*/
|
||||
uncover(coords: { x: number, y: number }): void {
|
||||
async uncover(coords: { x: number, y: number }): Promise<void> {
|
||||
const square = this.squares[coords.y][coords.x];
|
||||
if (square === undefined) throw new Error(`Cannot uncover undefined square at (${coords}).`);
|
||||
if (this.hasWon || this.hasLost) return;
|
||||
|
||||
this.runUndoably(() => {
|
||||
await this.runUndoably(async () => {
|
||||
if (!this.hasStarted) {
|
||||
this.statistics.gamesStarted++;
|
||||
|
||||
if (this.isSolvable) {
|
||||
let i = 1;
|
||||
const time = Timer.time(() => {
|
||||
while (!Solver.canSolve(this, coords)) {
|
||||
this.shuffle(this.rng.uint32());
|
||||
i++;
|
||||
let i = 0;
|
||||
const time = await Timer.time(async () => {
|
||||
// while (!(await Solver.canSolve(this, coords))) {
|
||||
// this.shuffle(this.rng.uint32());
|
||||
// i++;
|
||||
// }
|
||||
|
||||
|
||||
let seed = undefined;
|
||||
|
||||
while (seed === undefined) {
|
||||
const seeds = Array.from({length: 10}, () => this.rng.uint32());
|
||||
i += seeds.length;
|
||||
|
||||
const tasks = seeds.map(async seed => {
|
||||
const copy = this.copy();
|
||||
copy.shuffle(seed);
|
||||
return await Solver.canSolve(copy, coords);
|
||||
});
|
||||
const solvable = await Promise.all(tasks);
|
||||
|
||||
seed = seeds[solvable.indexOf(true)];
|
||||
}
|
||||
|
||||
this.shuffle(seed);
|
||||
});
|
||||
console.log(`Found solvable field in ${time}ms in ${i} attempts.`);
|
||||
}
|
||||
this.clearMines(square);
|
||||
await this.moveMinesAwayFrom(square);
|
||||
|
||||
this._hasStarted = true;
|
||||
this.timer.start();
|
||||
// @formatter:off
|
||||
this.addAction(new Action(() => {}, () => false, () => false));
|
||||
await this.addAction(new Action(() => {}, () => false, () => false));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
@ -271,7 +290,7 @@ export class Field {
|
|||
if (!next.isCovered || next.hasFlag || next.hasMark) continue;
|
||||
|
||||
let remainingFlags: Square[] | undefined;
|
||||
this.addAction(new Action(
|
||||
await this.addAction(new Action(
|
||||
() => {
|
||||
next.isCovered = false;
|
||||
if (!this.isAutoSolving) this.statistics.squaresUncovered++;
|
||||
|
@ -335,12 +354,12 @@ export class Field {
|
|||
*
|
||||
* @param coords the coordinates of the square to toggle the flag at
|
||||
*/
|
||||
toggleFlag(coords: { x: number, y: number }): void {
|
||||
async toggleFlag(coords: { x: number, y: number }): Promise<void> {
|
||||
const square = this.squares[coords.y][coords.x];
|
||||
if (square === undefined) throw new Error(`Cannot toggle flag of undefined square at (${coords}).`);
|
||||
if (!square.isCovered || square.hasMark || this.hasWon || this.hasLost) return;
|
||||
|
||||
this.addAction(new Action(
|
||||
await this.addAction(new Action(
|
||||
() => {
|
||||
square.hasFlag = !square.hasFlag;
|
||||
if (!this.isAutoSolving && square.hasFlag) this.statistics.squaresFlagged++;
|
||||
|
@ -360,12 +379,12 @@ export class Field {
|
|||
*
|
||||
* @param coords the coordinates of the square to toggle the question mark at
|
||||
*/
|
||||
toggleMark(coords: { x: number, y: number }): void {
|
||||
async toggleMark(coords: { x: number, y: number }): Promise<void> {
|
||||
const square = this.squares[coords.y][coords.x];
|
||||
if (square === undefined) throw new Error(`Cannot toggle flag of undefined square at (${coords}).`);
|
||||
if (!square.isCovered || square.hasFlag || this.hasWon || this.hasLost) return;
|
||||
|
||||
this.addAction(new Action(
|
||||
await this.addAction(new Action(
|
||||
() => {
|
||||
square.hasMark = !square.hasMark;
|
||||
if (!this.isAutoSolving && square.hasMark) this.statistics.squaresMarked++;
|
||||
|
@ -389,9 +408,9 @@ export class Field {
|
|||
* @param callback a function such that all its calls to `#addAction` should be undoable with a single invocation of
|
||||
* `#undo`
|
||||
*/
|
||||
runUndoably(callback: () => void): void {
|
||||
async runUndoably(callback: () => Promise<void>): Promise<void> {
|
||||
this.history.startSequence();
|
||||
callback();
|
||||
await callback();
|
||||
this.history.commitSequence();
|
||||
}
|
||||
|
||||
|
@ -403,11 +422,11 @@ export class Field {
|
|||
* @param action the action that can be undone
|
||||
* @private
|
||||
*/
|
||||
private addAction(action: Action): void {
|
||||
private async addAction(action: Action): Promise<void> {
|
||||
if (this.history.hasUncommittedSequence)
|
||||
this.history.addAction(action);
|
||||
else
|
||||
this.runUndoably(() => this.history.addAction(action));
|
||||
await this.runUndoably(async () => this.history.addAction(action));
|
||||
|
||||
action.run();
|
||||
}
|
||||
|
|
|
@ -218,12 +218,12 @@ export class Game {
|
|||
this.hintForm = $("#hintForm");
|
||||
this.hintForm.addEventListener(
|
||||
"submit",
|
||||
event => {
|
||||
async event => {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.field !== null) {
|
||||
this.statistics.hintsRequested++;
|
||||
this.display.hintSquare = Solver.getHint(this.field);
|
||||
this.display.hintSquare = await Solver.getHint(this.field);
|
||||
}
|
||||
blurActiveElement();
|
||||
}
|
||||
|
@ -233,12 +233,12 @@ export class Game {
|
|||
this.solveForm = $("#solveForm");
|
||||
this.solveForm.addEventListener(
|
||||
"submit",
|
||||
event => {
|
||||
async event => {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.field !== null) {
|
||||
this.statistics.solverUsages++;
|
||||
Solver.solve(this.field);
|
||||
await Solver.solve(this.field);
|
||||
}
|
||||
blurActiveElement();
|
||||
}
|
||||
|
@ -355,11 +355,11 @@ export class Game {
|
|||
this.canvas.addEventListener("contextmenu", event => event.preventDefault());
|
||||
this.canvas.addEventListener(
|
||||
"mousedown",
|
||||
event => {
|
||||
async event => {
|
||||
event.preventDefault();
|
||||
if (this.field === null) return;
|
||||
|
||||
this.field.runUndoably(() => {
|
||||
await this.field.runUndoably(async () => {
|
||||
const coords = this.display.posToSquare({x: event.clientX, y: event.clientY});
|
||||
if (this.field === null || !this.field.hasSquareAt(coords)) return;
|
||||
|
||||
|
@ -372,13 +372,13 @@ export class Game {
|
|||
const square = this.field.getSquareOrElse(coords);
|
||||
if (square !== null) {
|
||||
if (square.hasFlag) {
|
||||
this.field.toggleFlag(coords);
|
||||
await this.field.toggleFlag(coords);
|
||||
if (preferences.marksEnabled)
|
||||
this.field.toggleMark(coords);
|
||||
await this.field.toggleMark(coords);
|
||||
} else if (square.hasMark) {
|
||||
this.field.toggleMark(coords);
|
||||
await this.field.toggleMark(coords);
|
||||
} else {
|
||||
this.field.toggleFlag(coords);
|
||||
await this.field.toggleFlag(coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -395,30 +395,30 @@ export class Game {
|
|||
);
|
||||
this.canvas.addEventListener(
|
||||
"mouseup",
|
||||
event => {
|
||||
async event => {
|
||||
event.preventDefault();
|
||||
if (this.field === null) return;
|
||||
|
||||
this.field.runUndoably(() => {
|
||||
await this.field.runUndoably(async () => {
|
||||
const coords = this.display.posToSquare({x: event.clientX, y: event.clientY});
|
||||
if (this.field === null || !this.field.hasSquareAt(coords)) return;
|
||||
|
||||
switch (event.button) {
|
||||
case 0:
|
||||
if (this.leftDown && this.rightDown)
|
||||
this.field.chord(coords);
|
||||
await this.field.chord(coords);
|
||||
else if (!this.holdsAfterChord && this.leftDown)
|
||||
this.field.uncover(coords);
|
||||
await this.field.uncover(coords);
|
||||
|
||||
this.leftDown = false;
|
||||
this.holdsAfterChord = this.rightDown;
|
||||
break;
|
||||
case 1:
|
||||
this.field.chord(coords);
|
||||
await this.field.chord(coords);
|
||||
break;
|
||||
case 2:
|
||||
if (this.leftDown && this.rightDown)
|
||||
this.field.chord(coords);
|
||||
await this.field.chord(coords);
|
||||
|
||||
this.rightDown = false;
|
||||
this.holdsAfterChord = this.leftDown;
|
||||
|
|
|
@ -13,28 +13,30 @@ export class Solver {
|
|||
*
|
||||
* @param field the field to solve
|
||||
*/
|
||||
static solve(field: Field): void {
|
||||
static async solve(field: Field): Promise<void> {
|
||||
if (field.hasWon || field.hasLost) return;
|
||||
if (field.hasStarted && !this.step(field.copy())) return;
|
||||
if (field.hasStarted && !(await this.step(field.copy()))) return;
|
||||
|
||||
if (!field.hasStarted) {
|
||||
field.isAutoSolving = true;
|
||||
field.runUndoably(() => {
|
||||
await field.runUndoably(async () => {
|
||||
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);
|
||||
if (targetSquare.hasFlag) await field.toggleFlag(target);
|
||||
if (targetSquare.hasMark) await field.toggleMark(target);
|
||||
await 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));
|
||||
await field.runUndoably(async () => {
|
||||
for (const squares of field.squareList.filter(it => it.hasFlag))
|
||||
await field.toggleFlag(squares.coords);
|
||||
for (const squares of field.squareList.filter(it => it.hasMark))
|
||||
await field.toggleMark(squares.coords);
|
||||
|
||||
while (this.step(field)) {
|
||||
while (await this.step(field)) {
|
||||
// Repeat until `step` returns false
|
||||
}
|
||||
});
|
||||
|
@ -52,10 +54,10 @@ export class Solver {
|
|||
* @param field the field to check for solvability
|
||||
* @param initialSquare the initial coordinates to click at
|
||||
*/
|
||||
static canSolve(field: Field, initialSquare: { x: number, y: number } | undefined = undefined): boolean {
|
||||
static async canSolve(field: Field, initialSquare: { x: number, y: number } | undefined = undefined): Promise<boolean> {
|
||||
const copy = field.copy();
|
||||
if (initialSquare !== undefined) copy.runUndoably(() => copy.uncover(initialSquare));
|
||||
this.solve(copy);
|
||||
if (initialSquare !== undefined) await copy.runUndoably(async () => await copy.uncover(initialSquare));
|
||||
await this.solve(copy);
|
||||
return copy.hasWon;
|
||||
}
|
||||
|
||||
|
@ -65,7 +67,7 @@ export class Solver {
|
|||
* @param field the field to suggest a move for
|
||||
* @returns a suggestion for a next move based on the current state of the field
|
||||
*/
|
||||
static getHint(field: Field): Square | null {
|
||||
static async getHint(field: Field): Promise<Square | null> {
|
||||
if (!field.hasStarted || field.hasWon || field.hasLost) return null;
|
||||
const knowns = Solver.getKnowns(field);
|
||||
|
||||
|
@ -102,22 +104,22 @@ export class Solver {
|
|||
* @returns `true` if a step could be solved
|
||||
* @private
|
||||
*/
|
||||
private static step(field: Field): boolean {
|
||||
private static async step(field: Field): Promise<boolean> {
|
||||
let flagCount = field.flagCount;
|
||||
let coveredCount = field.coveredNonMineCount;
|
||||
|
||||
if (field.hasWon || field.hasLost)
|
||||
return false;
|
||||
|
||||
this.stepSingleSquares(field);
|
||||
await this.stepSingleSquares(field);
|
||||
if (field.hasWon || field.flagCount !== flagCount || field.coveredNonMineCount !== coveredCount)
|
||||
return true;
|
||||
|
||||
this.stepNeighboringSquares(field);
|
||||
await this.stepNeighboringSquares(field);
|
||||
if (field.hasWon || field.flagCount !== flagCount || field.coveredNonMineCount !== coveredCount)
|
||||
return true;
|
||||
|
||||
this.stepAllSquares(field);
|
||||
await this.stepAllSquares(field);
|
||||
// noinspection RedundantIfStatementJS // Makes it easier to add more steps
|
||||
if (field.hasWon || field.flagCount !== flagCount || field.coveredNonMineCount !== coveredCount)
|
||||
return true;
|
||||
|
@ -134,13 +136,16 @@ export class Solver {
|
|||
* @param field the field to solve
|
||||
* @private
|
||||
*/
|
||||
private static stepSingleSquares(field: Field): void {
|
||||
Solver.getKnowns(field)
|
||||
.forEach(square => {
|
||||
field.chord(square);
|
||||
if (square.getNeighborCount(it => it.isCovered) === square.getNeighborCount(it => it.hasMine))
|
||||
square.neighbors.filter(it => !it.hasFlag).forEach(it => field.toggleFlag(it));
|
||||
});
|
||||
private static async stepSingleSquares(field: Field): Promise<void> {
|
||||
for (const square of Solver.getKnowns(field)) {
|
||||
await field.chord(square);
|
||||
|
||||
if (square.getNeighborCount(it => it.isCovered) !== square.getNeighborCount(it => it.hasMine))
|
||||
continue;
|
||||
|
||||
for (const neighbor of square.neighbors.filter(it => !it.hasFlag))
|
||||
await field.toggleFlag(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,14 +160,14 @@ export class Solver {
|
|||
* @param field the field to solve
|
||||
* @private
|
||||
*/
|
||||
private static stepNeighboringSquares(field: Field): void {
|
||||
private static async stepNeighboringSquares(field: Field): Promise<void> {
|
||||
const knowns = Solver.getKnowns(field);
|
||||
knowns.forEach(known => {
|
||||
Solver.applySolution(
|
||||
for (const known of knowns) {
|
||||
await Solver.applySolution(
|
||||
field,
|
||||
this.matrixSolve(field, known.neighbors.filter(it => !it.isCovered).concat(known), true)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -174,11 +179,11 @@ export class Solver {
|
|||
* @param field the field to solve
|
||||
* @private
|
||||
*/
|
||||
private static stepAllSquares(field: Field): void {
|
||||
private static async stepAllSquares(field: Field): Promise<void> {
|
||||
if (!field.hasStarted || field.hasWon || field.hasLost) return;
|
||||
|
||||
const knowns = Solver.getKnowns(field);
|
||||
Solver.applySolution(
|
||||
await Solver.applySolution(
|
||||
field,
|
||||
this.matrixSolve(field, knowns, false)
|
||||
);
|
||||
|
@ -245,14 +250,14 @@ export class Solver {
|
|||
* @param solution the solution to apply
|
||||
* @private
|
||||
*/
|
||||
private static applySolution(field: Field, solution: Solution): void {
|
||||
solution.forEach(target => {
|
||||
if (target === undefined) return;
|
||||
private static async applySolution(field: Field, solution: Solution): Promise<void> {
|
||||
for (const target of solution) {
|
||||
if (target === undefined) continue;
|
||||
|
||||
const [solution, square] = target;
|
||||
if (solution === 0) field.uncover(square.coords);
|
||||
else if (solution === 1) field.toggleFlag(square.coords);
|
||||
});
|
||||
if (solution === 0) await field.uncover(square.coords);
|
||||
else if (solution === 1) await field.toggleFlag(square.coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,9 +69,9 @@ export class Timer {
|
|||
*
|
||||
* @param callback the callback to time the execution of
|
||||
*/
|
||||
time(callback: () => void): void {
|
||||
async time(callback: () => Promise<void>): Promise<void> {
|
||||
this.start();
|
||||
callback();
|
||||
await callback();
|
||||
this.stop();
|
||||
}
|
||||
|
||||
|
@ -82,9 +82,9 @@ export class Timer {
|
|||
* @param callback the function to time the execution of
|
||||
* @returns the number of milliseconds the callback took to execute
|
||||
*/
|
||||
static time(callback: () => void): number {
|
||||
static async time(callback: () => Promise<void>): Promise<number> {
|
||||
const timer = new Timer();
|
||||
timer.time(callback);
|
||||
await timer.time(callback);
|
||||
return timer.elapsedTime;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue