Skip to content

Instantly share code, notes, and snippets.

@PotOfCoffee2Go
Last active June 15, 2020 20:55
Show Gist options
  • Save PotOfCoffee2Go/3c0978370277dae7da27d2137014f5a3 to your computer and use it in GitHub Desktop.
Save PotOfCoffee2Go/3c0978370277dae7da27d2137014f5a3 to your computer and use it in GitHub Desktop.
Javascript Cellular Automaton Engine

_conway_life

  • Press 'raw' button of the _index.html file below

  • Copy the file (ctrl-a)(ctrl-c)

  • Paste into fav editor (ctrl-v) and save (ctrl-s).

    • (Name can be anything but should have a '.html' extension)
  • Open in browser - no server needed.

    • (The other files in this gist will be automatically fetched)

Copyright: (c) Kim McKinley 2020 License: MIT https://mit-license.org/

<!doctype html>
<html><head><script>
// Fetches resources from network
class InetFetch {
constructor () {
// Fetch from local server when dev
if (window.location.href.match(/localhost/)) {
this.domain = '';
}
// Otherwise get resources from gist
else {
this.domain = 'https://gist.githubusercontent.com/PotOfCoffee2Go/' +
'3c0978370277dae7da27d2137014f5a3/raw/';
}
}
// Fetch the URI path and return the content as text
text(filepath) {
return fetch(filepath).then(res => res.ok
? Promise.resolve(res.text())
: Promise.reject(`HTTP error: (${res.status}) - ${res.statusText}\n${res.url}`));
}
// Fetch the URI path and return the content as JSON
json(filepath) {
return fetch(filepath).then(res => res.ok
? Promise.resolve(res.json())
: Promise.reject(`HTTP error: (${res.status}) - ${res.statusText}\n${res.url}`));
}
// Create and load Html elements
element(tag, appendTo, text) {
let elem = document.createElement(tag);
elem.innerHTML = text;
appendTo.appendChild(elem);
}
// Get document scripts, css, html
resources() {
let loader = this.domain + 'loader.js', engine = this.domain + 'engine.js';
let game = this.domain + 'game.js', style = this.domain + 'game.css';
let html = this.domain + 'game.html';
return Promise.all([
this.text(loader), this.text(engine), this.text(game), this.text(style),
this.text(html)
]).then(texts => {
this.element('script', document.head, texts[0]);
this.element('script', document.head, texts[1]);
this.element('script', document.head, texts[2]);
this.element('style', document.head, texts[3]);
this.element('div', document.body, texts[4]);
});
}
}
// ------------------
// Conway's Game of Life
// Game of life will be written by extending
// the Agent and World javascript classes.
var Gol;
window.onload = () => {
(new InetFetch).resources().then(() => { Gol = new Game(30, 80); })
}
</script></head><body></body></html>
// ---- Cellular automaton
//
// An agent that contains basic information required
// to implement cellular automaton on a grid.
// The agent.state can be used by apps using the
// library but normally Agent is extended to hold
// values specific to an apps needs.
// Agent.msg is standard place to place data that
// is to be cleared at beginning of each timestep.
//
// An agent contains:
// Cell number, row, and column
// Persistent state associated with this cell
// A message object that resets at start of each generation
class Agent {
constructor(n, r, c) {
this.nbr = n; this.row = r; this.col = c;
this.state = {};
this.msg = {};
}
// MUST define when agent is alive.
// Can be here or in extended class
// (in this case is defined in class Cell - see below)
get alive() { throw 'agent alive getter not defined!'; }
set alive(v) { throw 'agent alive setter not defined!'; }
// Example:
//get alive() { return this.isAlive ? true : false; }
//set alive(v) { this.isAlive = v ? true : false; }
}
// The world is a grid populated by agents with a edge
// around all sides.
// The 'arena' is the region of play within the edges.
// The neighbor(hood) access is defined by a rectangle
// or a 'region' which is an object of top/left position
// by number of rows/columns.
class World {
constructor(rows, cols, edge = 1, agentType = Agent) {
// Grid containing population of agents
this.agent = [];
// Access to grid is 'top' and 'left' position
// then number of 'rows' and 'cols'(columns)
// inclusive - absolute from grid 0,0.
// Origin of region of play within grid
this.origin = {top: edge, left: edge};
// The complete grid including the boundries
this.grid = {top: 0, left: 0, rows: rows+(edge*2), cols: cols+(edge*2)};
// The edge regions
this.edge = {
top: { top: 0, left: 0, rows: edge, cols: cols+(edge*2) },
right: { top: edge, left: cols+edge, rows: rows, cols: edge },
bottom: { top: rows+edge, left: 0, rows: edge, cols: cols+(edge*2) },
left: { top: edge, left: 0, rows: rows, cols: edge },
};
// The region of play is inside the edge
this.arena = {top: edge, left: edge, rows: rows, cols: cols };
// Access to neighbors is 'top' and 'left' position
// and number of 'rows' and 'cols'(columns)
// inclusive - relative to requested cell(agent).
// Define region of two most common neighborhood types:
// The default region of neighbors - relative to agent
// 3x3 region - eight neighbors - Conway Game of Life
this.conwayNeighbors = {top:-1, left:-1, rows:3, cols:3};
// Elementary region of neighbors - relative to agent
// 1x3 region - two neighbors - (Stephen) Wolfram Code
this.wolframNeighbors = {top:0, left:-1, rows:1, cols:3};
// Default neighbors region - conways - app can set
this.neighborsRegion = this.conwayNeighbors;
// A continuous sequence of timesteps are running
this.isRunning = false;
this.stepWait = 0; // millisec wait between timesteps
this.stepTimeout = null; // setTimeout timer
// Phases of cellular automaton
// Functions pushed onto the appropriate phase are
// executed in sequence
this.on = {
initialize: [], // Run world construction and/or reset
// Steps run each timestep:
prepare: [], // Gather data for computing new state
compute: [], // Compute new state
resolve: [], // Apply new state to world
finalize: [] // Display/store generation results
};
// Create grid of agents - flag edge agents
this.createAgents(rows, cols, edge, agentType);
// Resets all agents for restart
this.on.initialize.push(() => {
this.forEachAgent((agent) => { agent.msg = {}; }, this.grid);
})
// Resets also at start of timestep
this.on.prepare.push(this.on.initialize[0]);
} // constructor
// Execute functions pushed to the phases
automate(phase) { phase.forEach((fn) => fn()); }
// Convert region relative to agent to an absolute region
regionRelToAbs(agent, region) { return {
top: agent.row+region.top, left: agent.col+region.left,
rows: region.rows, cols: region.cols}; }
// Instantiate population of agents
createAgents(rows, cols, edge, agentType) {
let n = 0;
for (let r=0; r<rows+(edge*2); r++) {
this.agent[r] = [];
for (let c=0; c<cols+(edge*2); c++) {
this.agent[r][c] = new agentType(n++, r, c);
}
}
this.flagEdges(rows, cols, edge);
}
// Flag agents out of play - ie: on the edge
flagEdges(rows, cols, edge) {
this.forEachAgent((agent) => {
if (this.isEdge(agent, rows, cols, edge)) { agent.state.isEdge = true; }
}, this.grid);
}
// Check if an agent is on the edge
isEdge(agent, rows, cols, edge) {
return (agent.row < edge || agent.col < edge ||
agent.row >= rows+edge || agent.col >= cols+edge);
}
// -----------
// Rest of the functions are called by the class
// that extends this class (World)
// Clear the arena
clear() { this.forEachAgent((agent) => agent.alive = false); }
// Initialize/reset grid
initialize() { this.automate(this.on.initialize); }
// Run phases of the cellular animation
timestep() {
this.automate(this.on.prepare);
this.automate(this.on.compute);
this.automate(this.on.resolve);
this.automate(this.on.finalize);
if (this.running) {
this.stepTimeout = setTimeout(() => this.timestep(), this.stepWait);
}
}
// Start timestep sequence
startStepSequence(ms) {
if (this.running) { return; }
this.running = true;
this.timestep();
}
// Stop timestep sequence
stopStepSequence(ms) {
if (!this.running) { return; }
this.running = false;
clearInterval(this.stepTimeout);
}
// Run function fn(agent) for each agent
// in the arena or agents within given absolute region
forEachAgent(fn, absregion) {
let f = absregion || this.arena;
for (let r=f.top, y=f.top+f.rows; r<y; r++)
for (let c=f.left, x=f.left+f.cols; c<x; c++)
fn(this.agent[r][c], this);
}
// Execute function on agent neighbors
// Region values are relative to the agent
// (note: if agentoo is true the agent is treated
// as a neighbor and function will
// execute against the agent as well)
neighbors(fn, agent, relregion, agentoo) {
this.forEachAgent((nbor) => {
if (!agentoo && nbor.row === agent.row && nbor.col === agent.col) { return; }
fn(nbor);
},
relregion
? this.regionRelToAbs(agent, relregion)
: this.regionRelToAbs(agent, this.neighborsRegion));
}
// Executes two functions on agent neighbors
// first function executes once on the agent
// then second executes for each neighbor
// (note: if agentoo is true the agent is treated
// as a neighbor and second function will
// execute against the agent as well)
neighborhood(agentfn, nborfn, relregion, agentoo) {
this.forEachAgent((agent) => {
agentfn(agent);
this.neighbors((nbor) => nborfn(agent, nbor), agent, relregion, agentoo);
});
}
} // class World
:root {
--clrbackgrnd: black;
--clrborn: #9DC183;
--clrreborn: lightblue;
--clrtext: white;
}
body {
background: var(--clrbackgrnd);
color: var(--clrtext);
}
#game-region {
font-family: monospace;
line-height: .75em;
margin: 1em;
overflow: hidden;
}
#game-array {
font-family: monospace;
font-size: .7em;
margin: 1em;
overflow: hidden;
}
.b {
color: var(--clrborn);
}
.r {
color: var(--clrreborn);
}
.p {
cursor: pointer;
}
/*
A range control that seems to work consistant across browsers
https://www.htmllion.com/html5-range-input-with-css.html
*/
input[type='range'] {
-webkit-appearance: none ;
width:200px;
}
input[type='range']::-webkit-slider-runnable-track {
-webkit-appearance: none ;
appearance: none ;
border-radius: 5px;
box-shadow: inset 1px 1px 1px rgba(000,000,000,0.10);
background-color: #CCC;
height: 8px;
vertical-align:middle;
border: none;
cursor: pointer;
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
border-radius: 100%;
background-color: #037cd5;
box-shadow: inset 0 0 0 4px #CCC, inset 0 0 4px #CCC, inset 0 0 0 4px #CCC;
height: 22px;
width: 22px;
vertical-align:middle;
border: none;
cursor: pointer;
margin-top: -6px;
}
input[type='range']::-webkit-slider-thumb:hover {
background: #fdd921;
}
input[type='range']:active::-webkit-slider-thumb {
background: #fdd921;
}
input[type='range']::-moz-range-track{
-moz-appearance: none ;
border-radius: 5px;
box-shadow: inset 1px 1px 1px rgba(000,000,000,0.10);
background-color: #CCC;
height: 8px;
vertical-align:middle;
margin:0; padding:0;
border: none;
cursor: pointer;
}
input[type='range']::-moz-range-thumb {
-moz-appearance: none ;
border-radius: 100%;
background-color: #037cd5;
box-shadow: inset 0 0 0 4px #CCC, inset 0 0 4px #CCC, inset 0 0 0 4px #CCC;
height: 22px;
width: 22px;
vertical-align:middle;
border: none;
cursor: pointer;
margin-top: -6px;
}
input[type='range']::-moz-range-thumb:hover {
background: #fdd921;
}
input[type='range']:active::-moz-range-thumb {
background: #fdd921;
}
input[type=range]::-ms-track {
width: 100%;
height: 8px;
cursor: pointer;
animate: 0.2s;
background: transparent;
border-color: transparent;
border-width: 39px 0;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #CCC;
border: 0px solid #000101;
border-radius: 50px;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
}
input[type=range]::-ms-fill-upper {
background: #CCC;
border: 0px solid #000101;
border-radius: 50px;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
}
input[type=range]::-ms-thumb {
box-shadow: inset 0 0 0 4px #CCC, inset 0 0 4px #CCC, inset 0 0 0 4px #CCC;
border: 0px solid #000000;
height: 22px;
width: 22px;
border-radius: 100%;
background: #037cd5;
cursor: pointer;
margin-top: -2px;
}
/*
Buttons
https://codepen.io/cheeriottis/pen/inluv
*/
.animate {
transition: all 0.1s;
-webkit-transition: all 0.1s;
}
.action-button {
position: relative;
border-top: transparent;
border-left: transparent;
border-right: transparent;
padding: 0px 8px;
margin: 0px 5px 5px 0px;
float: left;
border-radius: 10px;
font-size: 1em;
font-family: 'Pacifico', cursive;
color: #fff;
text-decoration: none;
}
.blue {
background-color: #3e4347;
border-bottom: 5px solid #2980B9;
}
.red {
background-color: #3e4347;
border-bottom: 5px solid #BD3E31;
}
.green {
background-color: #3e4347;
border-bottom: 5px solid #669644;
}
.yellow {
background-color: #3e4347;
border-bottom: 5px solid #D1B358;
}
.action-button:active {
transform: translate(0px,5px);
-webkit-transform: translate(0px,5px);
border-bottom: 1px solid;
}
<div>
<button onclick="Gol.restart();"
class="action-button animate red">Reset</button>
<button onclick="Gol.toggleTimestep();"
class="action-button animate blue">Start/Stop</button>
<button onclick="Gol.singleTimestep();"
class="action-button animate green">Step</button>
<input id="gen-speed" type="range" style="float: left;"
min="0" max="1000" step="50" value="150">
<button onclick="Gol.clear();"
class="action-button animate red">Clear</button>
<button onclick="Gol.edit();"
class="action-button animate blue">Edit</button>
</div>
<div style="clear: both;">
<span> Generation: </span><span id="nbr-gens"></span>
<span> Alive: </span><span id="nbr-alive"></span>
<span> Dead: </span><span id="nbr-dead"></span>
<span class="b"> Born: </span><span id="nbr-born"></span>
<span class="r"> Reborn: </span><span id="nbr-reborn"></span>
<span> Died &#183; : </span><span id="nbr-died"></span>
</div>
<div><pre id="game-region"></pre></div>
<div>
<textarea id="txt-array"></textarea>
<button onclick="Gol.textInput();"
class="action-button animate yellow">Submit</button>
</div>
<div><pre id="game-array"></pre></div>
// ------------------
// Conway's Game of Life Cell
// Persistent data
// - if cell is populated (isAlive)
// - statistics by generation; born, died, unchanged
// Temporary data
// - in agent.msg which clears every timestep
// Keeps track of alive or dead
// Gather stats on born, died, etc.
class Cell extends Agent {
constructor(n, r, c) {
super(n, r, c);
this.isAlive = false;
this.stats = [];
}
// The required getter and setter for alive
get alive() { return this.isAlive ? true : false; }
set alive(v) { this.isAlive = v ? true : false; }
}
// Extend the World class to implement the game
// Conways rules are added to the World class
// which handles the details of the automaton.
// Have super create 'Cell's instead of 'Agent's
// (works because Cell extends from Agent)
class Game extends World {
constructor(rows, cols) {
super(rows, cols, 1, Cell); // edge of 1 cell
this.gen = {}; // Info about current generation
this.loader = new Loader(this);
this.setAutomata();
this.restart();
this.sampleTextArray();
}
// Load sample into textarea
sampleTextArray() {
document.getElementById('txt-array').value =
'0 0 0 0 0 0 0\n 0 0 0 0 0 0\n';
}
// ------------------
// Button Handlers
// Restart/reinitialize grid and on screen
restart() { this.stopStepSequence(); this.initialize(); this.updateDisplay(); }
// Turn timestep on/off
toggleTimestep() {
if (this.running) { this.stopStepSequence(); }
else { this.startStepSequence(); }
}
// Step a single timestep one at a time
singleTimestep() { this.stopStepSequence(); this.timestep(); }
// Clear and reset to generation zero
clear() { this.restart(); this.setInitialize(false); this.updateDisplay(); }
// Edit mode
edit() { this.editMode(); }
// Textarea to import an array of the grid
textInput() {
let txt = document.getElementById('txt-array').value;
this.loader.fromArray(this.loader.makeArray(txt));
this.updateDisplay();
}
// -------------
// Function to set initial state of world
setInitialize(isRestart = true) {
this.gen.count = 0;
this.gen.stats = { alive: 0, dead: 0, born: 0, reborn:0, died: 0 };
// Clear compete grid including edges
this.forEachAgent((cell) => { cell.alive = false; }, this.grid);
// Populate the world - Generation zero - 50% alive
if (isRestart) {
this.forEachAgent((cell) => {
cell.alive = Math.random() < 0.5 ? true : false;
this.gen.stats.alive += cell.alive ? 1 : 0;
this.gen.stats.dead += cell.alive ? 0 : 1;
});
}
}
// Prepare Phase
// Gather data to be used by the compute phase and
// place into agent.msg object. agent.msg is emptied
// at the start of each new generation
// Initialize stats and counters
initialStats() {
this.gen.stats = { alive: 0, dead: 0, born: 0, reborn:0, died: 0 };
this.gen.count++;
}
// Count alive neighbors
sumNeighbors() {
this.neighborhood(
(cell) => { cell.msg.aliveNbors = 0; },
(cell, nbor) => { cell.msg.aliveNbors += nbor.alive; }
);
}
// Compute Phase
// Computes the next state of each agent using
// the values gathered during the Prepare Phase
// Conway's rule #1
// Cells with less than 2 neighbors die
conwayRule1() {
this.forEachAgent((cell) => {
if (cell.msg.aliveNbors < 2) {
cell.msg.willBeAlive = 0;
if (cell.alive) { cell.msg.died = true; this.gen.stats.died++; }
}
})
}
// Conway's rule #2
// Cells with more than 3 neighbors die
conwayRule2() {
this.forEachAgent((cell) => {
if (cell.msg.aliveNbors > 3) {
cell.msg.willBeAlive = 0;
if (cell.alive) { cell.msg.died = true; this.gen.stats.died++; }
}
})
}
// Conway's rule #3
// Cells with exactly 3 neighbors are born
conwayRule3() {
this.forEachAgent((cell) => {
if (cell.msg.aliveNbors === 3) {
cell.msg.willBeAlive = 1;
if (cell.alive) { cell.msg.reborn = true; this.gen.stats.reborn++; }
else { cell.msg.born = true; this.gen.stats.born++; }
}
})
}
// Conway's rule #4
// Cells with exactly 2 neighbors are unchanged
conwayRule4() {
this.forEachAgent((cell) => {
if (cell.msg.aliveNbors === 2) {
cell.msg.willBeAlive = cell.alive;
}
})
}
// Resolve Phase
// Apply computed cell states to the world
// Update state of the cells
updateAgents() {
this.forEachAgent((cell) => {
cell.alive = cell.msg.willBeAlive;
this.gen.stats.alive += cell.alive ? 1 : 0;
this.gen.stats.dead += cell.alive ? 0 : 1;
});
}
// Update state of the cells
updateEdges() {
this.forEachAgent((cell) => {
cell.alive = this.agent[this.arena.rows][cell.col].alive;}, this.edge.top);
this.forEachAgent((cell) => {
cell.alive = this.agent[cell.row][this.arena.left].alive;}, this.edge.right);
this.forEachAgent((cell) => {
cell.alive = this.agent[this.arena.top][cell.col].alive;}, this.edge.bottom);
this.forEachAgent((cell) => {
cell.alive = this.agent[cell.row][this.arena.cols].alive;}, this.edge.left);
}
// Finalize Phase
// Display stats and cells
updateDisplay() {
let curStr = "", curRow = 0;
this.forEachAgent((cell) => {
if (cell.row !== curRow) { curStr += '<br>'; curRow++; }
if (cell.state.isEdge) { curStr += cell.alive === true ? '#' : '-'; }
else if (cell.msg.born) { curStr += '<span class="b">0</span>'; }
else if (cell.msg.reborn) { curStr += '<span class="r">0</span>'; }
else if (cell.msg.died) { curStr += '&#183;'; }
else { curStr += cell.alive ? '0' : ' '; }
}, this.grid);
document.getElementById('game-region').innerHTML = curStr;
this.stepWait = +document.getElementById('gen-speed').value;
this.updateJson();
this.updateStats();
}
// Show grid on screen
updateStats() {
document.getElementById('nbr-gens') .innerHTML = +this.gen.count;
document.getElementById('nbr-alive') .innerHTML = +this.gen.stats.alive;
document.getElementById('nbr-dead') .innerHTML = +this.gen.stats.dead;
document.getElementById('nbr-born') .innerHTML = +this.gen.stats.born;
document.getElementById('nbr-reborn').innerHTML = +this.gen.stats.reborn;
document.getElementById('nbr-died') .innerHTML = +this.gen.stats.died;
}
// JSON representation of grid
updateJson() {
document.getElementById('game-array').innerHTML =
JSON.stringify(this.loader.toArray((cell) => cell.alive)).replace('[','[<br> ')
.replace(/},"/g,'},<br> "').replace(/","/g,'",<br> "').replace(']','<br>]<br>');
}
// End of functions for timestep sequence
// Push timestep functions into respective phase
setAutomata () {
// Initialize
this.on.initialize.push(() => this.setInitialize());
// Prepare phase
this.on.prepare.push(() => this.initialStats());
this.on.prepare.push(() => this.sumNeighbors());
// Compute Phase
this.on.compute.push(() => this.conwayRule1());
this.on.compute.push(() => this.conwayRule2());
this.on.compute.push(() => this.conwayRule3());
this.on.compute.push(() => this.conwayRule4());
// Resolve phase
this.on.resolve.push(() => this.updateAgents());
this.on.resolve.push(() => this.updateEdges());
// Finalize Phase
this.on.finalize.push(() => this.updateDisplay());
}
// --------------
// Enter edit mode
editMode() {
this.stopStepSequence();
let curStr = "", curRow = 0;
this.forEachAgent((cell) => {
if (cell.row !== curRow) { curStr += '<br>'; curRow++; }
let r = cell.row, c = cell.col;
if (cell.state.isEdge) {{ curStr += cell.alive === true ? '#' : '-'; } }
else if (cell.msg.born) {
curStr += `<span id="r${r}c${c}" onclick="Gol.upd(${r},${c});" ` +
`class="b p">${cell.alive ? '0' : ' '}</span>`; }
else if (cell.msg.reborn) {
curStr += `<span id="r${r}c${c}" onclick="Gol.upd(${r},${c});" ` +
`class="r p">${cell.alive ? '0' : ' '}</span>`; }
else if (cell.msg.died) {
curStr += `<span id="r${r}c${c}" onclick="Gol.upd(${r},${c});" ` +
`class="p">${cell.alive ? '0' : ' '}</span>`; }
else {
curStr += `<span id="r${r}c${c}" onclick="Gol.upd(${r},${c});" `+
`class="p">${cell.alive ? '0' : ' '}</span>`;
}
}, this.grid);
document.getElementById('game-region').innerHTML = curStr;
}
// Edit mode - clicked in grid area
upd(row, col) {
this.agent[row][col].alive = !this.agent[row][col].alive;
document.getElementById(`r${row}c${col}`).innerHTML =
this.agent[row][col].alive ? '0' : ' ';
this.updateEdges();
this.updateJson();
this.updateStats();
}
// root.style.setProperty('--clrbackgrnd', 'aliceblue');
} // class game
// Load agents to/from an array of strings. The first element of the
// array contains a JSON object of the top and left origin to place
// agents into the grid
class Loader {
constructor(world) {
this.world = world;
}
// Given function that returns true/false and an absolute region.
// Returns array (rows) of strings (columns) containing '0' (alive)
// ' ' (dead) for every agent in the region.
toArray(agentfn, absregion) {
absregion = absregion || this.world.arena;
let flags = [], curRow = absregion.top, curCols = "";
flags.push(absregion);
this.world.forEachAgent((agent) => {
if (curRow !== agent.row) { flags.push(curCols); curRow = agent.row; curCols = ""; }
curCols += agentfn(agent) ? '0' : ' ';
}, absregion);
flags.push(curCols);
return flags;
}
// Applies array (see above) to the grid
fromArray(txtArray) {
// Place arena region at beginning of array
if (typeof txtArray[0] === 'string') { txtArray.unshift(this.world.arena); }
let region = txtArray.shift();
for (let r=0; r<txtArray.length; r++) {
let cols = txtArray[r].split('');
for (let c=0; c<cols.length; c++) {
this.world.agent[r+region.top][c+region.left].alive = cols[c] === ' ' ? false : true;
}
}
}
// Given strings(rows) where non-spaces represent live agents
// builds array for import into world grid
// If a line contains JSON object is used as origin otherwise
// attemps to center the agents in the grid
makeArray(str) {
let region, arr = [], maxlen = 0;
let strArray = str.split(/\r|\n|\r\n/);
strArray.forEach(astr => {
if (astr.match(/{(.*)}/)) {
if (!region) { region = JSON.parse('{' + astr.match(/{(.*)}/)[1] + '}'); }
} else {
arr.push(astr);
maxlen = Math.max(maxlen, astr.length);
}
});
if (!region) {
let reg = JSON.parse(JSON.stringify(this.world.origin));
reg.top = Math.floor((this.world.arena.rows/2) - (strArray.length/2));
reg.left = Math.floor((this.world.arena.cols/2) - (maxlen/2));
region = reg;
}
arr.unshift(region);
return arr;
}
}
@PotOfCoffee2Go
Copy link
Author

_conway_life

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment