Sunday, December 23, 2007

A Labour of Love

No, this is not another post about Kevin Rudd. This is a post about Facebook.

A few months ago Aldie asked for testers for his Facebook board game app. Of course I joined Facebook to see what the story was. I found a few people that I know there, so I hung around for a couple of days adding friends. Somehow I started playing Scrabulous, and that was when I realised what a great site this really was. I was playing Scrabble against people I didn't see very often (because they lived in London, for example) and with people who never had the time for a proper game (even though they lived in the same house, for example). I' ve played 47 games of Scrabulous so far.

So I realised Facebook was a great site for playing games against friends you just can't get to in real life. And other games, as well as Scrabble. I decided to write my own. To avoid copyright issues, I decided to write Hex.

Of course, I had no idea how to. I didn't know whether Aldie had written his app in MSVC or Java or Flash or what, and I had no idea how it got deployed on Facebook. But that was the fun part. I was pretty sure that I could program it, whatever it was. Besides, I wanted to know about this Web 2.0 thing Yehuda was into.

I did some research and found out that Facebook apps are very clever indeed. You write your application as a web service and Facebook delegates requests to your app on Facebook through to your web server. So you can write your app in whatever you like and you host it on your own machine, so Facebook doesn't care about your database maintenance or CPU or anything. It just cares that your app responds to requests. I had used Apache in the past, so I installed it on my home machine. I also wanted to write my application in Python because Java bores me sometimes, so I installed mod_python (lets you write Apache services in Python) as well. I already had Linux and MySQL running, so I'd finally got myself a complete LAMP stack.

I couldn't even get the test example going! I did everything it told me to do and it just didn't work! It was very frustrating! It turned out I was running Apache 1.3 and reading the Apache 2 documentation. You can see at this stage that I didn't know much about what I was doing.

Then there was the Facebook API. It talked all about authentication (yawn) and making requests to the Facebook server. I really struggled with how that was relevant to me. I googled and found something called minifb.py which was Python code for doing Facebook stuff. All of the Facebook doco refers to PHP which I don't speak. minifb did more authentication stuff, and I still didn't get it. Eventually by following examples I figured out what I was supposed to do, and I loaded a Facebook page which made a request to my web server. A small victory, at last.

I've glossed over a few things here. Firstly, my ISP gives me a dynamic IP address, so whenever that changed I'd have to go to Facebook and reconfigure my app to point to the new address. A mate at work told me about dyndns.org which provides DNS services for exactly my situation, and I figured out that my router supports it. So now whenever the router is restarted it connects to dyndns.org and tells it that friendless.servegame.org is at this new IP address now. It's very cool. I also had to deal with the network security - our router was configured to not allow any requests from the outside to come into our home network. I had to tell it to route requests for the HTTP service through to my desktop. Not that hard to do, but tricky to realise that you have to do it.

OK, so we were at the "one request got through once" stage. Nothing got any easier. Facebook uses HTML, but it extends it with FBML (Facebook Markup Language) which implements all of the cool widgets that you use to invite friends to do stupid stuff. So I needed to learn a little about that. I also needed to figure out how I was going to implement the user interface. JavaScript looked pretty handy, so I learned that as well.

JavaScript really is pretty cool. You can do all sorts of programming on the client side, and with AJAX you can even make calls back to the server from within the web page. Without worrying about the Facebook thing, I coded up some JavaScript to render the board in the web page, figure out which cell the user clicked on, then make an AJAX RPC back to the server to make the move. It took a while, but it was sweet. After I got it going I hooked it into the Facebook code, and Facebook said "JavaScript not allowed". They're afraid you'll hack around with there HTML and corrupt the site experience.

Well that was a wasted week! It was fun, but ultimately got me no closer to having a working game. I experimented with many solutions. I knew that Scrabulous used Flash, but Flash is not free and I wasn't particularly motivated to learn it, so I couldn't copy from them. My goal was to do as much work as possible in the client, but as JavaScript wasn't allowed I couldn't see how to do much at all. I probably could have written a Java applet (I mean, I know I could do it, I just don't know whether Facebook would allow it), but even I understand that users don't want to install Java.

This was all complicated by the fact that I couldn't get Facebook to display my images. In fact, I still can't. Any image included in my app has to come direct from my web server - if it goes through Facebook its gets interpreted as text. Yes I'm setting the content type. I still don't understand that one. I have another app in progress using the Django framework and it does the same thing. So I had to solve the client technology problem and the image serving problem at the same time.

The solution I eventually came up with was to generate the board image on the server side, serve it direct from my own machine, into a HTML form, so when the user clicked on a cell it would send me back the (x,y) coordinates of the click. I could then convert that back to a cell on the server side. Nothing at all happens on the client, but at least it seems to work.


I got the game basically working. The code about detecting wins and so on was EASY compared to all of the technology nonsense. The game requires two real players so there's no AI involved. However them I needed to add some of the bells and whistles, for example, notification to your opponent that you've started a game.

That was when the authentication tarrasque bit me. You see, Facebook has this completely sensible system where your page is treated as a form with lots of hidden fields. Some of those fields, e.g. user id, Facebook session id, and so on, come from Facebook and tell you Facebook stuff. Those fields are digitally signed and you use your application secret to check they're kosher. EXCEPT, if you have a form on your page like I do, it sends some of that stuff twice, and digitally signs it twice. So you have to figure out what is authenticated by what signature. I ended up changing minifb to deal with it, because of course I was the first person to use minifb to receive a form submission and nobody else cared that it didn't work.

So after I figured out authentication I was able to get the session key which allowed me to send notifications to the server. At the moment I send a notification only when a game is started, as if your application sends too many notifications in one day your application gets classified as spam. Given the spamminess of many Facebook applications, the fact that they monitor that is a good thing.

Anyway, it's all sort-of working. I need some users to try it out.

http://apps.facebook.com/hexgame/

Please.

5 comments:

Iain Cheyne said...

I installed it. Someone challenge me...

Iain Cheyne said...

I typed "Mikko Saari" into the challenge a friend box, but got a MOD_PYTHON_ERROR.

MOD_PYTHON ERROR

ProcessId: 10894
Interpreter: '127.0.1.1'

ServerName: '127.0.1.1'
DocumentRoot: '/var/www/'

URI: 'http:///facebook/hexgame/app.py/startNewGame'
Location: None
Directory: '/home/john/facebook/hexgame/'
Filename: '/home/john/facebook/hexgame/app.py'
PathInfo: '/startNewGame'

Phase: 'PythonHandler'
Handler: 'app'

Traceback (most recent call last):

File "/usr/lib/python2.5/site-packages/mod_python/importer.py", line 1537, in HandlerDispatch
default=default_handler, arg=req, silent=hlist.silent)

File "/usr/lib/python2.5/site-packages/mod_python/importer.py", line 1229, in _process_target
result = _execute_target(config, req, object, arg)

File "/usr/lib/python2.5/site-packages/mod_python/importer.py", line 1128, in _execute_target
result = object(arg)

File "/home/john/facebook/hexgame/app.py", line 77, in handler
return publisher.handler(req)

File "/usr/lib/python2.5/site-packages/mod_python/publisher.py", line 213, in handler
published = publish_object(req, object)

File "/usr/lib/python2.5/site-packages/mod_python/publisher.py", line 425, in publish_object
return publish_object(req,util.apply_fs_data(object, req.form, req=req))

File "/usr/lib/python2.5/site-packages/mod_python/util.py", line 554, in apply_fs_data
return object(**args)

File "/home/john/facebook/hexgame/app.py", line 173, in startNewGame
game = _createNewGame(args, int(args['boardSize']), starter, opponent, first, usePieRule)

File "/home/john/facebook/hexgame/app.py", line 429, in _createNewGame
g.save()

File "/home/john/facebook/hexgame/app.py", line 245, in save
_insertRow(self._db, self, "hex")

File "/home/john/facebook/hexgame/app.py", line 235, in _insertRow
library.dbexec(db, sql, tuple(args))

File "/home/john/facebook/hexgame/library.py", line 222, in dbexec
c.execute(sql, args)

File "/var/lib/python-support/python2.5/MySQLdb/cursors.py", line 168, in execute
if not self._defer_warnings: self._warning_check()

File "/var/lib/python-support/python2.5/MySQLdb/cursors.py", line 82, in _warning_check
warn(w[-1], self.Warning, 3)

File "warnings.py", line 62, in warn
globals)

File "warnings.py", line 102, in warn_explicit
raise message

Warning: Field 'moveList' doesn't have a default value


MODULE CACHE DETAILS

Accessed: Tue Dec 25 22:51:39 2007
Generation: 3

_mp_7d2d3a1236969e9194e20a232162da86 {
FileName: '/home/john/facebook/hexgame/app.py'
Instance: 1
Generation: 3
Modified: Mon Dec 24 15:22:00 2007
Imported: Mon Dec 24 22:56:39 2007
Children: '/home/john/facebook/hexgame/library.py',
'/home/john/facebook/hexgame/minifb.py'
}

_mp_946845283f607afa1b1d3025c165133f {
FileName: '/home/john/facebook/hexgame/library.py'
Instance: 1
Generation: 1
Modified: Mon Oct 22 18:25:44 2007
Imported: Mon Dec 24 22:56:39 2007
}

_mp_7a667233a5361419ef8262b59e13ee22 {
FileName: '/home/john/facebook/hexgame/minifb.py'
Instance: 1
Generation: 2
Modified: Wed Dec 19 17:29:27 2007
Imported: Mon Dec 24 22:56:39 2007
Friends: '/home/john/facebook/hexgame/library.py'
}

Iain Cheyne said...

Shoot me an email, if you want me to give you feedback by email.

Mikko got the invitation and accepted. I tried to place my first piece, but got kicked out with the same MOD_PYTHON_ERROR.

Friendless said...

Thanks Iain, I think I have it sorted now. I don't understand why the errors didn't happen all the time. It feels like a timing problem but there's no concurrency happening here.

Sebastien said...

I'll help you try it out. I'll connect with you in FB.