import type { StateUpdater } from "preact/hooks"; export const nextFrame = async (): Promise => new Promise((resolve) => requestAnimationFrame(resolve)); export const delay = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); export const randInt = (min: number, max: number) => Math.round(min + (max - min - 1) * Math.random()); export const randBool = () => Math.random() < 0.5; export const choice = (array: T[]): T => array[randInt(0, array.length)]; export const weightedChoice = (options: [T, number][] | Partial>): T | null => { if (!Array.isArray(options)) { options = Object.entries(options) as [T, number][]; } const sum = options.reduce((acc, o) => acc + o[1], 0); const rnd = Math.random() * sum; let weight = 0; for (const [item, probability] of options) { weight += probability; if (rnd < weight) { return item; } } return null; } export const shuffle = (array: T[]): T[] => { const shuffledArray = [...array]; for (let i = shuffledArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; } return shuffledArray; } export const mapNumber = (num: number, inMin: number, inMax: number, outMin: number, outMax: number) => (num - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; export function zip(a1: Iterable, a2: Iterable, a3: Iterable): Generator<[T1, T2, T3]>; export function zip(a1: Iterable, a2: Iterable): Generator<[T1, T2]>; export function zip(a1: Iterable): Generator<[T1]>; export function* zip(...args: Iterable[]) { const iterators = args.map(i => i[Symbol.iterator]()); while (true) { const nextValues = iterators.map(i => i.next()); if (nextValues.some(v => v.done)) return; yield nextValues.map(v => v.value); } } export function* enumerate(iterable: Iterable): Generator<[number, T]> { let i = 0; for (const item of iterable) { yield [i++, item]; } } export const range = (size: number | string) => Object.keys((new Array(+size)).fill(0)).map(k => +k); export const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value)); export const lerp = (start: number, end: number, t: number) => (start + (end - start) * t); export const prevent = (e: Event) => (e.preventDefault(), false); export const intHash = (seed: number, ...parts: number[]) => { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; for (let i = 0; i < parts.length; i++) { const ch = parts[i]; h1 = Math.imul(h1 ^ ch, 2654435761); h2 = Math.imul(h2 ^ ch, 1597334677); } h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); return h1; }; export const sinHash = (...data: number[]) => data.reduce((hash, n) => Math.sin((hash * 123.12 + n) * 756.12), 0) / 2 + 0.5; type F = (this: T, ...args: A) => R; export const throttle = function (func: F, ms: number, trailing = false): F { let isThrottled = false; let savedResult: R; let savedThis: T; let savedArgs: A | undefined; const wrapper = function (this: T, ...args: A): R { if (isThrottled) { savedThis = this; savedArgs = args; } else { savedResult = func.apply(this, args); savedArgs = undefined; isThrottled = true; setTimeout(function () { isThrottled = false; if (trailing && savedArgs) { savedResult = wrapper.apply(savedThis, savedArgs); } }, ms); } return savedResult; }; return wrapper; } export const callUpdater = (f: StateUpdater, prev: T) => typeof f === 'function' ? (f as Function)(prev) : f; export const formatNumber = (n: number): string => { if (n >= 1_000_000_000_000) return `${(n / 1_000_000_000_000).toFixed(2)}T`; if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(2)}B`; if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(2)}k`; return String(n); }; export const formatTime = (seconds: number): string => { const secInMinute = 60; const secInHour = 60 * secInMinute; const secInDay = 24 * secInHour; const secInWeek = 7 * secInDay; const secInMonth = 30 * secInDay; const secInYear = 365 * secInDay; const y = Math.floor(seconds / secInYear); let rem = seconds % secInYear; const mo = Math.floor(rem / secInMonth); rem %= secInMonth; const w = Math.floor(rem / secInWeek); rem %= secInWeek; const d = Math.floor(rem / secInDay); rem %= secInDay; const h = Math.floor(rem / secInHour); rem %= secInHour; const mi = Math.floor(rem / secInMinute); const s = Math.floor(rem % secInMinute); const parts: string[] = []; if (y > 0) parts.push(`${y}y`); if (mo > 0) parts.push(`${mo}m`); if (w > 0) parts.push(`${w}w`); if (d > 0) parts.push(`${d}d`); const hasBigParts = parts.length > 0; const timeStr = hasBigParts || h > 0 ? `${h}:${String(mi).padStart(2, '0')}:${String(s).padStart(2, '0')}` : `${mi}:${String(s).padStart(2, '0')}`; parts.push(timeStr); return parts.join(' '); };