I am a beginner learning Java, and I coded a command line version of the game 2048 for practice. Any feedback, especially regarding best practices, object-oriented principles, and tidying up the code logic, is appreciated.
import java.util.Scanner; class Cell { int value = 0; boolean hasMerged = false; } class Game { final int BOARD_SIZE = 4; Cell[][] cells; void create() { cells = new Cell[BOARD_SIZE][BOARD_SIZE]; for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { cells[i][j] = new Cell(); cells[i][j].value = 0; } } } int rowStart = 0; int columnStart = 0; int rowStep = 0; int columnStep = 0; int nextRow = 0; int nextColumn = 0; //to be used for move() and isLegal() void setDirection (char direction) { switch (direction) { case 'W': rowStart = 1; columnStart = 0; rowStep = 1; columnStep = 1; nextRow = -1; nextColumn = 0; break; case 'A': rowStart = 0; columnStart = 1; rowStep = 1; columnStep = 1; nextRow = 0; nextColumn = -1; break; case 'S': rowStart = 2; columnStart = 0; rowStep = -1; columnStep = 1; nextRow = 1; nextColumn = 0; break; case 'D': rowStart = 0; columnStart = 2; rowStep = 1; columnStep = -1; nextRow = 0; nextColumn = 1; break; } } void move() { boolean changed = true; while (changed) { changed = false; for (int i = rowStart; i >= 0 && i < BOARD_SIZE; i += rowStep) { for (int j = columnStart; j >= 0 && j < BOARD_SIZE; j += columnStep) { if (cells[i][j].value != 0 && cells[i + nextRow][j + nextColumn].value == 0) { changed = true; cells[i + nextRow][j + nextColumn].value = cells[i][j].value; cells[i + nextRow][j + nextColumn].hasMerged = cells[i][j].hasMerged; cells[i][j].value = 0; cells[i][j].hasMerged = false; } else if (cells[i][j].value != 0 && cells[i + nextRow][j + nextColumn].value == cells[i][j].value && !(cells[i][j].hasMerged || cells[i + nextRow][j + nextColumn].hasMerged)) { changed = true; cells[i + nextRow][j + nextColumn].value *= 2; cells[i + nextRow][j + nextColumn].hasMerged = true; cells[i][j].value = 0; cells[i][j].hasMerged = false; } } } } for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { cells[i][j].hasMerged = false; } } } void generateRandomCell() { int count = 0; int[][] empty = new int[16][2]; for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { if (cells[i][j].value == 0) { empty[count][0] = i; empty[count][1] = j; count += 1; } } } int randomIndex = (int) (Math.random() * count); if (Math.random() < 0.9) { cells[empty[randomIndex][0]][empty[randomIndex][1]].value = 2; } else { cells[empty[randomIndex][0]][empty[randomIndex][1]].value = 4; } } boolean isLegal() { for (int i = rowStart; i >= 0 && i < BOARD_SIZE; i += rowStep) { for (int j = columnStart; j >= 0 && j < BOARD_SIZE; j += columnStep) { if (cells[i][j].value != 0 && (cells[i + nextRow][j + nextColumn].value == 0 || cells[i][j].value == cells[i + nextRow][j + nextColumn].value)) { return true; } } } return false; } boolean isOver() { char[] directions = {'W', 'A', 'S', 'D'}; for (int i = 0; i < BOARD_SIZE; i++) { setDirection(directions[i]); if (isLegal()) { return false; } } return true; } void printBoard() { int width = 5; int space; System.out.print("\n"); for (int i = 0; i < BOARD_SIZE; i++) { for (int j = 0; j < BOARD_SIZE; j++) { space = width - String.valueOf(cells[i][j].value).length(); if (cells[i][j].value != 0) { System.out.print(cells[i][j].value); } else { System.out.print("_"); } System.out.print(" ".repeat(space)); } System.out.print("\n\n"); } System.out.println(""); } } class Game2048 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); Game game = new Game(); game.create(); game.generateRandomCell(); System.out.print("\n\nHello!\nEnter W, A, S, D to move, and Q to quit.\n\n"); while (!game.isOver()) { game.printBoard(); System.out.println("Enter your move: "); String userInput = scanner.nextLine(); if (userInput.length() != 1) { System.out.print("\nInvalid!\n"); continue; } char userMove = userInput.charAt(0); if (userMove == 'Q') { System.exit(0); } else if (userMove != 'W' && userMove != 'A' && userMove != 'S' && userMove != 'D') { System.out.print("\nInvalid!\n"); continue; } game.setDirection(userMove); if (game.isLegal()) { game.move(); game.generateRandomCell(); } else { System.out.print("\nIllegal!\n"); } } System.out.println("Game over!"); } }