From 1e840ae7c282e75242af6f87b45e4d56eda153fb Mon Sep 17 00:00:00 2001 From: "Felix W. Dekker" Date: Wed, 12 Aug 2020 18:52:02 +0200 Subject: [PATCH] Implement question marks Fixes #75. --- package.json | 2 +- src/main/js/Display.ts | 14 ++++++- src/main/js/Field.ts | 82 ++++++++++++++++++++++++++------------- src/main/js/Game.ts | 15 ++++++- src/main/js/Solver.ts | 10 +++-- src/main/js/Statistics.ts | 17 ++++++++ 6 files changed, 104 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 4de099b..a07af08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minesweeper", - "version": "0.80.5", + "version": "0.81.0", "description": "Just Minesweeper!", "author": "Felix W. Dekker", "browser": "dist/bundle.js", diff --git a/src/main/js/Display.ts b/src/main/js/Display.ts index dddeb44..261af7e 100644 --- a/src/main/js/Display.ts +++ b/src/main/js/Display.ts @@ -24,6 +24,7 @@ export class Display { private coverSymbol: HTMLCanvasElement | undefined; private flagSymbol: HTMLCanvasElement | undefined; + private markSymbol: HTMLCanvasElement | undefined; private uncoveredMineSymbol: HTMLCanvasElement | undefined; private mineSymbol: HTMLCanvasElement | undefined; private digitSymbols: HTMLCanvasElement[] | undefined; @@ -74,6 +75,10 @@ export class Display { ctx = this.flagSymbol.getContext("2d")!; fillText(this.font.flag, this.font.fontFace, "#f00"); + this.markSymbol = createCanvas(this.scale, this.scale); + ctx = this.markSymbol.getContext("2d")!; + fillText(this.font.mark, this.font.fontFace, "#00f"); + this.uncoveredMineSymbol = createCanvas(this.scale, this.scale); ctx = this.uncoveredMineSymbol.getContext("2d")!; ctx.fillStyle = "#f00"; @@ -275,9 +280,9 @@ export class Display { if (this.field!.hasLost || this.field!.hasWon || this.mouseSquare === null) return true; if (this.mouseHoldUncover && this.mouseSquare === it) - return it.hasFlag; + return it.hasFlag || it.hasMark; if (this.mouseHoldChord && (this.mouseSquare === it || this.mouseSquare.neighbors.indexOf(it) >= 0)) - return it.hasFlag; + return it.hasFlag || it.hasMark; return true; }) @@ -326,6 +331,8 @@ export class Display { let icon; if (square.hasFlag) icon = this.flagSymbol; + else if (square.hasMark) + icon = this.markSymbol; else if (square.hasMine && !square.isCovered) icon = this.uncoveredMineSymbol; else if (!square.isCovered) @@ -438,6 +445,7 @@ export interface IconFont { fontFace: string; flag: string; + mark: string; uncoveredMine: string; mine: string; clock: string; @@ -451,6 +459,7 @@ export class BasicIconFont implements IconFont { fontFace = "Courier New"; flag = "F"; + mark = "?"; uncoveredMine = "X"; mine = "Mines"; clock = "Time"; @@ -464,6 +473,7 @@ export class ForkAwesomeFont implements IconFont { fontFace = "ForkAwesome"; flag = "\uf024"; + mark = "\uf059"; uncoveredMine = "\uf1e2"; mine = "\uf1e2"; clock = "\uf017"; diff --git a/src/main/js/Field.ts b/src/main/js/Field.ts index 26c9920..8571c81 100644 --- a/src/main/js/Field.ts +++ b/src/main/js/Field.ts @@ -208,6 +208,7 @@ export class Field { if (square === undefined) throw new Error(`Cannot chord undefined square at (${coords}).`); if (square.isCovered || this.hasWon || this.hasLost) return; + if (square.getNeighborCount(it => it.hasMark) > 0) return; if (square.getNeighborCount(it => it.hasFlag) !== square.getNeighborCount(it => it.hasMine)) return; this.statistics.squaresChorded++; @@ -220,31 +221,6 @@ export class Field { if (this.hasLost) this.statistics.squaresChordedLeadingToLoss++; } - /** - * Toggles the flag at the given square. - * - * @param coords the coordinates of the square to flag - */ - flag(coords: { x: number, y: number }): 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 || this.hasWon || this.hasLost) return; - - this.addAction(new Action( - () => { - square.hasFlag = !square.hasFlag; - if (square.hasFlag) this.statistics.squaresFlagged++; - this._flagCount += (square.hasFlag ? 1 : -1); - }, - () => { - square.hasFlag = !square.hasFlag; - this._flagCount += (square.hasFlag ? 1 : -1); - return true; - }, - () => true - )); - } - /** * Uncovers this square, revealing the contents beneath. * @@ -282,7 +258,7 @@ export class Field { const uncoverQueue: Square[] = [square]; while (uncoverQueue.length > 0) { const next = uncoverQueue.pop()!; - if (!next.isCovered || next.hasFlag) continue; + if (!next.isCovered || next.hasFlag || next.hasMark) continue; let remainingFlags: Square[] | undefined; this.addAction(new Action( @@ -337,12 +313,61 @@ export class Field { )); if (next.hasMine) break; - if (next.getNeighborCount(it => it.hasMine || it.hasFlag) === 0) + if (next.getNeighborCount(it => it.hasMine || it.hasFlag || it.hasMark) === 0) uncoverQueue.push(...next.neighbors.filter(it => it.isCovered)); } }); } + /** + * Toggles the flag at the given square. + * + * @param coords the coordinates of the square to toggle the flag at + */ + toggleFlag(coords: { x: number, y: number }): 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( + () => { + square.hasFlag = !square.hasFlag; + if (square.hasFlag) this.statistics.squaresFlagged++; + this._flagCount += (square.hasFlag ? 1 : -1); + }, + () => { + square.hasFlag = !square.hasFlag; + this._flagCount += (square.hasFlag ? 1 : -1); + return true; + }, + () => true + )); + } + + /** + * Toggles the question mark at the given square. + * + * @param coords the coordinates of the square to toggle the question mark at + */ + toggleMark(coords: { x: number, y: number }): 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( + () => { + square.hasMark = !square.hasMark; + if (square.hasMark) this.statistics.squaresMarked++; + }, + () => { + square.hasMark = !square.hasMark; + if (square.hasMark) this.statistics.squaresMarked++; + return true; + }, + () => true + )); + } + /** * Runs the given callback such that all calls to `#addAction` can be undone with a single invocation of `#undo`. @@ -433,6 +458,7 @@ export class Square { isCovered: boolean; hasMine: boolean; hasFlag: boolean; + hasMark: boolean; /** @@ -451,6 +477,7 @@ export class Square { this.isCovered = true; this.hasMine = hasMine; this.hasFlag = false; + this.hasMark = false; } /** @@ -463,6 +490,7 @@ export class Square { const copy = new Square(field, this.x, this.y, this.hasMine); copy.isCovered = this.isCovered; copy.hasFlag = this.hasFlag; + copy.hasMark = this.hasMark; return copy; } diff --git a/src/main/js/Game.ts b/src/main/js/Game.ts index 862b790..90d41bd 100644 --- a/src/main/js/Game.ts +++ b/src/main/js/Game.ts @@ -293,8 +293,19 @@ export class Game { this.leftDown = true; break; case 2: - if (!this.leftDown) - this.field.flag(coords); + if (!this.leftDown) { + const square = this.field.getSquareOrElse(coords); + if (square !== undefined) { + if (square.hasFlag) { + this.field.toggleFlag(coords); + this.field.toggleMark(coords); + } else if (square.hasMark) { + this.field.toggleMark(coords); + } else { + this.field.toggleFlag(coords); + } + } + } this.rightDown = true; break; diff --git a/src/main/js/Solver.ts b/src/main/js/Solver.ts index d24a4f7..03a978d 100644 --- a/src/main/js/Solver.ts +++ b/src/main/js/Solver.ts @@ -19,13 +19,15 @@ export class Solver { if (!field.hasStarted) { field.runUndoably(() => { - field.squareList.filter(it => it.hasFlag).forEach(it => field.flag(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.uncover({x: Math.floor(field.width / 2), y: Math.floor(field.height / 2)}); }); } field.runUndoably(() => { - field.squareList.filter(it => it.hasFlag).forEach(it => field.flag(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)); let flagCount = -1; let coveredCount = -1; @@ -137,7 +139,7 @@ export class Solver { .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.flag(it)); + square.neighbors.filter(it => !it.hasFlag).forEach(it => field.toggleFlag(it)); }); } @@ -249,7 +251,7 @@ export class Solver { const [solution, square] = target; if (solution === 0) field.uncover(square.coords); - else if (solution === 1) field.flag(square.coords); + else if (solution === 1) field.toggleFlag(square.coords); }); } } diff --git a/src/main/js/Statistics.ts b/src/main/js/Statistics.ts index 0b65677..0afe2da 100644 --- a/src/main/js/Statistics.ts +++ b/src/main/js/Statistics.ts @@ -18,6 +18,7 @@ export interface Statistics { squaresChorded: number; squaresChordedLeadingToLoss: number; squaresFlagged: number; + squaresMarked: number; squaresUncovered: number; hintsRequested: number; @@ -171,6 +172,16 @@ export class LocalStatistics implements Statistics { this.write(statistics); } + get squaresMarked(): number { + return +(this.read()["squaresMarked"] ?? 0); + } + + set squaresMarked(value: number) { + const statistics = this.read(); + statistics["squaresMarked"] = "" + value; + this.write(statistics); + } + get squaresUncovered(): number { return +(this.read()["squaresUncovered"] ?? 0); } @@ -261,6 +272,10 @@ export class LocalStatistics implements Statistics { Squares flagged ${this.squaresFlagged} + + Squares marked + ${this.squaresMarked} + Squares uncovered ${this.squaresUncovered} @@ -296,6 +311,7 @@ export class MemoryStatistics implements Statistics { squaresChorded: number = 0; squaresChordedLeadingToLoss: number = 0; squaresFlagged: number = 0; + squaresMarked: number = 0; squaresUncovered: number = 0; hintsRequested: number = 0; solverUsages: number = 0; @@ -313,6 +329,7 @@ export class MemoryStatistics implements Statistics { this.squaresChorded = 0; this.squaresChordedLeadingToLoss = 0; this.squaresFlagged = 0; + this.squaresMarked = 0; this.squaresUncovered = 0; this.hintsRequested = 0; this.solverUsages = 0;