IBM has a tradition of producing technology that defeats the smartest humans. For example,
- IBM Deep Blue bested world chess champion Gary Kasparov
- IBM Watson defeated Jeopardy TV game show champion Ken Jennings
The Cloud Data Services Developer Advocacy team takes up the banner with a solution challenging human dominance in child's play, namely Rock-Paper-Scissors.
For IBM Insight 2015 in Las Vegas, we built a Rock-Paper-Scissors game powered by the IBM Analytics for Apache Spark service available in IBM Bluemix. People play the classic childhood game against Spark, and the first to win 3 rounds wins the game. Try it yourself: Play Rock-Paper-Scissors.
Of course, there are no grand-master champions or tournaments for such a simple game, but each of us has our own strategies: Always throw rock. Throw the same move twice in a row. Anticipate and out-think what your opponent will throw.
Using the data and analytics power of Apache® Spark™, we set out to create a pattern-recognition engine that could browse a large collection of interactions to determine what would most likely be the winning move.
The requirements for this app were:
- Application code would be open source, available on GitHub for anyone to see and reuse (coming soon)
- Players would compete on iPads mounted on stands
- A monitor would display statistics showing what Spark is thinking
- First-class design and art for the game front-end web application
- Spark-powered analysis would learn from past games and identify patterns. Of course, Spark would not compete against a single individual but many humans. So we made the assumption that human strategies have a pattern, and if so, Spark would find it over time.
- The game would run on Bluemix: The front-end would be a Bluemix web app and IBM Analytics for Apache Spark would handle the back-end analysis.
The team had only 2 months to meet this challenge and build all components of the app. We reached out to the IBM Design team in Austin (mainly Russell Parish and Joe Meersman) and pitched the project. They didn't hesitate to help and showed us how design thinking could produce very exciting results (more about that in a minute).
To start, we needed to come up with an elegant architecture to connect the following components:
- Spark Streaming to process the game data events in real-time
- Spark Core to build the different game strategies
- IBM Cloudant to store the historical plays and other various game data
- Node.js for the game's front-end web app middleware
- WebSockets to handle messages between Spark and the Web Application middleware
- Mozaik, ReactJS, D3JS/C3JS to build the dashboard showing the various Spark statistics
- AngularJS, JQuery, and Bootstrap to structure the front-end
After multiple iterations, the overall architecture looks like this:
In this post, I focus on how we built the front-end application. In a separate post, I'll delve under the hood.
Player Experience Design
The IBM Design team, located in Austin, Texas, used design thinking practices to create the best user experience for conference attendees. They spent many hours designing a game play that accurately represents Rock-Paper-Scissors. Their research showed slight variations in how people play, including what words players shout prior to a round, the timing to reveal a move, and the number of rounds it takes to determine a winner. Creating one holistic experience that a player could instantly understand on iPad, and orchestrating play against a machine was all part of the challenge. The design team created rough wireframe prototypes and tested the experience with users to determine the best flow, timing, and animation to make the game feel right.
Creating an emotional response was one thing missing from early prototypes. The team thought that maybe Spark should have a know-it-all attitude. They considered audio responses from Spark that would taunt or undermine the users. Ultimately they decided to design an experience that felt competitive and resonated with the spirit of Las Vegas’ boxing matches (since the conference was being held in Mandalay Bay Casino).
Historic boxing posters were the inspiration for the typography and split-tone illustrations.
The design team not only created a digital experience, but also screen printed promotional posters to attract users from around the conference to the event.
Implementation with Node.js
The web application middleware was implemented using Node.js. Why Node.js? For years, I've been a Java afficianado, using multiple J2EE frameworks like Tomcat, WebSphere, Liberty, and even OSGi Equinox. I've been working with Node.js for only just over a year, but there are multiple reasons why I'd pick it over a more established Java J2EE framework:
- Performance: Node.js is wicked fast compared any of its Java counterparts (yes, even with an optimized framework like OSGI Equinox with Jetty)
- Less boilerplate code: As any developer would tell you, writing less code is always better and means more productivity and less bugs. With Node.js, you can write a fully functional web app with a few lines of code in a single file. No need for arcane deployment files or war packaging, and so on. Try to do the same with a J2EE framework!
- Rich npm registry of open source modules to choose from: If you're looking to build a certain capability or algorithm, most likely there is already someone who has published a node module that you can add to your package.json as a dependency. While some can argue that the proliferation of these modules without control can be a bad thing, the benefits simply outweigh the risks, and there are ways to control the versioning of the dependencies.
- Better for microservice architecture: Node.js apps are light (easy to build, deploy, and run), modular (easy to break up and refactor) and I/O driven (asynchronous programming). Therefore, they're perfectly suited to work within microservice architectures–all the rage today for cloud deployments.
- Open source community adoption: the number of web sites powered by Node.js keeps increasing, with big companies running mission-critical applications.
Having said all that, Java-based framework still has a lot of good things to offer:
- 20+ years of development makes it a very mature platform
- First class IDEs with Eclipse and IntelliJ, which are great editors with content assist, continuous compilation, and more. Also has great debuggers.
- Multi-threading support: this gives Java the advantage when you need to run CPU-intensive algorithm, as Node.js is single threaded.
The development of Rock-Paper-Scissors' user interface was all about keeping things clean and simple. You play the game on an iPad, but it's actually a web application, not a native iOS app. This means you can also access the game with desktop browsers and non-iOS devices. We chose the technologies (AngularJS, Bootstrap, JQuery) because they:
- are easy to use and learn. There's lots of documentation and support available.
- integrate well with each other.
- are simple. The game is a single-page app.
- are comprehensive and extensible.
All these things contribute to faster development and rapid iteration through development and test cycles.
The UI provided by the design team was spectacular enough and game flow simple enough that development of the front-end mostly entailed swapping backgrounds and replacing images. Gotta love great design and Design Thinking!
The single page was comprised of multiple DOM sections coinciding with the different game states (like New Round, Select Move, Show Results, and so on). The
ngSwitch directive in AngularJS was used to conditionally switch the DOM sections depending on the game state.
The game front-end is unaware of any historical game plays, nor does it care who its opponent is. It is self-contained and could be played without a connection to the back-end. (But in that scenario, you wouldn't be playing against Spark.)
In addition, the game front-end makes only two REST calls:
GET Spark's move.
During this request no data is sent to the back-end. Spark's move is retrieved first then the user makes their pick. This ensures no cheating by Spark. Once the user has made their move, the round play is evaluated and round winner determined.
POST the round result
These 2 steps repeat until someone wins three rounds.
To achieve a more native look and feel for the game (when accessed from an iPad), we consulted the iOS Developer Libary. The resulting tweaks, although mostly cosmetic, result in a better overall user experience.
There are multiple open source dashboard frameworks that we could have used to build the Rock-Paper-Scissors Spark dashboard. Some that I looked at are:
All of these frameworks were very attractive and use the state-of-the-art HTML5/CSS3 technologies. However, I settled in the end with Mozaik because it:
- is easy to use and integrates into your application. It provides declarative means for configuring the data APIs that drive each widget content.
- features real-time support with WebSockets
- has a modular and extendable widget framework based on ReactJS (this gave me an excuse to start learning ReactJS!)
- offers customizable themes based on Stylus
The next step was to build widgets that display the payload data coming from the Spark back-end analytics. For example, we showed a Wins over time histogram, a Round Patterns chart, Spark's next move, most winning move, and the most popular move.
Because the layout is configured declaratively, we were able to experiment with multiple designs iteratively, incorporating feedback from test users until we found the right approach.
We built the widgets by extending the ReactJS Component class, which provides very nice lifecycle APIs. Some that we used in our app are:
- getInitialState: initialize the widget state before the component is mounted.
- getApiRequest: specify the API id configured in the main App.jsx module
- onApiData: called when the mozaik framework is receiving new data from the WebSocket channel
- componentDidMount: Invoked immediately after the widget is rendered. This is where the widget content is dynamically created. For example, in the WinsOverTime widget, the c3 chart is created at this time.
- componentDidUpdate: Invoked when the new data has been received so that the widget can be refreshed accordingly
- componentWillUnmount: Invoked when the component is about to be destroyed, so the widget can clean up any associated resources.
- render: return the html fragment that will contain the widget.
The last architectural component related to data flow and persistence. Here's how the data flows between the different components:
- User makes a play, the client app sends data to an API endpoint on the middleware
- Data is posted to the Spark Streaming back-end and processed in real time
- Data is saved into Cloudant
- Ouput of real-time analysis (metrics, next spark play) is sent back to the middleware via WebSocket
- Dashboard updates to show the latest data.
At the time we built this app, we chose WebSockets as the message broker mechanism between Spark and the middleware. In retrospect, a better architecture would be to use Kafka (provided as a service on Bluemix with MessageHub)
Now you know how we designed this app's front-end and middleware. Check back here later for a follow-up blog post that delves into the back-end code. We'll also put together a tutorial that walks you through the app. Sign up to get notified.
Meanwhile, stand up for humanity! Test your Rock-Paper-Scissors skills against Apache® Spark™. Play now. Or, better yet, go play in person. Bradley Holt will host and referee some Rock-Paper-Scissors rounds at Node.js Interactive in Portland, Oregon on Dec. 8-9th, when he speaks on Offline-First Apps with PouchDB. If you can't make it, no worries. We'll feature Rock-Paper-Scissors at future events too.