Some checks failed
Test and deploy staging / staging (push) Has been cancelled
Dutch and Korean (explained in English) and English (explained in Dutch) from A1 to C1, with a Hangul level for Korean. Lessons with notes, words and sentences generate multiple-choice, typing, sentence building, cloze, listening and matching exercises. Endless challenges: a seeded daily challenge, sprint, survival, mixed practice, level tests, spaced-repetition reviews and generated drills (numbers, clock times, de/het, plurals, Dutch and Korean conjugation, particles, Hangul reading, irregular verbs). Progress is stored in localStorage with export/import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
export type Rng = () => number;
|
|
|
|
/** Small deterministic generator, so a daily challenge is the same all day. */
|
|
export function seeded(seed: number | string): Rng {
|
|
let h = typeof seed === 'number' ? seed >>> 0 : 2166136261;
|
|
if (typeof seed === 'string') {
|
|
for (let i = 0; i < seed.length; i++) h = Math.imul(h ^ seed.charCodeAt(i), 16777619);
|
|
}
|
|
return () => {
|
|
h = (h + 0x6d2b79f5) >>> 0;
|
|
let t = h;
|
|
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
};
|
|
}
|
|
|
|
export const int = (rng: Rng, min: number, max: number) =>
|
|
min + Math.floor(rng() * (max - min + 1));
|
|
|
|
export const pick = <T>(rng: Rng, list: readonly T[]): T => list[Math.floor(rng() * list.length)];
|
|
|
|
export function shuffle<T>(rng: Rng, list: readonly T[]): T[] {
|
|
const copy = [...list];
|
|
for (let i = copy.length - 1; i > 0; i--) {
|
|
const j = Math.floor(rng() * (i + 1));
|
|
[copy[i], copy[j]] = [copy[j], copy[i]];
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
/** Up to `count` distinct values from `list` that differ from `exclude` once normalized. */
|
|
export function sample<T>(
|
|
rng: Rng,
|
|
list: readonly T[],
|
|
count: number,
|
|
key: (value: T) => string = String,
|
|
exclude: string[] = []
|
|
): T[] {
|
|
const seen = new Set(exclude.map((value) => value.toLowerCase()));
|
|
const out: T[] = [];
|
|
for (const value of shuffle(rng, list)) {
|
|
if (out.length >= count) break;
|
|
const k = key(value).toLowerCase();
|
|
if (seen.has(k)) continue;
|
|
seen.add(k);
|
|
out.push(value);
|
|
}
|
|
return out;
|
|
}
|