Published
Edited
Dec 10, 2020
2 forks
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import { slider } from "@jashkenas/inputs"
Insert cell
viewof width = slider({
min: 0,
max: 800,
step: 10,
value: 600,
title: "SVG Width",
description: "How wide should the cellular automata grid be?"
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof jumpCA = {
//create button HTML entity
let button = html`<button>Jump Forward 100 Generations</button>`;
//attach event listener without D3's .on(event, function(){})
button.onclick = function() {
//keep track of actions
let counter = 0;
//start D3 timer
let ticker = d3.interval(elapsed => {
//run functions at interval
step();
counter++;
//if we have run 100 times...
if (counter > 100) {
//turn off the timer
ticker.stop();
}
}, generationalDelay); //how long the timer should wait between executions
};
//show in observable
return button;
}
Insert cell
Insert cell
Insert cell
data = {
//create container for data
let caData = [];

//loop through rows and columns in two for loops
for (let i = 0; i < rows; i++) {
//i is the row, its vertical position
for (let j = 0; j < columns; j++) {
//j is the column, its horizontal position

//create data for the cell, recording its location and neighbors
caData.push({
row: i,
column: j,
alive: 0,
survive: 0,
s: i * columns + j,
t: (i - 1) * columns + j,
r: i * columns + (j + 1),
l: i * columns + (j - 1),
b: (i + 1) * columns + j,
tl: (i - 1) * columns + (j - 1),
tr: (i - 1) * columns + (j + 1),
bl: (i + 1) * columns + (j - 1),
br: (i + 1) * columns + (j + 1)
});
}
}
return caData;
}
Insert cell
Insert cell
function randomizeCA() {
//iterate throuh CA dataset
data.forEach(function(automaton) {
//check if the cell should become alive
if (Math.random() < lifeChance) {
//cell is alive at start
automaton.alive = 1;
} else {
//cell is dead at start
automaton.alive = 0;
}
});
}
Insert cell
Insert cell
function drawGrid() {
//determine width and height of each cell
let cellHeight = height / rows;
let cellWidth = width / columns;

//destroy any existing grid cells
d3.select('#mainSvg')
.selectAll('.automaton')
.remove();

//create grid based on synthetic dataset
d3.select('#mainSvg')
.selectAll('.automaton') //look for something that doesn't exist
.data(data) //compare it against data
.enter()
.append('rect')
.attr('width', cellWidth)
.attr('height', cellHeight)
.attr('x', function(d) {
return d.column * cellWidth;
})
.attr('y', function(d) {
return d.row * cellHeight;
})
.attr('fill', function(d) {
if (d.alive == 1) {
return 'black';
} else {
return 'white';
}
})
.attr('stroke', 'black')
.attr('stroke-width', '1')
.attr('class', 'automaton');
}
Insert cell
Insert cell
function step() {
//iterate through the cells to determine if each should live or die
for (let i = 0; i < data.length; i++) {
//clarifying variable
let currentCell = data[i];

//holder to collect all of our living neighbors
let neighborSum = 0;

//check if this cell is the top-left corner
if (currentCell.row == 0 && currentCell.column == 0) {
//corners have 3 neighbors
neighborSum =
data[currentCell.b].alive +
data[currentCell.r].alive +
data[currentCell.br].alive;
}
//check if this cell is the top-right corner
else if (currentCell.row == 0 && currentCell.column == columns - 1) {
neighborSum =
data[currentCell.b].alive +
data[currentCell.l].alive +
data[currentCell.bl].alive;
}
//check if this cell is the bottom-left corner
else if (currentCell.row == rows - 1 && currentCell.column == 0) {
neighborSum =
data[currentCell.t].alive +
data[currentCell.r].alive +
data[currentCell.tr].alive;
}
//check if this cell is the bottom-right corner
else if (currentCell.row == rows - 1 && currentCell.column == columns - 1) {
neighborSum =
data[currentCell.t].alive +
data[currentCell.l].alive +
data[currentCell.tl].alive;
}
//check if this cell is on the top edge
else if (currentCell.row == 0) {
//edge cells have 5 neighbors
neighborSum =
data[currentCell.b].alive +
data[currentCell.l].alive +
data[currentCell.r].alive +
data[currentCell.br].alive +
data[currentCell.bl].alive;
}
//check if this cell is on the bottom edge
else if (currentCell.row == rows - 1) {
neighborSum =
data[currentCell.t].alive +
data[currentCell.l].alive +
data[currentCell.r].alive +
data[currentCell.tr].alive +
data[currentCell.tl].alive;
}
//check if this cell is on the left-hand edge
else if (currentCell.column == 0) {
neighborSum =
data[currentCell.t].alive +
data[currentCell.b].alive +
data[currentCell.r].alive +
data[currentCell.tr].alive +
data[currentCell.br].alive;
}
//check if this cell is on the right-hand edge
else if (currentCell.column == columns - 1) {
neighborSum =
data[currentCell.t].alive +
data[currentCell.b].alive +
data[currentCell.l].alive +
data[currentCell.tl].alive +
data[currentCell.bl].alive;
}
//if all the checks above fail, this cell is an interior cell
else {
//interior cells have 8 neighbors
neighborSum =
data[currentCell.t].alive +
data[currentCell.b].alive +
data[currentCell.l].alive +
data[currentCell.r].alive +
data[currentCell.tr].alive +
data[currentCell.tl].alive +
data[currentCell.br].alive +
data[currentCell.bl].alive;
}

//Rules of the Game
//this sim uses the *survive* property to determine if the cell should live or die in the next generation
// These rules are *Conway's Game of Life*
// Any live cell with two or three live neighbors survives.
// Any dead cell with three live neighbors becomes a live cell.
// All other live cells die in the next generation. Similarly, all other dead cells stay dead.

// is the cell dead?
if (currentCell.alive == 0) {
//the cell is dead
if (neighborSum == 3) {
//if the cell is dead with three live neighbors, becem alie
currentCell.survive = 1;
} else {
//otherwise stay dead
currentCell.survive = 0;
}
} else {
//the cell is alive...
if (neighborSum == 2 || neighborSum == 3) {
//the living cell has 2 or 3 live neighbors, so it survives
currentCell.survive = 1;
} else {
//the cell dies
currentCell.survive = 0;
}
}
}

//replace the alive property with survive
//this seems unnecessary, right? why not just update alive directly above?
//if we do, things get really buggy, can you think of why?
//zach didn't catch it, and pulled out lots of hair trying to solve it!
for (let i = 0; i < data.length; i++) {
let currentCell = data[i];
currentCell.alive = currentCell.survive;
}

//update the visualization with a data join
d3.select('#mainSvg')
.selectAll('.automaton')
.data(data)
.transition()
.duration(generationalDelay)
.delay((d, i) => i / 10)
.attr('fill', function(d) {
if (d.alive == 1) {
return 'black';
} else {
return 'white';
}
});
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more