I was recently making a card guessing game in RxJS. You know the one where you flip over two cards at a time and if they are a match you keep them.

About a year earlier I had live coded this game for a class in a non reactive way. I finished the game very quickly but it was pretty buggy. Having to track the state of flipped cards so they always behaved correctly got pretty complex. What if they clicked really fast, or clicked the same card twice, etc. etc. The version using streams worked perfectly out of the gate because the view always reflected the stream and there was no complex state to manage. The tricky part though was getting the stream in the format that was needed.

I knew I needed to stream the selected cards as pairs. My first thought was to use the pairwise operator. With that my stream of cards would look like this:

--cardA--cardB----cardC--cardD------
            ↓ pairwise ↓
--[cardA, cardB]----[cardC, cardD]--

At first this might seem ideal. In practice though it was not. Think about it, the first value observed is a combination of the first and second card. This means that when a user selects the first card, they will not see it flipped until they select the second card. This would prove to be a very difficult twist.

What I really needed was a stream that looks like this:

--cardA--cardB----cardC--cardD-------------------
             ↓ needed ↓
--[cardA, null]--[cardA, cardB]----[cardC, null]- etc.

I then tried using the window operator which allows you to select a window of values but that didn't work either because essentially I needed my stream to behave differently for every other value. I needed the even values to include the previous value but not the odd values.

Sometimes the stream you need, has to be broken into multiple streams that can be modified separately before combined back into the final stream.

After reading through all the available operators I came across partition. I used this to split my stream into evens and odds:

let [odd$, even$] = flip$  
  .partition((card, index) => index % 2 === 0);

yields:

--cardA--cardB----cardC--cardD------
            ↓ partition ↓
--cardA-----------cardC-------------
---------cardB-----------cardD------

Once partitioned I could map the odd into an array with a null value, and add the latest odd with the even.

let oddPairs$ = odd$.map(odd => [odd, null]);  
let evenPairs$ = even$  
  .withLatestFrom(odd$, (even, odd) => [odd, even]);

yields:

--cardA-----------cardC-------------
               ↓ map ↓
-[cardA, null]---[cardC, null]------

---------cardB-----------cardD------
          ↓ withLatestFrom ↓
-[cardA, cardB]--[cardC, cardD]-----

Now the 2 streams make up the contents of what is needed, so the only thing left to do is combine them back into one:

let pairs$ = Rx.Observable  
  .merge(oddPairs$, evenPairs$);

yields:

-[cardA, null]---[cardC, null]-------------------
-[cardA, cardB]--[cardC, cardD]------------------
             ↓ merge ↓
-[cardA, null]--[cardA, cardB]----[cardC, null]-- etc.

Yay!

I hope that inspires you to new and creative ways to manipulating streams.

© 2017. All Rights Reserved.

Proudly published with Ghost