minesweeper/src/main/js/Common.ts

153 lines
4.9 KiB
TypeScript

// @ts-ignore
import alea from "alea";
/**
* Removes focus from the currently active element, if possible.
*/
export function blurActiveElement(): void {
// @ts-ignore
document.activeElement?.blur?.();
}
/**
* Slices `array` into chunks of `chunkSize` elements each.
*
* If `array` does not contain a multiple of `chunkSize` elements, the last chunk will contain fewer elements.
*
* @param array the array to chunkify
* @param chunkSize the size of each chunk
* @returns an array of the extracted chunks
*/
export function chunkifyArray(array: any[], chunkSize: number): any[] {
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize)
chunks.push(array.slice(i, i + chunkSize));
return chunks;
}
/**
* Formats the given time into a nice representation of `hh:mm:ss`.
*
* @param seconds the number of seconds; the time to be formatted
* @param minutes whether to include minutes
* @param hours whether to include hours; requires that `minutes` is true
* @return the formatted time
*/
export function formatTime(seconds: number, minutes: boolean, hours: boolean): string {
if (!minutes && hours) throw new Error("Cannot format time with hours but without minutes.");
if (!minutes && !hours) return "" + seconds;
const secondsString = ("" + (seconds % 60)).padStart(2, '0');
const minutesString = hours
? ("" + Math.floor((seconds % 3600) / 60)).padStart(2, '0')
: ("" + Math.floor(seconds / 60));
if (!hours) return `${minutesString}:${secondsString}`;
const hoursString = Math.floor(seconds / 3600);
return `${hoursString}:${minutesString}:${secondsString}`;
}
/**
* Creates an array of `size` consecutive integers starting at `startAt`.
*
* Taken from https://stackoverflow.com/a/10050831 (CC BY-SA 4.0).
*
* @param length the number of consecutive integers to put in the array
* @param beginAt the first integer to return
* @returns the array of consecutive integers
*/
export function range(length: number, beginAt: number = 0): number[] {
return [...Array(length).keys()].map(i => i + beginAt);
}
/**
* Shuffles the given array in-place.
*
* @param array the array to shuffle
* @param seed the seed for the random number generator
* @returns the array that was given to this function to shuffle
*/
export function shuffleArrayInPlace(array: any[], seed: number): any[] {
const rng = alea("" + seed);
for (let i = array.length - 1; i > 0; i--) {
const j = rng.uint32() % (i + 1);
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
/**
* Hashes the given string.
*
* @param string the string to hash
* @returns the hash of the string
*/
export function stringToHash(string: string): number {
let hash = 0;
for (let i = 0; i < string.length; i++) {
const chr = string.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
}
return hash;
}
/**
* Waits for FontAwesome to have loaded and then invokes the callback.
*
* Taken from https://stackoverflow.com/a/35572620/ (CC BY-SA 3.0).
*
* @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, or `null` if there is no limit
*/
export function waitForForkAwesome(onSuccess: () => void, onFailure: () => void,
timeout: number | null = null): void {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
const fontSize = 36;
const testCharacter = "\uF047";
const targetPixelCount = 500;
const ccw = canvas.width = fontSize * 1.5;
const cch = canvas.height = fontSize * 1.5;
ctx.font = `${fontSize}px ForkAwesome`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const startTime = performance.now();
const failTime = timeout == null ? null : startTime + timeout;
requestAnimationFrame(fontOnload);
/**
* Repeatedly invokes itself until the font has loaded or the timeout has been reached.
*
* @param time the time in milliseconds at which this function is invoked
*/
function fontOnload(time: number): void {
const currentCount = getPixelCount();
if (failTime != null && time > failTime) onFailure();
else if (currentCount < targetPixelCount) requestAnimationFrame(fontOnload);
else onSuccess();
}
/**
* Draws a character in the canvas and returns the number of pixels that have been drawn.
*
* @returns the number of pixels that have been drawn
*/
function getPixelCount(): number {
ctx.clearRect(0, 0, ccw, cch);
ctx.fillText(testCharacter, ccw / 2, cch / 2);
const data = ctx.getImageData(0, 0, ccw, cch).data;
let count = 0;
for (let i = 3; i < data.length; i += 4)
if (data[i] > 10) count++;
return count;
}
}