Browse in : |
All
> Journal Columns
> Code Critique
All > Journals > CVu > 141 Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: Student Code Critique
Author: Administrator
Date: 06 February 2002 13:15:49 +00:00 or Wed, 06 February 2002 13:15:49 +00:00
Summary:
Body:
Sponsored by Blackwells Bookshops in collaboration with Addison-Wesley and open to all, experts and novices alike.
When I published the source code I wondered if anyone would notice that it is actually quite unnecessary to count the aces in order to score a Blackjack (also known as Vingt-et-Un) hand. Any hand that contains at least one ace has two possible scores, a minimum and a (minmum+10) because (minimum+20) will always be a bust and so cannot be an acceptable score. All we need is a Boolean flag that marks a hand as with or without an ace. And much to my delight one of you (Tony Houghton) did so.
A feature of this competition was the number of people who just made the deadline (and one missed it but...). One contributor wondered if I could set longer deadlines. Actually I doubt that would improve things even if timing allowed it (note that this column has to be submitted to the editor in time for him to cut the printed version as necessary, and after the next issue he will have to do that early enough to allow the production editor to do her job)
Another thing that makes it hard to choose a winner and hard to decide what to cut out of the printed copy is the wide variety of approaches. Do not stop, that is part of what this is about. There is no single right way.
Because code along with the article can be helpful but can take a lot of space I have taken liberties with its format. Because I am too lazy to create too complete versions, on for print and one for the web both will have condensed code format.
Now none of the entries were pristine examples of coding and several include dubious practices. However the purpose of this column is to encourage you to read source code critically. I will be very disappointed if there are not a number of letters to the editor highlighting over-sights and taking issue with some of the code in what follows. OK so you do not get a prize for it, but you do get to become a better programmer. That should be worth a lot. You also help others to become better programmers and that should give you a warm glow.
From James Holland <jamie_holland@lineone.net>
To start with, it is best to have the rules of how to find the value of a blackjack hand spelt out in English. In other words we need a specification. We can then decide whether the program is doing what is should. After a bit of hunting I find that the rules for adding the cards of a blackjack hand are as follows.
The score of a hand is the sum of the values of the cards. An ace counts as either 11 or one, whichever results in a better score for the hand. Any face card counts as ten, and any other card counts as its face value. Any hand with a total greater than 21 "busts" and cannot win.
Well let's have a quick scan through the program to see if we can understand how it works and to pick out any immediate problems. After a welcome message the program enters a loop that will end only when the user doesn't want to have another go. So far, so good. The first line of the loop is a call to a function named number. The function takes a single parameter of type int as can be gleaned from the prototype. The int value is passed by a variable by the name of num_cards. This is where things aren't quite right. The bad news is that num_cards has not been initialized to any particular value. Its value could be anything (within the range of an int). The good news is that when this unknown value is passed to the function number, the function does not take any notice of it anyhow. Of course this is not really good news because it shows that the student has got somewhat muddled in defining the function, but at least it will not prevent the program from working.
What the student should have done is to declare the function number with no parameters and to have defined num_cards as a local variable within the number function. With this out of the way we will press on.
The function number returns with the number of cards as entered by the user. This number must be within the range two to five inclusive. If the number entered by the user is not within this range the function will insist that the user enters another number.
The main function now repeatedly calls card_val, once for each card in the hand. The sequence number of each card is passed to the function by means of an int parameter. The card_val function invites the user to enter a card. It then calculates and returns the value of the card. Things get a little confusing here because the student has declared a variable within the card_val function with the same name as the function, namely card_val. Another name, such as value_of_card would have been a better choice.
While we are on the subject of the naming of variables, there are one or two things that should be born in mind. The name of the variable should reflect the use put to the variable. The name should be clear and easy to read. Non-standard abbreviations should be avoided. There is no reason to call local variables card_val, for example, when the name card_value does not take a great deal more typing and is explicit. I actually agree with the general way the student constructs variable and function names, i.e. a short phrase, all in lower case and separated with underscores. In every day life most text is written in lower case and separated with spaces and this, presumably, is what most people are used to.
There is a slight complication if the user chooses an ace for a card. The number of aces entered has to be counted for use later on. This is where the student comes unstuck.
The student has declared a variable within the card_val function with the name of aces. Now, there is already a variable with the name aces declared in the main function. This is bound to cause confusion. The aces variable that was declared in the card_val function is created and initialised to zero each time the function is called. It is a reference to this variable (that has the value zero) that is passed to ace_count (or as the student incorrectly types, ace_card). The aces variable (the one declared in card_val) is incremented by one by the ace_count function. Unfortunately, when the card_val function finally returns, the aces variable no longer exists. What should be used is the aces variable that was declared in the main function. There is no need to declare the aces variable in card_val at all.
The only problem now is that aces declared in main is not directly accessible from within card_val. One way to get out of that problem is to add another parameter to card_val that passes the main aces variable by reference. The call to card_val would have to be modified as well, of course.
Having summed the value of all the cards within the hand, the main function now attempts to display the value of the hand. So far the program has assumed that all aces are worth 11 points each. This is the correct thing to do as long as sum does not exceed a value of 21. If the hand does exceed 21, then, for each ace in the hand 10 points can be subtracted until the value of the hand becomes 21 or less. This is where the hand_value function comes in. I think the student was getting a bit fed up at this stage as there are a couple of simple errors in as many lines here. The definition of the function is called new_value whereas the prototype and the call refer to a function called hand_value. Presumably, the definition should be written as hand_value. The hand_value function contains a while loop that refers to ace_counts, I think what the student really means is aces.
Now that all these little problems have been put right, does the program work? Well, not quite. The number of aces is not set to zero when the user wishes to choose another hand. The student has remembered to zero the total value of the hand, but not the number of aces. After rectifying this omission, we now have a working program (I think).
There are, however, one or two things that could be done to improve the general structure of the program. There is a rule of thumb that states that variables should be declared as late as possible, i.e., as near to the point of use as possible. This helps when maintaining the program; you don't have to look so far to find the declaration of variables. It also keeps the scope of variables limited to where they are used. There is no point in spreading their influence too far a field. num_cards, total and aces and be declared and initialised within the do loop of main(), for example.
Code layout is always a matter of personal taste, but I would like to see a space either side of an operator, such as value = 10. I also prefer opening and closing braces to be vertically aligned. I simply do not understand the idea of having the opening brace and the end of controlling line. It makes working out what code constitutes a block much more difficult. I know many experts use this asymmetric approach, so if anyone can explain to me how it makes the code more readable I would like to hear about it. (I can see that the asymmetric approach is of some use when printing code as it uses one less line per pair of braces on the written page. Exactly, so you should not judge layout on the basis of what is printed. Layout should be a feature of function not religion. Francis)
The code that deals with displaying the total hand could be tidied up a bit. If new_hand was called regardless of the calculated total, then the system of nested if statements could be simplified and now becomes as shown below.
total = hand_value(total, aces); if (total <= 21) cout << "\nYour hand is: " << total << "."; else cout << "\nYou busted with " << total;
It does not matter if hand_value is called with total less than or equal to 21, as it will return immediately with the same value as total.
It is useful to know that, within a switch statement, a case can have more than one label. In this way, duplicated code can be avoided, as shown below.
case 't': case 'j': case 'q': case 'k': value = 10; break;
Here is my version.
#include<cctype> #include<iostream> using namespace std; int card_val(int card_no, int & aces); int number(); int ace_count(int & aces); int hand_value(int total, int aces); int main(){ cout << "\nWelcome to Blackjack Counter.\n"; char again; do { int num_cards = number(); int total = 0; int aces = 0; for (int card_no = 1; card_no <= num_cards; card_no++) total += card_val(card_no, aces); total = hand_value(total, aces); if (total <= 21) cout<< "Your hand is: " << total; else cout<< "You busted with " << total; cout << endl; cout << "Do you wish to continue?\n"; cout << "Enter 'Y' (Yes) or 'N' (No): "; cin >> again; } while ((again == 'Y') || (again == 'y')); return 0; } int number(){ int num_cards; do { cout << "How many cards do you have? "; cin >> num_cards; if ((num_cards <= 1) || (num_cards >= 6)) cout << "You must have between 2 and 5 cards (inclusive)."; } while((num_cards <= 1)||(num_cards >= 6)); return num_cards; } int card_val(int card_no, int & aces){ bool invalid_card; int value; do { invalid_card = false; cout << "Enter value of card number " << card_no << ": "; char card_val; cin >> card_val; switch (tolower(card_val)){ case 'a': value = ace_count(aces); break; case '2': <similar lines snipped> case '9': value = 9; break; case 't': case 'j': case 'q': case 'k': value = 10; break; default: cout << "Not a valid entry. Please try again. \n"; invalid_card = true; } } while (invalid_card); return value; } int ace_count(int & aces){ aces++; return 11; } int hand_value(int total, int aces){ while ((total > 21) && (aces >= 1)){ aces--; total -= 10; } return total; }
From Catriona O'Connell <catriona38@hotmail.com>
There are several problems with the code as published - some of which are so odd it looks as if the code has been deliberately mangled :-)
The function ace_count() is declared and defined, but never referenced. An undeclared and undefined function ace_card() is called from within card_val. I suspect, given the function signatures and results that the call to ace_card() should be replaced with a call to ace_count().
The function hand_value is declared, called by main, but never defined. Again given the signatures and function I suspect that the undeclared function new_value should be renamed hand_value. A more informative name would indicate that it really recalculates the value of the hand. The new_value() function has a problem with the logic in that it refers to ace_count as a variable and not the aces value actually passed as the second argument.
The function number(), takes an unnecessary argument. While it does mean that a temporary value need not be used in number(), it is misleading. The function itself should retrieve the number of cards held by the user, but has insufficient error handling and duplicates the test for valid input. This would have been better coded as per The Harpist's code critique in C Vu 13.6 p14 or equivalently using code based on getInput() from my code in the same article.
Both main() and card_val() could handle requests for character input in a cleaner and safer fashion than they do. Again I refer to the code referenced above for how this might be better done.
The problem with counting aces is rooted in the card_val() function. The count of the number of aces held is a local variable in this function, and while it is updated by the ace_count() function, it is destroyed when card_val() returns. As card_val() is called once per card by main() the value of the variable aces in main() will always be zero. Given the current code structure, it is difficult to find an elegant solution to the problem, short of using a global variable (yeuch!).
In card_val() the large number of case statements can be simplified to three essential cases - numbers, tens and court cards and aces. I note in passing that there is a missing semicolon after the break statement associated with case '9'.
I think that covers the majority of errors in the code. With regard to structure I feel that the intertwining of user input and calculation, and indeed user input from multiple routines is messy. The solution I have proposed probably betrays my prejudice against handling user input. In almost all the code I write, it either never interacts directly with a person or obtains all the information required "up front" and then proceeds to completion.
// Replacement code to work out the value of a // blackjack hand. // Catriona O'Connell 23.11.2001 #include <iostream> using namespace std; enum ifailValues {Success = 0, Error_Too_Few_Cards, Error_Too_Many_Cards, Error_Invalid_Card, Error_Busted}; char *ifailMessages[] = {"Success", "Too Few Cards", "Too Many Cards", "Invalid Card(s)", "Busted"}; bool CheckValidCharacters( const char * testString, const char * validChars); int GetBlackjackHandValue( const char *cards, int *ifail); bool CheckValidCharacters( const char * testString, const char * validChars){ // Check if the first argument string contains // only characters in the second argument // string. Returns true if it is the case, // otherwise returns false. size_t testStringLen; testStringLen = strlen(testString); if(strspn(testString, validChars) != testStringLen) return false; return true; } int GetBlackjackHandValue( const char *cards, int *ifail) { // Returns the value of a blackjack hand. The // value returned is the sum of the value of // the cards held, if all the cards are valid // and the number of cards is within the // presecribed limits. In the event of the // hand being invalid, ifail is set to one of // the enumerated ifailValue values. int cardsLen; int handValue = 0; int aceCount = 0; *ifail = Success; cardsLen = strlen(cards); // Handle simple error cases by setting the // ifail status value and returning a hand // value of zero. if(cardsLen < 2){ *ifail = Error_Too_Few_Cards; return 0; } if(cardsLen > 5) { *ifail = Error_Too_Many_Cards; return 0; } // Check that all the cards held are valid. bool validHand = CheckValidCharacters(cards, "23456789tTjJqQkKaA"); if(!validHand) { *ifail = Error_Invalid_Card; return 0; } // Now that we've checked the pre-conditions, // calculate the value of the cards held. For // numeric cards, we use the fact that the // characters representing the digits are // required to be contiguous and in the // expected collation order. For ten and // picture cards we add 10. For aces, we add // 11, but retain a count of the number of // aces, in case we need to use the // alternative lower value of 1, to attempt to // reduce a busted hand to an acceptable one. while(*cards) { switch(*cards){ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': handValue += (*cards-'0'); break; case 't': case 'T': case 'j': case 'J': case 'q': case 'Q': case 'k': case 'K': handValue += 10; break; case 'a': case 'A': handValue += 11; aceCount++; break; } cards++; } // If the hand is busted and there are any // aces, then substitute the value 1 for 11, // while the value of the hand is over 21 and // there are still unconverted aces remaining. while((handValue > 21) && (aceCount>=1)) { aceCount--; handValue -= 10; } // If despite the above, the value of the hand // is over 21, return the value but set the // ifail status value to reflect that. if(handValue > 21) *ifail = Error_Busted; return handValue; } int main() { char *testString[] = {"8", "ak2233", "aaaa2","kkaa4","2234f"}; int ifail; int result; for(int i=0; i<5;++i) { result = GetBlackjackHandValue(testString[i], &ifail); cout << testString[i] << " " << result << " " << ifailMessages[ifail] << endl; } return 0; }
Caliv Nir <shanir@netvision.net.il>
At first, as in the past I want to apologize for my English, please be patient.
{At the author's request I have cleaned it up for him, but I can assure you that it was much better than my Hebrew <g> Francis}
At last I got the courage and time (new baby boy :) to do a critique. I am neither an authority nor a C++ guru, like the amazing people who write here, just a student, so writing this critique is based on my own personal experience.
After reading so many books, columns and articles about C++ it was very hard to start this critique. I have in my mind all, what you should do and what you shouldn't do, and writing for such audience like you its very, well, brrrr.
But then I thought (hell) why not, let me try it (and I would even appreciate a critique on my critique), let see if I can contribute a little.
Reading the code and trying to "fix" it, I've decided on some key guidelines that I will follow:
-
First of all make the code work.
-
Readability of the code is most important
-
This code is not for guiding a rocket missile, so although I would love to use the most efficient code, it would not come over readability.
-
Reading the first two lines of code it's clear that it's author wants to use C++, although it could easily be written in C. I will keep with C++ and not be temped to convert it into OOP, because it's a critique, no?
But I will try to use more "C++ as a better C" features in the code.
-
Fixing a typo (to be able to build the program):
hand_value should be replaced with new_value
-
In new_value function:
while((total>21) && (ace_count >= 1 ))
Oops, ace_count ?? ace_count is a function name. The author probably meant to use aces but...
What a nasty bug, my VC++6 with warning level of 4, didn't make a sound, well we better be careful. The right line is then:
while((total>21) && (aces >= 1 ))
-
Now for the aces counting problem.
The author uses the aces variable in main to keep track of the aces count. Several functions use and try to update aces, but here comes the bug (bugs actually). The author has problems with his reference passing.
The default of passing variables in C and C++ is by value, meaning that, only the variable's value is passed, and not the variable itself.
Actually, copying the variable that was passed to the function does it, any attempt to modify it will be seen only in its function scope, but not outside the function. Let us see an example:
void tryMe ( ){ int x = 5; int y = 5; incMeByVal(x); incMeByRef(y); std::cout << "x=" << x << std::endl; std::cout << "y=" << y << std::endl; } void incMeByVal( int x ){ ++x; } void incMeByRef( int& y ){ ++y; }
The output of course is :
x=5 y=6
x was sent by value therefore incMeByVal copied it and modified its copy and not the "original" x. y was sent by reference so incMeByRef worked on the same object that tryMe created, and was able to modify it.
Well, the only difference is a small '&' and look what it does!
Back to our code, lets fix the reference problems:
new_value should get aces by reference so it can modify aces also in main's scope. So new_value signature becomes:
int new_value (int total, int& aces );
card_val also needs to modify aces, but instead of modifying main's aces, it creates it's own variable and sends it to ace_count, as soon as card_val terminates, all it local variables are destroyed and aces is one of them, leaving main with un-updated aces. card_val should be change to get main's aces by reference so it could update it correctly.
So, card_val should remove its local aces variable and card_val signature becomes:
int card_val(int card_no, int& aces);
and main should call it like:
total+=card_val(card_no,aces);
-
Starting with line number 3.
using namespace std;
So many programmers use it, many even think that they must do it after including <iostream> (by the way, great, the code uses standard headers!).
Doing so just ruins the whole purpose of namespace. The few times that you need to use std:: in this code doesn't give you any excuse for using it. We are just polluting the global namespace with all that <iostream> contains, and it's a lot!.
Use std::cout, std::cin etc. and don't use using namespace std;
If you are really lazy :-) , or the number of cout's in your code is so great, let's be more selective and use :
using std::cout; using std::endl;
instead.
-
As my lecturer (again, thanks Dr. Kimchi) always said ... "make a function do only one thing!", I think it's the most important guideline in programming style. Follow it and you get more readable, maintainable & reusable code. The first thing that bothers me while reading this code are functions that do a lot of things.
Look at main, what a whole bunch of stuff this function does
-
display a welcome message
-
playing the game
-
looping for more games
-
get user input for continuing with the game
-
checking and activating new_value when it's needed
Wow, really lots of things.
My recommendation stick with one task per function, write your self a list of tasks and make a function do only one thing.
If you practice it, you will gain the ability to sense when a new function is needed in the future (for example when I write an inner loop, all my senses is telling me, mmm does it really fit here? ).
-
-
Code duplication
The main enemy of maintainable and bug free code. When you duplicate you copy-paste your bugs! don't do it.
Why do double checks of :
if (total <=21)
or
(num_card <= 1 || num_card >= 6)
If you want to change something in the future you should do it twice. If a bug crept into those conditions you have duplicated it, yes, just like a virus.
Use functions instead, yes, a function that checks if your number of cards is in the proper bounds. Right, for all of you efficiency maniacs, it will make another function call (you can make it inline if you like), but as I mentioned before, it's not a real-time, need to be super fast application, right?
-
Magic numbers,
Don't we love them? Numbers that seem to us the most trivial thing, numbers that whoever reads the code will always understand where they come from.
It's an illusion, magic numbers are a maintenance nightmare, every one after a short period forgets where they come from and they are the cause of so many bugs. A number needs to be changed and now I must scan all my code to replace it, and who can decide if that number is that special number that needs to be change or it's another misplaced number that another programmer use?
Don't use numbers in your code (or at least, try to minimize the use of explicit numbers - try a game that I played with some students in the past, who can write a program with the fewest numbers).
Use const in C++, or #define in C. Define your constants only once and use their readable text form. Then when you need to change a constant simply change the constant definition and voila ... For example instead of 2, 5, 11, 21 use :
const int maxNumberOfCards(5); const int minNumberOfCards(2); const int ace(11); const int blackJack(21);
Isn't it clearer to see ace instead of 11 in your code ?
-
Believe it or not but most of the time the code talks to it's reader. The simple switch .. case can say more then you can imagine and not only to choose the proper value. For example, instead of :
case 't': value = 10; break; case 'j': value = 10; break; case 'q': value = 10; break; case 'k': value = 10; break;
write :
case 't' : case 'j' : case 'q': case 'k': value = 10; break;
Why ? Well, not only have I typed less, not only have we removed code duplication, but I also said something, I've said that ten, jack, queen and king have the same value in BlackJack and it's 10.
-
Ok, I've said that in this code I prefer readability over efficiency, but in places that it doesn't hurt readability you must always think on how it could be done better. I don't want to enter here the wonderful world of effective C++ (thanks Scott Meyers for your great work) but it's worth mentioning that habits are hard to break, but you break them if you try really hard, and have someone to remind you all the time.
Well here am I as a one that broke the habit of using postfix ++ whenever I can avoid it. Prefix ++ is more efficient the postfix ++, right, in native types like int it's hardly worth mentioning, but that all about breaking habits, use prefix ++ all the time, then you will not forget and use it when it really matters. I will not go into the details because it was written so many times before, but I only say that postfix ++ most of the time creates a temporary object.
So when writing in C++, try to use prefix ++ when ever you can. After the 1000th time you will remember it ...
So replace:
aces++ ;
with:
++aces ;
-
You chose to write in C++, so why don't you use its ability to declare variables when you need them, and not at the start of block used to be required in C?
Seeing the variable declaration and initialization close to the place you use it, does not make you go up the source file and look for this variable. It helps you to avoid forgetting initialization and it's very convenient when you write the code. So again try to drop the old habit from C and use the freedom of C++. For example: you can alter the for loop in main to be:
for(int cardNumber = 1 ; cardNumber < numberOfCards; ++ cardNumber) { // ... }
Notice that I also change the variable names, personally I hate using '_' but that is not the important thing, don't be cheap in letters, be more readable, name the variable numberOfCards (or for the '_' fans number_of_cards ) and not num_cards. Remember that the one who will read and maintain your code is not always familiar with your short names.
Oh, by the way did you notice the prefix ++ ?
-
std::endl .
endl - end line , much more readable (though not perfect) then '\n'. Right we all come from C, C++, Java etc. but what if we come from Visual Basic?, Who wants to see that strange things as '\n'. I am not saying stop using it, it's silly, but when you have an option prefer std::endl to print out a new line.
Wow, I hear you back there shouting and typing a fast replay, (yep) but std::endl do more, it also flushes the stream, well I know but here the battle of readable vs. efficient ends with readability with the upper hand, sorry. (if you insist make your own manipulator, make it inline what ever you want, but be ... : ) you got it - readable)
{It is a matter of developing good habits. std::endl seems OK in this context, but it can be bad news when writing to such things as files. Francis}
-
Comments
Well I will just say that, I will accept a comment free source file only in the "Student Code Critique Competition" section of ACCU, or in a text book that have a full explanation of that code after the source.
The code that I've attached was stripped of comment so it will fit the magazine, and I want you to read it to see if it readable.
{You will have to go to the web site because James is going to be shouting about me taking over again <g> Francis. However, I would suggest that if code needs lots of comments, it is badly written. Just as if a piece of text needs lots of footnotes it needs rewriting. Francis}
Ok, I think I will stop now, one thing I've learnt, too much critique is as good as no critique at all.
My (remember comment free) source code is on the web site, and I will be happy to here what you think of it. It's always easier to critique code then to actually write it yourself.
From Simon Swift <spswift@ntlworld.com>
First of all the function prototype hand_value should be new_value.
In the main function the use of card_no for the for-loop would be better if it was defined in the for-statement and card_num would be better though I would have preferred simply i for a counter.
I think I would have preferred to put the prompt to continue in a separate function that returned a bool then you could have something like:
... } while (yn_continue() ); ...
that would tidy up the main function a bit.
Now onto the other functions. First the number function. having a argument num_card is not needed as it returns the value entered; it should be declared in the body of the function. The if-statement could have been better put as
if(num_card < 2 || num_card > 5 )
Though the original would work fine. there is no check to see if the user entered a digit or other invalid character or any flushing of '\n' characters which could course problems. And finally the name of the function could be better i.e. number_of_cards();
Now to card_val and the ace_count functions. As Francis pointed out, there's a problem with aces. Basically it needs to have an int pointer in the parameter list to access the aces variable and not define the variable again in the body of the function. In the switch-statement we have some cases that receive the same value so these could be allow to drop through :
//.... case 't': case 'j': case 'q': case 'k': value = 10; break; //....
Also you do not need the flag invalid_card as setting value to zero would do:
while( ! value );
The acs_count function has a reference to aces but I think in this case a pointer would be better though I wonder if we needed that to be put in a separate function.
Finally after thinking about it I decided that the output was a bit inadequate and was in need of improvement. This, for me, means displaying, in English, the cards entered and then the total of my hand. For the program this means storing what the user enters in an array. In my example below I use a map to keep and retrieve the words as needed. This map could than be used to validate the input and this also allowed me to get rid of the switch-statement to find the value of the card. I have not put in a check to make sure that you cannot enter five of a kind; It seems a bit unnecessary and with displaying the cards with the result a user should be able to see that mistake.
So here's my version, as you can see some functions have changed, gone and new ones added.
// blackjack.C #include <iostream> #include <cctype> #include <map> #include <iterator> #include <string> using namespace std; #define MAX_VAL 21 #define MIN_CARDS 2 #define MAX_CARDS 5 int number_of_cards(); int value_of_hand(int, int *, char *, map<char, string> &); int new_val(int, char *, int); int card_value(map<char, string> &, char, int *); bool yn_continue(); void display_hand(map<char, string> &card_kind, const char *hand, int num_cards); int main(){ char *str[] = {"ace-low", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "Jack", "Queen", "King", "ace-high" }; char * ch = "A123456789tjqka"; map<char, string> card_kind; // build map to keep of each type for(char **s = str, *c = ch; *c; c++, s++ ) card_kind.insert(map<char, string>::value_type(*c , string(*s))); cout << "Welcome to the Blackjack Counter." << endl; do { int total = 0, aces = 0; int num_cards = number_of_cards(); // storage for cards in hand char hand[MAX_CARDS]; for(int i = 0; i < num_cards; i++ ) total += value_of_hand(i + 1, &aces, hand + i, card_kind); // if bust with aces in try making them ace-low if(total > MAX_VAL && aces > 0) total=new_val(total, hand, num_cards); display_hand(card_kind, hand, num_cards); if( total <= MAX_VAL ) cout << "Your hand is " << total << "." << endl; else cout << "You Bust with " << total << endl; } while ( yn_continue() ); return 0; } int number_of_cards() { int num_cards; do { cout << "How many cards do you have? "; cin >> num_cards; if(!cin) { cout << "Invalid input: " << "Numeric input only!" << endl; cin.clear(); while(cin.get() != '\n'); } if(num_cards < MIN_CARDS || num_cards > MAX_CARDS ) cout << "You must have between 2 and 5" << "cards (inclusive)." << endl; }while (num_cards < MIN_CARDS || num_cards > MAX_CARDS); return num_cards; } int value_of_hand(int card_num, int *ace_count, char *hand, map<char, string> &card_kind) { char card; int value = 0; do { cout << "Enter value of card number " << card_num << ": " << endl; cin >> card; while (cin.get() != '\n'); card = tolower(card); value = card_value(card_kind, card, ace_count); }while(!value); *hand = card; return value; } int card_value(map<char, string> &card_kind, char card, int *ace_count){ map<char, string>::iterator it = card_kind.find(card); if(it != card_kind.end() ) { if(card == 'a' ) { (*ace_count)++; return 11; } else if(card == 't' || card == 'j' || card == 'q' || card == 'k' ) return 10; else return card - 48; } return 0; } int new_val(int total, char *hand, int num) { for(int i = 0; i<num && total>MAX_VAL; i++) { if(hand[i] == 'a' ) { hand[i] = 'A'; total-= 10; } } return total; } bool yn_continue() { cout<<"Do you whish to enter another hand? "; char c = cin.get(); while (cin.get() != '\n'); return ( c == 'y' || c == 'Y' ); } void display_hand(map<char, string> &card_kind, const char *hand, int num_cards){ cout << "In your hand you have cards:" << endl; for(int i = 0; i < num_cards; i++ ){ map<char, string>::iterator it = card_kind.find(hand[i]); if(it != card_kind.end()) cout << (*it).second << endl; } }
From Simon Sebright <simonsebright@brightsoftware.freeserve.co.uk>
I always feel guilty about not entering. Here is my approach to the problem. I hope it is seen as an attempt to make you think. The lecturing style should be considered as a literary device. That is assuming I am entering the (Student Code) Critique and not the Student (Code Critique) competition, although both are equally valid. It is actually the eleventh hour. For some reason, my magazine had been lying undetected and I read the code critique competition the day before the due date. Today is that date, and I write hurriedly before I turn into a pumpkin.
Whilst driving between A and B this morning, I was trying to crystallise the essence of what I might write. Note that I was neither reading on the motorway nor have I a photographic memory. My approach is going to be to get across to the student things that from my experience will benefit their rite of passage. I come from a background of little formal training in either C++ or the standard libraries. However, I have had the benefit of working for more than one modelling company and have been in positions of, albeit minor, influence within them. So, I'm not going to pick holes in things a student should know, but from the standpoint of a senior technical person for whom the student had produced the code (and who must deliver the code to another party).
Let's suppose my brief was, "Write me a program to workout the value of a blackjack hand, and demonstrate that it works". It would appear to me that the first thing this student did was to switch on a computer and create main.cpp (or whatever the listing is). Right we start with main()...
What are you doing? Switch it off. My god, don't you want to think about it? Take a drive for a few hours. Whatever. I would expect that the brief should first need to be clarified. What do you mean by a "program"? What use is it to be put to? What kind of test-harness would you like? My answer: I want a "class" I can include in my program to add up the values of blackjack hands. Maybe it's in a DOS window, maybe I use it in an activeX control. Furthermore, I would like a test harness "class", which is also reusable, so that I (or rather you) can test your evaluation class. So, we call these Blackjack Evaluator and Blackjack Test Harness. Your deliverable program will also have some controlling class, let's call it main. I don't know, but I may have already raised eyebrows. Have I insulted the student? I know you want to prove that you can write an algorithm, but that's not the deliverable. We'll get there in a minute. Given that we'd like to separate interface and implementation, we now have five files for the student - main.c, Blackjack Evaluator.h & c, Blackjack Test Harness.h & c. Note the judicious use of capital letters. Now, on our class diagram, we have drawn a relationship from the controller to the test harness and from the test harness to the evaluator. These are uni-directional, so we have one file including the other. In main.c, we have #include "Blackjack Test Harness.h" and in the test harness implementation file, we have #include "Blackjack Evaluator.h". Note that there is no backward inclusion going on i.e. that the user of the evaluator does not need the test harness.
So, what next? Well, the blackjack evaluator needs something to evaluate. So, we have a function "Evaluate Hand". This would seem to be one of the things main is doing. What parameters should it take? Using noun-analysis, we conclude it must be a "Hand". Another class, representing a hand of cards, that is a collection of somethings. What? "Card Values". I'm not up on stl, so I don't know the most appropriate container class to use. It might be nice to have it ordered so that busting could be given in more detail. A list? A vector? You tell me.
I now see a potential problem, we can't just ask for a hand and then evaluate it, as it might be the case that a part of that hand was already over the total allowable, so our test harness needs to keep calling the evaluate as it grows the hand. This means that each card is potentially counted more than once. Well, if this program is so abominably slow that this is unacceptable, then my brief was wrong (or rather the constraints were not documented). It's interesting to note here that although my algorithm is non-linear, I know the limits of it (5 cards maximum). We couldn't do this for a hand of undefined length without cause for concern.
Having expounded, I realise I am not criticising the code in the traditional sense. Line 31, missing semi-colon. Tut, tut. I won't continue the above train of thought to the bitter end, perhaps I can now apply some more specific comments, but still along the lines of standing back from the issue of being a human compiler.
The first thing I object to is a function called number. Meaningless. GetNumberOfCards is my best offering after a few pints of homebrew. Why does it take an in-parameter? No reason that I can see apart from not wanting to declare (and initialise) a local variable.
Next function, card_val. The name is better. GetCardValue would be better, but perhaps you are feeling sheepish by now, so we'll be kind (but can I recommend that you take a touch typing course instead of playing computer games?). But, again, why the in-parameter? So you can tell the user which card they are entering. Fair enough given this structure, but in my ideal structure, we'd have one lump looking after getting the card values from the user and another working out the value of the hand. There are uninitialised variables here. It makes me shiver. Yes, they are set up in this case, well, all apart from value when the user enters something that invokes the default case of the switch statement. I can quite accept that there are people who could look at the code and say, "In this case it doesn't matter". But, I don't think you should have had to get someone to go that far. There is a problem in assigning a sensible initial value. I usually go for zero on numerical variables unless the logic dictates something specific.
ace_count. An admirable function, provided it counts the aces in a hand, which it doesn't. This function as implemented should be called IncrementNumberAndReturnEleven. Enough said, I think.
new_value. Yes, it does get a new value, but so would a random generator. What is this function doing? It's working out a refactored value based on the fact that instead of busting, you can count an ace as 1 instead of 11. A good name for this function escapes me. GetMaxNonBustTotal would be a first stab. Not that long really. I typed it in a second or two.
I've now run out of code. It would seem I have been reduced to the level I wanted to avoid. I don't see how it would compile and link:
-
Where is hand_value(), I assume you meant new_value()
-
card_val() calls ace_card() which I assume should be ace_count()
-
new_value() references ace_count as a function pointer rather than a function call return value. I assume that the formal parameter int aces was meant here. Presumably this will always be greater than or equal to one and thus I would assume that all hands will be able to be refactored to a non-bust value.
A general point, Mr/Ms Student: Your code should compile and link, and do so with no warnings. You might "know about that one", but when you leave for higher echelons, your successor might not. I might not, you might not in a year's time.
I sincerely hope that by thinking about the problem in the way I have eluded to above, a student would largely escape the nasties here. Name your functions well, and only give them the parameters they need. My class modelling would have applied to C, C++, in fact any language for which you could devise a suitable mapping between logical classes and code units. I may have missed the things that Francis was getting at, I may have picked on things that weren't a problem, but then, that's my point - it's difficult to analyse a mess.
From: Lewis Everly <le_everly3@yahoo.com>
After looking at the code I could see some visible syntax problems. I'm sure the code would not compile as is. After repeatedly looking at the code these are the things I have problems with or would like to improve on.
-
I would not have a parameter for the number function. I think the number function should look like this.
int number(){ int num_cards = 0; while(true){ cout <<"\nHow many cards do you have? "; cin >> num_cards; if((num_cards <= 1) || (num_card >= 6)) { cout <<"You must have between" <<" 2 and 5 cards (inclusive)."; } else break; } return num_cards; }
I think that making num_cards a local variable is better.
-
I was bothered by the card_val function and when it is used. The function in call inside a for-loop for each card and it also has a loop inside the function to check that the input is valid. The for-loop that calls the function should be removed. The loop inside the function should be modified to handle the number of cards sent in. The function should return the total value for all cards.
int card_val(int num_cards){ char card_val; int value = 0; int aces = 0; int card = 1; do{ cout <<"\nEnter value of card number" <<card<<":"; cin >> card_value; card++; switch (tolower(card_val)) { case 'a': value += ace_card(aces); break; case '2': value += 2; break; <similar cases for 3-9 snipped> case 't': value += 10; break; <similar cases for j, q and k snipped> default: cout <<"\n Not a valid entry." <<" Please Try again."; card--; break; } }while(num_cards >= card) return value; }
-
I also notice that the aces variable is declared locally inside the card_val function. When the number of aces is calculated the count is lost when card_val returns. This can be handle two ways, make aces a global variable and remove the local inside card_val or pass aces as int& aces parameter in card_val. I will do the second.
int card_val(int num_card, int& aces);
-
After looking at the if-statement that checks the total against the value 21. I think I will rewrite that code to look simple. I will do all the calculation of the card value inside card_val function and remove new_value (should be hand_value ) function. So the ending of the card_val function will do an extra check before it returns the total.
while(( value > 21) && aces >= 1) aces--; value -= 10; } return value; }
With the above change I guess statement 3. is not useful. So keeping aces as a local variable is OK now, but the aces on the main level should be removed to avoid confusion.
And finally the program could use a few comments.
From: Colin Hersom <colin@hedgehog.cix.co.uk>
Glancing at this code, my brain says "C". Looking more closely, I notice the use of cout, and the reference parameter in ace_count, so this must be C++. Whilst I have no objection to using C++ as a better C, I do not think that this code is "better C": all variables are declared at the top of functions, rather than where they are first needed and the switch statement could so easily be replaced by a better C++ construct, e.g. using std::map.
I don't much like the use of "using namespace std" at the beginning of every file, but since my compiler effectively does this anyway, I have not means of finding places where I use a std:: item unqualified, so I shall leave this here.
Let us first tackle the internal design issue: how do we represent the cards in such a way that the value of the hand can be calculated when there are aces present. The code presented fails on this because the aces count inside card_val is not propagated up to the main program for passing to new_value - which with this code is always going to see zero aces. We can represent the value of a card or set of cards by a pair of integers for the value and the ace-count:
class card_value { int value; int aces;
N.B. If you are feeling paranoid about space, we can note that the value will never exceed 60, so these two members could be char rather than int. Such a choice does not affect the rest of the implementation.
An item of this sort can be initialised in these three ways:
-
no value (for the total)
-
ordinary card
-
ace card
We can easily make this using one constructor with default arguments:
card_value(int val=0, int ace=0) : value(val), aces(ace){}
however this would leak the implementation of aces, so I prefer to add a helper routine for case (c) and use the constructor for normal cards only:
public: card_value(int val=0): value(val), aces(0){} static card_value ace();
The other constructors and the destructor can be left to the compiler because the default ones will be correct. I shall return to the implementation of the ace() function.
We need to add a card to the total, so we need an operator +=:
card_value &operator+=(const card_value &c){ value += c.value; aces += c.aces; return *this; }
And now the only thing remaining is to calculate the value of the cards accumulated, so we need an evaluation function:
int hand_value() const; };
"Cheat" I hear you say, "you haven't defined this function." Well, yes and no. I can defer the implementation of the function to be external to the class, but also I have not yet defined how the value/ace split is made in order to be able to write this function anyway.
So how am I going to define the card_value member variables? I think that it is clear that non-ace cards have their value as specified in the original "switch" statement, with aces being zero. Also aces for an ace needs to be one, so the problem is the value. I can see three equally valid values of "value" for an ace card:
-
zero - we add either 1 or 11 in the hand_value function
-
one - this is one possible value, and we add ten if necessary
-
eleven - this is the other value and we can subtract ten
I shall choose (b) simply because I want to. Now we can write the ace and hand_value functions:
card_value card_value::ace() { card_value new_ace(1); new_ace.aces = 1; return new_ace; } int card_value::hand_value() const { int total = value; int ace_count = aces; while (total < 11 && ace_count > 0) { // Use up an ace counting as eleven total += 10; ace_count--; } return total; }
Now we need to look at how the card_value class is going to be used. Firstly, there must be a declaration of the total value:
card_value total;
This picks up the constructor with two zero arguments, and so conveniently initialised to zero. There is a loop, which finds the card and adds it to the hand.
for (...) total += read_card();
Here read_card will return a card_value, and so the operator+= adds it to the hand using the operator we defined. read_card is my replacement to card_val. This function needs defining.
I already suggested that the switch-statement could be replaced by a map, so let us look at this possibility. The map has to take a character representing the card and return the card_value for that card. To save RSI, I like to define a type for the map, which also allows possible future transformation into a class type, if required:
typedef std::map<char, card_value> card_map;
We are going to need to initialise one of these maps with all the valid values, so let us define this function:
void init_card_map(card_map &cards) {
Most of the cards are simple values, and so we can initialise them with integers, since this uses the (val, ace=0) constructor:
cards['2'] = 2; <similar lines snipped> cards['t'] = 10; <similar lines, both upper and lower case snipped> cards['K'] = 10; </prNotes:
More fields may be available via dynamicdata ..