patternjavascriptMinor
Functional Conway's Game of Life in JavaScript
Viewed 0 times
javascriptconwaylifegamefunctional
Problem
In this code, I tried to integrate my little knowledge of (and great fascination towards) functional programming. I'd like to make this code more functional, and faster, if that's possible.
```
'use strict';
var insert = require('./insert');
const STEP = 50;
const WIDTH = 200, HEIGHT = 100;
var ptr = [[0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[1, 1, 0, 0, 1, 1, 1]];
function init(scr) {
var cwObj = {
canvas: document.getElementById(scr),//set up the graphics department
gMap: new Array(HEIGHT).fill(new Array(WIDTH).fill(0))
};
draw(cwObj, ptr, 30, 60);
step(cwObj.gMap);
return cwObj;
}
function draw(cwObj, pattern, x = 0, y = 0) {
if(pattern){//draw pattern to map
pattern.map(function (cur, i){
cwObj.gMap[x + i] = insert(cwObj.gMap[x + i], cur, y);
//insert() is a helper, just inserts at an index
});
}
var out = '';
cwObj.gMap.map(function (cur) { //create a string of active and
cur.map(function (cell) { //inactive cells
if(cell) {out += "■";} else {out += "■";}
});
out += "";
});
cwObj.canvas.innerHTML = out; //put it in the HTML
}
function step (gm){
return gm.map(function (cur, y) {
return cur.map(function (cell, x){
var n = getNb(gm, x, y);
n = cell === 1 ? n - 1 : n;//getNb includes the cell itself
return n === 3 ? 1 : ((n === 2 && cell === 1) ? 1 : 0);//rules
});
});
}
function getNb (gm, x, y){
var w = WIDTH, h = HEIGHT,
xmin = (x w) ? w : x+2, ymax = (y+2 > h) ? h : y+2;
var env = gm.slice(ymin, ymax).map(function (row){//get environment array
return row.slice(xmin, xmax);
});
env = [].concat.apply([], env);//flatten array
return env.reduce(function (prev, cell) {//sum the neighbors
return prev += cell;
});
}
var Game = init("screen");
setInterval(function (){//rep
```
'use strict';
var insert = require('./insert');
const STEP = 50;
const WIDTH = 200, HEIGHT = 100;
var ptr = [[0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[1, 1, 0, 0, 1, 1, 1]];
function init(scr) {
var cwObj = {
canvas: document.getElementById(scr),//set up the graphics department
gMap: new Array(HEIGHT).fill(new Array(WIDTH).fill(0))
};
draw(cwObj, ptr, 30, 60);
step(cwObj.gMap);
return cwObj;
}
function draw(cwObj, pattern, x = 0, y = 0) {
if(pattern){//draw pattern to map
pattern.map(function (cur, i){
cwObj.gMap[x + i] = insert(cwObj.gMap[x + i], cur, y);
//insert() is a helper, just inserts at an index
});
}
var out = '';
cwObj.gMap.map(function (cur) { //create a string of active and
cur.map(function (cell) { //inactive cells
if(cell) {out += "■";} else {out += "■";}
});
out += "";
});
cwObj.canvas.innerHTML = out; //put it in the HTML
}
function step (gm){
return gm.map(function (cur, y) {
return cur.map(function (cell, x){
var n = getNb(gm, x, y);
n = cell === 1 ? n - 1 : n;//getNb includes the cell itself
return n === 3 ? 1 : ((n === 2 && cell === 1) ? 1 : 0);//rules
});
});
}
function getNb (gm, x, y){
var w = WIDTH, h = HEIGHT,
xmin = (x w) ? w : x+2, ymax = (y+2 > h) ? h : y+2;
var env = gm.slice(ymin, ymax).map(function (row){//get environment array
return row.slice(xmin, xmax);
});
env = [].concat.apply([], env);//flatten array
return env.reduce(function (prev, cell) {//sum the neighbors
return prev += cell;
});
}
var Game = init("screen");
setInterval(function (){//rep
Solution
const STEP = 50;
const WIDTH = 200, HEIGHT = 100;
var w = WIDTH, h = HEIGHT,
xmin = (x w) ? w : x+2, ymax = (y+2 > h) ? h : y+2;Be consistent. I usually recommend a
const/let/var per variable and a line each to to make it easy to add and remove variables as needed. No surgical removal of variables in between commas and it's easy to spot the variables.var Game = init("screen");
setInterval(function (){//repeat the steps every 50ms
Game.gMap = step(Game.gMap);
draw(Game);
}, STEP);The problem with above is that it's not kosher in terms of functional programming. Each step is mutating
Game, specifically Game.gmap. If I remember correctly, the functional way to create a game loop is to use recursion and construct a new game state on each step.function gameLoop(gameState){
draw(gameState);
const newState = update(gameState);
return gameLoop(newState);
}
return gameLoop(createInitialState());The big difference is that you don't have
Game or Game.gmap that mutates on each step. Each step takes in an entirely different state object than the previous. This is inefficient given existing JS data structures, but can be optimized using persistent data structures.But JS has no TCO yet and you'll blow away the stack if you do it in existing engines (Node.js has it under a flag). The closest you can get to maintaining a similar code structure is by scheduling the next step asynchronously using
setTimeout.function gameLoop(gameState){
draw(gameState);
const newState = update(gameState);
setTimeout(() => gameLoop(newState), STEP);
}
gameLoop(createInitialState());function init(scr) {
var cwObj = {
canvas: document.getElementById(scr),//set up the graphics department
gMap: new Array(HEIGHT).fill(new Array(WIDTH).fill(0))
};
draw(cwObj, ptr, 30, 60);
step(cwObj.gMap);
return cwObj;
}I would suggest this
init function only construct state. Drawing should just be a part of the game loop.function createInitialState(){
return { /* initial game state */ }
}pattern.map(function (cur, i){
cwObj.gMap[x + i] = insert(cwObj.gMap[x + i], cur, y);
//insert() is a helper, just inserts at an index
});
var out = '';
cwObj.gMap.map(function (cur) { //create a string of active and
cur.map(function (cell) { //inactive cells
if(cell) {out += "■";} else {out += "■";}
});
out += "";
});You are misusing
array.map. array.map is meant to be a 1:1 transform of each item in an array into another. What you're doing here is a job for array.forEach. And since array.forEach does nothing but loop and return undefined, it has to be mutating something outside the callback, making it not functional.Your first operation could be done with an
array.map as it's essentially updating each value with a new value. You will have to replace the array, not mutate the existing one.cwObj.gMap = pattern.map((current, i) => updatedValueForValueAt(i));The second one appears to be a job for
array.reduce as it is reducing the array into a single string.const out = cwObj.gMap.reduce((prev, cur) => prev + cur.map(c => c? '■' : '■').join(''), '');JS isn't entirely functional and you will often break rules where necessary. At best, you keep mutation to a minimal.
Code Snippets
const STEP = 50;
const WIDTH = 200, HEIGHT = 100;
var w = WIDTH, h = HEIGHT,
xmin = (x <= 0) ? 0 : x-1, ymin = (y <= 0) ? 0 : y-1,//handle borders
xmax = (x+2 > w) ? w : x+2, ymax = (y+2 > h) ? h : y+2;var Game = init("screen");
setInterval(function (){//repeat the steps every 50ms
Game.gMap = step(Game.gMap);
draw(Game);
}, STEP);function gameLoop(gameState){
draw(gameState);
const newState = update(gameState);
return gameLoop(newState);
}
return gameLoop(createInitialState());function gameLoop(gameState){
draw(gameState);
const newState = update(gameState);
setTimeout(() => gameLoop(newState), STEP);
}
gameLoop(createInitialState());function init(scr) {
var cwObj = {
canvas: document.getElementById(scr),//set up the graphics department
gMap: new Array(HEIGHT).fill(new Array(WIDTH).fill(0))
};
draw(cwObj, ptr, 30, 60);
step(cwObj.gMap);
return cwObj;
}Context
StackExchange Code Review Q#140169, answer score: 2
Revisions (0)
No revisions yet.