/**
* Детерминированный RNG (mulberry32): одинаковый seed — одинаковая последовательность.
* Используется для генерации мира, лута, частиц — везде, где нужен повторяемый «случай».
*/
export interface Rng {
/** Следующее число 0..1. */
next(): number;
/** Целое в диапазоне [min, max] включительно. */
int(min: number, max: number): number;
/** Случайный элемент массива (массив должен быть непустым). */
pick<T>(arr: readonly T[]): T;
/** true с вероятностью p (0..1). */
chance(p: number): boolean;
/** Текущее состояние — можно сохранить и восстановить для продолжения последовательности. */
state(): number;
}
/** Создать RNG из 32-битного seed. */
export function createRng(seed: number): Rng {
let s = seed >>> 0;
const next = (): number => {
s = (s + 0x6d2b79f5) >>> 0;
let t = s;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
return {
next,
int: (min, max) => min + Math.floor(next() * (max - min + 1)),
pick: <T,>(arr: readonly T[]) => {
if (arr.length === 0) throw new Error('pick из пустого массива');
return arr[Math.floor(next() * arr.length)];
},
chance: (p) => next() < p,
state: () => s
};
}
/**
* Детерминированный хэш координат тайла без состояния: одинаковые (x, y, seed)
* — одинаковый результат навсегда. В отличие от createRng не потребляет
* последовательность и не зависит от порядка вызовов — годится для выбора
* варианта текстуры по тайлу (hashTile(x, y, id) % варианты.length).
*/
export function hashTile(x: number, y: number, seed = 0): number {
let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 2654435761)) >>> 0;
h = (h ^ (h >>> 13)) >>> 0;
h = Math.imul(h, 1274126177) >>> 0;
return (h ^ (h >>> 16)) >>> 0;
}