Saturday, November 20, 2010

Unfinished Projects

1) Learn Sencha Touch
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.html

HTML 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

Hi team,

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 ArrayList coords;
        
        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
                List nameValuePairs = 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

On any given day you might want to write a CakePHP component that say does some simplified user authentication. If improper credentials are received you'd probably want to re-direct to some sort of error page. It makes sense. Seems like a common thing. Guess what? This is tricky.

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) {
 }


Kinda weird, huh?

Yeah, I knew you'd agree.