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

🎯 Задачи
В этом проекте вы научитесь:
- Создавать файлы проекта
- Определять константы для игры
- Реализовывать функцию
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 на языке C. Вы инициализировали игровое поле, реализовали функции для проверки состояния игры, создали логику для перемещения плиток, отобразили игровое поле и запустили игру. Наслаждайтесь игрой и дальнейшим улучшением своей игры 2048!



