Answered step by step
Verified Expert Solution
Link Copied!

Question

1 Approved Answer

package ui; import java.awt.Color; import java.awt.Dimension; import java.util.Random; import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import hw3.Game; import hw3.GameUtil; /** * Main class for

package ui;

import java.awt.Color;

import java.awt.Dimension;

import java.util.Random;

import javax.swing.BoxLayout;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.SwingUtilities;

import hw3.Game;

import hw3.GameUtil;

/**

* Main class for a GUI for a game of "Threes" sets up a

* GamePanel instance in a frame.

*/

public class GameMain

{

/**

* Size of grid for the game.

*/

private static final int GRID_SIZE = 4;

/**

* Attempt to animate movement of tiles.

*/

private static final boolean USE_ANIMATION = true;

/**

* Print lots of output to the console.

*/

private static final boolean VERBOSE = true;

/**

* Tile size in pixels.

*/

public static final int TILE_SIZE = 100;

/**

* Font size for displaying score.

*/

public static final int SCORE_FONT = 24;

/**

* Size of the preview tile.

*/

public static final int PREVIEW_SIZE = 30;

/**

* Colors for tiles.

*/

public static final Color[] colors = {

Color.CYAN, // 1

Color.PINK, // 2

Color.WHITE, // 3 and up

};

/**

* Entry point. Main thread passes control immediately

* to the Swing event thread.

* @param args not used

*/

public static void main(String[] args)

{

final Random rand = new Random();

final GameUtil config = new GameUtil();

Runnable r = new Runnable()

{

public void run()

{

create(GRID_SIZE, config, rand, USE_ANIMATION, VERBOSE);

}

};

SwingUtilities.invokeLater(r);

}

/**

* Helper method for instantiating the components. This

* method should be executed in the context of the Swing

* event thread only.

*/

private static void create(int gridSize, GameUtil config, Random rand, boolean useAnimation, boolean verbose)

{

// create a game

Game game = new Game(gridSize, config, rand);

// create the two UI panels

ScorePanel scorePanel = new ScorePanel();

PreviewPanel previewPanel = new PreviewPanel();

GamePanel panel = new GamePanel(game, scorePanel, previewPanel, useAnimation, verbose);

// arrange the two panels vertically

JPanel mainPanel = new JPanel();

mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));

mainPanel.add(previewPanel);

mainPanel.add(panel);

mainPanel.add(scorePanel);

// put main panel in a window

JFrame frame = new JFrame("Com S 227 Threes Game");

frame.getContentPane().add(mainPanel);

// give panels a nonzero size

Dimension d = new Dimension(gridSize * GameMain.TILE_SIZE, gridSize * GameMain.TILE_SIZE);

panel.setPreferredSize(d);

d = new Dimension(gridSize * GameMain.TILE_SIZE, GameMain.TILE_SIZE);

scorePanel.setPreferredSize(d);

previewPanel.setPreferredSize(d);

frame.pack();

// we want to shut down the application if the

// "close" button is pressed on the frame

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

panel.grabFocus();

// rock and roll...

frame.setVisible(true);

}

}

/**

* Represents a position and value on a grid that can be

* animated from an old position to a new position.

*/

public class Tile

{

// label

private int currentValue;

private int value;

private int initialValue;

// initial pixel location

private int initialX;

private int initialY;

// final pixel location

private int x;

private int y;

// is it moving

private boolean movingX;

private boolean movingY;

// in case it is moving

private double currentX;

private double currentY;

// how much to move each frame

private double incrementX;

private double incrementY;

public Tile(int x, int y, int value)

{

movingX = false;

movingY = false;

this.x = x * GameMain.TILE_SIZE;

this.y = y * GameMain.TILE_SIZE;

this.value = value;

// save the initial values incase of undo

this.initialValue = this.value;

this.initialX = this.x;

this.initialY = this.y;

// these values may differ if animating

this.currentX = this.x;

this.currentY = this.y;

this.currentValue = value;

}

public void reverse(int frames)

{

value = initialValue;

currentValue = initialValue;

x = initialX;

y = initialY;

incrementX = (x - currentX) / frames;

incrementY = (y - currentY) / frames;

movingX = true;

movingY = true;

}

public void setNew(int newX, int newY, int newValue, int frames)

{

// target values after animation

x = newX * GameMain.TILE_SIZE;

y = newY * GameMain.TILE_SIZE;

value = newValue;

incrementX = (x - currentX) / frames;

incrementY = (y - currentY) / frames;

movingX = true;

movingY = true;

}

public int getCurrentX()

{

return (int) Math.round(currentX);

}

public int getCurrentY()

{

return (int) Math.round(currentY);

}

public void step()

{

if (movingX)

{

currentX = currentX + incrementX;

if (Math.abs(currentX - x) < incrementX)

{

currentX = x;

movingX = false;

}

}

if (movingY)

{

currentY = currentY + incrementY;

if (Math.abs(currentY - y) < incrementY)

{

currentY = y;

movingY = false;

}

}

if (!movingX && !movingY)

{

currentValue = value;

}

}

// stop animation

public void finish()

{

movingX = false;

movingY = false;

currentX = x;

currentY = y;

currentValue = value;

}

public boolean moving()

{

return movingX || movingY;

}

public int getCurrentValue()

{

return currentValue;

}

}

I need help on this homework please

* The Game class contains the state and logic for an implementation of a

* video game such as "Threes". The basic underlying state is an n by n

* grid of tiles, represented by integer values. A zero in a cell is considered

* to be * "empty". To play the game, a client calls the method shiftGrid(),

* selecting one of the four directions (LEFT, RIGHT, UP, DOWN). Each row or

* column is then shifted according to the rules encapsulated in the

* associated GameUtil object. The move is completed by

* calling newTile, which makes a new tile appear in the grid

* in preparation for the next move.

*

* In between the call to shiftGrid() and the call to

* newTile, the client may also call undo(), which

* reverts the grid to its state before the shift.

*

* The game uses an instance of java.util.Random to generate new tile values

* and to select the location for a new tile to appear. The new values

* are generated by the associated GameUtil's

* generateRandomTileValue method, and the new positions are

* generated by the GameUtil's

* generateRandomTilePosition method. The values are always

* generated one move ahead and stored, in order to support the ability

* for a UI to provide a preview of the next tile value.

*

* The score is the sum over all cells of the individual scores returned by

* the GameUtil's getScoreForValue() method.

*/

public class Game

{

/**

* Constructs a game with a grid of the given size, using a default

* random number generator. The initial grid is produced by the

* initializeNewGrid method of the given

* GameUtil object.

* @param givenSize

* size of the grid for this game

* @param givenConfig

* given instance of GameUtil

*/

public Game(int givenSize, GameUtil givenConfig)

{

// just call the other constructor

this(givenSize, givenConfig, new Random());

}

/**

* Constructs a game with a grid of the given size, using the given

* instance of Random for the random number generator.

* The initial grid is produced by the initializeNewGrid

* method of the given GameUtil object.

* @param givenSize

* size of the grid for this game

* @param givenConfig

* given instance of GameUtil

* @param givenRandom

* given instance of Random

*/

public Game(int givenSize, GameUtil givenConfig, Random givenRandom)

{

// TODO

}

/**

* Returns the value in the cell at the given row and column.

* @param row

* given row

* @param col

* given column

* @return

* value in the cell at the given row and column

*/

public int getCell(int row, int col)

{

// TODO

return 0;

}

/**

* Sets the value of the cell at the given row and column.

* NOTE: This method should not be used by clients outside

* of a testing environment.

* @param row

* given row

* @param col

* given col

* @param value

* value to be set

*/

public void setCell(int row, int col, int value)

{

// TODO

}

/**

* Returns the size of this game's grid.

* @return

* size of the grid

*/

public int getSize()

{

// TODO

return 0;

}

/**

* Returns the current score.

* @return

* score for this game

*/

public int getScore()

{

// TODO

return 0;

}

/**

* Copy a row or column from the grid into a new one-dimensional array.

* There are four possible actions depending on the given direction:

*

*

LEFT - the row indicated by the index rowOrColumn is

* copied into the new array from left to right

*

RIGHT - the row indicated by the index rowOrColumn is

* copied into the new array in reverse (from right to left)

*

UP - the column indicated by the index rowOrColumn is

* copied into the new array from top to bottom

*

DOWN - the row indicated by the index rowOrColumn is

* copied into the new array in reverse (from bottom to top)

*

* @param rowOrColumn

* index of the row or column

* @param dir

* direction from which to begin copying

* @return

* array containing the row or column

*/

public int[] copyRowOrColumn(int rowOrColumn, Direction dir)

{

// TODO

return null;

}

/**

* Updates the grid by copying the given one-dimensional array into

* a row or column of the grid.

* There are four possible actions depending on the given direction:

*

*

LEFT - the given array is copied into the the row indicated by the

* index rowOrColumn from left to right

*

RIGHT - the given array is copied into the the row indicated by the

* index rowOrColumn in reverse (from right to left)

*

UP - the given array is copied into the column indicated by the

* index rowOrColumn from top to bottom

*

DOWN - the given array is copied into the column indicated by the

* index rowOrColumn in reverse (from bottom to top)

*

* @param arr

* the array from which to copy

* @param rowOrColumn

* index of the row or column

* @param dir

* direction from which to begin copying

*/

public void updateRowOrColumn(int[] arr, int rowOrColumn, Direction dir)

{

// TODO

}

/**

* Plays one step of the game by shifting the grid in the given direction.

* Returns a list of Move objects describing all moves performed. All Move

* objects must include a valid value for getRowOrColumn() and

* getDirection(). If no cells are moved, the method returns

* an empty list.

*

* The shift of an individual row or column is performed by the

* method shiftArray of GameUtil.

*

* The score is not updated.

*

* @param dir

* direction in which to shift the grid

* @return

* list of moved or merged tiles

*/

public ArrayList shiftGrid(Direction dir)

{

// TODO

return null;

}

/**

* Reverts the shift performed in a previous call to shiftGrid(),

* provided that neither newTile() nor undo()

* has been called. If there was no previous call to shiftGrid()

* without a newTile() or undo(),

* this method does nothing and returns false; otherwise returns true.

* @return

* true if the previous shift was undone, false otherwise

*/

public boolean undo()

{

// TODO

return false;

}

/**

* Generates a new tile and places its value in the grid, provided that

* there was a previous call to shiftGrid without a

* corresponding call to undo or newTile.

* The tile's position is determined according

* to the generateRandomTilePosition of this game's

* associated GameUtil object. If there was no previous call to

* shiftGrid without an undo or newTile,

* this method does nothing and returns null; otherwise returns a

* TilePosition object with the new tiles's position and value.

* Note that the returned tile's value should match the current

* value returned by getNextTileValue, and if this method returns

* a non-null value the upcoming tile value should be updated according

* to generateRandomTileValue(). This method should

* update the total score and the score should include the newly generated tile.

* @return

* TilePosition containing the new tile's position and value, or null

* if no new tile is created

*/

public TilePosition newTile()

{

// TODO

return null;

}

/**

* Returns the value that will appear on the next tile generated in a call to

* newTile. This is an accessor method that does not modify

* the game state.

* @return

* value to appear on the next generated tile

*/

public int getNextTileValue()

{

// TODO

return 0;

}

}

The game consists of a grid of moveable tiles. Each tile contains a number 1, 2, 3, or a value that results from starting with a 3 and doubling a bunch of times. We will think of the grid as a 2D array of integer values, where the value 0 means there is no tile in that cell. There are only four possible operations: to "shift" the grid up, down, left, or right. What this means is that the tiles are shifted in the indicated direction, and certain combinations of tiles may be merged if "pushed" together against one of the boundaries. The exact rules for merging are discussed in a later section.

The screenshot on the right shows the result of shifting in the "up" direction. The two 12's are "pushed" against the top and merge to make 24. In addition, the 1 and 2 in the second column are merged to make a 3. All other tiles that can move (the two 2's in this case) are shifted up as well.

Whenever the grid is shifted in some direction, a new tile appears on the opposite side. The game ends when the grid can't be shifted in any direction because there are no empty cells and no merges are possible. The object of the game is to get the highest possible score. The score is the sum, for all tiles on the grid, of a predetermined number associated with each individual tile value.

There are many versions online you can try out to get an idea of how it works; for example, as of the moment this document is being written, there is one you can play at http://play.threesgame.com/ Please note that this and other versions you find online may not correspond precisely to the game as specified here.

The two classes you implement will provide the "backend" or core logic for the game. In the interest of having some fun with it, we will provide you with code for a GUI (graphical user interface), based on the Java Swing libraries, as well as a simple text-based user interface. There are more details below.

The sample code includes a documented skeleton for the two classes you are to create in the package hw3. A few of the methods of the GameUtil class are already implemented and you should not modify them. The additional code is in the packages ui and api. The ui package is the code for the GUI, described in more detail in a later section. The api package contains some relatively boring types for representing data in the game.

You should not modify the code in the api package.

Specification

The specification for this asssignment includes

this pdf,

any "official" clarifications posted on Piazza, and

the online javadoc

Overview of the GameUtil class

The class GameUtil is a "utility" class, meaning that it is stateless (has no instance variables). It consists of a collection of algorithms that implement some of the basic rules and logic of the game.

In particular, the key algorithm for shifting and merging the tiles in an individual row or column is implemented in the shiftArray() method of GameUtil. This method only operates on a one-dimensional array and only shifts to the left. We will later see how this simpler operation can be used easily by the Game class to shift in any direction. Isolating this special case (a one- dimensional form that only shifts left) makes the algorithm easier to write and test independently.

Here is a summary of the methods of this class. For full details see the javadoc:

int mergeValues(int a, int b)

Determines whether the two tile values can be merged and returns the merged value, returning zero if no merge is possible. The basic rules are that 1 can be merged with 2, and values larger than 2 can be merged if they are equal. In either case the merged value is the sum. This method is already implemented.

int getScoreForValue(int value)

Returns the score for a tile with the given value.

int calculateTotalScore(int[][] grid)

Returns the total score for the given grid.

This method is already implemented.

int[][] initializeNewGrid(int size, Random rand)

Initializes a new grid, using the given Random object to position two initial tiles.

This method is already implemented.

int[][] copyGrid(int[] grid)

Makes a copy of the given 2D array.

This method is already implemented.

int generateRandomTileValue(Random rand)

Returns a randomly generated tile value according to a predefined set of probabilities, using the given Random object.

TilePosition generateRandomTilePosition(int[] grid, Random rand, Direction lastMove)

Returns a randomly selected tile position on the side of the grid that is opposite the direction of the most recent shift.

ArrayList shiftArray(int[] arr)

Shifts the array elements to the left according to the rules of the game, possibly merging two of the cells. See below for details.

The shiftArray() method

The basic rules for shifting are as follows. Remember that we interpret an array cell containing zero to be "empty".

If there is an adjacent pair that can be merged, and has no empty cell to its left, then the leftmost such pair is merged (the merged value goes into the left one of the two cells) and all elements to the right are shifted one cell to the left.

Otherwise, if there is an empty cell in the array, then all elements to the right of the leftmost empty cell are shifted one cell to the left.

Otherwise, the method does nothing.

The method mergeValues() (see overview above) determines which pairs of values can be merged, and if so what the resulting value is. Note that at most one pair in in the array is merged. Here are some examples:

For arr = [3, 1, 2, 1], after shiftArray(arr), the array is [3, 3, 1, 0] For arr = [1, 2, 1, 2], after shiftArray(arr), the array is [3, 1, 2, 0] For arr = [6, 3, 3, 3, 3], after shiftArray(arr), the array is [6, 6, 3, 3, 0] For arr = [3, 6, 6, 0], after shiftArray(arr), the array is [3, 12, 0, 0] For arr = [3, 0, 1, 2], after shiftArray(arr), the array is [3, 1, 2, 0]

The return value of shiftArray() is a list of Move objects. A Move object is a simple data container that encapsulates the information about a move of one cell or a merge of a pair of cells. The Move objects do not directly affect the game state, but can be used by a client (such as a GUI) to animate the motion of tiles. The Move class is in the api package; see the source code to see what it does.

Overview of the Game class

The Game class encapsulates the state of the game. The basic ingredients are

an n x n grid (2D array of integers) representing the tiles

a reference to an instance of GameUtil

an instance of Random for generating new tile positions and values

the direction of the most recent move

an integer storing the value for the next tile to be generated

a second 2D array for storing the previous state of the grid (to support the undo()

operation

Basic game play: the shiftGrid() method

The basic play of the game takes place by calling the method

public ArrayList shiftGrid(Direction d),

which shifts each row or column in the indicated direction. This is normally followed by calling the method

public TilePosition newTile()

which (if anything in the grid was actually moved) puts a new tile in the grid. The type Direction is just a set of constants for indicating which direction to collapse the grid:

Direction.LEFT Direction.RIGHT Direction.UP Direction.DOWN

Instead of just using integers for these four values, Direction is defined as an enum type. You use these values just like integer constants, but because they are defined as their own type you can't accidentally put in an invalid value. You compare them to each other (or check whether equal to null) using the == operator.

Implementing shiftGrid()

The shiftArray method will make use of the algorithm implemented in GameUtil to shift the entire grid in the indicated direction. However, the shiftArray method in GameUtil only collapses to the left. How do we do the other three directions? The simple solution is to define a method that copies a row or column from the grid into a temporary array. We can copy any row or column in either direction. The method to do so is:

public int[] copyRowOrColumn(int rowOrColumn, Direction dir)

For example, suppose we have the grid,

Then a call to copyRowOrColumn(1, Direction.DOWN) would return the array [8, 0, 4, 2]. Note that the rowOrColumn argument is a row index for directions left and right, but is a column index if the direction is up or down. There is a corresponding method

public void updateRowOrColumn(int[] arr, int rowOrColumn, Direction dir)

that takes the given array and copies its elements into the grid in the given direction.

The method returns a list of Move objects. The Move objects themselves may be the same ones generated by the calls to the shiftArray method of GameUtil; however, in order to be interpreted as moves in a 2D grid, each Move object must have the appropriate direction and row/column index set by calling setDirection. Thus the basic implementation of shiftGrid() would normally look like:

for each index i up to the size copy the row or column i into a temp array call shiftArray with the temp array, and add the Move objects to the result copy the temp array back into the row or column update the row/column and direction for each Move object

The method newTile()

Each time a shiftGrid() operation actually moves one or more cells, a new tile will eventually have to appear in the grid. The purpose of the newTile() method is to select a new position and value for the tile using the game's instance of Random. The rules for selecting tile values are specified by the GridUtil method generateRandomTileValue(), and the game must use this method. Likewise, the rules for selecting new tile positions are specified in the GridUtil method generateRandomTilePosition(). The new tile is always placed in a randomly selected empty cell on the side of the grid that is opposite the move direction. The newTile() method updates the grid to contain the new tile value, and also returns a TilePosition object, which is just a simple data container for a row, column, and value.

The newTile() method should also recalculate the score. The score should include the value of the new tile that was just placed.

One thing that might seem odd at first is that the new tile value and the new tile position are not generated at the same time. A nice feature in playing the game is that the player gets a partial preview of the value that will appear on the next tile to be generated, and can base the decision about which direction to shift based on this preview. This means that your game must generate

each new value one move before it is actually used. Clients can observe this value by calling the Game method getNextTileValue(). (This method is an accessor and should not actually generate the value. Generating the value should happen once when the Game is constructed, and again each time newTile() puts a new tile on the grid.) You might also notice that getNextTilePosition() has an argument of type Direction in order to know the direction of the most recent move (to determine on which side of the grid to generate the new tile). That implies that you'll need an instance variable to keep track of this.

The undo() operation

Although the basic game play from the client point of view consists of calls to shiftGrid() alternated with calls to newTile(), there is one more feature the game supports that may cause this sequence to vary. Before the call to newTile(), the player can undo the shift operation and restore the grid to its previous state by calling the undo() method. This is actually easy to implement: define an instance variable of type int[][], and use it to save a copy of the current grid at the beginning of the shiftGrid() method. If the shift operation causes any change to the grid, then the updated grid is considered "pending" until newTile() is actually called, after which the operation cannot be undone. If undo() is called before newTile(), the original grid is restored and the operation is no longer considered "pending". If undo() is called when no shift operation is pending, it has no effect; likewise, if shiftGrid() or newTile() is called when an operation is already pending, nothing should happen. If shiftGrid() does not actually cause any modification of the grid, that is not considered a pending operation.

The text-based UI

The util package includes the class ConsoleUI, a text-based user interface for the game. It has a main method and you can run it after you get the required classes implemented. The code is not complex and you should be able to read it without any trouble. It is provided for you to illustrate how the classes you are implementing might be used to create a complete application. Although this user interface is very clunky, it has the advantage that it is easy to read and understand how it is calling the methods of your code. It does not use the list of Move objects returned by the shiftGrid() method, and it does not use the TilePosition object returned by the newTile() method, so you can try it out before you have those parts working.

The GUI

There is also a graphical UI in the ui package. The GUI is built on the Java Swing libraries. This code is complex and specialized, and is somewhat outside the scope of the course. You are not expected to be able to read and understand it.

The controls are the four arrow keys and the shift key. Pressing an arrow key just invokes the game's shiftGrid() method in the corresponding direction. Releasing the key normally invokes the newTile() method; however, if the shift key is down while the arrow key is released, the UI instead invokes the undo() method.

The main method is in ui.GameMain. You can try running it, and youll see the initial window, but until you start implementing the required classes youll just get errors. All that the main class does is to initialize the components and start up the UI machinery.

You can configure the game by setting the first few constants in GameMain: to use a different size grid, to attempt to animate the movement of the tiles, or to turn the verbose console output on or off. Animation requires that the list of Move objects returned by the shiftGrid() method and the TilePosition object returned by newTile(), be completely valid. You can still try out the UI with animation off.

GuI

Step by Step Solution

There are 3 Steps involved in it

Step: 1

blur-text-image

Get Instant Access to Expert-Tailored Solutions

See step-by-step solutions with expert insights and AI powered tools for academic success

Step: 2

blur-text-image

Step: 3

blur-text-image

Ace Your Homework with AI

Get the answers you need in no time with our AI-driven, step-by-step assistance

Get Started

Recommended Textbook for

More Books

Students also viewed these Databases questions

Question

2. What are five ways to create positive emphasis? (LO 3-2)

Answered: 1 week ago