Last week at the Global Day of CodeRetreat in Mumbai, I wrote the Game Of Life using RxJS. Though I used the Node.js and text console to render the game during the retreat, below is the HTML version for rendering an Oscillating Pulsar. Hit Run to see it oscillate.
Code uses all pure functions and ES6 arrows. Below is the excerpt, Check out the code on GitHub if it interests you.
var cellAt = function(r, c, world) { var row = world[r]; if (row == undefined || row[c] == undefined) { return 0; } return world[r][c]; } var neighbours = function(r, c, world) { return [ cellAt(r-1, c-1, world), cellAt(r-1, c , world), cellAt(r-1, c+1, world), cellAt(r, c-1, world), cellAt(r, c+1, world), cellAt(r+1, c-1, world), cellAt(r+1, c , world), cellAt(r+1, c+1, world) ]; } var isAlive = cell => cell != 0; var isUnderPopulated = liveNeighbours => liveNeighbours < 2; var canSurvive = liveNeighbours => liveNeighbours == 2 || liveNeighbours == 3; var isOverCrowded = liveNeighbours => liveNeighbours > 3; var canReproduce = liveNeighbours => liveNeighbours == 3; var newCell = function(cell, neighbours) { var liveNeighbours = neighbours.reduce((a, b) => a + b); if (isAlive(cell) && (isUnderPopulated(liveNeighbours) || isOverCrowded(liveNeighbours))) { return 0; } if (isAlive(cell) && canSurvive(liveNeighbours)) { return 1; } if(!isAlive(cell) && canReproduce(liveNeighbours)) { return 1; } return 0; } var newWorld = oldWorld => oldWorld.map((row, r) => row.map((oldCell, c) => newCell(oldCell, neighbours(r, c, oldWorld))));
and here is the HTML. In the inline script below, I use Rx.Observable
‘s generateWithRelativeTime
to produce a new world from seed world. It starts with the seed world pattern and ticks every 1 sec producing a new world from the previous world based on the rules of the game encoded above. The subscribe
function’s success callback uses the render
function display the newly generated world by mapping the 2D world-array to HTML div
s. I take only first 30 generations as this pattern continues infinitely.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="css/gameOfLife.css"> <title>Rx Game Of Life</title> </head> <body> <div id="output"></div> <script src="lib/rx/rx.all.js"></script> <script src="src/gameOfLife.js"></script> <script src="src/gameOfLifePatterns.js"></script> <script> function toDiv(cell) { var cellStyle = (cell == 0) ? "dead" : "live"; return "<div class='cell cell-" + cellStyle + "'> </div>"; } function render(world) { var html = world.map(r => r.map(toDiv)) .join("<br>") .replace(/,/g, ""); document.getElementById("output").innerHTML = html; } var seedWorld = oscillatingPulsar(); Rx.Observable.generateWithRelativeTime(seedWorld, w => true, // don't terminate newWorld, // next function w => w, // return value () => 1000 // tick every ) .take(30) .subscribe( render, e => console.info(e.message), () => console.info("*** Complete ***") ); </script> </body> </html>
You can replace line 30 with calls to other patterns to see them in action.
Below is the game using ReactJS. As ReactJS helps render the View in MVC, only view related functions: toDiv
and render
have changed to accommodate it. Additionally, I’ve passed row and column ids as rid
and cid
respectively, so that ReactJS does not give a warning:
Warning: Each child in an array or iterator should have a unique "key" prop. Check the React.render call using <div>.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="css/gameOfLife.css"> <title>Rx Game Of Life Using ReactJS</title> </head> <body> <div id="output"></div> <script src="lib/rx/rx.all.js"></script> <script src="lib/reactjs/JSXTransformer.js"></script> <script src="lib/reactjs/react.js"></script> <script src="src/gameOfLife.js"></script> <script src="src/gameOfLifePatterns.js"></script> <script type="text/jsx"> function toDiv(cell, rid, cid) { var cellStyle = (cell == 0) ? "dead" : "live"; return (<div key={rid + "" + cid} className={`cell cell-${cellStyle}`}> </div>); } function render(world) { var grid = world.map((r, rid) => { return (<div key={rid}> {r.map((cell, cid) => toDiv(cell, rid, cid) )} </div>); }); React.render(<div>{grid}</div>, document.getElementById("output")); } var seedWorld = oscillatingPulsar(); Rx.Observable.generateWithRelativeTime(seedWorld, w => true, // don't terminate newWorld, // next function w => w, // return value () => 1000 // tick every ) .take(30) .subscribe( render, e => console.info(e.message), () => console.info("*** Complete ***") ); </script> </body> </html>
See it in action below 🙂
Below is the game using RxGroovy.
import rx.Observable import java.util.concurrent.* List.metaClass.collectWithIndex = { body -> def idx = 0 delegate.collect { body(it, idx++) } } def cellAt(rowIdx, colIdx, world) { def r = rowIdx >= 0 ? rowIdx : Integer.MAX_VALUE def c = colIdx >= 0 ? colIdx : Integer.MAX_VALUE def row = world[r] if (row == null || row[c] == null) return 0 return world[r][c] } def neighbours(r, c, world) { [ cellAt(r-1, c-1, world), cellAt(r-1, c , world), cellAt(r-1, c+1, world), cellAt(r, c-1, world), cellAt(r, c+1, world), cellAt(r+1, c-1, world), cellAt(r+1, c , world), cellAt(r+1, c+1, world) ] } def isAlive(cell) { cell != 0 } def isUnderPopulated(liveNeighbours) { liveNeighbours < 2 } def canSurvive(liveNeighbours) { liveNeighbours == 2 || liveNeighbours == 3 } def isOverCrowded(liveNeighbours) { liveNeighbours > 3 } def canReproduce(liveNeighbours) { liveNeighbours == 3 } def newCell(cell, neighbours) { def liveNeighbours = neighbours.inject(0) {a,e -> a + e} if (isAlive(cell) && (isUnderPopulated(liveNeighbours) || isOverCrowded(liveNeighbours))) return 0 if (isAlive(cell) && canSurvive(liveNeighbours)) return 1 if (!isAlive(cell) && canReproduce(liveNeighbours)) return 1 return 0 } def newWorld = { oldWorld -> oldWorld.collectWithIndex { row, rIdx -> row.collectWithIndex { oldCell, cIdx -> newCell(oldCell, neighbours(rIdx, cIdx, oldWorld)) } } } def blinker = [ [0, 1, 0], [0, 1, 0], [0, 1, 0] ] Observable.interval(1, TimeUnit.SECONDS) .scan(blinker, { old, tick -> newWorld(old)}) .take(10) // 10 generations .subscribe ( { println("$it") }, { e -> println("onError() => $e") }, { println('onComplete() => *** Complete ***') } )