Shoot me an email so I can email you the project4_start file at email@example.com
For this project, you will be writing code to mimic the strategy you use or might use when playing a game called Uno.
(Note you will be turning in one class from a group of classes I am providing to you as a starting point. You will not be writing a main method as you have in past projects and labs. You will be implementing two methods within a class that will eventually be called by a main program.)
If you have never played Uno, you should read the Wikipedia article (Links to an external site.) to get informed. Briefly, Uno is a popular card game in which each player holds a hand of cards, and tries to be the first one to "go out," or play all of their cards. Players are seated in a ring, and in the middle of this ring is an "up card," or a card placed on the table face up. Players take turns in sequence, clockwise around the ring. When it is your turn, you have the opportunity to play one of your cards on this up card (which will then become the new up card) and thus reduce the size of your hand. The card you play, however, must be playable on the up card, according to the following rules:
- Most cards have a color -- red, green, blue, or yellow -- and you may play a card if it has the same color as the up card.
- The colored cards each have a rank -- either a number from 0-9, or else a special "skip," "reverse," or "draw 2" rank. You may play a card if it has the same rank as the up card, even if it is of a different color.
- There are two kinds of "wild" cards: ordinary wilds, and "draw 4" wilds. Either kind can be played on any up card. When you do so, you "call" a new color, specifying what color the next player must play.
Some special cards have an effect after being played:
- If a "skip" card is played, the next player in sequence is skipped.
- If a "draw two" card is played, the next player in sequence must draw two cards from the deck, and is then skipped.
- If a "wild draw four" card is played, the next player in sequence must draw four cards from the deck, and is then skipped. (The player who played the "wild draw four" must then call a color as with a normal wild.)
- If a "reverse" card is played, the sequence of players is reversed to counterclockwise (or back to clockwise, after an even number of reverses.)
The object of the game is to run out of cards. When this happens, the player going out is awarded points based on the cards remaining in the opponents' hands. These points are calculated as follows:
- For every numbered card held by an opponent, the winner of the round gets points equal to that number. (5 points for a 5, no points for a 0, etc.)
- For every "special" colored card (draw two, reverse, and skip), the winner of the round gets 20 points.
- For every "wild" card (either normal, or draw four), the winner of the round gets 50 points.
Normally, players continue playing hand after hand until one player reaches 500 points, and is declared the overall winner of the game.
A colleague has written an Uno simulation game we are going to borrow. It simulates shuffling a deck, dealing hands to players, drawing an initial up card, enforcing all of the rules above, declaring a winner, and calculating scores. The only thing it does not do is actually choose a card to play from a hand (or call a color if a wild is played.)
Every student in the class will be writing their own code to do those two things: play a card from a hand, and call a color in case a wild was played. The simulation program will run the game, and then at the appropriate points, call your method(s) to do those two things. In this way, your program will be able to "play" Uno against your classmates in a tournament. Whoever has the best algorithm for playing a card should win.
You may object to that last statement, claiming that luck is a major factor. This would be true except for one thing: I am not going to pit your Uno program against your fellow students' programs in just one game, but in 50,000 straight games. This will admittedly take a few seconds. But over that many games, any "lucky deals" that any one player might get will even out over time, leaving a superior algorithm with the lead.
- Download this Project4_start.zip download. It is an already set-up BlueJ project. Save the unzipped folder wherever you like to save your projects. To open the whole project in BlueJ, click on the package.bluej file. (If you use a different IDE, copy all the .java files from the unzipped folder to a project created in your IDE.)
- Find the class called
jzeitz_UnoPlayer. Open this file. On the line with
public class jzeitz_UnoPlayer, change jzeitz to your UMW userid. Please do not name the class anything else. Please do not capitalize it differently, or use a hyphen instead of an underscore, or deviate from this naming convention in any way.
- Open up the
Uno class. Replace "jzeitz" with your actual UMW userid. This class represents one of many scenarios (hands of cards plus chosen "up card") that you will use to test your methods.
- Open up the
TestCaseProcessor class. Replace "jzeitz" with your actual UMW userid. This class will help you intensely test your methods.
- Finally, right click on the Uno class. Click the main method to run it. You should see the following output. Did it work? If not, please see me ASAP.
Player did not think any card could be played.
This is an error, since cards 0, 1, and 5 are legal plays.
You are now ready to begin your mission of implementing the
callColor() methods within the
jzeitz_UnoPlayer class file.
The UnoPlayer.java file
UnoPlayer.java file contains what is called a "Java interface." It's alright if you aren't very familiar with interfaces, everything you'll need to know about them is on this page. What is important about this file is the two lines that begin with "
I have created these two "enumerated types" to represent the colors and the ranks of the cards in the program. Essentially, what I have done here is add to the basic list of Java data types (int, double, etc.) Now, in addition to having a variable of type "int" or type "double," you can have a variable of type "Color" or "Rank."
The way you specify a value of one of these types is to prefix one of the capitalized words with "Color." or "Rank." For instance, here is some legal code:
int x = 5;
double y = 3.14;
Color myFavoriteColor = Color.BLUE;
Rank aPowerfulRank = Rank.WILD_D4;
There's really nothing more to it than that. Just be aware that the way you say "green" in the program is "Color.GREEN", and you'll be fine.
Uno player: methods
jzeitz_UnoPlayer.java file has detailed comments describing the
callColor() methods you are to write code for. Reading these detailed comments is an excellent and praiseworthy idea. Note that the
play() method takes four parameters. Here is its method signature:
public int play(List<Card> hand, Card upCard, Color calledColor, GameState state);
Collectively, these arguments tell your method (1) what cards are in your hand, (2) what card is the "up card," (3) what color was called (this argument only has relevance if the "up card" is a wild), and (4) a way to find out other miscellaneous things about the state of the game, for your use in building a sophisticated strategy.
Your job is to write code that returns the integer of the card you wish to play. In the event that you cannot play any card, you should return -1 from this method. (Note that returning a -1 for a hand in which you can legally play is an error.)
If you wish, you may call methods on the GameState object passed to access detailed information about the state of the game. This object supports the following methods:
int getNumCardsInHandsOfUpcomingPlayers() - the array returned by this method will have length equal to the number of players minus 1 (in a normal tournament game, this will be 3.) It tells you in order how many cards the next player to play after you has (at index 0), how many the player across from you has (at index 1), and how many the player who just played has (at index 2.) Note that when I say "the next player to play after you," that presumes, of course, that you do not play a skip or a reverse, in which case the player represented at index 1 or index 2, respectively, will be the next player.
Color getMostRecentColorCalledByUpcomingPlayers() - this array follows the same format as the previous, except it contains Colors, not ints. It tells you the most recent color each player called when they played a wild. (The value will be "Color.NONE" if that player has not yet played a wild card this round.)
List<Card> getPlayedCards() - this method returns a list of the cards that have been played, in order, since the last deck remix. (A deck "remix" occurs if/when the draw pile becomes exhausted, and all of the cards in the discard pile are reshuffled and turned face down to become the new draw pile.) Interesting note: just from experimenting with my simulator, deck remixes are pretty uncommon. It seems that a large majority of games complete without ever requiring a deck remix, even when the players have the simplest possible strategy (just play a random matching card.)
int getTotalScoreOfUpcomingPlayers() - finally, this array tells you the total cumulative score for each player (in the grand 50,000-game Uno match), in order of their presumed turns, in the same way that arrays from the first two method calls in this list represented that order.
You can take advantage of the game state object by choosing to call any of these methods on it that you choose. Note that you are also free to ignore any of them if they're not of interest to you in developing your strategy.
callColor() method is simpler. It takes two parameters: (1) hand that tells you what's in your hand and (2) the state of the game as explained above:
public Color callColor(List<Card> hand, GameState state);
My code will call this method of yours when you have just played a wild and I need to know what color you want to call. It must return one of the four valid Color values (not Color.NONE.)