JavaScript: Using ChipmunkJS to build ping pong

A couple of weeks ago, we picked up a RaspberryPi for the office and started brainstorming ideas for cool hacks for it. One of the ideas that was floated was using the Pi as a server for some sort of multiplayer JavaScript game. I’ve actually never written a game before so I figured it would be an interesting project. We ended up trying to use node-qt for graphics along with ws to receive input via websockets but the refresh rate of Qt was just to low. A story for another time.

Anyway, so back to ping pong. One of the things we wanted to avoid was writing our own code to manage the game objects, we really wanted to use a game engine for this. The only caveat we had was that the engine had to be UI agnostic since we were planning to output graphics on Qt instead of a HTML5 Canvas. After looking around, most of the JavaScript game engines have strong dependencies on Canvas so we started looking at other options. The two strongest options were Box2JS and ChipmunkJS. We decided to go with ChipmunkJS in part because it was handwritten while Box2JS was automatically converted from ActionScript so the resulting code is harder to follow.

Getting ChipmunkJS setup is relatively painless, just include the file and you’re off to the races. Since we were looking to eventually use Qt, we tried our best to cleanly separate the Chipmunk code from what we’d be using to draw the objects on the screen. Because of that, it’s pretty easy to follow what’s going on with the Chipmunk code. If you take a look at cpPong.js it basically initializes the physics space and then starts the game.

// The physics space size is 640x480, with the origin in the bottom left.
// Its really an arbitrary number except for the ratio - everything is done
// in floating point maths anyway.

var GRABABLE_MASK_BIT = 1<<31;
var NOT_GRABABLE_MASK = ~GRABABLE_MASK_BIT;

if( typeof cp == 'undefined') {
	var cp = require("./cp.js");
}
    
var cpPong = function(){

    this.addDrawables();
    
    var ball = null, paddleOne = null, paddleTwo = null;    
    
    this.v = cp.v;
    this.space = new cp.Space();
        
    this.space.iterations = 10;
    this.space.gravity = this.v(0, 0);
    this.space.sleepTimeThreshold = 0.5;
    this.space.collisionSlop = 0.1; 
    
    this.addFloor();
    this.addWalls();
    
    var mass = 10;    
    var radius = 20;
        
    this.body = this.space.addBody( new cp.Body(mass, cp.momentForCircle(mass, 0, radius, this.v(0, 0))) );            
    this.ball = this.space.addShape( new cp.CircleShape(this.body, radius, this.v(0, 0)) );
    this.ball.setElasticity(1);    
    this.ball.setFriction(0.0);   
    this.ball.setCollisionType(1);
        
    this.paddleOneBody = this.space.addBody( new cp.Body(100000, cp.momentForBox(Infinity, 75, 10)) );        
    this.paddleOne = this.space.addShape( new cp.BoxShape(this.paddleOneBody, 75, 10) ); 
    this.paddleOne.setElasticity(1);
    this.paddleOne.setFriction(0);         

    this.paddleTwoBody = this.space.addBody( new cp.Body(100000, cp.momentForBox(Infinity, 75, 10)) );        
    this.paddleTwo = this.space.addShape( new cp.BoxShape(this.paddleTwoBody, 75, 10) ); 
    this.paddleTwo.setElasticity(1);
    this.paddleTwo.setFriction(0);        
    
    this.scoreboard = {s1: 0, s2: 0};
    
    var self = this;    
    
    this.resetGameObjects();
    this.renderScoreBoard();
    this.isRunning = true;
    this.run();
        
    this.space.setDefaultCollisionHandler(null, null, function(el){     	    	
        if( el.a.isGameEnder || el.b.isGameEnder ){        	        	
        	var pointFor = el.a.pointFor ? el.a.pointFor : el.b.pointFor;
        	
        	self.isRunning = false;
        	
        	self.scoreboard[ pointFor ] += 1;            	
        	self.renderScoreBoard();
        	
        	self.alertRoundOver();
        	self.resetGame();        	        	        	
        }        
    });          
    
};

cpPong.prototype.resetGameObjects = function(){
    this.body.setPos( this.v(20, 320) );
    this.body.setVel( this.v(200, -200) );
    
    this.paddleOneBody.setPos( this.v(320, 60) );
    this.paddleTwoBody.setPos( this.v(320, 420) );
    
    this.paddleOneBody.setVel( this.v(0, 0) );
    this.paddleTwoBody.setVel( this.v(0, 0) );		
};

cpPong.prototype.addFloor = function() { 	
    this.floor = this.space.addShape( new cp.SegmentShape(this.space.staticBody, this.v(0, 0), this.v(640, 0), 0) );
    this.floor.setElasticity(1);
    this.floor.setFriction(0.0);    
    this.floor.setCollisionType(1);
    
    this.floor.isGameEnder = true;
    this.floor.pointFor = "s1";
    this.floor.lineWidth = 5;
    
    this.ceiling = this.space.addShape( new cp.SegmentShape(this.space.staticBody, this.v(0, 480), this.v(640, 480), 0) );
    this.ceiling.setElasticity(1);
    this.ceiling.setFriction(0.0);    
    this.ceiling.setCollisionType(1);
    
    this.ceiling.isGameEnder = true;
    this.ceiling.pointFor = "s2";
    this.ceiling.lineWidth = 5;
};

cpPong.prototype.addWalls = function() {
    var space = this.space;
    
    var wall1 = space.addShape(new cp.SegmentShape(space.staticBody, this.v(0, 0), this.v(0, 480), 0));
    wall1.setElasticity(1);
    wall1.setFriction(0.0);
    wall1.setLayers(NOT_GRABABLE_MASK);
    wall1.setCollisionType(1);
    wall1.lineWidth = 5;
    
    var wall2 = space.addShape(new cp.SegmentShape(space.staticBody, this.v(640, 0), this.v(640, 480), 0));
    wall2.setElasticity(1);
    wall2.setFriction(0.0);
    wall2.setLayers(NOT_GRABABLE_MASK);
    wall2.setCollisionType(1);
    wall2.lineWidth = 5;
};

cpPong.prototype.movePaddle = function(paddle, direction){	
	var target = paddle == "one" ? this.paddleOneBody : this.paddleTwoBody;
	var velocity = direction == "right" ? 500 : -500;
	
    var pt = target.getVel().add( this.v(velocity, 0) ); 
    target.setVel( this.v(velocity, 0) );
    // target.setVel( pt );
};

//Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
  if (typeof module !== 'undefined' && module.exports) {
    exports = module.exports = cpPong;
  }
  exports.cpPong = cpPong;
} else {
  window.cpPong = cpPong;
}

With the physics space setup, the next step is setting up the drawing. Following the Chipmunk demo’s lead, I added specific “draw” methods to the Prototypes of each shape. Doing this, allows you to just iterate over each shape in the scene and call “draw” to have it appear on Canvas or Qt. Check out pong.html to see how we added the draw methods to the shapes. And that’s about it! The only other interesting part is using “requestAnimationFrame” to avoid using setTimeout to update the scene.

var scoreboardTemplate = _.template( $("#scoreboardTemplate").html() );

var ctx;
var canvas = $("canvas")[0];
 
var canvasBackend = {
        
    scale: 0,
    
    run: function(){ 
    	
    	if( this.isRunning == false ){
    		return false;
    	}
    	
        this.drawScene();
        this.space.step(1/60);                
        
        var raf = window.requestAnimationFrame
        || window.webkitRequestAnimationFrame
        || window.mozRequestAnimationFrame
        || window.oRequestAnimationFrame
        || window.msRequestAnimationFrame
        || function(callback) {
            return window.setTimeout(callback, 1000 / 60);
        };      
        
        var draw = _.bind(this.run, this);
        raf(draw);             
    },
    
    drawScene: function(){
        var self = this;
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.space.eachShape(function(shape) {          
            shape.draw(self.ctx);           
        });        
    },
    
    resetGame: function(){
    	var self = this;
        window.setTimeout(function(){
            self.resetGameObjects();
            self.isRunning = true;
            self.run();
        }, 3000);    	
    },
    
    renderScoreBoard: function(){
    	$("#scoreboard").html( scoreboardTemplate(this.scoreboard) );
    },
    
    alertRoundOver: function(){
    	alert("Round over!");
    },
    
    addDrawables: function(){
        
        this.canvas = $("canvas")[0];
        this.ctx = $("canvas")[0].getContext('2d');
                                  
        var width = Math.ceil( (window.innerHeight * 640) / 480 ) + 25;
        
        $("canvas").attr("height", window.innerHeight);
        $("canvas").attr("width", width);        
        
        // var width = this.canvas.width;
        var height = this.canvas.height;
                
        window.scale = 0;          
        
        if (width / height > 640 / 480) {
            window.scale = height / 480;
        } else {
            window.scale = width / 640;
        }                       
        
        var self = this;
        $(window).keydown(function(e){                    
            if( e.keyCode == 39 ){
                self.movePaddle("one", "right");
            }
            
            if( e.keyCode == 37 ){         
            	self.movePaddle("one", "left");
            }
                    
            if( e.keyCode == 87 ){
            	self.movePaddle("two", "right");
            }
            
            if( e.keyCode == 81 ){         
            	self.movePaddle("two", "left");
            }
        });         
        
        cp.Shape.prototype.point2canvas = function(point){          
            return cp.v( point.x * window.scale, (480 - point.y) * window.scale );        	
        };
        
        cp.Shape.prototype.drawLine = function(ctx, a, b){        
            a = this.point2canvas(a); 
            b = this.point2canvas(b);
            ctx.beginPath();
            ctx.moveTo(a.x + .5, a.y);
            ctx.lineTo(b.x + .5, b.y);
            ctx.stroke();
        };
        
        cp.Shape.prototype.drawCircle = function(ctx, c, radius){
            var c = this.point2canvas(c);
            ctx.beginPath();
            ctx.arc(c.x, c.y, window.scale * radius, 0, 2 * Math.PI, false);
            ctx.fill();
            ctx.stroke();           
        }
        
        cp.SegmentShape.prototype.draw = function(ctx) {                        
            ctx.strokeStyle = "green";                      
            ctx.fillStyle = "green";
            ctx.lineCap = 'round';
            ctx.lineWidth = this.lineWidth ? this.lineWidth : Math.max(1, this.r * window.scale * 2);
            this.drawLine(ctx, this.ta, this.tb);
        };

        cp.CircleShape.prototype.draw = function(ctx) { 
            ctx.lineWidth = 1;
            ctx.strokeStyle = "black";                      
            ctx.fillStyle = "red";  
            this.drawCircle(ctx, this.tc, this.r);
            
            ctx.strokeStyle = "white";                      
            ctx.fillStyle = "white";
            this.drawLine(ctx, this.tc, cp.v.mult(this.body.rot, this.r).add(this.tc));
        };
        
        cp.PolyShape.prototype.draw = function(ctx){
            
            ctx.strokeStyle = "blue";                      
            ctx.fillStyle = "blue";
            ctx.lineWidth = 1;
            
            ctx.beginPath();

            var verts = this.tVerts;
            var len = verts.length;
            var lastPoint = this.point2canvas( new cp.Vect(verts[len - 2], verts[len - 1]) );
            
            ctx.moveTo(lastPoint.x, lastPoint.y);

            for(var i = 0; i < len; i+=2){
                var p = this.point2canvas(new cp.Vect(verts[i], verts[i+1]));
                ctx.lineTo(p.x, p.y);
            }
            
            ctx.fill();
            ctx.stroke();
        };      
        
    },
};

_.extend( cpPong.prototype, canvasBackend );

There’s a live demo running here and of course the code on GitHub.

Any questions or comments welcome!

SQL: 5 Tips and tricks to impress your DBA friends

I was poking around the ThoughtBot blog a couple of days ago and ran across a post titled Refactoring Ruby Iteration Patterns to the Database. At a high level, the post was summarizing how you can take an ActiveRecord aggregation (a sum in this case) and run it in directly in your RDMS with SQL. Not really rocket science, but it was a keen reminder of how ORMs often mask over much of the power of “regular” SQL. This isn’t a specific criticism of ActiveRecord, it’s an issue with every ORM from Doctrine to Hybernate.

We’ve actually been writing some straight SQL lately, mostly for analytics work, so I had the team shoot over their favorite “maybe hidden” SQL feature. Since life is better with examples, the sample use cases and queries are written against a schema describing the “items” found on a “receipt” which are optionally related to a “category”. The SQL to create the schema is:

CREATE TABLE IF NOT EXISTS `receipt` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `total` decimal(15,2) DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

INSERT INTO `receipt` (id) VALUES (7006),(3493);

CREATE TABLE IF NOT EXISTS `item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `category_id` int(11) DEFAULT NULL,
  `receipt_id` int(11) DEFAULT NULL,
  `total` decimal(15,2) DEFAULT '0.00',
  `quadrant` varchar(8) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=40779 ;

--
-- Dumping data for table `item`
--

INSERT INTO `item` (`id`, `category_id`, `receipt_id`, `total`, `quadrant`) VALUES
(1925, 390, 1712, 1.00, 'WE'),
(11834, 263, 3168, 8.40, 'AX'),
(18012, 263, 3493, 7.99, 'AX'),
(25128, 390, 5681, 14.99, 'WE'),
(26898, 267, 4214, 9.99, 'AX'),
(27486, 267, 7345, 1.49, 'AX'),
(33331, 263, 7006, 29.94, 'BT'),
(34947, 390, 9053, 20.01, 'BT'),
(39309, 390, 13544, 14.09, 'BT'),
(40778, 267, 14153, 5.94, 'BT');

Note: SQLFiddle is unfortunately down right now or I’d add this as a fiddle. Anyway, if you have the schema setup feel free to run the queries as you go down the list.

Order rows by fixed ordering of a column

At some point, you might find yourself needing to sort a list of rows by a column in an arbitrarily enforced order. For example, say on our item table, you needed to sort the rows by the “quadrant” column such that WE was first, followed by AX, and finally BT.

Turns out, it’s possible to specify an arbitrary ordering using the ORDER BY FIELD statement:

SELECT * FROM item WHERE 1 ORDER BY FIELD (quadrant, "WE", "AX", "BT")

Check that a LEFT JOIN relation exists

If you’re only running JOINs on columns with foreign key relations this isn’t an issue, but what happens if you need to run a JOIN where a FK doesn’t necessarily exist? In our example, lets say you wanted to select only the items which had a corresponding row in the “receipt” table.

The most straightforward way to accomplish this is generally to check that the JOIN’ed column on the related table isn’t NULL:

SELECT * FROM item
LEFT JOIN receipt ON receipt.id = item.receipt_id
WHERE receipt.id IS NOT NULL

Assign an aggregate value to a variable and re-use

One of the SQL features that’s usually glossed over or ignored in web development is the ability to create variables and then reuse them in subsequent statements. With this schema, an example would be calculating the “% of total spend” for the individual items - most people would run one query to generate the total and then a separate query to calculate the % of spend. For something trivial like this it doesn’t matter but if you were involving complex WHERE predicates it could be a nice performance boost.

The syntax for variables is relatively easy and it’s actually a powerful concept:

SET @total_spend = (SELECT total_spend FROM (SELECT SUM(item.total) AS total_spend FROM item) it);
SELECT receipt_id, (SUM(item.total) / @total_spend) * 100 FROM item GROUP BY receipt_id;

Add synthetic “pseudo” columns using variables

This one is a Matt Daum favorite and pretty handy. Looking at the example, say that you wanted to assign a “sequence” value to each item depending on their rank order based on “total” within their “category_id”. In plain English, for each “category_id” you want to assign the most expensive item a “1”, the second most a “2”, and so on.

This seems straightforward, but try and construct a result set using only a GROUP BY or some combination of sub queries, I’ll wait. Turns out, the easiest way to accomplish this is to use variables to construct a “pseudo” column that increments and resets when the category changes.

SET @type='';
SET @num = 1;
 
SELECT @num := if(@type = category_id, @num + 1, 1) as sequence, @type := category_id as extra, bfa.*
FROM (
   SELECT id, category_id, total FROM item ORDER BY category_id ASC, total DESC
) bfa

Select only GROUP’ed rows that fulfill a second clause

Sorry for the terrible description, an example will make it clearer. Given our schema, lets say you wanted to select *only* the most expensive items per category, how could you set about doing it? The obvious approach would be using some combination of GROUP BY and MAX but unfortunately because of the semantics of GROUP BY that wont work as expected.

A better approach, is to leverage an INNER JOIN along with MAX() to only select the rows that match the max total per category:

 SELECT item.id,
       item.category_id,
       item.total
FROM   item
       INNER JOIN (SELECT Max(it0.total) AS total,
                          it0.id, it0.category_id
                   FROM   item it0 GROUP BY it0.category_id
                   ) it0
               ON item.total = it0.total AND it0.category_id = item.category_id  
GROUP  BY item.category_id

The caveat here is that you’re really selecting the highest total, so if two rows have the same total you’re not guaranteed which one you’ll end up with. This approach also scales out, in the sense that you can add additional INNER JOINs to limit the resultset in situations where you’re getting tripped up by GROUP BYs and ORDER BYs.

Anyway, as always, we’d love to hear your favorite tips and tricks in the comments!

Phonegap: Fixing black bars on iOS7/iPhone5

Last week, we were using Phonegap Build to build an iOS IPA for a project an ran into an odd issue. When we launched the app on an iPhone 5 running iOS7 black bars appeared at the top and bottom of the screen. On top of that, when launching the app we were observing the same issue with the splash screen.

In typical Phonegap fashion, Googling for people suffering from similar issues brought back dozens of results across several versions each with a different root cause and solution. One of the first promising leads we noticed was this comment in the top of the default Phonegap template:

<!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />

Unfortunately, that comment seems either be invalid or the issue has since been resolved since removing those meta attributes had no effect.

As it turns out, the issue is that the “config.xml” file created by the default Phonegap “Hello world” project is missing an entry for the iPhone5’s screen size. Oddly enough, there’s actually a splash screen image of the correct height in the demo project but its not referenced in the config file. To resolve this issue, you just need to add this line to your config.xml:

<gap:splash gap:platform="ios" height="1136" src="res/screen/ios/screen-iphone-portrait-568h-2x.png" width="640" />

Just make sure that you have a file named “res/screen/ios/screen-iphone-portrait-568h-2x.png” where the rest of your splash screens are and you should be good to go.

Javascript: Phonegap with Backbone and Marionette

We’ve worked with our clients to execute a couple of Phonegap apps lately and in doing so used Backbone and Marionette to structure the apps. For some background, Phonegap, now Apache Cordova, is a project that allows developers to build native iOS, Android, and WP apps using HTML, CSS, and of course Javascript. As a developer, you write some HTML and Javascript, pass it to Phonegap, and Phonegap returns a native app that displays your code inside a WebView without any surrounding chrome. On top of this, Phonegap provides a set of Javascript APIs that allow you to leverage some of the device’s native functions, like the accelerometer or camera.

Writing apps with HTML/JS is great, but it presents some issues particularly that triggering a full page reload for navigation appears “non-native” on mobile. On technique to combat this issue is developing single page Javascript apps. In a single page app, the entire page is never reloaded, instead portions of the DOM are dynamically re-rendered using Javascript. Because of the complexity of managing this process with straight Javascript, several libraries, including Backbone, have been developed to simplify this process. Marionette is a companion library to Backbone which provides a set of features to make managing complex applications easier. So, what were the pros and cons of using Backbone with Marionette to build a Phonegap app?

The Good

Structure: Using a library like Backbone guides you to structuring your code in loosely a MVC design pattern. Coupled with a templating framework, this ends up producing code that’s much easier to follow and maintain. Before Phonegap, I’d already started using Backbone in traditional Symfony2 projects just to get the benefit of better structured code.

It’s familiar: This is a personal preference, but compared to declarative frameworks like AngularJS, Backbone/Marionette apps “look” like regular HTML/JS. Primarily because of the regular HTML templates and use of jQuery, the learning curve for Backbone isn’t very steep. A team member without Backbone experience can quickly grok how things work and make changes quickly.

The Not So Good

“There’s more than one way to do it”: Although flexibility is good, having a generally “well recommended” way usually helps decrease confusion and frustration. With Backbone/Marionette, there doesn’t seem to be much consensus on how to do “standard” things like message passing or even structuring the app as a whole. There’s dozens of StackOverflow answers debating the “best” way to approach things, often with outright conflicting viewpoints. In contrast, Symfony2 and Rails typically have “best practices” for approaching common tasks, even if they’re not appropriate in every circumstance.

Documentation: The documentation for Marionette, and to a lesser degree Backbone, is pretty lacking. The Marionette documentation explains how the individual components work but they didn’t provide much insight to the “big picture”. The docs were also missing some explanation into the “why”, which of course lead to StackOverflow answers and then differing viewpoints. Marionette is also short example apps which Backbone does have. The Backbone documentation is thorough, its just a bit hard to navigate and purposefully introduces the “There’s more than one way to do it” mantra.

Anyway, on the whole using Backbone with Marionette to build a Phonegap app was a positive experience. Unfortunately, our clients’ own the code so we can’t release it. That said, we’ll do our best to build something in-house that we can share.

Musing: Thoughts on Seattle vs. Uber and an AirBnB orgy

In the last two weeks, there’s been two frontpage stories coming out of the “sharing economy” space. First up, news broke that Seattle passed new regulation to limit the number of drivers that Uber or Lyft can have on the road at any time, effectively hamstringing both services. Then, over the weekend a story started circulating about an Airbnb stay gone awry involving an orgy, Twitter, and of course the cops. While the stories are wildly different, they both sit at the intersection of the new “sharing economy” and the role of government regulation. Because of this, both stories sparked an intense debate everywhere from The Wall Street Journal to Hacker News. The attitudes and viewpoints of the discourse were interesting and revealing about people’s attitudes towards regulation in the taxi and hospitality space.

The opinion towards the new regulations in Seattle were overwhelmingly negative, ranging from claims of stifling innovation to accusations of outright corruption. Empirically, it seems that most people, even outside of early adopters, have generally positive feelings about Uber and Lyft. It might be because of the horrible experiences people have had in normal cabs or because of the perception of hackney companies as entrenched monopolies but I think it’s primarily due to people’s perception of risk surrounding Uber or Lyft. As a non-user, the perception of how “risky” it is to have Uber drivers operating in your city is probably near zero. Given how small a percentage of total drivers they’ll make up and that “licensed cab drivers” typically already operate in the area, I don’t think many people feel threatened by having additional drivers potentially ferrying people around their city. Because of this, it’s been easier for people to take hold of the “sharing economy” narrative where Lyft or Uber who were empowered to make a living are suddenly shut out by corrupt, entrenched interests.

Contrast this with the reactions from the same people to the Airbnb story, which ranged from disgust that someone would rent out their condo to strangers through feelings that Airbnb should be held liable for the actions of an independent third party. There’s no argument that the Airbnb story is significantly more disturbing, but it isn’t the first time something like this has happened and it certainly won’t be the last. So why such a different, visceral reaction? It’s perceived risk. As a non-user, the perceived risk to having Airbnb operate in your area is undeniably significant. Take a typical condo apartment building where every occupant is either a member of a condo association or a vetted rental tenant. Introducing the possibility of short term, “random occupants” certainly sounds risky and unnerving to anyone living in the building. Anyone considering the situation immediately evaluates worst case scenarios, “what if they’re criminals?” or “drug dealers?” and so on. Compared to driving, where interactions with strangers is a given, introducing “random interactions” into a scenario where it’s unexpected seems to push people towards favoring legislation.

As a whole, the emergence of the new “sharing economy” is probably a net positive. Despite that, companies are certainly thumbing their nose at government regulation and entrenched players which is going to cause ruffled feathers along the way. To win the hearts and minds, companies will definitely need to manage the perception of how risky their services are both to users and non-users alike.