Programming in C

Chapter 4, 7/6/93

The Second Program: Blackjack
It's time to move on from the compound interest program to a new project. This time I've chosen an entertaining subject: a blackjack- playing program. This may seem trivial, but in fact it requires the introduction of several new and important features of the C language.
For the unfamiliar, and to clarify the particular set of rules that will be employed, blackjack is a card game, played with a standard 52-card deck. Each turn, each player is dealt a card from the deck, and given the option of "standing" (taking no card) or "hitting" (taking a new card). If the net value of the cards in a player's hand exceeds 21, they have lost the game. If the net value of a player's hand is exactly 21, they have won the game.
The 52 cards are divided into four suits (diamonds, clubs, hearts and spades). The 13 cards in each suit are named follows: two through ten, jack, queen, king, and ace. For simplicity's sake, the four "face cards" will be valued as worth 11, 12, 13, and 14 points (ace). The numbered cards will be worth their face value.
This is a substantial project, so we will break it into several chapters, in order to introduce new concepts gradually. The first version of the program will simply create a deck of cards, display them, shuffle the deck, display the deck again, and then deal several cards off the top.
The program follows. Note that here, for the first time, we write several other functions in addition to main(), in order to split up the program into manageable pieces. Several other new techniques are introduced as well.
(Name the file in which you type in the code "bj.c".)
---
#include < stdio.h >
#include < stdlib.h >
#include < time.h >

/* The function card_print takes a card as represented by an integer
  between 1 and 52 and displays the name of the card. The actual function
  comes later; here at the beginning we give a "prototype" to
  tell the compiler what to expect. */ 
  
void card_print(int card);

/* The function deck_shuffle accepts deck of cards, as represented
  by an array of integers, and shuffles them into a random order. */

void deck_shuffle(int deck[]);

/* The function deck_draw draws a card from a deck. It returns 
  an integer between 0 and 51 representing the card. deck_draw()
  reshuffles the deck if necessary. The function accepts an
  array of cards and a pointer to the number of the top card
  in the array, which it will update after drawing a card. */

int deck_draw(int deck[], int *card_top);

/* deck_init accepts a pointer to a deck of cards and fills it
  with an initial set of unshuffled cards. */

void deck_init(int deck[]);

/* deck_print prints out all the cards in the deck. (Naturally this
  isn't used in an actual game, but the function will help us
  verify that the program is correct.) */

void deck_print(int deck[]);

/* Now the main program, which for now simply tests the 
  functions above. */  

int main() {
  /* The deck consists of an array of 52 integers. */
  int deck[52]; 
  
  /* The position in the deck of the topmost card. In a freshly
    shuffled deck, this position will be 0; when only one card
    is left, it will be 51. So deck[card_top] is the topmost
    card at any given time. */
  int card_top = 0;

  /* Counter variable. */

  int i;

  /* Card variable. */
  int c;

  /* Initialize random numbers. Take advantage of the clock to
    choose a different seed on each run. If your compiler encounters
    difficulty compiling this line, you may wish to remove it, but if 
    you do so the same sequence of cards will appear on each run. */

  srand(time(0));

  /* Initialize the deck. */
  deck_init(deck);

  /* Print out the contents of the deck. */
  printf("Unshuffled deck:\n");
  deck_print(deck);

  /* Now shuffle the deck. */
  deck_shuffle(deck);

  /* Print out the shuffled deck. */
  printf("Shuffled deck:\n");
  deck_print(deck);

  /* Now draw the first five cards. */
  printf("Top five cards:\n");
  for (i=0; (i < 5); i++) {
    /* Note the use of the & operator to pass the address of card_top,
      rather than just its value, in order to permit it to be changed. */
    c = deck_draw(deck, &card_top);

    printf("Drawing card %d: ", i);
    card_print(c);
    printf(" New position in deck: %d\n", card_top);
  }
  printf("Test complete.\n");
  /* All went well. */
  return 0;
}
 
/* The actual code for card_print(). The "prototype" given earlier defined
  the arguments and return type of the function; now we provide the
  actual implementation. */
   
void card_print(int card) {
  /* Suit of the card (0-3). */
  int suit;
  
  /* Value of the card (2-14). */
  int value;

  /* Calculate the value of the card. The % operator divides the
    number of the card by 13 and yields the *remainder* of that
    division, yielding a number between 0 and 12. */

  value = (card % 13) + 2;

  /* Calculate the suit of the card. In C, division operations
    round down, so any card number between 0 and 12, when divided
    by 13, will yield 0; any card between 13 and 25 will yield
    1; and so on. */

  suit = card / 13;

  /* Now print out the name of the card. */

  printf("the ");

  /* For the number cards (value between 2 and 10), just print 
    the number */

  if (value < 11) {
    printf("%d ", value);
  } else {
    /* Otherwise it's a "face" card; use a switch to print the names */
    switch (value) {
      case 11: 
        printf("jack ");
        break;
      case 12:
        printf("queen ");
        break;
      case 13:
        printf("king ");
        break;
      case 14:
        printf("ace ");
        break;
      default:
        /* This shouldn't happen, so say so */
        printf("Unknown card! Value: %d\n", value);
        break;
    } 
  }
  printf("of ");
  switch (suit) {
    case 0:
      printf("hearts");
      break;
    case 1:
      printf("diamonds");
      break;
    case 2:
      printf("spades");
      break;
    case 3:
      printf("clubs");
      break;
    default:
      printf("Unknown suit! suit: %d\n", suit);
      break;
  }
}

/* Implementation of deck_shuffle. */

void deck_shuffle(int deck[]) {
  /* Counter variable */
  int i;

  /* The card being swapped */
  int c;
 
  /* Position of card being swapped with */ 
  int cpos;

  /* Our shuffling algorithm will be a simple one. It doesn't produce
    a perfect distribution of cards, but neither does a real shuffling! 
    We will exchange each card with another card, once. */

  for (i=0; (i < 52); i++) {
    /* Swap with a random position between 0 and 51.
      NOTE TO EXPERTS: I know rand() is not one of the better
      random-number generators in the world, but at least it's
      available in all standard C systems! */ 

    cpos = rand() % 52;
    /* Get the card */ 
    c = deck[i];
    /* Now swap the two cards */ 
    deck[i] = deck[cpos];
    deck[cpos] = c;
  }
}

/* Implementation of deck_draw(). The second argument is a "pointer"
  to a variable containing the number of the current top card.
  This approach allows deck_draw() to modify the value. */
  
int deck_draw(int deck[], int *card_top) {
  /* Position of top card */
  int pos;
  
  /* Card to be returned */
  int c;

  /* Use the * operator, which in this context is the opposite of
    the & operator, to fetch the *value* stored at the *location*
    referred to by the pointer card_top */
  pos = *card_top;

  /* Fetch the card from the deck */
  c = deck[pos];
 
  /* Advance the card pointer */
  pos++;

  /* If we have just drawn the last card, reshuffle the deck */
  if (pos == 52) {
    deck_shuffle(deck);
    /* Which brings us back to the first card, at position 0 */
    pos = 0;
  }

  /* Now store this pos back into the location pointed to by card_top */ 

  *card_top = pos;

  /* Finally we return the card drawn */
  return c;
}

/* Implementation of deck_init() */

void deck_init(int deck[]) {
  /* Counter variable */
  int i;

  /* Fill the deck with the 52 cards, in order */
  for (i=0; (i < 52); i++) {
    deck[i] = i;
  }
}

/* Implementation of deck_print() */

void deck_print(int deck[]) {
  /* Counter variable */
  int i;
  /* Current card */
  int c;

  /* Print out the 52 cards */
  for (i=0; (i < 52); i++) {
    c = deck[i];
    card_print(c);
    /* Add a carriage return to separate cards */
    printf("\n");
  } 
}
---
Now I'll proceed through the program and explain the new features of the C language and its libraries used.
Note the new #include directives:
#include < stdlib.h >
#include < time.h >
The first is necessary to bring in the definitions of functions which deal with random numbers, which we need to shuffle the deck.
The second grants access to functions which manipulate the time of day. These are used to provide a different sequence of random numbers on each run. (Random number generators require a "seed" number, and will always produce the same sequence given the same seed.)
Now consider the following line:
void card_print(int card);
This line is a "function prototype." In this project, unlike the previous one, we break the program up into several distinct functions to perform various tasks. This allows a short, understandable main() program to be written, and places the details in separate, manageable packages.
In order to make the program easier to read, we place the main() function first, so the program can be immediately understood at a general level before tackling the details. The C language allows us to do this, but only if we give "prototypes" for each function that will be called. The prototype gives the return type of the function, the name of the function, and its list of arguments, which the compiler can use to check the accuracy of calls made to the function.
Function prototypes always end in a semicolon. This distinguishes them from actual functions, which are followed by code between braces ( "{" and "}" ).
In this case, the function returns nothing, so its return type is "void". The function accepts one integer as an argument, so "int card" is specified. (The name of the argument is optional in a prototype, but providing a meaningful name helps make the purpose of the function clear.)
Now consider the prototype for deck_shuffle():
void deck_shuffle(int deck[]);
Once again, the return type is void.
The one argument, "deck", is an array of integers.
An array is like a numbered row of filing cabinets. To fetch something from one of them, you need to know the number of the cabinet. To represent a deck of cards, I use an array of 52 integers.
The "[]" characters after the name of the argument declare it to be an array.
Unlike simple variables, when an array is passed to a function, the array is the very same one that was passed from the calling function, not a copy of it. This is primarily because arrays can be quite large, and copying the entire array in order to pass it to a function is impractical and inefficient.
As a result, a function can change the elements of an array and expect that these changes will still hold true in the function which called it ( main() or any other function). deck_shuffle() will take advantage of this to shuffle the deck.
Now consider the next prototype:
int deck_draw(int deck[], int *card_top);
This function, unlike those preceding it, does return a value, in this case an integer representing the card drawn from the deck. (I have chosen to represent cards with integers between 0 and 51.)
The first argument is the array of cards, as described above. The second is a "pointer" to a variable which holds the index of the first card in the deck.
A pointer is a special type of variable which, instead of containing a value, contains the *location* of a value.
"HUH?"
All right, listen closely. This is the most important concept in C programming, but fortunately it is not really that difficult to understand.
Imagine a friend wants to borrow your bicycle. There are two basic ways you can deal with this (besides saying no!):
You can buy him a brand-new bicycle identical to your own and give it to him. Needless to say this is not very practical.
*OR*, you can simply tell him where your bicycle is and give him the combination.
Alternatively, you may want your friend to *fix* your bicycle. In this case you have no choice: to fix the bicycle your friend needs to know where to find it!
A pointer is a piece of paper on which we have written down the *location* of something useful-- a bicycle, a house, or some other object that we either can't afford to copy or want to be modified by others.
Returning to our function prototype,
int *card_top
declares a *pointer* to an integer, giving the function the *location* of the integer instead of a copy of it. This way deck_draw() will be able to change which card is on top, and the change will affect the calling function as well.
Finally, we have:
void deck_init(int deck[]);
which declares a function that will be used to initialize the deck of cards with ascending values in ascending suits, and
void deck_print(int deck[]);
which will be used to print out the entire deck of cards, in order to help us decide if the program is correct.
Now we can move on to the main() function. Note the first variable declaration:
int deck[52];
This line declares an array of 52 integers.
Arrays, as discussed earlier, behave like a set of filing cabinets; you fetch something from the cabinets by knowing the number of the cabinet in which it is kept.
In the C language, "indexes" in an array begin at *ZERO* (NOT ONE). So in the deck[] array, above, the 52 integers are numbered from 0 through 51.
To refer to a particular item in an array, you follow the name of the array with a number in brackets ("[" and "]"). So the fifth element in the array above is:
deck[4]
(remember that indexes in an array begin at 0, so the fifth element is at index 4.)
Now note the following declaration:
int card_top = 0;
Here we have taken advantage of a convenience feature of C by specifying a value for card_top at the time we declare it. This is permissible, but the expression on the right-hand side of the "=" must be "static." A simplified explanation of this is that there should be no variables in the expression, so:
int x = 47+28/2;
is acceptable, but:
int x = y;
is *not* acceptable. (Of course x = y; is completely acceptable as a statement, *after* all variables have been declared!)
Now skip ahead to the following definition:
  srand(time(0));
This statement first calls the time() function, which returns the number of seconds that have elapsed since January 1st, 1970. It then passes the result to the srand() function, which accepts a seed and uses it to set up the random number generator. We use the time to do this in order to have a different series of random numbers, and thus a different shuffling of the deck, on each run of the program. As an experiment, try removing this line; you will find that the program produces identical results on every run.
Now consider the following:
deck_init(deck);
Here we call our deck_init() function to initialize the deck[] array.
Note that when passing an array to a function, no "[" and "]" characters are used. To pass the entire array we simply give the name of the array.
Notice how simple the main() function appears, due to the fact that the details have been "abstracted away" into separate functions.
In the same vein, we next invoke deck_print(), deck_shuffle(), and (again) deck_print() on our deck of cards, in order to test our functions, which together make up a family of tools for manipulating cards and decks of cards.
Finally we use a for loop to draw the first five cards. Inside the loop we use our deck_draw() function:
c = deck_draw(deck, &card_top);
Note the use of the & operator to pass the *location* of the variable card_top to the deck_draw() function, just as we passed the locations of variables in Chapter 3 to scanf() in order to store the user's input.
We then print out the card we have drawn:
printf("Drawing card %d: ", i);
card_print(c);
printf(" New position in deck: %d\n", card_top);
note the use of the %d specifier of printf() to output integers. The call to our card_print() function comes in between in order to place the name of the card on the same line; note the deliberate lack of a carriage return at the end of the first printf() call.
We print out the value of card_top in order to verify that it has been changed by deck_draw to reflect the new top card in the deck.
After the for loop, main() returns to the operating system with the usual success value.
Now consider the function card_print:
void card_print(int card) {
The first line looks much like the prototype; the difference is the opening "{" at the end, which signals that this is the beginning of the actual code of the function.
card_print() declares its own variables, just as main() does:

int suit;
int value;

Note that these variables are COMPLETELY INDEPENDENT of the variables in any other function. We can declare variables by the same name as those in other functions and be confident that they will not interfere with those functions. Those who are used to older line-numbered versions of BASIC may initially find this confusing but it should come as a great relief to those tired of chasing down conflicts between identically- named variables.
Now consider the following:
  value = (card % 13) + 2;
Here we compute the value of the card by using the % operator, which returns the remainder of an integer division (a number between 0 and 12 in this case), and then adding two to bring the value up to the range 2-14.
Next, we compute the suit:
suit = card / 13;
The suit of the card is represented by a number between 0 and 3. We get this number by dividing the card by 13, and taking advantage of the fact that division rounds down in C. Thus cards between 0 and 12, divided by 13, yield 0, while those between 13 and 25 yield 1, and so on.
The next several lines print the beginning of the card's name, then check to see if the card is a face card or a number card. If it is a number card, the value is simply printed out. If it is a face card (the "else" clause), then a "switch" statement is used to determine which face card it is.
The switch statement is useful for deciding between a large number of simple cases, such as the possible face cards. A sequence of if statements could be used instead to test each possible value, but switch is more convenient when there are a large number of possible values.
A switch statement consists of the "switch" keyword, followed by an expression in parentheses, followed by a series of "case" clauses and an optional "default" clause, all contained within a set of braces ("{" and "}"):
switch (value) {
  case 11: 
    printf("jack ");
    break;
  case 12:
    printf("queen ");
    break;
  case 13:
    printf("king ");
    break;
  case 14:
    printf("ace ");
    break;
  default:
    /* This shouldn't happen, so say so */
    printf("Unknown card! Value: %d\n", value);
    break;
}
Here the expression in parentheses is simply the value of the card. A case clause begins with the keyword "case", followed by an integer expression, followed by a colon, followed by a sequence of statements, usually ending in a "break" statement.
When the switch statement executes, it evaluates the expression, and then executes the case clause whose expression matches that value. Thus, in this example, if the variable "value" contains 11, then "jack" will be printed.
IMPORTANT NOTE: the "break" statement is necessary to prevent the program from "falling through" and executing the *next* case clause as well! In sophisticated programs this technique is sometimes taken advantage of deliberately, but this is usually not intended and is a very common programming error.
If no case clause matches the value of the expression, then the "default:" clause will be executed. In this case, we have reason to believe no card's value should ever fall outside the range we have provided for, so we print an error message in the default clause.
To round out the function, we use a similar switch statement to print out the name of the suit.
Now consider the next function:
void deck_shuffle(int deck[]) {
Again, the first line of the function differs from the prototype only in the "{" that signals this is the beginning of the actual code for this function. deck_shuffle() accepts an array of cards to be shuffled and returns nothing.
Following the variable declarations, deck_shuffle() uses a simple for loop through the 52 cards (indexed from 0 to 51). At each card, it chooses another card at random from the deck, fetches it, and swaps it with the card at the "i"th position:
cpos = rand() % 52;
This statement fetches a random integer, then uses the "%" operator to take the remainder when dividing by 52. This will be a number between 0 and 51, and is the position of the card we are swapping the "i"th card with.
c = deck[i];
Here we fetch the "i"th card out of the array. Note the use of the "[]" operator to retrieve an individual integer from the array.
deck[i] = deck[cpos];
deck[cpos] = c;
Here we swap the cards, by assigning the card at position cpos to position i, and vice versa.
(If we had simply done the following:
deck[i] = deck[cpos];
deck[cpos] = deck[i];
then the original value of deck[i] would have been lost before the second statement could execute. This is why we store the value in the intermediate variable "c".)
Now consider deck_draw:
int deck_draw(int deck[], int *card_top) {
This function, as discussed earlier, accepts a deck of cards and the *location* of the variable which records the index of the top card (a "pointer" to that variable).
Consider the following line:
pos = *card_top;
Here we use the "*" operator, which in this context means "value at." Placing "*" in front of a pointer yields the actual value stored at the location pointed to. In this way we fetch the index of the top card and place it in the variable "pos" for our convenience.
Next we fetch the card at that position from the array:
c = deck[pos];
And then advance to the next card, using the "++" increment operator:
pos++;
The if statement that follows checks to see if we have reached index 52. Since the cards are indexed from 0 to 51 in the array, this means we have passed the last card; the bottom card has been dealt from the deck. Accordingly, we call deck_shuffle() to reshuffle the deck, and we set pos back to 0.
Finally, consider the following:
  *card_top = pos;
Again, we use the "*" operator to access the "value at" card_top, and store our updated version of pos at that location. This updates card_top in the calling function, so it will remain changed when deck_draw() returns.
Finally, the deck_draw() function returns the value of c, the card it was originally asked to draw from the deck.
The function:
void deck_init(int deck[]) {
initializes the deck by setting the 52 elements of the array to ascending values. Recall that in C, variables are not automatically assigned any meaningful value. It is important to initialize the deck properly.
Finally, the function:
void deck_print(int deck[]) {
prints the name of each card in the deck. It does this by invoking card_print() on each card in a simple for loop.
Phew!
Now, go ahead and compile and run the program.
"The output scrolled off my screen!"
Well, yes. Since we print out the entire deck of cards twice, the screen is likely to scroll. If you are using a workstation or Macintosh, you'll have no problem using scrollbars to review the results. (The first printout of the deck should be unshuffled, the second shuffled.) If you are using a terminal on a Unix system, or an MSDOS system, or any other operating system that supports Unix-like pipes, and you have named your program "bj", try the following command at your operating system shell prompt:
bj | more
This will display the output a page at a time for your review.
FURTHER EXPLORATION
Try removing the srand() call in the main() function. Notice the effect this has on several consecutive runs of the program.
Experiment with other approaches to shuffling the deck in deck_shuffle(). See if you can find one that more realistically mimics a human dealer.
Add a printf() call to deck_shuffle() that alerts you that it has been called, and then change the for loop in main() to draw several hundred cards from the loop. Note that the deck reshuffles every 52 cards.
<< Previous || Next >>