Question: Flood It Game JAVA Background information We are going to improve our FloodIt game. You can either start from you own version of the game

Flood It Game JAVA

Background information

We are going to improve our FloodIt game. You can either start from you own version of the game (resulting from assignment 3), or you can chose to start from the version provided, which might be slightly different from the one you have already implemented.

We are pursuing several goals here. This goals are independent from one another, but you will see that if you do not have follow a clean and rational design for your code, expanding it in several directions can quickly become somewhat messy.

The first thing we are going to do is provide a few news features to the game: the ability to select the starting dot, the ability to change the directions followed by the flooding algorithm... we will also slightly update the GUI so that the next colour is selected directly from the grid.

Next, we are going to add an undo/redo feature to the game. The player should be able to undo all the moves, all the way back to the initial state of the game. After going through a series of undo, the player will be able to redo some or all of the undone moves.

This feature, which might seem quite involved at first, will turn out to be quite simple to do thanks to the ModelViewController design we have been using. Using a couple of stacks, we will be able to record the various states of the game (that is, the model in the MVC design), and the only thing that we will need to do is store and restores these states as required. For this, we will use one of the feature of Java: object cloning, which will provide copies of our objects.

Finally, we will add the ability to save the state of the game. We want to be able to close the application and then be able to resume the game exactly at the point it was left. In order to implement this, we will use another feature of Java: objects serialization. This will give us an easy way to store in a file the state of the game, and to read it back later. To make this simple, we will automatically save the state of the game if the user clicks the button Quit midway through a game. When a game is started, we will automatically look for a saved game and if one is found, it will be loaded instead of starting a fresh game (the saved game will be discarded at this point).

Shallow copy versus Deep copy

As you know, objects have variables which are either a primitive type, or a reference type. Primitive variables hold a value from one of the language primitive type, while reference variables hold a reference (the address) of another object (including arrays, which are objects in Java).

If you are copying the current state of an object, in order to obtain a duplicate object, you will create a copy of each of the variables. By doing so, the value of each instance primitive variable will be duplicated (thus, modifying one of these values in one of the copy will not modify the value on the other copy). However, with reference variables, what will be copied is the actual reference, the address of the object that this variable is pointing at. Consequently, the reference variables in both the original object and the duplicated object will point at the same address, and the reference variables will refer to the same objects. This is known as a shallow copy: you indeed have two objects, but they share all the objects pointed at by their instance reference variables. The Figure figure1 provides an example: the object referenced by variable b is a shallow copy of the object referenced by variable a: it has its own copies of the instances variables, but the references variables title and time are referencing the same objects.

Figure 1: A example of a shallow copy of objects.

Often, a shallow copy is not adequate: what is required is a so-called deep copy. A deep copy differs from a shallow copy in that objects referenced by reference variable must also be recursively duplicated, in such a way that when the initial object is (deep) copied, the copy does not share any reference with the initial object. The Figure figure2 provides an example: this time, the object referenced by variable b is a deep copy of the object referenced by variable a: now, the references variables title and time are referencing different objects. Note that, in turn, the objects referenced by the variable time have also been deep-copied. The entire set of objects reachable from a have been duplicated.

You can read more about shallow versus deep copy on wikipedia, for example.

1 Linked Stack and Exceptions

In the previous version of the game, we have been using a stack implementation that was based on an array. That implementation did not include precondition checks and did not throw any exceptions.

We will first switch from an array implementation to a linked element implementation, and add the proper checks and exceptions.

1.1 Linked Stack Implementation

You must replace the GenericArrayStack class with a GenericLinkedStack class. Follow the steps seen in class for this.

1.2 Defining new exception types and checking preconditions

You must create a new unchecked exception type called EmptyStackException.

Figure 2: A example of a deep copy of objects.

In the classe GenericLinkedStack, you must verify the preconditions in each relevant method, and throw the correct exception when required. For example (but there are other cases to handle as well), pushing a null reference into the stack is not allowed. If this happens, a NullPointerException must be thrown.

2 Basic game improvements

We are going to add a few new feature to the game.

2.1 Next colour selection

In the previous version of the game, we had 6 larger dots at the bottom of the screen to select the next colour. This interface was not the best. The player typically wants to select a colour which is next to the captured region.

Change the interface so that the selection of the next colour is done directly on the board. Clicking on any dot on the board selects that dots colour as the next colour. The previous panel of larger colour selection dots has been removed. Figure figure3 illustrates the new user interface. Note that the selected dot does not need to be next to a captured dot.

Figure 3: Selection of the next colour.

2.2 Capture of the initial dot

Before, the game always started with the initially captured dot at the top left corner of the board.. We want to change that. Now, the first dots that is clicked on the board is the initially captured dot. That selection does not count as a step in the game, so the text at the bottom left should initially read Select initial dot (Figure figure4, left), and then once an initial dot has been selected, to should resume to showing the number of steps (Figure figure4, right).

Figure 4: Before, and after capturing the initial dot.

2.3 Neighbour definition

A button Settings must be added to the top left of the screen. Clicking that button shows a dialog, which presents two independent selections: the type of play (Plane or Torus) and the type of move (Orthogonal or Diagonal) (Figure figure5).

Figure 5: Settings dialog.

Playing on the plane is what the game used to do: the border of the boardgame represent the end of the surface of the game. A dot that sits on the border has no neighbour on the border side. Playing on a torus essentially removes the border of the board, and the board now wraps around to the other side. So a dot always has neighbours on all sides, but these neighbours can be on the other side of the board if the dot is on the border. Figure figure6 illustrates this concept: the dot 1 has 2 neighbours in plane mode: dot 2 to the right, and dot 3 below. In torus mode, it has also dot 4 as a left neighbour (wrapped around to the right side of the board), and dot 5 as an above neighbour (wrapped around to the bottom side of the board).

Figure 6: Plane vs Torus.

Orthogonal moves are the usual move: each dot has (up to) four neighbours, vertically and horizontally. Diagonal moves add (up to) four other neighbours, along the diagonals. Figure figure7 illustrates this concept: the dot 1 has 4 neighbours in orthogonal mode: dot 5 to the left, dot 3 on top, dot 6 to the right, and dot 8 below. In diagonal mode, it also has dots 2, 4, 9 and 7 as neighbours, for a total of 8 neighbours.

Note that it is possible to combine both settings in any ways, which yield 4 different modes for playing the game. If the mode is changed midway through a game, the game simply continues with the new settings.

Figure 7: Orthogonal vs Diagonal.

3 Undo/Redo

3.1 Cloning the model

One of the methods of the class Object is the method clone:

protected Object clone() throws CloneNotSupportedException

Because clone is a method of the class Object, every Java class inherits that method. In a nutshell, the clone method returns a shallow copy of the object, provided that the object supports cloning. In many cases, a shallow copy will not be sufficient. Class can provide a more specialized implementation by overriding the method clone. Typically, the goal is to provide a deep copy, and also possibly avoid copying some of the variables that are not needed in the copy.

Calling clone on an object that doesnt support cloning will generate a CloneNotSupportedException exception. A class signifies that it is ready to be cloned simply by implementing the interface cloneable. In order to obtain a deep-copy, you need to:

Implement the interface cloneable;

Override the method clone;

Call the clone method of the superclass;

Recursively clone objects referenced by the reference variables.

Unlike the interfaces we have seen so far, cloneable doesnt have any method. In a sense, for a class, implementing cloneable is simply a statement that the objects of that class can be cloned.

Modify the class GameModel and possible other classes as required, so that instances of GameModel can effectively be cloned.

3.2 Implementing an Undo feature

Now that we can clone GameModel, we can use a Stack to easily add an undo feature to our game. What can be undone here are the players moves, and the corresponding flooding that occured (that is, if you undo the last move, the game must go back to the state it was in just before the players last move).

Note that user actions such as the initial dot selection or changes to the settings should also be part of that feature, so that it can also be undone, so it should be possible to undo a initial dot selection, and if the settings are changed partway through a game, undoing past that step should revert the settings to what they were before. Both the initial dot selection and a change in the settings is an actual step that is undone in isolation from the rest.

Using the stack implementation of question section1, you must implement this feature by cloning the model as required and use the stack appropriately.

You will see that you will need to be able to restore a gameModel object to a state that has been previously cloned, so you need to add the corresponding method(s) as well.

In your user interface, you will need to add an undo button, to use your feature. Make sure that the button is only enabled when undo is indeed possible.

3.3 Implementing a Redo feature

Having done undo, it should be relatively straightforward to add a redo feature. There is only a little bit of logic to add, to figure out when redo should be possible, and when it should not.

Here too, you need to add a new button, and make sure that the button is only enabled when redo is possible.

4 Saving and Restoring the game

Java provides another feature: the ability to serialize an object, often to store its state on a file. Much like the case of cloning, a class indicates that it supports serialization by implementing the interface serializable. Unlike cloning, serialization does an automatic deep copy of the object being serialized.

The feature you must implement is the following: when the player exits the game by clicking on the Quit button while the game is still going on, you must automatically serialize the current instance of the GameModel object inside a file named savedGame.ser, located in the current game directory. When the game starts your program should check if the file savedGame.ser is available. If so, it should use it to restore the previously saved game state, and then delete the file. If the file is not present, the game starts as before[1].

[1] When a previously saved state is restored, the size of the game is the size of the restored game. If a different size was passed on as parameter when the game was restarted, the saved game is ignored and a new game of the specified size is stated.

My Classes

DOTBUTTON.JAVA

import javax.swing.*; import java.awt.*;

public class DotButton extends JButton { private ImageIcon greyM; private ImageIcon orangeM; private ImageIcon blueM; private ImageIcon greenM; private ImageIcon purpleM; private ImageIcon redM; private ImageIcon greyS; private ImageIcon orangeS; private ImageIcon blueS; private ImageIcon greenS; private ImageIcon purpleS; private ImageIcon redS; private ImageIcon greyL; private ImageIcon orangeL; private ImageIcon blueL; private ImageIcon greenL; private ImageIcon purpleL; private ImageIcon redL; private int row; private int column; private int color; private final int iconSize; public DotButton(int row, int column, int color, int iconSize) { this.row = row; this.column = column; this.color = color; this.iconSize = iconSize; if (iconSize == 0) { this.setPreferredSize(new Dimension(11, 11)); if (color == 0) { greyS = new ImageIcon("data/S/ball-0.png"); setIcon(greyS); }else if (color == 1) { orangeS = new ImageIcon("data/S/ball-1.png"); setIcon(orangeS); }else if (color == 2) { blueS = new ImageIcon("data/S/ball-2.png"); setIcon(blueS); }else if (color == 3) { greenS = new ImageIcon("data/S/ball-3.png"); setIcon(greenS); }else if (color == 4) { purpleS = new ImageIcon("data/S/ball-4.png"); setIcon(purpleS); }else if (color == 5) { redS = new ImageIcon("data/S/ball-5.png"); setIcon(redS);} }else if (iconSize == 1){ this.setPreferredSize(new Dimension(28, 28)); if (color == 0) { greyM = new ImageIcon("data/M/ball-0.png"); setIcon(greyM); }else if (color == 1) { orangeM = new ImageIcon("data/M/ball-1.png"); setIcon(orangeM); }else if (color == 2) { blueM = new ImageIcon("data/M/ball-2.png"); setIcon(blueM); }else if (color == 3) { greenM = new ImageIcon("data/M/ball-3.png"); setIcon(greenM); }else if (color == 4) { purpleM = new ImageIcon("data/M/ball-4.png"); setIcon(purpleM); }else if (color == 5) { redM = new ImageIcon("data/M/ball-5.png"); setIcon(redM); } } setOpaque(true); setBorderPainted(false); setFocusPainted(false); setContentAreaFilled(false); setBackground(Color.WHITE); }public DotButton(int color, int iconSize) { this.color = color; this.iconSize = iconSize; this.setPreferredSize(new Dimension(40, 40)); if (color == 0) { greyL = new ImageIcon("data/L/ball-0.png"); setIcon(greyL); setActionCommand("0"); }else if (color == 1) { orangeL = new ImageIcon("data/L/ball-1.png"); setIcon(orangeL); setActionCommand("1"); }else if (color == 2) { blueL = new ImageIcon("data/L/ball-2.png"); setIcon(blueL); setActionCommand("2"); }else if (color == 3) { greenL = new ImageIcon("data/L/ball-3.png"); setIcon(greenL); setActionCommand("3"); }else if (color == 4) { purpleL = new ImageIcon("data/L/ball-4.png"); setIcon(purpleL); setActionCommand("4"); }else if (color == 5) { redL = new ImageIcon("data/L/ball-5.png"); setIcon(redL); setActionCommand("5"); } setContentAreaFilled(false); setBackground(Color.WHITE); setOpaque(true); setBorderPainted(false); }public void setColor(int color) { this.color = color; }public int getColor() { return color; }public int getRow() { return row; }public int getColumn() { return column; } }

DOTINFO.JAVA

public class DotInfo { private final int x; private final int y; protected int color; private boolean captured; public DotInfo(int x, int y, int color) { this.x = x; this.y = y; this.color = color; }public int getX() { return x; }public int getY() { return y; }public boolean isCaptured() { return captured; }public void setCaptured(boolean captured) { this.captured = captured; }public int getColor() { return color; } }

FLOODIT.JAVA

public class FloodIt { public static void main(String[] args) { StudentInfo.display(); if (args.length > 0) { if (Integer.parseInt(args[0]) < 10) { GameController controller = new GameController(12); } else { GameController controller = new GameController(Integer.parseInt(args[0])); } } else { GameController controller = new GameController(12); } } }

GAMECONTROLLER.JAVA

import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import static javax.swing.JOptionPane.NO_OPTION; import static javax.swing.JOptionPane.YES_OPTION; public class GameController implements ActionListener, Stack { private final GameModel gameModel; private final DotInfo[] stack; private final GameView gameView; public GameController(int size) { gameModel = new GameModel(size); gameView = new GameView(gameModel, this); stack = new DotInfo[size * size]; selectColor(-1); }public void reset() { gameModel.reset(); selectColor(-1); }public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); switch (command) { case "0": selectColor(0); break; case "1": selectColor(1); break; case "2": selectColor(2); break; case "3": selectColor(3); break; case "4": selectColor(4); break; case "5": selectColor(5); break; case "Reset": reset(); break; case "Quit": System.exit(0); break; } }public void selectColor(int color) { if (!gameModel.isFinished()) { if (color != -1 && color != gameModel.getCurrentSelectedColor()) { gameModel.setCurrentSelectedColor(color); gameModel.step(); for (int i = 0; (i < gameModel.getSize()); i++) { for (int j = 0; (j < gameModel.getSize()); j++) { if (gameModel.isCaptured(i, j)) { gameModel.get(i, j).color = gameModel.getCurrentSelectedColor(); push(gameModel.get(i, j)); } } } } else if (color == -1) { push(gameModel.get(0, 0)); }DotInfo verify; while (!isEmpty()) { boolean westExists = true; boolean eastExists = true; boolean southExists = true; boolean northExists = true; verify = pop(); if (verify.isCaptured()) { if (verify.getY() == 0) { westExists = false; }if (verify.getY() + 1 == gameModel.getSize()) { eastExists = false; }if (verify.getX() + 1 == gameModel.getSize()) { southExists = false; }if (verify.getX() == 0) { northExists = false; }if (westExists && (verify.getColor() == gameModel.getColor(verify.getX(), verify.getY() - 1)) && !gameModel.isCaptured(verify.getX(), verify.getY() - 1)) { gameModel.capture(verify.getX(), verify.getY() - 1); push(gameModel.get(verify.getX(), verify.getY() - 1)); }if (eastExists && (verify.getColor() == gameModel.getColor(verify.getX(), verify.getY() + 1)) && !gameModel.isCaptured(verify.getX(), verify.getY() + 1)) { gameModel.capture(verify.getX(), verify.getY() + 1); push(gameModel.get(verify.getX(), verify.getY() + 1)); }if (southExists && (verify.getColor() == gameModel.getColor(verify.getX() + 1, verify.getY())) && !gameModel.isCaptured(verify.getX() + 1, verify.getY())) { gameModel.capture(verify.getX() + 1, verify.getY()); push(gameModel.get(verify.getX() + 1, verify.getY())); }if (northExists && (verify).getColor() == gameModel.getColor(verify.getX() - 1, verify.getY()) && !gameModel.isCaptured(verify.getX() - 1, verify.getY())) { gameModel.capture(verify.getX() - 1, verify.getY()); push(gameModel.get(verify.getX() - 1, verify.getY())); } } } } gameView.update(); if (gameModel.isFinished()) { int selectedValue = JOptionPane.showConfirmDialog(gameView, "You won! You used " + gameModel.getNumberOfSteps() + " steps! Would you like to start a new game?", "Congratulations", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE); if (selectedValue == YES_OPTION) { reset(); }else if (selectedValue == NO_OPTION) { System.exit(0); } }else if (gameModel.getNumberOfSteps() == 20) { int selectedValue = JOptionPane.showConfirmDialog(gameView, "You've reached the maximum number of steps. Would you like to try again?", "Game Over", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE); if (selectedValue == YES_OPTION) { reset(); }else if (selectedValue == NO_OPTION) { System.exit(0); } } }public boolean isEmpty() { for (int i = 0; i < stack.length; i++) { if (stack[i] != null) { return false; } } return true; } public DotInfo peek() { DotInfo lastElement = null; for (int i = 1; i < stack.length; i++) { if (stack[i] == null) { lastElement = stack[i - 1]; break; } }return lastElement; }public DotInfo pop() { DotInfo lastElement = null; for (int i = 1; i < stack.length; i++) { if (stack[i] == null) { lastElement = stack[i - 1]; stack[i - 1] = null; break; } }return lastElement; }public void push(DotInfo element) { for (int i = 0; i < stack.length; i++) { if (stack[i] == null) { stack[i] = element; break; } } } }

GAMEMODEL.JAVA

import java.util.*; public class GameModel { public static final int COLOR_0 = 0; public static final int COLOR_1 = 1; public static final int COLOR_2 = 2; public static final int COLOR_3 = 3; public static final int COLOR_4 = 4; public static final int COLOR_5 = 5; public static final int NUMBER_OF_COLORS = 6; final Random random; final int size; int numberOfSteps; int currentSelectedColor; final DotInfo[][] board;

public GameModel(int size) { this.size = size; random = new Random(); board = new DotInfo[size][size]; reset(); }public int getSize() { return size; }public int getColor(int i, int j) { return board[i][j].getColor(); }public boolean isCaptured(int i, int j) { return board[i][j].isCaptured(); }public void capture(int i, int j) { board[i][j].setCaptured(true); }public int getNumberOfSteps() { return numberOfSteps; }public void reset() { numberOfSteps = 0; currentSelectedColor = -1; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { board[i][j] = new DotInfo(i, j, random.nextInt(NUMBER_OF_COLORS)); board[i][j].setCaptured(false); } } board[0][0].setCaptured(true); }public int getCurrentSelectedColor() { return currentSelectedColor; }public void setCurrentSelectedColor(int val) { currentSelectedColor = val; }public void step() { numberOfSteps++; }public DotInfo get(int i, int j) { return board[i][j]; }public boolean isFinished() { for (int i = 0; i < size; i++) { for (int j = 0; j < size - 1; j++) { if (board[i][j].getColor() != board[i][j + 1].getColor()) { return false; } } }return true; }public String toString() { String captured = ""; String color = ""; for (int i = 0; i < board.length; i++) { for (int j = 0; j < board.length; j++) { captured += isCaptured(i, j) + " "; color += getColor(i, j) + " "; if (j == board.length - 1) { captured += " "; color += " "; } } }return color; } }

GAMEVIEW.JAVA

import javax.swing.*; import java.awt.*;

public class GameView extends JFrame {

private final GameModel model; private final GameController gameController; private final JButton reset; private final JButton quit; private final JPanel boardPanel; private final JPanel colorSelectionPanel; private final JPanel controlPanel; private final JPanel southPanel; private final DotButton greyP; private final DotButton orangeP; private final DotButton blueP; private final DotButton greenP; private final DotButton purpleP; private final DotButton redP; private final DotButton[][] board; private int iconSize;

public GameView(GameModel model, GameController gameController) { setTitle("ITI1121 FloodIt"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.model = model; this.gameController = gameController;

reset = new JButton("reset"); reset.addActionListener(this.gameController); reset.setContentAreaFilled(false); reset.setBackground(Color.WHITE); reset.setOpaque(true); quit = new JButton("quit"); quit.addActionListener(this.gameController); quit.setContentAreaFilled(false); quit.setBackground(Color.WHITE); quit.setOpaque(true);

boardPanel = new JPanel(new GridLayout(model.getSize(), model.getSize())); colorSelectionPanel = new JPanel(); controlPanel = new JPanel(); southPanel = new JPanel(new BorderLayout());

iconSize = -1;

board = new DotButton[model.getSize()][model.getSize()];

if (model.getSize() <= 25 && model.getSize() >= 10) { iconSize = 1; } else if (model.getSize() > 25) { iconSize = 0; }

for (int i = 0; i < model.getSize(); i++) { for (int j = 0; j < model.getSize(); j++) { board[i][j] = new DotButton(model.get(i, j).getX(), model.get(i, j).getY(), model.getColor(i, j), iconSize); boardPanel.add(board[i][j]); } }

controlPanel.setBackground(Color.WHITE); controlPanel.setOpaque(true);

controlPanel.add(new JLabel("Number of steps Done:" + Integer.toString(model.getNumberOfSteps()))); controlPanel.add(reset); controlPanel.add(quit);

colorSelectionPanel.setBackground(Color.WHITE); colorSelectionPanel.setOpaque(true);

greenP = new DotButton(3, 2); purpleP = new DotButton(4, 2); redP = new DotButton(5, 2); greyP = new DotButton(0, 2); orangeP = new DotButton(1, 2); blueP = new DotButton(2, 2); greenP.addActionListener(this.gameController); purpleP.addActionListener(this.gameController); redP.addActionListener(this.gameController); greyP.addActionListener(this.gameController); orangeP.addActionListener(this.gameController); blueP.addActionListener(this.gameController);

colorSelectionPanel.setBackground(Color.WHITE); colorSelectionPanel.setOpaque(true); colorSelectionPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY, 3));

colorSelectionPanel.add(greenP); colorSelectionPanel.add(purpleP); colorSelectionPanel.add(redP); colorSelectionPanel.add(greyP); colorSelectionPanel.add(orangeP); colorSelectionPanel.add(blueP);

southPanel.add(colorSelectionPanel, BorderLayout.CENTER); southPanel.add(controlPanel, BorderLayout.SOUTH);

add(boardPanel, BorderLayout.CENTER); add(southPanel, BorderLayout.SOUTH);

pack(); setLocationRelativeTo(null); setResizable(false); setVisible(true); }

public void update() { boardPanel.removeAll(); for (int i = 0; i < model.getSize(); i++) { for (int j = 0; j < model.getSize(); j++) { board[i][j] = new DotButton(i, j, model.getColor(i, j), iconSize); boardPanel.add(board[i][j]); } } controlPanel.removeAll(); controlPanel.add(new JLabel("Number of steps Done: ")); controlPanel.add(new JLabel(Integer.toString(model.getNumberOfSteps()))); controlPanel.add(reset); controlPanel.add(quit); revalidate(); repaint(); } }

STACK.JAVA

public interface Stack {

public abstract boolean isEmpty();

public abstract E peek();

public abstract E pop();

public abstract void push(E element);

}

Step by Step Solution

There are 3 Steps involved in it

1 Expert Approved Answer
Step: 1 Unlock blur-text-image
Question Has Been Solved by an Expert!

Get step-by-step solutions from verified subject matter experts

Step: 2 Unlock
Step: 3 Unlock

Students Have Also Explored These Related Databases Questions!