arcade2d
Class

Random

utils/random.ts:115

A seedable pseudo-random number generator and a grab-bag of common game-development helpers built on top of it.

Random is a thin, dependency-free wrapper around mulberry32, a small, fast 32-bit PRNG with a 2³² period — more than adequate for gameplay use (particle jitter, loot rolls, spawn placement, deterministic replays, etc.). It is not cryptographically secure; do not use it where an attacker controls or observes outputs.

Seeding

Construct with new Random(options). Passing no seed produces a time-based seed; passing a seed (number or string) produces a fully deterministic stream — two generators built with the same seed emit the same sequence forever. String seeds are hashed so named seeds like 'daily-2026-05-20' work as expected.

Determinism and persistence

Use Random.getState / Random.setState (or new Random({ state })) to snapshot and restore the exact internal state. This is what powers deterministic replays — capture the state at the start of a frame, capture the inputs, and re-running both reproduces the frame exactly.

Sampling helpers

The sampling helpers (Random.inRectangle, Random.inCircle, Random.inRing, Random.inPolygon) return frozen PointPrimitives in absolute coordinates — pass the world-space center or origin of the area you want to sample within. They produce uniformly distributed points (not biased toward the center, which a naive polar sampler would do).

Example

const rng = new Random({ seed: 'level-3' });

rng.between(0, 10);              // float in [0, 10)
rng.integer(1, 6);               // integer in [1, 6] inclusive (a d6 roll)
rng.boolean(0.25);               // 25% chance of true
rng.pick(['rock', 'paper']);     // one of the items, uniformly
rng.inCircle(100, 100, 50);      // uniform point in a 50-radius disc
rng.color();                     // random 24-bit color
const a = new Random({ seed: 42 });
a.next(); a.next();

const b = new Random({ state: a.getState() });
b.next() === a.next(); // true — same state, same next value

Constructors

#
constructor(options: RandomOptions): Random

Creates a new generator.

Parameters

Properties

readonly #
seed: number

The 32-bit unsigned integer seed this generator was constructed with. Stable for the lifetime of the instance — even after the internal state has advanced — so it can be logged or echoed back in a save file to reproduce a run.

When the generator was constructed with state rather than seed, the seed is the value of that initial state.

Methods

#
angle(): number

Returns a uniformly distributed angle in radians, in the range [0, 2π). Pair with Math.cos / Math.sin for a random unit vector.

Returns

number
#
between(min: number, max: number): number

Returns a uniformly distributed float in the range [min, max). If min is greater than max the arguments are swapped, so the caller does not need to pre-sort them. When min === max the result is exactly min.

Parameters

  • min number
  • max number

Returns

number
#
boolean(probability: number): boolean

Returns true with the given probability, false otherwise. The default probability of 0.5 makes the call read as a fair coin flip.

Probabilities <= 0 always return false; probabilities >= 1 always return true. The PRNG is still advanced in those cases, keeping the stream deterministic regardless of the threshold.

Parameters

  • probability number

Returns

boolean
#
color(min: number, max: number): number

Returns a 24-bit color as a number in the range [min, max], suitable for passing to PIXI tinting / fill APIs. The default range covers the full RGB space (0x000000 to 0xffffff).

Bounds outside [0x000000, 0xffffff] are clamped, and a min greater than max is swapped. Note this samples integer colors uniformly over the numeric range — it does not perform any perceptually uniform sampling in HSL/LAB space.

Parameters

  • min number
  • max number

Returns

number
#
getState(): number

Captures the current internal state of this generator as a 32-bit unsigned integer. Pass the result back via new Random({ state }) (or Random.setState) to recreate a generator that resumes the stream from exactly this point.

Returns

number
#
hexString(length: number): string

Returns a string of random lowercase hexadecimal characters of the given length. Useful for ad-hoc ids, mock asset hashes, debug labels, and so on. Each character is drawn independently from 0-9a-f.

Non-positive or non-integer lengths produce an empty string.

Parameters

  • length number

Returns

string
#
inCircle(x: number, y: number, radius: number): Readonly

Returns a frozen point uniformly distributed within the closed disc centred at (x, y) with the given radius. Uses the r = R * sqrt(u) mapping to avoid the bias toward the center that a naive (r, θ) sampler produces.

A radius of 0 (or negative, which is treated as its absolute value) returns the center point.

Parameters

  • x number
  • y number
  • radius number

Returns

Readonly
#
inFrame(x: number, y: number, width: number, height: number, thickness: number): Readonly

Returns a frozen point uniformly distributed within the rectangular frame described by an outer rectangle of width × height anchored at (x, y) (its top-left, screen-space) and a uniform inset thickness. This is the rectangle analog of Random.inRing: sampling lands anywhere in the band between the outer rectangle and the inner rectangle carved out by the inset, never inside the inner hole.

Sampling is exact (not rejection-based): the four border strips are weighted by area and one is picked, then the point is drawn uniformly within it. Cost is constant regardless of how thin the frame is.

Edge cases:

  • When thickness * 2 >= min(width, height) the frame fills the entire outer rectangle (no interior hole left), and the result is equivalent to Random.inRectangle.
  • When thickness <= 0 the frame has zero area; the call returns the outer rectangle's top-left corner (x, y) without consuming a draw from the underlying PRNG.
  • Negative width or height are treated as their absolute value, with (x, y) remaining the top-left corner of the resulting outer box.

Parameters

  • x number
  • y number
  • width number
  • height number
  • thickness number

Returns

Readonly
#
inPolygon(vertices: readonly PointPrimitive[]): Readonly

Returns a frozen point uniformly distributed inside the given simple (non-self-intersecting) polygon, expressed by its vertices in absolute coordinates. Uses bounding-box rejection sampling backed by Polygon.containsPoint: simple and correct for any shape, but cost grows with how much of the bounding box the polygon doesn't fill.

For pathological inputs (a polygon with effectively zero area, or extreme aspect ratio that defeats rejection sampling), the method gives up after a bounded number of attempts and falls back to the polygon's area centroid so callers still get a sensible point inside the shape.

Polygons with fewer than three vertices are degenerate; the first vertex (or (0, 0) for an empty array) is returned instead.

Parameters

Returns

Readonly
#
inRectangle(x: number, y: number, width: number, height: number): Readonly

Returns a frozen point uniformly distributed within the axis-aligned rectangle described by the given origin and size. The origin is the rectangle's top-left corner in screen-space (y increases downward).

Negative width or height are treated as their absolute value, with the origin still the top-left corner of the resulting box.

Parameters

  • x number
  • y number
  • width number
  • height number

Returns

Readonly
#
inRing(x: number, y: number, minRadius: number, maxRadius: number): Readonly

Returns a frozen point uniformly distributed within the annulus (ring) centred at (x, y), bounded by minRadius and maxRadius. Uses the r = sqrt(u * (R² - r²) + r²) mapping so points are uniform over the ring's area, not its radius.

If minRadius is greater than maxRadius the arguments are swapped. Negative radii are treated as their absolute value.

Parameters

  • x number
  • y number
  • minRadius number
  • maxRadius number

Returns

Readonly
#
integer(min: number, max: number): number

Returns a uniformly distributed integer in the range [min, max], both ends inclusive — the conventional "roll a d6" semantics. Non-integer bounds are floored toward zero; if min is greater than max the arguments are swapped.

Parameters

  • min number
  • max number

Returns

number
#
pick(items: readonly T[]): T

Picks one element from items uniformly at random.

Parameters

  • items readonly T[]

Returns

T

The chosen element.

Throws

An EngineError with code ErrorCode.RANDOM_EMPTY_ITEMS when items is empty — picking from nothing is always a programming error.

#
setState(state: number): this

Restores this generator's internal state to a value previously captured by Random.getState. Non-finite inputs are ignored.

Parameters

  • state number

Returns

this

This generator, for chaining.

#
shuffle(items: T[]): T[]

Shuffles items in place using a Fisher–Yates pass and returns the same array, so the call can be chained or its result captured inline. An empty or single-element array is returned untouched.

Parameters

  • items T[]

Returns

T[]

The same array reference, now shuffled.

#
sign(): -1 | 1

Returns either -1 or 1 with equal probability. Useful for randomising a velocity direction or flipping a sprite.

Returns

-1 | 1
ESC