Home >
A few weeks ago (ok, a bit over a month ago, sorry!) I wrote a two part series (part 2) about creating a simple game with jQuery and AIR. My game, Hangman, made use of a large dictionary of words loaded from a SQLite database. jQuery was used for all the display and user interaction. Altogether the game worked pretty well, but I got some really good feedback from the last entry that led me to make some improvements. As before, I encourage folks to rip apart the code and describe how they would do it. Alright, so with that out of the way, let me talk about the updates!
The first update was my - probably feeble - attempt to turn the game into more of an object oriented application. In the initial version the game's display and logic were all tied closely together. My first change was to create a game object called Hangman. The logic isn't too terribly complex, so I'll share the entire file:
The Hangman object is simple. It contains some basic methods to handle setting and getting various properties. What's nice though is that the more complex logic (like getting the masked word) no longer clutters up the core HTML file. So for example, the HTML file starts up a new game with just:
All of the variables I had used before are now hidden away behind the object. I simply worry about display. So as an example, to render the letters already picked for the game, I can do:
As another example, after every turn, the logic to handle game state is also nicely abstracted:
I've included the complete source in the download (towards the end of the article) and I think it's a big improvement over the last version.
Next up was some movement in my JavaScript libraries. "Cowboy" Ben Alman made the point that my runSQL jQuery plugin really wasn't a proper jQuery plugin. I agreed with him. I removed it from my jquery.air.js file and moved it to a new generic util.js library. I also modified my runSQL code to better handle SQL statements that don't return a result set:
The modification was all of one line: if(!result.data) return. This will come in handy in a few minutes when I describe the database changes I made.
So the net result of the two previous changes are - in my opinion - a slightly better architected JavaScript application. What's interesting is that I feel like I was able to apply some of my MVC based knowledge from web sites to the JavaScript-based AIR application. I'll probably look back at it in a year and shudder, but for now I'm proud.
Ok, so the last change is rather cool I think. If you remember, the Hangman application used a database to retrieve a random word for every game. I decided to make more use of the database. Initially my application ran a function called initGame on startup. This function was also run whenever a new game was started. I broke this up into two methods: initApp and initGame. initApp is run once and initGame is run on ever game instance. The previous version of the application copied the database from the installation directory to a specific application directory for the game. That hasn't changed:
Now though I've decided to actually create a table as well. This table will handle tracking your wins and losses. I decided to build a simple table that would store the date of your game and a boolean value that represents a win.
The SQL uses CREATED TABLE IF NOT EXISTS to handle the logic of creating the table once and only one. Now when the game ends, I can quickly insert the result:
That's an example of a win. A loss simply uses a 0 instead of a 1. At the end of the game I display their stats. This is done with a few simple SQL statements:
Here is an example of the game running. The stats are beneath the Play Again link:
You can download the game (AIR installer and source code) here. Enjoy! Now I just need to get to work on my thermonuclear war game.
The first update was my - probably feeble - attempt to turn the game into more of an object oriented application. In the initial version the game's display and logic were all tied closely together. My first change was to create a game object called Hangman. The logic isn't too terribly complex, so I'll share the entire file:
function Hangman(word) {
this.lettersused = ""
this.currentword = word
this.misses = 0
return true
}
Hangman.prototype.getMisses = function() { return this.misses; }
Hangman.prototype.getCurrentWord = function() { return this.currentword; }
Hangman.prototype.setCurrentWord = function(word) {
this.currentword = word
}
Hangman.prototype.getLettersUsed = function() { return this.lettersused; }
Hangman.prototype.getMaskedWord = function() {
var maskedWord = ""
for(var i=0; i<this.currentword.length;i++) {
var thisLetter =""
thisLetter = this.currentword.substring(i,i+1)
if(this.lettersused.indexOf(thisLetter) >= 0) maskedWord += thisLetter
else maskedWord += "-"
}
return maskedWord
}
Hangman.prototype.isLetterUsed = function(l) {
if(this.getLettersUsed().indexOf(l) >= 0) return true
else return false
}
Hangman.prototype.makeGuess = function(l) {
//ensure it's not used
if(!this.isLetterUsed(l)) this.lettersused+=l
if(this.currentword.indexOf(l) == -1) {
this.misses++
}
}
Hangman.prototype.lost = function() {
return this.misses == 9
}
Hangman.prototype.won = function() {
if(this.getMaskedWord() == this.currentword) return true
}
The Hangman object is simple. It contains some basic methods to handle setting and getting various properties. What's nice though is that the more complex logic (like getting the masked word) no longer clutters up the core HTML file. So for example, the HTML file starts up a new game with just:
game = new Hangman(pickRandomWord())
All of the variables I had used before are now hidden away behind the object. I simply worry about display. So as an example, to render the letters already picked for the game, I can do:
$("#letterList").html(game.getLettersUsed())
As another example, after every turn, the logic to handle game state is also nicely abstracted:
if(game.won()) {
doWin()
} else if(game.lost()) {
doDeath()
}
I've included the complete source in the download (towards the end of the article) and I think it's a big improvement over the last version.
Next up was some movement in my JavaScript libraries. "Cowboy" Ben Alman made the point that my runSQL jQuery plugin really wasn't a proper jQuery plugin. I agreed with him. I removed it from my jquery.air.js file and moved it to a new generic util.js library. I also modified my runSQL code to better handle SQL statements that don't return a result set:
runSQL = function(con,sql) {
var getStmt = new air.SQLStatement()
getStmt.sqlConnection = con
getStmt.text = sql
getStmt.execute()
var result = getStmt.getResult()
if(!result.data) return
var tableresult = []
for(var i=0;i<result.data.length;i++) {
var row = {}
for(col in result.data[i]) {
row[col] = result.data[i][col]
}
tableresult[tableresult.length] = row
}
return tableresult
}
The modification was all of one line: if(!result.data) return. This will come in handy in a few minutes when I describe the database changes I made.
So the net result of the two previous changes are - in my opinion - a slightly better architected JavaScript application. What's interesting is that I feel like I was able to apply some of my MVC based knowledge from web sites to the JavaScript-based AIR application. I'll probably look back at it in a year and shudder, but for now I'm proud.
Ok, so the last change is rather cool I think. If you remember, the Hangman application used a database to retrieve a random word for every game. I decided to make more use of the database. Initially my application ran a function called initGame on startup. This function was also run whenever a new game was started. I broke this up into two methods: initApp and initGame. initApp is run once and initGame is run on ever game instance. The previous version of the application copied the database from the installation directory to a specific application directory for the game. That hasn't changed:
function initApp() {
//I handle copying the db from local to storage dir
var installTo = air.File.applicationStorageDirectory
var installToFile = installTo.resolvePath("words.db")
if(!installToFile.exists) {
air.trace("Copying db file to "+installToFile.nativePath)
var installFromLoc = air.File.applicationDirectory
var installFromFile = installFromLoc.resolvePath("database/words.db")
air.trace("from "+installFromFile.nativePath)
try {
installFromFile.copyTo(installToFile,true)
} catch(error) {
//Total Failure...
alert(error.message+'\n'+error.details)
air.NativeApplication.nativeApplication.exit()
return
}
}
//connect to db
try {
dbcon = new air.SQLConnection()
var dbFile = air.File.applicationStorageDirectory.resolvePath("words.db")
air.trace(dbFile.nativePath)
dbcon.open(dbFile)
} catch(error) {
//Total Failure...
alert(error.message+'\n'+error.details)
air.NativeApplication.nativeApplication.exit()
return
}
Now though I've decided to actually create a table as well. This table will handle tracking your wins and losses. I decided to build a simple table that would store the date of your game and a boolean value that represents a win.
//Add my custom stuff to the db.
var tableSQL = "CREATE TABLE IF NOT EXISTS history(" +
"played DATE," +
"won INTEGER)"
runSQL(dbcon, tableSQL)
}
The SQL uses CREATED TABLE IF NOT EXISTS to handle the logic of creating the table once and only one. Now when the game ends, I can quickly insert the result:
var sql = "insert into history(played,won) "+
"values(datetime(),1);"
runSQL(dbcon,sql)
That's an example of a win. A loss simply uses a 0 instead of a 1. At the end of the game I display their stats. This is done with a few simple SQL statements:
function doHistory() {
var totalSQL = "select count(won) as total from history";
var total = runSQL(dbcon, totalSQL)[0].total
var wonSQL = "select count(won) as total from history where won = 1";
var totalWon = runSQL(dbcon, wonSQL)[0].total
$("#gameStatus").append("<br/>So far, you have won "+totalWon+" game(s) out of "+total+" played.")
}
Here is an example of the game running. The stats are beneath the Play Again link:
You can download the game (AIR installer and source code) here. Enjoy! Now I just need to get to work on my thermonuclear war game.
- Metadata and the Flash Platform
- RIA Unleashed Boston Conference - My thoughts
- Using UDP socket connections for low-latency and loss-tolerant scenarios in AIR 2 (Part 2)
- Using UDP socket connections for low-latency and loss-tolerant scenarios in AIR 2 (Part 1)
- AIR 2.0 and FP 10.1 now Available on Adobe Labs





Facebook Application Development
Comments
Leave a comment