Tuesday 5 March 2013

iPhone Coding Tutorial - Creating an Online Leaderboard For Your Games


As you may have seen, there are quite a few services out there offering free leaderboards. These are great and all, but sometimes you want to have complete control over your data. I have put together a complete tutorial detailing step by step how you can create your own online leaderboard for use in your iPhone games. For those of you who do not know, a leaderboard is Essentially a high scores list. This tutorial will also give you a very simple introduction to interfacing with web services.
The first part of this tutorial will discuss how to build the server. We will be doing this using PHP and MYSQL. I will also assume you have some working knowledge of PHP and MYSQL (if not, use the Google machine to get up to speed).
Since this tutorial is so long, I have decided to split it up into pages. You will notice the page number at the bottom. Please use them to navigate between parts of this post I feel that I have to make this explicit as I will undoubtably get some dude in the comments saying "Where is the rest of the tutorial."
Skill level: Advanced

Creating The Database

When creating a leaderboard, you will need some way of Storing the data. We will be using MySQL for our storage. You can also be lame and simply use flat files . This would add quite a bit of code in the long run as you would have to write all of the sorting and pagination functionality yourself. Do not do it.
One thing to note about my php server files. I know they could be cleaned up a little and optimized (you could create a config.php file that contains all the db interface code), but the goal of this tutorial is not to show you how to code PHP killer. It's to show you how to create code that you can connect to your iPhone apps.
I like to create one file that does a complete flash of the database. That way when, I'm testing or switch from staging to production, it is a very simple process. So, with that being said, here is my code for create_db.php.
<? Php 
        / / create_db.php 
	/ ** MySQL database name * / 
	define ( 'DB_NAME' ,  '' ) ; 
	/ ** MySQL database username * / 
	define ( 'DB_USER' ,  '' ) ; 
	/ ** MySQL database password * / 
	define ( 'DB_PASSWORD' ,  '' ) ; 
	/ ** MySQL hostname * / 
	define ( 'DB_HOST' ,  $ _ENV { database_server } ) ;
 
	$ Table  =  "highscores" ;
 
	/ / Initialization 
	$ conn  =  mysql_connect ( DB_HOST , DB_USER , DB_PASSWORD ) ; 
	mysql_select_db ( DB_NAME ,  $ conn ) ;
 
	/ / Error checking 
	if ( ! $ conn )  { 
		the ( 'Could not connect'  .  mysql_error ( ) ) ; 
	}
 
	/ / Drop existing table if exists 
	mysql_query ( "DROP TABLE $ table " ,  $ conn ) ;
 
	$ Retval  =  mysql_query ( "CREATE TABLE $ table (
		id INT (11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
		UDID VARCHAR (45),
		VARCHAR (25),
		FLOAT score (10.2),
		date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
	) " , $ conn ) ;
 
	if ( $ retval )  { 
		echo  "Database created ..." ; 
	}  else  { 
		echo  "Could not create database"  .  mysql_error ( ) ; 
	}
 
	mysql_close ( $ conn ) ; 
?>
I'm not going to go into too much detail about this code, however I will give a high level description of what's going on. One thing to note about this code is it Assumes you already have the database created.
First, we are defining variables that contain our database information . Make sure you replace my empty strings with your database info. After we connect to the database, we drop the table if it already exists. This is useful if you want to wipe out your test data. Finally, we create the scores table. Navigate to this file in your browser to run it and create the scores table. Pretty easy right?
You will want to make sure to remove this file from your server after running it to avoid people resetting your database.
Now that our database table has been created, it's time to implement the web service code to publish new scores to our leaderboard.

Scores Inserting Into The Database

Inserting scores is very simple to do. We will simply make a GET request from our iPhone app to our put_score.php page and pass in information through the GET parameters. An example of this might be
http://icodeblog.com/ws/put_score.php?udid=0123456789012345678901234567890123456789&name=brandontreb&score=210.13&secret=some_secret
Here is an explanation of the variables.
VariableDescription
secretThis is some password that only you know. It will prevent people from inserting invalid data into your database. We will hardcode this into the script below
UDIDThis is the UDID of the user's device. It will be used to uniquely identify each user
nameThe name to display in the lead board
scoreThe score for that given user.
And now the code for put_score.php
<? Php 
	/ / put_score.php 
	/ ** MySQL database name * / 
	define ( 'DB_NAME' ,  '' ) ; 
	/ ** MySQL database username * / 
	define ( 'DB_USER' ,  '' ) ; 
	/ ** MySQL database password * / 
	define ( 'DB_PASSWORD' ,  '' ) ; 
	/ ** MySQL hostname * / 
	define ( 'DB_HOST' ,  $ _ENV { database_server } ) ;
 
	$ Table  =  "highscores" ;
 
	/ / Initialization 
	$ conn  =  mysql_connect ( DB_HOST , DB_USER , DB_PASSWORD ) ; 
	mysql_select_db ( DB_NAME ,  $ conn ) ;
 
	/ / Error checking 
	if ( ! $ conn )  { 
		the ( 'Could not connect'  .  mysql_error ( ) ) ; 
	}
 
	if ( $ _GET [ 'secret' ]  =!  "some_secret" )  { 
		the ( 'Nothing to see here ...' ) ; 
	}
 
	/ / Localize the GET
	      
	       
	     
 
	/ / Protect against sql
	   
	   
	  
 
	/ / Insert the score 
	$ retval  =  mysql_query ( "INSERT INTO $ table (
			UDID,
			name,
			score
		) VALUES (
			' $ UDID '
			' $ name ',
			' $ score '
		) " , $ conn ) ;
 
	if ( $ retval )  { 
		echo  "Inserted score $ score for $ name " ; 
	}  else  { 
		echo  "Unable to insert score"  .  mysql_error ( ) ; 
	}
 
	mysql_close ( $ conn ) ; 
?>
So we see a lot of the same initialization code as we did in our create_db.php method. As you can see, there is not a lot of code here. We first just localize the GET variable and escape them To ensure that they can not be sql injected. PHP developers are so lazy that they always fail to do this. It is one line of code that can prevent a huge security flaw.
After localization and sanitation, we simply insert these values ​​into the database and print out the result. The last part of our server code Involves displaying the leaderboard. Now, we could write a service for returning xml and display it natively in the application, however displaying a table inside of a webview is much simpler.
So, we are going to output this data into HTML table that wants to get displayed inside of a UIWebView. Keep in mind that my table looks like crap and you should definitely style it before using it in your applications.


As mentioned before, we will be displaying a table on a webpage. The url that the webview will access will be something like this
http://icodeblog.com/ws/get_scores.php?sort=score% 20DESC
There are quite a few parameters that can not be used with my script that will deter mine how the table will be displayed. The parameters and their descriptions are in the table below:
VariableDescription
gradeThis value will deter mine what data the table will display. The possible values ​​for it are global, device, and name. Global will simply show the global high scores list containing all of the users.Device is specific to the calling device users. It will only display high scores for a given UDID. Finally, we have name, whichwill only display high scores for a given username.
offsetUsed for paging the SQL results. It will be the first parameter in the ORDER BY clause of the SQL select statement. Ex: An offset of 10 will tell the system to show records . starting with the 10th
countUsed for paging the SQL results. It will be the second parameter in the ORDER BY clause of the SQL select statement. This is basically the number of results to show in the table.
sortTells how to sort the table. If this is not specified, the default sorting is set to "score DESC". This will order by scores from highest to lowest.
UDIDThe UDID of the device viewing the leaderboard. This variable is required if you specify type = device
nameThe name of the device viewing the leaderboard. This variable is required if you specify type = name
As you can see, you have quite a bit of control over how the results get displayed.You may want to offer multiple high score to your user views (global, personal, etc ...). You can also fancy this up quite a bit and give the users the ability to view the high scores of other users on the list.
Here is the code for get_scores.php
<? Php 
    / / get_scores.php 
	/ ** MySQL database name * / 
	define ( 'DB_NAME' ,  '' ) ; 
	/ ** MySQL database username * / 
	define ( 'DB_USER' ,  '' ) ; 
	/ ** MySQL database password * / 
	define ( 'DB_PASSWORD' ,  '' ) ; 
	/ ** MySQL hostname * / 
	define ( 'DB_HOST' ,  $ _ENV { database_server } ) ;
 
	$ Table  =  "highscores" ;
 
	/ / Initialization 
	$ conn  =  mysql_connect ( DB_HOST , DB_USER , DB_PASSWORD ) ; 
	mysql_select_db ( DB_NAME ,  $ conn ) ;
 
	/ / Error checking 
	if ( ! $ conn )  { 
		the ( 'Could not connect'  .  mysql_error ( ) ) ; 
	}
 
	    DESC " ;  
	    
	     
	      
 
	/ / Localize the GET variable 
	$ UDID   =  isset ( $ _GET [ 'UDID' ] ) ? $ _GET [ 'UDID' ]  :  "" , 
	$ name   =  isset ( $ _GET [ 'name' ] ) ? $ _GET [ 'name ' ]   :  "" ;
 
	/ / Protect against sql
	    
	  
	   
	    
	    
	    
 
	/ / Build the sql query 
	$ sql  =  "SELECT * FROM $ table WHERE " ;
 
	switch ( $ type )  { 
		case  'global' : 
			$ sql  =.  "1" ; 
			break ; 
		case  "device" : 
			$ sql  . =  "UDID = ' $ UDID '" ; 
			break ; 
		case  'name' : 
			$ sql  =.  "name = ' $ name '" ; 
			break ; 
	}
 
	$ Sql  . =  "ORDER BY $ sort " ; 
	$ sql  =.  "LIMIT $ offset , $ count " ;
 
	$ Result  =  mysql_query ( $ sql , $ conn ) ;
 
	if ( ! $ result )  { 
		the ( "Error retrieving scores"  .  mysql_error ( ) ) ; 
	} 
>?
<DOCTYPE html PUBLIC "- / / W3C / / DTD XHTML 1.0 Strict / / EN"
         "Http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
	<meta name="viewport" content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
	</ Head>
	<body>
		<Php 
			/ / Display the table 
			echo  <table style="width:100%"> '
					<thead>
						<tr>
							<th> Name </ th>
							<th> Score </ th>
						</ Tr>
					</ Thead>
					<tbody> ' ; 
		    while  ( $ row  =  mysql_fetch_object ( $ result ) )  { 
				echo  '<tr>
						<td>
						' . $ row -> name . '
						</ Td>
						<td>
						' . $ row -> score . '
						</ Td>
					  </ Tr> ' ; 
			} 
			echo  '</ tbody>
				</ Table> ' ; 
			mysql_free_result ( $ result ) ; 
			mysql_close ( $ conn ) ; 
		?>
	</ Body>
</ Html>
After initialization, we build the SQL query based on the parameters that were specified. This will deter mine what data will get displayed. Notice that after the code to query the database, there is some HTML. This is because we want to output a full HTML page for our table. You can use this opportunity to ad seeking things as styling, images, and advertising.
Within the body, we simply loop over the results returned from MYSQL and output the score data. And that's it! You now have a fully functional high score server.
Complete with our server, there are only two things left to do: submit score data in our iphone application and display the leaderboard.

Submitting the high scores is very simple. I have created a method that you can drop right into your code and use the scores to submit to your server. Here is thecode for this method.
-  ( void ) submitScore : ( float ) theScore username : ( NSString  * ) username {
 
	NSString  * UDID =  [ [ UIDevice current device ] uniqueidentifier ] ;
	 NSString  * secret =  @ "some_secret" ;
	username =  [ username stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding ] ;
 
	NSString *urlString = [NSString stringWithFormat:@"http://icodeblog.com/ws/put_score.php?secret=%@&udid=%@&name=%@&score=%f",returningResponse : nil error : & e ] ;
	 return [ [ NSString alloc ] init with data : data encoding : NSUTF8StringEncoding ] ;
 }   	       
Let's go over this code. The first bit of code initializes some of the variables that will be sent to the server. We get the user's UDID and the secret for accessing our server. So, we must encode the username to be passed to our server in case the user enters any special characters.
Following that, we build the URL that we will be making our request to and build on NSURLRequest from that. Make sure you replace icodeblog with the URL of your server. Now the magic ...
We call the sending synchronous requestMethod of NSURLConnection to send the data to our server. Using synchronous instead of asynchronous tells the calling thread to block and wait for the request to complete before returning. I went this route in the sample code just to simply things. You could have also sent the data Asynchronously, but you would then have to implement all of the delegate methods.I will leave that up to you as a challenge.
Once the data returns from the request, we simply convert it to NSString and to return it. Here is a quick example of how you would call this method.
[ self submitScore : score username : usernameTextbox.text ] ;
This code Assumes that you prompted the user for their username via some sort oftext box name usernameTextbox. You can choose to implement this however you like.
The last part of this tutorial will show you a simple way to display the leaderboard in a UIWebView within your application .

Displaying the leaderboard in Your Application

This section of the tutorial will show you how to display the data in a UIWebView leaderboard within your application. One assumption I am going to make is that you will be displaying the leaderboard from inside a viewcontroller.
Here are the steps involved in displaying the leaderboard data.
First Add a new viewcontroller to your project and name it Highscore ViewController.Make sure you check to have it create the. H and. Xib interface file.
Second We need to write the code to display this viewcontroller. Go to the method where you want to display the high scores table. This could be in response to abutton click or done automatically at the end of the game. Some simple code for doing this is:
Highscore View * hsv =  [ [ Highscore View alloc ] initWithNibName : @ "highscore View" 
 	bundle : [ NSBundle main bundle ] ] ,
 [ self presentModalViewController : hsv animated : YES ] ;
 [ hsv release ] ;
This will cause the high scores viewcontroller to slide up from the bottom and display on top of the current view.
Third Now open up HighScoresViewController.h. Add a declaration for. UIWebView to IBOutlet in the header file as well as to IBAction named done Done will get called when the user presses a button done to hide the high scores table. Your header file should now look something like this.
@ Interface Highscore ViewController : UIViewController { 
	IBOutlet UIWebView * webview;
 }
 
@ Property ( nonatomic, retain ) UIWebView * webview;
 - ( IBAction ) done : ( id ) sender;
 
@ End
4th Open up Interface Builder and add a toolbar with a button that says "Done" and a UIWebView. The interface should look like this:
screenshot_01
5th Connect the UIWebView to your webview outlet and connect the touchUpInside method of the done button to your IBAction done and close Interface Builder.
6th Implement the viewDidLoad method of the highscore view controller to load your high scores webpage into the webview. Here is some code for doing this:
-  ( void ) viewDidLoad { 
        [ super load request : requestObj ] ;
 }      
One thing to note here is I am calling get_scores.php without any parameters. If you wanted to change the data displayed (or paginate) you might add something like? Type = name & name = brandontreb to show scores for a specific user. You really have a lot of freedom here to design this how you want.
5th Implement the method done. Since we displayed this viewcontroller using presentModalViewControler, we can simply do the opposite by calling dismissModalViewController on the parent viewcontroller. Here is the code for the method done.
- ( IBAction ) done : ( id ) sender { 
	[ self.parentViewController dismissModalViewControllerAnimated : YES ] ;
 }
And that's it! You now have a fully featured leaderboard that you can use in all of your iPhone games. 

No comments:

Post a Comment