Wednesday, February 27, 2008

2pTD - Development Part 3 (Dead Reckoner)


Continuing in the series describing how 2pTD was built:
My initial approach to unit movement was to send out complete individual unit paths. These were basically driving directions for the units by defining a set of waypoints. This worked for simple cases, however, units needed to start taking different directions based on newly placed towers on the field. The old "driving" directions didn't include these detours so they'd need to be retransmitted to the client. The result was it actually was using more bandwidth than a dead-reckoning system would. The image on the right shows the test application where the full path was being sent to the client. The lines show the path of the unit (the purple circles).


So what is dead reckoning?
Think of dead-reckoning as simply describing where a something has been in the past. It takes a while to grasp the concept of this delayed system but it is the key to most online environments. The idea is to describe retroactively where a unit has been and to simply replay a units movement slightly delayed. Game development sites have great resources dealing with this problem which can become quite involved if a robust system is required. For this particular project I went bare-bones minimum.

Basically, approximately every second for every unit the last position (1 second ago) and the current position of the unit is sent to the client. The client then linearly interpolates between the two points over a one second period. This works for most constant movement. It starts to get a little 'weird' when units start varying their speed, which can be seen when you place towers which change the speed of the unit. The result is the client shows more of an average of the units movement, when in reality the units movement may be more erratic.

Sunday, February 24, 2008

Itch Scratched

Matt Ball posts The Forgotten Delicious about his criticism of the delicious generation where apps are created with huge hype only to effectively stagnate and die.

He describes the typical programmer behavior of losing interest in a project once the "fun" part is done (scratching the "itch"). It happens to me, I'm sure it happens to most others as well. There are those programmers who find creating the code to be the enjoyable part. For them debugging, for example, just ends up being a chore and something to be avoided. Other things like documentation can also be categorized as unenjoyable but is often necessary.

From a business standpoint it could also be that the applications released weren't actually profitable and a new direction needed to be taken. The applications mentioned in the article are at the same time both incredibly niche and incredibly inexpensive.

In defense of the AppZapper creators, was there much that actually needed to be done? It's not as though the software offering was incomplete in functionality (I assume, I haven't actually tried AppZapper).

Saturday, February 23, 2008

JavaScript Core in Leopard (The Basics)

I just discovered the JavaScriptCore.framework which is new to Leopard. Creating a hello world or a similar very basic app is always the first step I take when try to learn some new programming API and the javascript framework was no exception for me. Here is an example of something entirely useless with no practical use: Defining a function using javascript which returns the sum of two numbers, then calling that javascript function from C through the JavaScriptCore.framework. Maybe I should've had it read the two numbers from stdin?

The getObjectProperty function was borrowed from another Apple sample. Remember you'll need to add the JavaScriptCore.framework to your application.

What happens in this example:

  • A javascript context is created and then the global object is obtained

  • The target javascript is converted from a NSString/CFStringRef to a JSStringRef

  • The javascript is evaluated which in this case defines the sum function.

  • The sum function is located in the global object and called with two arguments

  • The result of calling the function is converted to a number and then printed to stdout

  • javascript context is released



#import <JavaScriptCore/JavaScriptCore.h>

/* Convenience function for getting a property that is an object. */
static JSObjectRef getObjectProperty(JSContextRef ctx, JSObjectRef object, CFStringRef name)
{
JSStringRef nameJS = JSStringCreateWithCFString(name);
JSValueRef function = JSObjectGetProperty(ctx, object, nameJS, NULL);
JSStringRelease(nameJS);

if (!function || !JSValueIsObject(ctx, function))
return NULL;
return (JSObjectRef)function;
}
int main (int argc, const char * argv[])
{
JSGlobalContextRef g_context = JSGlobalContextCreate(NULL);
JSObjectRef jsGlobalObject = JSContextGetGlobalObject(g_context);
NSString *script = @"\
function sum(a,b) { return a+b; } \n\
";


JSStringRef scriptJS = JSStringCreateWithCFString((CFStringRef)script);
JSEvaluateScript(g_context, scriptJS, NULL, NULL, 0, NULL);
JSStringRelease(scriptJS);

JSObjectRef function = getObjectProperty(g_context, jsGlobalObject, CFSTR("sum"));
if(!function)
{
printf("no function called sum!\n");
}
else
{
JSValueRef arguments[] = { JSValueMakeNumber(g_context, 123.456 ), JSValueMakeNumber(g_context, 543.21 ) };
JSValueRef result = JSObjectCallAsFunction(g_context, function, NULL, 2, arguments, NULL);

printf("resulting number: %f", JSValueToNumber(g_context, result, NULL) );

}

JSGlobalContextRelease( g_context );
return 0;
}



Other resources:
2 JavaScriptCore samples from Apple (that I've found):
JSPong
JSInterpreter

Wednesday, February 20, 2008

MySQL for Python (MySQLdb)

Quick note about building the MySQL-Python module under OS X 10.5 w/ MySQL 5.0.51a:
When building there are some issues w/ the default setup so you need to change some files. The Red Elephants blog describes one approach, this way is more "proper" and makes less changes:

Step 1
install MySQL
Step 2
extract MySQL-python-1.2.2.tar.gz
Step 3
modify the file "site.cfg" uncomment the line starting with "#mysql_config" (line #13):
mysql_config = /usr/local/mysql/bin/mysql_config

Step 4
if you get errors about "error: duplicate ‘unsigned’" during compiling change the file _mysql.c at line 38 from:
#define uint unsigned int

to:
#include <sys/types.h>


Step 5
python setup.py build
sudo python setup.py install

A side note: defining uint explicitly like that isn't really best "practices" and causes problems. The mysql client library header will include sys/types.h anyway so we just include it a little bit earlier.

Monday, February 18, 2008

2pTD - Development Part 2 (A* Pathing)

Continuing in the series describing how 2pTD was built (Part 1):
When I set out to create this multiplayer game I knew the unit pathing system would be the most integral part of the whole system. Unit pathing is an algorithm which determines the path the unit will take from a source location to a destination. There are some great resources online for learning all about graph search algorithms. I chose the A* search algorithm for its simplicity.

There are two cases in the 2pTD game where pathing occurs:
1. When a tower is placed onto the field all units are checked to make sure that a path exists from where units currently are to where they are going.
2. When a new unit enters the field.

In retrospect the implementation of the system isn't exactly the greatest. Specifically, towers should take up a 2x2 area of the field instead of the 1x1 they currently do. This would make the paths of the units more interesting and would also greatly increase the placement options because the field size would be doubled.



Here is the pathing sandbox which was built for the purpose of developing the pathing algorithm and also to develop the networking system.

Friday, February 15, 2008

New Time Machine Icon in 10.5.2


Time Machine got its own status icon in the top menu bar in the new Leopard 10.5.2 update. Why is this icon always here? I would prefer it to only show up when it's doing a backup. At the very least get rid of the little analog clock in the middle of it, I sometimes try to read the time off it, or maybe set the time to when it last did a backup. I might be wrong but the time it shows is currently meaningless and confusing. At least I can disable the icon in the system preferences...
/steps-off-soapbox

Thursday, February 14, 2008

Usability and first impressions

So it's been a few hours after I submitted my 2pTD tower-d game to digg. Things I've learned so far:

  • 6 diggs after first submission, only ONE actual visit to the page (my guess is these are paid or volunteer moderators)

  • only 2 or 3 people actually played the game :(

  • because I did something stupid: I placed "New Game" above the "Single player" button -- the result is people get to the waiting for another player message and then leave (because no one else is playing!). Can't say I blame them!

  • those that did play were interested in apparently getting their score in the high scores gallery

  • python is a bit heavy even for this small use -- it never seems to release any memory. I chose to use one python process for all sessions because of the python startup time and resident memory size; I could startup individual processes for each session but would quickly run out of memory taking that approach (not to mention the startup time of python). What I'd really like is to be able to have an individual memory pool for each session but only have one python instance and just cleanup a sessions pool when completed. My implementation is probably to blame for any memory issues here.

  • Not that I expected it to be, but it wasn't a smash-hit... back to the drawing board.

  • If it did get lots of traffic the site could get killed by bandwidth usage. Weighing in at 1,883,094 bytes the SWF file is quite large. Then add in the bandwidth used by the actual game and it starts to become significant.

  • There is a general lack of interaction -- there should be chat capabilities or at the very least forums

NSData Base64 extension - revisited, again

Not exactly base-64 related but Apple does provide a limited set of crypto functions via a CommonCrypto API in 10.5+; start with the CCCryptor man pages. The advantage to this approach is the CommonCrypto routines are provided in the libSystem library not from OpenSSL (which reduces overall code size). A quick google search didn't find any frameworks or extensions built around this API (probably because it'd be trivial to implement).

Also semi-related, there is a Keychain framework which provides wrappers around the Common Data Security Architecture (CDSA) library in OS X.

Wednesday, February 13, 2008

NSData Base64 extension - revisited

Reading the cocoa-dev mailing list I saw some mention of a SSCrypto framework which provides a wrapper around OpenSSL. I just checked it out and compiled it but haven't put it to use yet. Licensed under a BSD-style license... If only I had seen this sooner. This wheel has been reinvented at least three times publicly, perhaps Apple should add something like this to NSData? :)

Tuesday, February 12, 2008

2pTD - Development Part 1

Here's my post to attempt to describe the technology behind my 2pTD game.
Basics:

  • One developer (Grant), MTASC + swfmill, 2 months to develop

  • Flash-based client communicates with servers using XML via XMLSocket connection.

  • dmtdsrv: Custom Python/C server utilizing libevent (base game engine)

  • 1 or more dmtdsrv servers acting as lobbies (where games start, high scores, current games)

  • 1 or more dmtdsrv servers acting as game session servers (where the actual game is simulated)

  • lighttpd web server

  • memcached which holds game and misc. state info


Details:
Once the flash client is loaded, they connect to the lobby which shows current games. When they select start single or multiple, players are assigned to a session ID, which is used to determine which session server they will be connecting to. Players are internally mapped to a session as well during the transition from the lobby to the game session -- this also prevents players from joining sessions which they weren't meant to be in. All communication is done via (not so compact) XML messages.

The client is intentionally as dumb as possible to avoid cheating and to allow the server to be the authority. All the tower specifics such as amount of damage and ranges are all determine server-side. This has the side-effect of making replays and real-time spectating of games possible without making massive changes to the flash client code. The flash client code was actually written completely in ActionScript using swfmill and MTASC to build.

Graphical resources were built using a combination of custom vector editors/animators and various graphics editors. Stay tuned for more info on these tools.

After every tower wave the game session state is sent to the memcache so it can be displayed in the lobby. Each session server is uniquely named and can handle many sessions. The system is designed to be able to handle high loads by starting many session daemons across many distributed servers -- although right now it's just 1 lobby and 1 game session.

Try playing 2pTD!

Part 2 will cover the unit pathing system, part 3 will cover the dead reckoning system.

Monday, February 11, 2008

TwirlVectorEdit

TwirlVectorEdit: The first step in creating a nice symmetric image that can be rotated (or neat effects can be applied to). You can download TwirlVectorEdit for your Mac running Leopard for free. It's a pretty specific tool (I don't even think of it as an application) so it is very rough around the edges. I'm almost positive illustrator does something like this but I don't own it so I had to make this. I used this to create a starting point for making rotating selection indicators (originally for the 2pTD game). I later ended up using the output of this program along with another custom tool to create animated "pulse" graphics which are currently use in the 2pTD game. The left pane contains the filled polygon you create with the mouse, the right pane shows the "twirled" resulting image. See the contained README for instructions on how to use it.

TwirlVectorEdit Application
TwirlVectorEdit Source (not much to it really!)


I used this tool as the starting point for making flash animations like these:

More to come on the tools I used to animate these.

Saturday, February 9, 2008

NSData Base64 extension

Encoding/decoding Base64 NSData seems to be a common problem that many Cocoa developers run into. I ended up writing my own category extension to the NSData -- but what I should have done is went to google and done a search for "NSData base64" where I would have found the exact code I was looking for.

Specifically: Dave Dribin's explaination of how to use the OpenSSL to do Base64 encoding/decoding. Incidentally using the OpenSSL library was the route I took as well -- yes, I am kicking myself for not taking the one second it takes to do a google search. I actually stumbled upon the base64 NSData Dave Dribin discusses at the beginning of his post and I was also put off by the lack of any licensing discussion.

On the other hand, I can justify reinventing the wheel because now the OpenSSL block I/O system isn't as mysterious as it once was. Only by actually implementing something with it can you really get how to use a library like this... and no, reading the documentation doesn't really help -- in fact, you'll probably get further reading the header files than the nearly non-existent online OpenSSL documentation. So with a grasp of the basics of the BIO system it's not such a far leap to add fancy encryption to NSData -- which is exactly what I had to do when I needed to encrypt and decrypt data.