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 🙏

© 2024 – Pkg Stats / Ryan Hefner

procedural

v0.1.3

Published

Library for defining procedural functions.

Downloads

7

Readme

procedural

Library for defining procedural functions in a hierarchy to allow for complex procedurally generated content.

Introduction

This library aims to make it easy to create procedurally generated content, which usually means semi-random content that follows a complex set of rules. One great example of procedural generated content is the world in Minecraft. It's random for everyone who starts the game, but follows certain rules that together make a recognizable environment (hills, trees, caves, villages, ...)

You use this library by defining procedural functions. A procedural function may take any number of parameters, and can provide any number of values. Additionally, it may generate other procedural functions that depend on it, as well as its values.

Each instance of a procedural function (which you get by calling the function) has a unique hash which is used when calling any of the built-in pseudo-random number generators. This means you will always get the same sequence of random numbers for the same set of parameters, which is the most important aspect of procedurally generated content.

Here's how you define a procedural function that takes a single parameter:

var procedural = require('procedural');
var world = procedural('world').takes('name');

You may want a world to define if it's habitable:

world.provides('habitable', function (world) {
  // For now, all worlds will be habitable.
  return true;
});

You can now create your world instance like this:

var earth = world('Earth');
console.log('Does', earth.name, 'support life?');
console.log(earth.habitable ? 'Yes' : 'No');

Now it makes sense for a region in a world to be accessible...

var region = world.generates('region')
  .takes('x', 'y')
  .provides('temperature', function (region) {
    // Get a random number generator for this region.
    var rnd = region.getRandGen();

    // Make temperature depend on what kind of world we're on.
    if (region.world.habitable) {
      return rnd.nextInt(10, 35);
    } else {
      return rnd.nextInt(1000, 3500);
    }
  });

Let's find out what temperature we've got at (0, 0).

console.log('Temperature is:', earth.region(0, 0).temperature);

Note that generated random numbers will be different for different parameters:

var kryptonTemp = world('Krypton').region(0, 0).temperature;
console.log('But on Krypton it is:', kryptonTemp);

But wait, Krypton isn't habitable! Let's fix that by revisiting the habitable value of worlds.

world.provides('habitable', function (world) {
  // Only Earth is known to be habitable (for now...).
  return world.name == 'Earth';
});

If you rerun all the code together now, you'll see that Krypton will be a bit hotter than before, while Earth is still comfortable.

I hope this example shows how useful it is to have a set of procedurally generated values that depend on each other when you want to create random content that still follows a set of rules.

For another example, see the bottom of this README, or go check out one of these demos:

  • TODO: The space demo.

API

TODO: Define the API here.

Full example

How to define a procedurally generated avatar.

var procedural = require('procedural');

var avatar = procedural('avatar')
  // The block size is just visual, so it shouldn't affect randomization.
  .doNotHash('blockSize')
  // The username is needed to create a unique avatar for every user.
  .takes('username')
  // Size, in blocks. Different sizes will create different avatars.
  .takes('size', function validate(avatar, blocks) {
    // Ensure that size is a positive integer divisible by 2.
    return typeof blocks == 'number' && blocks > 0 && !(blocks % 2);
  })
  // The pixel size of a single (square) block.
  .takes('blockSize', function validate(avatar, px) {
    return typeof px == 'number' && px > 0;
  })
  // Calculate the colors that make up the avatar.
  .provides('hueAngle', function (avatar) {
    // Use a named number generator to get an independent sequence.
    return avatar.getRandGen('color').nextInt(360);
  })
  .provides('background', function (avatar) {
    return 'hsl(' + avatar.hueAngle + ', 100%, 50%)';
  })
  .provides('foreground', function (avatar) {
    var hueAngle = (avatar.hueAngle + 180) % 360;
    return 'hsl(' + hueAngle + ', 100%, 50%)';
  })
  // 75% of avatars have a mirrored effect, others don't.
  .provides('isMirrored', function (avatar) {
    return avatar.getRandGen('mirror').nextFloat() > .25;
  })
  // A particular avatar has a unique set of blocks.
  .generates('block')
    // The validator will run independently for both parameters.
    .takes('x', 'y', function validate(block, xy) {
      // We can refer to the parent instance (the avatar).
      return typeof xy == 'number' && xy >= 0 && xy < block.avatar.size;
    })
    // The color of this block.
    .provides('color', function (block) {
      // You don't have to use named random generators.
      if (block.getRandGen().nextFloat() > .5) {
        return block.avatar.foreground;
      } else {
        return block.avatar.background;
      }
    })
    // Go back to defining the parent (avatar).
    .done()
  // Renders to a canvas and returns a URL for <img>.
  .provides('url', function (avatar) {
    var canvas = document.createElement('canvas'),
        context = canvas.getContext('2d');

    canvas.width = avatar.size * avatar.blockSize;
    canvas.height = avatar.size * avatar.blockSize;

    context.fillStyle = avatar.background;
    context.fillRect(0, 0, avatar.size, avatar.size);

    var finalX = avatar.isMirrored ? avatar.size / 2 : avatar.size,
        blockSize = avatar.blockSize;

    for (var y = 0; y < avatar.size; y++) {
      for (var x = 0; x < finalX; x++) {
        var realX = x * blockSize, realY = y * blockSize;

        var block = avatar.block(x, y);
        context.fillStyle = block.color;
        context.fillRect(realX, realY, blockSize, blockSize);

        if (avatar.isMirrored) {
          var mirroredX = avatar.size * blockSize - realX - blockSize;
          context.fillRect(mirroredX, realY, blockSize, blockSize);
        }
      }
    }

    return canvas.toDataURL();
  });

var img = document.createElement('img');
img.src = avatar('bob', 16, 4).url;
document.body.appendChild(img);