Assigned: 2020-02-12
Due Date: 2020-02-18 by 11:59PM U.S. Central Time
Write a game engine for the Crazy 8's card game. The game engine must use the provided interface to communicate with the players of the game.
Get your copy of the assignment here: https://classroom.github.com/a/BkrgNo6F
The rubric can be found here.
There is no autograder for this assignment, but you still must submit your GitHub repository to Gradescope.
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:
50
points10
points1
pointFirst, 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:
4
instances of a PlayerStrategy
implementation200
pointsNote 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 at least 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:
4
PlayerStrategy
2
implementations of PlayerStrategy
i18n
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.