Welcome to the second entry in the Intermediate ColdFusion Contest. The earlier entry may be found at the end of this post. Today's entry is from Seth Petry-Johnson. Before reading on, please check his application here. You can download his code from the download link at the bottom. (Remember that his code belongs to him!)
The first thing I noticed was a bug, a pretty bad one. Being the mischievous person I am, when the game asked me to enter a name, I decided not to. This gave me an infinite URL recursion error. I noticed later on that any error seemded to cause this. I decided to dig right into the code to see where the bug was. Turns out - this was quite the complex little application. To be honest, it took me a while to figure out the problem. This doesn't mean the application was bad - just that I had to spend a few minutes looking into his setup to follow the 'path' of the application. Due to the nature of the bug, I tried cflog. This helped me determine that the problem occurred in blackjack.cfm, not in application.cfm. (At this point, you may want to follow along in the source code.) Specifically, the issue occurred here:
<!--- advance the game to its next point of user interaction --->
<cfset session.blackjackGame.play()>
So I began to peek around and saw quite a sophisticated setup. The Blackjackgame CFC was actually BlackjackGameController.cfc. (One little pet peave - I would probably suggest using variable names close to the CFC file name - especially if you have a CFC with the exact same name that isn't the same as what is actually being stored in the data.) So, as far as I could see, play() wasn't doing anything yet since I was still in 'step 0' of the game so to speak. Returning to blackjack.cfm, I see:
From what I can tell, and this is pretty nice, the controller CFC is dictating which include to run next. This makes perfect sense and is a good approach. So, this method led me to the file, inc_GetPlayInfo.cfm. I can tell by the file name that this is the file asking me for my name (duh).
Ok - still with me? This file posts to blackjack_actions.cfm. This file also seems to have the only cflocation in the application, so it must be the culprit for the infinite looping. I then noticed this:
<cfif errorMgr.hasErrors()>
<cfset session.SavedRequestArgs = Request.args>
<cfset session.ValidationErrorMgr = errorMgr>
<cflocation url="#AddQuerystringToUrl(onError, 'GetParamsFromSession=yes&ShowErrorMessages=yes')#" addtoken="no">
<cfelse>
<cflocation url="#onNext#" addtoken="no">
</cfif>
I added a cfoutput right before the cflocation, along with a cfabort. I noticed the URL had no file, just a ? with parameters after it. I'd be willing to bet this worked on Seth's box, and that he had IIS, while I was using Apache. So I quickly uploaded it this server, which is IIS, and it worked fine. I know I've had issues with IIS versus Apache before, so I certainly don't blame Seth. I did a bit more digging. He was passing onError to AddQuerystringToUrl. The value of onError should have been the URL to hit. Again, from the same file:
<cfif IsDefined("Request.args.onError") AND (Len(Trim(Request.args.onError)) GT 0)>
<cfset onError = Request.args.onError>
<cfelse>
<cfset onError = CGI.HTTP_REFERER>
</cfif>
Turns out - my cgi.http_referer was blank! I'm guessing then what he should do is add a sanity check and maybe default to blackjack.cfm. (Which is what I did. I simply added it in the cflocation.)
So, after all that complaining about one little bug - what do I like. First off - I forgot to mention something on the first entry - and this entry has it as well. I really like how both entries show you your card total. This is great for people who may not know the game very well. I'd probably add an option though to turn this off. (But that is way beyond the scope of the original specifications.)
Another thing I like - the validation (once the error handling bug if fixed) seems dead on. I tried a bet of "apple", I tried a bet of "-9", and the application caught them all. One thing it didn't catch was that it allowed a bet of 9.999999999. It seemed to just round it up, which is probably fair. Outside of that - it even seemed to handle the back button well, and my reloading without hitting anything. I may have missed something - but it looks like the application is pretty rock solid.
Seth's application makes very intense use of CFCs - including a CFC for the game, a playing card, a deck, a player, a blackjack dealer, a card's rank and a card's suit. But wait - there is more. There are also Factory CFCs which help manage those other CFCs. Definitely not a "Uber CFC" like my BlogCFC (which is a good thing!). His system was complex enough for him to provide a UML diagram. This is a pretty nice setup, and I encourage folks to download the zip (link below) and look around his setup.
Lastly - and I almost didn't notice this - he uses an ini file to control aspects of the game. Specifically the starting amount and where the dealer stays. Here is his INI file:
[blackjack]
dealerHitsBelow=17
startingMoney=100.00
This is nice as it makes tweaking the core aspects of the game something a non-technical person could handle. (I discussed ini files in an earlier post.)
Earlier Entries:
Archived Comments
color me crazy, but i do not see any place to download either entries code?
tw
It's the Download link, next to Print.
I think it is to the left of the comments link. Though I did not try it.
your other left
You're correct, this was developed on IIS and on my box the error handling on the first page works fine. I thought I'd added the "sanity check" you mentioned but (obviously) I never did. Too much caffeine, not enough sleep :)
Its strange that the HTTP_REFERRER was blank on your box on the form action page, it should have been the URL from which the form was submitted. Is this a bug with Apache or am I missing something important?
As for allowing a bet of 9.999999999, I just truncate anything past the second decimal point. I thought about making the user re-enter but it didn't seem worth the effort, provided that the truncated amount was a valid bet.
since constructive criticism is welcomed on this blog, i would say that the download link DOES NOT in anyway stand out to me, someone who was looking, as anything related to the downloading of anything in the post. just lettin ya know.
tw
Tony - I _did_ say the link was at the bottom, didn't I. ;)
Seth - I hope you know - for all the harping I do on validation, your code was the most 'tight' I've seen yet.
No one said it yet, so I wanted to add that the second code block appears to be missing.
-----
....Returning to blackjack.cfm, I see:
[no code block]
From what I can tell, ....
-----
Did I read that wrong?
Nice work, Seth. Your app works very nicely. I does seem pretty complex, though. I consider myself a Coldfusion intermediate, but I dont think I could have put that together. Really though, I do like it.
Oops, sorry, this was the line:
<cfinclude template = "BlackjackCFC_content/#session.blackjackGame.getContentPanelName()#">
Heh, I forgot my html would be escaped.
The 2 entries so far have one thign in common...its 'Stand' not 'Stay'
Not sure if anyone else noticed this. One hand one, I bet $99.23, which I lost, leaving me with $0.77.
On teh next hand, when I tried betting $0.77, it would not allow me to. Initially I received an error stating that I only had $0.77 and could not bet more than that. I kept submitting teh form, but the other submissions did not generate an error, nor did they allow me to play.
I decresed teh bet to $0.76, same issue. Decreased to $0.75 and it allowed me to play the hand.
This continued until I had $0.01 left, and I am unable to play a hand betting that amount.
That's my fault. My specs said 'stay' instead of 'stand.'
Scott said:
> Not sure if anyone else noticed this. One hand
> one, I bet $99.23, which I lost, leaving me with
> $0.77.
>
> On teh next hand, when I tried betting $0.77, it
> would not allow me to.
Something similar happened to me on two different occasions, but I couldn't reproduce it consistently. I'm pretty sure its a rounding issue somewhere, and its probably something really stupid.
Had this been a "real" app I would have done some real troubleshooting but this thing took a lot longer than I had counted on, and I just needed to get it finished. I found that I couldn't concentrate on my course work -OR- my day job while working on this, all I could think about was my Blackjack app. I was kinda hoping no one would notice this "feature" :)
Another Scott wrote:
> Nice work, Seth. Your app works very nicely. I
> does seem pretty complex, though. I consider
> myself a Coldfusion intermediate, but I dont
> think I could have put that together. Really
> though, I do like it.
Thanks for the comments! I'm finally working on finishing my compsci degree, and that has meant learning object oriented programming via Java. This was the second CFC based app I've ever done and I think my Java background is pretty evident. (In Java, nearly everything is an object, so it leads to designs with lots of small classes like Hand, Deck, Card, Rank, etc)
However, since Java apps are usually stateful, synchronous things and web based apps are not, some of my design choices didn't work out very well when implemented as CFCs. But that's cool, since my goal was just to get practice with a CFC based, object oriented CF app.
Thanks again for the comments!
A couple of notes (although I haven't looked at the source code yet):
* Never, never, never, ever write code that relies on the CGI.HTTP_REFERER being present. First there are issues that creep up when running the code on different servers (which may have different CGI values.) The biggest problem is that more and more client-side applications are starting to block the HTTP_REFERER from being sent. I know Norton Internet Security blocks this by default. It's never been a very reliable value to track, but you definitely don't want to write code that expects it to be there. You're run into problem.
One way to recreate this value is to manually track the user's last visited page. Just make sure that you don't overwrite the value for page refreshes. You could do this in the session scope. This will give you a controlled "http_referer" value that is guarenteed to be available. Plus, this value can't be spoofed.
* Second, and this relates to the game itself. I was dealt 17 (7H + KS) and the dealer had 12 (9C + 3H.) The dealer then hit for the 5D giving the dealer 17 as well. However, the game told me the dealer had the best hand and I lost the bet instead of it being a push.
* My last comment was from a UI standpoint. When the dealer hits multiple times in a row, there's no visible pause between each hit. Part of the joy of playing BJ is the suspense. I would have prefer for the computer to pause between each "hit".
PS - Seth, don't take my first point too personally. I know many people will use these exercises as way to learn and I just wanted other developers to know how bad it is to rely on this variable.
I've just seen too many people use the CGI.HTTP_REFERER as a way to help add "security" to their applications--when in actuality this doesn't add any security to the application at all. Heck, even I've been guilty of using this in the past.
In general, I try to avoid using values from the CGI scope altogether. I think the only thing I ever grap is the REMOTE_ADDR and HTTP_USER_AGENT values for logging purposes.
great work seth, the code is tight, the code is sleek, and im certainly impressed :)
ray. i get your point, that you said at the bottom. but just the word download, with no qualifier to tell me what im downloading, seems bad to me. what am i downloading? i have no idea with that just saying "Download"
i do not want to harp on it, so ill stop here, and please be nice when looking at my code :)
tw
Dan, thanks for the comments about the referrer value. I didn't realize some client software suites were preventing this from being sent. I'll definately treat CGI vars with more care in the future.
As for there not being a "push" if the players tie, the contest rules specifically stated that if there is a tie that the dealer should win. Just followin' the rules, man :)
in that spot, the push vs. dealer win situation, i went on the rules, and not ray's rules, as i thought he might have just mistyped that one :)
who knows. its all sooo very objective in this contest, i think the best part is, it will be a good learning experience for all who did it.
tw
Just to show how ignorant I am - what would 'push' do? No one wins?
Hey Seth, don't fret too much the 'rounding' issue. As I said, I think your app, out of all the entries so far, is the most 'hack proof'. As I said, no one does this perfectly.
Tony: " do not want to harp on it, so ill stop here, and please be nice when looking at my code :)"
Dude, you are SO going to lose now. ;)
A 'push' simply means no one wins.
You can choose to keep the bet on the table, increase the bet, decrease the bet, or walk away.
You are not required to make the same bet, or bet again at all, after a 'push'
push is no one wins. money stays on the table.
its a nice relief when it happens at the tables in vegas :)
and you've got 20 and are thinking, LOCK, and he bangs two face cards...
Full Disclosure: I wrote the previous contest entry.
Here is my take on this entry.
Issues:
The biggest issue I see with this version of Blackjack is an issue I
see in many applications: it overly complicates a very simple application.
The application has 3,528 lines of code ( including whitespace and
comments. Even cutting that in half to account for the comments comes
to 1,764) and 30 files ( excluding images). This compares to 568 lines
of code (including comments and css) and 5 files in the first example.
This can quickly become a maintenance nightmare.
index.cfm does nothing but a cflocation this is both pointless and an improper usage for CFLOCATION. Pete Freitag posted an article regarding the improper use of CFLOCATION (http://www.petefreitag.com/... .
Bugs:
Dead End: If you have less than a dollar, bet it all and lose. You are left with no money but you can still click "play again". If you do you are at a dead end. You can't place a bet and you can't do anything else.
Occassionly you can't place a bet for the amount you have left as noted in an earlier comment.
The app will not display an error message if there are two consecutive errors.
Oddity:
If you run the app with debugging turned on and you throw an error (ie: enter a NAN value into a bet) the app does not display, only the debugging info appears.
Minor annoyance:
If I start a new game, I should not need to re-enter my name.
Sorry, I meant to include that overall the app works very well and is a solid entry!
Good Job Seth.
Gus
Gus made a lot of good comments about my design. I think there is a lot to be learned when developers discuss the *different* ways that they do things, as well as WHY they do them that way. I welcome any and all comments on my design and the comments I've made after the fact... if you have something to add please do!
Just like Gus made design choices to keep his app cluster-safe, I made choices oriented around a different goal. If you download my app and look at the README file, it explains that I was trying to create a very portable and easily distributed application. Instead of writing a Blackjack app that met the specific needs of this contest I wanted something that someone could take and easily plug into an existing website.
> The biggest issue I see with this version of
> Blackjack is an issue I see in many applications:
> it overly complicates a very simple application.
> ...
> This can quickly become a maintenance nightmare.
One of the reasons my core files have so much code is that they support a much more complex game. The core Blackjack.cfc file supports multiple human players, multiple card decks in the shoe, and could be easily extended to provide support for doubling-down, splitting hands, etc.
The idea was that the Blackjack.cfc file would simply track game state without imposing any logic upon it, and that the BlackjackController.cfc file would implement the rules of a specific game. In this case my controller supports a single human player and no special features, someone else could write another controller that supported multiple players.
I totally agree this is overboard according to the contest specs, but it made the app more interesting to build.
> index.cfm does nothing but a cflocation this is both
> pointless and an improper usage for CFLOCATION.
Again, I wanted this to be as "plug and play" as possible, so I made the default filename "blackjack.cfm" so that it could (hopefully) be copied into an existing directory without overwriting any existing files. I added the CFLOCATION in index.cfm just to simply testing and use in the contest.
> Oddity:
> If you run the app with debugging turned on and you
> throw ... the app does not display
Weird. I'll have to try that myself.
> Minor annoyance:
> If I start a new game, I should not need to
> re-enter my name.
I agree, it annoyed me too :)
While everyone in the comments seemed to be able to play the game quite successfully, I had many issues. On every page I went to, I either had to hit the submit button twice or hit the refresh button after submitting. It was very strange. I then somehow got into a mode when I submitted a bet that a CF error would be thrown stating "The game is not ready to accept bets from a human player!", yet my name was showing at the bottom with $80 remaining. I haven't looked at the code yet, but my guess is that is has something to do with how the state is being tracked and related to the fact that I am going through a proxy server. I'm not exactly sure why that would matter (it didn't on the first game), but I figured it may matter since everyone else was able to run the game.