Java 2048 Game – Merge, Match, Master
The 2048 game has become a sensation in the world of puzzle games since its creation in 2014. The objective of the game is to merge numbered tiles on a grid to reach the elusive 2048 tile, all while strategically planning each move to achieve the highest score possible.
About Java 2048 Game
- The game is played on a 4X4 grid, and each tile can have a value that is a power of 2, starting from 2.
- When two tiles with the same value collide while sliding, they merge into a single tile with double the value.
- The goal is to keep merging tiles and creating larger numbers until you reach the 2048 tile.
- To play the game, you can use the arrow keys on your keyboard. After each move, a new tile will appear in an empty spot on the grid.
- The game ends when the grid is completely filled, and no more moves can be made.
Prerequisites for 2048 Game Using Java
- IDE Used: IntelliJ
- Java 1.8 or above must be installed.
Download Java 2048 Game Project
Please download the source code of Java 2048 Game Project from the following link: Java 2048 Game Project Code.
Steps to create 2048 game in Java:
These are the steps to build a 2048 game in Java:
- Game structure and GUI setup
- Initializing the game
- Adding new tiles
- Grid Manipulation
- Updating the GUI
- Game over and Restart
- Update the score
- Main() method
- Test the game
1. Game structure and GUI setup:
Import Statements: The import statements at the beginning of the code import necessary classes and packages from the Java AWT, Swing, and util libraries.
import java.awt.*; import java.awt.event.*; import java.util.Random; import javax.swing.*;
Class definition and declaring variables: Define a class called “Game2048” and declare all necessary variables and GUI components needed to manage the game state and display the game on the screen.
public class Game2048 {
// Size of the game grid
private static final int SIZE = 4;
//Instance variables
private int[][] grid;
private Random random;
private int score;
private int highScore;
private JFrame frame;
private JPanel gridPanel;
private JLabel[][] gridLabels;
private JLabel scoreLabel;
private JLabel highScoreLabel;
private boolean winConditionReached;
Constructor: The class constructor initializes the game state, sets up the GUI components, and starts the game.
public Game2048() {
// Constructor code
grid = new int[SIZE][SIZE];
random = new Random();
score = 0;
highScore = 0;
// Create the GUI components
frame = new JFrame("TechVidvan's 2048 Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 450);
frame.setLayout(new BorderLayout());
gridPanel = new JPanel(new GridLayout(SIZE, SIZE));
gridLabels = new JLabel[SIZE][SIZE];
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
gridLabels[i][j] = new JLabel("", JLabel.CENTER);
gridLabels[i][j].setFont(new Font("Arial", Font.BOLD, 24));
gridLabels[i][j].setOpaque(true);
gridLabels[i][j].setBackground(Color.LIGHT_GRAY);
gridPanel.add(gridLabels[i][j]);
}
}
The GUI components are set up, including the frame, grid panel, and information panel. Key listeners are added to handle user input.
// Add components to the frame
frame.add(gridPanel, BorderLayout.CENTER);
JPanel infoPanel = new JPanel(new GridLayout(2, 2));
scoreLabel = new JLabel("Score: 0", JLabel.CENTER);
highScoreLabel = new JLabel("High Score: 0", JLabel.CENTER);
infoPanel.add(scoreLabel);
infoPanel.add(highScoreLabel);
frame.add(infoPanel, BorderLayout.NORTH);
frame.addKeyListener(new KeyAdapter() {
// Handle key events
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_UP) {
moveUp();
} else if (keyCode == KeyEvent.VK_DOWN) {
moveDown();
} else if (keyCode == KeyEvent.VK_LEFT) {
moveLeft();
} else if (keyCode == KeyEvent.VK_RIGHT) {
moveRight();
}
updateGridLabels();
updateScore();
if (isGameOver()) {
showGameOverMessage();
}
}
// Set frame properties and initialize the grid
frame.setFocusable(true);
frame.requestFocus();
frame.setVisible(true);
initializeGrid();
updateGridLabels();
2. Initializing the game:
The initializeGrid() method is responsible for initializing the game grid. This method iterates through each cell of the grid and initializes it to 0. It then calls the method addNewNumber() twice to add two initial numbers to the grid.
public void initializeGrid() {
// Grid initialization code
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
grid[i][j] = 0;
}
}
// Add two new numbers to random positions
addNewNumber();
addNewNumber();
}
3. Adding new tiles:
The ‘addNewNumber()’ method is responsible for adding a new tile (either 2 or 4) to a random empty cell in the grid. It uses the random number generator to select a random row and column and checks if the selected cell is empty. If the cell is empty, a new tile is added.
public void addNewNumber() {
// New number generation code
int row, col;
do {
// Generate random row and column indices
row = random.nextInt(SIZE);
col = random.nextInt(SIZE);
} while (grid[row][col] != 0);
// Assign a new number (2 or 4) to the empty cell
grid[row][col] = (random.nextInt(2) + 1) * 2;
}
4. Grid Manipulation:
The game allows the player to move the tiles in four directions: up, down, left, and right. The ‘moveup()’, ‘moveDown()’, ‘moveLeft()’, and ‘moveRight()’ methods handle the logic for moving the tiles in the respective directions. These methods use nested loops to iterate over the grid and update the positions of the tiles accordingly. If any tiles are merged during the movement, the score is updated.
public void moveUp() {
//Moves and merges the tiles upwards
int prevScore = score;
boolean moved = false;
for (int j = 0; j < SIZE; j++) {
int mergeValue = -1;
for (int i = 1; i < SIZE; i++) {
if (grid[i][j] != 0) {
int row = i;
while (row > 0 && (grid[row - 1][j] == 0 || grid[row - 1][j] == grid[row][j])) {
if (grid[row - 1][j] == grid[row][j] && mergeValue != row - 1) {
grid[row - 1][j] *= 2;
score += grid[row - 1][j];
grid[row][j] = 0;
mergeValue = row - 1;
moved = true;
} else if (grid[row - 1][j] == 0) {
grid[row - 1][j] = grid[row][j];
grid[row][j] = 0;
moved = true;
}
row--;
}
}
}
}
if (moved) {
addNewNumber();
updateScore();
}
}
public void moveDown() {
//Moves and merges the tiles downwards
int prevScore = score;
boolean moved = false;
for (int j = 0; j < SIZE; j++) {
int mergeValue = -1;
for (int i = SIZE - 2; i >= 0; i--) {
if (grid[i][j] != 0) {
int row = i;
while (row < SIZE - 1 && (grid[row + 1][j] == 0 || grid[row + 1][j] == grid[row][j])) {
if (grid[row + 1][j] == grid[row][j] && mergeValue != row + 1) {
grid[row + 1][j] *= 2;
score += grid[row + 1][j];
grid[row][j] = 0;
mergeValue = row + 1;
moved = true;
} else if (grid[row + 1][j] == 0) {
grid[row + 1][j] = grid[row][j];
grid[row][j] = 0;
moved = true;
}
row++;
}
}
}
}
if (moved) {
addNewNumber();
updateScore();
}
}
public void moveLeft() {
//Moves and merges the tiles towards left
int prevScore = score;
boolean moved = false;
for (int i = 0; i < SIZE; i++) {
int mergeValue = -1;
for (int j = 1; j < SIZE; j++) {
if (grid[i][j] != 0) {
int col = j;
while (col > 0 && (grid[i][col - 1] == 0 || grid[i][col - 1] == grid[i][col])) {
if (grid[i][col - 1] == grid[i][col] && mergeValue != col - 1) {
grid[i][col - 1] *= 2;
score += grid[i][col - 1];
grid[i][col] = 0;
mergeValue = col - 1;
moved = true;
} else if (grid[i][col - 1] == 0) {
grid[i][col - 1] = grid[i][col];
grid[i][col] = 0;
moved = true;
}
col--;
}
}
}
}
if (moved) {
addNewNumber();
updateScore();
}
}
public void moveRight() {
//Moves and merges the tiles towards right
int prevScore = score;
boolean moved = false;
for (int i = 0; i < SIZE; i++) {
int mergeValue = -1;
for (int j = SIZE - 2; j >= 0; j--) {
if (grid[i][j] != 0) {
int col = j;
while (col < SIZE - 1 && (grid[i][col + 1] == 0 || grid[i][col + 1] == grid[i][col])) {
if (grid[i][col + 1] == grid[i][col] && mergeValue != col + 1) {
grid[i][col + 1] *= 2;
score += grid[i][col + 1];
grid[i][col] = 0;
mergeValue = col + 1;
moved = true;
} else if (grid[i][col + 1] == 0) {
grid[i][col + 1] = grid[i][col];
grid[i][col] = 0;
moved = true;
}
col++;
}
}
}
}
if (moved) {
addNewNumber();
updateScore();
}
}
5. Updating the GUI:
The updateGridLabels() method is used to update the visual representation of the grid in the GUI. It iterates over the grid and updates the JLabel components accordingly.
public void updateGridLabels() {
// Update the GUI grid labels with the current grid state
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
if (grid[i][j] == 0) {
gridLabels[i][j].setText("");
gridLabels[i][j].setBackground(Color.LIGHT_GRAY);
} else if (grid[i][j] == 2048) {
winConditionReached = true;
gridLabels[i][j].setText(String.valueOf(grid[i][j]));
gridLabels[i][j].setBackground(getTileColor(grid[i][j]));
} else {
// Set the text of each label to the corresponding grid cell value
gridLabels[i][j].setText(String.valueOf(grid[i][j]));
gridLabels[i][j].setBackground(getTileColor(grid[i][j]));
}
// Add grid lines
gridLabels[i][j].setBorder(BorderFactory.createLineBorder(Color.BLACK));
}
}
}
The background colour and text of each label are set based on the value of the corresponding grid cell. The getTileColor() method maps each tile value to a specific color.
public Color getTileColor(int value) {
//Maps each tile value to a specific color
switch (value) {
case 2:
return new Color(238, 228, 218);
case 4:
return new Color(237, 224, 200);
case 8:
return new Color(242, 177, 121);
case 16:
return new Color(245, 149, 99);
case 32:
return new Color(246, 124, 95);
case 64:
return new Color(246, 94, 59);
case 128:
return new Color(237, 207, 114);
case 256:
return new Color(237, 204, 97);
case 512:
return new Color(237, 200, 80);
case 1024:
return new Color(237, 197, 63);
case 2048:
return new Color(237, 194, 46);
default:
return Color.WHITE;
}
}
6. Game over and Restart:
The ‘isGameOver()’ method checks if the game is over by examining the grid or by checking whether the win condition is reached. If no empty cells are available, and there are no adjacent cells with the same value, the game is considered over.
public boolean isGameOver() {
//Game-over condition
if(winConditionReached){
return true;
}
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
if (grid[i][j] == 0 ||
(i > 0 && grid[i][j] == grid[i - 1][j]) ||
(i < SIZE - 1 && grid[i][j] == grid[i + 1][j]) ||
(j > 0 && grid[i][j] == grid[i][j - 1]) ||
(j < SIZE - 1 && grid[i][j] == grid[i][j + 1])) {
return false;
}
}
}
return true;
}
The ‘showGameOverMessage()’ method displays a message dialog to the player, indicating whether they have won or lost the game. The player can choose to play again or exit the game.
public void showGameOverMessage() {
//Displays game-over message
String message;
if (winConditionReached) {
message = "Congratulations! You reached the 2048 tile!\nDo you want to continue playing?";
} else {
message = "Game over! Do you want to play again?";
}
int choice = JOptionPane.showConfirmDialog(frame, message, "Game Over", JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.YES_OPTION) {
restartGame();
} else {
System.exit(0);
}
}
The ‘restartGame()’ method resets the game state, including the score, win condition, grid, and GUI. It then initializes the game again.
public void restartGame() {
//Restarts the game
score = 0;
winConditionReached=false;
updateScore();
initializeGrid();
updateGridLabels();
}
7. Update the score:
The ‘updateScore()’ method updates the score and high score labels in the GUI.
public void updateScore() {
//Updates the score
scoreLabel.setText("Score: " + score);
if (score > highScore) {
highScore = score;
highScoreLabel.setText("High Score: " + highScore);
}
}
8. Main() method:
The ‘main()’ method is the entry point of the program. It creates an instance of the Game2048 class, which starts the game by invoking the constructor.
public static void main(String[] args) {
//Main method
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Game2048();
}
});
}
9. Test the game:
Verify that the game functions correctly by playing through various scenarios, including wins and losses.
a. Beginning
b. Game Won
c. Game Lost
Conclusion
In conclusion, the provided code implements the popular game 2048 using Java and Swing for the graphical user interface (GUI). The game features a 4×4 grid where the player combines numbered tiles to reach the goal of 2048. The code utilizes object-oriented programming principles to organize the game logic and GUI components.



