Add option to disable flag hints
This commit is contained in:
parent
7af33026a4
commit
6d93d3394f
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "minesweeper",
|
"name": "minesweeper",
|
||||||
"version": "0.81.7",
|
"version": "0.81.8",
|
||||||
"description": "Just Minesweeper!",
|
"description": "Just Minesweeper!",
|
||||||
"author": "Felix W. Dekker",
|
"author": "Felix W. Dekker",
|
||||||
"browser": "dist/bundle.js",
|
"browser": "dist/bundle.js",
|
||||||
|
|
|
@ -148,6 +148,9 @@
|
||||||
<form id="preferencesForm">
|
<form id="preferencesForm">
|
||||||
<label for="preferencesEnableMarks">Enable question marks</label>
|
<label for="preferencesEnableMarks">Enable question marks</label>
|
||||||
<input type="checkbox" id="preferencesEnableMarks" />
|
<input type="checkbox" id="preferencesEnableMarks" />
|
||||||
|
|
||||||
|
<label for="preferencesShowTooManyFlagsHints">Highlight squares with too many flags around them</label>
|
||||||
|
<input type="checkbox" id="preferencesShowTooManyFlagsHints" />
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<button>Save</button>
|
<button>Save</button>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import confetti from "canvas-confetti";
|
import confetti from "canvas-confetti";
|
||||||
import {formatTime, range} from "./Common";
|
import {formatTime, range} from "./Common";
|
||||||
import {Field, Square} from "./Field";
|
import {Field, Square} from "./Field";
|
||||||
|
import {Preferences} from "./Game";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +13,7 @@ export class Display {
|
||||||
private readonly minSquareWidth: number = 6;
|
private readonly minSquareWidth: number = 6;
|
||||||
|
|
||||||
private readonly canvas: HTMLCanvasElement;
|
private readonly canvas: HTMLCanvasElement;
|
||||||
private readonly font: IconFont;
|
private readonly preferences: Preferences;
|
||||||
|
|
||||||
private field: Field | null = null;
|
private field: Field | null = null;
|
||||||
private winTime: number | null = null;
|
private winTime: number | null = null;
|
||||||
|
@ -38,11 +39,11 @@ export class Display {
|
||||||
*
|
*
|
||||||
* @param canvas the canvas to draw the field in
|
* @param canvas the canvas to draw the field in
|
||||||
* @param field the field to draw
|
* @param field the field to draw
|
||||||
* @param font the font to draw symbols with
|
* @param preferences the player's preferences; may be changed at any time
|
||||||
*/
|
*/
|
||||||
constructor(canvas: HTMLCanvasElement, field: Field | null, font: IconFont = new BasicIconFont()) {
|
constructor(canvas: HTMLCanvasElement, field: Field | null, preferences: Preferences) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.font = font;
|
this.preferences = preferences;
|
||||||
|
|
||||||
this.setField(field);
|
this.setField(field);
|
||||||
}
|
}
|
||||||
|
@ -52,6 +53,7 @@ export class Display {
|
||||||
*/
|
*/
|
||||||
initSymbols(): void {
|
initSymbols(): void {
|
||||||
let ctx: CanvasRenderingContext2D;
|
let ctx: CanvasRenderingContext2D;
|
||||||
|
const font = this.preferences.font;
|
||||||
const createCanvas = (width: number, height: number) => {
|
const createCanvas = (width: number, height: number) => {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
|
@ -73,21 +75,21 @@ export class Display {
|
||||||
|
|
||||||
this.flagSymbol = createCanvas(this.scale, this.scale);
|
this.flagSymbol = createCanvas(this.scale, this.scale);
|
||||||
ctx = this.flagSymbol.getContext("2d")!;
|
ctx = this.flagSymbol.getContext("2d")!;
|
||||||
fillText(this.font.flag, this.font.fontFace, "#f00");
|
fillText(font.flag, font.fontFace, "#f00");
|
||||||
|
|
||||||
this.markSymbol = createCanvas(this.scale, this.scale);
|
this.markSymbol = createCanvas(this.scale, this.scale);
|
||||||
ctx = this.markSymbol.getContext("2d")!;
|
ctx = this.markSymbol.getContext("2d")!;
|
||||||
fillText(this.font.mark, this.font.fontFace, "#00f");
|
fillText(font.mark, font.fontFace, "#00f");
|
||||||
|
|
||||||
this.uncoveredMineSymbol = createCanvas(this.scale, this.scale);
|
this.uncoveredMineSymbol = createCanvas(this.scale, this.scale);
|
||||||
ctx = this.uncoveredMineSymbol.getContext("2d")!;
|
ctx = this.uncoveredMineSymbol.getContext("2d")!;
|
||||||
ctx.fillStyle = "#f00";
|
ctx.fillStyle = "#f00";
|
||||||
ctx.fillRect(1, 1, this.scale - 2, this.scale - 2);
|
ctx.fillRect(1, 1, this.scale - 2, this.scale - 2);
|
||||||
fillText(this.font.uncoveredMine, this.font.fontFace, "#000");
|
fillText(font.uncoveredMine, font.fontFace, "#000");
|
||||||
|
|
||||||
this.mineSymbol = createCanvas(this.scale, this.scale);
|
this.mineSymbol = createCanvas(this.scale, this.scale);
|
||||||
ctx = this.mineSymbol.getContext("2d")!;
|
ctx = this.mineSymbol.getContext("2d")!;
|
||||||
fillText(this.font.mine, this.font.fontFace, "#00007b");
|
fillText(font.mine, font.fontFace, "#00007b");
|
||||||
|
|
||||||
const digitColors =
|
const digitColors =
|
||||||
["", "#0000ff", "#007b00", "#ff0000", "#00007b", "#7b0000", "#007b7b", "#000000", "#7b7b7b"];
|
["", "#0000ff", "#007b00", "#ff0000", "#00007b", "#7b0000", "#007b7b", "#000000", "#7b7b7b"];
|
||||||
|
@ -100,15 +102,15 @@ export class Display {
|
||||||
|
|
||||||
this.clockSymbol = createCanvas(this.scale, this.scale);
|
this.clockSymbol = createCanvas(this.scale, this.scale);
|
||||||
ctx = this.clockSymbol.getContext("2d")!;
|
ctx = this.clockSymbol.getContext("2d")!;
|
||||||
fillText(this.font.clock, this.font.fontFace);
|
fillText(font.clock, font.fontFace);
|
||||||
|
|
||||||
this.deathsSymbolA = createCanvas(this.scale, this.scale);
|
this.deathsSymbolA = createCanvas(this.scale, this.scale);
|
||||||
ctx = this.deathsSymbolA.getContext("2d")!;
|
ctx = this.deathsSymbolA.getContext("2d")!;
|
||||||
fillText(this.font.deaths, this.font.fontFace, "#fff");
|
fillText(font.deaths, font.fontFace, "#fff");
|
||||||
|
|
||||||
this.deathsSymbolB = createCanvas(this.scale, this.scale);
|
this.deathsSymbolB = createCanvas(this.scale, this.scale);
|
||||||
ctx = this.deathsSymbolB.getContext("2d")!;
|
ctx = this.deathsSymbolB.getContext("2d")!;
|
||||||
fillText(this.font.deaths, this.font.fontFace, "#f00");
|
fillText(font.deaths, font.fontFace, "#f00");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -299,13 +301,15 @@ export class Display {
|
||||||
private drawHints(ctx: CanvasRenderingContext2D): void {
|
private drawHints(ctx: CanvasRenderingContext2D): void {
|
||||||
if (this.field === null) return;
|
if (this.field === null) return;
|
||||||
|
|
||||||
ctx.save();
|
if (this.preferences.showTooManyFlagsHints) {
|
||||||
ctx.fillStyle = "rgba(255, 0, 0, 0.3)";
|
ctx.save();
|
||||||
this.field.squareList
|
ctx.fillStyle = "rgba(255, 0, 0, 0.3)";
|
||||||
.filter(it => !it.isCovered)
|
this.field.squareList
|
||||||
.filter(it => it.getNeighborCount(it => it.hasMine) < it.getNeighborCount(it => it.hasFlag))
|
.filter(it => !it.isCovered)
|
||||||
.forEach(square => ctx.fillRect(square.x * this.scale, square.y * this.scale, this.scale, this.scale));
|
.filter(it => it.getNeighborCount(it => it.hasMine) < it.getNeighborCount(it => it.hasFlag))
|
||||||
ctx.restore();
|
.forEach(square => ctx.fillRect(square.x * this.scale, square.y * this.scale, this.scale, this.scale));
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
if (this.hintSquare !== null) {
|
if (this.hintSquare !== null) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {$} from "@fwdekker/template";
|
||||||
import alea from "alea";
|
import alea from "alea";
|
||||||
import {blurActiveElement, stringToHash} from "./Common";
|
import {blurActiveElement, stringToHash} from "./Common";
|
||||||
import {customDifficulty, defaultDifficulty, difficulties} from "./Difficulty";
|
import {customDifficulty, defaultDifficulty, difficulties} from "./Difficulty";
|
||||||
import {BasicIconFont, Display, ForkAwesomeFont} from "./Display";
|
import {BasicIconFont, Display, ForkAwesomeFont, IconFont} from "./Display";
|
||||||
import {Field} from "./Field";
|
import {Field} from "./Field";
|
||||||
import {Solver} from "./Solver";
|
import {Solver} from "./Solver";
|
||||||
import {LocalStatistics} from "./Statistics";
|
import {LocalStatistics} from "./Statistics";
|
||||||
|
@ -37,6 +37,7 @@ export class Game {
|
||||||
private readonly solvableInput: HTMLInputElement;
|
private readonly solvableInput: HTMLInputElement;
|
||||||
private readonly preferencesOverlay: Overlay;
|
private readonly preferencesOverlay: Overlay;
|
||||||
private readonly enableMarksInput: HTMLInputElement;
|
private readonly enableMarksInput: HTMLInputElement;
|
||||||
|
private readonly showTooManyFlagsHintsInput: HTMLInputElement;
|
||||||
private readonly preferencesOpenForm: HTMLFormElement;
|
private readonly preferencesOpenForm: HTMLFormElement;
|
||||||
private readonly statisticsOverlay: Overlay;
|
private readonly statisticsOverlay: Overlay;
|
||||||
private readonly statisticsDiv: HTMLDivElement;
|
private readonly statisticsDiv: HTMLDivElement;
|
||||||
|
@ -57,14 +58,12 @@ export class Game {
|
||||||
* Constructs and starts a new game of Minesweeper.
|
* Constructs and starts a new game of Minesweeper.
|
||||||
*
|
*
|
||||||
* @param preferences the preferences to play the game under; may be changed during gameplay
|
* @param preferences the preferences to play the game under; may be changed during gameplay
|
||||||
* @param withForkAwesome whether ForkAwesome can be used
|
|
||||||
*/
|
*/
|
||||||
constructor(preferences: Preferences, withForkAwesome: boolean = true) {
|
constructor(preferences: Preferences) {
|
||||||
this.canvas = $("#canvas");
|
this.canvas = $("#canvas");
|
||||||
|
|
||||||
this.field = null; // Placeholder until `initNewField`
|
this.field = null; // Placeholder until `initNewField`
|
||||||
this.display =
|
this.display = new Display(this.canvas, this.field, preferences);
|
||||||
new Display(this.canvas, this.field, withForkAwesome ? new ForkAwesomeFont() : new BasicIconFont());
|
|
||||||
this.display.startDrawLoop();
|
this.display.startDrawLoop();
|
||||||
this.canvas.style.visibility = "unset";
|
this.canvas.style.visibility = "unset";
|
||||||
|
|
||||||
|
@ -240,6 +239,7 @@ export class Game {
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
this.enableMarksInput = $("#preferencesEnableMarks");
|
this.enableMarksInput = $("#preferencesEnableMarks");
|
||||||
|
this.showTooManyFlagsHintsInput = $("#preferencesShowTooManyFlagsHints");
|
||||||
this.preferencesOpenForm = $("#preferencesOpenForm");
|
this.preferencesOpenForm = $("#preferencesOpenForm");
|
||||||
this.preferencesOpenForm.addEventListener(
|
this.preferencesOpenForm.addEventListener(
|
||||||
"submit",
|
"submit",
|
||||||
|
@ -247,6 +247,7 @@ export class Game {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.enableMarksInput.checked = preferences.marksEnabled;
|
this.enableMarksInput.checked = preferences.marksEnabled;
|
||||||
|
this.showTooManyFlagsHintsInput.checked = preferences.showTooManyFlagsHints;
|
||||||
this.preferencesOverlay.show();
|
this.preferencesOverlay.show();
|
||||||
blurActiveElement();
|
blurActiveElement();
|
||||||
}
|
}
|
||||||
|
@ -255,7 +256,10 @@ export class Game {
|
||||||
$("#preferencesOverlay"),
|
$("#preferencesOverlay"),
|
||||||
$("#preferencesForm"),
|
$("#preferencesForm"),
|
||||||
$("#preferencesCancelForm"),
|
$("#preferencesCancelForm"),
|
||||||
() => preferences.marksEnabled = this.enableMarksInput.checked
|
() => {
|
||||||
|
preferences.marksEnabled = this.enableMarksInput.checked;
|
||||||
|
preferences.showTooManyFlagsHints = this.showTooManyFlagsHintsInput.checked;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
|
@ -451,11 +455,18 @@ export class Game {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The player's preferences.
|
* The player's preferences.
|
||||||
|
*
|
||||||
|
* Contains a mixture of persistent and transient preferences.
|
||||||
*/
|
*/
|
||||||
export class Preferences {
|
export class Preferences {
|
||||||
private readonly storage = new Storage("/tools/minesweeper//preferences");
|
private readonly storage = new Storage("/tools/minesweeper//preferences");
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The font to be used when drawing the display.
|
||||||
|
*/
|
||||||
|
font: IconFont = new BasicIconFont();
|
||||||
|
|
||||||
get marksEnabled(): boolean {
|
get marksEnabled(): boolean {
|
||||||
return this.storage.getBoolean("marksEnabled", true);
|
return this.storage.getBoolean("marksEnabled", true);
|
||||||
}
|
}
|
||||||
|
@ -463,4 +474,12 @@ export class Preferences {
|
||||||
set marksEnabled(value: boolean) {
|
set marksEnabled(value: boolean) {
|
||||||
this.storage.setBoolean("marksEnabled", value);
|
this.storage.setBoolean("marksEnabled", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showTooManyFlagsHints(): boolean {
|
||||||
|
return this.storage.getBoolean("showTooManyFlagsHints", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
set showTooManyFlagsHints(value: boolean) {
|
||||||
|
this.storage.setBoolean("showTooManyFlagsHints", value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import "../css/main.css";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import {$, doAfterLoad, footer, header, nav} from "@fwdekker/template";
|
import {$, doAfterLoad, footer, header, nav} from "@fwdekker/template";
|
||||||
import {waitForForkAwesome} from "./Common";
|
import {waitForForkAwesome} from "./Common";
|
||||||
|
import {BasicIconFont, ForkAwesomeFont} from "./Display";
|
||||||
import {Game, Preferences} from "./Game";
|
import {Game, Preferences} from "./Game";
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,11 +24,16 @@ doAfterLoad(() => {
|
||||||
|
|
||||||
|
|
||||||
// Start game
|
// Start game
|
||||||
|
const preferences = new Preferences();
|
||||||
waitForForkAwesome(
|
waitForForkAwesome(
|
||||||
() => new Game(new Preferences()),
|
() => {
|
||||||
|
preferences.font = new ForkAwesomeFont();
|
||||||
|
new Game(preferences);
|
||||||
|
},
|
||||||
() => {
|
() => {
|
||||||
alert("External font could not be loaded. Using fallback font. Is a browser extension blocking fonts?");
|
alert("External font could not be loaded. Using fallback font. Is a browser extension blocking fonts?");
|
||||||
new Game(new Preferences(), false);
|
preferences.font = new BasicIconFont();
|
||||||
|
new Game(preferences);
|
||||||
},
|
},
|
||||||
3000
|
3000
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue