@jeremyckahn/farmhand
Version:
A farming game
79 lines (65 loc) • 2.46 kB
text/typescript
import seedrandom from 'seedrandom'
import globalWindow from 'global/window.js'
import { randomStream } from '../../enums.js'
export class RandomNumberService {
seededRandom: (() => number) | null = null
/**
* Values queued per stream name that are returned, in order, before any
* seeded or Math.random value. This lets tests force a specific outcome
* (e.g. rain tonight) without searching for a seed that happens to produce
* it. Exposed via the window.farmhand debug hook.
*/
queuedNumbers: Map<randomStream, number[]> = new Map()
constructor() {
// The availability of window.location needs to be checked before accessing
// its .search property. This code runs in both a browser and Node.js
// context, and window.location is not defined in Node.js environments.
const initialSeed = new URLSearchParams(globalWindow.location?.search).get(
'seed'
)
if (initialSeed) {
this.seedRandomNumber(initialSeed)
}
}
seedRandomNumber(seed: string) {
this.seededRandom = seedrandom(seed)
}
/**
* @param stream Optional name of the random number stream being drawn
* from, which lets queueRandomNumbers target it. Values that aren't queued
* come from the same seeded (or Math.random) sequence regardless of stream.
*/
generateRandomNumber(stream?: randomStream): number {
if (stream !== undefined) {
const queuedNumber = this.queuedNumbers.get(stream)?.shift()
if (queuedNumber !== undefined) {
return queuedNumber
}
}
return this.seededRandom ? this.seededRandom() : Math.random()
}
/**
* @param stream Name of the random number stream to queue values for.
* @param numbers Values to return from the stream, in order, before it
* resumes producing random numbers.
*/
queueRandomNumbers(stream: randomStream, numbers: number[]) {
this.queuedNumbers.set(stream, [
...(this.queuedNumbers.get(stream) ?? []),
...numbers,
])
}
unseedRandomNumber() {
this.seededRandom = null
}
/**
* Compares given number against a randomly generated number.
* @param chance Float between 0-1 to compare dice roll against.
* @returns True if the dice roll was equal to or lower than the
given chance, false otherwise.
*/
isRandomNumberLessThan(chance: number): boolean {
return this.generateRandomNumber() <= chance
}
}
export const randomNumberService = new RandomNumberService()