Assignments Office Hours Hall of Fame Notes
Assignments Office Hours Hall of Fame Notes

Crazy Card Game

Assigned: 2019-09-25
Due Date: 2019-10-01 by 11:59PM U.S. Central Time

Life consists not in holding good cards but in playing those you hold well.

— Josh Billings
UNO!

— Exclamation from a game that, for legal reasons, we cannot use in an assignment

After releasing your debut text adventure game to rave reviews, your managers at Fiction Interactive are considering their next move. They've decided that text adventure games are a bit too 1980's. Clearly, the future of gaming entertainment is in digital card games instead! Since you've been such a stellar employee, your manager has tasked you with leading an experimental foray into the latest and greatest in card gaming technology. You're going to make a game engine that can manage a game of Crazy 8's, and your coworkers are going to write the artificial intelligence players that play the game in your engine.

There's only one problem: the junior devs aren't sure what kind of AI strategies they want to make. Instead, they've given you a generic contract (in the form of a Java interface) that you'll have to honor in order for your game engine to work with whatever AI systems they come up with. So, without further ado, let's get to work!

Goals

Assignment Spec

Getting Started

Get your copy of the assignment here: https://classroom.github.com/a/3ernQohP

Crazy 8's

You need to implement a system that can orchestrate games of Crazy 8's. There are several variants of this game; this is the version we want you to implement:

The game is played between 4 players with a standard 52 card deck, which should be shuffled at the beginning of the game. There is a draw pile and a discard pile. At the start of the game, each player is dealt 5 cards, and the rest of the deck becomes the draw pile. The top card of the draw pile is then drawn and placed into the discard pile, unless the card is an eight, in which case it should be shuffled back into the draw pile and another card should be drawn. Then a series of rounds are played:

In a round, each player takes a turn one after the other, where they must do one of the following:

  1. Discard a card in their hand that matches the suit or value of the card at the top of the discard pile
  2. Discard an eight in their hand, and name a new suit that the next player must match
  3. Add a card from the draw pile into their hand

Rounds are played until either the draw pile is empty, or a player is able to win by discarding all of the cards in their hand. The player who won will gain the summed value of the cards left in the other players' hands. In the event of a tie (no player manages to empty their hand), each player should receive the summed value of the other players' hands, not including their own hand.

Card point values:

Part 1: The Game Engine

First, some terminology:

Your game engine should:

  1. Take in 4 instances of a PlayerStrategy implementation
  2. Orchestrate a tournament of Crazy 8's with these strategies until a single strategy reaches a total score of 200 points

Note that the interface provides no way to tell a PlayerStrategy whether they won, lost, or how many points they earned. It is up to your game engine to decide how to keep track of this information in your application.

Your program will need a public static void main() and at least two implementations of PlayerStrategy to demonstrate your game engine working. You should report which of the PlayerStrategy implementations wins the tournament.

Things to keep in mind:

Part 2: Some Simple Strategies

You need to create two different classes that each implement the interface differently so that you can demonstrate your game engine working. Your implementations should only make legal plays (they should not try to cheat), but otherwise, they can play the game in whatever style you want. You should use these implementations to demonstrate a tournament in your main().

Extra Credit

You can earn some extra credit by extending this assignment. Some suggestions include:

Deliverables and Grading

  1. A program with a main() that uses your game engine to demonstrate running a Crazy 8's tournament
  2. A game engine that can run a game of Crazy 8's, as well as a tournament of those games
  3. At least two implementations of PlayerStrategy

Additional design things we will be looking for:

Use your best judgement to write the cleanest code you can; make sure you can justify any design decisions you make in code review. If something about your code doesn't feel right, investigate!

Try to make commits at regular intervals. You want to leave a clear trail of discrete pieces of progress that you've made as you work. Commits are cheap! Make lots of them. Once again, try to involve testing in your code development process; you don't have to write all of your tests before the code, but you should try to develop your code alongside your tests and vice versa. See this for a set of general guidelines for writing good commit messages.

Additional Info: Why an Interface?

We deliberately went out of our way to give you an interface PlayerStrategy rather than having you come up with your own way to manage how players interact with the game engine. Why?

Interfaces are a powerful tool for representing the concept of an abstract contract that can be implemented without the user knowing the specifics of the implementation. In this case, your game engine knows about certain methods in the PlayerStrategy interface that it can call, but it doesn't have to know anything about how those methods are overridden and implemented in a base class.

This gives us immense flexibility regarding how we choose to implement our Crazy 8's game engine. We don't have to worry about what specific player strategies exist; we only have to concern ourselves with upholding the contract, and leave those details to whoever is going to actually implement the interface.

Let's take a look at an example from the Java standard library: the Comparable interface, and Collections.sort(). Collections.sort() sorts collections, but only if it knows that the elements in that collection can be put in some kind of order. That's where Comparable comes in; Comparable defines a contract that classes must implement (the compareTo() method) in order for them to fulfill this requirement of "being orderable".

Here's a silly example:

public class Grape implements Comparable<Grape> {
    private int sweetness;

    // ...

    int compareTo(Grape other) {
        return this.sweetness - other.sweetness;
    }
}

And now I can sort a List<Grape> by sweetness with Collections.sort().

Collections.sort() doesn't know that it's sorting grapes; it only knows that given two instances of an object, it can compare and order them, because the class implements Comparable, so it can freely call the compareTo() method in the sorting algorithm. And the writers of Collections.sort() didn't have to concern themselves with the possibility of someone wanting to use their code to sort grapes, of all things.