Conway’s Game of Life with Redux + Canvas
In the past few weeks I’ve been doing a fair bit of work with Redux, a library which helps manage data and state in JavaScript applications. There’s an emphasis on functional programming and immutable data, which takes a little getting used to, but it does feel like a suitable approach—particularly for implementing Conway’s Game of Life…
each generation is a pure function of the preceding one
Conway’s Game of Life on Wikipedia
This fits nicely with Redux’s concept of “reducers”, which take in the current state and an “action”, and return a new state.
The app state is just the grid: an array of arrays containing cell values (1s and 0s). There is just a single action, a 'TICK'
, and when a 'TICK'
is dispatched, the grid
reducer generates a new grid based on the current grid:
function grid (state, action) {
if (typeof state === 'undefined') return randomGrid()
switch (action.type) {
case 'TICK':
return nextGrid(state)
default:
return state
}
}
function nextGrid (grid) {
return grid.reduce(function (nextGrid, currentRow, y) {
var newRow = currentRow.map(function (value, x) {
return nextValue(x, y, value, grid)
})
nextGrid.push(newRow)
return nextGrid
}, [])
}
The grid values for the next tick are computed based on the neighbouring values (as per the rules):
function nextValue (x, y, value, grid) {
var neighbours = neighboursOf(x, y, grid)
var livingNeighbours = neighbours.filter(function (value) {
return !!value
}).length
return +willLive(value, livingNeighbours)
}
function willLive (value, livingNeighbours) {
return value
? livingNeighbours === 2 || livingNeighbours === 3
: livingNeighbours === 3
}
function neighboursOf (x, y, grid) {
return [
[-1, -1], [0, -1], [1, -1],
[-1, 0], /*x,y*/ [1, 0],
[-1, 1], [0, 1], [1, 1]
].map(function (coords) {
return valueAt(x + coords[0], y + coords[1], grid)
})
}
function valueAt (x, y, grid) { return grid[y] && grid[y][x] }
The render
function redraws the state on a <canvas>
whenever it changes, and a 'TICK'
is dispatched every 100ms to generate the next state:
var store = Redux.createStore(grid)
store.subscribe(render)
render()
setInterval(function () {
store.dispatch({ type: 'TICK' })
}, 100)
I’m sure this could be improved, but it feels like a pretty nice solution.
See it in action: Conway’s Game of Life with Redux and Canvas, and view the full source.
Hat tip to Alan R. Soares. I had a peak at his implementation with React and refined my willLive
function a based on it.