Add option to disable flag hints

This commit is contained in:
Florine W. Dekker 2020-08-25 16:36:06 +02:00
parent 7af33026a4
commit 6d93d3394f
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
5 changed files with 59 additions and 27 deletions

View File

@ -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",

View File

@ -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>

View File

@ -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();

View File

@ -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);
}
} }

View File

@ -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
); );