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)
Complete the function
valid_card
which takes astruct card
and returnstrue
if the card is a valid playing card, andfalse
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.
Complete the function
cut
which takes a “deck of cards” (an array ofstruct 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).
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 theenum 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.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, andfalse
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 arrayhand
.
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.
Complete the function
bust
which takes a hand of cards and its length and returnstrue
if player “busts”, andfalse
otherwise. In Twenty-one, a player busts if their hand has more than 21 points.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 lengthnum_cards1
belongs to Player 1 andhand2
of lengthnum_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.