Homework #4

Due Date: Monday, May 2nd at 9pm.

The following homework exercises are intended to help you practice some of the programming concepts introduced in week 5, including enums, unions, tagged structs, and switch statements.

Getting Started

You will be using a new repository for each homework in this course. Please follow the instructions in the Ed post “Homework #4 now available” to set up your Homework #4 repo.

Compiling and testing your code

We will continue to use the testing infrastructure that you are now accustomed to. For a refresher, please read through the “Compiling and testing your code” section of Homework #1. To run the Homework #4 tests, you will need to replace homework1 with homework4 as needed.

You also might want to run your code with LLDB and Valgrind to aid in debugging. Please see the “Debugging with LLDB and Valgrind” section of Homework #3 for instructions on how to run your code with these tools.

Exercises

In this assignment, you will work with enums, unions, and structs that implement playing cards of the type used for card games in the United States. Each playing card has a suit (Hearts, Diamonds, Spades, or Clubs) and a rank (Ace, 2-10, Jack, Queen, or King). In addition, there is a special card that has neither a suit or a rank called a Joker. The cards with rank Jack, Queen, or King are called “face cards” and the cards with rank Ace or a numeric value 2-10 are called “numbered cards”.

If you are familiar with a different variation of playing cards and this description does not match your experience, you may want to briefly review the style of playing cards used in the US further, but this explanation is intended to tell you everything needed for this particular assignment.

We will use the following types to represent these cards:

enum face {
   JACK, QUEEN, KING
};

enum suit {
   HEARTS, DIAMONDS, SPADES, CLUBS
};

enum card_tag {
   FACE, NUMBERED, JOKER
};

struct face_card {
   enum face rank;
   enum suit suit;
};

struct numbered_card {
   int rank;
   enum suit suit;
};

union card_type {
   struct face_card f;
   struct numbered_card n;
};

struct card {
   union card_type type;
   enum card_tag tag;
};

A union card_type stores information about the rank and suit of a non-joker card, using the appropriate variant depending on whether the card is a face card or a numbered card. The enum card_tag of a Joker will simply be set to JOKER and the union card_type will not be used at all. For a numbered card, the rank is stored as an integer. In our implementation, the rank of Ace is 1.

For examle, here is code to create the Queen of Hearts, the Ace of Spades, and a Joker:

struct card queen;
queen.tag = FACE;
queen.type.f.rank = QUEEN;
queen.type.f.suit = HEARTS;

struct card ace;
ace.tag = NUMBERED;
ace.type.n.rank = 1;
ace.type.n.suit = SPADES;

struct card joker;
joker.tag = JOKER;

We have added the constructors make_face_card, make_numbered_card, and make_joker to student_test_homework4.c. You can use these functions to create cards while you debug and test your code.

Throughout this assignment, you may assume that pointer parameters point to valid data and are not NULL. You can also assume that the value of the length of an array passed as parameter to a function is indeed the length of that array. Throughout this assignment, you should not use functions from C libraries unless it is explicitly stated that you can do so in a task.

Restriction Throughout this assignment, you must use switch statements to choose amongst enum types. That is, you should not use an if statement to compare enums for equality, as in:

if (queen.type.f.suit == HEARTS)

You must use a switch instead. You may use if statements in other contexts, like when you’re comparing integers:

if (ace.type.n.rank < 5)
  1. Complete the function valid_card which takes a struct card and returns true if the card is a valid playing card, and false otherwise. A playing card is valid if it is either a Joker or it is a card with valid suit (Hears, Diamonds, Spades, or Clubs) and a valid rank (Ace, 2-10, Jack, Queen, or King).

    For the remaining problems in this assignment, you can assume that all cards are valid cards.

  2. Complete the function cut which takes a “deck of cards” (an array of struct card) and the length of the array and “cuts” the deck of cards in-place. To cut a deck of cards, take the deck and split it in half, then place the former second half on before of the former first half. You can assume that the length of the array is even.

    Consider an example using integers. Cutting the array:

    {1, 2, 3, 4, 5, 6}

    results in:

    {4, 5, 6, 1, 2, 3}

    Before cutting, the first half of the array contains the values 1, 2, 3 and the second half contains 4, 5, 6. After cutting, the first half contains 4, 5, 6 (the former second half) and the second half contains 1, 2, 3 (the former first half).

  3. A hand of cards is called a “flush” when all of the cards in a hand have the same suit. Complete the function:

    enum suit flush(struct card *hand, int num_cards)

    which takes a “hand of cards” (an array of struct card) and the number of cards in the hand. If the hand is a flush of any suit, this function should return the enum suit that created the flush. You can treat Jokers as “wildcards” that match with any suit. You may assume that there is at least one non-Joker card in each hand. Return -1 if the hand is not a flush.

  4. Complete the function

    bool four_of_a_kind(struct card *hand, int num_cards)

    which takes a hand and the number of cards in the hand and returns true if the hand contains at least four cards of the same rank, and false otherwise. You can again treat Jokers as wildcards that match with any rank.

    For example:

    • The hand with the 9 of Hearts, King of Clubs, 9 of Clubs, and 9 of Diamonds should return false.

    • The hand with the Jack of Diamonds, 4 of Spades, Jack of Clubs, King of Hearts, Jack of Spades, and Jack of Hearts should return true. Note that the Jacks do not need to be adjacent for the hand to contain a four-of-a-kind.

    • The hand with the Jack of Diamonds, 4 of Spades, Jack of Clubs, King of Hearts, Jack of Spades, and Joker should return true. In this example, the Joker served as the fourth Jack.

    Restriction This work can be done in linear time, that is, in time proportional to the length of the array. Your implementation is allowed to make exactly one pass over the array hand. You may use multiple loops in this problem, but only one of them should iterate over the array hand.

The next two exercises involve the card game “Twenty-one”. The goal of Twenty-one is to have a high scoring hand of cards that does not exceed 21. Here is how cards are scored:

  • Jokers are worth 0 points

  • Face cards are worth 10 points

  • Numbered cards (except Ace) are worth their rank values

  • Aces are worth 1 or 11

You will need to take Aces into account and score them so that the hand has its highest possible total without going over 21, if possible. There can be more than one Ace in a hand.

Here are some examples:

  • The hand with the King of Hearts, 7 of Hearts, and 3 of Clubs scores 20 points.

  • The hand with the King of Hearts and Ace of Diamonds scores 21 points.

  • The hand with the King of Hearts, Ace of Diamonds, and 4 of Clubs scores 15 points. In this example, the Ace is scored as a 1 because using an 11 would exceed 21 points.

  • The hand with the 5 of Hearts, Ace of Diamonds, and Ace of Spades scores 17 points. In this example, one Ace is scored as a 1 and the other an 11 (it does not matter which is which).

  • The hand with the King of Hearts, Ace of Diamonds, and Ace of Spades scores 12 points. In this example, both Aces are scored as 1s.

  • The hand with the King of Hearts, Queen of Hearts, Ace of Diamonds, and 4 of Clubs scores 25 points. This is the smallest possible score, although it still exceeds 21.

Restriction You must implement a helper function to simplify the two tasks below. Think about what they have in common and write a helper function to perform common tasks. Your helper function should be properly documented. That is, you should include a header explaining the purpose of the function, the expected input, and the function’s return value.

  1. Complete the function bust which takes a hand of cards and its length and returns true if player “busts”, and false otherwise. In Twenty-one, a player busts if their hand has more than 21 points.

  2. Complete the function

    int winner(struct card *hand1, int num_cards1, struct card *hand2, int num_cards2)

    which takes two hands of cards where hand1 of length num_cards1 belongs to Player 1 and hand2 of length num_cards2 belongs to Player 2. winner should return:

    • A negative number if Player 1 wins

    • Zero if Player 1 and Player 2 tie

    • A positive number if Player 2 wins

    In Twenty-one, a player wins over another when they have a higher score than the other player without going over 21 points. If both players bust, it’s a tie.

Grading

For this assignment, the weights will be:

  • Completeness: 60%

  • Code Quality: 40%

The code quality score will be determined by code clarity, style, and whether your implementation stays within the allowed constructs for each exercise.

Preparing your submission

There are three things you should do before you submit your work.

First, make sure you have added the required information to the header comment in homework4.c:

  • your name,

  • the names of anyone beyond the course staff you discussed the assignment with, and

  • links to any resources that you used other than course materials and the course textbook.

Please remove the explanation for what is required. Note: write None in the relevant field, to indicate that you did not use any sources and/or consult anyone beyond the course staff.

Second, remove directives, such as:

// YOUR CODE HERE

from your code. Make sure to recompile and rerun the tests after you make these changes. It is easy to introduce a syntax error at this stage.

Third, add, commit, and push your work to GitHub.

And finally, get your directory into a clean state. Run make clean to remove any executables that are laying around.

Submission

To submit your work, you will need to add, commit, and push your code to GitHub. Before you upload your code to GitHub, run git status . to make sure you did not forget to add/commit any of the required files. Then upload your submission to Gradescope under the “Homework #4” assignment. Make sure to choose the right assignment on Gradescope!

You are welcome to upload your code to Gradescope multiple times before the deadline, but it is wildly inefficient to use Gradescope as a compute server. You should test your code on the CS Linux machines and only upload it when you think you are finished or when you are getting close to the deadline and want to do a “safety” submission.

You are responsible for making sure that the code you upload compiles and runs. You will get zero credit for code that does not compile.

Finally, a reminder that we will not accept late work.