Hacking Foosball


Here at Learnosity, the development team enjoy a “Hack Day” every quarter. Our hack days are a great way for us to get a break from everyday work, and allows us to experiment and collaborate on fun projects. All projects are team selected, and are challenging opportunities to learn new skills, and get under the hood of new programming libraries. Learnosity hack days bring together people from our various teams and development backgrounds to participate on projects of common interest, with a curiosity and fanaticism fuelled by caffeine, food, prizes and beers.

Foosball at Learnosity

Writing code requires a level of concentration that is hard to sustain for hours on end. With that in mind, it’s important to allow us, as developers to relax, unwind and blow off a little steam. Many members of our team do this with foosball breaks.

Foosball has been a part of Learnosity culture for over two years, and as such, a number of intense games get played every day.

When the games started to become more and more competitive – with preferred team combinations and long running feuds – we figured we needed to set up a way to track games and team statistics. A hack day seemed like the perfect opportunity to jump in and do just that.

The idea was quite simple. Capture score data from the table (while limiting player interference), and transfer the data for use in a web app that could be accessed anywhere on the office network. This would allow us to monitor live scores from within the office, and also have a custom scoreboard for the foosball games displayed on a tablet.

Hardware setup

To capture the goal events from our table, we added an Arduino and some IR sensors to each goal. We removed the goals from the table temporarily and added an IR LED emitter and an IR phototransistor to each, with a basic comparator to clean up the signal going to the Arduino.

sensorsThe Arduino is an Ethernet capable EtherTen that is also powered by POE (power over ethernet). This means only a single ethernet cable is running to the table (so fewer trip hazards!). WiFi would have been a similar amount of hassle, as a power cable would still be required, and using batteries would then need regular charging and maintenance.

The Arduino is running a fairly simple script that uses the hardware interrupt pins to detect a goal. Once a goal has been detected, it sends a HTTP GET to an endpoint of the foos app running on a local server.

Although this is a simple script being only an 8-bit processor, anything to do with ethernet can quickly overwhelm the processor. By utilizing the hardware interrupts and making the functions as tight and fast as possible, the table is capable of handling almost concurrent goals when a “multi-ball” game is played on a late Friday afternoon.

Screen Shot 2015-03-13 at 11.37.13 am

goal mouth with sensors

UI Mock uplogin page

The landing screen, that we named ‘login view’ deals with Google+ Authentication to manage players,  initialises an Audio Player for game sound effects, and routes to the ‘team selection’ page when the select team button is clicked.

 

The ‘team selection’Select Teams view splits the teams into two columns. A team consists of one to two players. Some logic has to be made to ensure a player can’t play against themselves and that a team must have at least one player and no more than two.

 

 

live gameThe ‘game view’ displays live score, which is automatically updated by the app. It also has the ability of editing score in case a goal has to be cancelled, etc.

It also features a timer and a ‘submit’ button which needs to be clicked when the game is finished.

Front end app

Due to the limited available time during the hack day, we decided to go for our standard front end stack that we use in our APIs (require/almond, Backbone, lodash and jQuery). This allowed us to have a running single page app within the few hours of the day.

The app is made of the following modules:

  • Main –  main controller
  • Login – start page
  • Auth – deals with G+ Auth and gets users from Learnosity G+ community.
  • Users – team selection page.
  • Game – live score view
  • Rankings – displays rankings by team
  • Audio – audio player that plays audio when a goal is scored

Simplified class diagram

Here is a simple overview of the app. The App class initialises the various main models, views and collections and the Router takes care of rendering layout views such as Login, UsersViews and GameViews.Class Diagram

Authentication

The user only needs to click on Authenticate once. Members of Learnosity Google Plus community are fetched from G+ and saved in the browser localStorage.
To do so, we are using Backbone.Sync pointing to the local storage so that when the users collection is initialised, users are fetched from the local storage. If a new user is added to the community, it will be automatically added to the the local users data by a G+ sync which is operated as a background task.Authentication Sequence Diagram

Audio

We want the app to automatically play a sound when a goal is scored. It gives us an audio feedback to confirm a goal has been detected and adds some fun to the game.
But auto-playing audio on tablets is a bit tricky in that it requires a user interaction (touch) to initialise the HTML5 player.
We got around the problem by simply playing a short blank audio when the user first clicks the login button or start game.

Router

The app uses Backbone Router to handle dynamic routing. The following rules are currently implemented:

  • “/selection” – team selection
  • “/match” – live match
  • “/match/get/(.*)” – returns a JSON containing a match details
  • “/rankings” – rankings page
  • “/client”  – web socket client handler
  • “/game” – save a game JSON
  • “/rankings/get”  – returns team rankings JSON
  • “/events/goals/(.*)” – web socket goals handler

ECMAScript 6

Hack day projects are the perfect opportunity to try out new technologies. We’ve been keeping an eye on ES6 transpilers for a while and decided to use Babel and write all our front end using features introduced in ES6.

Babel is extremely easy to setup, and works with all the major build systems out there.

We decided to use Grunt, mostly because we use it across all our APIs and we knew it would only take a few minutes to set up:

  • A Gruntfile that deals with Sass to CSS compilation
  • ES6 to ES5 transform using the grunt-babel package
  • A watcher that compiles both CSS and JS and also runs jshint on the go.

The option to enable JS source maps is a must have when working with a transpiler and it made working with Babel so easy and fun from the beginning.

As mentioned above, our first app skeleton was using requirejs. One of the most exciting features introduced in ES6 is the modules. Its syntax is very compact, has a great cyclic dependencies support and allows asynchronous loading – as AMD does.

We configured the transpiler to use AMD, so the compiled code would still use the syntax we are the most familiar with. https://babeljs.io/docs/usage/modules/#amd

ES6 makes Backbone classes very readable.

A class that we would declare as such in ES5

var Login = Backbone.View.extend({
    initialize: function () { … }
}) ;

is now written like this

class Login extends Backbone.View {
    constructor (options) { ... }
}

The introduction of the super is also a nice shortcut to not have to deal with prototypical inheritance (which we can still use in ES6).
Having used CoffeeScript for a while, one of our favourite features in the language is the fat arrow, as it saves the trouble of dealing with different levels of reassigning this  (var self = this;) to pass scoping of the ‘this’ keyword. This will also feel familiar if you’ve been using C# (lambda expressions).

this.fetch().done(users => {
    if (users.length) {
        this.trigger('users:fetched');
    }
});

We also used the let keyword which removes some possible confusion with block scoping.
Native promises are really cool. We initially wanted to use them but we figured that using jQuery Deferred would be more consistent as Backbone uses it internally and having two different Promises patterns within a project doesn’t bring much value.

Backend

One very important goal for the foos app was to keep track of stats from every match, so we could finally settle old scores and let the numbers do the talking! So, we needed a backend.

We chose Tornado, a simple web framework written in Python which comes with good support for WebSockets. This would work well for listening for goals from the foosball table and handing them down to our app, and didn’t take much code for us to get up and running.

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("static/index.html")

def main():
    parse_command_line()

    application = tornado.web.Application([
        (r"/", MainHandler),
        // the rest of our routes go here
    ],
        static_path=os.path.join(os.path.dirname(__file__), "static"),
        debug=options.debug,
    )

    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

Then we needed a database, so we created a couple of very simple MySQL tables to keep track of all the data we need.

CREATE TABLE matches (
  id             varchar(100),
  red_player1    varchar(100),
  red_player2    varchar(100),
  yellow_player1 varchar(100),
  yellow_player2 varchar(100),
  red_score      int(11),
  yellow_score   int(11),
  started        timestamp,
  PRIMARY        KEY (id)
);

CREATE TABLE players (
  id varchar(100),
  name varchar(100),
  PRIMARY KEY (id)
);

To populate the database, the client-side app chooses an ID for the current match, and posts the full state of the match after every goal, including the player names, IDs, and the current score. This was a simple approach that didn’t need much logic in our Tornado routes. We just needed to pass the data through using PyMySQL.

The next problem was how to generate the rankings. Being a 4-player game, the rankings had to be based on each 2-player team. So we had to figure out the list of possible teams from the database before looking at the match results. So, we created quick and dirty view which performs a CROSS JOIN with the players table to itself i.e. every possible combination of players. But that data included duplicate teams, as well as teams where player 1 and 2 are the same. We removed those cases with a simple string comparison to ensure that the ID for player 1 is always less than the ID for player 2.

CREATE VIEW teams AS
SELECT
   player1.id   AS player1_id,
   player1.name AS player1_name,
   player2.id   AS player2_id,
   player2.name AS player2_name
FROM
	players player1 CROSS JOIN players player2
WHERE
	STRCMP(player1.id, player2.id) = -1;

From there, it was simply a case of joining each team with all of its results from the matches table, and doing some basic SQL aggregation to determine total matches played, wins and losses, goals scored etc. We created this as another MySQL view and exposed the data to the client-side app via another Tornado route.

Conclusion

As engineers of varying backgrounds, it’s great to be able to take breaks and be serious about non-serious things – especially improving on our leisure activities.

It pays off to have a preparedness for things to come and be let loose on tools that we aren’t yet using in production environments.

We have really enjoyed using the new ES6 features in Babel. It makes JS code more readable and easier to understand for people less familiar with prototypical inheritance. We aren’t ready to use it in our APIs, but, using it as part of this hack day project was a great experience as we learnt a lot and we’re now using ES6/Babel is small code bases, including this blog!

login page

team selection match view

Related Content


This post was posted in , , by on