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.
So now into the code. This is another entry that uses AJAX, and specifically the CFAJAX project. I'm still not sold on the whole AJAX thing. I appreciate it from a technical side and have no problem requiring JavaScript, but as I said, I'm not sold on it.
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>
<body>
<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[1] EQ curHandValue[2] >
For a total of <cfoutput>#curHandValue[1]#</cfoutput> points.
<cfelse >
For a total of <cfoutput>#curHandValue[1]#/#curHandValue[2]#</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.
Earlier Entries:
Archived Comments
that's just soooo wrong.
i may just start harping again :)
Very nice overall. I didn't find any bugs in my brief testing.
On the UI side of things, the game really needs a "Clear My Bet" function. Clicking on the chips is nice, but you have no way to correct a bet if you messed up.
Very nice interface.
I played a little and in one hand, after I clicked Deal, The game said that the Dealer has Blackjack and winned. Dealer had an Ace, and the other card was not visible. Is this behaviour acceptable or is it a bug?
Behrang - I noticed the same thing.
Also, I palyed for about 15 minutes, never won a hand. I don't think its a bug, just that I suck at Blackjack.
First off, the game looks amazing. Is the background image something you created yourself, or was it borrowed from the web?
Before I get to the code I have 3 three brief UI comments:
1) Like Dan said, would be nice if I could clear my bet.
2) IMHO the "game stats" area should be located closer to the chip rack, perhaps along the bottom of the table instead of the right. When I first started playing I didn't realize I was betting, because when my eyes were focused on the chip rack I couldn't clearly see the "current bet" area of the stats. And if I focus on the current bet area then I can't clearly see which chip my mouse is hovering over.
3) Lastly, my preference is that if you are going to use a visual element to control betting (i.e. the stack of chips) then that element should give feeback feedback to the user regarding what options are available. In other words, if I only have $5 left in my bank, the chip rack shouldn't show a full stack of $100 chips. Showing someone a stack of chips *implies* that it represents their actual stack, and I was confused until I realized it was just a static image. [I know this is impossible with your current setup, its just a thought.]
Other than those small issues the UI looks and works great, nice job! Now, on to the code...
Generally speaking I thought the code was structured nicely and easy to understand. Not "var"-ing local method variables is a huge problem, but Ray has already covered that. I do have two comments of my own as well:
1) Drawing a card is not a constant time operation. In fact, under certain circumstances it could take an inordinate amount of time to complete. This is because drawing a card involves picking a card at random, checking its "inDeck" flag, and then repeating this process if the selected card has already been pulled from the deck.
Imagine the situation when there is only one card left: there is only a 1/52 chance that a given loop iteration will select that card, so the loop could very possibly run a long time until that card is chosen at random.
A better solution would be to maintain a list of the array indices for cards that remain in the deck. Then, to draw a card all you need to do is randomly select an item from this and delete its index from the list of remaining cards. No loop needed. To shuffle the deck just re-populate the list of remaining cards so that it lists them all.
2) Player.cfc and Dealer.cfc both include logic for determining their hand values. I would have preferred to see this logic in a single place instead of duplicated. This could be handled by creating a Hand.cfc object that stores the cards in a hand and has a method for obtaining their value(s). Both the dealer and player objects would contain a Hand object.
All in all I thought this was very well done, both visually and from a code perspective. Nice job!
I just thought I'd say that I played for about 15 minute and only lost one hand. I'm at about $5000+ and counting.
I have a few comments about the game.
- The UI looks very nice.
- I had a problem when I had 5-2-A-2 which is 11/21. I tried to stay, but the stand and hit buttons were disabled, so I could not do anything. At that point my game was over b/c had to hit new game to do anything. This also happened again when I had 6/16 and the dealer had Ace showing. I think the dealer had 21 b/c something flashed very quick about 21. I did not have time to read what it said. So I would say the timed messages are not the best, a little user interaction might be good so the user can acknowledge they read the message.
- If the user refreshes the screen the game is cleared, basically like hitting new game.
- I like the use of AJAX.
I did not download the code and look at it, but those are just my observations from just playing the game online for a bit.
Good entry.
Thanks for the comments on the UI. I will have to say that the graphics were borrowed from different sites and I pieced together what I liked for the table.
As far as the visual element goes on the chips, my wife said the same thing. Being that this was a CF project though, I decided to leave that 'minor' feature until later.
Yeah, I missed the var thing. I have not been doing CFCs for very long, so I took this project as an excuse to dive into several technologies I don't have a chance to work with much on a daily basis ... i.e. CFCs and AJAX.
I won't guarantee there are no bugs, but I did not see any in my testing. The problem with the aces is interesting, I will have to go back and check that.
Thanks.
I haven't had time to look at this one too closely, but the thing that jumped out at me immediately is in the Application.cfc:
clientManagement= "true"
clientStorage = "registry"
Client variables aren't used anywhere in the app, yet due to these settings new registry entries are being made for each player.
Gus
very cool . I'd like to learn CF but haven't found a place that clearly shows how to get started .