Code for Task #1 - Random Random Roger Roger!

In early, because it looks like I’m going to be stuck working on a plumbing repair the rest of the day. Thanks @scruffynerf for running this show – I’m already looking forward to next week’s installment!

Today, you will see elegant, compact and efficient code for this job. Then, there’s this. @jeff put the idea in my head - the only way to get rid of it was to write the code. It uses… yes, the center column from a cellular automaton running Wolfram’s Rule 30 to generate a stream of random bits, with which I control all the blinking lights. Without, further ado, here it is:

/*
  Rule 30 Flasher
  https://en.wikipedia.org/wiki/Rule_30 
  
  Flashes a random pixel for a random length of time, with a random amount of twinkling.
  
  Uses a 1D cellular automaton running Wolfram's rule 30 to generate a stream
  of random bits, which we use to build various random numbers.
  
  This pattern looks random, but for a given set of initial conditions, it's actually
  completely deterministic -- it'll be the same every time. 
*/

// arrays to hold the state of the current and previous generations of our
// automaton.
var ca_size = 32;                   // number of cells per generation
var ca_center = floor(ca_size / 2); // index center cell -- precalculates because we use it often

var g0 = array(ca_size);  
var g1 = array(ca_size);

// pointers to data arrays, so we can swap arrays by just swapping pointers
// instead of moving data.
var currState = g0;
var nextState = g1;  

var elapsedTime = 0;   // accumulator for timing pixel displays
var lifetime = 500;    // how long (ms) the current pixel will be displayed
var pix,hue,bri;       // pixel index, color and current brightness

// Get random fraction of specified length from rule 30 generator
function getRandomFraction(n) {
  return result = getRandomBits(n) >> n
}

// return the specified number of random bits into a 32-bit buffer
// (will discard oldest bits if n > 32)
function getRandomBits(n) {
  var result = 0;
  
  for (i = 0; i < n; i++) {
    calcNextGen();    
    result = result << 1;
    result |= currState[ca_center];
  }
  return result;
}

// set up the CA mechanics
function initialize() {

// set up pointers to our data array.
  currState = g0;
  nextState = g1;  

// seed the cell at the center of the array.  Bits in this
// column are reliably random, so it's where we'll get our
// random bits while running.
  currState[ca_center] = 1;
}

// Use the current state and Wolfram's Rule 30 to calculate the CA's next
// generation. Rule 30 generates very chaotic patterns, and the
// center column of the pattern is known to be a reasonably good RNG.
function calcNextGen() {
  var left,curr,right;

// set up 8-bit representation of current cell state. Note that "neighborhood"
// wraps around the ends of the array.
  for (var i = 0; i < ca_size; i++) {
    left = currState[(i > 0) ? i - 1 : ca_size - 1] << 2;
    curr = currState[i] << 1;
    right = currState[(i + 1) % ca_size];
    
// apply transformation rule
    nextState[i] = (30 & (1 << (left | curr | right))) > 0;
  }

// swap state arrays so the data for the generation we just calculated is in
// currState, which we will use to make random things happen.
  var a = currState; currState = nextState; nextState = a;  
}

// Main entry point - set up the CA before we start drawing pixels
initialize();

export function beforeRender(delta) {
  elapsedTime += delta;

// display timer -- accumulate deltas, generate new pixel when
// total time has reached or exceeded this pixel's random
// lifetime
  if (elapsedTime >= lifetime) {

// 8 bits will generate 256 distinct hues.     
    hue = getRandomFraction(8);    
    
// pixels will live for from 100 to 1200 ms   
    lifetime = 100+(1100*getRandomFraction(8));
    
// pick a random pixel. 11 bits will generate 2k unique indices. If your
// strip has more than 2k LEDs, not all pixels will be hit, but the indices
// will still be distributed over the whole strip.
    pix = floor(pixelCount * getRandomFraction(11)); 
    
// reset timer for the new pixel    
    elapsedTime = 0;  
  }
  
// calculate fadeout and twinkle, then adjust gamma.
// twinkle is controlled by grabbing a random bit from the CA on every frame
  bri = 1-((elapsedTime / lifetime)+(0.1 * getRandomBits(1)));
  bri = bri * bri;
}

// draw current pixel.Fadeout is a function of the random lifetime calculated
// in beforeRender, random twinkling is generated by varying saturation according to a bitstream
// from the CA 
export function render(index) {
  hsv(hue,1,(pix == index) * bri)
}
2 Likes