Add fallback font

Fixes #83.
This commit is contained in:
Florine W. Dekker 2020-08-10 19:56:28 +02:00
parent 0e9c1816c7
commit 6adc3de1c4
Signed by: FWDekker
GPG Key ID: B1B567AF58D6EE0F
6 changed files with 79 additions and 22 deletions

View File

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

View File

@ -151,6 +151,6 @@
<!-- Scripts --> <!-- Scripts -->
<!--suppress HtmlUnknownTarget --> <!--suppress HtmlUnknownTarget -->
<script src="bundle.js"></script> <script src="bundle.js?v=%%VERSION_NUMBER%%"></script>
</body> </body>
</html> </html>

View File

@ -54,10 +54,12 @@ export function range(length: number, beginAt: number = 0): number[] {
* *
* Taken from https://stackoverflow.com/a/35572620/ (CC BY-SA 3.0). * Taken from https://stackoverflow.com/a/35572620/ (CC BY-SA 3.0).
* *
* @param callback the function to invoke once the font has loaded * @param onSuccess the function to invoke once the font has loaded
* @param onFailure the function to invoke if the font cannot be loaded
* @param timeout the maximum time in milliseconds to wait for the font to load * @param timeout the maximum time in milliseconds to wait for the font to load
*/ */
export function waitForForkAwesome(callback: () => void, timeout: number | undefined = undefined): void { export function waitForForkAwesome(onSuccess: () => void, onFailure: () => void,
timeout: number | undefined = undefined): void {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!; const ctx = canvas.getContext("2d")!;
const fontSize = 36; const fontSize = 36;
@ -81,9 +83,9 @@ export function waitForForkAwesome(callback: () => void, timeout: number | undef
*/ */
function fontOnload(time: number): void { function fontOnload(time: number): void {
const currentCount = getPixelCount(); const currentCount = getPixelCount();
if (failTime !== undefined && time > failTime) alert(`ForkAwesome failed to load after ${timeout}ms.`); if (failTime !== undefined && time > failTime) onFailure();
else if (currentCount < targetPixelCount) requestAnimationFrame(fontOnload); else if (currentCount < targetPixelCount) requestAnimationFrame(fontOnload);
else callback(); else onSuccess();
} }
/** /**

View File

@ -10,7 +10,9 @@ import {Field, Square} from "./Field";
export class Display { export class Display {
private readonly scale: number = 30; private readonly scale: number = 30;
private readonly minSquareWidth: number = 6; private readonly minSquareWidth: number = 6;
private readonly canvas: HTMLCanvasElement; private readonly canvas: HTMLCanvasElement;
private readonly font: IconFont;
private field: Field | null = null; private field: Field | null = null;
private winTime: number | null = null; private winTime: number | null = null;
@ -22,8 +24,8 @@ export class Display {
private coverSymbol: HTMLCanvasElement | undefined; private coverSymbol: HTMLCanvasElement | undefined;
private flagSymbol: HTMLCanvasElement | undefined; private flagSymbol: HTMLCanvasElement | undefined;
private mineSymbol: HTMLCanvasElement | undefined;
private uncoveredMineSymbol: HTMLCanvasElement | undefined; private uncoveredMineSymbol: HTMLCanvasElement | undefined;
private mineSymbol: HTMLCanvasElement | undefined;
private digitSymbols: HTMLCanvasElement[] | undefined; private digitSymbols: HTMLCanvasElement[] | undefined;
private clockSymbol: HTMLCanvasElement | undefined; private clockSymbol: HTMLCanvasElement | undefined;
private deathsSymbolA: HTMLCanvasElement | undefined; private deathsSymbolA: HTMLCanvasElement | undefined;
@ -35,9 +37,12 @@ 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
*/ */
constructor(canvas: HTMLCanvasElement, field: Field | null) { constructor(canvas: HTMLCanvasElement, field: Field | null, font: IconFont = new BasicIconFont()) {
this.canvas = canvas; this.canvas = canvas;
this.font = font;
this.setField(field); this.setField(field);
} }
@ -58,7 +63,7 @@ export class Display {
ctx.font = `${Math.floor(this.scale * 0.55)}px ${font}`; ctx.font = `${Math.floor(this.scale * 0.55)}px ${font}`;
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillText(text, Math.floor(this.scale / 2), Math.floor(this.scale / 2)); ctx.fillText(text, Math.floor(this.scale / 2), Math.floor(this.scale / 2), this.scale);
ctx.restore(); ctx.restore();
}; };
@ -67,17 +72,17 @@ 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("\uf024", "ForkAwesome", "#f00"); fillText(this.font.flag, this.font.fontFace, "#f00");
this.mineSymbol = createCanvas(this.scale, this.scale);
ctx = this.mineSymbol.getContext("2d")!;
fillText("\uf1e2", "ForkAwesome", "#00007b");
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("\uf1e2", "ForkAwesome", "#000"); fillText(this.font.uncoveredMine, this.font.fontFace, "#000");
this.mineSymbol = createCanvas(this.scale, this.scale);
ctx = this.mineSymbol.getContext("2d")!;
fillText(this.font.mine, this.font.fontFace, "#00007b");
const digitColors = const digitColors =
["", "#0000ff", "#007b00", "#ff0000", "#00007b", "#7b0000", "#007b7b", "#000000", "#7b7b7b"]; ["", "#0000ff", "#007b00", "#ff0000", "#00007b", "#7b0000", "#007b7b", "#000000", "#7b7b7b"];
@ -90,15 +95,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("\uf017", "ForkAwesome"); fillText(this.font.clock, this.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("\uf0f9", "ForkAwesome", "#fff"); fillText(this.font.deaths, this.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("\uf0f9", "ForkAwesome", "#f00"); fillText(this.font.deaths, this.font.fontFace, "#f00");
} }
/** /**
@ -424,3 +429,43 @@ export class Display {
} }
} }
} }
/**
* A font that can be used to display icons for the display.
*/
export interface IconFont {
fontFace: string;
flag: string;
uncoveredMine: string;
mine: string;
clock: string;
deaths: string;
}
/**
* A basic font that can be displayed in any browser.
*/
export class BasicIconFont implements IconFont {
fontFace = "Courier New";
flag = "F";
uncoveredMine = "X";
mine = "Mines";
clock = "Time";
deaths = "Deaths";
}
/**
* ForkAwesome, which can be used on any browser that does not block external fonts.
*/
export class ForkAwesomeFont implements IconFont {
fontFace = "ForkAwesome";
flag = "\uf024";
uncoveredMine = "\uf1e2";
mine = "\uf1e2";
clock = "\uf017";
deaths = "\uf0f9";
}

View File

@ -4,7 +4,7 @@ import {$} from "@fwdekker/template";
import alea from "alea"; import alea from "alea";
import {stringToHash} from "./Common"; import {stringToHash} from "./Common";
import {customDifficulty, defaultDifficulty, difficulties} from "./Difficulty"; import {customDifficulty, defaultDifficulty, difficulties} from "./Difficulty";
import {Display} from "./Display"; import {BasicIconFont, Display, ForkAwesomeFont} 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";
@ -51,12 +51,15 @@ export class Game {
/** /**
* Constructs and starts a new game of Minesweeper. * Constructs and starts a new game of Minesweeper.
*
* @param withForkAwesome whether ForkAwesome can be used
*/ */
constructor() { constructor(withForkAwesome: boolean = true) {
this.canvas = $("#canvas"); this.canvas = $("#canvas");
this.field = null; // Placeholder until `initNewField` this.field = null; // Placeholder until `initNewField`
this.display = new Display(this.canvas, this.field); this.display =
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";

View File

@ -23,5 +23,12 @@ doAfterLoad(() => {
// Start game // Start game
waitForForkAwesome(() => new Game(), 3000); waitForForkAwesome(
() => new Game(),
() => {
alert("External font could not be loaded. Using fallback font. Is a browser extension blocking fonts?");
new Game(false);
},
3000
);
}); });