CS 5007: Assignment 4 (Part 1 and 2)
Summer 2019
Part 1 DUE Jun 5, 2019, 6pm.
Part 2 DUE Jun 12, 2019, 6pm.
Objectives
- Implementing applied data structures
- Utilizing Valgrind to ensure clean memory
- Practice with
mallocandfree - Working with
assertto test your code
Summary/Overview
In this assignment, you are going to implement some key components to a card game. The card game is NEUchre (pronounced "N-E-U-ker"), which is a 2-player version of a game called Euchre (pronounced "U-ker"). To get a sense of the game, you can play the traditional version at https://cardgames.io/euchre/.
There are two parts to this project, and many options for a challenge. Part 1 requires you to implement some data structures for the game. Part 2 requires you to implement some logic to support the game play.
Once you've implemented these two parts, you can play a simple version of the game. Solving some of the Challenges at the end of this assignment will build on the game and make it more fun!
Relevant Reading
- Pointers and Memory by Nick Parlante. Review Section 1 and 2 if you need; read Section 3 and 4.
- Yale Notes, Pointers. Section 4.9, through 4.9.7. (Some of this includes last week's reading, which is always helpful to review)
- Using Valgrind
- Linked Lists and Simple Data Structures
- C Tutorial, Section 8 (Pointers and Heap); Section 9 (UDTs and Structs); Section 13 (Data Structures)
Background
Basic rules of NEUchre
NEUchre is a 2-player game. The player with the most points after 5 rounds wins. A player wins a round by "collecting the most tricks". A round consists of each player being dealt 5 cards, choosing a trump (a suit designated as the highest/most powerful for this round), and then each player takes turns playing cards for a "trick". A player wins a trick by playing the highest value card. There are a couple of rules that must be followed for playing a card: the first player can play any card, but the second player must "follow suit": if they have a card of the same suit, they must play it (but they can choose which card they want to play). Further, a card of the trump suit is higher than all other cards in the deck. For example, if Spades is trump, a 9 of Spades is higher than an Ace of Diamonds. Within a suit, including trump, the face value of the card is highest, with Aces being the highest value card.
In this version of the game, Player 1 (the computer) ALWAYS goes first and leads the first card.
The deck used for NEUchre is a subset of the traditional 52-card deck. It includes the Ace, King, Queen, Jack, 10 & 9 of all four suits (Spades, Diamonds, Hearts, and Clubs).
We've provided support code to play the game; you need to fill in some very specific pieces of code.
Details
You'll see that in a4.h, we've begun to define some data structures for you. Cards have Suits and Names. A Deck is a Stack of Cards, and a Hand is a doubly-linked-list of list nodes called CardNodes.
Suits have both a Name and a Color. Cards have a Name, Suit and Value.
Part 0: Check out starter code and copy into your repo
The CS 5007 resources repo has some starter code for this assignment. Check it out (clone into it's own folder/local repo), and copy the a4 folder over to your own personal repo.
Part 1: Implementing the Game Data Structures
Implementing the Deck
The basic struct for Deck is already there, but you need to implement the Deck functions yourself.
Add your code to deck.c.
Implement the Deck as a Stack based on an array. For this implementation, aDeck->topCard is always pointing to the card at the top of the stack; when it's initialized (there are no cards in the stack), aDeck->topCard should equal -1.
You are asked to implement the following stack functions that will form the stack:
-
Deck* createDeck()Creates the deck, initializing any fields necessary. Returns a pointer to the deck, which has been allocated on the heap. Deck* pushCardToDeck(Card*, Deck*)Adds a card to the top of the deck. Returns a pointer to the deck.Card* peekAtTopCard(Deck*)Shows the top card, but does not remove it from the stack. Returns a pointer to the top card.Card* popCardFromDeck(Deck*)Removes the top card from the deck and returns it. Returns a pointer to the top card in the deck.int isDeckEmpty(Deck*)Determines if the deck is empty. Returns 0 if the Deck has any cards; 1 otherwise.void destroyDeck(Deck*)Removes all the cards from the deck and frees() them, preventing a memory leak
Part 2: Implementing the Hand
Implement the hand in a4.c.
The Hand has the following characteristics:
- It can be ordered in various ways
- We can easily add new cards and remove cards from the hand
For this assignment, implement the Hand as a doubly-linked list.
Hand* createHand();Creates a Hand struct and initializes any necessary fields. Returns a pointer to the new Hand, which has been allocated on the heap.void addCardToHand(Card *card, Hand *hand);Adds a card to the hand.Card* removeCardFromHand(Card *card, Hand *hand);Removes a card from the hand. Return a pointer to the card that's been removed from the hand. Consider if you need to remove the CardNode from the heap.int isHandEmpty(Hand*);Determines if there are any cards in the hand. Return 1 if the hand is empty; 0 otherwise.void destroyHand(Hand*);Destroys the hand, freeing any memory allocated for it.
Part 3: Implementing Game Functions
Now that you have some of the data structures, write the code that uses them to make the game run.
The following functions should be implemented in a4.c:
int isLegalMove(Hand *hand, Card *leadCard, Card *playedCard);
Given a card that is lead, a player's hand, and the card the player wants to play, is it legal? If the player has a card of the same suit as the \texttt{leadCard}, they must play a card of the same suit. If the player does not have a card of the same suit, they can play any card. For this, it might be helpful to define a helper function that determines if a hand has any cards of a given suit.-
int whoWon(Card *leadCard, Card *followedCard, Suit trump);
Given two cards that are played in a hand, which one wins? If the suits are the same, the higher card value wins. If the suits are not the same, player 1 wins, unless player 2 played trump. Return 1 if the person who led won, 0 if the person who followed won.
These functions should be implemented in deck.c:
-
populateDeck();
Create all the cards and pushes them into the Deck. -
shuffleDeck(Deck*);
Takes all the cards in the deck, rearrange the order, and push the cards back into the Deck. -
deal(Deck*, Hand*, Hand*);
Takes a shuffled deck and deals the cards one by one to the each hand.
Part 4: Using Valgrind to verify you have no leaks
Since we're starting to use the heap, it's time to learn how to use Valgrind.For the rest of the semester, we'll be testing whether your code has any memory leaks. We use Valgrind for this.
First, install Valgrind:
bash% sudo apt-get install valgrind
Try out Valgrind for yourself, to make sure you can run it:
bash% valgrind --leak-check=full ./hello_world
You want to see that there are no memory leaks. Your output should be something like this. Note that the HEAP SUMMARY notes that there is no heap in use at exit.
==8114== Memcheck, a memory error detector ==8114== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==8114== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==8114== Command: ./example2 ==8114== aThing is at 0xfff0003e0 with value 6 and age 42 ==8114== ==8114== HEAP SUMMARY: ==8114== in use at exit: 0 bytes in 0 blocks ==8114== total heap usage: 2 allocs, 2 frees, 1,032 bytes allocated ==8114== ==8114== All heap blocks were freed -- no leaks are possible ==8114== ==8114== For counts of detected and suppressed errors, rerun with: -v ==8114== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Part 5: Check style issues
Please be sure to follow the Course Style Guide for your C code.
Since you used clint for the last assignment, it is still available for you to use this assignment. If you followed the directions last time, you DO NOT need to copy clint.py to your a4 directory this time.
Run clint as such:
bash% clint.py a4.c
No style-checking tool is perfect, but you should try to clean up any problems that clint detects unless clint flags something that is definitely not a problem in this particular context (but be sure you have very good reasons to ignore any warnings).
Be sure to check all the files you write.
Part 6: Challenges
If you've implemented the above, consider the following improvements to the game. Note: currently, tests haven't been written to test the challenge functions. These will be added later, or you can write your own.Submit these changes with your final assignment submission. Partial implementation of any of these will be reviewed. Progress will be considered for your final grade. Not attempting any of these will have no impact on your assignment grade or final grade.
Challenge 1: Shuffle the computer's hand so it is unpredictable
Difficulty: Medium
Implement void shuffleHand(Hand*) in a4.c:
Right now, the computer (Player 1) is only playing the first card in the hand. Implement shuffleHand(), which will random a hand of cards. The computer will then play a random card.
Reflection question: You implementedshuffle() on the Deck, which is a Stack. How is this similar or different from how you can shuffle a Hand, which is a LinkedList?
Challenge 2: Sort a hand in terms of value
Difficulty: Medium
Implement void sortHand(Hand*, Suit) in a4.c:
A nice feature would be to print out the hand for the user (Player 2) in descending order of value. Given a Hand and a suit for trump, sort the hand so that it is in decreasing value, with cards of the trump suit first, then all other suits.
Challenge 3: Replace Deck using a LinkedList
Difficulty: Medium
In this quiz, you implemented both a linked list (Hand) and a stack (Deck). Create a new deck.c file (call it deck_2.c), and re-implement Deck. This time, instead of using an array to back the Deck, use a linked list. You can re-use the CardNode used in the Hand implementation. To test and use your implementation of deck_2.c, modify the Makefile by replacing deck.c with deck_2.c. Note, some of the tests for Deck will fail because they are checking your array implementation.
Challenge 4: Be smarter about calling trump
Difficulty: Hard
Right now, trump is decided randomly. In original Euchre, the process for calling trump requires the dealer and the other player to take turns having the opportunity to call trump in a structured way.
The process is like this:
- After the deal, show the top card in the deck.
- The player that is not the dealer looks at that card and decides if they want that suit for trump.
- If the player wants the trump, they "order it up".
- The dealer adds that top card to their hand.
- The dealer discards one card.
- Play begins.
- If the player does not want the top card for trump, the dealer gets to decide. If the deal wants that card, the process is the same as above (dealer picks up the card, and discards another card).
- If the deal does not want the top card suit for trump, the top card is turned over.
- The player gets to "call trump" or choose a suit.
- If the player declines to call trump, the dealer must call trump.
getTrump(Card*) function in a4_run.c to use this process. Look at the other functions for examples of how to get input from the user.
Challenge 5: Give the computer player strategy
Difficulty: Hard
getBestMove():
Given a hand of cards, the card that was just led, and the current trump, pick the best card in the hand to play. Note: there might be some strategy in this one!
Submit your code to git by pushing
- Pull your repo to get any files added by TAs
- Add the new files in the a4 directory to git.
- DO NOT push any executables (the files that are output after you compile, and the ones that you run). You will lose points if you do.
- Commit the changes:
git commit -am 'adding a4 code'. (You can and should do this many times throughout the process of doing a4) - Push the changes to the remote git repo:
git push
git tag 'a4-final', then git push origin a4-final to push the final tag to the repo so we can see it. Milestone 1 deliverables: Due 5 June 2019, 6pm
- Parts 0 and 1 (Getting started, Implement the Deck)
- Implementing the Deck
- Tag on github of
a4-part1 - Relevant tests passing:
TestCreateDeckTestPushCardToDeckTestPopulateDeckTestPeekAtTopCardTestPopCardFromDeckTestIsDeckEmpty- Valgrind working/being clean
- Make sure valgrind gives a clean memory report for the above tests.
- Everything else.
- Implementing the hand functions
- Implementing the game functions
- Tag on github of
a4-part2 - Valgrind still working/being clean
- Make sure valgrind gives a clean memory report for the all tests.
Hopefully Helpful Tips
This time we're giving you a more substantial test file.
makewill build all the codemake run_testwill run the testsmake run_playwill run the game so you can play it- The output from the tests can be verbose; sometimes I use
./test | grep FAILEDto only see the lines of tests that failed, rather than ALL the output. - Start by stubbing out all the functions. Then your code will compile and run, even if the tests don't pass.
- In the file
a4_test.c, you'll see in the main function thatPart2Tests()has been commented out. When you're ready, uncomment that line to start running the Part 2 tests.
Submission Details
- In your class git repo, we want to see a directory called "a4". In that folder, we want to see:
a4.ca4.hdeck.ca4_helpers.ca4_test.ca4_run.cMakefile
- Any other files
- Any executable file (e.g.,
fsort) - back up files (.DS_Store, foobar.c~, ...)
- Make sure your repo is tagged! It must be tagged
a4-final, and pushed before the assignment deadline.
In general, you should only need to modify a4.c and deck.c. You may modify a4_test.c to write your own tests, but be aware that we will not necessarily use your test file to test your code. If you do some of the challenges, you may be changing a4_helper.c or a4.run
You may also modify your Makefile for your dev process; once again, don't assume we will use your Makefile to test or run your code.
Notes on submitting Part 1 versus Part 2:
- Submit the same thing for both parts
- We will not expect the
Helpful Hints
- The first step is usually to put the function definitions in the .c files, with the body of the function doing nothing. When a function has a return, just
return NULL. This will get you to a point where the code will compile. - Start building by running the tests. It's usually helpful to comment out the tests (in
a4_test.c), and uncommenting them one by one, from the first to the last. When the first test passes, uncomment the next and work on making that one pass. - When you get a test passing, run Valgrind on your code at that point. This will help you make sure you don't have leaks at the end, and not knowing where they came from.