Table of contents
Here's a brief explanation of the game:
Game Rules:
Game Board:
The game is played on a 4x4 grid.
Tiles with numbers (powers of 2) are randomly placed on the grid.
Tile Movement:
Tiles can be moved in four directions: up, down, left, and right.
When a player makes a move, all tiles on the board slide as far as possible in the chosen direction.
Tile Merging:
When two tiles with the same number collide while moving, they merge into a tile with the sum of their values.
For example, if two tiles with the number 2 collide, they merge into a single tile with the number 4.
Scoring:
The player earns points for every merged tile.
The goal is to reach the highest possible tile value, ideally reaching the tile with the number 2048.
Game Over:
- The game ends when there are no more valid moves (i.e., the grid is full, and no tiles can be merged).
Features Required:
Game Board:
A 4x4 grid representing the game board.
Tiles with values, initially two tiles with a value of 2 or 4.
Game Logic:
Merging tiles with the same value when they collide.
Scoring based on the merged tiles.
User Input:
- Accepting user input for movement (left, right, up, down).
Score Calculation:
- Calculating and updating the score based on merged tiles.
Design Patterns Involved:
Singleton Pattern:
- GameManager as a singleton to manage the game state.
Observer Pattern:
Subject (GameManager) and Observer (UI elements) for reacting to state changes.
Observers for changes in the game state.
Example: UI elements that update when the game state changes.
Command Pattern:
Command interface and concrete command classes for different user inputs.
Example: MoveLeftCommand, MoveRightCommand, MoveUpCommand, MoveDownCommand.
Factory Pattern:
CommandFactory for creating instances of commands.
Example: CommandFactory for creating move commands.
Relationship Diagram
Code:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
// Singleton Pattern
class GameManager {
private static GameManager instance;
private int[][] board;
private List<GameObserver> observers;
private GameManager() {
// Private constructor to prevent instantiation
initializeBoard();
observers = new ArrayList<>();
}
public static GameManager getInstance() {
if (instance == null) {
instance = new GameManager();
}
return instance;
}
private void initializeBoard() {
// Initialize the game board
board = new int[4][4];
addRandomTile();
addRandomTile();
}
private void addRandomTile() {
// Add a new tile (2 or 4) to a random empty cell
Random random = new Random();
int value = (random.nextInt(2) + 1) * 2; // Either 2 or 4
int row, col;
do {
row = random.nextInt(4);
col = random.nextInt(4);
} while (board[row][col] != 0);
board[row][col] = value;
}
public void move(Direction direction) {
// Handle the move logic (left, right, up, down)
// ...
// After each move, add a new tile
addRandomTile();
notifyObservers(); // Notify observers about the board update
}
public int[][] getBoard() {
return board;
}
// Observer Pattern
public void addObserver(GameObserver observer) {
observers.add(observer);
}
private void notifyObservers() {
for (GameObserver observer : observers) {
observer.update(board);
}
}
}
// Observer Pattern
interface GameObserver {
void update(int[][] board);
}
class ScoreManager implements GameObserver {
private int score;
public void update(int[][] board) {
// Update the score based on the current state of the board
// For simplicity, let's assume the score is the sum of all tile values
score = calculateScore(board);
// Notify UI or other components about the score change
System.out.println("Score Updated: " + score);
}
private int calculateScore(int[][] board) {
int totalScore = 0;
for (int[] row : board) {
for (int cell : row) {
totalScore += cell;
}
}
return totalScore;
}
public int getScore() {
return score;
}
}
// PlayerData class
class PlayerData {
private String playerName;
private int score;
public PlayerData(String playerName) {
this.playerName = playerName;
this.score = 0;
}
public int getScore() {
return score;
}
public void updateScore(int points) {
score += points;
}
}
// Command Pattern
interface MoveCommand {
void execute();
}
class MoveLeftCommand implements MoveCommand {
public void execute() {
GameManager.getInstance().move(Direction.LEFT);
}
}
class MoveRightCommand implements MoveCommand {
public void execute() {
GameManager.getInstance().move(Direction.RIGHT);
}
}
class MoveUpCommand implements MoveCommand {
public void execute() {
GameManager.getInstance().move(Direction.UP);
}
}
class MoveDownCommand implements MoveCommand {
public void execute() {
GameManager.getInstance().move(Direction.DOWN);
}
}
// Factory Pattern
class CommandFactory {
public MoveCommand createCommand(Direction direction) {
switch (direction) {
case LEFT:
return new MoveLeftCommand();
case RIGHT:
return new MoveRightCommand();
case UP:
return new MoveUpCommand();
case DOWN:
return new MoveDownCommand();
default:
throw new IllegalArgumentException("Invalid direction");
}
}
}
// Enum for directions
enum Direction {
LEFT, RIGHT, UP, DOWN
}
// Main class
public class Game2048 {
public static void main(String[] args) {
GameManager gameManager = GameManager.getInstance();
// Observer Pattern
ScoreManager scoreManager = new ScoreManager();
gameManager.addObserver(scoreManager);
// Player Data
PlayerData player = new PlayerData("Player1");
// Command Pattern
CommandFactory commandFactory = new CommandFactory();
MoveCommand moveLeftCommand = commandFactory.createCommand(Direction.LEFT);
MoveCommand moveRightCommand = commandFactory.createCommand(Direction.RIGHT);
// Execute commands
moveLeftCommand.execute();
moveRightCommand.execute();
// Get the current state of the board
int[][] currentBoard = gameManager.getBoard();
// Print the board
for (int[] row : currentBoard) {
for (int cell : row) {
System.out.print(cell + "\t");
}
System.out.println();
}
// Observer Pattern: Update the score
System.out.println("Current Score: " + scoreManager.getScore());
// Update player data based on the score
player.updateScore(scoreManager.getScore());
System.out.println("Player Score: " + player.getScore());
}
}
This example provides a basic structure for a 2048 game, incorporating the Singleton, Observer, Command, and Factory design patterns. The game manager is a singleton that manages the game state, and observers are notified of changes in the game state. Command classes encapsulate different user inputs, and a factory is used to create instances of these commands.