The Game of Life is a cellular automaton created by mathematician John Conway in 1970. The game consists of a board of cells that are either on or off. One creates an initial configuration of these on/off states and observes how it evolves. There are four simple rules to determine the next state of the game board, given the current state:
Overpopulation: if a living cell is surrounded by more than three living cells, it dies.
Stasis: if a living cell is surrounded by two or three living cells, it survives.
Underpopulation: if a living cell is surrounded by fewer than two living cells, it dies.
Reproduction: if a dead cell is surrounded by exactly three cells, it becomes a live cell.
A forums for Conway's Game of Life: https://conwaylife.com/forums/
A collection of epic conway's game of life: https://www.youtube.com/watch?v=C2vgICfQawE
The evolvation could be simulated in few lines of Python from this article. It use a torroidal geometry but not on an flat space. I changed some code so the game could be on a finite flat space.
import numpy as np
def evolate(grid):
grid_ = np.zeros((grid.shape[0]+2, grid.shape[1]+2), dtype=bool)
grid_[1:-1, 1:-1] = grid
surrounding_ = sum(np.roll(np.roll(grid_, i, axis=0), j, axis=1)
for i in (-1, 0, 1) for j in (-1, 0, 1)
if not(i == 0 and j == 0))
surrounding = surrounding_[1:-1, 1:-1]
return (surrounding == 3) | (grid & (surrounding == 2))
r = np.random.random((5, 5)) > 0.75
r = evolate(r)
np.roll(grid, 1, axis=0)
is a new array that the 1th row is the last row of grid, the 2th row is the 1st row of grid, ..., the last row is the last 2th row of grid(func(x) for x in X)
is the generator expressionssurrounding == 3
, if surrounding is 3, live no matter beforegrid & (surrounding == 2)
, if surrounding is 2, a living cell still livePython is not good at interactive and dynamic visiuallization (I konw it can). JS is good at it. I was motivated by this article.
The output shown in this page
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#world {
padding-bottom: 10px;
}
table {
background-color: whitesmoke;
}
td {
border: 1px solid darkgray;
width: 10px;
height: 10px;
}
td.dead {
background-color: transparent;
}
td.alive {
background-color:rgb(69, 204, 114);
}
</style>
</head>
<body>
<div id='world'></div>
<div>
<input type='button' value='Evolve (press Space)' onclick='evolve()'>
<Input type='button' id='btnstartstopautoevolve' value='Start Auto Evolve' onclick='startStopAutoEvolve()'>
<input type='button' value='Reset' onclick='resetWorld()'>
</div>
<br/>
<div>
<input type="text" size="3" id="worldrows" value="25">
<input type="text" size="3" id="worldcols" value="25">
<input type='button' value='Set Size' onclick='setSize()'>
</div>
<br/>
<div>
<input type="text" size="5" id="speed" value="500">
<input type='button' value='Set Speed (ms)' onclick='setSpeed()'>
</div>
<script type="text/javascript">
let rows = 25;
let cols = 25;
let mouseHold;
let cellAim;
let autoEvolveStatu = 0;
let speed = 500;
// 2D arrays
let currGen = [rows];
let nextGen = [rows];
function createGenArrays() {
for (let i = 0; i < rows; i++) {
currGen[i] = new Array(cols);
nextGen[i] = new Array(cols);
}
}
function initGenArrays() {
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
currGen[i][j] = 0;
nextGen[i][j] = 0;
}
}
}
function createWorld() {
// use # to select by id
let world = document.querySelector('#world');
let table = document.createElement('table');
table.setAttribute('id', 'worldgrid');
for (let i = 0; i < rows; i++) {
let tr = document.createElement('tr');
for (let j = 0; j < cols; j++) {
let cell = document.createElement('td');
cell.setAttribute('id', i + '_' + j);
cell.setAttribute('class', 'dead');
// when hold and move through, excute function 'cellHold'
cell.addEventListener('mousedown', startHold);
cell.addEventListener('mouseenter', cellHold);
cell.addEventListener('mouseup', stopHold);
tr.appendChild(cell);
}
table.appendChild(tr);
}
world.appendChild(table);
}
// batch switch the cell from alive to dead, or dead to alive
// [startHold, stopHold, cellHold]: hold and move through
function startHold() {
mouseHold = 1;
let loc = this.id.split("_");
let row = Number(loc[0]);
let col = Number(loc[1]);
if (this.className === 'alive'){
cellAim = 'dead';
currGen[row][col] = 0;
}else{
cellAim = 'alive';
currGen[row][col] = 1;
}
this.setAttribute('class', cellAim);
}
function stopHold() {
mouseHold = 0;
}
function cellHold() {
if (mouseHold === 1){
let loc = this.id.split("_");
let row = Number(loc[0]);
let col = Number(loc[1]);
this.setAttribute('class', cellAim);
if (cellAim === 'dead'){
currGen[row][col] = 0;
}
if (cellAim === 'alive'){
currGen[row][col] = 1;
}
}
}
// core function, getNeighborCount
function getNeighborCount(row, col) {
let count = 0;
let nrow=Number(row);
let ncol=Number(col);
// Check top neighbor if not at the first row
if (nrow - 1 >= 0) {
if (currGen[nrow - 1][ncol] == 1)
count++;
}
// Check upper left neighbor if not at the first row or first column
if (nrow - 1 >= 0 && ncol - 1 >= 0) {
if (currGen[nrow - 1][ncol - 1] == 1)
count++;
}
if (nrow - 1 >= 0 && ncol + 1 < cols) {
if (currGen[nrow - 1][ncol + 1] == 1)
count++;
}
if (ncol - 1 >= 0) {
if (currGen[nrow][ncol - 1] == 1)
count++;
}
if (ncol + 1 < cols) {
if (currGen[nrow][ncol + 1] == 1)
count++;
}
if (nrow + 1 < rows && ncol - 1 >= 0) {
if (currGen[nrow + 1][ncol - 1] == 1)
count++;
}
if (nrow + 1 < rows && ncol + 1 < cols) {
if (currGen[nrow + 1][ncol + 1] == 1)
count++;
}
if (nrow + 1 < rows) {
if (currGen[nrow + 1][ncol] == 1)
count++;
}
return count;
}
// evolve functions
function createNextGen() {
for (row in currGen) {
for (col in currGen[row]) {
let neighbors = getNeighborCount(row, col);
if (currGen[row][col] == 1) {
// If Alive
if (neighbors == 2 || neighbors == 3) {
nextGen[row][col] = 1;
} else {
nextGen[row][col] = 0;
}
} else {
// If Dead
if (neighbors == 3) {
nextGen[row][col] = 1;
}
}
}
}
}
function updateCurrGen() {
for (row in currGen) {
for (col in currGen[row]) {
currGen[row][col] = nextGen[row][col];
nextGen[row][col] = 0;
}
}
}
function updateWorld() {
let cell='';
for (row in currGen) {
for (col in currGen[row]) {
cell = document.getElementById(row + '_' + col);
if (currGen[row][col] == 0) {
cell.setAttribute('class', 'dead');
} else {
cell.setAttribute('class', 'alive');
}
}
}
}
function evolve() {
createNextGen();
updateCurrGen();
updateWorld();
}
function autoEvolve() {
createNextGen();
updateCurrGen();
updateWorld();
timer = setTimeout(autoEvolve, speed);
}
function startStopAutoEvolve() {
let startstop=document.querySelector('#btnstartstopautoevolve');
if (autoEvolveStatu == 0) {
autoEvolveStatu = 1;
startstop.value='Stop Auto Evolve';
autoEvolve();
} else {
autoEvolveStatu = 0;
startstop.value='Start Auto Evolve';
clearTimeout(timer);
}
}
function resetWorld() {
initGenArrays();
updateWorld();
}
function setSize() {
rows = document.getElementById("worldrows").value;
cols = document.getElementById("worldcols").value;
document.querySelector('#worldgrid').remove();
createWorld();
createGenArrays();
initGenArrays();
}
function setSpeed() {
speed = document.getElementById("speed").value;
}
// Arrow Function, shorter function syntax
// window.onload, execute the function when load window
window.onload=()=>{
createWorld();
createGenArrays();
initGenArrays();
}
// Shortcut Space for evolve()
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
if (e && e.keyCode == 32){
evolve();
}
};
</script>
</body>
</html>