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!
Get your copy of the assignment here: https://classroom.github.com/a/3ernQohP
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:
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:
First, some terminology:
PlayerStrategy
manages to win enough games to score at least 200
points, or if the game engine detects a cheater.PlayerStrategy
implementations. A game starts with a fresh, shuffled deck of
cards, and ends when no more rounds can be played, either because
one of the PlayerStrategy
's emptied their hand or because there
are no cards left in the draw pile.PlayerStrategy
implementations. A round ends once every player has taken their
turn. A single turn includes doing things like drawing a card,
discarding a card, etc.Your game engine should:
PlayerStrategy
implementationNote 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:
PlayerStrategy
is implemented; all it can guarantee is that an implementor of
PlayerStrategy
implements the methods detailed in the
interface
. The game engine should call these methods to interact
with the PlayerStrategy
without knowing what those
implementations do under the hood.PlayerStrategy
implementation may try to
cheat (playing cards they don't have, etc.), which you should
detect and handle by ending the tournament after reporting the
cheater in some way. (Testing Hint: it may be to your benefit to
create an implementation that cheats so that you can test that your
game engine handles this correctly.)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()
.
You can earn some extra credit by extending this assignment. Some suggestions include:
PlayerStrategy
PlayerStrategy
main()
that uses your game engine to demonstrate
running a Crazy 8's tournamentPlayerStrategy
Additional design things we will be looking for:
static
keyword being used correctly when applied to a
method signature?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.
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.