Welcome to the third entry in the ColdFusion contest that I'm running. For those who are wondering, there were seven entries, and my goal is to wrap this up by next Friday, otherwise I won't be able to name a winner till after MAX. If you haven't looked at the previous entries, here are the links: Entry 1, Entry 2.
The third entry may be viewed here. As before, I suggest you go play with it a bit before reading the rest of the entry. The code for the entry is listed below:
<cfsetting enablecfoutputonly="Yes" />
<!---
HiLo
We likes games...
To do:
- Handle creative users (guessing number outside of new hi-lo range, guessing same number twice, etc.)
- Tighten up code to conform to xhtml transitional and css standards.
- More appealing display.
- Mask goal value when saved in URL.
- etc.
--->
<cfset temp="#StructInsert(Request,'s_CurrentTemplateFile',GetFileFromPath(GetCurrentTemplatePath()),True)#" />
<cfset temp="#StructInsert(Request,'i_GoalMin',1,True)#" />
<cfset temp="#StructInsert(Request,'i_GoalMax',100,True)#" />
<cfparam name="URL.i_Goal"
default="" />
<cfparam name="Form.i_Goal"
default="#URL.i_Goal#" />
<cfset temp="#StructInsert(Request,'i_Goal',Form.i_Goal,True)#" />
<cfset temp="#StructInsert(Request,'i_GoalFormValue',Request.i_Goal,True)#" />
<cfparam name="URL.lsti_GuessHistory"
default="" />
<cfparam name="Form.lsti_GuessHistory"
default="#URL.lsti_GuessHistory#" />
<cfset temp="#StructInsert(Request,'lsti_GuessHistory',Form.lsti_GuessHistory,True)#" />
<cfparam name="URL.i_GuessLast"
default="" />
<cfparam name="Form.i_GuessLast"
default="#URL.i_GuessLast#" />
<cfset temp = "#StructInsert(Request,'i_GuessLast',Form.i_GuessLast,True)#" />
<cfset temp = "#StructInsert(Request,'s_GoalDisplay','???',True)#" />
<cfset temp = "#StructInsert(Request,'str_Comp',StructNew(),True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'lsti_GuessHistory','',True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'lsti_GuessHistorySorted','',True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'i_GuessCount',0,True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'idx_Hi',0,True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'idx_Lo',1,True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'s_GoalDisplay','???',True)#" />
<cfswitch expression="#Len(Request.i_Goal)#">
<cfcase value="0">
<cfset temp = "#StructInsert(Request,'s_HTMLTitle','New Game',True)#" />
<cfset temp = "#Randomize(DayOfYear(Now()) + Hour(Now()) + Minute(Now()) + Second(Now()))#" />
<cfset temp = "#StructInsert(Request,'i_Goal',RandRange(Val(Request.i_GoalMin),Val(Request.i_GoalMax)),True)#" />
<cfset temp = "#StructInsert(Request,'i_GoalFormValue',Request.i_Goal,True)#" />
<cfset temp = "#StructInsert(Request,'s_Message','Starting a new game. Enter a number below to begin.',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormLegend','Let#Chr(39)#s get started...',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormPrompt','Enter your first guess:',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormButton','Click here to submit',True)#" />
<cfset temp = "#StructInsert(Request,'lsti_GuessHistory','',True)#" />
</cfcase>
<cfdefaultcase>
<cfswitch expression="#((IsNumeric(Request.i_GuessLast)) AND
(Request.i_GuessLast GE Request.i_GoalMin) AND
(Request.i_GuessLast LE Request.i_GoalMax))#">
<cfcase value="True">
<cfset temp = "#StructInsert(Request,'lsti_GuessHistory',ListAppend(Request.lsti_GuessHistory,Request.i_GuessLast,','),True)#" />
<cfswitch expression="#CompareNoCase(Request.i_GuessLast,Request.i_Goal)#">
<cfcase value="0">
<cfset temp = "#StructInsert(Request,'s_HTMLTitle','You win!',True)#" />
<cfset temp = "#StructUpdate(Request,'s_GoalDisplay',Request.i_Goal)#" />
<cfset temp = "#StructInsert(Request,'i_GoalFormValue','',True)#" />
<cfset temp = "#StructInsert(Request,'s_Message','Congratulations! Your guess of #Val(Request.i_GuessLast)# was correct!',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormLegend','Start a new game?',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormPrompt','',True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'i_GuessHi',Request.i_GoalMax,True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'i_GuessLo',Request.i_GoalMin,True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'i_GuessLast',Val((Request.str_Comp.i_GuessLo + Request.str_Comp.i_GuessHi + RandRange(0,1))\2),True)#" />
<cfloop condition="(Request.str_Comp.i_GuessLast NEQ Request.i_Goal)">
<cfset temp ="#StructUpdate(Request.str_Comp,'lsti_GuessHistory',ListAppend(Request.str_Comp.lsti_GuessHistory,Request.str_Comp.i_GuessLast,','))#" />
<cfswitch expression="#(Request.str_Comp.i_GuessLast GT Request.i_Goal)#">
<cfcase value="True">
<cfset temp = "#StructUpdate(Request.str_Comp,'i_GuessHi',Request.str_Comp.i_GuessLast - 1)#" />
</cfcase>
<cfdefaultcase>
<cfswitch expression="#(Request.str_Comp.i_GuessLast LT Request.i_Goal)#">
<cfcase value="True">
<cfset temp = "#StructUpdate(Request.str_Comp,'i_GuessLo',Request.str_Comp.i_GuessLast + 1)#" />
</cfcase>
</cfswitch>
</cfdefaultcase>
</cfswitch>
<cfset temp = "#StructInsert(Request.str_Comp,'i_GuessLast',Val((Request.str_Comp.i_GuessLo + Request.str_Comp.i_GuessHi + RandRange(0,1))\2),True)#" />
<cfswitch expression="#(ListFindNoCase(Request.str_Comp.lsti_GuessHistory,Request.str_Comp.i_GuessLast,','))#">
<cfcase value="True">
<cfswitch expression="#(Request.str_Comp.i_GuessLast LT Request.str_Comp.i_GuessHi)#">
<cfcase value="True">
<cfset temp = "#StructInsert(Request.str_Comp,'i_GuessLast',Val(Request.str_Comp.i_GuessLast + 1),True)#" />
</cfcase>
<cfdefaultcase>
<cfset temp = "#StructInsert(Request.str_Comp,'i_GuessLast',Val(Request.str_Comp.i_GuessLast - 1),True)#" />
</cfdefaultcase>
</cfswitch>
</cfcase>
</cfswitch>
</cfloop>
<cfset temp = "#StructInsert(Request.str_Comp,'lsti_GuessHistorySorted',
ListSort(Request.str_Comp.lsti_GuessHistory,'Numeric','Desc',','),True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'i_GuessCount',ListLen(Request.str_Comp.lsti_GuessHistory,','),True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'idx_Hi',ListFindNoCase(Request.str_Comp.lsti_GuessHistorySorted,Request.str_Comp.i_GuessHi + 1,','),True)#" />
<cfset temp = "#StructInsert(Request.str_Comp,'idx_Lo',Request.str_Comp.idx_Hi + 1,True)#" />
</cfcase>
<cfdefaultcase>
<cfset temp = "#StructInsert(Request,'s_HTMLTitle','Guess Again',True)#" />
<cfset temp = "#StructInsert(Request,'s_Message','You are almost there...',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormLegend','Guess Again',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormPrompt','Enter your next guess:',True)#" />
</cfdefaultcase>
</cfswitch>
</cfcase>
<cfdefaultcase>
<cfset temp = "#StructInsert(Request,'s_HTMLTitle','Oops',True)#" />
<cfset temp = "#StructInsert(Request,'s_Message','Resuming from saved game or you entered an invalid number. Please enter a number between #Val(Request.i_GoalMin)# and #Val(Request.i_GoalMax)# to continue.',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormLegend','Try again',True)#" />
<cfset temp = "#StructInsert(Request,'s_FormPrompt','Enter your next guess:',True)#" />
</cfdefaultcase>
</cfswitch>
</cfdefaultcase>
</cfswitch>
<cfset temp="#StructInsert(Request,'lsti_GuessHistorySorted',ListSort(Request.lsti_GuessHistory,'Numeric','Desc',','),True)#" />
<cfset temp="#StructInsert(Request,'i_GuessCount',ListLen(Request.lsti_GuessHistorySorted,','),True)#" />
<cfset temp="#StructInsert(Request,'idx_Hi',Request.i_GuessCount,True)#" />
<cfset temp="#StructInsert(Request,'idx_Lo',Request.idx_Hi+1,True)#" />
<cfloop index="Request.idx" from="1" to="#Request.i_GuessCount#">
<cfswitch expression="#(ListGetAt(Request.lsti_GuessHistorySorted,Request.idx,',') EQ Request.i_Goal)#">
<cfcase value="True">
<cfset temp="#StructUpdate(Request,'idx_Hi',Request.idx - 1)#" />
<cfset temp="#StructUpdate(Request,'idx_Lo',Request.idx + 1)#" />
<cfbreak />
</cfcase>
<cfdefaultcase>
<cfswitch expression="#(ListGetAt(Request.lsti_GuessHistorySorted,Request.idx,',') LT Request.i_Goal)#">
<cfcase value="True">
<cfset temp="#StructUpdate(Request,'idx_Hi',Request.idx - 1)#" />
<cfset temp="#StructUpdate(Request,'idx_Lo',Request.idx)#" />
<cfbreak />
</cfcase>
</cfswitch>
</cfdefaultcase>
</cfswitch>
</cfloop>
<cfsetting enablecfoutputonly="No" />
<html>
<head>
<cfoutput>
<title>Hi-Lo's Helper: #Request.s_HTMLTitle#</title>
</cfoutput>
<basefont face="Trebuchet MS" color="Navy" />
<style>
body {
font-family: "Trebuchet MS";
color: Black;
}
h1 {
color: Navy;
font-size: 140%;
text-align: center;
margin: 0 0 0 0;
}
p.Hi {
text-align:right;
color:Navy;
font-size:300%;
}
p.Lo {
text-align:left;
color:Navy;
font-size:300%;
}
small {
font-size:84%;
}
</style>
</head>
<body onload="document.forms[0].i_GuessLast.focus()">
<cfoutput>
<table border="0"
cellpadding="2"
cellspacing="0"
width="600">
<tr>
<td colspan="9" valign="middle">
<h1 align="center"><sup>Hi</sup>-<sub>Lo</sub><small>'s Helper</small></h1>
</td>
</tr>
</table>
<table border="0" cellpadding="2" cellspacing="0" width="600">
<tr>
<td colspan="3"
width="30%"
valign="bottom"
bgcolor="silver">
<p align="center"><strong>YOU</strong></p>
</td>
<td rowspan="5"
width="4"
bgcolor="silver">
<p> </p>
</td>
<td>
<p> </p>
</td>
<td rowspan="5"
width="4"
bgcolor="silver">
<p> </p>
</td>
<td colspan="3"
width="30%"
valign="bottom"
bgcolor="silver">
<p align="center"><strong>COMPUTER</strong></p>
</td>
</tr>
<tr>
<td valign="bottom">
<p class="Hi">Hi</p>
</td>
<td valign="bottom">
<p align="center" style="text-align:center;font-size: 140%;"><span style="color:Silver;">#Request.i_GoalMax#</span><br />
<strong>
<cfloop index="Request.idx"
from="1"
to="#Request.idx_Hi#">
<br />#ListGetAt(Request.lsti_GuessHistorySorted,Request.idx,',')#
</cfloop>
</strong></p>
</td>
<td>
<p> </p>
</td>
<td rowspan="3"
valign="top">
<p>#HTMLEditFormat(Request.s_Message)#</p>
<form method="post"
action="#Request.s_CurrentTemplateFile#">
<fieldset>
<legend>#HTMLEditFormat(Request.s_FormLegend)#</legend>
<p align="center">#HTMLEditFormat(Request.s_FormPrompt)# <input type="text" name="i_GuessLast" value="" size="3" maxlength="3" /><br />
<input type="submit" name="btn_Guess" value="Click here to submit" /><input type="hidden" name="i_Goal" value="#Request.i_GoalFormValue#" /><input type="hidden" name="lsti_GuessHistory" value="#Request.lsti_GuessHistorySorted#" /></p>
</fieldset>
</form>
</td>
<td valign="bottom">
<p class="Hi">Hi</p>
</td>
<td valign="bottom">
<p align="center"
style="text-align:center;font-size: 140%;"><span style="color:Silver;">#Request.i_GoalMax#</span><br />
<strong>
<cfloop index="Request.idx" from="1" to="#Request.str_Comp.idx_Hi#">
<br />#ListGetAt(Request.str_Comp.lsti_GuessHistorySorted,Request.idx,',')#
</cfloop>
</strong></p>
</td>
<td>
<p> </p>
</td>
</tr>
<tr>
<td colspan="3">
<p align="center"
style="text-align:center;font-size:200%;border:outset thin silver;margin:0 0 0 0"> <strong>#HTMLEditFormat(Request.s_GoalDisplay)#</strong></p>
</td>
<td colspan="3">
<p align="center"
style="text-align:center;font-size:200%;border:outset thin silver;margin:0 0 0 0"> <strong>#HTMLEditFormat(Request.s_GoalDisplay)#</strong></p>
</td>
</tr>
<tr>
<td>
<p> </p>
</td>
<td valign="top">
<p align="center"
style="text-align:center;font-size: 140%;"><strong>
<cfloop index="Request.idx"
from="#Request.idx_Lo#"
to="#Request.i_GuessCount#">
#ListGetAt(Request.lsti_GuessHistorySorted,Request.idx,',')#<br />
</cfloop>
</strong><br />
<span style="color:Silver;">#Request.i_GoalMin#</span></p>
</td>
<td valign="top">
<p class="Lo">Lo</p>
</td>
<td>
<p> </p>
</td>
<td valign="top">
<p align="center"
style="text-align:center;font-size: 140%;"><strong>
<cfloop index="Request.idx"
from="#Request.str_Comp.idx_Lo#"
to="#Request.str_Comp.i_GuessCount#">
#ListGetAt(Request.str_Comp.lsti_GuessHistorySorted,Request.idx,',')#<br />
</cfloop>
</strong><br />
<span style="color:Silver;">#Request.i_GoalMin#</span></p>
</td>
<td valign="top">
<p class="Lo">Lo</p>
</td>
</tr>
<tr>
<td colspan="3"
width="30%"
bgcolor="silver">
<p align="center"><strong><small>COUNT:</small> #Request.i_GuessCount#</strong></p>
</td>
<td>
<p title="Add this link to your Favorites to save game"><small>Save game as:<br />
<!--- line below broken up a bit to display better on blog --->
<a href = "#Request.s_CurrentTemplateFile#?
i_goal=#Request.i_Goal#&lsti_GuessHistory=
#Request.lsti_GuessHistorySorted#">Hi-Lo -- Saved Game #DateFormat(Now(),'mmm dd')# #TimeFormat(Now(),'h.m')# #LCase(TimeFormat(Now(),'TT'))#</a></small></p>
</td>
<td colspan="3"
width="30%"
bgcolor="silver">
<p align="center"><strong><small>COUNT:</small> #Request.str_Comp.i_GuessCount#</strong></p>
</td>
</tr>
</table>
</cfoutput>
</body>
</html>
So, let me start with my general observations before digging into the code. While I didn't want to make this a "visual" contest, I do like the design of this one. The only thing that confused me is the "Computer" box on the right. It seems to reflect the computer's attempt to guess the number at the same time you do - but you only see the computer's guesses at the end. That's why I'm a bit confused. (I'm thinking the author may post a comment and clear this all up.) I think that is kind of neat as it gives you a competitor, but it is kind of odd that you only see it at the end. Either way, he made the computer "imperfect" as well, which is good game design. Another note - like the first entry, the author reversed my original intent of making the computer guess. Again though, that's fine. (For the next contest I'll try to be more clear in the specs.) Now let's dig into the code a bit.
Like the other entries, this one doesn't do proper validation of the variables. The author does know this and notes it in the header. I hate to harp on it - but my readers know this is one of the things that I like to be anal about. I always bring it up because far too many public sites don't do a good job of it.
There are two things in particular I want to point out about the code. I'm not calling these mistakes, but differences in style. This line is repeated (with variations in the values of course) throughout the template:
<cfset temp="#StructInsert(Request,'s_CurrentTemplateFile',GetFileFromPath(GetCurrentTemplatePath()),True)#" />
The first thing I'd point out is that for functions that return a "throw away" value, or a value you simply don't need, as above, you can rewrite the code like so:
<cfset StructInsert(Request,'s_CurrentTemplateFile',GetFileFromPath(GetCurrentTemplatePath()),True) />
This is a bit less typing, and in my opinion, a bit cleaner. Secondly, I've never been a fan of structInsert. It isn't a bad function per se, I just don't like the extra typing. I've never heard a good reason to use it. I have heard people say they use it to insert dynamic keys into a structure, but bracket notation works fine that as well:
<cfset key = "name">
<cfset s = structNew()>
<cfset s[key] = "Jacob">
Again, this is just personal preference on my part.
So, my last comment, and this is definitely in the realm of being picky - I don't care for the use of cfswitch. I find it makes it a bit hard to follow the logic flow. How do others feel?
p.s. In general, the comments I've seen on my blog postings about this contest have been very fair and polite. I ask that people keep this in mind. These are beginners, and we all make mistakes. So please be gentle.
Archived Comments
Editors Note: I had to 'break' the code a bit by adding extra line breaks. This was due to stupid IE not supporting max-width and max-height propery. I really, really hate IE's CSS support.
The one place that I have found a need for structInsert() is when both the structure variable as well as the key are dynamic. But yeah, most times it doesnt make sense to use it.
And personally, I like cfswitch for any case where I have multiple if statements using the same variable.
I agree with Ryan on the use of CFSWITCH... if I have a bit of conditional logic that has more than 3 possibilities I definitely prefer CFSWITCH over a bunch of CFELSEIF statments; plus CFSWITCH should evaluate faster than multiple CFELSEIFs
I just went and played this game and it appears there is a small bug in the displayed results.
It took me 6 guesses to get 88 as the magic number:
50,75,87,95,90,88
It took the computer 6 guesses also
51,76,82,86,89,88
However, under the computer answer it says it took only a COUNT: 5
supposedly this URL will show the final results:
http://ray.camdenfamily.com...
I'm all for cfswitch.
It really depends on the condition you are evaluating, and how many possibilities there are. I personally find it easier to read, when used properly.
For example (can we use code in the comments, I'm about to find out):
<code>
<cfif MyVar is "1">
do something
<cfelseif MyVar is "2">
Do something else
<Cfelseif MyVar is "3">
Do yet another something
etc.. etc.. etc..
</cfif>
</code>
That would be easier to read as a cfswitch, especially with lots of nested cfifs.
A "bug", or UI flaw, is when you end a game the field to guess a number and start a new game is present, but whatever value you enter is ignored when you submit it. Only after the page load can you actually start guessing again.
Either you need to be able to instantly start a game, or remove the text input.
I really like the idea of the competitor. Since he programmed the logic of the computer playing both sides, it would be cool if when you started playing you selected which game you wanted to play, you could either make the computer guess your number, or you guess the computer's number. Rather than the computer and you both guessing the computer's number.
Other UI flaw:
The "You are almost there..." text. Two things: 1) I thought this was an actual clue the first game I played. I guessed 50 and it told me I was almost there... so I thought the end number was close to 50, however it was actually 100. Doh! 2) Clues would ruin the game IMHO.
I generally use cfswitch inside a cfscript block if I need to evaluate set a bunch of parameters based on another variable's value. For anything that is diferentiating between performing an action of some sort I use cfif. I find cfswitch cleaner, especially in cfscript, for doing lots of comparisons.
Also wanted to say thanks for doing this contest and evaluations, I didn't enter because I don't consider myself a novice, but I'm tempted to write my own solution just to compare against how others have done it. It's fascinating to see people's interpretations of your task and the variety of ways to solve the problem.
I think it illustrates an important point to us all. If you are really stuck on something, it often times can be best to start over and try a different approach since there are so many ways to solve a problem.
My .02 - I like CFSWITCH for anything more than a few conditions.
Just so it's clear - I do like cfswitch as well, but felt his use of it here wasn't great.
ugh, your right. his use of CFSWITCH here is odd. only two conditions being evaluated in the first "parent" and then another cfswitch in the defaultcase of the first.
I hadn't really looked at the code previously.
This is great. Thanks for all of the feedback.
The comment about the count bug (Posted By Bill / Posted At 10/7/05 9:08 AM) is a great catch. I ran across this myself playing the game over the weekend. Doh! Let's just chalk it up to stacking the odds in the house's favor;-)
The UI flaw with the text input box (Posted By ErikG / Posted At 10/7/05 10:36 AM) needs to be fixed. Just plain ugly.
The computer can't show its guesses each time the player guesses because this would aid the player. So it actually just does all of it's guessing once the player finds the match. Then the counts are compared. The interface should explain this.
The computer doesn't actually just randomly guess. It does use a random solution to an interesting situation that crops up. If there are an odd number of integers left in the valid set, then picking the middle number is easy. If there are an even number of integers, then how do you pick the middle number. There is none, or two, depending on your point-of-view. You could always default to the lowest, but this does seem too rigid. So it randomly chooses to go high or low. With a big enough statistical set it probably makes no diff, but it's just more fun. (Also in the design of game where the user gets to set the goal number, it reduces the ability of the user to outsmart the computer. e.g A number like 51 could take 7 steps to reach w/ a defaulting to low algorithm, or just 1 step in a defaulting to high.)
The use of CFSWITCH and CFSET (w/ StructInsert and a throw way result) is an interesting habit. For a while I was playing around with a strongly validating editor that wanted everything in a tag written as attribute_name=quoted_value. I could 'teach' the editor to recognize a few new attribute names for the CFSET tag (e.g. 'temp'). Actually even though I've since switched to CF Studio, all my code still tries to look like well-formed XML. I'm hoping one day it will pay-off, but I agree that it is much less clear to read. If only I could get Macromedia to create two new cfset attributes such as varname= and varvalue=.
Thanks again. (If I can squeeze in some time I may try a version 2 where the user and computer truly go head-to-head.)