Due date: Thursday, February 06 @ 11:59PM Note that this due date is not Tuesday as the calendar initially suggested. We are extending it to give some slack since there is some overlap with PA3, but don't wait to get started, start early and start often!
There are a number of ways to get started on development. The following is the recommended way to ensure that your code will compile during grading.
ssh cs8bwi20__@ieng6.ucsd.edu
cp -r ~/../public/pa4 ~/
(this will copy the entire starter files directory to your home directory)ls ~
to verify that you have copied the pa4
directory over.git clone https://github.com/CaoAssignments/cse8b-wi20-pa4-2048-starter.git
cd cse8b-wi20-pa4-2048-starter
mv cse8b-wi20-pa4-2048-starter pa4
vim GameState.java
or gvim GameState.java
javac
command.
javac file1.java file2.java etc...
javac GameState.java
java
command passing in the name of the class with the main method that you want to run.
java nameOfClass
java GameState
2048 is a single-player puzzle game created in March 2014 by 19-year-old Italian web developer Gabriele Cirulli, in which the objective is to slide numbered tiles on a grid to combine them and create a tile with the number 2048. Cirulli created the game in a single weekend as a test to see if he could program a game from scratch. 2048 was an instant hit when the game received over 4 million visitors in less than a week.
The original version of the game was programmed in JavaScript. You will be creating a Java version of the 2048 game that can be played in the terminal (text based). Here is a link if you don’t know of this game http://2048game.com/. Your version of the game will have the exact same functionality as the original version of the game.
In this PA you will be implementing the infrastructure of the game.
2048 is played on a simple grid of size NxN (4x4 by default), with numbered tiles (all powers of 2) that will slide in one of 4 directions (UP, DOWN, LEFT, RIGHT) based on input from the user. Every turn, a new tile will randomly appear in an empty spot on the board with a value of either 2 or 4 (determined randomly with a 90% probability of being a tile with value 2). Tiles will slide as far as possible in the chosen direction until they are stopped by either another tile or the edge of the grid. If two tiles of the same number collide while moving, they will merge into a tile with a total value of the two tiles that collide. For example, if two tiles of value 8 slide into one another the resulting tile will have a value of 16. The resulting tile cannot merge with another tile again in the same move.
A score will also be kept based on the user’s performance. The user’s score starts at zero, and is increased whenever two tiles combine, by the value of the newly combined tile. For example, if two tiles of value 8 are merged together, the resulting tile will have a value of 16 and the user’s score will be increased by 16.
DO NOT MODIFY THESE FILES. We will use our own copy when grading, so any modifications you make will not be carried over. The only file you should change is Config.java
but do not add anything that does not already exist in the file; you should only change the value of these constants to test your code.
This file contains constants and settings for playing the game. These are magic numbers that define the configuration of the game, so you must place your own magic numbers in their respective files.
This file contains an enum. You can read the file for more information.
This file implements functionality for playing the game. You don't need to know how this file works to write your code in GameState.java
.
This file starts and plays the game.
You will be writing a file called GameState.java
. You will need to create this file from scratch. Make sure that it contains a public class called GameState
with the following functionality. Since we are not giving you starter code, you should make sure your file follows the strict method signatures we give to you, otherwise the autograder will not be able to run. We have defined what each method is required to do, but you are free to implement it however. In particular, we will only be grading based on what a function call does to the returned value of getBoard()
and getScore()
and what the called function returns (see below), so you are free to store the backend representation of your game however you wish.
NOTE: Some of these methods are REQUIRED. If you do not implement them, you will fail their respective autograder tests. Make sure that your method signatures for these are exactly as noted on the writeup - this is the most likely reason your code "compiles on your own computer but not on Gradescope."
There are three suggested instance variables (you are free to use whatever variables you think might help you, these are what we used):
private Random rng; private int[][] board; private int score;
rng
: we used this everytime we wanted to generate a number so that we didn't need to create a new Random
object every time.board
: we used this to keep track of the board. For tiles that had no value (empty), we had a 0 in board
.score
: we used this to keep track of the scorepublic String toString ()
Add the following code to your GameState.java
so your game board will be displayed in the correct format. You may have to fix the style of this code to consistent with your own styling. Do not implement your own toString()
method otherwise your game may have undefined behavior.
// REQUIRED - put this in your GameState.java public String toString () { StringBuilder outputString = new StringBuilder(); outputString.append(String.format("Score: %d\n", getScore())); for (int row = 0; row < getBoard().length; row++) { for (int column = 0; column < getBoard()[0].length; column++) { outputString.append(getBoard()[row][column] == 0 ? " -" : String.format("%5d", getBoard()[row][column])); } outputString.append("\n"); } return outputString.toString(); }
You must add this method to your GameState.java
and do not implement your own toString()
.
public GameState (int numRows, int numCols); // REQUIRED
This is the sole constructor for the GameState class. To implement it, you will use the parameters passed in to initialize your instance variables. In this constructor, you should:
numRows
rows and numCols
columns. This should be an empty board. You don't need to do any checks for the validity of numRows
and numCols
.If you choose to create your Random
object here, you may want to use the variable RANDOM_SEED
in Config.java
as the argument (seed). The sequence of random numbers generated by the Random
object is solely determined by the value of the seed, meaning that each time you run the program you will get the same sequence of numbers. If you do not specify a seed, then a seed is used depending on the literal time of the initialization of the object.
You must implement this method with the given method signature and it must have the specified behavior.
public int[][] getBoard (); // REQUIRED public void setBoard (int[][] newBoard); // REQUIRED public int getScore (); // REQUIRED public void setScore (int newScore); // REQUIRED
public int[][] getBoard ()
In this method, you should return a deep copy of the board of GameState class. Although you are free to represent your board however you want, we require the return board to be a 2d-array with the appropriate number of rows and columns. Each element of the 2d-array should either be the value of the tile or 0 if the tile does not exist. This method should not make any changes to the game state.
You must implement this method with the given method signature and it must have the specified behavior.
public void setBoard (int[][] newBoard)
In the method, you should make a deep copy of the input board and set the board of GameState class to be the deep-copied board. For null
inputs, make no change and return immediately. The format of the input 2d array is the same as the format of the output 2d array from getBoard()
. This method should not make any changes to the game state other than setting the new board state.
You must implement this method with the given method signature and it must have the specified behavior.
public int getScore ()
Return the current score. This method should not make any changes to the game state.
You must implement this method with the given method signature and it must have the specified behavior.
public void setScore (int newScore)
Sets the current score to be the input score. Note that this should also allow us to set a negative score. This method should not make any changes to the game state other than setting the score.
You must implement this method with the given method signature and it must have the specified behavior.
protected int rollRNG (int bound); // optional protected int randomTile (); // REQUIRED protected int countEmptyTiles (); // REQUIRED protected int addTile (); // REQUIRED
protected int rollRNG (int bound)
Return a random integer between 0(inclusive) and bound(exclusive). You should use the nextInt()
method of the Random
class.
protected int randomTile ()
In the game of 2048, a tile numbered either 2 or 4 is added to the board after each successful move/slide. You should be using this method to decide which kind of tile should be added to the board whenever a new tile is needed. Return the int value 2
with probability TWO_PROB
(from the Config.java
file, where TWO_PROB=70
means 70% chance) and 4
with the remaining probabilty. This method should not make any changes to the game state.
You must implement this method with the given method signature and it must have the specified behavior.
protected int countEmptyTiles ()
Return the number of empty tiles on the board. This method should not make any changes to the game state.
You must implement this method with the given method signature and it must have the specified behavior.
protected int addTile ()
In this method, add a new tile to the board. You should return the type of the tile added, either 2 or 4 (this must be whatever randomTile()
returns to us). Note that we might theoretically call this method on a full board, in which case we wouldn't be able to add any tiles. Return 0 if you are unable to add any tile. This method should not make any changes to the game state other than adding the new tile.
You must implement this method with the given method signature and it must have the specified behavior.
rollRNG()
to help you choose the spot to add the tile). Then, add the tile decided by randomTile()
, and add it to the board. Return the type of the tile just added to the board (i.e. return 2
if you added a 2 and return 4
if you added a 4).protected void rotateCounterClockwise (); // optional protected boolean canSlideDown (); // REQUIRED public boolean isGameOver (); // REQUIRED protected boolean slideDown (); // REQUIRED public boolean move (Direction dir); // REQUIRED
protected void rotateCounterClockwise ()
Rotate the board counter-clockwise once. You may want to read the specification for the move()
method before deciding whether or not to implement this method. This should be very similar to how you did the transposition for PA3. You are not required to implement this method.
protected boolean canSlideDown ()
Use this method to determine if you can slide down. You will return true
if sliding down is possible and false
otherwise. This method should not make any changes to the game state.
In particular, there are two cases where sliding down is possible:
More precisely, this method should return true
if slideDown()
(defined below) should change the state of the board and return false
if slideDown()
should not be able to change the state of the board.
You must implement this method with the given method signature and it must have the specified behavior.
public boolean isGameOver ()
A game is over when you cannot move in any direction. To implement isGameOver()
, use rotateCounterClockwise()
and canSlideDown()
to see if movement in any direction is possible. If any movement is possible, return false
as the game is not over. Otherwise, return true
to indicate that there are no more possible moves and that the game is over. This method should not make any changes to the game state.
You must implement this method with the given method signature and it must have the specified behavior.
protected boolean slideDown ()
Slide the board down and return whether the sliding was successful (if it changed the board). This method should also increase the score for this game, if applicable.
This method will return true
if sliding down is successful (i.e. the move was possible and the board changed after the sliding is completed) and return false
otherwise (the board didn't change).
To slide down, there are few things you need to implement in order for the game to work.
One thing you might notice is that all the empty spaces on the board can be represented by 0
s. The functionalities of movement in any direction is the same. Here is what you should implement for each column of the board to slide down.
0
. This combined tile cannot merge with any other tile in the same move. Suppose for the following examples that the listed tiles are from top to bottom. Suppose we had 4 4 4. This would combine to be 0 4 8 (note that it is not 0 8 4, in other words, the farthest down tiles combine first). If we had 4 4 4 4, this would combine to be 0 0 8 8.You must implement this method with the given method signature and it must have the specified behavior.
Here are some examples for you to visualize the basic movements of our 2048.
Example 1:
Should return true
0 0 2 0 0 0
0 0 0 -> 0 0 0
0 0 0 0 0 2
Example 2:
Should return true
0 0 0 0 0 0
0 0 2 -> 0 0 0
0 0 2 0 0 4
Example 3:
Should return true
0 0 2 0 0 0
0 0 2 -> 0 0 2
0 0 2 0 0 4
Example 4:
Should return false
0 0 0 0 0 0
0 0 4 -> 0 0 4
0 0 8 0 0 8
An example game may be:
[cs8bwi20XX@ieng6-203]:pa4: java PlayManager
Starting a default-sized random game...
Score: 0
- - - -
- - - -
2 - - 2
- - - -
> d
Score: 4
- - - -
- - - -
- - - 4
- - 2 -
> s
Score: 4
- - - -
- - - -
- - - -
2 - 2 4
> a
Score: 8
- - - -
- - - -
- - 4 -
4 4 - -
> w
Score: 8
4 4 4 -
- - - -
- - - -
- - - 2
> a
Score: 16
8 4 - -
- - 2 -
- - - -
2 - - -
> a
Score: 16
8 4 - -
2 - - 2
- - - -
2 - - -
> s
Score: 20
- - - -
- - - -
8 - - 4
4 4 - 2
> q
public boolean move (Direction dir)
This method should slide the board in the specified direction. If the sliding is done successfully, then a new random tile must be added to the board using your addTile()
method. This method should return whether the move was successful or not. If dir
is null, return false
without doing anything.
You must implement this method with the given method signature and it must have the specified behavior.
Note: If you implemented the rotateCounterClockwise()
method then here is where its power shines. You can combine rotateCounterClockwise()
, the getRotationCount()
method from the Direction
enum, and the slideDown()
method to implement this method for all directions.
Compile your code with javac *.java
. Then, use java PlayManager
to load a default game. Type one of wasd
to determine the direction to slide (w is up, a is left, s is down, and d is right), o
to save the game to a file called save.2048
, or q
to exit. You can also type java PlayManager [SAVEFILE]
to load a game from [SAVEFILE]
.
The following questions about provided files and general policies are graded for fair effort and completeness. It is recommended to look at gradescope rubric to see our suggested answers afterward even if you get full credit.
Config.java
file? In general, what is the purpose of setting these magic numbers in a scope accessible by all your code (e.g. at the top of your file inside the class)?GameState.java
we had to return a deep copy of the board in getBoard()
. Why might we want to create a deep copy rather than a shallow copy of the board if we only need read access to it?Please fill out our student satisfaction survey. We are changing how we approach giving assignments and would like to hear about your experiences.
We will grade your code style thoroughly. Namely, there are a few things you must have in each file / class / method:
A full style guideline can be found here. If you need any clarifications, feel free to ask on Piazza.
Submit the following files to Gradescope. Make sure to wait until you see the autograder output. Required Submission Files:
GameState.java
README.md
Start early and start often!