You can see an index of all the posts in this series: go to index.
In the previous post I set myself the challenge of writing a C version of the BASIC game Acey Ducey.
In this post I’ll walk you through my solution.
Here are the project files for the finished solution: challenge project 1.
Here’s the code in full:
// // acey-ducey.ct // Acey Ducey // // Created by Laurence Scotford on 01/05/2014. // // #include <stdio.h> #include <stdlib.h> #include <time.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <stdbool.h> // Function declarations void runGame(); int pickCard(); void getCardName(int card, char cardName[]); void strToLowerCase(char string[]); int main() { // Display instructions printf("Acey Ducey card game\nBased on a program from Creative Computing, Morristown, New Jersey\n\n\nAcey Ducey is played in the following manner.\nThe dealer (computer) deals two cards face up.\nYou have an option to bet or not, depending\non whether or not you feel the next card will have\na value between the first two.\n\nIf you do not want to bet, input a 0\n"); char rawInput[80]; // This will hold the raw input entered by the user char inputString[80]; // This will hold the processed input string // Seed the random number generator srand((unsigned int)time(NULL)); do { // Play a game runGame(); // Ask the player if they want to play again printf("\nTry Again (yes or no)? "); fgets(rawInput, sizeof(rawInput), stdin); sscanf(rawInput, " %s", inputString); strToLowerCase(inputString); } while (!strncmp(inputString, "yes", 3)); printf("\nOK. Hope you had fun!\n\n"); return 0; } void runGame() { char rawInput[80]; // This will hold the raw input entered by the user int bet = 0; // This will hold the amount of the player's bet int money = 100; // Amount of money remaining to the player // Keep playing while the player has money remaining while (money > 0) { // Show player how much money is remaining printf("\n\nYou now have %i dollars\n\n", money); // Pick first card int cardA = pickCard(); int cardB = cardA; // Continue picking second card until it is not the same as first card while (cardB == cardA) { cardB = pickCard(); } // If cardB is lower than cardA, swap them if(cardB < cardA) { int tmp = cardA; cardA = cardB; cardB = tmp; } // Get the card names char cardAName[6]; char cardBName[6]; getCardName(cardA, cardAName); getCardName(cardB, cardBName); // Show the cards to the player printf("Here are your next two cards:\n%s\n%s\n\n", cardAName, cardBName); // Get the player's bet bool validBet = false; // Continue to ask for player's bet until player enters a valid amount while (!validBet) { printf("What is your bet? "); fgets(rawInput, sizeof(rawInput) / sizeof(rawInput[0]), stdin); // Attempt to convert bet to a number int varsFilled = sscanf(rawInput, " %i", &bet); // Only validate bet if it's a positive number equal to or less than player's money, otherwise display an error message if (varsFilled != 1 || bet < 0) { printf("\nPlease enter 0 for no bet or a whole number greater than zero to bet that amount.\n\n"); } else if(bet > money) { printf("\nSorry my friend but you bet too much.\nYou only have %i dollars to bet.\n\n", money); } else { validBet = true; } } // Only pick third card if player has chosen to bet if (bet == 0) { printf("\nChicken!\n"); } else { // Pick third card int cardC = pickCard(); // Get name of third card char cardCName[6]; getCardName(cardC, cardCName); // Display third card to player printf("\n%s\n", cardCName); // Check if player has won or lost if( cardA < cardC && cardC < cardB ) { printf("You win!!!\n"); money += bet; } else { printf("Sorry, you lose.\n"); money -= bet; } } } printf("\nSorry friend, but you blew your wad.\n"); } // This function returns a card picked at random int pickCard() { return rand() % 13 + 2; } // This function returns the correct card name for the card passed as the first argument, // the second argument is a character array where the card name will be stored (which must have space for at least six characters) void getCardName(int card, char cardName[]) { switch(card) { case 11: strcpy(cardName, "Jack"); break; case 12: strcpy(cardName, "Queen"); break; case 13: strcpy(cardName, "King"); break; case 14: strcpy(cardName, "Ace"); break; default: sprintf(cardName, "%i", card); break; } } // This function converts the string to lower case letters void strToLowerCase(char string[]) { for(int i = 0; string[i]; i++) { string[i] = tolower(string[i]); } }
I’ve created four functions, other than main, for this project. You can see the declarations for these functions in lines 18 to 21. Remember that I have to declare all functions I create before I call them. I’ll look at the detail of each of these functions later in this post.
Note that I’ve also taken the parameters out of this version of main. Getting command line arguments in main is optional. It won’t hurt to leave them in, but removing them from the function definition lets other people viewing the code know that they’re not used.
The next thing you should note is that the main function itself does not contain much code. As I begin to write more complex programs, this will generally be the case. Most of my program’s functionality will be put into functions and main‘s job will be to do a bit of initialisation and then call one or more of these functions.
I have main displaying the instructions on line 25, as that is done once, when the program is first entered, initialising some variables on lines 27 and 28, which I’ll explain in a moment, seeding the random number generator on line 31, and then entering a loop, between lines 33 and 42, which plays the game as many times as the player wishes.
Note that the entire game logic is contained in a single function called runGame. When this function is called on line 35, it plays a single game, then returns when the game is over. It’s always a good idea to break my programs up into functions representing logical chunks. It makes them easier to read and maintain.
When a game has finished, the program needs to ask the player if they want to play again. Unlike Buzzword, which looks for a single letter ‘y’, Acey Ducey wants the answer to be “yes”. Here’s how I do this.
In line 27, I create a character array that I will use to store the raw input. In line 28 I create another character array that I will use to store the word that will be extracted from that input.
In line 39 I use the fgets function, as I did with Buzzword, to get all the player’s input into the rawInput array. In line 40 I use the sscanf function, once again, to extract the bit I want from the player’s input and store it in the inputString array. Note the initial space in the format string and recall that this will get sscanf to ignore any white space that comes before the word. There are two differences from the way I used this function in Buzzword. The first is that I’ve used the conversion specifier %s instead of %c because the program is now scanning for a full string rather than a single character. You’ll also note that the name of the variable, inputString, doesn’t have the reference operator, &, in front of it. Why is this?
You’ll remember that, when I pass a character variable as an argument, what actually gets passed is the value in that variable. But sscanf wants the address of the variable, not the value, because it is going to place a value at that address. When I pass an array as an argument, however, I don’t pass all the values in the array – I pass a pointer to the first element of the array. In this case I do want the variable’s value because that’s the address of the array. This is why I don’t use the reference operator, &, when I am using sscanf to read a string. This diagram shows the difference between an argument passed by value, e.g. a char variable, an argument passed by an explicit reference, e.g. a char variable with a reference operator, and an argument passed by implicit reference, e.g. a char array:
Note that, once sscanf finds the start of a word, it will only read as far as the first white space character, e.g. a space. So even if the player were to enter something like “yes I’d like to play again”, after the sscanf function, only “yes” would be stored in inputString.
Now I need to check if the player has entered “yes”. This is done with the while command on line 44. You might have been expecting to see something like while (inputString == “yes”). Recall from earlier in this post that inputString is an array and when I use the name of an array as an argument, it is passed by reference, not value. In other words, I would be trying to compare a pointer to the first element of the array with a string literal. This just doesn’t make sense. Be aware that C will quite happily let me try to do this, so I need to watch out for nonsensical comparisons of this kind.
Instead I use a new function, strncmp, which, now you’re getting used to C naming conventions, you have probably worked out is short for string compare. The strncmp function is part of the string library, so I’ve imported this on line 12. This function has three parameters. The first two are either pointers to strings or string literals to be compared (although obviously only one would ever be a string literal as there wouldn’t be much point in comparing two string literals). The third parameter is the maximum number of characters to compare. Note there is a related function, strcmp, which doesn’t have this third parameter. I’ve used strncmp and limited the characters to 3, so that a response like “yessir” or “yesssss” would also match.
The result returned by strncmp (or strcmp) is a negative number if string 1 is less than string 2, 0 if the strings are the same and a positive number if string 1 is greater than string 2. How is “greater than” or “less than” calculated in this context? Each character is actually represented by a number. The actual number used depends on the text encoding standard used by the system on which the machine runs. For the sake of this discussion, let’s assume the encoding standard is one called ASCII (American Standard Code for Information Interchange). In ASCII, the letter ‘a’ is represented by the number 97, ‘b’ by the number 98, ‘c’ by 99 and so on. The strncmp function compares the strings one character at a time until it finds a difference. For example, “cat” is less than “dog” because the value of the first character in “cat” is 99, but the value of the first character in “dog” is 100. Note though that “BIG” is actually less than “big” because the value of an upper case ‘B’ is 66, while the value of a lower case ‘b’ is 98.
Because strncmp returns a zero when the strings are equal, and a zero result for a condition is false, note that I have used the logical NOT, !, operator to negate the result returned by strncmp. This means the program will loop again if the strings are equal and exit if not.
In that discussion we discovered that “BIG” is not the same as ‘big’ as far as strncmp is concerned. The same will go, for “yes”, “Yes” and “YES”, which are all considered to be different strings. Clearly though, the program should accept all of them. I could do something like this:
} while (!strncmp(inputString, "yes", 3) || !strncmp(inputString, "Yes", 3) || strncmp(inputString, "YES", 3));
But not only is that quite an unwieldy and error-prone statement, it also doesn’t cater for mixed case responses like “YEs” or “YeS”. You can easily work out that there are eight different combinations of upper and lower case letters I’d have to check for. A more elegant solution is to use a function to convert any upper case letters to lower case first, then I only have to check for a single response, “yes”.
This function is declared on line 21 and defined on lines 164 to 168. Here’s what it looks like:
void strToLowerCase(char string[]) { for(int i = 0; string[i]; i++) { string[i] = tolower(string[i]); } }
Note that this function has a single parameter, which is the character array to be converted. Because the function modifies the character array directly, it doesn’t return a value, so it’s return type is void. When a function modifies variables outside of the function (other than through returning a value), this is known as a side effect.
Inside the function, I loop through all the characters in the string. Previously, when I have used a for loop, I’ve used loop conditions that look something like i < numberOfPhrases, meaning that the loop continues while i is less than the value of the variable numberOfPhrases, but here our condition is string[i]. What’s going on here?
Well, recall that, when evaluating a condition, zero is taken as false and a non-zero number is taken as true. In this loop, each time it gets to the condition, string[i] gets the value of the character at position i. Let’s assume that the string contains the text “yes”. The first time through the loop i is equal to 0, so it will get the character at position 0, ‘y’, which has the value 121. That’s non-zero so the loop is repeated. Next time through, i is 1, and position 1 has the character ‘e’ with the value 101, so again the loop is repeated. Third time through i is 2, and position 2 has the character ‘s’, which has a value of 115, so once again we go through the loop. On the next iteration i is 3. Wait a moment, you might be saying, there is no character at position 3. But remember, all strings are terminated with the null character, ‘\0’, which happens to have a value of 0. So at this point the loop ends. This is a convenient facet of strings – every character in a string is guaranteed to have a non-zero value, except for the terminating null character at the end of the string. Note though that this only works because the condition is always tested at the top of a for loop, so execution ends as soon as the null character is found. If I tried to do the same with a do … while loop, it wouldn’t work, because the character would already have been processed by the time the program checked it.
Within the for loop I make use of another C library function, tolower, which returns the lower case equivalent of the character passed as an argument. So ‘y’ and ‘Y’ would both be returned as ‘y’. If I pass a non-alphabetic character into tolower, e.g. ‘%’, that character will be returned unchanged. The tolower function is part of the ctype library, which is included on line 13.
I call this function on line 41, just before the loop condition is tested, like this:
strToLowerCase(inputString);
Note that this function is only called once, so you might be wondering why I bothered to use a function at all. Why not just include the code to convert to lower case directly at this point? Firstly, as I mentioned before, it’s always a good idea to split my program up into logical pieces, and for each of those pieces to live in its own function. Secondly, converting a string to lower case is something I’m likely to do again in future programs. If I have that functionality nicely wrapped up in a function, it’s much easier for me to reuse it again in other programs.
If the player choses not to continue the game, by entering a value other than “yes”, I display a farewell message and end the program with an error code of 0, indicating success.
printf("\nOK. Hope you had fun!\n\n"); return 0;
So that’s main‘s part in all this taken care of – now let’s look at the function runGame, which is responsible for the main game logic.
void runGame() { char rawInput[80]; // This will hold the raw input entered by the user int bet = 0; // This will hold the amount of the player's bet int money = 100; // Amount of money remaining to the player
I begin by declaring and initialising a couple of variables, a char array called rawInput, which will be used to capture the player’s input when they bet, an int called bet, which will hold the numeric value of the bet and an int called money, which holds the amount of money remaining to the player.
You might have noticed that I also have a char array called rawInput in the main function, but remember, from the discussion on variable scope in an earlier post, that variables declared within functions are local to those functions. As far as C is concerned, the rawInput in main and the rawInput in runGame are two entirely different variables.
Most of the rest of the function is contained in a while loop from line 56 to 131. This checks that the player still has money remaining and continues picking cards and taking bets while they do have money. Once the player is out of money an ‘out of cash’ message is displayed, on line 133 and the function ends (and returns to main).
// Keep playing while the player has money remaining while (money > 0) {
⋮
} printf("\nSorry friend, but you blew your wad.\n"); }
Each time through this loop, the first thing to do is show the player how much money they have left.
// Show player how much money is remaining printf("\n\nYou now have %i dollars\n\n", money);
Next the program picks the first card, which is stored in the int called cardA:
// Pick first card int cardA = pickCard();
Note that I’ve created another function for picking a card, called pickCard. This is because the program needs to pick three different cards each time through the game, so it makes sense to have this functionality wrapped up in a function.
The pickCard function, which starts on line 137, looks like this:
// This function returns a card picked at random int pickCard() { return rand() % 13 + 2; }
This code should look very familiar to you by now. It is picking at random from a range of 13 cards, but starting them with a value of 2, so the actual range will be 2 to 14, with 11 representing a Jack, 12 a Queen, 13 a King and 14 an Ace (Aces are high in this game). Now we return to the runGame function:
int cardB = cardA; // Continue picking second card until it is not the same as first card while (cardB == cardA) { cardB = pickCard(); } // If cardB is lower than cardA, swap them if(cardB < cardA) { int tmp = cardA; cardA = cardB; cardB = tmp; }
This next section of code might look a little odd, so let’s look at it in detail. I start by initialising the second card, cardB to be the same as the first card, cardA. What I now want to do is replace the current cardB with randomly picked cards until cardA and cardB are different. This is achieved by the loop between line 65 and line 57. Note that when I have a single statement in a loop like this, (or between the braces in an if or if … else statement), I can optionally omit the braces and do something like this:
while (cardB == cardA) cardB = pickCard();
In my opinion, that is not very readable code, so I always use braces, even if the braces contain only a single statement. Again it’s purely a style choice whether to omit braces for single statements, but whatever I decide, I need to be consistent.
Once cardA and cardB are different, I then have to get them into the correct order, so that cardA is the lower of the two. This is achieved with the if statement from lines 70 to 75. If cardB is the lower card, this simply swaps the two cards over. Note that I have to use a third temporary variable here, called tmp, to preserve the value of cardA while I copy cardB to cardA.
// Get the card names char cardAName[6]; char cardBName[6]; getCardName(cardA, cardAName); getCardName(cardB, cardBName);
The next bit of code converts the card values into card names. Again, I have created a function to do this, because the same thing has to be done for three cards for each bet. Note that I create two arrays to hold the card names in lines 78 and 79 and these are passed to the getCardName function in lines 80 and 81, along with the value of each card. The function is declared on line 20 and defined on lines 143 to 160.
// This function returns the correct card name for the card passed as the first argument, // the second argument is a character array where the card name will be stored (which must have space for at least six characters) void getCardName(int card, char cardName[]) { switch(card) { case 11: strcpy(cardName, "Jack"); break; case 12: strcpy(cardName, "Queen"); break; case 13: strcpy(cardName, "King"); break; case 14: strcpy(cardName, "Ace"); break; default: sprintf(cardName, "%i", card); break; } }
The function takes two parameters – the first is the numeric value of the card, the second is a pointer to the character array where the name of the card is to be stored. This should have space for at least six characters. Why six, when the longest card name, “Queen” only has five? Remember that all strings are terminated with a null character, ‘\0’, so I need to allow space for that as well.
The switch command on line 144 is on I haven’t used yet. Switch can often be used as a more compact alternative to a long chain of if … else if … else statements. What switch does is follow one of several paths depending on the value of the expression in parentheses. In this case the expression I am checking is simply the value of card. The different paths are enclosed within a pair of braces, and each path begins with the keyword case, followed by a value and then a colon. So you can see from lines 146, 149, 152, and 155 that I am checking for the cases where the value of card is 11, 12, 13, and 14, i.e. the values of all the picture cards and the Ace. There’s a final path called default: (note this doesn’t have the word case in front of it). This path will be selected for any value not already matched by one of the other cases. In other words, cards 2 to 10.
In each case I call another function that I’ve not used before, called strcpy. It shouldn’t take too much effort to work out that this is short for string copy. This function is defined in the string library and takes two parameters, a string pointer and then either another string pointer or a string literal. The function copies the contents of string 2 into string 1. I must always take care with this function that there is sufficient room in string 1 for the string I am trying to copy, if not the program could overwrite other important values in memory, causing it to stop working. I use this function to copy the relevant card name into the cardName char array.
If the card value is less than 11, i.e. one of the cards from two to ten, the number of the card will also be its name. To get this into my cardName array, on line 158 I use a variant on the printf function called sprintf. The sprintf function is to the printf function, what the sscanf function is to the scanf function. In other words, instead of sending its output to stdout, like printf does, it sends it to a string. A pointer to the destination string is the first parameter, and then the remaining parameters work in exactly the same way as the parameters in printf. You probably won’t be surprised to learn that sprintf is part of the stdio library.
You’ll notice each of the cases ends with another unfamiliar command, break. The break command causes the switch statement to end. It’s needed because, without it, control would fall through to the cases following the current one. In other words, without the break statements, every card would end up being printed as a number because regardless of which case was initially executed, all the subsequent cases would also be executed, ending with the default case. Note it’s not strictly necessary to include the break statement at the end of the final case, but it’s good practice to do so, as not having it can cause errors if I include additional cases at a later point. This diagram should help clarify how the switch statement works:
Now, you might be wondering why this function had a second parameter – an array pointer for the character array where the string is to be stored. Why not simply return a string? Wouldn’t it have been better if my function looked something like this:
char * getCardName(uint card) { char cardName[6]; switch(card) { case 11: strcpy(cardName, "Jack"); break; case 12: strcpy(cardName, "Queen"); break; case 13: strcpy(cardName, "King"); break; case 14: strcpy(cardName, "Ace"); break; default: sprintf(cardName, "%i", card); break; } return cardName; }
And then in runGame, I could get the cardNames like this:
strcpy(cardAName, getCardName(cardA));
To answer this question, I need to take a more detailed look at how the stack works. You’ll remember from an earlier post that the stack is an area of memory where variables are stored. So let’s consider what happens as I create variables in this program. The first function to be run is main. In main I declare two variables, rawInput and inputString. After these variables have been declared, the stack looks like this:
A little later I call runGame from main and runGame begins declaring its local variables. By the time the program is about to call getCardName, the stack looks like this:
Note that main‘s variables are still there, because main is still running. Even though there are two char arrays called rawInput in the stack, they are local variables belonging to different functions, so C considers them to be different. Any reference to rawInput in runGame will be taken to be a reference to runGame‘s variable, not main‘s.
You might be able to see now why this part of the memory is called the stack, because variables are stacked in order as they are created.
So now we call our new version of getCardName and it declares its local variable, which is a char array called cardName. The stack now looks like this:
So far, so good. At this point getCardName does its job of putting the relevant name into its local variable cardName. Then it returns a pointer to cardName. This brings the getCardName function to an end, so C tidies up by removing any local variables owned by getCardName. So now control has returned to the runGame function, which now tries to copy the string pointed to by the returned pointer into cardAName. But guess what, that string was stored in cardName, which has just been deleted by C. So the return value points to a variable that no longer exists:
Now, what can cause further complications with this sort of erroneous programming, is that, often C will mark the memory occupied by cardName as being free for reuse, but it won’t actually erase what was there. So sometimes, especially in cases like this where I am using the returned pointer straight away, the data it points to will still be intact, even though, as far as C is concerned, the variable no longer exists. What this means is that the program could appear to work, but when I run it on a different machine, or a different operating system, or compile it with a different compiler, it can then suddenly fail for no apparent reason. But this will most likely be because, in the different environment, the data has been overwritten.
This is why I should never attempt to return pointers to local variables. As you will discover in future posts, pointers are a very powerful feature of C. But, as a certain superhero was once told, “with great power comes great responsibility”. That’s certainly true of pointers. They can be dangerous if not used with care. The issue we’ve just been looking at is known as a “dangling pointer” and is one of many ways in which pointers can get programmers into trouble. I’ll look at some others in later posts.
Compare this to my original version of the getCardName function, where Iask the calling function, i.e. runGame, to provide its own location for the string to be saved. Think of it like taking your own sturdy bags to the shop instead of relying on the flimsy paper bags the shop provides – the paper bag might get your shopping back safe, but it could just as easily break and you’ll lose your groceries. I know my version of sturdy bags, the variables created in runGame, will exist before the getCardName function is called, will exist while the getCardName function is running and will still exist when the getCardName function returns. It’s a much safer strategy. So now we continue with the runGame function:
// Show the cards to the player printf("Here are your next two cards:\n%s\n%s\n\n", cardAName, cardBName);
The next thing I do is display the card names to the player. Now, the only thing I ever use the card names for is to display them to the player like this and each card only ever gets displayed once. So you might be wondering why I didn’t just write a function that displays the appropriate card name when it’s passed the card value, something like this:
void displayCardName(int card) { switch(card) { case 11: printf("Jack"); break; case 12: printf("Queen"); break; case 13: printf("King"); break; case 14: printf("Ace"); break; default: printf("%i", card); break; } }
While that’s a functionally valid solution, there are two reasons why I chose not to do it that way. The first has to do with reusability. It’s highly likely that I will create more card games in the future. In other card games I create I might not want to display the card name immediately. I might, for example, first want to combine the name of the card with another string containing the suit. A generalised function that returns the card name as a string is much more likely to be reusable in a future program than one that displays the card name directly. As you will have already discovered, programming is an intensive and time consuming task. It’s often worth spending a bit more time to make my code reusable today, if it saves me twice as much time not having to reinvent the wheel tomorrow.
The second reason relates to how the different parts of my program work together. You might think of this program as having two fundamental parts. One part is the model – this is the data, such as the card values and the bet amount and the logic to deal with that data. The second part is the interface – this is the code to display information to the player and to get the player’s input. Best practice is to separate those two parts as much as I possibly can, and the more complex my program is, the more important it is to maintain that separation. Obviously they can’t be completely separate – at some point my model needs to talk to my interface and vice versa, but I should try to make those communication points between the two as well-defined and as minimal as possible.
Why bother to do that? Well, at the moment I am creating programs that run in a command line interface. At some point in the near future, I will progress to developing programs that have a graphical user interface. At that point I might think “you know, wouldn’t it be great to have a version of that Acey Ducey program that used images of the cards rather than just text.” Now if the code for the model and the code for the interface are horribly tangled up I may well just decide to give up and start developing the graphical version of my program from scratch. But if I’ve been careful to maintain as much separation as possible between the model and the interface, it’s probably not going to be too difficult to unpick the text interface code and replace it with the code for the graphical interface. All being well, I will have very little additional work to do on the model code. This is an important concept in programming and I’ll be coming back to it again in future posts.
// Get the player's bet bool validBet = false; // Continue to ask for player's bet until player enters a valid amount while (!validBet) { printf("What is your bet? "); fgets(rawInput, sizeof(rawInput) / sizeof(rawInput[0]), stdin); // Attempt to convert bet to a number int varsFilled = sscanf(rawInput, " %i", &bet); // Only validate bet if it's a positive number equal to or less than player's money, otherwise display an error message if (varsFilled != 1 || bet < 0) { printf("\nPlease enter 0 for no bet or a whole number greater than zero to bet that amount.\n\n"); } else if(bet > money) { printf("\nSorry my friend but you bet too much.\nYou only have %i dollars to bet.\n\n", money); } else { validBet = true; } }
Now the program has displayed the first two cards to the player, it’s time to get the player’s bet. There will be a number of checks I need to make on the player’s input to validate it, so I need a way of knowing when I have valid input. I’m going to track this with a type of variable I’ve not used yet. It has the type bool, which is short for Boolean. Boolean variables are used in Boolean algebra, which was invented by the 19th Century mathematician George Boole. You’ve already seen Boolean algebra at work in the conditions I’ve used in if, while and for statements. Expressions in Boolean algebra always evaluate as either true or false. You’ve seen that C considers zero to be false and any non-zero value to be true, however, you can also store these values in a bool variable.
Because bool is a late addition to the C language, I need to import the standard bool library, stdbool, if I want to make use of it. I import this on line 15. As well as defining the bool type, this also defines the values true and false. You can see an example of this on line 87 where I declare the bool variable validBet and initialise it to false.
Lines 90 to 106 are a loop that repeats while the bet is not valid – note that because I am testing for a false condition, I use the logical NOT operator, !, in the condition on line 90.
Notice that I’m once again using a combination of fgets on line 93 and sscanf on line 96 to get input from the player. There are a couple of differences in the way I’m using sscanf here though. Firstly, note that the conversion specified is %i for integer, because the input we want from the player here is a number – how much they want to bet. Next I’m putting a return value from sscanf into an int variable, varsFilled. When sscanf finishes, it returns the number of variables it successfully filled. It can also return an error code, EOF, if it reaches then end of the input stream before it has processed all the variables. EOF will evaluate as a negative constant (in the GCC compiler it is -1, but I shouldn’t ever rely on it being a specific value).
This introduces another important principle of C. If a function is a void type, i.e. it doesn’t return a value (our getCardName and strToLowerCase functions are examples), I should never try to assign the output of that function to a variable or use it in an expression, and I will get a compiler error if I do try. If, however, the function does return a value, as sscanf does, I can assign that value to a variable of an appropriate type, as I do with with sscanf on line 96, or use it in an expression, as I do with the output of rand in line 138. But, I can also choose to ignore the returned value entirely, as I do with sscanf on line 40. Of course, whether it makes sense to do that depends on the nature of the function and how my program is using it.
There are three possible outcomes from the attempt to read the user’s input:
- The user entered either a non-numeric value, an empty string (by just pressing the return or enter key) or a negative number. I test for these conditions on line 99 and display an error message if this is the case. Note that I’m relying on short circuiting again here – varsFilled != 1 covers both non-numeric values and empty strings because in each of these cases varsFilled will hold either zero or a negative number (EOF), and in either case the second condition won’t be tested. If the user has entered a valid integer then varsFilled will be 1, so the first condition will be fulfilled and the second condition will be tested, which checks whether the number entered is either 0 or a positive integer.
- The user tried to bet more money then they have available. I test for this on line 101 and display an error message, including a reminder of the available amount, if this is the case.
- The user entered a valid bet. If this is the case, I set validBet to true on line 104 and the loop exits.
// Only pick third card if player has chosen to bet if (bet == 0) { printf("\nChicken!\n"); } else { // Pick third card int cardC = pickCard(); // Get name of third card char cardCName[6]; getCardName(cardC, cardCName); // Display third card to player printf("\n%s\n", cardCName); // Check if player has won or lost if( cardA < cardC && cardC < cardB ) { printf("You win!!!\n"); money += bet; } else { printf("Sorry, you lose.\n"); money -= bet; } } }
Once the program has the player’s bet, it will either be zero, indicating they don’t want to bet, or the amount they want to wager. I test for this on line 109, and if the bet is zero, I just display the “chicken” message and return to the top of the loop for the next round.
If the player has chosen to bet, I pick a third card and display its name. This code, between lines 116 and 120, should be familiar to you now, as it is very similar to the code for the first two cards. Finally, in lines 123 to 129, I check if the player has won or lost, display an appropriate message and either add or deduct the amount of the bet.
Lines 125 and 128 contain two operators I’ve not used yet. This line:
money += bet;
is equivalent to:
money = money + bet;
Assigning a variable with the value of itself added to another value or variable is something programmers will do a lot, so over time this operator will save me a fair few keystrokes. It’s also less error prone because I only have to type the destination variable once. As you will have worked out by now:
money -= bet;
is equivalent to:
money = money - bet;
These are known as compound assignment operators. I can also use *=, /= and %= for multiplication, division and modulus operations respectively.
You can see the source code here. You can play the game in OnlineGDB.
Well that’s been a lot to take in for one post, but hopefully it’s opened your eyes to some of the considerations I need to make when creating games in C. In the next post in this series I’ll begin developing a new game that will introduce some more advanced C skills.
Be First to Comment