Welcome to the third entry in the Intermediate ColdFusion Contest. The earlier entries may be found at the end of this post. Today's entry is from Jeff D. Chastain, which for some reason sounded like a 'rogue gamblers' type name, so it's perfect for the contest. Before reading on, please check his application here. You can download his code from the download link at the bottom. That's the word Download for the Weegs out there. (Sorry man, couldn't resist. :) Remember that his code belongs to him!
So - some initial non-ordered opinions on the game experience before getting into the code. I like how you have to click on the chips to bet. Not the graphics per se, which are nice, but this isn't a contest for graphics, but rather, I noticed how on this entry, and the first one, I didn't have to type something to bet. I think I like that. Makes the game a bit quicker to play. Or maybe I'm just a lazy guy.
It's funny. I was going to complain about how I didn't like having to hit Deal after I placed my bet. I was going to say - shouldn't the game just throw the cards out as soon as I bet. Then it occurred to me that it would be a bit silly to let modify my bets while staring at the cards. (This is why I don't go to Las Vegas.)
There is a typo when you try bet more money than you have in your bank. Only idiots tpyo.
And lastly, maybe it was the old school gamer in me - but I was sure if I clicked in the right place I'd uncover a secret.
His code makes use of Application.cfc, but I noticed this in index.cfm:
<cfif isDefined('url.init') OR NOT isDefined('session.dealer')> <cfset structClear(session) /> <cfset session.dealer = createObject('component', '_com.dealer').init() /> <!--- new dealer ---> <cfset session.player = createObject('component', '_com.player').init(2000) /> <!--- new player with inital $2000 bank ---> </cfif>
I'd probably consider moving parts of this to onSessionStart, since thats what it is there for. Now - he doesn't just use this once. When the game starts over he uses it to reset the bank. The code, however, could be put in the onRequestStart. His CFC won't allow that as it is right now. The setBank() function is private, but it should be safe to change that to public and allow onRequestStart to do it. That would make the app a tiny bit quicker, as you wouldn't need to reload the CFCs for a new game. Nit picky maybe - but I think if you have the Application.cfc file there - you should use it.
I'm going to point one bad thing about his CFCs than a good one. He didn't var scope his local variables. As my readers now, I consider that a cardinal sin. I'm going to keep repeating myself while I keep seeing code that doesn't do this. On the other hand - I like code like this:
<cffunction name="getBank" access="public" returntype="numeric" output="false" hint="get the bank amount"> <cfreturn variables.myBank /> </cffunction>
This is from player.cfc. Why do I like this? In the past I would create global CFC variables and simply use them. So if some method needs variables.foo, I'd just use it. But by creating a simple method to abstract it, I think you gain a few things. First off - if you ever move the variable from the variables scope, you don't need to change N variables. Instead, you just update method. I swore I had another reason, but now I can't think of it. Anyway, I first noticed code like this in Model-Glue examples. I thought it was a bit crazy at first - but as I said, it makes sense to me now.
I was looking at this CFC folder when I noticed something odd. Every CFC had a corresponding CFM file. I opened up one and noticed that it was a test file. Here is a portion of player.cfm:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Player.cfc Test Harness</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head>
<h1>Player.cfc Test Harness</h1>
<p><strong>Creating new dealer ...</strong><br/> <cfset dealer = CreateObject('component', 'dealer').init() /> Dealer created</p>
<p><strong>Creating new player ...</strong><br/> <cfset player = CreateObject('component', 'player').init(2000) /> Player created</p>
<p><strong>Player's current hand includes ...</strong><br/> <cfset curHand = player.getHand() /> <cfset curHandValue = player.getHandValue() />
<cfloop from="1" to="#arrayLen(curHand)#" index="ptr"> <cfoutput>#curHand[ptr].show().value# of #curHand[ptr].show().suite#</cfoutput><br/> </cfloop>
<cfif arrayLen(curHand) EQ 0 > Player's hand is empty <cfelseif curHandValue EQ curHandValue > For a total of <cfoutput>#curHandValue#</cfoutput> points. <cfelse > For a total of <cfoutput>#curHandValue#/#curHandValue#</cfoutput> points. </cfif> </p>
In case it isn't obvious - what he has done is written a file he can hit and ensure the functionality of his CFC is still working right. This is a Good Thing(tm) and nowhere near enough of us do it. I don't do it, and you know what, I really need to help ensure all those projects I have up in the air run right. I was asked to check out CFUnit, a unit testing framework, and I just haven't had the time, but I'm going to recommend other folks check it out as well as checking out cfcUnit. At worst - look at Jeff's simple one page test file. It does the job well I think. I'm kinda riffing now (how long is this blog post going to be), but one of the problems I have with my projects is ensuring my changes work across the databases I support. Using a test script that runs the same code in a loop over multiple databases would help me flesh out those problems earlier.
So, I think I've said enough about this application. Time for my readers to chime in as well.