Software Artisan's Blog

~ onLife >> onMusic >> onDesign >> onPatterns >> onRefactoring >> onAgile

Tag Archives: rxjs

Game of Life in RxJS (and ReactJS) and RxGroovy

15 Sunday Nov 2015

Posted by Dhaval Dalal in Functional Programming, Functional Reactive, Groovy, JavaScript

≈ Leave a comment

Tags

Functional Programming, reactjs, rxjs


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 divs. 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 + "'>&nbsp;</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}`}>&nbsp;</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 ***') }
)

Advertisements

Share this:

  • Twitter
  • LinkedIn
  • Google
  • Reddit
  • Email
  • Facebook
  • Print

Like this:

Like Loading...

Reactive (in RxJS) and Non-Reactive (in Scala) Snakes And Ladders

09 Wednesday Sep 2015

Posted by Dhaval Dalal in Functional Programming, Functional Reactive, JavaScript, Scala

≈ Leave a comment

Tags

rxjs


While playing around with RxJS, I thought it would be interesting to create a Snake and Ladders game that I played during my childhood days. A typical game is played by 3-4 players with a board size of 100 cells and a dice. The board has snakes and ladders on it. Rules are very simple; when a player ends up in a position where the ladder starts, it gets a lift to a position where the ladder ends and if a player ends up in a position where a snake’s mouth starts, it gets gulped down by the snake and reach the tail as a new position. Otherwise, the player moves to a new position from the old by the throw of a dice. Players take turn at throwing the dice.

In the reactive version using RxJS, lets start with a generator that would push a player (represented as a value starting from 0 for first player, 1 for second player and so on…) every few milliseconds, in my case 100ms. In the increment function, lets bump up the value by 1 and finally in the ‘Return Value’ function where the generated numbers are cast out by the number of players using mod operator. So if there are 3 players, the first player would start at 0 and the third would have number of 2. I’m using Node.js to run this and have imported the RxJS module. Here is what I ended up creating for starters.

var Rx = require("rx");

var howManyPlayers = 3;

Rx.Observable.generateWithRelativeTime(0,
  function (x) { return true; },  // Condition
  function (x) { return x + 1; }, // Increment function
  function (x) { return x % howManyPlayers; }, // Return value function
  function ()  { return 100; } // Produce next value every 100 ms
)
.take(6)
.subscribe(
  function(player) { 
    console.info("player =", player); 
  },
  function(e) { 
    console.info("error =", e.message);
  },
  function() { 
    console.info("*** Game Over ***");
});

As the generator goes on generating values infinitely, we take only 6 values to make sure that we get the expected results 0, 1, 2, 0, 1, 2 in our subscription. Next, its time to add dice throws for the players. So, lets define a throwDice() function and just map the above. We will use the tap() function (for side-effects) to print to console and make sure we get what we expect.

var throwDice = function() {
  return Math.ceil(Math.random() * 6);
};

Rx.Observable.generateWithRelativeTime(0,
  function (x) { return true; },  
  function (x) { return x + 1; }, 
  function (x) { return x % howManyPlayers; }, 
  function ()  { return 100; } 
)
.map(function(player) {  
  return [player, throwDice()];
})
.tap(function(playerWithDice){
  console.info("player", playerWithDice[0], " threw on dice", playerWithDice[1]);
})
.take(6)
.subscribe(
  function(playerWithDice) { 
  },
  function(e) { 
    console.info("error =", e.message);
  },
  function() { 
    console.info("*** Game Over ***");
});

As a next step, lets lets get rid of hard-coded players, instead use an array to hold each players’ initial position and choose value 0 to represent it.

var initialPlayerPositions = [0, 0, 0];
var players = initialPlayerPositions.length;

We will introduce newPlayerPositions() function, which for now, returns the same positions that it receives, but a copy of the old array (we don’t want to mutate input variables passed to us).

var newPlayerPositions = function(player, dice, playerPositions) {
  return playerPositions.slice(0);  // create a copy of current  
};

We will call the newPlayerPositions() function from within scan() of the observable. scan() is equivalent to foldLeft() or reduce(). The code now looks like this –

var initialPlayerPositions = [0, 0, 0];
var players = initialPlayerPositions.length;

Rx.Observable.generateWithRelativeTime(0, 
  function(x) { return true; },
  function(x) { return x + 1; },
  function(x) { return x % players; },
  function()  { return 100; }
)
.map(function(player){  
  return [player, throwDice()];
})
.tap(function(playerWithDice){
  console.info("player", playerWithDice[0], " threw on dice", playerWithDice[1]);
})
.scan(function(playerPositions, playerWithDice) {
  var player = playerWithDice[0];
  var dice = playerWithDice[1];
  return newPlayerPositions(player, dice, playerPositions);
}, initialPlayerPositions)
.take(6)
.subscribe(
  function(playerPosition) { 
  },
  function(e) { 
    console.info("error =", e.message);
  },
  function() { 
    console.info("*** Game Over ***");
});

Next step is to setup a board with positions of snakes and ladders. For simplicity, lets consider a board with 10 cells, 2 ladders and a snake.

var cells = 10;
var board = {
  3: 7, // Ladder
  8: 4, // Snake
  5: 9  // Ladder
};

Having done that, lets now start implementing the rules for Snakes and Ladders. The newPlayerPositions() function below encodes all the rules of the game.

Lets first start with adding to a players current position the dice value.

var newPlayerPositions = function(player, dice, playerPositions) {
  var newPlayerPositions = playerPositions.slice(0);  // create a copy of current player positions
  var currentPosition = newPlayerPositions[player];
  var newPosition = currentPosition + dice;
  newPlayerPositions[player] = newPosition;
  return newPlayerPositions;
}

Next, lets encode the next rule that an exact throw of dice is required for win.

var newPlayerPositions = function(player, dice, playerPositions) {
  var newPlayerPositions = playerPositions.slice(0);  // create a copy of current player positions
  var currentPosition = newPlayerPositions[player];
  var newPosition = currentPosition + dice;
  if (newPosition <= cells) {  //exact throw of dice required for win.
    newPlayerPositions[player] = newPosition;
  } 
  return newPlayerPositions;
};

Finally, we bring in the board with Snakes and Ladders to introduce the jumps for a ladder climb and a gulp of a snake.

var newPlayerPositions = function(player, dice, playerPositions) {
  var newPlayerPositions = playerPositions.slice(0);  // create a copy of current player positions
  var currentPosition = newPlayerPositions[player];
  var newPosition = currentPosition + dice;
  if (newPosition <= cells) {  //exact throw of dice required for win.
    newPlayerPositions[player] = newPosition;
  } 
  
  if(board[newPosition]) {
    newPlayerPositions[player] = board[newPosition];
  }
  
  return newPlayerPositions;
};

Now, we know that the game cannot finish after 6 rounds as defined early by the call to take(6). We need to implement the logic to determine the winner and stop the game. So, lets define a function that determines a winner –

var isWinner = function(playerPositions) {
  return playerPositions.some(function(playerPosition) { 
    return playerPosition == cells;
  });
};

Using this function, we will get rid of the hard-coded take(6) and instead use takeWhile(), which consumes a predicate and goes on allowing values to pass through until the predicate remains satisfied. As soon as the predicate returns false, the observable is disposed. We tap() again to make sure we get to see the output. So, the complete code now looks like –

var Rx = require("rx");

var throwDice = function() {
  return Math.ceil(Math.random() * 6);
};

var initialPlayerPositions = [0, 0, 0];
var players = initialPlayerPositions.length;
var cells = 10;
var board = {
  3: 7, 
  8: 4, 
  5: 9
};

var newPlayerPositions = function(player, dice, playerPositions) {
  var newPlayerPositions = playerPositions.slice(0);
  var currentPosition = newPlayerPositions[player];
  var newPosition = currentPosition + dice;
  if (newPosition <= cells) {
    newPlayerPositions[player] = newPosition;
  } 
  
  if(board[newPosition]) {
    newPlayerPositions[player] = board[newPosition];
  }
  
  return newPlayerPositions;
};

var isWinner = function(playerPositions) {
  return playerPositions.some(function(playerPosition) { 
    return playerPosition == cells;
  });
};

Rx.Observable.generateWithRelativeTime(0, 
  function(x) { return true; },
  function(x) { return x + 1; },
  function(x) { return x % players; },
  function()  { return 100; }
)
.map(function(player){  
  return [player, throwDice()];
})
.tap(function(playerWithDice){
  console.info("player", playerWithDice[0], " threw on dice", playerWithDice[1]);
})
.scan(function(playerPositions, playerWithDice) {
  var player = playerWithDice[0];
  var dice = playerWithDice[1];
  return newPlayerPositions(player, dice, playerPositions);
}, initialPlayerPositions)
.tap(function(playerPositions) {
  console.info('Player Positions = ', playerPositions);
})
.takeWhile(function(playerPositions) {
  return !isWinner(playerPositions);
})
.subscribe(
  function(playerPositions) { },
  function(e) { console.error("Oops! ", e.message); },
  function()  { console.info("*** Game Over ***");}
);

Here is the same code in ES6, using Fat Arrows and Destructuring.

var Rx = require("rx");

var throwDice = () => Math.ceil((Math.random() * 100) % 6)

var initialPlayerPositions = [0, 0, 0];
var players = initialPlayerPositions.length;

var cells = 10;
var board = {
  3: 7, 
  8: 4, 
  5: 9
};

var newPlayerPositions = function(player, dice, playerPositions) {
  var newPlayerPositions = playerPositions.slice(0);
  var currentPosition = newPlayerPositions[player];
  var newPosition = currentPosition + dice;
  if (newPosition <= cells) { 
    newPlayerPositions[player] = newPosition; 
  } 

  if(board[newPosition]) { 
    newPlayerPositions[player] = board[newPosition]; 
  } 
  return newPlayerPositions; 
};
 
var isWinner = playerPositions => playerPositions.some(_ => _ == cells)

Rx.Observable.generateWithRelativeTime(0, 
  x => true,
  x => x + 1,
  x => x % players,
  () => 100)
.map(player => [player, throwDice()])
.tap(([player, dice]) => console.info("player", player, " threw on dice", dice))
.scan((playerPositions, [player, dice]) => 
   newPlayerPositions(player, dice, playerPositions), initialPlayerPositions)
.tap(playerPositions => console.info('Player Positions = ', playerPositions))
.takeWhile(playerPositions => !isWinner(playerPositions))
.subscribe(playerPositions => { },
  e => console.error("Oops! ", e),
  () => console.info("*** Game Over ***")
);

Here is the same game in Scala, except that it is not based on reactive push model, but on the pull-model employing streams. Also, please note that I’m doing printing (side-effects) from within scanLeft() and takeWhile()

val initialPlayerPositions = List(0, 0, 0)

val board = Map(3 -> 7, 8 -> 4, 5 -> 9)

val cells = 10

def throwDice = Math.ceil((Math.random() * 6)).toInt

def newPlayerPositions(player: Int, dice: Int, playersPos: List[Int]) = {
  val newPosition = playersPos(player) + dice
  if (newPosition <= cells) 
    playersPos.take(player) ::: List(board.getOrElse(newPosition, newPosition)) ::: playersPos.drop(player + 1)   
  else 
    playersPos 
} 

def isNotWinner(playerPos: List[Int]) = !playerPos.exists(_ == cells) 

Stream 
  .from(0)
  .map(x => (x % initialPlayerPositions.size, throwDice))
  .scanLeft(initialPlayerPositions) { 
    case (playersPos, (player, diceValue)) =>
      println(s"Player $player threw on dice $diceValue")
      newPlayerPositions(player, diceValue, playersPos)
  }
  .takeWhile(playersPos => {
    println(s"$playersPos")
    isNotWinner(playersPos)
  })
  .force

Share this:

  • Twitter
  • LinkedIn
  • Google
  • Reddit
  • Email
  • Facebook
  • Print

Like this:

Like Loading...

Dhaval Dalal

Profile

Dhaval Dalal

Slides

View DhavalDalal's profile on slideshare

Enter your email address to follow this blog and receive notifications of new posts by email.

Join 290 other followers

Ideas

  • Code Jugalbandi

Open Source Projects

  • Java8 Exception Monad
  • Midas
  • Tayra

Recent Posts

  • Exploring Concurrency CodeJugalbandi February 6, 2019
  • Destructuring and Pattern Matching in Functional Programming December 29, 2018
  • Help yourself by making iTerm2 vivid May 30, 2018
  • How to disable failed discrete-GPU (NVIDIA GeForce GT 650M) for 15″ MacBook-Pro 10,1 (mid-2012) on High Sierra 10.13.4 May 29, 2018
  • Improvised Tasks.tmbundle for TextMate April 15, 2018

Calendar

February 2019
M T W T F S S
« Dec    
 123
45678910
11121314151617
18192021222324
25262728  

Categories

  • Agile (13)
  • APL (2)
  • Array-Oriented (1)
  • C# (14)
  • Clojure (2)
  • Code Jugalbandi (5)
  • Code Retreat (2)
  • Computer (2)
  • Education (1)
  • Erlang (3)
  • Functional Programming (10)
  • Functional Reactive (3)
  • General (34)
  • Groovy (15)
  • GWT (6)
  • Haskell (2)
  • Java (28)
  • JavaScript (10)
  • Life (2)
  • Microservices (1)
  • MongoDB (2)
  • Music (2)
  • Neo4J (1)
  • Sanskrit (4)
  • Scala (8)
  • Thoughts-Philosophy (9)
  • Yoga (2)

Archives

Follow me on Twitter

My Tweets

Blog Stats

  • 26,703 hits

Tags

Agile Code Jugalbandi Erlang Functional Programming General Groovy Homeopathy iTerm2 java Java8 Life Microservices reactjs rxjs Sanskrit Scala Textmate2 Thoughts-Philosophy

RSS

  • RSS - Posts
  • RSS - Comments
Advertisements

Create a free website or blog at WordPress.com.

loading Cancel
Post was not sent - check your email addresses!
Email check failed, please try again
Sorry, your blog cannot share posts by email.
Privacy & Cookies: This site uses cookies. By continuing to use this website, you agree to their use.
To find out more, including how to control cookies, see here: Cookie Policy
%d bloggers like this: