Saturday, November 20, 2010
Unfinished Projects
2) Make a PhoneGap app with Sencha Toucha and get it in the iPhone and Android app-stores
3) Learn ExpressJS and how to run Node stably
4) Do a video podcast of how to get up and running with Node.js
5) Learn and write some good tutorials / examples for progressive enhancement at Rain
6) Prepare presentations on Javascript Best Practices, Node, and HTML5/CSS/Progressive Enhancement for Designers
7) etc..
Saturday, November 6, 2010
Real-Time Collaborative Painting With Node.JS and Socket.IO
Introduction
I <3 Node.js!. This post will show you how to put together an amazing collaborative painting web app using Node.jS and web sockets in about 130 lines of code. The whole source code can be found here. I'll mention it's modeled loosely after the chat example found in the Socket.io-node source code.This is more of a simple demo at the moment, but it proves a few points.
1) Node is seriously awesome (and easy to get up and running)
2) Socket.io is equally awesome
3) It's easy to support the iPhone and other touch devices
4) Canvas is cool too :)
Unfortunately I do not currently have a live demo. I'm still hitting https://api.no.de/coupons hoping for a free trial to one of Joyent's Smart Machine's, but so far no luck :)
A Picture
*The red is what you drew, the blue is what someone else drew.
Overview
There are pretty much only 3 files of consequence and you can view them all here:
1) collabpaint.htmlHTML file that just gives you a canvas and contains some simple CSS content. Used to display the page.
2) collabpaint.js
This file is responsible for drawing to your canvas. It contains all of the event listeners and code for listening for messages from the node server.
3) server.js
Tells node to start an http server and socket.io instance to handle messaging between clients.
Getting started with Node
There are a handful of great tutorials for getting started with Node.
In general if you just want to get started with node, it's very easy. There are several tutorials linked from the node wiki page here:
https://github.com/ry/node/wiki
I think I read all of them for doing this example. The biggest things for me was basically figuring out how to install modules. Other than, I noticed that node crashes pretty easily if you're doing something wrong and it doesn't always give you a useful error messages.
Another big thing. Stick with the current stable version of node! The docs mention 0.3.0 on the website, but the current stable version is actually 2.4. That means, if you're checking out the source code with git, you'll need to switch away from the master branch to the 0.2.x branch like so, before configuring and installing Node:
git clone https://github.com/ry/node.git cd node git checkout -b v0.2 origin/v0.2 ./configure make sudo make install
Also, the docs for version 0.2.4 can be found here.
Adding support for web sockets with Socket.IO
Coming soon...
Canvas Quirks
The biggest problem I ran into with the canvas stuff was figuring out where on the screen to draw. I noticed that the coordinates I retrieved initially were only working on Google Chrome (e.offsetX and e.offsetY). Later I changed to a way that seemed not to work if the page had been scrolled down at all. My current solution of relying on the clientX and clientY, subtracting the target offsetX and offsetY and then adding the window scrollX and scrollY seems to work okay Chrome, Safari and Firefox. See the code:
coords = { x:e.clientX - e.target.offsetLeft + window.scrollX, y:e.clientY - e.target.offsetTop + window.scrollY };
Adding iOS Support
There were two things I needed to change to allow this to work really well on an iPhone (and presumably other mobile devices). First, I used a media query to take away all of the extraneous content on the page. The mobile version is just a big canvas. No silly border and no header.
@media screen and (max-width: 320px) { h1 { display: none; } canvas { border: none; border-radius: none; -webkit-box-shadow: none; } }
Next I had to listen for special events in order to tell when we were drawing on the iPhone:
// iOS alternative to mouse move canvas.ontouchmove = function(e) { move(e, true); };
Lastly, we needed to handle multi-touch. This was the trickiest thing I guess, but a few Google searches found me some great docs from Apple website about how to capture those events.
for (var i=0; i<e.targetTouches.length; i++) { coords = { x: e.targetTouches[i].clientX, y: e.targetTouches[i].clientY }; drawCircle("red", coords); send(coords); }
This may not be the fanciest way to detect touch events. I'm not doing any real feature detection, but the browsers don't seem to mind too much about having these extra event handlers in there, so I'm pretty happy with the solution.
The Future
Well, I'm working on a lot of variations to this simple app.1) Different "sessions" where people can only draw with people they invite
2) Different colors / Brush types
3) An eraser
4) Doing fun things with sound.
Some of these are already available in the code itself under different branches.
Android / HTML5 Canvas Mashup
Recently I had a vision in my mind about something I wanted to build. It started with an Android app that I was working on for a presentation. It was a simple game. Simple games are boring. I wanted to bring in web services. Some more interesting games offer a feature where you can watch your game later, so I decided to do a version of this that works on the web. Because this was a simple game (at this point all you could do was roll around a ball using the accelerometer) and because I used the Android 2D Drawing API to do the entire thing. I decided that it wouldn't be very hard to do the replay on the web with canvas.
See some of the completed games here:
http://www.touchenabledweb.com/games/
There were a few important things we needed to make this work. On the Android side, first we need to tell the device that we will be accessing the Internet. The we will needed to asynchronously connect to the web and then we need to parse some JSON. Later we'll figure out how to store and this data on the server side and how to display it using the HTML5 <canvas>l tag
Android Stuff
Obtaining Permission
We need to make sure we add this permissions requests to our AndroidManifest.xml file:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
Asynchronous-ness
There were two different types of requests. One to create a new game and two to update the latest scores. For both of them we started with the AsyncTask class found in the android.os package. It's really weird actually and I can't fully explain how it works, but I can explain how I got it to work. First you declare your class like this (in this case in a file called PostDataTask.java:
public class PostDataTask extends AsyncTask{
GameFrame is just a class that I created to store information about the game. The Integer and Long classes are needed for some of the callback functions that the AsyncTask makes available, however I don't use them in my example. Once you declare what type of arguments you need, you need to over-ride a method on the AsyncTask class called doInBackground:
@Override protected Long doInBackground(GameFrame...frame) { boolean succcess = this.postData(frame[0]); return null; }
The do in background wants you to give it as an argument a fancy kind of array, which you can specify with the three dots between the class and variable names. I know that I only will ever be sending one GameFrame object in at a time, so I just directly grab frame[0] and send that to the postData method I created, which also expects a GameFrame, but only one.
private boolean postData(GameFrame myFrame) {
When you're ready to call off the task to post the data in your main game class all you need to do is create and execute and new post data task with the appropriate arguments. In this case:
new PostDataTask().execute(new GameFrame(this.gameID, (ArrayList) coordinates.clone()));
Using the AsyncTask I'm able to successfully make updates to the web server in the background. If you want to know by the way GameFrame contains a very simple array of coordinates and the ID of the game that the ball has moved around to and is defined in its own GameFrame.java file:
package org.example.dirtsweeper; import java.util.ArrayList; import android.graphics.PointF; public class GameFrame { public int gameID; public ArrayListcoords; public GameFrame(int gameID, ArrayList coords) { this.coords = coords; this.gameID = gameID; } }
JSON-ification
The AsyncTask stuff was super easy to get going, but I was a little bit worried that the web services would be a lot harder than the PHP stuff I typically a usually use. It was. It was really hard and a small typo meant that it took me hours to get it working properly. The first tip is to use the org.apache.http.client class. This seriously made things a lot easier than some of the other solutions out there! Next you have use things like the JSONTokener to parse the JSON data. It kind of sucks, but you just have to do it. Here is an example of my newGame function in my NewGameTask class:
private int newGame(Sweeper sweeper) { int gameID = 0; // Create a new HttpClient and Post Header HttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost(NEW_GAME_URL); try { // Add your data ListnameValuePairs = new ArrayList (); nameValuePairs.add(new BasicNameValuePair("data[Game][width]", String.valueOf(sweeper.floor.getWidth()))); nameValuePairs.add(new BasicNameValuePair("data[Game][height]", String.valueOf(sweeper.floor.getHeight()))); httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs)); // Execute HTTP Post Request HttpResponse response = httpclient.execute(httppost); BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8")); String json = reader.readLine(); JSONTokener tokener = new JSONTokener(json); try { JSONObject finalResult = new JSONObject(tokener); gameID = finalResult.getInt("game_id"); } catch (JSONException e) { Log.d(TAG, TAG + "ERROR JSON EXCEPTION: " + e.getMessage()); // TODO Auto-generated catch block e.printStackTrace(); } } catch (ClientProtocolException e) { Log.d(TAG, TAG + "This is your error ClientProtocol " + e.getLocalizedMessage()); } catch (IOException e) { e.printStackTrace(); Log.d(TAG, TAG + "This is your IOException error " + e.getLocalizedMessage()); } return gameID; }
If you want to know what my JSON response actually looked like follow along to the next section....
Server-side
I use the CakePHP framework for doing most of my websites. I find it's super easy to get create fully functional data-driven websites with a ton of web services up and running in no time.
Database Schema
The DB here is very simple. The schema is also available in the repo, but here it is:
CREATE TABLE IF NOT EXISTS `games` ( `id` int(11) NOT NULL AUTO_INCREMENT, `width` int(10) unsigned NOT NULL DEFAULT '320', `height` int(10) unsigned NOT NULL DEFAULT '430', `created` datetime NOT NULL, `modified` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -- -------------------------------------------------------- -- -- Table structure for table `game_times` -- CREATE TABLE IF NOT EXISTS `game_times` ( `id` int(11) NOT NULL AUTO_INCREMENT, `game_id` int(11) NOT NULL, `x` float(10,6) unsigned NOT NULL, `y` float(10,6) unsigned NOT NULL, `created` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Web Services
We only created two web services.
/games/addGame
and
/games/addTime
Both the views look exactly the same. They just indicate success and return the gameID.
<?php echo json_encode(array('success' => $success, 'game_id' => $gameID)); ?>
PHP
Animating the movements of a ball on a canvas is pretty easy. I'm not going to explain how to draw on a canvas. There are some great tutorials over at the Mozilla Developer's Center. I will show you what I'm doing though to prepare us to print (and of course you can use all the source code). Here is the contents of the watch game view.
<canvas id="floorSweeper" width="<?php echo $game['Game']['width']; ?>" height="<?php echo $game['Game']['height']; ?>"></canvas> <script type='text/javascript'> var FLRSWPR = { id:'floorSweeper', name:'Floor Sweeper', width:<?php echo $game['Game']['width']; ?>, height:<?php echo $game['Game']['height']; ?>, fps:10, background:'black', times:<?php echo json_encode($times); ?> }; </script> <script type="text/javascript" src="http://www.touchenabledweb.com/js/floorsweeper.js"></script>
In case your wondering the times array is very simple and populated that section might look something like this:
times:[{"x":"25.000000","y":"267.500000"},{"x":"22.000000","y":"265.000000"},{"x":"19.000000","y":"262.500000"}, /* ... */ {"x":"22.000000","y":"265.000000"}]
JavaScript
Okay, so finally the real magic. We have the balls coordinates. How do move the ball around on the screen? I'm not going to go over the entire Javascript file. (You can see it here: http://www.touchenabledweb.com/js/floorsweeper.js) Instead I just want to show you a few of the interesting parts.
Here is the Ball prototype:
Ball.prototype = { moveMe: function () { if (this.currentFrame < this.frames.length) { this.x = this.frames[this.currentFrame].x; this.y = this.frames[this.currentFrame].y; this.currentFrame++; } }, drawMe: function(ctx) { ctx.fillStyle = this.color; ctx.moveTo(this.x,this.y); ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI+(Math.PI*360)/2, false); ctx.fill(); } };
There are two very simple methods on the Ball prototype: drawMe() and moveMe(). They do what they say. The moveMe() function checks if we're not at the end of the frames that the object was instantiated with and then just moves the coordinates to the next frames and updates its count of which frame it's displaying. The drawme() function draws a really basic ball.
There is also a Game prototype. When we start the game prototype it is given an arbitrary frames per second and it sets an interval to go through call the moveMe() and drawMe() methods on all of its registered "actors". Simple no?
Phew!
That was a lot to cover. A little bit of Java, a bit of PHP and even some JavaScript. In the future I'd love to use Node.js and Socket.IO so that we can watch the games in real-time. (It might also be nice to finish the Java part, so that there's actually an interesting game).
Please leave a comment or a suggestion if you can see a place where I could have done something better. I'm much more of a web programmer than a Java programmer!
All of the source code (Android and PHP) can be found here: https://github.com/xjamundx/DirtSweeper
Wednesday, November 3, 2010
Component-based Re-directs in CakePHP
See: http://www.dom111.co.uk/blog/coding/cakephp-components-redirect-fail-on-my-part/171
Then see: http://book.cakephp.org/view/65/MVC-Class-Access-Within-Components
The answer:
For reasons which are explained in the above two articles, components that wish to use redirect need to include the following callback function in their component:
function beforeRedirect(&$controller, $url, $status=null, $exit=true) { }