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!

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.

Phonegap: Recording audio with Phonegap

I was working on a Phonegap app recently and one of the goals was to allow the user to record some feedback as an audio file which would later be shared. If you hit Google, there’s plenty of StackOverflow threads which claim to have working sample code. Almost all of the SO code is using the Phonegap Media API with the “startRecording”/“stopRecording” functions. Despite it looking simple, I could never get the Media based code working on iOS. For whatever reason, I kept hitting iOS permission errors trying to create the recording file. On Android, the code works but I kept ending up with an “AMR” file instead of the expected 3gp recording.

So what’s the alternative? Turns out there’s another Phonegap API called Capture which has a captureAudio method which lets you record audio. As opposed to Media, captureAudio uses the device’s native audio recording UI to let you to record audio. Because of this, captureAudio won’t work for all use cases but the implementation is straightforward and consistent across Android and iOS.

Anyway, here’s the code to record some audio and upload it to a server:

navigator.device.capture.captureAudio(function(mediaFiles){

    // mediaFiles will be an array of MediaFile objects

    var options = new FileUploadOptions();
    var ft = new FileTransfer();
    var fileURI = mediaFiles[0].fullPath;

    options.params = _.extend({}, {id: 5);
    options.fileKey = "file";
    options.fileName = fileURI.substr(fileURI.lastIndexOf('/') + 1);
    options.headers = {Connection: "close"};

    // upload the file to the server
    ft.upload(fileURI, encodeURI("http://www.setfive.com/upload.php"), function(resp){
        var data = JSON.parse(resp.response);
        alert( data.successMsg );        
    }, function(error){
        alert("Sorry! Something went wrong. Please try again.");
    }, options);

}, {limit: 1});

Then, on the server you can just grab the file from say the $_FILES[“file”] variable.

Javascript: Using PhantomJS-node with Deferreds

Earlier this week, a buddy of mine reached out asking for a good solution to programmatically taking screenshots of a few thousand URLs. For whatever reason, this question seems to come up ever so often so I pointed him towards PhantomJS and figured he’d be on his way. Wrong. Not one to pass up free beer and the opportunity to learn something I agreed to write up the script to generate screenshots from a list of URLs.

Looking at PhantomJS, it seems relatively straightforward but it’s clear you’d really need something “else” to orchestrate this entire process. After some poking around, NodeJS, everyone’s favorite hipster runtime, seemed to be the obvious choice. There’s a handful of node modules that basically “bridge” node with phantom and allow a node script to asynchronously manipulate a PhantomJS instance. Based solely on the funny description I decided to run with phantomjs-node and was off to the races.

Getting everything setup was straightforward enough but then as I started looking at the phantomjs-node examples I started realizing this was a one way trip to callback soup. I’ve been doing some PhoneGap work recently and using jQuery’s Deferreds has significantly help keep the project from becoming a mess of callbacks. On the NodeJS side, it looks like there’s two functionally equivalent implementations but I decided to run with Q since the “wrapper” function names are shorter.

The Code

Anyway, the main problem we’re trying to address is that with multiple nested callbacks code becomes particularly difficult to follow. It’s hard to read, hard to trace control flow, and obviously hard to debug. Take for example, the phantomjs-node example:

phantom.create(function(ph) {
  ph.createPage(function(page) {
    page.open('http://www.mdscollections.com/cat_mds_accessories.cfm',
      function(status) {
        console.log('Opened site? %s', status);
        another_funny(page, ph);
      });
  });
});

It’s already THREE callbacks deep and all it’s done is initialize PhantomJS and load a page. Imagine layering on a few more asynchronous operations, doing all of this in a loop, and then some post-processing. Enter Deferreds. How To Node has an eloquent explanation of what deferreds are and how Node impliments them but in a nutshell they’re useful for making asynchronous code easier to follow.

The main issue I ran into using Q was that “Q.ninvoke” and “Q.npost” wrapper functions kept causing exceptions while using them with phantomjs-node. What I ended up doing instead was creating my own Deferreds in separate functions and then resolving them inside a normal callback.

My PhantomJS-node code ended up looking like:

var Q = require("q");
var phantom = require("phantom");

var DomainScraper = function(){	
		
	this.createPhantom = function(){	
		var df = Q.defer();
		phantom.create(function(ph){
			df.resolve(ph);
		});
		return df.promise;		
	};
	
	this.createPage = function(ph){
		var df = Q.defer();
		ph.createPage(function(page, err){
			df.resolve(page);
		});
		return df.promise;
	};
	
	this.openPage = function(page, url){
		var df = Q.defer();
		page.open(url, function(status) {			
			df.resolve( {page: page, status: status} );
		});
		return df.promise;
	};
	
	this.screenshotPage = function( url ){
		
		var domainScraper = this;
		var url = "http://" + this.domainList.pop();				
		
		console.log( "Processing: " + url );
		
		var val = domainScraper
		.createPage(domainScraper.ph)
		.then(function(page){
			page.set("viewportSize", { width: 1600, height: 790 });
			return domainScraper.openPage(page, url);
		}).then(function(obj){
			
			if( obj.status == "success" ){											
				obj.page.render( url + ".png" );				
				console.log("Rendered " + url);
			}
			
			return result;
		})
		.done(function(){
			domainScraper.ph.exit();
		});
		
	};
			
	
};

var ds = new DomainScraper();
ds.screenshotPage("http://www.setfive.com");

It’s without a doubt easier to follow and would also make it much easier to do more complicated PhantomJS related tasks.

So how did screenshotting a few thousand domains go? Well there’s a story about that as well…