I am currently making a game that illustrates John Conways 'Game of Life' I've been learning about closures and the modular patterns and am trying to implement more of what I'm learning in Javascript. You can check out my repo here if your interested. All of these methods are inside a 'Game' object. I have it working, but am trying to limit variables and make it as functional as possible (passing the grid array between functions). I would like some advice on wether I should be keeping higher scope variable immutable or wether it doesnt matter.
Which solution is better practise, in terms of structure?
// initial grid
// will be bigger than this but is 3x3 for this question.
let grid = [
[1,0,0],
[0,1,0],
[0,0,1]
]
Solution 1: My initial way of solving:
function nextGrid(){
grid = applyRules(grid)
renderGrid(grid)
}
function applyRules(grid){
// makes changes
}
function renderGrid(grid){
// makes changes
}
// call nextGrid() to generate subsequent new grids.
Solution 2: Having looked into closures more, is this better?
function nextGrid() {
let prevGrid = grid;
return function(){
prevGrid = applyRules(prevGrid)
renderGrid(prevGrid)
}
}
function applyRules(prevGrid){
// makes changes
}
function renderGrid(grid){
// makes changes
}
const next = nextGrid();
// call next() to generate subsequent new grids.
Both your solutions employ mutation. In your first solution you mutate the global variable grid
. In your second solution you mutate the local variable prevGrid
. A truly functional program would use recursion instead of mutation. For example, this is what I'd do:
const game = /* initial game state */;
play(game);
function play(game) {
render(game);
setTimeout(play, 100, update(game));
}
As you can see, instead of mutating the global variable game
we call play
with an updated game state every 100 milliseconds. Our initial call play(game)
on the third line starts the game. If we comment it out, nothing will be displayed.
Here's a complete example of Conway's Game of Life in a few lines of code:
const glyph = [" ", "■"];
document.querySelectorAll("pre.life").forEach(e => play(e, read(e.innerHTML)));
function read(text) {
return text.split("\n").map(l => l.split("").map(c => glyph.indexOf(c)));
}
function play(pre, game) {
pre.innerHTML = show(game);
setTimeout(play, 100, pre, update(game));
}
function show(game) {
return game.map(line => line.map(cell => glyph[cell]).join("")).join("\n");
}
function update(game) {
return game.map((line, i) => line.map((cell, j) => {
const back = game[i - 1], next = game[i + 1], h = j - 1, k = j + 1;
const neighbors = (back && back[h] || 0)
+ (back && back[j] || 0)
+ (back && back[k] || 0)
+ (line[h] || 0)
+ (line[k] || 0)
+ (next && next[h] || 0)
+ (next && next[j] || 0)
+ (next && next[k] || 0);
switch (neighbors) {
case 3: return 1;
case 2: return cell;
default: return 0;
}
}));
}
pre.life {
line-height: 0.666;
}
<pre class="life">
■
■ ■
■■ ■■ ■■
■ ■ ■■ ■■
■■ ■ ■ ■■
■■ ■ ■ ■■ ■ ■
■ ■ ■
■ ■
■■
</pre>
The above pattern is the Gosper glider gun.