Point Break or Bad Boys 2?

Point Break or Bad Boys 2?I’ve always wanted to try “crowdsorting”, that is, using a large number of subjective comparisons to order a large set of data.

People are good at comparing things, but not at assigning a fixed score or estimate. It takes a lot of rigour to be consistent with scoring things, for example, giving movies a score out of 10. If I give Star Wars a score of 10, and I think Spinal Tap is better, where do I go? I can’t go one louder!

So, I wondered what would happen if I took a list of movies, and pulled two of them at random and asked people to choose which they’d prefer to watch.

Point Break or Bad Boys 2 was born – give it a try, then come back to read the rest!

(Incidentally, the name comes from a line in Hot Fuzz, in case you wondered).

I try to learn a few things with each of these toys. Here’s what I covered doing this…

Crowdsorting with an Elo ranking

Elo was originally a method of ranking chess players. All players start with a rank of 1600, and after a match the losing player loses some of their ranking points to the winner. The number of points is proportional to probability of the win. So, if a low ranked player beats a high ranked one, they get more points than if they beat someone with a similar rank.

The movie sorting uses exactly the same algorithm. We show two films, and which ever one is picked is the ‘winner’ and rises up the rankings.

The code records all the comparisons so that I can ‘replay’ games with different k-values to see how it affects the ranking. As I write this, I’ve only got a few hundred comparisons logged. Once I have more than a few thousand I’ll see what analyses can be drawn from the data.

In the meantime, the top 100 chart can be viewed here.

Integrating with Facebook

I thought it might be fun to make this a social game, so to play you must login with Facebook. This kind of integration can be done with Javascript, but didn’t want the front end to be too heavy with JS so I opted for a server-side approach.

First, I added the Facebook PHP SDK to composer.json

{
    "require": {
         "facebook/php-sdk": "dev-master",
         ...
    }
}

Then ran composer update to fetch the necessary code. Next, I added a facebook service to my Silex application after creating a application id on the Facebook Developer portal:

$app['facebook_app_id']='....';
$app['facebook_secret']='....';

$app['facebook'] = $app->share(function ($app) {
    return new Facebook(
        array(
            'appId' => $app['facebook_app_id'],
            'secret' => $app['facebook_secret'],
        )
    );
});

if you’re not familiar with Silex, this just ensures the Facebook class is created on demand and is a shared instance.

Now making the login is fairly simple. Here’s a simplified example of the handling for the front page in Silex…

$app->get('/', function (Silex\Application $app, Request $req) {

    $host=$req->getHost();
    
    //are we logged in? 
    $user = $app['facebook']->getUser();
    if ($user) {
        
        //calculate a logout url which will return to /logout
        $logoutUrl = $app['facebook']->getLogoutUrl(array(
            'next' => "http://$host/logout", 
        ));

        //render main game page
        return $app['twig']->render('game.twig', array(
            'logout' => $logoutUrl,
        ));
    } else {

        //we are not logged in - calculate a login url
        $loginUrl = $app['facebook']->getLoginUrl(array(
            'redirect_uri' => "http://{$host}/login"
        ));

        //show page inviting login
        return $app['twig']->render('index.twig', array(
            'fblogin' => $loginUrl,
        ));
    }

});

The /login route is pretty simple. We get bounced back here after logging into Facebook. If the SDK can give us a user id, then the login was successful. Otherwise, it failed…

$app->get('/login', function (Silex\Application $app, Request $req) {
    $user = $app['facebook']->getUser();
    if ($user) {
        return $app->redirect('/');
    } else {
        return 'login failed';
    }
});

Finally, the /logout route just does some cleanup to ensure we are completely logged out

$app->get('/logout', function (Silex\Application $app, Request $req) {
    $app['facebook']->destroySession();
    return $app->redirect('/');
});

That’s it! The application logs the user_id of each person who makes a comparison, but doesn’t store any names. It might be interesting to offer some feedback based on how closely your choses match those of your friends, but that’s for another day.

Layout using 960.gs

I wanted to begin adopting a common layout for these toys, so I used a fairly lightweight grid layout from 960.gs. This very easy to implement, it only took a few minutes to pick it up and rework my templates. They also provide some printable templates which are great for sketching rough layouts.

The grid is just a first step – with my next project I’ll see how I can pull in some shared templates and css through composer.

Vertical alignment of text

This is a small thing, but I wanted to vertically align the film titles inside a rectangle. Absolute Horizontal And Vertical Centering In CSS by Stephen Shaw of Smashing Magazine provided a very neat solution.

Summary

A fun experiment, many things learned, and I’ll be keen to explore the data some more once I’ve collected a few thousand comparisons!