Programming Assignment 12: War
Estimated reading time: 15 minutes
Estimated time to complete: two to three hours (plus debugging time)
Starter code: war-student.zip
Collaboration: not permitted
Overview
War, huh, yeah! What is it good for? Absolutely nothin’! Uh-huh!
— Whitfield and Strong…except to serve as the basis of a 186 programming assignment, say it again, uh huh!
— Liberatore
War is a two player children’s card game, usually played with a standard 52-card deck of playing cards. It’s also a great toy problem for various applications. In this programming assignment, you’ll implement a simulation of a version of War based upon a textual description.
This version of War is deterministic, that is, there is no randomness involved. The winner can be determined entirely from the starting deck. Simulations (also known as computer models) are a way to find the outcomes of both deterministic and non-deterministic situations. Non-deterministic simulations are usually run many times, and the results are aggregated in some way (for example, to find the most likely outcome, or the median outcome, or whatnot).
Unlike most previous assignments, we’ve not provided a large set of unit tests to help with automated testing or your design process. You will need to translate the relatively simple rules of the game from English text into Java code, to decide if and how to break the program up into methods, and to write your own tests. Or, you know, caveman debug and trial-and-error code until it works or you give up.
The Gradescope autograder includes some tests of your program’s overall correctness. We have left the names of these tests visible as a hint to what they’re testing, but you will likely need to write some tests of your own to complete this assignment.
Goals
- Build a program to simulate the War card game, as described in English text.
- Practice choosing and using relevant data structures for this program.
- Practice breaking up portions of the program into separate methods, as needed.
- Practice writing unit tests.
- Test code using unit tests.
Downloading and importing the starter code
As in previous assignments, download and save (but do not decompress) the provided archive file containing the starter code. Then import it into Eclipse in the same way; you should end up with a war-student
project in the “Project Explorer”.
The Game of War
If you haven’t played the game of War before, you might want to read the Wikipedia article describing it and find a classmate to play it with a few times, just to get a sense of how it works. But as noted in the article, there are many variants, so you’ll also want to read on to make sure you implement it exactly as described here.
An informal overview
The game is played by dealing the deck to two players. Each player plays a card; these two cards are compared in a “battle.” The higher card (by value, or rank, as suits are irrelevant) wins the battle, and the player of the winning card takes both cards (the “spoils” of war), adding them to the bottom of their deck.
In case of tie, declare “war.” Each player deals out three more cards as additional spoils of war. Then each player plays another card. The winner of this battle takes all 10 cards (or more, in case of repeated ties).
Repeat this process until one player runs out of cards and is declared the loser.
A more formal description, with examples
Cards are represented as Integer
values. In a classical game of War, the values range from 2 to 14, but your implementation should work with any value Integer
and any non-negative number of cards.
Dealing out the cards
The initial “deck” of cards is provided as a List<Integer>
. This deck must be dealt to the two players, A and B. Dealing the deck out entails giving alternating cards to each player, that is, the first card to player A, the second to player B, the third to player A, and so on. Each player accumulates cards into their own deck in this order.
For example, if the initial deck contains the values [2, 3, 4, 5] and we deal out all the cards, player A’s deck would then contain [2, 4] and player B’s deck would contain [3, 5]. The “top” of each of these decks is the left-hand side, that is, player A’s next card they would play would be 2.
Battling
Players then engage in a series of battles. To start a battle, they each take the top card off of their deck and place them into a temporary “spoils” pile, in the order A’s card, then B’s card. If both players have no cards in their deck before attempting to place a card into the spoils pile, the game is a draw (and the game is over). If just one of the players has no cards in their deck, they lose (and the game is over).
If one card’s value is numerically greater than than the other, then the player who played that card wins. The contents of the spoils pile are added, in order, to the bottom of the winner’s deck (and the spoils pile is emptied as a result). When there are only two cards, Player A’s card always comes before player B’s card, regardless of which player won the battle.
Continuing our earlier example, to start the first battle, player A would play a 2, and player B would play a 3. Their decks would then consist of [4] and [5], respectively, until the battle is decided. Player B wins this battle, and so would add the two cards, in the specified order, to their deck. Player A’s deck is still [4], and player B’s deck is now [5, 2, 3]. They play the next battle, B wins again, and A’s deck is now empty. At the start of the next battle, A would be out of cards and declared the loser of the game.
War
If a battle is between two cards of the same value, then a “war” is declared. Player A must add three more cards, in order, to the spoils pile, and then B must do the same. The players then battle again. Note that before this second battle commences, the spoils pile contains eight cards: the two from the first battle, and the six from the war. If a player wins this second battle, they collect all ten cards (the previous eight, plus the two from this battle), in order, into the bottom of their deck. If there is another war, then there will be 16 cards in the spoils pile before the third battle (the two from the first battle, the six from the first war, the two from the second battle, and the six from the second war) and a total of 18 cards awarded to the winner of the third battle. Wars continue until a player wins a battle, or the game ends due to one or both players running out of cards.
Much like during a battle, if both players are unable to lay down the required three cards during the same war, the game is a draw. If just one player is unable to lay down three cards during a war, they lose.
For example, suppose that player A’s deck contained [2, 8, 9, 10, 5, 4] and player B’s deck contained [2, 6, 7, 11, 6, 4].
First, each player would play their top card, both of which are 2. So the spoils pile would contain [2, 2] and a war would occur. Player A would add the top three cards from their deck to the spoils pile, and then player B would do so. The spoils pile now contains [2, 2, 8, 9, 10, 6, 7, 11]. Player A’s deck contains [5, 4] and player B’s deck contains [6, 4].
Time for the next battle. Both players remove the top card from their deck and add it to the spoils pile, which now contains [2, 2, 8, 9, 10, 6, 7, 11, 5, 6]. Player B wins this battle (5 vs 6), and so adds the entire spoils pile to their deck. Player A’s deck is now just [4], and player B’s deck is now [4, 2, 2, 8, 9, 10, 6, 7, 11, 5, 6].
The next battle causes another war, but player A will run out of cards and thus lose.
Next, consider the example of A’s deck containing [5, 2, 3] and B’s containing [5, 4]. Even though A has more cards than B, they will both run out of cards during the war and the game will end in a draw.
Technical Draws
If, after the 1,000th battle in a game of war neither player has won, then the game is declared a draw. (We don’t have all day here!)
What to do
Implement the method in War.java
. You’re welcome (and in fact, encouraged) to add other methods as needed to this class. You can also add other classes to the src/
directory, though likely you won’t need to do so.
Other notes
Organizing and debugging your code
Note that findWinner
is a static method! You can complete this assignment using only static methods, or you might want to create and instantiate a War
object and call its methods. In other words, treat findWinner
like you would the main
in a “driver” class. I strongly recommend this latter approach. If you take this approach your findWinner
method might look something like:
public static int findWinner(List<Integer> deck) {
War w = new War(deck);
return w.simulateGame();
}
If you instead decide to write a bunch of static methods, go ahead, but make sure you understand what it means to do so. And in particular, make sure you understand the semantics of static variables. Notably, static
variables are not re-initialized between method calls, including tests. So, for example, if your class contains a line like:
static List<Integer> deck = new ArrayList<>();
…then the deck
will only be empty the first time a test is run. After that, it will keep whatever value is stored in it. This problem will manifest as having your tests work in isolation, but fail when they are all run together. Prevent it by making sure you initialize static variables within static methods as needed, or by using only instance variables and methods. Again, I recommend the latter approach.
You could, of course, stuff all of your simulation code into the one findWinner
method. But we strongly suggest breaking it up into sections that you can test separately. This suggestion is, in actuality, a requirement if you expect us to help you debug – if you can’t figure out what your 200-line wall-of-code is doing, why would Adam, Fatemeh, Justin, or I be able to do so?
Testing your code
I strongly suggest you write additional unit tests to help you incrementally build up to the correct solution. Here are some simple cases (“smoke tests”) I’d consider; to get you started, the first four are already in the set of tests we’ve provided.
- An empty starting deck.
- A starting deck with one card.
- A starting deck with two cards, first card greater value.
- A starting deck with two cards, second card greater value.
- A starting deck with two cards, equal values.
- Starting decks that result in the examples given in the assignment writeup.
- A modified version of the above where the other player wins.
- A starting deck that results in a technical draw.
- A starting deck that results in a draw during a war.
- A starting deck that results in a battle → war → battle sequence containing at least three wars.
- A starting deck that results in a war where player A wins due to player B running out of cards during the war.
- A starting deck that results in a war where player B wins due to player A running out of cards during the war.
You are welcome and encouraged to swap tests (but not other code) with one another on Piazza. But I suggest you take the time to write the above tests yourself, first (and maybe don’t post these particular tests – let everyone get a chance to write them).
Code, tests, and planning
For reference, my War
implementation required writing under 100 lines of code, and there’s nothing particularly tricky about it – it consists of if
/ then
/ else
statements and while
and for
loops, split across a few methods, along with a small number of instance variables to track the current game’s state.
There are easily twice as many lines of code in the tests as there are in the implementation.
Perhaps there’s a lesson there about where you should spend your time.
Submitting the assignment
When you have completed the changes to your code, you should export an archive file containing the src/
directory from your Java project. To do this, follow the same steps as from Assignment 01 to produce a .zip
file, and upload it to Gradescope.
Remember, you can resubmit the assignment as many times as you want, until the deadline. If it turns out you missed something and your code doesn’t pass 100% of the tests, you can keep working until it does.