Creating a 2048 Game in C

CCBeginner
Practice Now

Introduction

2048 is a popular number puzzle game where the goal is to reach the 2048 tile by merging adjacent tiles with the same number. In this project, you'll learn how to create a simple 2048 game in C. We'll provide step-by-step instructions to build the game, from initializing the board to implementing game logic and running the game.

👀 Preview

2048 Game

🎯 Tasks

In this project, you will learn:

  • How to create the project files
  • How to define constants for the game
  • How to implement the main() function to run the game loop
  • How to initialize the game board
  • How to implement functions to check game state
  • How to create the logic for moving tiles
  • How to display the game board
  • How to compile and test the game

🏆 Achievements

After completing this project, you will be able to:

  • Create a C program for a game
  • Use arrays to represent the game board
  • Implement game logic for merging tiles
  • Display the game board
  • Handle player input
  • Check for game over and victory conditions

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL c(("`C`")) -.-> c/UserInteractionGroup(["`User Interaction`"]) c(("`C`")) -.-> c/BasicsGroup(["`Basics`"]) c(("`C`")) -.-> c/ControlFlowGroup(["`Control Flow`"]) c(("`C`")) -.-> c/PointersandMemoryGroup(["`Pointers and Memory`"]) c(("`C`")) -.-> c/FunctionsGroup(["`Functions`"]) c/UserInteractionGroup -.-> c/output("`Output`") c/BasicsGroup -.-> c/comments("`Comments`") c/BasicsGroup -.-> c/variables("`Variables`") c/BasicsGroup -.-> c/data_types("`Data Types`") c/BasicsGroup -.-> c/operators("`Operators`") c/ControlFlowGroup -.-> c/if_else("`If...Else`") c/ControlFlowGroup -.-> c/while_loop("`While Loop`") c/ControlFlowGroup -.-> c/for_loop("`For Loop`") c/ControlFlowGroup -.-> c/break_continue("`Break/Continue`") c/UserInteractionGroup -.-> c/user_input("`User Input`") c/PointersandMemoryGroup -.-> c/memory_address("`Memory Address`") c/PointersandMemoryGroup -.-> c/pointers("`Pointers`") c/FunctionsGroup -.-> c/function_parameters("`Function Parameters`") c/FunctionsGroup -.-> c/function_declaration("`Function Declaration`") c/FunctionsGroup -.-> c/recursion("`Recursion`") subgraph Lab Skills c/output -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/comments -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/variables -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/data_types -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/operators -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/if_else -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/while_loop -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/for_loop -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/break_continue -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/user_input -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/memory_address -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/pointers -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/function_parameters -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/function_declaration -.-> lab-298825{{"`Creating a 2048 Game in C`"}} c/recursion -.-> lab-298825{{"`Creating a 2048 Game in C`"}} end

Create the Project Files

First, create a new file named 2048.c and open it in your preferred code editor.

cd ~/project
touch 2048.c

Define Constants

First, we need to write the C code. The first step is to include the header files:

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

Before writing the main() function, let's complete some basic tasks to define some constants:

#define SIZE 4
#define WIN_SCORE 2048

int board[SIZE][SIZE];
int score = 0;

main Function

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(): This function call initializes the game board, sets all cells of the game board to 0, and then generates two initial random numbers (2 or 4) at random locations.
  • print_board(): This function is used to display the current state of the game board, including the current score and the number on each cell.
  • while (1): This is an infinite loop that will keep running the game until the game ends condition is met.

The main flow of 2048 game is implemented here, including initializing the game board, moving the number blocks, judging the game victory or defeat, and waiting for player input to control the progress of the game.

Initialize the Game Board

To initialize the game board, we'll create a function init_board that sets up the board and generates two initial random numbers.

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;
    }
}

What this function does is, at the beginning of the game, empty all the cells on the game board and generate two initial random blocks of numbers in random locations, providing the player with an initial game state.

Implement Functions to Check Game State

We need functions to check if the player has won, if the board is full, and if there are any valid moves left. Here are the functions:

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(): This function is used to check if the board is full, that is, all the cells are occupied.

  • int is_won(): This function checks if the player has won, that is, if there is a cell with the value of WIN_SCORE (usually 2048) for victory in the game.

  • int can_move(): This function is used to check if there are still valid move steps to ensure that the game can continue.

Create the Logic for Moving Tiles

Implement the logic for moving tiles in the move function. This function handles the player's move in four directions: up, down, left, and right. It also checks if the move is valid, updates the score, and generates a new random number.

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;
    }
}
  • First define a variable moved to mark whether a move has occurred. The initial value is 0, indicating that no movement occurred. Create a temporary 2D array called prev_board to store the current state of the game board so that it can be restored to the previous state if the move fails.

  • Copy the current state of the game board to prev_board, saving the current state of the game board in case of a failed move.

  • According to the value of the dir parameter (0 for up, 1 for down, 2 for left, 3 for right), the corresponding move operation is performed.

  • If a move or merge occurs, the moved flag is set to 1, indicating that the game state has changed. If the move or merge operation is performed successfully, a new random number is generated, which is used to generate a new block of numbers in a blank spot on the game board. Finally, if a move occurs (moved is 1 ), the function returns 1, indicating that the move was successful. If the move fails (there is no move or merge operation), the game board is restored to its previous state, returning 0, indicating that the move failed.

Display the Game Board

To display the game board, we'll create a print_board function that clears the terminal and prints the current state of the 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");
    }
}

The main purpose here is to clear the terminal screen, then display the current score at the top of the screen, and then print the status of the game board on the screen so that the player can clearly see the number block on the game board and the score. This helps provide a user-friendly interface that lets players know the current state of the game.

Compile and Test

Enter the following command in the terminal to compile and run:

cd ~/project
gcc -o 2048 2048.c
./2048
2048 Game

Summary

In this project, you've learned how to create a basic 2048 game in C. You initialized the game board, implemented functions to check the game state, created logic for moving tiles, displayed the game board, and ran the game. Have fun playing and enhancing your 2048 game further!

Other C Tutorials you may like