npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@spin-wheel/core

v1.2.1

Published

Readme

@spin-wheel/core

Pure-logic spinning wheel engine — deterministic RNG, weighted segment selection, and angle calculation. Zero DOM dependencies. Works in browsers, Node.js, and any JavaScript runtime.

Install

npm install @spin-wheel/core

Quick Start

import { WheelEngine } from '@spin-wheel/core';

const engine = new WheelEngine({
  segments: [
    { id: '1', label: 'Prize A', weight: 3 },
    { id: '2', label: 'Prize B', weight: 1 },
    { id: '3', label: 'Prize C', weight: 2 },
  ],
  seed: 'my-seed',  // optional — makes results deterministic
});

const result = engine.spin();

console.log(result.index);          // 0-based winning index
console.log(result.segment.label);  // e.g. "Prize A"
console.log(result.finalAngle);     // rotation angle in degrees

When to Use This Package

  • Headless / server-side — determine winners without a browser
  • Custom rendering — use your own renderer (Three.js, Pixi, etc.) with the engine
  • Unit testing — seed-based determinism makes outcomes predictable
  • Building higher-level abstractions — this is the foundation the other @spin-wheel/* packages build on

If you want a ready-made visual wheel, use @spin-wheel/widget instead.


API Reference

WheelEngine

The main class. Manages spin state, weighted selection, and angle computation.

Constructor

new WheelEngine(config: WheelEngineConfig)

| Config field | Type | Default | Description | | ------------ | ---------------- | ------------ | ----------------------------------------------------- | | segments | WheelSegment[] | required | At least one segment | | minSpins | number | 4 | Minimum full rotations | | maxSpins | number | 8 | Maximum full rotations | | seed | string | undefined | Seed for deterministic RNG. Omit for Math.random(). |

Throws if:

  • segments is empty
  • minSpins < 1
  • maxSpins < minSpins

engine.spin(): SpinResult

Determine the winning segment and compute the final angle. The result is known synchronously — no animation at this layer.

const result = engine.spin();

Returns a frozen SpinResult:

interface SpinResult {
  readonly index: number;         // 0-based winning segment index
  readonly segment: WheelSegment; // frozen copy of the winning segment
  readonly finalAngle: number;    // total rotation in degrees (≥ minSpins × 360)
}

Throws if the engine is currently in the 'spinning' state (call reset() first).

State transitions: idlespinningfinished

engine.getState(): WheelState

type WheelState = 'idle' | 'spinning' | 'finished';

Returns the current lifecycle state.

engine.getLastResult(): SpinResult | null

Retrieve the most recent spin result, or null if no spin has occurred.

engine.getSegments(): readonly WheelSegment[]

Returns a frozen copy of the current segments array.

engine.setSegments(segments: WheelSegment[]): void

Replace the segments. Resets the engine state to idle and clears the last result.

Throws if the array is empty.

engine.reset(): void

Reset the engine to idle state — clears lastResult so you can spin again.


Types

WheelSegment

interface WheelSegment {
  readonly id: string;        // unique identifier
  readonly label: string;     // display text
  readonly weight?: number;   // relative probability (default: 1)
  readonly data?: unknown;    // arbitrary payload
}

The weight field controls how likely a segment is to be selected. Higher weight = higher probability. Weights are relative — { weight: 3 } vs { weight: 1 } means 75% vs 25%.

WheelEngineConfig

interface WheelEngineConfig {
  readonly segments: readonly WheelSegment[];
  readonly minSpins?: number;   // default: 4
  readonly maxSpins?: number;   // default: 8
  readonly seed?: string;       // deterministic RNG seed
}

SpinResult

interface SpinResult {
  readonly index: number;
  readonly segment: Readonly<WheelSegment>;
  readonly finalAngle: number;
}

WheelState

type WheelState = 'idle' | 'spinning' | 'finished';

Utility Functions

These are also exported for advanced use cases.

createSeededRng(seed: string): () => number

Create a deterministic PRNG (mulberry32 algorithm). Returns a function that produces values in [0, 1). Same seed always produces the same sequence.

import { createSeededRng } from '@spin-wheel/core';

const rng = createSeededRng('my-seed');
console.log(rng()); // always 0.4413108...
console.log(rng()); // always 0.6529685...

pickWeightedIndex(segments: WheelSegment[], rng: () => number): number

Select a segment index using weighted random selection. Uses each segment's weight field (defaults to 1).

import { pickWeightedIndex, createSeededRng } from '@spin-wheel/core';

const segments = [
  { id: '1', label: 'Common', weight: 5 },
  { id: '2', label: 'Rare',   weight: 1 },
];
const rng = createSeededRng('test');
const idx = pickWeightedIndex(segments, rng); // 0 or 1

Throws on empty segments, negative weights, or zero total weight.

calculateFinalAngle(index, count, extraSpins, rng): number

Compute the final rotation angle in degrees for a given winning segment.

import { calculateFinalAngle, createSeededRng } from '@spin-wheel/core';

const angle = calculateFinalAngle(
  2,                       // winning index
  5,                       // total segments
  6,                       // extra full rotations
  createSeededRng('seed'), // RNG
);
// Returns a positive angle ≥ 6 × 360

The pointer is at the top (0°). A 10% edge padding ensures the landing never sits right at a segment boundary.

easeOutCubic(t: number): number

Cubic ease-out function: 1 - (1 - t)³. Input and output in [0, 1].


Deterministic Mode

Pass a seed to make spins fully reproducible:

const engine1 = new WheelEngine({ segments, seed: 'abc' });
const engine2 = new WheelEngine({ segments, seed: 'abc' });

const r1 = engine1.spin();
const r2 = engine2.spin();

console.log(r1.index === r2.index);           // true
console.log(r1.finalAngle === r2.finalAngle); // true

This is useful for:

  • Server-side validation — verify a client-claimed result
  • Replays — reproduce exact spin sequences
  • Testing — predictable outcomes in unit tests

License

MIT