In this assignment, you will implement various algorithms that are useful for implementing the Flip game. Play the game a few times—it is more subtle than it appears at first.

To practice playing with a (very stupid) opponent, try this application:

Program doesn't start? Install the latest version of Java:

H@x0r$—don't bother decompiling that program. It uses a different implementation strategy.

When implementing a game, you need to store the current *game
state*: the positions of all the pieces in the game. Players alternate
making *moves*. Each move modifies the game state.

Note the stalemate rule. We need to keep track of the dice that are not flippable.

There is a particular subtlety in Flip. We want to carefully separate the decisions of each player. Suppose your opponent "plays" you. It is your opponent's decision which of your dice to put into the middle. But it is your decision which dice to take from the middle. Therefore, we will consider your opponent's turn to end immediately after the "play". Your turn will begin by taking dice from the middle and end with your flip or "play".

In summary, the Flip game state consists of

- the flippable and non-flippable dice of each player
- the die in the middle that was just "played" (if any)
- the other dice in the middle

We will consider two implementations of the game state.

A) Sets of dice

Use this `Die`

class. A die has a value
and a “flippable” state.

Here is a part of the game state class:

public class GameState { . . . private Set<Die> player1; private Set<Die> player2; private Set<Die> middle; private Die justPlayed; // one of the middle dice; may be null }

B) Bags of integers

It is more efficient to store integers than objects and hash sets. A set
of dice can simply be stored as a frequency table, using this `Dice`

class.

Then the game state becomes

public class GameState { . . . private Dice player1Flippable; private Dice player1Unflippable; private Dice player2Flippable; private Dice player2Unflippable; private Dice middle; private int justPlayed; // may be 0 }

Implement the following methods for each of the two game state classes.

GameState(int dice)

Give each player `dice / 2`

flippable dice with random values.
There are no dice in the middle.

GameState(int[] player1DieValues, int[] player2DieValues)

Give each player the dice with the given values as flippable dice. There are no dice in the middle.

void flip(int dieValue)

Flip a die with the given value for player 1. You may assume that such a die exists. Note that the "just played" die must be reset.

void play(int dieValue)

Have player1 "play" a die of player 2 with the given value. You may assume that such a die exists. (Try player 2's flippable dice first.) Note that all of player1's dice become flippable again.

void take(int[] dieValues)

Take the given dice from the middle and give them to the flippable dice of player 1. You may assume that all dice exist.

boolean checkTake(int[] dieValues)

Check that the "take" operation is legal, that is, all dice exist, and their sum is less than the value of the die that was just played.

boolean checkFlip(int[] takenDieValues, int flippedDieValue)

Check that the flip operation is valid, that is, that the "take" operation is valid and that player 1 has a flippable die of the given value. (Taken dice are flippable.)

boolean checkPlay(int[] takenDieValues, int playedDieValue)

Check that the "play" operation is valid, that is, that the "take" operation is valid and that player 2 has a die of the given value.

boolean isWinning()

Check whether the current state is winning for player 1. The method will
be called after a legal move of player 1. That is the case when player 2 has
no dice left *and* has no chance of getting dice in the next turn.

void switchPlayers()

Switch the roles of players 1 and 2. This will be called after player1 has
moved and after the `isWinning`

check. All of the data belonging
to each player should be swapped, so that after the call player 1 has become
player 2 and vice versa.

String toString()

Return a string representation that indicates the current state in the following format:

player1 flippable dice (plus) player1 unflippable dice (bar)

middle dice other than just played (plus) just played (bar)

player2 flippable dice (plus) player2 unflippable dice

For example,

135+6|466+5|+56

The dice in each of the subsets must be sorted by increasing value—this will help with unit testing.

For each method, provide a set of unit tests. For example, here is a
simple test of the `switchPlayers`

method:

int[] p1 = { 2, 6, 4 }; int[] p2 = { 1, 4, 2 }; GameState state = new GameState(p1, p2); assert state.toString().equals("246+||124+"); state.switchPlayers(); assert state.toString().equals("124+||246+");

Finally, drop your class into this FlipRunner program. If all is well, you will be able to play the game against a worthy opponent—yourself.

Here is a typical game:

Enter moves in the format taken dice, followed by f (flip) or p (play) and the value of the die to flip or play Examples: f2 or p4 or 12f2 Game state: 244+|+|124+ Enter move for player 1: p4 Game state: 12+|+4|244+ Enter move for player 2: f1 Game state: 244+|4+|2+6 Enter move for player 1: p2 Game state: +6|4+2|244+ Enter move for player 2: p4 Game state: 24+|24+4|6+ Enter move for player 1: 2p6 Game state: +|44+6|224+ Enter move for player 2: 4p4 Game state: 22+|46+4|4+ Enter move for player 1: p4 Congratulations! You won.