Saturday, November 6, 2010

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

1 comment: