Yesterday after the class i saw a friend of mine playing a super simple game: Piano Tiles. Comming home i grabbed my phone and downloaded it. What’s cool about it is that its logic is simple as hell but the idea behind it is actually interesting. Why not digging into it with React? Take a look at the result: cirocosta.github.io/react-piano-tiles. (edit: now working on FF as it should — yay!)
In this post i’ll try to cover some of the things i’ve gone through while building react-piano-tiles.
As i said, simple:
- A circular buffer represents what’s the current state of the piano;
- an iterator controlling the circular buffer;
- a random row generator (no sound analysis as what is done in RockBand)
- a way to draw part of the circular buffer.
The Circular Buffer, iterator and the row generator are the simplest part, nothing more than ~150lines of code. What’s actually intereseting is managing the UI and how to deal with the interactions.
In a simplified version we are going to have at least 3 screens: Start, Game and Fail. Start and Fail are simple, just some sort of dialog which we are able to generalize. The caution here is to control the time to show one or another. More on this later. The game is also simple, we only need a representation of a matrix — which i’ve already done for a Snake played on matrices — that we’re going to be mutating (the circular buffer contains lists, that represents our rows).
Along with these static representations we need to take care of the transitions, but this is all for the drawing part. Not a big deal, React deals nice with this.
The way the things are going to get dynamic is by letting our views and other things dispare events in our system. The coordination of all of them is going to be done using a design pattern called Flux, which, in summary is: an application archictecture for building clientside web apps. Flux is going to fit nicely with our game as this is what is going to let us not couple our app-layer data and actions to the view hierarchy.
Flux is our rockstar. By decoupling our data&state (Stores) from our views and action creators we are able to exposse those to whoever wants to mess with them or just know how they look at a particular moment. Another thing is that by having the possibility of adding Action Creators not coupled to views lets us force the system to react to anything that we want, at any time.
The Game Screen
This is where the magic lives. Here is where the user actually plays the game, interacting with the piano which leds the system to propagate a bunch of events through it. This is the roadmap for an interaction:
- user clicks in a tile
- the click gets validate - component-level
- a transition is triggered (change our buffer representation)
- at the same time of the transition, an action is emitted representing the Click with the (x,y) coordinates — GameStore will listen to it and then treat this, updating the state of the matrix or emitting a Fail action to propagate the Fail state in the system
- (on transition end) emit a “next tick action” to move the game iterator.
Flux let’s us think of all of what is going on in a serial manner, a chain of reactions. What if i wanted to add a counter to count the number of tiles that the user successfully clicked till the current moment? Just add a representation of the number of tiles in the GameStore and then, after validating the click in the store-level, just tiles++. As someone listens to change events in this store and then requests the GameStore state whenever it needs something fresh, this will be propagated seamlessly. It scales greatly.
My idea, firstly, was to just let the timer be a component with start() and stop() static methods. These would be sufficient for just addind a timer to the game. Although it’d work, this would not be the flux way.
A TimerStore was created and also an utilitary rAFLoop, a manager for creating a kind of main loop using requestAnimationFrame (rAF). Using rAF is great as it tackles the problem with page visibility by default and, along with syncing in the right way with the frames, it calls our tick method that we pass to it with the result of performance.now, the best one can get in terms of measuring time in a browser.
The Application layer was the simplest part. Its store maintains the state of the screen, indicating what screen to present. As the Piano is the base of everything (Dialogs fit into the Piano, laying on top of it), this is present everytime. The ideia is just to decide, based on the state that Application gives to us, if we should show or not a dialog and which dialog to show.
Transitioning between one or another is just a matter of triggering an Action that would change the state of the Application with a new screen and then adding/removing some CSS classes to provide the transition effect (again, React deals with that). As this is very simple, i’ll won’t get into much detail (see the source code!).
Programming with React and thinking in terms of event systems is great. Very cool, very predictable if done right. Flux helps us to coordinate the mess that we could create.
Not everything fits with this mindset, but it is possible to create a bunch of things with it, in a fast way. Although one could say it was ‘horse coding’ to do this in 10hrs (wasting a bunch of time learning how to deal with some CSS stuff), with this architecture i wasn’t trapped by regression bugs. Development flowed like a charm.
If you enjoyed, go take a deeper look! Facebook is doing great with their open source projects. Kudos!