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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@paperscissors/unique-random-generator

v0.1.0

Published

A perceptual randomizer that makes random sequences feel more random by preventing recent repetitions

Readme

UniqueRandomGenerator

npm version npm downloads License: MIT

A perceptual randomizer that makes random sequences feel more random by preventing recent repetitions.

True randomness can feel clustered and repetitive. This utility generates cryptographically random numbers while maintaining a buffer of recent values, ensuring a more natural distribution that humans perceive as "truly random."

Why?

Pure random number generators can produce jarring repetitions—the same card twice in a row, back-to-back sound effects, or repeated quiz questions. While mathematically random, these patterns feel wrong to users. UniqueRandomGenerator solves this by tracking recently generated values and avoiding them, creating sequences that are both random and perceptually smooth.

Features

  • 🎲 Cryptographically secure random generation using Web Crypto API
  • 🔄 Configurable history buffer to prevent recent repetitions
  • ⛓️ Chainable API for clean, readable code
  • 🎯 Perfect for games, quizzes, playlists, and UI variety
  • 🪶 Zero dependencies, TypeScript-native

Quick Start

import { UniqueRandomGenerator } from '@paperscissors/unique-random-generator';

const gen = new UniqueRandomGenerator()
  .setMinMax(1, 10)
  .setBufferSize(3); // Remember last 3 values

const number = gen.getRandomIntegerInRange(); // Never repeats the last 3 numbers

Installation

npm install @paperscissors/unique-random-generator

Or with yarn:

yarn add @paperscissors/unique-random-generator

Or with pnpm:

pnpm add @paperscissors/unique-random-generator

Usage Examples

Basic Usage

import { UniqueRandomGenerator } from '@paperscissors/unique-random-generator';

// Create a generator for dice rolls that doesn't repeat the last 2 rolls
const dice = new UniqueRandomGenerator()
  .setMinMax(1, 6)
  .setBufferSize(2);

console.log(dice.getRandomIntegerInRange()); // e.g., 4
console.log(dice.getRandomIntegerInRange()); // e.g., 2 (not 4)
console.log(dice.getRandomIntegerInRange()); // e.g., 6 (not 2)

Shuffled Deck

function createShuffledDeck(numCards: number = 52) {
  const generator = new UniqueRandomGenerator()
    .setMinMax(0, numCards - 1)
    .setBufferSize(numCards); // Remember all cards for complete shuffle

  return Array.from({ length: numCards }, () =>
    generator.getRandomIntegerInRange()
  );
}

const deck = createShuffledDeck();
console.log(deck); // All cards, shuffled, no repeats

Sound Effect Randomizer

class SoundEffectRandomizer {
  private generator: UniqueRandomGenerator;
  private sounds: string[];

  constructor(sounds: string[]) {
    this.sounds = sounds;
    this.generator = new UniqueRandomGenerator()
      .setMinMax(0, sounds.length - 1)
      .setBufferSize(Math.min(3, sounds.length - 1)); // Avoid last 3 sounds
  }

  play() {
    const index = this.generator.getRandomIntegerInRange();
    const sound = this.sounds[index];
    console.log(`Playing: ${sound}`);
    return sound;
  }
}

const footsteps = new SoundEffectRandomizer([
  'step1.mp3', 'step2.mp3', 'step3.mp3', 'step4.mp3'
]);

// Natural-sounding footsteps without jarring repeats
for (let i = 0; i < 10; i++) {
  footsteps.play();
}

Quiz Question Selector

interface Question {
  id: number;
  text: string;
  difficulty: 'easy' | 'medium' | 'hard';
}

class QuizSelector {
  private generator: UniqueRandomGenerator;
  private questions: Question[];

  constructor(questions: Question[]) {
    this.questions = questions;
    this.generator = new UniqueRandomGenerator()
      .setMinMax(0, questions.length - 1)
      .setBufferSize(Math.floor(questions.length * 0.75)); // Remember 75% of questions
  }

  getNextQuestion(): Question {
    const index = this.generator.getRandomIntegerInRange();
    return this.questions[index];
  }
}

API Reference

Constructor

new UniqueRandomGenerator()

Creates a new instance with default settings.

Methods

setMinMax(min: number, max: number): this

Sets the range (inclusive) for random number generation.

generator.setMinMax(1, 100); // Generate numbers from 1 to 100

setMin(min: number): this

Sets only the minimum value.

generator.setMin(0);

setMax(max: number): this

Sets only the maximum value.

generator.setMax(99);

setBufferSize(size: number): this

Sets how many recent values to remember and avoid.

generator.setBufferSize(5); // Avoid last 5 generated numbers

getRandomIntegerInRange(min?: number, max?: number): number

Generates a random integer that hasn't been recently generated.

// Use configured range
const num = generator.getRandomIntegerInRange();

// Override range for this call only
const num2 = generator.getRandomIntegerInRange(1, 10);

How It Works

  1. Cryptographic Randomness: Uses Web Crypto API (crypto.getRandomValues()) for secure random generation, compatible with Node.js 18+ and all modern browsers
  2. Buffer Tracking: Maintains an internal array of recently generated values
  3. Smart Avoidance: Regenerates numbers that exist in the buffer until finding a fresh value
  4. Dynamic Adaptation: Buffer size can be auto-adjusted based on range when not explicitly set

Development

Setup

# Install dependencies
pnpm install

# Run tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Run tests with coverage
pnpm test:coverage

# Build the package
pnpm build

# Build in watch mode
pnpm dev

# Lint code
pnpm lint

# Format code
pnpm format

# Type check
pnpm typecheck

Project Structure

unique-random-generator/
├── src/
│   ├── index.ts                    # Main entry point with exports
│   └── UniqueRandomGenerator.ts    # Core class implementation
├── tests/
│   └── UniqueRandomGenerator.test.ts # Test suite
├── dist/                           # Build output (generated)
├── tsconfig.json                   # TypeScript configuration
├── tsup.config.ts                  # Build configuration
├── vitest.config.ts                # Test configuration
└── package.json                    # Package metadata

Publishing

Before publishing to npm:

# This runs automatically via prepublishOnly
pnpm run lint && pnpm run typecheck && pnpm run test && pnpm run build

# Publish to npm (requires authentication)
npm publish --access public

Best Practices

Buffer Size Guidelines

  • Strict avoidance: Set buffer size to 50-75% of your range
  • Natural feeling: Set buffer size to 20-30% of your range
  • Complete shuffle: Set buffer size equal to your range
  • Minimal repetition: Set buffer size to 1-3 for small ranges

When to Use

Good for:

  • UI element selection (backgrounds, colors, animations)
  • Game mechanics (levels, enemies, power-ups)
  • Audio (music playlists, sound effects)
  • Content delivery (quiz questions, flashcards)
  • Any scenario where perceived randomness matters

Not ideal for:

  • Cryptographic applications (use pure crypto.getRandomValues)
  • Statistical sampling (use unbiased random)
  • Security-critical randomness (predictability is introduced by the buffer)

Performance Notes

  • Efficient for typical ranges (hundreds to thousands of values)
  • Buffer checking is O(n) where n is buffer size
  • For very large ranges with large buffers, consider pre-generating values
  • For extreme non-repetition needs, consider Fisher-Yates shuffle instead

The Philosophy

When used correctly, this increases the perception of randomness by aligning random generation with human expectations of variety. True randomness includes clusters and repetitions that feel "broken" to users. By preventing recent repetitions, we create sequences that are both mathematically random and experientially satisfying.

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.


Made with ❤️ for better user experiences