Friday, March 9, 2012

Cross-Platform Global High-Score using Amazon SimpleDB

In this post I am going to describe my solution for a simple global high score that works with WP7, iOS and Android. I wasn't really following the development of web technologies for more than 10 years. So when looking for a global high score solution for my new game I needed to catch up with the recent technologies and then had to decide which one to use. I don't know if what I came up with is the best solution, but it is powerful yet simple and easy.
The preconditions for me for the global high score where:
  • It should be easy and fast to implement and maintain.
  • It should use standard web technologies.
  • It should be scaleable.
  • It should use the standard web http protocol.
  • It should be secure,
  • and it should be as cross platform as possible.
When it came to a standard interface using http I soon was sure that web services are the way to go. From there I decided that a RESTful (Representational state transfer) web service using XML should be most suited for .NET applications.
The next decision was whether to use my own virtual root server and setup the appropriate interface to a data base or use a cloud service. Even though there are a lot of tools available that support you to develop server side web services, in the end it looked like it would really brake the precondition of easy and fast implementation. Also scaling might get a problem if the game becomes very popular. I found that Amazon and Microsoft both offer a cloud service to a simple nosql database that already comes with a complete RESTful web service interface:
I actually first wanted to try the Microsoft Azure Service, but just when I tried to open an account, the management service was down and kept being down for the whole afternoon. Not a good omen for a cloud service, so I decided to go for Amazon SimpleDB instead.
Microsoft(!) offers a SDK for Windows Phone (though still beta) for the Amazon cloud services and of course Amazon offers SDKs for iOS and Android. But the cool part is, that the Windows Phone SDK comes with source code and can therefore be easily used with MonoTouch and Mono for Android as well! Using the SDK there is no need to write low level client side RESTful web services yourself, instead you have an easy to use interface to the Amazon SimpleDB data base.

Setting up the Amazon SDK
The source code for the AWS-SDK for Windows Phone is available from a GIT-Hub repository. So first check out the source code to a local directory. It comes with a complete set of samples for different Amazon cloud services. Inside the directory AWSWP7SDK you find the actual sdk. For WP7 you just have to include the project file AWSSDKWP7.csproj to your game solution and add the project as reference to your game.
For MonoTouch and Mono for Android you have to create a new project file for each. Just create a new MonoTouch and Mono for Android library and add all the SDK files. Then you need to make small changes in two files:
  • Amazon.Runtime\AWSCredentials.cs
  • Amazon.Runtime\ConfigurationManager.cs
In both files you should make a #if WINDOWS_PHONE ... #endif around all using statement to the System.Windows domain and sub domains. Inside the ConfigurationManager.cs you also should to this to the body of the LoadConfigFile() function, because we cannot read Windows config files with Mono. To initialize the database we therefore have to set the database key and security key in code instead of the config file, which is the the better way to do anyhow.
Now the SDK should compile with MonoTouch and Mono for Android and you can add the projects to your game solutions.

Getting started with Amazon SimpleDB
Before you can start developing you need to sign up for Amazon AWS here. There is a good setup guide from Amazon here. Unfortunately Amazon does not include SimpleDB within their web based management console. Therefore it is a good idea to setup a management software like the Scratchpad from the setup guide.

Simple Global High Score implementation
Amazon itself has an article for a global high score using the iOS and Android SDK on their web page. The sample I provide here is not very different from that. 
First you should create a domain for your game in the database using a management tool like the Scratchpad. Then start implementing by creating a SimpleDB client object:
// add at beginning of your file
// Amazon db
using Amazon;
using Amazon.SimpleDB;
using Amazon.SimpleDB.Util;
using Amazon.SimpleDB.Model;

// make this a member variable
AmazonSimpleDB m_sdb;

// and create the client inside the constructor
m_sdb = AWSClientFactory.CreateAmazonSimpleDBClient("your_access_key", "your_secrete_access_key");
The SimpleDB is a nosql database and therefore a table does not have a fixed structure. Anyhow, when you store and read high score data from the database you still have to decide on which attributes a high score entry inside your domain table should have. The table I use has the following attributes:

  • Category - this is always "HighScore" for high score entries
  • Player - name of the player to display in high score list
  • Score - score of the entry
  • OS - I also store "Windows Phone", "iOS" or "Android" for statistics.
Using these attributes the function to store a high score entry now looks like this:
void WriteGlobalHighscore()
{
    SimpleDBResponseEventHandler<object, ResponseEventArgs> handler = null;
    handler = delegate(object senderAmazon, ResponseEventArgs args)
    {
        //Unhook from event.
        m_sdb.OnSimpleDBResponse -= handler;
        PutAttributesResponse response = args.Response as PutAttributesResponse;
        if (null != response)
        {
           // we could do something when we get a success response from the server
        }
    };

    m_sdb.OnSimpleDBResponse += handler;
    string uniqueID = "UNIQUE_ID";
    string playerName = "PLAYER_NAME";
    string domainName = "YOUR_DOMAIN_NAME";
    string operationSystem = "Windows Phone"; // change for other os
    PutAttributesRequest putAttributesRequest = new PutAttributesRequest { DomainName = domainName, ItemName = uniqueID };
    List<ReplaceableAttribute> attributesOne = putAttributesRequest.Attribute;

    attributesOne.Add(new ReplaceableAttribute().WithName("Category").WithValue("HighScore").WithReplace(true));
    attributesOne.Add(new ReplaceableAttribute().WithName("Player").WithValue(playerName).WithReplace(true));
    // zero pad highscore string - necessary for sorting in select request because all value types are strings
    string number = AmazonSimpleDBUtil.EncodeZeroPadding(m_saveGame.HiScore, 10);
    attributesOne.Add(new ReplaceableAttribute().WithName("Score").WithValue(number).WithReplace(true));
    attributesOne.Add(new ReplaceableAttribute().WithName("OS").WithValue(operationSystem).WithReplace(true));

    m_sdb.PutAttributes(putAttributesRequest);
}
As you can see, the call to the database is asynchronous. When the operation is completed the response handler is called. Out of laziness the handler is directly declared as delegate inside this function.
The variables uniqueID, playerName and domainName have to be assigned with proper values. The uniqueID is the key value of the database table. I follow the example from Amazon and use an ID that is unique for each phone. I do this by creating a GUID the first time the game is started and store this GUID in a file. To get the player name you have to implement a dialog to let the player enter a name. The name should also be stored inside a file, so that you don't have to ask the player each time again.

You should now use the Scratchpad to verify if your entry really is stored at your SimpleDB. To do that you use the Select function and enter the Select Expression "select * from <your_domain_name>".

If you confirmed your successful storage of your high score you are ready to implement the function to request the entry from your game:
void RequestGlobalHighscore()
{
    // create response handler and delegate
    SimpleDBResponseEventHandler<object, ResponseEventArgs> handler = null;

    handler = delegate(object sender, ResponseEventArgs args)
    {
        int globlaHighscore;
        string playerName;
        string operationSystem;
        //Unhook from event.
        m_sdb.OnSimpleDBResponse -= handler;
        SelectResponse response = args.Response as SelectResponse;

        if (null != response)
        {
            SelectResult selectResult = response.SelectResult;
            if (null != selectResult)
            {
                foreach (Item item in selectResult.Item)
                {
                    // actually should just be one item
                    foreach (Amazon.SimpleDB.Model.Attribute attribute in item.Attribute)
                    {
                        switch (attribute.Name)
                        {
                            case "Score":
                                globlaHighscore = Int32.Parse(attribute.Value);
                                break;
                            case "Player":
                                playerName = attribute.Value;
                                break;
                            case "OS":
                                operationSystem = attribute.Value;
                                break;
                        }
                    }
                }
            }
            // now store the attributes to some member variables
            // ....
        }
    };
    m_sdb.OnSimpleDBResponse += handler;

    // create request
    string domainName = "YOUR_DOMAIN_NAME";
    string sql = "SELECT * FROM " + domainName + " WHERE Category='HighScore' INTERSECTION Score IS NOT null ORDER BY Score DESC LIMIT 1";
    m_sdb.Select(new SelectRequest { SelectExpression = sql, ConsistentRead = true });
}
This database request again is asynchronous and the handler, that is called when the answer arrived, is directly defined as delegate inside this function. In this example the result attributes are just stored inside local variables. Of course you need to copy them to some global structure or member variables.

This ends the post about my solution for a simple global high score implementation that works with WP7, iOS and Android. I think it is very elegant as the actual game code is shared along all platforms without any special modifications for a platform necessary. Also the SDK for the Amazon AWS cloud services is very easy to use. Following this example adding a global high score to your game can be done in just a few hours.

1 comment:

  1. Someone may be interested in Amazon SimpleDB tool so called "SDBExplorer". SDB Explorer has been made as an industry leading graphical user interface (GUI) to explore Amazon SimpleDB service. SDB Explorer facilitates core functionality of Amazon’s NoSQL SimpleDB in productive way.

    http://www.sdbexplorer.com/

    ReplyDelete