xxxxxxxxxx
373
// Computation in Design
// Cellular Automata Cycle D
// Andreas Schlegel, 2021.
//
//
// This sketch is adapted from the Processing's
// Stephen Wolfram exmaple at
// https://processing.org/examples/wolfram.html
//
// and here a list of cellular automata outputs
// based on their rule:
// https://mathworld.wolfram.com/ElementaryCellularAutomaton.html
//
// at the same time you will encounter the following
// in the code below concepts:
// - Arrays
// - Decimal and binary numbers
// - Pattern generation
// - Algorithm
//
// Cellular Automatas or CAs are computational models that are
// typically represented by a grid with values (cells).
// A cell is a particular location on a grid with a value,
// like a cell on a spreadsheet you’d see in Microsoft Excel.
// Each cell in the grid evolves based on its neighbours and
// some rule. These rules are defined by a sequence of 0s and 1s,
// the ruleset. Based on that ruleset, a cell can either become
// 0 (inactive) or 1 (alive).
//
// a Cellular Automata rule is a 8-bit number (0-255)
// here each number can be represented by a sequence
// of eight 0s or 1s hence 8-bit. in the code below
// a rule or a ruleset is defined by an array with
// eight elements eg. [0,0,0,0,0,0,0,1] represents
// number 1. Notice that we start setting bits from
// the back.
// I know this is neither easy nor quick to digest,
// but check out this tutorial:
// https://medium.com/@LindaVivah/learn-how-to-read-binary-in-5-minutes-dac1feb991e
//
// if you have read and followed the above tutorial,
// you will be able to see why rule 90 can be represented
// in binary as
// see binary to decimal converter
// https://www.binaryhexconverter.com/binary-to-decimal-converter
// (scroll down to see binary-to-decimal table for numbers 0-255)
//
// and decimal to binary converter
// https://www.binaryhexconverter.com/decimal-to-binary-converter
//
//
// Lets get to work.
// Inside the code below, look for @make-changes-here
// to make changes.
///////////////////////////////////////////////////
// Start of code //////////////////////////////////
///////////////////////////////////////////////////
//
// An instance object to describe the
// Wolfram basic Cellular Automata
let ca;
let ruleset;
let bg, caBg, caFg;
function setup() {
createCanvas(600, 600);
// An initial rule system
// a rule-system here consists of an array filled
// with 8 numbers. arrays are essential components
// of a computer program that can store more than
// 1 value (like a variable does) but a sequence
// of values.
//
// @make-changes-here
// try to apply a different ruleset here.
// read the comments above for more details about
// the different rulesets and how they are defined.
// try rule 90 [0,1,0,1,1,0,1,0]
// or maybe rule 149 [1,0,0,1,0,1,0,1]
ruleset = [1,0,0,1,0,1,0,1]; // this is rule 193
//
// or use the next line instead
// ruleset = decimalToBinaryArray(183);
// @make-changes-here
// the colors for the CA and background
bg = color(209, 242, 0);
caBg = color(0, 149, 199);
caFg = color(217, 24, 165);
// after we have set the initial ruleset, then
// initialize CellularAutomata
ca = new CellularAutomata(ruleset);
background(bg);
noStroke();
}
function draw() {
// Draw the CA
ca.render();
// Generate the next level (the next line)
ca.generate();
// If we're done, clear the screen,
// (pick a new ruleset and) restart
// or take any other action here.
if (ca.finished()) {
// @make-changes-here
// (un)comment the 2 code lines below to randomly
// generate a new ruleset when the current
// ruleset has reached the bottom of
// the canvas
//
ca.randomize();
ca.restart();
// @make-changes-here
// (un)comment line below to override canvas with bg-color
//background(bg);
}
// draw the small little rule-label in the
// top left corner
fill(0)
rect(10, 10, 80, 30);
fill(255);
text("Rule: " + ca.currentRuleDecimal, 20, 29);
}
function keyPressed() {
switch (keyCode) {
case (UP_ARROW):
nextRule(10);
break;
case (DOWN_ARROW):
nextRule(-10);
break;
case (LEFT_ARROW):
nextRule(-1);
break;
case (RIGHT_ARROW):
nextRule(1);
break;
case (32): // Space-bar
nextRule(0); // clear the canvas
break;
}
}
function nextRule(theNum) {
background(0);
ca.next(theNum);
ca.restart();
}
class CellularAutomata {
constructor(theRuleSet) {
this.currentRuleSet = theRuleSet;
this.currentRuleDecimal = binaryArrayToDecimal(this.currentRuleSet);
// @make-changes-here
// change the size of a single cell-pixel
this.scl = 10;
this.cells = new Array(int(width / this.scl));
this.restart();
}
///////////////////////////////////////////////
// Set the rules of the CA
setRuleSet(theRuleSet) {
this.currentRuleSet = theRuleSet;
}
///////////////////////////////////////////////
// Make a random ruleset
randomize() {
for (let i = 0; i < 8; i++) {
this.currentRuleSet[i] = int(random(2));
}
this.currentRuleDecimal = binaryArrayToDecimal(this.currentRuleSet);
console.log("this is rule:", this.currentRuleDecimal, this.currentRuleSet);
}
///////////////////////////////////////////////
// select the next rule
next(theNum = 1) {
this.currentRuleDecimal += theNum;
if (this.currentRuleDecimal < 0) {
this.currentRuleDecimal = 255;
} else if (this.currentRuleDecimal > 255) {
this.currentRuleDecimal = 0;
}
this.currentRuleSet = decimalToBinaryArray(this.currentRuleDecimal);
}
///////////////////////////////////////////////
// Reset to generation 0
restart() {
for (let i = 0; i < this.cells.length; i++) {
this.cells[i] = 0;
}
// We arbitrarily start with just the
// middle cell having a state of "1"
this.cells[this.cells.length / 2] = 1;
this.generation = 0;
}
///////////////////////////////////////////////
// The process of creating
// the new generation
generate() {
// First we create an empty array
// for the new values
let nextgen = [];
// For every spot, determine new state by
// examing current state, and neighbor states
// Ignore edges that only have one neighor
for (let i = 1; i < this.cells.length - 1; i++) {
// Left neighbor state
let left = this.cells[i - 1];
// Current state
let me = this.cells[i];
// Right neighbor state
let right = this.cells[i + 1];
// @make-changes-here
// mutations:
// copy and replace code for variables left, me or right
// after the = sign with the following
// (of course the modify and play with number)
//
// random()<0.9 ? 0:1; //
// i%19 == 0 ? 1:this.cells[i + 1]; //
// Compute next generation state based on ruleset
nextgen[i] = this.executeRuleSet(left, me, right);
}
// Copy the array into current value
for (let i = 1; i < this.cells.length - 1; i += 1) {
this.cells[i] = nextgen[i];
}
// @make-changes-here
// change value after = sign
this.generation += 1;
}
///////////////////////////////////////////////
// This is the easy part, just draw
// the cells, fill 255 for '1', fill 0 for '0'
//
// @make-changes-here
// change the shapes or colors that will be
// rendered per cell.
//
render() {
noStroke();
for (let i = 0; i < this.cells.length; i++) {
let x = i * this.scl;
let y = this.generation * this.scl;
if (this.cells[i] == 1) {
noStroke();
fill(caFg);
ellipse(x, y, this.scl, this.scl)
fill(bg);
// @make-changes-here
ellipse(x, y, this.scl * sin(i * 0.1), this.scl * sin(i * 0.1))
// @make-changes-here
// disable ellipse command above
// and enable stroke and line below
//
// stroke(caBg);
// strokeCap(SQUARE)
// strokeWeight(0.01);
// line(x,y + this.scl,x + this.scl, y );
} else {
noStroke();
fill(caBg)
// @make-changes-here
let wScl = -100; // try a value below 1 or above 10
let hScl = 10; // try a value below 1 or above 10
rect(x, y, this.scl * wScl, this.scl * hScl);
// @make-changes-here
// disable rect command above
// and enable stroke and line below
//
// stroke(caFg);
// strokeCap(SQUARE)
// strokeWeight(1);
// line(x,y,x + this.scl, y + this.scl);
}
}
}
// Implementing the Wolfram rules
// Could be improved and made more concise,
// but here we can explicitly see what is
// going on for each case
executeRuleSet(a, b, c) {
if (a == 1 && b == 1 && c == 1) {
return this.currentRuleSet[0];
}
if (a == 1 && b == 1 && c == 0) {
return this.currentRuleSet[1];
}
if (a == 1 && b == 0 && c == 1) {
return this.currentRuleSet[2];
}
if (a == 1 && b == 0 && c == 0) {
return this.currentRuleSet[3];
}
if (a == 0 && b == 1 && c == 1) {
return this.currentRuleSet[4];
}
if (a == 0 && b == 1 && c == 0) {
return this.currentRuleSet[5];
}
if (a == 0 && b == 0 && c == 1) {
return this.currentRuleSet[6];
}
if (a == 0 && b == 0 && c == 0) {
return this.currentRuleSet[7];
}
return 0;
}
// The CA is done if it reaches the bottom of the screen
finished() {
if (this.generation > height / this.scl) {
return true;
} else {
return false;
}
}
}
function decimalToBinaryArray(theNum, theLen = 8) {
const result = new Array(theLen).fill(0);
let n = theNum;
let i = 0;
while (n > 0) {
result[i] = (n % 2 != 0) ? 1 : 0;
n = Math.floor(n / 2);
i += 1;
}
return result.reverse();
}
function binaryArrayToDecimal(theArray) {
return parseInt(theArray.toString().replaceAll(",", ""), 2);
};