Monday, 30 July 2012

Where in the world is your opponent?

Today I added in a feature to show you the country flag of where in the world your opponent is in the Phone Wars multiplayer shooter.

(Note: I'm not sure, but I think that hamburger is from Indonesia.)

I haven't heard feedback on this feature yet as it's just been released. But to me, it was so COOL and motivating, shooting the hell out of an Indonesian Hamburger, an American Android and a Chinese Android today.

I added in the flag over to the leaderboards too to add a little nationalism to the scores.

To get this implemented, involved a little C++, a little PHP and a little JavaScript.

First, to get the flag data, I went to http://www.geonames.org/countries/ and fished out the flags using a php script to download the image and rename it to the country name.
 if( isset( $_GET['generateflags'] ) )  
   {  
     $countryData;  
     url = "http://www.geonames.org/countries/";  
     OpenURL( $url, $countryData );   
     $countryData = SplitBetween( '<tr><th>', '</table>', $countryData );  
   
     $countryData = explode( '<tr', $countryData );  
     $countryDataLength = sizeof( $countryData );  
     for( $i=1; $i<$countryDataLength; ++$i )  
     {  
       $code = strtolower( SplitBetween( 'name="', '"', $countryData[$i] ) );  
       $country = strtolower( SplitBetween( '.html">', '</a>', $countryData[$i] ) );  
       $country = str_replace( " ", "", $country );  
   
       if( strlen( $country ) > 2 )  
       {  
         $fileGIF = "$currentDirectory/../geoips/flags/$country.gif";  
         $filePNG = "$currentDirectory/../geoips/flags/$country.png";  
         $flagURL = "http://www.geonames.org/flags/x/$code.gif";  
         $flagData;  
         if( OpenURL( $flagURL, $flagData ) )  
         {  
           SaveFile( $fileGIF, $flagData );  
   
           $imageData = imagecreatefromgif( $fileGIF );  
           $width = imagesx( $imageData );  
           $height = imagesy( $imageData );  
   
           // Calculate new size  
           $newWidth = 256;  
           $newHeight = floor( $height * ( $newWidth / $width ) );  
           $newImage = imagecreatetruecolor( $newWidth, $newHeight );  
   
           // copy and resize old image into new image  
           imagecopyresized( $newImage, $imageData, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height );  
   
           // Save out new image  
           imagepng( $newImage, $filePNG );  
   
           // Delete old image  
           unlink( $fileGIF );  
         }  
         echo "$code $country <p>";  
       }  
     }  
   }  

So now we have our flags, we do a bit more php to geo locate our country from an ip address. That just required the reuse of our map code that we implemented previously which used IPInfoDB, who provide a free geoIP look up.

Next was to go over to the world of NodeJS and SocketIO, and have each connection, run a loop up of their location info.
 function getGeoIPData(socket)  
 {  
      var ip = socket.handshake.address;  
      var options = { host: 'api.ipinfodb.com', port: 80 };  
      options.path = '/v3/ip-city/?ip=' + ip.address;  
      var request = http.get( options );  
      request.on( 'response', function (result)  
      {  
           result.setEncoding( 'utf8' );  
   
           var data = "";  
           result.on( 'data', function(chunk)   
           {  
                data += chunk;  
           });  
           result.on( 'end', function()  
           {  
                if( data.length > 2 )  
                {  
                     socket.geoIPData = JSON.parse( data );  
                     console.log( "getGeoIPData", socket.sessionID, socket.geoIPData );  
                }  
           });  
      });  
      request.on( 'error', function (error)   
      {  
            console.log( "getGeoIPData ERROR:", socket.sessionID, error.message );  
      });  
 }  

Finally over in the matchmaking code, just pass over the countryName to the client. The C++ client will pick up the country name using Jansson, strip out any unwanted characters, spaces, full stops, see if it matches the names of one of the flags we have stored. If so, bam.. display it.
 if( geoLocationData.length > 0 )  
   {  
     json_error_t error;  
     json_t *root = json_loads( geoLocationData.buffer, 0, &error );  
     if( root )  
     {  
       CCText statusCode;  
       json_object_string( statusCode, root, "statusCode" );  
       if( CCText::Equals( statusCode.buffer, "OK" ) )  
       {  
         json_object_string( geoLocationCountry, root, "countryName" );  
         if( geoLocationCountry.length > 1 )  
         {  
           geoLocationCountry.toLowercase();  
           geoLocationCountry.replaceChars( " ", "" );  
           geoLocationCountry.replaceChars( ".", "" );  
           geoLocationCountry.replaceChars( "-", "" );  
           
           CCText file = "Resources/Common/flags/";  
           file += geoLocationCountry.buffer;  
           file += ".png";  
           const bool exists = CCFileManager::DoesFileExist( file.buffer, Resource_Packaged );  
           if( exists )  
           {  
             ScenePlayManager::scene->createFlag( file.buffer );  
           }  
         }  
       }  
       json_decref( root );  
     }  
   }  

Easy as that.

Side note, it's seriously awesome being able to code in several languages, the full solution also involved a bit of Java to support the Android client. If you've ever been shy of learning another language, just jump right in, you don't need to be an expert, but knowing a little + GoogleFu/StackOverflow sure does help.