<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" 
      xmlns:thr="http://purl.org/syndication/thread/1.0">
  <link rel="alternate" type="text/html" href="http://insideria.com/2009/11/jquery-and-air---moving-from-w-2.html" />
  <link rel="self" type="application/atom+xml" href="http://insideria.com/atom.xml" />
  <id>tag:insideria.com,2010://34/tag:www.insideria.com,2009://34.38425-</id>
  <updated>2010-07-16T15:54:55Z</updated>
  <title>Comments for jQuery and AIR - Moving from web page to application (3) (http://insideria.com/2009/11/jquery-and-air---moving-from-w-2.html)</title>
  <generator uri="http://www.sixapart.com/movabletype/">Movable Type 4.21-en</generator>
  <entry>
    <id>tag:www.insideria.com,2009://34.38425</id>
    <link rel="alternate" type="text/html" href="http://insideria.com/2009/11/jquery-and-air---moving-from-w-2.html" />
    <link rel="service.edit" type="application/atom+xml" href="http://blogs.oreilly.com/cgi-bin/mt/mt-atom.cgi/weblog/blog_id=34/entry_id=38425" title="jQuery and AIR - Moving from web page to application (3)" />
    <published>2009-11-05T02:34:09Z</published>
    <updated>2009-11-05T02:34:09Z</updated>
    <title>jQuery and AIR - Moving from web page to application (3)</title>
    <summary>The third part of my series on creating a jQuery AIR game - Hangman. In this entry I discuss some architecture changes I made to the JavaScript and some new database support.</summary>
    <author>
      <name>Raymond Camden</name>
      <uri>http://www.coldfusionjedi.com</uri>
    </author>
    
    <category term="Blogs" />
    
    <content type="html" xml:lang="en" xml:base="http://insideria.com/">
      <![CDATA[A few weeks ago (ok, a bit over a month ago, sorry!) I wrote a two part series (<a href="http://www.insideria.com/2009/09/jquery-and-air---moving-from-w.html
>part 1</a> and <a href="http://www.insideria.com/2009/09/jquery-and-air---moving-from-w-1.html">part 2</a>) 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!
<br/><br/>
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:
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px; height: 240px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
<span class="category1">function</span> Hangman(word) {
 	<span class="category1">this</span>.lettersused = "<span class="quote"></span>"
 	<span class="category1">this</span>.currentword = word
 	<span class="category1">this</span>.misses = 0
 	
 	<span class="category1">return</span> <span class="category1">true</span>
}

Hangman.<span class="category2">prototype</span>.getMisses = <span class="category1">function</span>() { <span class="category1">return</span> <span class="category1">this</span>.misses; }

Hangman.<span class="category2">prototype</span>.getCurrentWord = <span class="category1">function</span>() { <span class="category1">return</span> <span class="category1">this</span>.currentword; }

Hangman.<span class="category2">prototype</span>.setCurrentWord = <span class="category1">function</span>(word) {
 	<span class="category1">this</span>.currentword = word
}

Hangman.<span class="category2">prototype</span>.getLettersUsed = <span class="category1">function</span>() { <span class="category1">return</span> <span class="category1">this</span>.lettersused; }

Hangman.<span class="category2">prototype</span>.getMaskedWord = <span class="category1">function</span>() {
 	<span class="category1">var</span> maskedWord = "<span class="quote"></span>"
 	<span class="category1">for</span>(<span class="category1">var</span> i=0; i&lt;<span class="category1">this</span>.currentword.<span class="category2">length</span>;i++) {
  		<span class="category1">var</span> thisLetter ="<span class="quote"></span>"
  		thisLetter = <span class="category1">this</span>.currentword.<span class="category2">substring</span>(i,i+1)
  		<span class="category1">if</span>(<span class="category1">this</span>.lettersused.<span class="category2">indexOf</span>(thisLetter) &gt;= 0) maskedWord += thisLetter
  		<span class="category1">else</span> maskedWord += "<span class="quote">-</span>"
  	}
 	<span class="category1">return</span> maskedWord
}

Hangman.<span class="category2">prototype</span>.isLetterUsed = <span class="category1">function</span>(l) {
 	<span class="category1">if</span>(<span class="category1">this</span>.getLettersUsed().<span class="category2">indexOf</span>(l) &gt;= 0) <span class="category1">return</span> <span class="category1">true</span> 
 	<span class="category1">else</span> <span class="category1">return</span> <span class="category1">false</span>
}

Hangman.<span class="category2">prototype</span>.makeGuess = <span class="category1">function</span>(l) {
 	<span class="linecomment">//ensure it's not used</span>
 	<span class="category1">if</span>(!<span class="category1">this</span>.isLetterUsed(l)) <span class="category1">this</span>.lettersused+=l
 	<span class="category1">if</span>(<span class="category1">this</span>.currentword.<span class="category2">indexOf</span>(l) == -1) {
  		<span class="category1">this</span>.misses++
  	}
}

Hangman.<span class="category2">prototype</span>.lost = <span class="category1">function</span>() {
 	<span class="category1">return</span> <span class="category1">this</span>.misses == 9
}

Hangman.<span class="category2">prototype</span>.won = <span class="category1">function</span>() {
 	<span class="category1">if</span>(<span class="category1">this</span>.getMaskedWord() == <span class="category1">this</span>.currentword) <span class="category1">return</span> <span class="category1">true</span>
}</pre>
</code>
</div></div>
<br/><br/>

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:
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
game = <span class="category1">new</span> Hangman(pickRandomWord())</pre>
</code>
</div></div>
<br/><br/>

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:
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
$("<span class="quote">#letterList</span>").<span class="category2">html</span>(game.getLettersUsed())</pre>
</code>
</div></div>
<br/><br/>

As another example, after every turn, the logic to handle game state is also nicely abstracted:
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
<span class="category1">if</span>(game.won()) {
 	doWin()
} <span class="category1">else</span> <span class="category1">if</span>(game.lost()) {
 	doDeath()
}</pre>
</code>
</div></div>
<br/><br/>

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.
<br/><br/>

Next up was some movement in my JavaScript libraries. "Cowboy" Ben Alman <a href="http://www.insideria.com/2009/09/jquery-and-air---moving-from-w-1.html#comment-2124087">made the point</a> 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:
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
runSQL = <span class="category1">function</span>(con,sql) {
 
 	<span class="category1">var</span> getStmt = <span class="category1">new</span> air.SQLStatement()
 	getStmt.sqlConnection = con
 	getStmt.<span class="category2">text</span> = sql
 	getStmt.execute()
 	<span class="category1">var</span> result = getStmt.getResult()
 	<span class="category1">if</span>(!result.<span class="category2">data</span>) <span class="category1">return</span>
 	<span class="category1">var</span> tableresult = []
 	<span class="category1">for</span>(<span class="category1">var</span> i=0;i&lt;result.<span class="category2">data</span>.<span class="category2">length</span>;i++) {
  		<span class="category1">var</span> row = {}
  		<span class="category1">for</span>(col <span class="category1">in</span> result.<span class="category2">data</span>[i]) {
   			row[col] = result.<span class="category2">data</span>[i][col]
   		}	
  		tableresult[tableresult.<span class="category2">length</span>] = row
  	}
 	<span class="category1">return</span> tableresult
}</pre>
</code>
</div></div>
<br/><br/>

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. 
<br/><br/>

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.
<br/><br/>

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:
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px; height: 240px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
<span class="category1">function</span> initApp() {
 	<span class="linecomment">//I handle copying the db from local to storage dir</span>
 	<span class="category1">var</span> installTo = air.File.applicationStorageDirectory
 	<span class="category1">var</span> installToFile = installTo.resolvePath("<span class="quote">words.db</span>")
 	<span class="category1">if</span>(!installToFile.exists) {
  		air.<span class="category2">trace</span>("<span class="quote">Copying db file to </span>"+installToFile.nativePath)
  		<span class="category1">var</span> installFromLoc = air.File.applicationDirectory
  		<span class="category1">var</span> installFromFile = installFromLoc.resolvePath("<span class="quote">database/words.db</span>")
  		air.<span class="category2">trace</span>("<span class="quote">from </span>"+installFromFile.nativePath)
  		<span class="category1">try</span> {
   			installFromFile.copyTo(installToFile,<span class="category1">true</span>)
   		} <span class="category1">catch</span>(error) {
   			<span class="linecomment">//Total Failure...</span>
   			alert(error.<span class="category2">message</span>+'<span class="quote">\n</span>'+error.details)
   			air.NativeApplication.nativeApplication.exit()
   			<span class="category1">return</span>
   		}
  	}
 
 	<span class="linecomment">//connect to db</span>
 	<span class="category1">try</span> {
  		dbcon = <span class="category1">new</span> air.SQLConnection()
  		<span class="category1">var</span> dbFile = air.File.applicationStorageDirectory.resolvePath("<span class="quote">words.db</span>")
  		air.<span class="category2">trace</span>(dbFile.nativePath)
  		dbcon.open(dbFile)
  	} <span class="category1">catch</span>(error) {
  		<span class="linecomment">//Total Failure...</span>
  		alert(error.<span class="category2">message</span>+'<span class="quote">\n</span>'+error.details)
  		air.NativeApplication.nativeApplication.exit()
  		<span class="category1">return</span>
  	}	</pre>
</code>
</div></div>
<br/><br/>

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.
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
	<span class="linecomment">//Add my custom stuff to the db.</span>
	<span class="category1">var</span> tableSQL = "<span class="quote">CREATE TABLE IF NOT EXISTS history(</span>" +
				   "<span class="quote">played DATE,</span>" +
				   "<span class="quote">won INTEGER)</span>"
	
	runSQL(dbcon, tableSQL)
}</pre>
</code>
</div></div>
<br/><br/>

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:
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
	<span class="category1">var</span> sql = "<span class="quote">insert into history(played,won) </span>"+
			  "<span class="quote">values(datetime(),1);</span>"
	runSQL(dbcon,sql)</pre>
</code>
</div></div>
<br/><br/>

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:
<br/><br/>

<div class="acode" style="overflow: auto; padding: 10px;" ><div style="overflow-x: visible;"><code language="perl">
<pre>
<span class="category1">function</span> doHistory() {
 	<span class="category1">var</span> totalSQL = "<span class="quote">select count(won) as total from history</span>";
 	<span class="category1">var</span> total = runSQL(dbcon, totalSQL)[0].total
 	<span class="category1">var</span> wonSQL = "<span class="quote">select count(won) as total from history where won = 1</span>";
 	<span class="category1">var</span> totalWon = runSQL(dbcon, wonSQL)[0].total
 	$("<span class="quote">#gameStatus</span>").append("<span class="quote">&lt;br/&gt;So far, you have won </span>"+totalWon+"<span class="quote"> game(s) out of </span>"+total+"<span class="quote"> played.</span>")
}</pre>
</code>
</div></div>
<br/><br/>

Here is an example of the game running. The stats are beneath the Play Again link:
<br/><br/>

<div class="ap_c">
<a href="http://www.insideria.com/upload/2009/11/Picture%201.png" class="highslide" onclick="return hs.expand(this)"><img src="http://www.insideria.com/upload/2009/11/Picture%201.png" alt="Picture 1.png" title="Click to enlarge" width="400"/></a></div>

You can download the game (AIR installer and source code) <a href="http://www.coldfusionjedi.com/downloads/hangman114.zip">here</a>. Enjoy! Now I just need to get to work on my thermonuclear war game.]]>
      
    </content>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.38425-comment:2217557</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.38425" type="text/html" href="http://insideria.com/2009/11/jquery-and-air---moving-from-w-2.html"/>
    <link rel="alternate" type="text/html" href="http://insideria.com/2009/11/jquery-and-air---moving-from-w-2.html#comment-2217557" />
    <title>Comment from Martin on 2009-12-08</title>
    <author>
        <name>Martin</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>Awesome Ray! Can I ask how you create and populate the words.db file. I didn't see it in the download but it gets created during the install.</p>]]>
    </content>
    <published>2009-12-08T12:02:35Z</published>
  </entry>

  <entry>
    <id>tag:www.insideria.com,2009://34.38425-comment:2217562</id>
    <thr:in-reply-to ref="tag:www.insideria.com,2009://34.38425" type="text/html" href="http://insideria.com/2009/11/jquery-and-air---moving-from-w-2.html"/>
    <link rel="alternate" type="text/html" href="http://insideria.com/2009/11/jquery-and-air---moving-from-w-2.html#comment-2217562" />
    <title>Comment from Raymond Camden on 2009-12-08</title>
    <author>
        <name>Raymond Camden</name>
        <uri></uri>
    </author>
    <content type="html" xml:lang="en" xml:base="">
        <![CDATA[<p>Ah, good question there. I actually populated it using ColdFusion. I looped over the file and inserted the words one by one.</p>]]>
    </content>
    <published>2009-12-08T12:10:10Z</published>
  </entry>

</feed
