Home  >  

jQuery and AIR - Moving from web page to application (3)

Author photo
| | Comments (0)
AddThis Social Bookmark Button
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:

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:

Picture 1.png
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.

Read more from Raymond Camden. Raymond Camden's Atom feed cfjedimaster on Twitter

Comments

Leave a comment


Tag Cloud

Question of the Week: Call for Topics

What do you want from InsideRIA?

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.