log in
register

Conway's Game of Life

Qingqi@2020-10-31 #杂谈

Conway's Game of Life

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.

Python code

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)

notes:

  • np.roll is a function that roll the array in an axis, for example 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 expressions
  • Suming the 8 rolled arrays to get the count of True surrounding
  • Adding the blank boundary into grid and clear them latter is to change torroidal geometry to infinite flat space
  • surrounding == 3, if surrounding is 3, live no matter before
  • grid & (surrounding == 2), if surrounding is 2, a living cell still live

JavaScript code

Python 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>
Comments

Log in to add your comment

Don't have an account? register here