Tuesday, April 27, 2010

The Facebook API battle/disaster night

I thought it would be fairly simple...


I had worked on a Facebook iframe application once before, and I remembered that the addition of requiring Facebook login and then being able to render XFBML components was not very complicated at all.  It was a matter of pasting in some code in a few different places, and magically, the page would request you to log in and add the application, and then it could show you your Facebook picture and name.  (By the way: FBML = Facebook Markup Language, and XFBML is the version you can use on off-site pages, i.e. your pages in an iframe, or on Connect app pages.)

Well, there I was, just over a year later.  I began delving into the wiki/documentation on authentication etc., and what I began to realize is that everything seemed to be in transition.  There was this new "graph API", and OAuth authentication, and a new Javascript API -- or at least, these all appear to be new, based on the look of the pages and what I could gather from the descriptions.

I really only wanted to be able do 3 things: require login and authentication of the app, pull/display users' names, and pull/display users' profile picture thumbnail.

Many long hours of confusion later, I had gone through stages something like this:
  1. using the wrong new stuff (a PHP API which I now think is supposed to be for Connect apps?);
  2. trying to use the old PHP library, cross-domain receiver file, XFBML, and iframe resizing... I don't even remember what didn't work in this combination, but some piece didn't;
  3. trying to combine the old PHP library and XFBML paste-in code with some of the newer stuff, I think? I don't even remember;
  4. attempting to manually implement the OAuth redirection cycle (things like obtaining a key and this and that), which actually almost worked...;
  5. deciding to give up on making this an iframe application that's actually within the Facebook chrome, and instead, switching it over to a Connect app, using the easy authentication/login via the new Javascript API and all of which is luckily still compatible with XFBML.
...wow.  It was a long, tiring, frustrating struggle.  Much of it was due to over-documentation, if you will, and a lack of clarity in terms of marking pages regarding at which level of API/platform transition the information and code on that page would still be relevant and functional.


And now for the bombardment of a list of links.  Here are some of the many Wiki pages/sections I tried to use to figure this out...

(Note: seems like they are really in transition currently - some of the links may not work or will give a message saying the content is moved/updated)


New (pages are in the theme of the new documentation)

Overview guide to apps (new)

Authentication (new)

Graph API (new)

JavaScript SDK (new)

Old REST API - apparently the precursor to the new Graph API


Old, but seems to still apply / be compatible with new stuff

XFBML (including tag list)


Old, and seems to not apply / not be compatible with new stuff

API (old?)

PHP (old?)

FB_RequireFeatures (old?) - for dynamically loading components of the JS API (via FeatureLoader.js.php)

Authorization and Authentication for Canvas Page Applications on Facebook - matches with the old(?) PHP library

Extended Permissions

Connect/Setting up your site - including XFBML rendering (but uses the FeatureLoader.js.php)

Friday, April 23, 2010

Features to implement by presentation day

A rough list for the moment; probably to be updated as I go...

Definitely:
  • Select from 4 different provided tracks to play on
  • Communication between Actionscript and Javascript on the page, so that various information about the game state (whose turn it is, players going out of bounds, etc.) can be displayed on the page... This will replace the "log" text box I currently have sitting in the middle of the track.
  • Players should lose every other turn while they are outside the track bounds -- see this discussion.
  • Displaying lines (options thereof?) connecting the dots of players' previous moves (maybe just the last move, to highlight it; probably all moves once the game is over, to show the overall trajectory)
  • Finish the database setup for real... because so far I've just been adding fields and tables along with adding corresponding features to the game.  I should probably redraw/re-post a new ER diagram too.
  • Facebook login required to get to the app (because right now I'm pretty sure you can just go to the page and see it); and consequent storage of app users in the database, including whether they are currently online.
  • Display on the main (index) page of your Facebook friends who are currently logged in to the app
  • [maybe] primitive inviting of your friends to join games
  • Obviously, the entire interface needs a nice layout and lots of CSS...
  • Some sort of about/help page(s)
Optimally would be nice to have these, but no guarantees:
  • AI opponent so you can play single-player
  • allow players to draw their own tracks
  • ...and save them for subsequent use?
  • public/private games or allowing the game organizer to moderate (real-time) other players' joining

Wednesday, April 21, 2010

Multi: -player, -game, -track support

Comprehensibility of the following bullet-point lists is questionable, unless you're me and therefore intimately familiar with this project's code...

Multiplayer support:
  • Games support 1, 2, 3, or 4 players.  Having just one player right now makes no sense because there is no AI opponent.
  • Gameplay page polls (Javascript AJAX calls) database to discover additional players joining the game.
  • Player who initiated the game can decide to stop the polling and start the game; alternatively, the game will automatically be started if 4 players have joined (since that's the maximum it supports).
  • Once the game is to be started, it's set to in_play = true in the database, and the flashvars string is sent to the AS (including: game_id, track_num, pid, numPlayers...yeah for variable-name inconsistency!)
Multi-game support:
  • Can start a new game from the index page - game_id is sent through the querystring as -1 to indicate a new game should be started; PHP figures out the current highest game_id number and inserts a new game to the db with the next-highest id number.
  • Index page displays a list of games that have been initiated but are not yet in play (i.e. are open for more players to join) - these have not yet been closed by their organizer and still have fewer than 4 players.  One need simply click to join; game_id is passed in the querystring and retrieved in the PHP via the global $_GET variable.
  • The game_id is sent to AS through flashvars as mentioned above.
Multi-track support:
  • I made a MovieClip object called 'tracks'.  Each of 4 keyframes will eventually display a different track I've drawn, but right now they all show the same one.
  • When you are starting a new game from the index page, thumbnails of the 4 options for default tracks are shown, and you simply select one before you click the 'go' button to head to the playgame.php page.   The track number you've selected is submitted via a form POST.
  • For new games, the playgame.php page pulls the selected track number from the form POST and inserts it as one of the fields when adding the new game to the database.
  • For existing games (that you're joining), the page queries the database based on the game_id provided in the querystring and thereby pulls the track_num.
  • The track number is then passed to the AS as one of the flashvars.  Since this number refers to the keyframe of the instance of the 'tracks' MovieClip, I simply pass it as a parameter to display the correct track: realtrack.gotoAndStop(track_num).
  • NOTE: I've currently not implemented support for players drawing their own tracks... If I get to that, I'm not sure if I will be able to associate the track number to each game by just storing it as one of the attributes in the Games table.  My original database design includes a separate table for tracks, and I imagine storing custom tracks somehow would require this table, BUT, right now, with only my 4 possible default tracks it works just fine to have an id number (1 - 4) as an attribute for each game.
Well this post feels a bit disorganized and haphazard... I will try to post a screen-capture video of all this stuff soon.

Thursday, April 15, 2010

Non-retangular tracks will work after all

Dear Joe: I don't think I'm capable of writing a short post.

So you know how in the past, the "track" has always been two concentric rectangles?  That's because I was simply drawing it as a Shape object.  To be able to call hitTestPoint on it (to check whether players' pieces were inside the track), I added a fill to the track space, but set alpha = 0 for those pixels so they would be transparent but still register as hits.

Well, no one wants to play this game on a rectangular track all the time.  So it's finally time to make more complex tracks work.  I didn't want to try to draw a curving track by piecing together arcs using the drawing API, so instead I made a new MovieClip object to add to the library.  I called it 'tracks' and am thinking I'll make several tracks, each one on a keyframe.  The user will somehow be able to select one, and then I'll just call gotoAndStop(frame#) on the one instance of the tracks object, and add it to the main scene.

For now I just drew one track using the pen tool and smoothing out the corner joints (vertices) between line segments.  If you go into the menu (Modify -> Shape -> Advanced Smooth...) you find a function that'll create curvature-editing handles for the vertices.  It's not exactly what I was expecting based on experience with Illustrator, but it came out decent enough.  Luckily, I was able to add a transparent fill within the track area (just using the fill tool), just like I'd done programmatically with the rectangle track.  And the bounds testing using hitTestPoint works = one hurdle easily cleared.

[Next steps: multiple tracks to choose from; users drawing their own tracks...!]

Resizable iframe

As I mentioned in my post about setting up the Facebook app, I selected the option to allow the iframe (which holds all of my content -- it pulls my app pages from toryg.net) to be resizable.  I didn't really know what that meant when I was setting it up, but now I've realized: if my content is longer vertically on the page than what's seen in the browser window, the content just gets cut off.  (Thanks to Firebug, I found the css command that was setting the iframe to have overflow: scroll; -- so when I was testing two instances of the swf on one page, I would simply go into Firebug and cancel that line so I was able to scroll the iframe and see everything.)

Today I decided to figure out how to actually fix this.  Thanks to the resizable iframe page (on the Developers' Wiki), I found out I had to set up two things first:
Then in JavaScript I had merely to call up the CanvasUtil features, set the location of the xd_receiver.htm file, and call a library function (startTimerToSizeToContent()) which watches changes in the canvas content and automatically resizes the iframe appropriately.  (In other words, I copied the several lines of code from the resizable iframe page.)

The only hitch I had was that at first I was placing those few lines of Javascript up in the head of the page, and then that hidden div in the body hadn't been created yet when the script was trying to initialize.  Thus, unfortunately, I have to stick the script in the body, right after that div.  But it works now, so that no matter how vertically-long my content is, the iframe changes to accommodate it and I can scroll freely and see everything.

Tuesday, April 6, 2010

Quick note on the database engine

I met up with Jeff Nimeroff briefly because I thought I was about ready to set up the actual database tables and start using those in place of the test tables with which I'd been working.  I'd forgotten which engine he had suggested to use, and why, from a previous discussion.

Though there are a number of engines that MySQL supports, Jeff explained that the two main engines used are MyISAM and InnoDB.  The key difference between them is that MyISAM locks entire tables during queries, whereas InnoDB only locks at the level of a single row.  The functions of my game require running various polling functions that query the database so that players check for and receive updates on each others' actions.  Therefore it probably does not make sense to lock entire tables when, for example, up to 4 client instances of the game are making calls to the same table at once.  Thus he suggested I go with the InnoDB engine.

However, because this is a turn-based game, for the most part only one client can be making an update or insertion query at a time.  Therefore it's not totally critical to avoid table-locking.

Finally, he informed me that I can switch the database engine at any time--I'd thought it was set in stone, which is why I'd wanted to check in with him about which to use prior to creating the "real" database tables.

Monday, April 5, 2010

Multiplayer WORKS! (separate instances updating through the database)

Joe gave me permission to make the blog posts short and sweet.  I'm going to try to keep this one more at an overview level.

The goal I set out to accomplish was to separately run multiple instances (i.e. one per player) of the swf with each one keeping itself updated on the actions made (turns taken) by the others.

How it works:
  • In the HTML for the embed/object elements, the FlashVars parameter provides (a) the total number of players in the game, and (b) with which player number that particular swf instance is to be associated.
  • As the "whole document" class (TraceRace) is a DisplayObject, its Actionscript constructor uses its LoaderInfo instance to grab the values of the FlashVars.
  • When it's a player's turn, that swf will both show and allow clicking on the options for the next turn.  Upon the click of an option, the URLVariables/URLLoader/etc. classes send off this data to a PHP handler script, which sends a MySQL query to input a row in the database table.
  • When it's not a player's turn, that swf will show the other player's turn options, but they are not clickable (so that you can't go taking someone else's turn).  The swf uses the same URL[whatever] classes to poll for other players' turns showing up in the database: every two seconds, a POST is made which is handled by a PHP script.  If the script returns the other player's turn data (since it has been entered in the database), it stops polling, and this swf updates the other player's move visually and in its own state (variables).  If the other player's turn has not been entered into the database yet, the script just returns a boolean indicating such, and it initiates the next polling request.
Problems:
  • After I thought I'd figured them out last time, turned out I still was having issues with booleans.   I'm now sending booleans through PHP echoes as 1 (true) or 0 (false), because the AS can easily convert them to actual booleans, whereas the word strings "true" and "false" are not so easily converted.
  • During debugging I ended up trying to be pretty precise with types, even though I'm not always sure if that was causing a bug or not.  For example, I cast variables parsed from PHP-echoed name/value strings to, say, a Number or a uint.  I also cast a piece to a piece when I pull it out of an array, since I don't think arrays are designated to hold a certain single type.
  • It looks like AS isn't happy with my passing an argument as 'null' (since I was going to retrieve the necessary object a different way, once inside the function).  Then I tried using two parameters with default values, but since you can only leave out arguments at the end of the list, I can't input the 2nd and drop the 3rd in one call while doing the reverse in another call.  Eventually I just wrote a getter function so I could access the object before the problematic call.
  • One of the weirdest problems I've had yet, I actually managed to create myself.  By initializing an Array with a specific length, and subsequently pushing more elements into it, I ended up with empty spaces prior to the pushed elements.  This actually makes pretty decent sense.  But since previously I hadn't initialized the array with a length, the pushed elements just added/filled spots from the beginning.  I finally realized what was going on and now add the elements by accessing the array slots via indices.
Now for the cool part.  I took a screencast video (thanks Screencast-O-Matic!!!) to show how this actually all operates in my testing setup.