Игра 2048 — это поле 4×4, по которому нужно сдвигать плитки с цифрами: одинаковые числа при столкновении суммируются, а цель — собрать плитку со значением 2048. Создать 2048 на Java можно самостоятельно всего за три класса — ниже разберём рабочий код и логику движения плиток. Такая мини-игра — хорошая тренировка навыков Java и ООП, а если нужен не учебный проект, а полноценный продукт — в YuSMP Group есть команда мобильной и игровой разработки, которая доведёт идею до релиза.
Как создать игру на Java на примере 2048
Что понадобится:
- Ноутбук.
- Eclipse, IntelliJ IDEA или любая другая IDE на ваш выбор.
- JDK 8 или новее — Swing входит в стандартную библиотеку, ставить ничего дополнительно не нужно.
Разделим игру на три класса: Board, Tile и Game. Ниже — рабочий код каждого класса и объяснение, как они взаимодействуют.
| Класс | Что делает | Ключевые методы |
| Board | Хранит матрицу 4×4 из объектов Tile, двигает и объединяет плитки, отслеживает счёт | spawn(), isGameOver(), getScore() |
| Tile | Одна ячейка поля: числовое значение и цвет, который от него зависит | getValue(), setValue(), getColor() |
| Game | Запускает окно, слушает нажатия клавиш, перерисовывает поле после каждого хода | main(), keyPressed(), paintComponent() |
Шаг 1: класс Board — игровое поле
Board работает с игровым полем: хранит матрицу объектов Tile, считает текущий счёт и находит самую крупную плитку. Здесь же живёт логика добавления новых плиток и проверки конца игры.
public class Board {
private final int size = 4;
private Tile[][] tiles;
private int score;
public Board() {
tiles = new Tile[size][size];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
tiles[i][j] = new Tile();
}
}
}
public Tile[][] getBoard() {
return tiles;
}
public int getScore() {
return score;
}
public int getHighTile() {
int max = 0;
for (Tile[] row : tiles) {
for (Tile t : row) {
max = Math.max(max, t.getValue());
}
}
return max;
}
// Добавляет 2 (90% случаев) или 4 (10% случаев) в случайную пустую клетку
public void spawn() {
List<int[]> empty = new ArrayList<>();
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (tiles[i][j].getValue() == 0) empty.add(new int[]{i, j});
}
}
if (empty.isEmpty()) return;
int[] cell = empty.get(new Random().nextInt(empty.size()));
int value = Math.random() < 0.9 ? 2 : 4;
tiles[cell[0]][cell[1]] = new Tile(value);
}
// Игра окончена, если нет пустых клеток и нет соседних клеток с одинаковым значением
public boolean isGameOver() {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
if (tiles[i][j].getValue() == 0) return false;
if (j < size - 1 && tiles[i][j].getValue() == tiles[i][j + 1].getValue()) return false;
if (i < size - 1 && tiles[i][j].getValue() == tiles[i + 1][j].getValue()) return false;
}
}
return true;
}
}

Шаг 2: класс Tile — одна плитка
Tile — самый маленький класс: одна ячейка поля с числовым значением и цветом, который зависит от этого значения. Конструктор по умолчанию создаёт пустую плитку со значением 0.
public class Tile {
private int value;
public Tile() {
this.value = 0;
}
public Tile(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
// Цвет плитки зависит от значения — чем больше число, тем ярче цвет
public Color getColor() {
return switch (value) {
case 2 -> new Color(0xEEE4DA);
case 4 -> new Color(0xEDE0C8);
case 8 -> new Color(0xF2B179);
case 16 -> new Color(0xF59563);
default -> new Color(0xCDC1B4);
};
}
}
Шаг 3: класс Game — окно и запуск
Game принимает классы Tile и Board и связывает их друг с другом: настраивает окно JFrame, слушает нажатия клавиш и перерисовывает поле методом paintComponent после каждого хода.
public class Game extends JPanel implements KeyListener {
private final Board board = new Board();
public static void main(String[] args) {
Game game = new Game();
JFrame frame = new JFrame("2048");
frame.add(game);
frame.addKeyListener(game);
frame.setSize(420, 420);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
game.board.spawn();
game.board.spawn();
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP -> board.up();
case KeyEvent.VK_DOWN -> board.down();
case KeyEvent.VK_LEFT -> board.left();
case KeyEvent.VK_RIGHT -> board.right();
}
board.spawn();
if (board.isGameOver()) {
JOptionPane.showMessageDialog(this, "Игра окончена! Счёт: " + board.getScore());
}
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Tile[][] tiles = board.getBoard();
for (int i = 0; i < tiles.length; i++) {
for (int j = 0; j < tiles[i].length; j++) {
g.setColor(tiles[i][j].getColor());
g.fillRect(j * 100 + 10, i * 100 + 10, 90, 90);
if (tiles[i][j].getValue() != 0) {
g.setColor(Color.DARK_GRAY);
g.drawString(String.valueOf(tiles[i][j].getValue()), j * 100 + 45, i * 100 + 55);
}
}
}
}
@Override public void keyTyped(KeyEvent e) {}
@Override public void keyReleased(KeyEvent e) {}
}
Шаг 4: логика движения и слияния плиток
Самая важная часть — сдвиг и слияние плиток. Как только вы поймёте движение в одну сторону, остальные три направления — то же самое, применённое к развёрнутой или транспонированной матрице. Весь сдвиг одного ряда влево укладывается в один метод: уберите нули, слейте соседние одинаковые значения слева направо.
// Сдвигает и сливает один ряд влево; для других направлений ряд/столбец
// предварительно разворачивают или транспонируют и применяют тот же метод
private int[] shiftRow(int[] row) {
int[] result = new int[row.length];
int pointer = 0;
for (int value : row) {
if (value == 0) continue;
if (pointer > 0 && result[pointer - 1] == value) {
result[pointer - 1] *= 2;
score += result[pointer - 1];
} else {
result[pointer++] = value;
}
}
return result;
}
Для движения вправо массив разворачивают перед сдвигом и обратно после него. Для движения вверх и вниз матрицу транспонируют (меняют строки и столбцы местами), применяют shiftRow к каждой строке, а затем транспонируют обратно — отдельный код для вертикальных ходов не нужен.

Шаг 5: когда игра окончена
Игра заканчивается, когда одновременно выполняются два условия: на поле нет пустых клеток и ни одна пара соседних по горизонтали или вертикали плиток не имеет одинакового значения — то есть ни один ход больше невозможен. Метод isGameOver() из класса Board (см. Шаг 1) проверяет это после каждого хода.
Шаг 6: обработка нажатий клавиш
Метод keyPressed в классе Game проверяет код нажатой клавиши и вызывает соответствующий метод Board — up(), down(), left() или right(). После каждого хода добавляется новая плитка через spawn() и вызывается repaint(), чтобы перерисовать поле с учётом изменений. Если хотите управление на WASD вместо стрелок — добавьте в switch коды KeyEvent.VK_W, VK_A, VK_S, VK_D как синонимы стрелок.
Шаг 7: запускаем и дорабатываем
Надеемся, это руководство дало представление о том, как написать игру на Java, в частности — 2048. Из очевидных улучшений: анимация движения плиток, сохранение рекорда между запусками (через файл или SharedPreferences на Android-версии) и отмена последнего хода. Если вам нужно создать более сложный продукт или систему для бизнеса — веб-услуги и разработка в YuSMP Group станут лучшим решением. Проекты, которые мы создали, уже приносят высокие показатели своим владельцам.
Частые вопросы о создании 2048 на Java
Сколько времени занимает написать 2048 на Java?
Минимальную консольную или Swing-версию с тремя классами (Board, Tile, Game) опытный разработчик пишет за 30–60 минут. Если добавить анимации движения, сохранение рекорда и адаптивный интерфейс, уйдёт день-два.
Какая библиотека нужна для графического интерфейса 2048 на Java?
Для простого десктопного прототипа достаточно встроенного Swing (JFrame, JPanel, Graphics) — он уже входит в JDK, ничего дополнительно ставить не нужно. Для более современного интерфейса используют JavaFX, а для Android-версии — Jetpack Compose или классические View.
Как реализовать слияние одинаковых плиток в коде?
Пройдите ряд или столбец от края к центру: если встретили две соседние ненулевые плитки с одинаковым значением, замените их на одну с удвоенным значением и прибавьте его к счёту, а вторую ячейку обнулите. Это и есть метод shiftRow() из Шага 4.
Почему новая плитка 2 или 4 появляется в случайном месте?
Так устроена оригинальная механика 2048: после каждого хода в случайную пустую клетку добавляется плитка — в 90% случаев со значением 2, в 10% — со значением 4. Это создаёт баланс сложности и непредсказуемость партии.
Как понять, что игра окончена?
Игра заканчивается, когда на поле нет пустых клеток и ни одна пара соседних по горизонтали или вертикали клеток не имеет одинакового значения — то есть ни один ход больше не возможен.
Можно ли сделать 2048 на Java для Android, а не только для десктопа?
Да, логика классов Board и Tile из этой статьи переносится на Android почти без изменений — меняется только слой отрисовки: вместо Swing используется View/Canvas или Jetpack Compose, а обработка касаний — вместо KeyListener.
Что делать, если плитки сдвигаются, но не сливаются?
Чаще всего причина в том, что сравнение значений идёт до сдвига нулей, а не после. Сначала уберите из ряда все нули (сожмите плитки к краю), и только затем сравнивайте соседние пары на равенство — иначе логика слияния пропускает часть ходов.
Нужны разработчики в команду?
Подберём инженеров нужного стека под ваш проект — быстрый старт.




