Tuesday 20 November 2012

Spock vs Darth Vader... The Game?

Is it just me, or would anyone else out there love it if this were an actual game you could play?

Sunday 2 September 2012

Phone Wars - Battle Royale Preview

Ok, so I'm almost finished implementing the up and coming Battle Royale mode for Phone Wars. Here's what it's currently looking like.


Of course the changes will be propagated to Food Fighters and Tank Legends too. So fingers crossed, things should get a little bit interesting soon. Tanks, Burgers, Fries, Soldiers, iPhones, Androids, all on the same map!!

I haven't set the player limit per map yet, but I'm honestly scared to see if the server will be able to cope.

Tuesday 28 August 2012

3D Cross-Platform 3rd Person Shooter - To Intel x86 Android

Ever wanted to port your Android NDK app to support the Intel x86 platform?

..No?

Oh, well, here's an article to help you do it anyway. Featuring guns, missiles, kapow sprites, Androids, iPhones and more..
http://www.codeproject.com/Articles/448748/3D-Cross-Platform-3rd-Person-Shooter-To-Intel-x86

Friday 10 August 2012

Native HTM5 Audio for Cross-Platform Lazy

I've been using WebViews to handle the multiplayer code in my games Phone Wars and Food Fighters. Yes, I know it sounds a bit silly, but it does give you some nice little bonuses like being able to update the code without pushing an app update and being able to easily communicate with webapps.

The next challenge I set this reverse PhoneGap methodology was to get our game background music running with an HTML5 audio tag.
(Quick justification as to why you'd want to do this would be to be able to change the music on the fly without having to release an app update).

To set this up, you just create an Audio object, set it to loop and fire play..
 var muisc = new Audio( "song.mp3" );  
 music.loop = true;  
 music.play();  

Well this might work in a web browser, however in WebViews on mobiles, it's not so straight forward.

Firstly on iOS, you need to disable mediaplayback requiring a user action.
 self.allowsInlineMediaPlayback = true;  
 self.mediaPlaybackRequiresUserAction = false;  

Ok, that was easy enough.

Android however, well, it's a bit more interesting. On some versions of Android I've tested, the audio doesn't loop and on some the audio does loop. To combat this, I tried setting the currentTime to 0, and re-playing the audio when the ended event gets fired.
 music.addEventListener( 'ended', function()   
           {  
             this.currentTime = 0;  
             this.play();  
           }, false);  
Again, this seemed to not work on some versions of Android.

After hours of trying various methods, it seemed the issue boiled down to currentTime not being set on some devices. To combat this, what I ended up doing was re-creating the audio when the audio finished.
 var music = false;  
function playMusic()  
{  
     music = new Audio( "song.mp3" );  
     music.addEventListener( 'ended', function()   
     {  
         playMusic();  
     }, false);   
     music.play();  
}  
playMusic();  

Perfect, we now have a looping audio track.

Problem? Well, when you exit your Android app, the music still loops in the background. By default your WebView still churns on in the background even when your application closes. This too occurs on iOS, however, on iOS while other operations may still fire in the WebKit process, the music gets killed.

Well ok, just make sure you turn off the audio when your application gets paused and you're done?

Almost.. If your application uses GCM push notifications.. you may have problems.
See the video below.


To be continued..

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.

Sunday 22 July 2012

Food Fighters Worldwide Player Activity Map

Was digging through the player activity stats for Food Fighters just now to see where in the world our players are located. Threw together a mashup of our players on a world map. Pretty cool seeing the global footprint of our player base.



If you're interested in making your own mashup, the OpenStreetMap apis are a breeze to use, especially with OpenLayers, and IPInfoDB gives out free reverse ip lookups.

Multiplayer Games Need An Activity Feed

Working on a multiplayer game is pretty fun, when you're challenging and playing your friend next to you, it's pretty easy to set up and play the game.

When you're challenging and playing someone somewhere around the world, it gets harder. As the communication channels just aren't there.

Some of the problems faced when setting up multiplayer games is trying to inform the players who's connected, and when someone else is available to play. Asynchronous multiplayer games have it easier as players can action their moves turn by turn in asynchronous time. Real time multiplayer games are more challenging.

Last week we introduced push notifications to help inform players when someone else was waiting to play, however we noticed that although this solution was pretty popular with our players, there was still some major issues. As several players were receiving the message at the same time, when more than one logs in to accept the challenge, the other players would log in to find no one waiting to play. Or even worse, by the time a player had connected to accept the challenge, the challenger would disconnect.

To solve this, we're trying out an activity feed, which logs all the major actions of the players logged in.

To implement such a system was pretty simple.

On the server side, we keep an array of the last 10 activity messages. When a player logs in, or registers to play, or logs out, or a match is made/ended, these messages are pushed onto the stack along with the UTC time the request was made.

 BurgersServer.prototype.updateActivityFeed = function(message)  
 {  
   console.log( "updateActivityFeed", message );  
   
   var activityFeed = this.activityFeed;  
   while( activityFeed.length > 10 )  
   {  
     activityFeed.popFirst();  
   }  
   
   var feedMessage = [GetTime(), message];  
   activityFeed.add( feedMessage );  
   
   var sockets = this.sockets;  
   for( var i=0; i<sockets.length; ++i )  
   {  
     var socket = this.sockets.list[i];  
     socket.emit( 'BurgersGameUpdate', { feed: [feedMessage] } );  
   }  
 }  

The messages are then broadcast to all connected clients. On the clientside javascript, the UTC time is converted back into local time.
 socket.on( 'BurgersGameUpdate', function (data)  
   {  
     debugLog( 'BurgersGameUpdate ' + JSON.stringify( data ) );  
   
     if( data.feed )  
     {  
       var minutesOffset = -( new Date().getTimezoneOffset() );  
       var hoursOffset = minutesOffset / 60;  
       minutesOffset = minutesOffset % 60;  
   
       var feed = data.feed;  
       var length = feed.length;  
       for( var i=0; i<length; ++i )  
       {  
         var time = feed[i][0];  
         var serverTime = time.split( ":" );  
         var hours = parseInt( serverTime[0], 10 );  
         var minutes = parseInt( serverTime[1], 10 );  
         minutes += minutesOffset;  
   
         if( minutes < 0 )  
         {  
           hours--;  
           minutes += 60;  
         }  
         else if( minutes >= 60 )  
         {  
           hours++;  
           minutes += 60;  
         }  
   
         hours += hoursOffset;  
         if( hours < 0 )  
         {  
           hours += 24;  
         }  
         else if( hours >= 24 )  
         {  
           hours -= 24;  
         }  
   
         if( hours < 10 )  
         {  
           hours = "0" + hours;  
         }  
         if( minutes < 10 )  
         {  
           minutes = "0" + minutes;  
         }  
   
         feed[i][0] = hours + "," + minutes;  
       }  
     }  

Then in the game, the last 10 messages are displayed in the character select lobby screen.
 if( json_object_get( jsonData, "feed" ) )  
   {    
     json_t *feedData = json_object_get( jsonData, "feed" );  
     if( feedData != NULL )  
     {  
       CCText time;  
       CCText message;  
       if( json_is_array( feedData ) )  
       {  
         const uint length = json_array_size( feedData );  
         for( uint i=0; i<length; ++i )  
         {  
           json_t *jsonFeed = json_array_get( feedData, i );  
           if( jsonFeed != NULL )  
           {  
             if( json_is_array( jsonFeed ) )  
             {  
               if( json_array_size( jsonFeed ) == 2 )  
               {  
                 json_t *jsonFeedTime = json_array_get( jsonFeed, 0 );  
                 json_t *jsonFeedMessage = json_array_get( jsonFeed, 1 );  
                 if( jsonFeedTime != NULL && jsonFeedMessage != NULL )  
                 {  
                   time = json_string_value( jsonFeedTime );  
                   message = json_string_value( jsonFeedMessage );  
                   updateActivityFeed( time.buffer, message.buffer );  
                 }  
               }  
             }  
           }  
         }  
       }  
     }  
   }  


 void SceneBurgersManager::updateActivityFeed(const char *time, const char *message)  
 {  
   CCText combinedMessage = "<";  
   combinedMessage += time;  
   combinedMessage += "> ";  
   combinedMessage += message;  
     
   CCTile3DButton *tile = NULL;  
   if( activityFeedTiles.length < 10 )  
   {  
     tile = new CCTile3DButton( this );  
     tile->setupText( " ", camera->targetHeight * 0.025f, true, false );  
     tile->drawOrder = 205;  
     tile->setTextColour( 1.0f );  
     tile->setTileScale( 1.0f );  
     activityFeedTiles.add( tile );  
   }  
   else  
   {  
     for( int i=0; i<activityFeedTiles.length-1; ++i )  
     {  
       CCTile3DButton *current = activityFeedTiles.list[i];  
       CCTile3DButton *next = activityFeedTiles.list[i+1];  
       current->setText( next->getTextModel()->getText().buffer, true );  
     }  
     tile = activityFeedTiles.last();  
   }  
     
   tile->setText( combinedMessage.buffer, true );  
     
   if( !( gameState >= GameState_CharacterSelectScreen && gameState <= GameState_ReadyToPlay ) )  
   {  
     tile->setTextAlpha( 0.0f, false );  
   }  
     
   // refresh the scene tile positioning and range  
   beginOrientationUpdate();  
 }  

Sunday 15 July 2012

GCM Push Notifications for Android


Push notifications are officially awesome for real time multiplayer gaming. If your server has no other players, you can't really have a game. With push notifications however, you can register your interest in playing, and whenever someone logs in, you can immediately be told and be able to log right in and play against them.

Some of the quirks of getting push notifications on Android were.
  • Including the gcm.jar file compiled but always gave me a class not found run time error.
    • To solve, I included the source files provided in gcm-client instead.

  • GCMIntentService must be put in the same class package as your application package. (I tend to re-use a lot of my code between projects, so I have a generic class package name with different application package names).


Apart from that, everything seemed to pretty much work well.

Some good material to get your head around is the Google I/O 2012 talk and the documentation.

If you're doing push messaging with NodeJS and have any problems, let me know and I'll try to help. If you hate server side development, you can always give Parse a try.

Wednesday 11 July 2012

Food Fighters (the game.. not the band)

Hi guys, whilst the Androids vs iPhones game is being reviewed by the kind folks at Apple, we decided to get high or hungry over a fast food shooting game.

Using the same mechanics as the iPhones vs Androids game and the same ability to play against different platforms (including PC/Mac/Linux),
you can have a sneak peak on how the Androids game will play out with FOOD FIGHTERS! (the game.. not the band).


Inline images 1

The Android version has been published (so should appear on the store momentarily).
I'll publish a Windows version later today (so you can play Windows vs Android).
If you'd like a version for another platform, let me know and I'll send you a beta.


If you'd like to see any other crazy/fun/stupid concepts (Justin Bieber vs King Kong anyone?), let me know.


Thanks for the support buddies :)

Friday 6 July 2012

MiniChe - The Importance of an Art Pipeline

Two months ago, myself and two buddies entered a game hackathon in which we worked on a concept game idea we've been bouncing around called MiniChe.

In case you don't know I'm Egyptian and we've been going through a revolutionary period in our countries history, so we figured, wouldn't it be cool, if you played the role of Che Gueverra and went around the world causing revolutions?

We'll we thought it would be awesome and thanks to the help of the wonderfully talented DinoAhmed and Rez we managed to get together a prototype going.

Initially we planned on continuing the momentum from the hackathon and releasing the game within 2 weeks. However, interestingly enough, that didn't happen. Working remotely is a big challenge when it comes to coordination and dedication. When you're hacking something together with your buddies, it's pretty easy to point and say, wrong, right, do it this way. When you're working remotely off an undefined pipeline where a perceived understanding is assumed, problems occur.

Here's probably the top three biggest time vacuums we ran into.

Anchoring
A lot of art assets (sprites and 3d models) seem to have varying origin points.

To combat this issue, just manually edit the sprites yourself (it's a headache, it's gruntwork, but sometimes it's the quickest thing to do to get the job done). For 3d models, calculate the width, depth and height of an object and re-center them in code.


Poly Counts
Sometimes for some reasons, the models provided go over the agreed poly counts, which leads to inflated loading and rendering times.



To combat this issue, we tend to look at the asset, if it's worth keeping, we have to re-iterate and reduce the poly count. If it doesn't really look good and the process is taking too long. Just drop it.


Naming Conventions
Sometimes you tend to get filenames that go against the agreed naming convention.

To combat this issue, if it's just a one off hit, be a man, rename it yourself, it's a losing war trying to teach people basic skills.


Well, after 2 months of hiatus, I'm happy to announce that we're finally hitting ALPHA on the project with all the required* art assets complete.

*The best part of producing your own game is that it's a lot easier to scale down the requirements.


Wednesday 9 May 2012

Qt way to Include all source files

About a year ago I got roped into becoming a Qt developer for its nice and easy support for the Symbian platform. Using Qt gave you the ability to create cross-platform desktop (and Symbian) applications using the same IDE.

The big pain has always been whenever I added a new directory to a project.

In xCode I could re-drag and drop the folder into the project and it create the make file based on the files it found.

In Eclipse for Android, you could just modify the makefile to include *.cpp and *.h files in certain subfolders.

In Qt however, you had to individually specify the files to be included.
There were some workarounds where you'd create a .pro file per subfolder and somehow link them. I found this approach too confusing and didn't want to bloat my project source folders for the other platforms I supported.

The solution I came up with was to move out all the source files into a sources.pri file which was to be included by the main project file. I'd then run a Java script I wrote which parsed through all the directories and write in the cpp, c, and h files found.

project.pro file

# CCRELEASE: Remove for release
debug:DEFINES += DEBUGON

QT              += opengl network webkit network script phonon
TARGET    = 2play
TEMPLATE   = app

OTHER_FILES += \
    ../../Engine/* \
    ../../Engine/Shaders/basic.fx \
    ../../Engine/Shaders/phong.fx

include( sources.pri )
...
The important part is the one in bold stating include(sources.pri)

sources.pri file
INCLUDEPATH += \ 
  \ 
 ../Source \ 
 ../Source/Rendering \ 
 ../Source/Tools \ 
 ../../Engine/Source \ 
 ../../Engine/Source/AI \ 
 ../../Engine/Source/Objects \ 
 ../../Engine/Source/Rendering \ 
 ../../Engine/Source/Scenes \ 
 ../../Engine/Source/Tools \ 
 ../../Engine/Source/UI \ 
 ../../External/3dsloader \ 
 ../../External/jansson-2.1/src \ 
 ../../External/ObjLoader3 \ 
 ../../App/Source \ 

SOURCES += \ 
  \ 
 ../Source/CCFBWebView.cpp \ 
 ../Source/CCGLView.cpp \ 
 ../Source/CCMainWindow.cpp \ 
 ../Source/CCPlatform.cpp \ 
 ../Source/CCRenderThread.cpp \ 
 ../Source/CCVideoView.cpp \ 
 ../Source/main.cpp \ 
  \ 
 ../Source/Rendering/CCDeviceRenderer.cpp \ 
 ../Source/Rendering/CCTexturePNG.cpp \ 
  \ 
 ../Source/Tools/CCDeviceControls.cpp \ 
 ../Source/Tools/CCDeviceFileManager.cpp \ 
...

HEADERS += \ 
  \ 
 ../Source/CCFBWebView.h \ 
 ../Source/CCGLView.h \ 
 ../Source/CCMainWindow.h \ 
 ../Source/CCPlatform.h \ 
 ../Source/CCRenderThread.h \ 
 ../Source/CCVideoView.h \ 
  \ 
 ../Source/Rendering/CCDeviceRenderer.h \ 
 ../Source/Rendering/CCTexturePNG.h \ 
  \ 
 ../Source/Tools/CCDeviceControls.h \ 
 ../Source/Tools/CCDeviceFileManager.h \ 
...

Now the Java file simply loads in the sources.pri file, inputs in the folders listed in the INCLUDEPATH, then re-writes the file with all the source and header files.
import java.io.*;
import java.util.ArrayList;

import javax.swing.JOptionPane;

public class QtMaker 
{
 /**
  * @param args
  */
 public static void main(String[] args) 
 {
  String currentDirectory = System.getProperty( "user.dir" );
  String qtProjectDirectory = currentDirectory + "/";
  
  boolean findProjectDirectory = true;
  {
   // See if we're in a folder with a .pri file
   File dir = new File( qtProjectDirectory );
   String[] files = dir.list();
   for( int i=0; i<files.length; ++i )
   {
    String file = files[i];
    if( file.endsWith( ".pri" ) )
    {
     findProjectDirectory = false;
     break;
    }
   }
  }
  
  // Our project specific case when running the program from our Tools directory structure
  if( findProjectDirectory )
  {
   String[] splitResults = qtProjectDirectory.split( "/" );
   qtProjectDirectory = "";
   for( int i=0; i<splitResults.length; ++i )
   {
    String directory = splitResults[i];
    if( directory.equals( "Tools" ) )
    {
     break;
    }
    qtProjectDirectory += directory;
    qtProjectDirectory += "/";
   }
   
   qtProjectDirectory += "Dev/Qt/Project/";
  }
  
  // Find the first .pri file to play with
  String projectFilename = "sources.pri";
  {
   File dir = new File( qtProjectDirectory );
   String[] files = dir.list();
   for( int i=0; i<files.length; ++i )
   {
    String file = files[i];
    if( file.endsWith( ".pri" ) )
    {
     projectFilename = file;
     break;
    }
   }
  }
  
  File projectFile = new File( qtProjectDirectory + projectFilename );
  if( !projectFile.exists() )
  {
   JOptionPane.showMessageDialog( null, "Unable to find sources.pri\n" + qtProjectDirectory );
  }
  else
  {
   ArrayList<String> projectFileContents = new ArrayList<String>();
   try
   {
      FileInputStream fstream = new FileInputStream( projectFile.getPath() );
      DataInputStream in = new DataInputStream(fstream);
      BufferedReader br = new BufferedReader( new InputStreamReader( in ) );
      
      //Read File Line By Line
      String readLine;
      while( ( readLine = br.readLine() ) != null )
      {
       projectFileContents.add( readLine );
      }
      
      //Close the input stream
      in.close();   
   } 
   catch ( Exception e )
   {
    System.err.println( "Error: " + e.getMessage() ); 
   }
   
   if( projectFileContents.size() > 0 )
   {
    ArrayList<String> sourceDirectories = new ArrayList<String>();
    findSourceFilePaths( sourceDirectories, projectFileContents );
    
    ArrayList<String[]> sourceFiles = new ArrayList<String[]>(); 
    for( int i=0; i<sourceDirectories.size(); ++i )
    {
     String path = sourceDirectories.get( i );
     String directoryPath = qtProjectDirectory + path;
     File dir = new File( directoryPath );
     String[] files = dir.list();
     sourceFiles.add( files );
    }
    
    String outputString = "INCLUDEPATH += \\ \n";
    for( int i=0; i<sourceDirectories.size(); ++i )
    {
     String path = sourceDirectories.get( i );
     outputString += "\t" + path + " \\ \n";
    }
    
    outputString += "\n";
    
    outputString += "SOURCES\t+= \\ \n";
    for( int i=0; i<sourceFiles.size(); ++i )
    {
     String path = sourceDirectories.get( i );
     String[] files = sourceFiles.get( i );
     for( int j=0; j<files.length; ++j )
     {
      String file = files[j];
      if( file.endsWith( ".cpp" ) || file.endsWith( ".c" ) )
      {
       outputString += "\t" + path + "/" + file + " \\ \n";
      }
     }

     outputString += "\t \\ \n";
    }
    
    
    outputString += "\n";
    
    outputString += "HEADERS\t+= \\ \n";
    for( int i=0; i<sourceFiles.size(); ++i )
    {
     String path = sourceDirectories.get( i );
     String[] files = sourceFiles.get( i );
     for( int j=0; j<files.length; ++j )
     {
      String file = files[j];
      if( file.endsWith( ".h" ) )
      {
       outputString += "\t" + path + "/" + file + " \\ \n";
      }
     }

     outputString += "\t \\ \n";
    }
    
    try
    { 
     FileWriter fstream = new FileWriter( qtProjectDirectory + projectFilename );
     BufferedWriter out = new BufferedWriter( fstream );
     out.write( outputString );
     out.close();
    }
    catch ( Exception e )
    {
     System.err.println("Error: " + e.getMessage());
    }
    
    //JOptionPane.showMessageDialog( null, outputString );
   }
     }
 }

 static void findSourceFilePaths( ArrayList<String> sourceDirectories, ArrayList<String> projectFileContents )
 {
  int includePathStartIndex = 0;
  for( int i=0; i<projectFileContents.size(); ++i )
  {
   String line = projectFileContents.get( i );
   String[] splitResults = line.split( "INCLUDEPATH" );
   if( splitResults.length > 0 )
   {
    includePathStartIndex = i;
    break;
   }
  }
  
  for( int i=includePathStartIndex; i<projectFileContents.size(); ++i )
  {
   String line = projectFileContents.get( i );
   if( line.length() == 0 )
   {
    break;
   }
   
   if( line.equals( "\\" ) )
   {
    continue;
   }
   
   String[] splitResults = line.split( "\\\\" );
   if( splitResults.length == 0 )
   {
    break;
   }
   else
   {
    String directoryString = findDirectoryString( line );
    
    // Trim any invalid characters
    directoryString = directoryString.replaceAll( "\t", "" );
    
    // Make sure we don't add repeat folders
    boolean found = false;
    for( int j=0; j<sourceDirectories.size(); ++j )
    {
     String currentDirectory = sourceDirectories.get( j );
     if( currentDirectory.equals( directoryString ) )
     {
      found = true;
      break;
     }
    }
    
    if( found == false )
    {
     sourceDirectories.add( directoryString );
    }
   }
  }
  
  for( int i=0; i<sourceDirectories.size(); ++i )
  {
   String line = sourceDirectories.get( i );
   System.out.println( line );
  }
 }
 
 static String findDirectoryString(String line)
 {
  String[] splitResults = line.split( " " );

  int longestLength = 0;
  String longestString = "";
  for( int i=0; i<splitResults.length; ++i )
  {
   String result = splitResults[i];
   if( result.contains( "/" ) )
   {
    if( result.length() > longestLength )
    {
     longestLength = result.length();
     longestString = result;
    }
   }
  }
  
  return longestString;
 }
}

It's simple. It works. It saves me the pain of having to include files individually.

Hopefully it'll help someone out there too.

Monday 7 May 2012

Quran Project Release Candidate

It's been around 5 months since starting work with the Quran Project charity to bring an iOS port for their hardback book which provides a complete westernised introduction to Islam for non-muslims. A lot of work has been done over the months experimenting with the design and trying to programitcally convert a Microsoft Word document into a fluid 3D iOS app.

Initially we concepted out a 3D scene where you get to select the chapters via a 3D book interface.

However, this design was deemed to be too confusing and demanded a lot of 3d modelling concepts to go forward.

So we switched over to a Windows Phone 7 inspired 3D blended mix. Where you'd select the chapters via tiles over a 3D scene featuring the book.


However, the design didn't really work. It looked too unprofessional. Which lead us to scrapping the 3D scenery and going towards a more art driven chapter selection design.

It's taken 5 months, but I feel like we're close to having a build worth submitting. If anyone has any feedback or suggestions for further improvements, please feel free to leave a comment below.

For more information on the Quran Project charity, please visit their webpage at quranproject.org.

Saturday 17 March 2012

iGrapher 3D 2 (The Dubstep Remix)

Here's a sneak peak of the iGrapher 3D 2 submission candidate (codenamed: Dubstep remix).

Featuring beefed up res, live stock charting tiles, Android support and more..

Thursday 16 February 2012

Don't Try This Design at Home

Facebook
Five months ago Facebook ruined their very useable Facebook chat application in order to try to change people's messaging habits. This change included the introduction of what I dub as the stalker view, which showed your friends activity in real time.


Today I'm happy to officially welcome the first step towards the dissolution of this awful design decision and the death of the stalker window.

 

Google
Three months ago Google ruined their very usable Google bar application navigation system, with a drop down menu requiring a mouse hover action to open an use.


Today I'm happy to officially welcome the dissolution of this awful design decision and the return of the old Google bar.


The War is Over
Ladies and Gentlemen.. UX has won! The War is Over!

 
UPDATE 16/02/12: Unfortunately, Facebook was just teasing us. The activity window is back.

Wednesday 15 February 2012

Wiki Based Documentation

Documenting your architecture is one of the holy grails of software development. While programmers tend to put effort into practicing commenting source code. Providing good system architecture design documents and sample code tends to be the challenging next step. This is because it's time consuming. While lot's of different hands in the pie, it becomes a valiant effort to get contributors to update the documents.

Wikis have provided a great step towards solving this problem. No longer would you have to navigate a 500 page Microsoft Word document looking for obsolete documentation. With Wikis you can collaboratively write and link different articles and samples together. The only problem remaining is that damn frustrating markup language Wikis use. The premier Wiki engine MediaWiki doesn't support WYSIWYG editing by default, which means creating documentation becomes more of a chore.

Here I will document my search for an easy WYSIWYG (what you see if what you get) editable Wiki engine and compare my findings.


WYSIWYG Wiki (http://www.wysiwygwiki.org/)

Allows you to modify pages using AJAX (try it), it's awesome. The only problem is that when you download it, you realize that you need to create your html pages manually, then include snippets of code in the header of the page you wish to modify.


Foswiki (http://foswiki.org/)

The website looks great, highlighting it's implementation of a World Class text editor.
But, I'll be honest and admit that I was scared off by the set up instructions.

Note this is only part 1

If anyone has used this Wiki, it'd be great to hear your thoughts.


MediaWiki (http://www.mediawiki.org/wiki/MediaWiki)

While MediaWiki just released a WYSIWYG editor, it only gives you an editing bar with the preview still showing wiki markup.

You can use Microsoft Word to export wiki marked up documents, although you'll probably need to do some editorial work when using images.


There' is an editor plugin for MediaWiki, however it only supports version 1.16-17 and of course may not work as desired out of the box.


Wikiwig (http://wikiwig.sourceforge.net/)

The best thing about using this Wiki engine is how each it is to set up. You simply, download the package, unzip it onto your webserver, setup a database for it. Then navigate to the page and it'll greet you with a configuration screen with a one click set up process.


On this screen you specify the name and credentials for the database. Note, if your web server doesn't support https, make sure you change the login/sign option to use http instead as no error will be reported until it's too late.

Another thing to note is that if you're using a newer version of php you'll get a couple of warnings when it's set up.

However, you can simply update the script in _wk/lib/Wiki_Db.php to remove the warning (or ignore it).
 
Feature wise, it's really simple to create and edit a new page. You can even directly insert HTML. While it may not be the best for security. It's great to actually get going with documentation.


Conclusion
Wikiwig was last updated in 2008, but I'm happy it exists. For my needs (software documentation), it's perfect. For a more comprehensive Wiki engine I'd suggest looking into the other options presented above with bias towards MediaWiki as it's the most recognized brand.

Monday 13 February 2012

Streaming JavaScript is still Great! Use It!

So back in January 2009 I was advocating splitting your codebase up into different functions, where you can stream in different logic bit by bit as required. This was due to support explosion of smart phones which didn't have the bandwidth or processing power desktop computers had.

It's been over 3 years since supporting this pattern, and I've honestly had very little to complain about, but I figured I'd highlight two of the negatives I've come across over the years.
  • Caching - Anything you stream will not be cached, so you'll have to cache the JavaScript yourself.
  • Bugs - As this isn't a practiced design pattern in the world of web programming, browser vendors don't always check for it before pushing out an update.

Caching
This one's a piece of cake. Simply use LocalStorage to store your cached JavaScript file.
LocalStorage gives you around 2mb no questions asked storage to store binary data, which is more than enough to store small javascript files.

Here we walkthrough the process of caching a JavaScript file.

 var jsScriptData = false;  
   
 // If we have localStorage  
 if( window.localStorage )  
 {  
   // See if we have the data  
      if( window.localStorage.getItem( "jsData" ) )  
      {  
           jsScriptData = window.localStorage.getItem( "jsData" );  
      }  
   else  
   {  
     // If not download the file normally  
     jsScriptData = DownloadFile( "http://urlto.com/streamingFile.js" );  
       
     // Then save to localStorage  
     window.localStorage["jsData"] = jsScriptData;  
   }  
 }  
 else  
 {  
   // If we don't have localStorage download the file normally  
   jsScriptData = DownloadFile( "http://urlto.com/streamingFile.js" );  
 }  
   
 // Create a new script element  
 var newScript = document.createElement( 'script' );  
   
 // Fill in the script element with more source code  
 newScript.text = jsScriptData;  
   
 // Add our script to the document  
 document.body.appendChild( newScript );  

Of course more smarter checks can be put in, for example, checking for an update every week, or having a flag in the main JavaScript file to flag when to stream in the update.


Bugs
Next issue to discuss is bugs. As of writing Mozilla recently updated Firefox to 10.0.1, which is great, updates are always good. However, for some reason iGrapher.com stopped working on the OSX skew.

On Windows it was fine, but the OSX just hung on streaming in more JavaScript files. Now on the iGrapher project we load 4 files.
  • startup - 21kb file which loads in the main view UI and downloading engine.
  • main - 98kb file which handles the graphing and stock market downloading logic.
  • extras - 150kb file which ads additional tools and menus to the graphing component.
  • news - 33kb file which handles the news stories.
What's shown in the screenshot above is the webapp hung getting the extras file. Now I didn't put in any time to debug the issue as I'm more focusing on the updated codebase. So unfortunately I can't deduce what exactly in the code causes it, but I found that by combining the startup and main files together into one file. The extras and news files stream in fine.

Mysterious, but I thought it'd be useful to highlight that yes, bugs occur, when they perhaps shouldn't.


Conclusion

I come across bloated websites all the time which rely on the browser to load in lots JavaScript files at the same time. This delivers a poor and very staggered start up experience. It's poor on desktop computers let alone the exploding smartphone/tablet market. We think about streaming in content all the time, why stop there? Let's start thinking about streaming in processing and fix this poor user experience we've accepted as the norm.

Thursday 9 February 2012

iGrapher 3D - Tiles Tech Preview

Here's a little sneak peak of the upcoming update for the iGrapher 3D project.


The main work behind the update involves updates to the rendering engine to support rendering out to custom frame buffers. This allows several different graphs to be drawn and interacted with on the screen at once.

Once this release goes gold, I'll pushing all the updates back into our open source engine codebase on GitHub for everyone to use and enjoy.

Tuesday 7 February 2012

Creative Writing

I am very easily distracted in today's world.

Sometimes I find it hard to stay focused on one project, as I have so many ideas and so many routes to distraction.

To combat this state of mind, I've started using 'typewriter apps' for my creative writings. I have to admit, it's a really beautiful feeling, removing the clutter from my computer screen and just seeing plain text waiting for my disruption.

WriteMonkey (Windows)
The first application I tried. I loved it, the only reason why I looked for an alternatives was because I wanted a version for Mac.

JDarkRoom (Windows/Mac/Linux)
Black screen, green text. Beautiful alternative to WriteMonkey for other platforms.

Ommwriter Dana (Windows/Mac/iOS)

Not quite a blank screen with simple text on, but this one ended up being my favorite. With soft touches such as typewriter inspired sound effects when typing keys and a softly presented beautiful background and ambiance. It's truly a pleasure to use. 

Conclusion
If you haven't used one of these applications I suggest you give one of them a whirl.
I really hope one day, they'll make an programming IDE with such beauty and simplicity that just makes you want to program, and if they don't make one. I will.

Saturday 4 February 2012

ERROR: Could not extract package's data directory. Are you sure that your installed application is debuggable?

Everytime I try to debug an Android NDK application, I'm greeted with a new random error message. It's driving me crazy!!!


If you ever get the titled error message, just keep renaming your package name until it works.

Seriously!

com.softpoetry.igrapher - refused to work
com.softpoetry.igrapher2 - refused to work
com.igrapher.is.awesome.app - refused to work
com.softpoetry.igrapher.app - refused to work
com.softpoetry.ig - works!


WTF?

Wednesday 25 January 2012

Shift It+ for OSX


Windows 7 has a great windows shifting mechanic for quickly splitting windows around the screen, to be able to use applications side by side.

For OSX I found an Open Source solution here.
http://code.google.com/p/shiftit/

It wasn't perfect for my needs, so I forked the source code primarily to add multiple monitor support, and more customized controls which didn't conflict with the applications I used.

Here is the presentation of the current features.

Here is a walkthrough of the source code so you can quickly jump in and add your own customizations.

Feel free to grab the forked source code here.
https://github.com/ashcairo/ShiftItPlus