In this project I implemented the tile moving & merging functionality. I think the main goal of this project was just to get more familiar with writing Java and navigating a code base, most of the project had already been implemented, all that was left was a couple of methods.
Helper methods:
public static boolean emptySpaceExists(Board b)
This method should return true if any of the tiles in the given board are null. You should NOT modify the Board.java file in any way for this project. For this method, you’ll want to use the tile(int col, int row) and size() methods of the Board class. No other methods are necessary.
Solution:
/** Returns true if at least one space on the Board is empty.
* Empty spaces are stored as null.
*/
public static boolean emptySpaceExists(Board b) {
for (int i = 0; i < b.size(); i += 1) {
for (int j = 0; j < b.size(); j += 1) {
if (b.tile(i, j) == null) {
return true;
}
}
}
return false;
}
public static boolean maxTileExists(Board b)
This method should return true if any of the tiles in the board are equal to the winning tile value 2048.
Solution:
/**
* Returns true if any tile is equal to the maximum valid value.
* Maximum valid value is given by this.MAX_PIECE. Note that
* given a Tile object t, we get its value with t.value().
*/
public static boolean maxTileExists(Board b) {
for (int i = 0; i < b.size(); i += 1) {
for (int j = 0; j < b.size(); j += 1) {
if (b.tile(i, j) != null && b.tile(i, j).value() == Model.MAX_PIECE) {
return true;
}
}
}
return false;
}
public static boolean atLeastOneMoveExists(Board b)
This method is more challenging. It should return true if there are any valid moves. By a “valid move”, we mean that if there is a button (UP, DOWN, LEFT, or RIGHT) that a user can press while playing 2048 that causes at least one tile to move, then such a keypress is considered a valid move.
There are two ways that there can be valid moves:
There is at least one empty space on the board.
There are two adjacent tiles with the same value.
Solution:
/**
* Returns true if there are any valid moves on the board.
* There are two ways that there can be valid moves:
* 1. There is at least one empty space on the board.
* 2. There are two adjacent tiles with the same value.
*/
public static boolean atLeastOneMoveExists(Board b) {
if (emptySpaceExists(b)) {
return true;
}
/** Go by rows, check direction bottom -> up and left -> right (start 0,0, is at bottom left) */
for (int y = 0; y < b.size(); y += 1) {
/** direction x - only loop till (size - 1) - (going left to right in comparisons) */
for (int x = 0; x < b.size() - 1; x += 1) {
/** check for a match in direction left to right (x axis) */
if (b.tile(x, y).value() == b.tile(x + 1, y).value()) {
return true;
}
/* if not last row we check if there's a match dir up (y axis) */
if (y != b.size() - 1 && b.tile(x, y).value() == b.tile(x, y + 1).value()) {
return true;
}
}
}
return false;
}
Main Task: Building the Game Logic
public void tilt(Side side)
The tilt method does the work of actually moving all the tiles around. For example, if we have the board given by:
And press up, tilt will modify the board instance variable so that the state of the game is now:
| 2| 8| 4| 2|
| 4| 4| 4| 8|
| 2| | | |
| | | | |
In addition to modifying the board, the score instance variable must be updated to reflect the total value of all tile merges (if any). For the example above, we merged two 4s into an 8, and two 2s into a 4, so the score should be incremented by 8 + 4 = 12.
All movements of tiles on the board must be completed using the move method provided by the Board class. All tiles of the board must be accessed using the tile method provided by the Board class. Due to some details in the GUI implementation, you should only call move on a given tile once per call to tilt. We’ll discuss this constraint further in the Tips section of this document.
Solution:
/** Tilt the board toward SIDE.
*
* 1. If two Tile objects are adjacent in the direction of motion and have
* the same value, they are merged into one Tile of twice the original
* value and that new value is added to the score instance variable
* 2. A tile that is the result of a merge will not merge again on that
* tilt. So each move, every tile will only ever be part of at most one
* merge (perhaps zero).
* 3. When three adjacent tiles in the direction of motion have the same
* value, then the leading two tiles in the direction of motion merge,
* and the trailing tile does not.
*/
public void tilt(Side side) {
_board.setViewingPerspective(side);
Hashtable<Tile, Boolean> MergedTiles = new Hashtable<>();
for (int x = 0; x < _board.size(); x += 1) {
for (int y = _board.size() - 1; y >= 0; y -= 1) {
Tile t = _board.tile(x, y);
if (t != null) {
int moveToRow = -1;
boolean shouldMerge = false;
for (int rowAbove = y + 1; rowAbove < _board.size(); rowAbove += 1) {
Tile aboveTile = _board.tile(x, rowAbove);
//if the tile above is null, tile t can move there
// continue to check if next is null or merge
if (aboveTile == null) {
moveToRow = rowAbove;
continue;
}
// we are merging if value equals and tile hasn't been merged
if (aboveTile.value() == t.value() && !MergedTiles.containsKey(aboveTile)) {
shouldMerge = true;
moveToRow = rowAbove;
}
// is not null, it can or can't merge - no need to check the other rows (tiles can either merge or take up empty space)
break;
}
if (shouldMerge) {
_board.move(x, moveToRow, t);
Tile movedT = _board.tile(x, moveToRow);
//newScore - add the value of the newly merged tile to total score;
int newScore = score() + movedT.value();
set_score(newScore);
MergedTiles.put(movedT, true);
} else if (moveToRow > - 1) {
_board.move(x, moveToRow, t);
}
}
}
}
_board.setViewingPerspective(Side.NORTH);
checkGameOver();
}