SIGCSE Nifty assignment -- Uno!

Stephen Davies

University of Mary Washington

stephen_AT_umw_DOT_edu

Uno!

Your assignment is to implement a sensible, strategic gaming algorithm that can compete in live game play against your fellow students and (ideally) slaughter them.

(Note that in this assignment, you will not be turning in a "main()" method. Indeed, the ultimate purpose of this assignment is not to create a main() method. Your purpose is rather to create two other methods that my main() method will call as it simulates an Uno game.)

The game

If you're one of the seventeen people in the U.S. who has never played Uno, you should read the Wikipedia article 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:

  1. 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.
  2. 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.
  3. 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, namely:

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:

  1. 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.)
  2. For every "special" colored card (draw two, reverse, and skip), the winner of the round gets 20 points.
  3. 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.

Your project

I have written an Uno simulation game. 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. My 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.

Getting started

Do the following to get your project set up in Netbeans:

  1. Create a new project. Call it "Uno." (Please do not call it anything else. Please call it "Uno," capitalized.)
  2. Right-click on the "uno" (lower-case) package that Netbeans created inside your Uno (capitalized) project, and choose "New > Java Class."
  3. Name the new class "Card."
  4. Open up the Card.java file you just created. Delete its entire contents, and replace it with the contents of this file.
  5. Right-click on the "uno" (lower-case) package that Netbeans created inside your Uno (capitalized) project, and choose "New > Java Class."
  6. Name this second new class "GameState."
  7. Open up the GameState.java file you just created. Delete its entire contents, and replace it with the contents of this file.
  8. Right-click on the "uno" (lower-case) package that Netbeans created inside your Uno (capitalized) project, and choose "New > Java Class."
  9. Name this third new class "UnoPlayer."
  10. Open up the UnoPlayer.java file you just created. Delete its entire contents, and replace it with the contents of this file.
  11. Right-click on the "uno" (lower-case) package that Netbeans created inside your Uno (capitalized) project, and choose "New > Java Class."
  12. Name this fourth (and final) new class "jsmith_UnoPlayer," where "jsmith" is your UMW userid. Please do not name the class literally "jsmith_UnoPlayer". Rather, name it (for example) "rjones2_UnoPlayer," if rjones2 is your userid. Please do not name the class anything else. Please do not get clever, or creative, or capitalize differently, or use a hyphen instead of an underscore, or deviate from this naming convention in any way.
  13. Open up the rjones2_UnoPlayer.java (or whatever) file you just created. Delete its entire contents, and replace it with the contents of this file. Note that when you do this, you will immediately have an error, which will be immediately remedied by step 14, below.
  14. In your newly pasted-in copy of this class, change the word "jsmith" to your userid in the "public class" line.
  15. Finally, open up the Main class that was created by NetBeans by default. You may delete its entire contents, and paste in this simple example of a small test case. This represents one of many scenarios (hands of cards plus chosen "up card") that your code will need to address properly and strategically. As before, replace "jsmith" with your actual UMW userid.

You are now ready to begin your mission of implementing the play() and callColor() methods.

The UnoPlayer.java file

The UnoPlayer.java file you have just copied contains what is called a "Java interface." This is an advanced topic that you will learn all about in CPSC 330; lucky for you, you don't need to understand anything about it right now except what I'm telling you on this page. What is important about this file is the two lines that begin with "public enum."

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

Your rjones2_UnoPlayer.java file has detailed comments describing the play() and 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:

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.

The callColor() method is simpler. It takes only one parameter, telling you what's in your hand:

public Color callColor(List<Card> hand);

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.)

Uno Tournament

Two weeks from Friday, during class, we will hold a bracket-style Uno tournament in which all functioning programs are entered as participants. Drama and excitement will be aplenty as the big screen shows the action. The grand prize winner will receive a valuable prize not available in stores.

Grading

You have two grading options for this assignment. You can go for a B, or you can go for an A.

For a B...

To receive a B for this assignment, it is only necessary that your methods return a correct answer. In other words, your play() method must always return the index of a card that can in fact be played on the up card, and must always return -1 only in the case where no card can in fact be legally played. Your callColor() method must simply always return a valid Color. Beyond that, it doesn't matter how "dumb" your methods are, or how badly they play: they only must play according to the rules.

For an A...

To get an A, you must go beyond simply legality and attempt to implement an intelligent strategy for playing the game. You must strive to play not only a legal card, but a good card. Think about how you actually play the game of Uno: how do you decide what card to play? What color to call? Whether to switch the color or stick with it? When to part with a wild card? Try to write code that imitates your thought process. (If you're stuck for ideas, see the following section "Strategy ideas.")

Now how will I judge whether you "attempted to implement an intelligent strategy?" Good question. Here's my grading criteria:

  1. If you place in the Final Four of the Uno tournament, your program will automatically and unquestionably judged to be of A quality.
  2. If you do not place in the Final Four, I will look at your well-commented, compellingly documented code to judge whether you attempted a non-trivial, intelligent approach. Your comments should narrate your algorithm completely and transparently. They should shed light on what your code is doing, and why, and explain the theory behind your approach. I will award "A-range" points solely at my discretion based on how thorough and creative your program looks to me.

For something worse...

Note that your program cannot be entered into the tournament if it does not meet the "for a B..." criteria above. This is for the simple reason that it will crash my program. Only programs that return correct answers will be eligible for the tournament, and I will test this with my test suite (see below.)

Vetting bugs

In order to thoroughly test your play() method, I have designed a comprehensive test suite. This program, when run properly, will execute your play() method on 10,000 random hands, ensuring that in each case you return a correct result. It is highly recommended that you perform this operation on your code before you turn it in. This is what I will use to determine whether your code is safe to enter the tournament.

To run it, perform the following steps:

  1. In NetBeans, right-click on your "uno" package (not your "Uno" project) and choose "New > Java Class". Call the new class "TestCaseProcessor", and paste the entire contents of the tester program into the window. Change "jsmith" to your username, and save.
  2. Also in NetBeans, right-click again on your "uno" package and choose "New > Other...". In the left "Categories" pick list, click on "Other" (at the bottom.) Then, in the right "File Types" pick list, click on "Empty File" (near the bottom.) Name the file "testCases.txt", and save. Now paste the entire contents of this file into the window, and save again.
  3. Change the run-time configuration of the program:
    1. In NetBeans, choose "Run > Set Project Configuration ... > Customize ...".
    2. Set the "Main Class" to be "uno.TestCaseProcessor".
    3. Set the Arguments to the string "testCases.txt".
    4. Change the "Working Directory" by pressing the "Browse..." button next to it. Navigate into your capitalized "Uno" project, then into "src" and finally "uno". Select "uno" as the working directory.
  4. Run it, clicking the green arrow. You should either get happy messages telling you how many test cases have passed as they run, or else you should get a helpful message about how one of them didn't pass.
  5. Stare hard at the message and then fix your error.

Strategy ideas

So you want to whip up on your fellow programmers. How can you do this? What should be a good strategy for choosing a card from an Uno hand? Here are some ideas:

Trash talking in Trinkle hallways

Heck yeah. Do it.

Turning it in

To submit this program, send me an email with your jsmith_UnoPlayer.java file attached. (Double-check that it's actually attached!) The email should have the subject line "Uno project turn-in".

Good luck!!