yurandom
Version:
Deterministic seed-based random generator using Xoroshiro128+. Useful for avatars, testing, procedural content, and more.
135 lines (116 loc) • 3.23 kB
JavaScript
export class Yurandom {
constructor(seed) {
const [h1, h2] = this.hashSeed(seed);
this.s0 = h1;
this.s1 = h2;
}
hashSeed(seed) {
let h1 = BigInt(0xdeadbeef);
let h2 = BigInt(0x41c6ce57);
for (let i = 0; i < seed.length; i++) {
const ch = BigInt(seed.charCodeAt(i));
h1 = BigInt.asUintN(64, h1 ^ ((h1 << 5n) + ch + (h1 >> 2n)));
h2 = BigInt.asUintN(64, h2 ^ ((h2 << 7n) + ch + (h2 >> 3n)));
}
return [h1, h2];
}
next() {
let s0 = this.s0;
let s1 = this.s1;
const result =
Number(BigInt.asUintN(64, s0 + s1)) / Number(0x1_0000_0000_0000_0000n);
s1 ^= s0;
this.s0 = BigInt.asUintN(64, this.rotl(s0, 55n) ^ s1 ^ (s1 << 14n));
this.s1 = BigInt.asUintN(64, this.rotl(s1, 36n));
return result;
}
rotl(x, k) {
return ((x << k) | (x >> (64n - k))) & 0xffffffffffffffffn;
}
random() {
return this.next();
}
int(min, max) {
return Math.floor(this.next() * (max - min + 1)) + min;
}
bool() {
return this.next() < 0.5;
}
pick(arr) {
return arr[Math.floor(this.next() * arr.length)];
}
shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(this.next() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
pastel() {
const h = Math.floor(this.next() * 360);
return `hsl(${h}, 70%, 85%)`;
}
range(count, min, max) {
const result = [];
for (let i = 0; i < count; i++) {
result.push(this.int(min, max));
}
return result;
}
uuid() {
const hex = () => this.hex(2);
return `${hex()}${hex()}-${hex()}-${hex()}-${hex()}-${hex()}${hex()}${hex()}`;
}
date(start, end) {
const t = start.getTime() + this.next() * (end.getTime() - start.getTime());
return new Date(Math.floor(t));
}
weighted(items) {
const total = items.reduce((sum, [, w]) => sum + w, 0);
let r = this.next() * total;
for (const [item, weight] of items) {
if (r < weight) return item;
r -= weight;
}
return items[items.length - 1][0];
}
string(
length,
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
) {
let result = "";
for (let i = 0; i < length; i++) {
const idx = Math.floor(this.next() * charset.length);
result += charset[idx];
}
return result;
}
hex(bytes) {
let str = "";
for (let i = 0; i < bytes; i++) {
const byte = this.int(0, 255);
str += byte.toString(16).padStart(2, "0");
}
return str;
}
color(format = "hex") {
const r = this.int(0, 255);
const g = this.int(0, 255);
const b = this.int(0, 255);
switch (format) {
case "hex":
return `#${[r, g, b]
.map((c) => c.toString(16).padStart(2, "0"))
.join("")}`;
case "rgb":
return `rgb(${r}, ${g}, ${b})`;
case "hsl": {
const h = this.int(0, 360);
const s = this.int(60, 90);
const l = this.int(50, 85);
return `hsl(${h}, ${s}%, ${l}%)`;
}
}
}
}