Создание игры 2048 на языке C

CCBeginner
Практиковаться сейчас

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

2048 - это популярная числовая головоломка, цель которой - достичь плитки с номером 2048, объединяя соседние плитки с одинаковыми числами. В этом проекте вы научитесь создавать простую игру 2048 на языке C. Мы предоставим пошаговые инструкции по созданию игры, начиная от инициализации игрового поля и заканчивая реализацией игровой логики и запуском игры.

👀 Предварительный просмотр

2048 Game

🎯 Задачи

В этом проекте вы научитесь:

  • Создавать файлы проекта
  • Определять константы для игры
  • Реализовывать функцию main() для запуска игрового цикла
  • Инициализировать игровое поле
  • Реализовывать функции для проверки состояния игры
  • Создавать логику для перемещения плиток
  • Отображать игровое поле
  • Компилировать и тестировать игру

🏆 Достижения

После завершения этого проекта вы сможете:

  • Создавать программу на языке C для игры
  • Использовать массивы для представления игрового поля
  • Реализовывать игровую логику для объединения плиток
  • Отображать игровое поле
  • Обрабатывать ввод от игрока
  • Проверять условия окончания игры и победы

Создание файлов проекта

Сначала создайте новый файл с именем 2048.c и откройте его в вашем предпочитаемом текстовом редакторе кода.

cd ~/project
touch 2048.c
✨ Проверить решение и практиковаться

Определение констант

Сначала нам нужно написать код на языке C. Первым шагом является включение заголовочных файлов:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

Перед написанием функции main() давайте выполним несколько базовых задач и определим некоторые константы:

#define SIZE 4
#define WIN_SCORE 2048

int board[SIZE][SIZE];
int score = 0;
✨ Проверить решение и практиковаться

Функция main

int main() {
    srand(time(NULL));
    init_board();
    print_board();

    while (1) {
        if (is_won()) {
            printf("You won!\n");
            break;
        }

        if (is_full() &&!can_move()) {
            printf("Game over!\n");
            break;
        }

        int direction;
        printf("Enter the move direction (0-Up, 1-Down, 2-Left, 3-Right): ");
        scanf("%d", &direction);

        if (move(direction)) {
            // Print the board after the move
            print_board();
        }
    }

    return 0;
}
  • init_board(): Этот вызов функции инициализирует игровое поле, устанавливает все ячейки игрового поля в 0, а затем генерирует два начальных случайных числа (2 или 4) в случайных местах.
  • print_board(): Эта функция используется для отображения текущего состояния игрового поля, включая текущий счет и число в каждой ячейке.
  • while (1): Это бесконечный цикл, который будет продолжать запускать игру до тех пор, пока не будет выполнено условие окончания игры.

Основной поток игры 2048 реализован здесь, включая инициализацию игрового поля, перемещение числовых блоков, определение победы или поражения в игре и ожидание ввода от игрока для управления ходом игры.

✨ Проверить решение и практиковаться

Инициализация игрового поля

Для инициализации игрового поля мы создадим функцию init_board, которая настроит поле и сгенерирует два начальных случайных числа.

void init_board() {
    // Initialize the board by setting all cells to 0
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            board[i][j] = 0;
        }
    }

    // Generate two initial random numbers
    for (int k = 0; k < 2; k++) {
        int i = rand() % SIZE;
        int j = rand() % SIZE;
        int value = (rand() % 2 + 1) * 2; // Generate 2 or 4 randomly
        board[i][j] = value;
    }
}

Что делает эта функция: в начале игры она очищает все ячейки на игровом поле и генерирует два начальных случайных блока чисел в случайных местах, предоставляя игроку начальное состояние игры.

✨ Проверить решение и практиковаться

Реализация функций для проверки состояния игры

Нам нужны функции для проверки, выиграл ли игрок, заполнено ли игровое поле и есть ли еще допустимые ходы. Вот эти функции:

int is_full() {
    // Check if the board is full
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (board[i][j] == 0) {
                return 0; // Board is not full
            }
        }
    }
    return 1; // Board is full
}

int is_won() {
    // Check if the player has won
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (board[i][j] == WIN_SCORE) {
                return 1; // Player has won
            }
        }
    }
    return 0; // Player has not won
}

int can_move() {
    // Check if there are any valid moves left
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (board[i][j] == 0) {
                return 1; // There are still empty cells to move
            }
            if (j > 0 && board[i][j] == board[i][j - 1]) {
                return 1; // Can move left
            }
            if (j < SIZE - 1 && board[i][j] == board[i][j + 1]) {
                return 1; // Can move right
            }
            if (i > 0 && board[i][j] == board[i - 1][j]) {
                return 1; // Can move up
            }
            if (i < SIZE - 1 && board[i][j] == board[i + 1][j]) {
                return 1; // Can move down
            }
        }
    }
    return 0; // No valid moves left
}
  • int is_full(): Эта функция используется для проверки, заполнено ли игровое поле, то есть все ли ячейки заняты.

  • int is_won(): Эта функция проверяет, выиграл ли игрок, то есть есть ли ячейка со значением WIN_SCORE (обычно 2048) для победы в игре.

  • int can_move(): Эта функция используется для проверки, есть ли еще допустимые ходы, чтобы убедиться, что игра может продолжаться.

✨ Проверить решение и практиковаться

Создание логики для перемещения плиток

Реализуйте логику перемещения плиток в функции move. Эта функция обрабатывает ходы игрока в четырех направлениях: вверх, вниз, влево и вправо. Она также проверяет, является ли ход допустимым, обновляет счет и генерирует новое случайное число.

int move(int dir) {
    int moved = 0;

    // Store the current board state
    int prev_board[SIZE][SIZE];
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            prev_board[i][j] = board[i][j];
        }
    }

    // Move upwards
    if (dir == 0) {
        for (int j = 0; j < SIZE; j++) {
            for (int i = 1; i < SIZE; i++) {
                if (board[i][j]!= 0) {
                    int k = i;
                    while (k > 0 && board[k - 1][j] == 0) {
                        board[k - 1][j] = board[k][j];
                        board[k][j] = 0;
                        k--;
                        moved = 1;
                    }
                    if (k > 0 && board[k - 1][j] == board[k][j]) {
                        board[k - 1][j] *= 2;
                        score += board[k - 1][j];
                        board[k][j] = 0;
                        moved = 1;
                    }
                }
            }
        }
    }

    // Move downwards
    else if (dir == 1) {
        for (int j = 0; j < SIZE; j++) {
            for (int i = SIZE - 2; i >= 0; i--) {
                if (board[i][j]!= 0) {
                    int k = i;
                    while (k < SIZE - 1 && board[k + 1][j] == 0) {
                        board[k + 1][j] = board[k][j];
                        board[k][j] = 0;
                        k++;
                        moved = 1;
                    }
                    if (k < SIZE - 1 && board[k + 1][j] == board[k][j]) {
                        board[k + 1][j] *= 2;
                        score += board[k + 1][j];
                        board[k][j] = 0;
                        moved = 1;
                    }
                }
            }
        }
    }

    // Move left
    else if (dir == 2) {
        for (int i = 0; i < SIZE; i++) {
            for (int j = 1; j < SIZE; j++) {
                if (board[i][j]!= 0) {
                    int k = j;
                    while (k > 0 && board[i][k - 1] == 0) {
                        board[i][k - 1] = board[i][k];
                        board[i][k] = 0;
                        k--;
                        moved = 1;
                    }
                    if (k > 0 && board[i][k - 1] == board[i][k]) {
                        board[i][k - 1] *= 2;
                        score += board[i][k - 1];
                        board[i][k] = 0;
                        moved = 1;
                    }
                }
            }
        }
    }

    // Move right
    else if (dir == 3) {
        for (int i = 0; i < SIZE; i++) {
            for (int j = SIZE - 2; j >= 0; j--) {
                if (board[i][j]!= 0) {
                    int k = j;
                    while (k < SIZE - 1 && board[i][k + 1] == 0) {
                        board[i][k + 1] = board[i][k];
                        board[i][k] = 0;
                        k++;
                        moved = 1;
                    }
                    if (k < SIZE - 1 && board[i][k + 1] == board[i][k]) {
                        board[i][k + 1] *= 2;
                        score += board[i][k + 1];
                        board[i][k] = 0;
                        moved = 1;
                    }
                }
            }
        }
    }

    // Check if the move was successful
    if (moved) {
        // Generate a new random number
        int i = rand() % SIZE;
        int j = rand() % SIZE;
        while (board[i][j]!= 0) {
            i = rand() % SIZE;
            j = rand() % SIZE;
        }
        board[i][j] = (rand() % 2 + 1) * 2; // Generate 2 or 4

        // Print the board after the move
        print_board();
    }

    // Check if the move was successful
    if (moved) {
        return 1;
    } else {
        // If the move failed, restore the previous board state
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                board[i][j] = prev_board[i][j];
            }
        }
        return 0;
    }
}
  • Сначала определите переменную moved для отметки, произошло ли перемещение. Начальное значение равно 0, что означает, что перемещение не произошло. Создайте временный двумерный массив prev_board для хранения текущего состояния игрового поля, чтобы можно было восстановить предыдущее состояние, если ход не удался.

  • Скопируйте текущее состояние игрового поля в prev_board, сохранив текущее состояние игрового поля на случай неудачного хода.

  • В зависимости от значения параметра dir (0 - вверх, 1 - вниз, 2 - влево, 3 - вправо) выполняется соответствующая операция перемещения.

  • Если произошло перемещение или слияние, флаг moved устанавливается в 1, что означает, что состояние игры изменилось. Если операция перемещения или слияния выполнена успешно, генерируется новое случайное число, которое используется для создания нового блока чисел в пустой ячейке игрового поля. Наконец, если произошло перемещение (moved равно 1), функция возвращает 1, что означает, что ход был успешным. Если ход не удался (не было перемещения или слияния), игровое поле восстанавливается в предыдущее состояние, и возвращается 0, что означает, что ход не удался.

✨ Проверить решение и практиковаться

Отображение игрового поля

Для отображения игрового поля мы создадим функцию print_board, которая очистит терминал и выведет текущее состояние поля.

void print_board() {
    // Clear the terminal
    system("clear");

    printf("Score: %d\n", score);

    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            printf("%4d", board[i][j]);
        }
        printf("\n");
    }
}

Основная цель здесь - очистить экран терминала, затем отобразить текущий счет в верхней части экрана, а затем вывести состояние игрового поля на экран, чтобы игрок мог четко видеть числовые блоки на игровом поле и счет. Это помогает создать удобный для пользователя интерфейс, который позволяет игрокам узнать текущее состояние игры.

✨ Проверить решение и практиковаться

Компиляция и тестирование

Введите следующую команду в терминале для компиляции и запуска:

cd ~/project
gcc -o 2048 2048.c
./2048
2048 Game
✨ Проверить решение и практиковаться

Итог

В этом проекте вы научились создавать базовую игру 2048 на языке C. Вы инициализировали игровое поле, реализовали функции для проверки состояния игры, создали логику для перемещения плиток, отобразили игровое поле и запустили игру. Наслаждайтесь игрой и дальнейшим улучшением своей игры 2048!