In the original game you play Guybrush Threepwood, a would-be pirate who must learn to become a great swordfighter if he's ever to realize his pirate dreams. In this case, however, the key to great sword fighting is in the mind, not in the hands, because the game is all about insults and comebacks—the fighter with the sharper tongue wins.
My version of the game is a blend of Rock-Paper-Scissors meets Memory meets Fencing. To begin, you select your character and your (computer) opponent (see Figure 1). Next, you select an insult from a list of insults to throw your opponent's way. If your opponent has an appropriate comeback, he will take the lead and throw an insult your way. Then it's your turn to select an appropriate comeback to reclaim the lead. If you select the wrong comeback, you lose. However, if you can hold your ground, you'll advance to higher levels, and will be able to unlock hidden characters. The final character in the game is a boss, who uses a special set of insults. (However, the same comebacks you've already learned will work against him. It's up to you to figure out which comeback goes with what insult.)
Tip: When playing the game, losing a lot in advance is key. Using wrong comebacks over and over will enable you to learn even more. When you meet the boss, your insults are useless to you. It all depends on the comebacks. Slow and steady wins the race.

Figure 1. Select your character.
Note: In Figure 1, Insult Dueler is running on top of the browser.
One of the cool things about Adobe AIR is that you can choose whether you want your app to use the standard window chrome (which has the look and feel of any other application window on your Windows or Mac OS system) or custom chrome. I'm a custom man myself, so I chose to go the custom chrome route—which also meant that I was responsible for adding my own Close and Minimize buttons (the Maximize button is not necessary for this game).
Luckily, all of the necessary code is built into Adobe AIR. For example, if you want to add minimize functionality to a window, all you need to do is this:
someDisplayObject.stage.nativeWindow.minimize();
Another fun feature of custom chrome is that you can build an exotic-looking window with its own transparency (see Figure 1). For this game, I even added a slider to control the alpha of the backmost movie clip, so users can choose their own transparency level.
But wait! There's more!
It's one thing to manipulate an app window—to move it around or make it semi-transparent. It's another matter entirely to remember the location and appearance of the application when the user quit it. To pull this off, you'll need to write a file to the user's hard drive that stores the x and y coordinates as well as the alpha value of the window. For this game, I also saved the win/loss record—to add replay value. I saved all this information in an XML file, since it's pretty easy to read. My default XML file looks like this:
<prefs> <stagePosition x='0' y='0' alpha='1' /> <record wins='0' losses='0' /> </prefs>;
When the application first opens, it'll attempt to read the file like this:
var s:FileStream = new FileStream();
var f:File = File.applicationStorageDirectory; // this is a reference to a predetermined directory. check the air api for more.
f = f.resolvePath(Constants.FILE_PREFS_XML); // basically converts a String to something the File class will understand.
s.addEventListener(Event.COMPLETE,_onPrefsReadSuccess,false,0,false);
s.addEventListener(IOErrorEvent.IO_ERROR,_onPrefsReadIOError,false,0,false);
// opens, reads and when complete fires Event.COMPLETE (aka our _onPrefsLoaded function).
s.openAsync(f,FileMode.READ);
That's it. Seven lines of code (minus comments) attempts to open a file. If it opens, you'll get _onPrefsReadSuccess; otherwise you get _onPrefsReadIOError. The IO error will realistically happen if the file is corrupt or if it doesn't exist at all—in either case, the game will simply begin to animate artwork. The success function is much more interesting:
private function _onPrefsReadSuccess($evt:Event):void
{
Out.status(this,"_onPrefsLoaded");
// hurrah! I know this is a returning user because the prefs xml has been created. let's set the user back where they left off.
var xml:XML = new XML($evt.target.readUTF());
_mc.stage.nativeWindow.x = xml.stagePosition.@x;
_mc.stage.nativeWindow.y = xml.stagePosition.@y;
_wins = xml.record.@wins;
_losses = xml.record.@losses;
Out.info(this,"Stage Position: " + _mc.x.toString() + " x " + _mc.y.toString());
// prefs are set, now I can animate the screen in.
_animateIn();
};
The app moves the stage window position to the number described in XML. It also stores the _wins and _losses as variables. Then it proceeds to display the content. What happens, after a game is won or lost, or the window gets dragged around? Have a look:
private function _updatePrefs():void
{
Out.status(this,"_updatePrefs");
// for updating, I'll do a synchronized file write. since this is a small file, it shouldn't require too much time to process.
var s:FileStream = new FileStream();
var f:File = File.applicationStorageDirectory;
f = f.resolvePath(Constants.FILE_PREFS_XML);
// open the file here
s.open(f,FileMode.WRITE);
// create a copy of the schema I'll use for preferences xml, so I'm not editing the schema itself.
var xml:XML = Constants.SAVED_PREFS.copy();
// populate the data I need.
xml.stagePosition.@x = _mc.stage.nativeWindow.x;
xml.stagePosition.@y = _mc.stage.nativeWindow.y;
xml.record.@wins = _wins;
xml.record.@losses = _losses;
Out.debug(this,"Prefs written as follows:");
Out.debug(this,xml.toXMLString());
// and finally I write.
s.writeUTF(xml);
s.close();
};
This time set FileStream to open the file as writable (the s.open() line). Then do the opposite of the read: Grab the saved variables (window position, wins and losses) and write the XML data as UTF bytes. Easy as pie.
Now that I have a cool custom application, I shall fill that window with my game.