Showing posts with label bugs. Show all posts
Showing posts with label bugs. Show all posts

Sunday, May 9, 2010

Oh hey, detecting and updating user presence is ridiculous. ( = pre-submission goals: update #2)

An annoying behind-the-scenes bug turned out, as I've learned after many hours of struggle today, to be far more frustrating and complicated than I could have imagined.

In my Users table in the database, I simply store a boolean, 'logged_in', by which I intend to record which players are currently using the app.  This does not mean whether they are logged into Facebook, since that status is likely unrelated to my app.  So, I define my logged_in boolean to mean whether the user is on either the index (home) page or the playgame.php page.

It's simple enough to ensure this boolean is marked true when the user loads either of these pages.  Detecting their leaving a page, however -- with the added requirements of updating all contingent information -- is unbelievably far more difficult.

Concerning the latter note, if a user leaves a game while it's in progress...
  • They should be marked 'logged_in' = false, since if they're navigating to one of the app pages, that page will restore that boolean to true.
  • Their 'current_game_id' and 'current_pid' variables must be set to null, to indicate they are no longer in that game.
  • If they were the last remaining player in that game, all turns pertaining to that game as well as the game's entry itself must be cleared from the Turns and Games tables, respectively.
  • If there are any other players left in that game, the 'num_players' value for that game must be set to the new correctly decremented value (and I don't want to just subtract 1, in case of race conditions, so I always want to check with how many players are still marked as involved in the game based on 'current_game_id' in the Users table).
  • The other players in the game should be checking for whether their fellow players have left, and must be informed of such dropouts.
 My original solution to detecting a player leaving the game page was to register the window.onunload event to make an AJAX POST to a php script that would update all appropriate things (see the list above).  This worked well enough in Firefox; I checked the results in the database, and Firebug showed me that the POST was indeed going through, despite the fact that the browser was navigating to another page.  However, I ended up discovering that the window.onunload event, or perhaps the POST, wasn't going through fully in Safari.  This led me to realize I should try an entirely different approach that would not rely on a browser event that may not have solid implementation in all browsers...

Many thanks are due here to John for helping me form new ideas for how to make this work.  Without going into the painful details and resulting unfortunate spaghetti-fication of my code (err..), the basic solution architecture is in three components:
  • The Users table now has a field called 'activity_time'.  Every 30 seconds, whether a user is on the home page or the gameplay page, an AJAX POST calls a php script to set this field to the MySQL CURRENT_TIMESTAMP.  So this is saying "user x is still here".
  • While on the gameplay page, the same script additionally checks for (a) any users marked with that 'current_game_id' but whose 'activity_time' is more than 30 seconds old, and (b) any users with one of the 'fb_id's registered (in a Javascript object stored by the page) as involved in that game, but with 'current_game_id' set to null.  [Note: (a) condition = those who have left the app by going to another page outside of it or closing the browser tab/window, and the cron job has not yet cleared up their data; see next bullet...  (b) condition = those who moved back to the app home page, which clears up their game data immediately, or those who have been cleared up already by the cron job.]  The id's of these "dropout" users are sent back to the page; the display of players in the game is updated to remove the dropouts, and a message is given to the user to advise them to return to the home page and start another game...(see below *).
  • There is now a cron job set up on the server to run every 5 minutes.  It calls another php script which retrieves all users whose activity time is more than 50 seconds old, removes any current game data they have, marks them as logged out, and either decreases the number of players in the game they had been in, or deletes that game entirely if it's now empty of players.
(*) Right now, there is no handling for updating the game state as it's stored in the ActionScript within the Flash app instance, nor does the page stop polling for the dropout players' turns.  As I see it, there are two options for where to go from here, regarding this issue:
  • Don't bother fixing the AS game state.  Just redirect the user automatically back to the home page.  This would make sense anyway for games that only had 2 players to start with, since playing alone is not as much fun.
  • Use the ExternalInterface class in AS to receive a notification from the page (via JS); update the game state accordingly; allow play to continue without the lost player(s).
Given how much trouble logout status has given me for many hours today, I'm inclined to go with the first option (or just let the user figure out that it doesn't work, and that they need to manually go back to the home page or leave the app).  It might turn out to be pretty quick to implement the second option, as long as there is still at least one player in the game, but I would rather turn my attention and remaining time towards a couple of the other goals on my pre-submission list, since I do think they are still important.

Finally, finally:


(I wonder how many Pandora hours I've used, out of my 40 hours given for the month, just from working today...)

Saturday, May 8, 2010

Pre-submission goals: update #1

Things fixed or completed so far...
  • The current velocity for each player is now displayed next to the thumbnail of their Facebook picture and is updated after each turn.
  • Players now cannot occupy the same gridpoint at the same time.  To do this, I simply don't display the little circle that indicates that you can move to a point if another player's piece is currently located on that point.  (I had one hitch initially -- I forgot that if your current velocity allows, you sometimes can stay on the same exact point you're already on, so I didn't want to hide the circle if it's on top of your own piece.)
  • I believe I have now fixed the bug of move-option circles not appearing when they should have, during the case of both (out of 2) players being out of bounds at the same time.  Luckily, it was a simple matter of updating a boolean that pertained to the other player; as in, I'd forgotten to update that bit of game state across all player instances (not just the instance of the player whose turn it had been).
  • (??) As John is graciously allowing me to test via his Facebook login, I was able to play a couple of full games (by logging in as myself in Firefox and as John in Safari) on my own screen, and the winner notification worked in all cases.  So maybe this is not a bug after all?
A thought I just had: I should really test this on more than just two players.  I don't think I ever really have.

p.s. Screenshot of preventing players from occupying the same point, without removing the option circle that coincides with your own current position:

Sunday, May 2, 2010

Goals prior to final submission

So after my first full night ever in the SIG lab, I managed to pull off my presentation pretty well, I think.  I even ran a live demo, playing against John, and nothing broke. (!!)

Post-presentation, I've still got about a week to wrap this project up.  Here's what I'm hoping to get done in that time:
  • (bug) Fix/rework the way user logout is recorded in the database... right now I keep finding that users are still marked logged-in when I don't think they are.  (At least login is working right, I think!)
  • (bug) Make sure the "player __ won!" popup works for all players.  I seem to remember a couple of test runs in which only one out of two players saw it when someone had won.
  • (feature) Show each player's current x- and y-velocity somewhere next to their picture.  This should be really easy to do and would be helpful to see.
  • ("feature") Write the About and Help pages (including gameplay instructions)
  • (enhancement) Spruce up the (logged-out) intro page and the main page with a short intro to the gameplay.
  • (enhancement) I kind of forgot to implement preventing players from occupying the same grid point at the same time.  This also should be extremely easy to implement.
  • (bug?) I just today played the game against a friend and we ran into the case in which both of us were out of bounds at the same time.  I think there might have briefly been a mix-up regarding displaying the move options for one of us.  (Gameplay still proceeded properly though, I think.)  So I should test this again and fix if needed.
  • (bug) I nearly forgot this one... but I really should do something about the case of players running off the edge of the entire grid.  Apparently this happens more easily in my online version than on grid paper with a pencil, haha.  (See previous blog post.)
  • (enhancement) Show more clearly when another player takes their move.  I'm thinking I should highlight the position circle they've chosen for about a second prior to turning off those circles and showing their piece in the new position.
  • ("enhancement") Write some back-logged blog posts.
I don't think any of these changes are too major, and none of them significantly change the gameplay, which was certainly functional by presentation time.

Will update this list if I think of anything else.

Saturday, May 1, 2010

Double fail: another full game played against a friend

Things I learned from this game:
  • I really need to do something about people flying off the edge of the entire grid.
  • I also really need to write an instructions page.  It needs to be really clear about how your acceleration works...
  • I need to double check what's going on when two players are out of bounds at the same time.  The flow of turns seemed to happen properly, but I think at one point my opponent's set of move-option circles didn't appear on my screen when they should have.  (I guess they showed up on his though because he was able to take his move.)
  • Also, Matt exploited his out-of-bounds moves to jump back into the track by just cutting straight through, whereas I basically slowed down to almost zero speed and turned around.  I'd actually never thought of his strategy before.  It's not exactly a bug that he was able to do this, and I'm not sure how to curb it in any simple way in the code, either...
Now for the fun screenshots...

Game 1 (well, the attempt):
(filename: matt_fail.png)



Game 2
(filename: tory_fail.png)

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.


Wednesday, March 31, 2010

Successful push of variables in AS =[PHP]=> database, plus AS callback

So the other night I set out to implement the plan I outlined before to figure out how to push data (i.e. each turn's information) from Actionscript variables to the database.  Here are the components I wrote/set up:
  • Database:  set up a test table called TestTurns with 3 fields - p_id (piece's ID number), xpos, and ypos.
  • Actionscript:  added a storeTurn() method to the 'piece' class, which makes use of the URLVariables, URLRequest, and URLLoader classes to structure and send an HTTP POST request, containing a piece's ID number, x-position, and y-position, to the PHP file.  Also wrote a callback function - which gets attached to the Loader's COMPLETE event - that spits the echoed data from the PHP script (provided in name/value query-string format) into a URLVariables object and "logs" some information into a dynamic text box currently sitting in the middle of the race track.  (This is a temporary debugging mechanism, obviously.)
  • PHP:  wrote a small script in a file which grabs the 3 POSTed variables, runs an insert query on the database, and echoes back the information it received and whether the query was successful.
These components seemed straightforward enough, but I did run into some issues along the way.
  • First, I was trying to test the POST to the PHP script by running the Actionscript locally.  It was able to pull the file, but in the AS callback function I was getting these strange strings as supposed value counterparts of the names that the URLVariables constructor was able to parse out.  I rearranged and tested things for some time until the "well duh" moment: I was expecting PHP to run while sitting on my machine.  Oh, wait.  :)  (To test locally, I wrote a simple text file containing a name/value string, directed the HTTP request to that file, and the URLVariables constructor parsed it just fine.)  After this, I uploaded the files so I could test it for real (i.e. with the PHP running).
  • I knew that for an insert query, the mysql_query function would return a true or false based on whether the query was successful or an error occurred.  However, I didn't know the details on the representation of that boolean, and how it would get converted when echoed by the PHP and then parsed by the URLVariables constructor in the Actionscript.  From the output I was getting dumped into the "log" it was appearing as a 1 or 0 instead of the string "true" or "false", so in order that the URLVariables variable for 'success' would actually have a value of true or false (that could then be used in a conditional), I just used the ternary conditional operator (?:) to convert the 1/0 value ahead of time in the PHP echo string.
  • Also from the output log I realized that since I was calling (in the handler PHP file) require_once for the db_connect file, whatever was echoed in that file was showing up amidst what the handler file was echoing.  Naturally this was screwing up the name/value query string I was trying to create so that the URLVariables constructor (in AS) could read it.  I was looking through the PHP documentation for the include/require functions to see if there was a way to suppress the output of the included file, but didn't find anything along those lines.  As an alternative for echoing messages of success or error in db_connect.php, I created a variable to store a boolean for connection success.  Then, when I require db_connect.php in the handler file, I can store that boolean (i.e. $connect_success = require_once "db_connect.php";) to discover and deal with connection errors.
Once I had figured out these problems, though, it actually worked...!  I took screenshots both of the turn data in the test table and the output as it showed up in Flash: