Here is a fun thing – above is cellular automation coded (by me) in React. Specifically, it is Conway’s Game of Life
Rules
The rules are
- Any live cell with two or three live neighbours survives.
- Any dead cell with three live neighbours becomes a live cell.
- All other live cells die in the next generation. Similarly, all other dead cells stay dead.
With these rules you get an impressive array of emergent behaviour. The example above is the evolution of the R-pentomino. The starting point is this simple 5 square pattern
Which develops into the above pattern that last for well over two thousand generations. If you look carefully, you’ll see simpler patterns within it, such as the glider
which propagates across the screen until it is stopped by another object. If it isn’t stopped it will go on forever.
Live Application
I’ve published the game of life application here for anyone to gaze at
http://gameoflife.codebuckets.com
You can set the size of the grid, interval between generations and the zoom. The grid wraps so it’s interesting to try the same pattern on different sizes and watch the effect of the available space on how the game develops.
Code
It’s coded up in React and is published here
https://github.com/timbrownls20/Game-Of-Life
and is freely available to download and tinker round with
The Game of Life rules are held within one file
https://github.com/timbrownls20/Game-Of-Life/blob/master/src/hooks/transformers/gameOfLife.js
import cellUtil from "../../utils/cellUtil"; const gameOfLife = (gameState, gridSettingState, gameStateDispatch) => { let arrTransformed = []; gameState.grid.filter((row) => { row.filter((item) => { if (item) { let rowUp = item.row <= 1 ? gridSettingState.rows : item.row - 1; let rowDown = item.row >= gridSettingState.rows ? 1 : item.row + 1; let columnUp = item.column <= 1 ? gridSettingState.columns : item.column - 1; let columnDown = item.column >= gridSettingState.columns ? 1 : item.column + 1; let neighbours = [ { row: rowUp, column: columnUp }, { row: rowUp, column: item.column }, { row: rowUp, column: columnDown }, { row: item.row, column: columnUp }, { row: item.row, column: columnDown }, { row: rowDown, column: columnUp }, { row: rowDown, column: item.column }, { row: rowDown, column: columnDown }, ]; let activeNeighbours = neighbours.reduce((acc, searchItem) => { return gameState.grid[searchItem.row][searchItem.column].selected ? acc + 1 : acc; }, 0); if (item.selected && (activeNeighbours < 2 || activeNeighbours > 3)) { addCellToTransform(cellUtil.deselectCell(item)); } else if (!item.selected && activeNeighbours == 3) { addCellToTransform(cellUtil.selectCell(item)); } } }); }); arrTransformed.filter((element) => { gameState.grid[element.row][element.column] = { ...element }; }); gameState.generation = gameState.generation + 1; gameStateDispatch({ type: "set-state", value: gameState }); function addCellToTransform(cellToAdd) { let existingCell = arrTransformed.find( (e) => e.column == cellToAdd.column && e.row == cellToAdd.row ); if (existingCell) { cellToAdd = existingCell.selected && !cellToAdd.selected ? existingCell : cellToAdd; }
It would be easy to swap these out and try other cellular automation rules. You can see two other simpler transformations in the same folder, that I wrote along the way.
So enjoy it if you choose to. In the age of Zoom it’s good to start a pattern then gaze at it hypnotically during online meetings. It makes you look very focused to your fellow meeting participants.