Maze generator game in C

Writing an interactive Maze generator is a task that helps to illustrate various computer science concepts in a simple way. This is a C implementation based of one school project I did a while ago.

Introduction

During my school subject of Operating Systems we had to write a simple program demonstrating the usage for the ncurses library. I choose to write a Maze generator and it turned out to be a really great challenge and fun project.

Maze generation is a really interesting area of application of diverse algorithms and user interaction techniques for it to become a game. I will try to share with you how to write a maze game using the C language and the ncurses library.

The game should generate a random maze and allow the player to navigate through it using the arrow keys. The goal is for the user to reach the finish point. Let’s dive into the code!

Libraries and Constants

The very first thing we need to do is to include the ncessary libraries and define the constatns required for the game:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Libraries
#include <stdio.h>      // We need standard I/O!
#include <ncurses.h>    // 🪄 magic for our text UI! 🪄
#include <time.h>       // We need to have a random(ish) seed for our map generation!
#include <stdlib.h>     // We use `malloc()` and such.

// Constants
#define WALL_CHAR         '#' // The character to show as a wall.
#define EMPTY_CHAR        ' ' // The character to show as an empty space.
#define PLAYER_CHAR       '@' // The character to show a player.
#define GOAL_CHAR         'G' // The character to show at the finish point!
#define DIRECTION_UP      0   // The int ID for up direction.
#define DIRECTION_LEFT    1   // The int ID for left direction.
#define DIRECTION_RIGHT   2   // The int ID for right direction.
#define DIRECTION_DOWN    3   // The int ID for down direction.
#define EXIT_OPTION_LOWER 'q' // Lowercase letter for exiting the game.
#define EXIT_OPTION_UPPER 'Q' // Uppercase letter for quitting the game.

In the libraries section, we include ncurses.h in order to use the ncurses library and manipulate more efficiently our terminal interface. We also have time.h over there because we’ll have to initilize the random seed for our map using functions from that library.

The constants defined are for redifining the characters we use to display the map, the direction ID’s and the letter that quits the game.

Enumerations and Structures

We define an enumeration CellContent for the cell’s content and a structure Cell to represent the maze cells:

1
2
3
4
5
6
7
8
9
10
11
12
enum CellContent {
    Wall,
    Empty,
    Player,
    Goal
};

typedef struct Cell{
    int row,    // Y coordinate.
        column; // X coordinate.
    enum CellContent content;
} Cell;

Each cell has to be in one of the following states: Wall, Empty, Player or Goal. We are going to be working with cartesian coordinates for the map so we store the row and column for each Cell.

Function Prototypes

By declaring our prototypes at the top of the file we can use the functions in our main() code without having to implement the function there (it doesn’t even need to be in the same file!).

With just the signatures we’re able to grasp what the print_map, depth_first_search and get_available_directions do:

1
2
3
void print_map(WINDOW *maze_window, int rows, int cols, Cell **map);
void depth_first_search(int current_row, int current_col, int rows, int cols, Cell **map);
void get_available_directions(int current_row, int current_col, int rows, int cols, Cell **map, int available_directions[4]);

However, if you’d like, you can jump into the implementation section where you can find the details of what (and why) each function does what it does.

Main Function

The main function does a bunch of stuff:

Many other things happen under the hood, but they’re mostly ramifications of those ones. Take a look:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
int main()
{
    WINDOW *maze_window; // ncurses Window.
    int rows,             // The amount of rows in the screen.
        cols,             // The amount of columns in the screen.
        current_row,      // The current row for iterations.
        current_col,      // The current column for iterations.
        maze_window_rows, // The amount of rows in the maze window.
        maze_window_cols; // The amount of columns in the maze window.
    int player_row = 1,   // The  current row of the player.
        player_col = 1,   // The current column of the player.
        goal_row,         // The row for the goal.
        goal_col,         // The column for the goal.
        key_pressed;      // The ID of the key pressed by the user.
    Cell **map;           // The map is a matrix of Cells.

    // Initialize the random seed.
    srand(time(NULL));

    // Initialize the screen.
    initscr();

    // Get the maximum amount of rows and columns for the current screen.
    getmaxyx(stdscr, rows, cols);

    // Calculate the amount of rows and columns available (margin).
    maze_window_rows = rows - 2;
    maze_window_cols = cols - 2;

    // Allocate memory for the array of pointers (arrays).
    map = malloc(maze_window_rows * sizeof(*map));

    // Allocate memory for each row.
    for (current_row = 0; current_row < maze_window_rows; current_row++)
    {
        map[current_row] = malloc(maze_window_cols * sizeof *map[current_row]);
    }

    // Clear the screen.
    clear();

    // Deactivate the "echo" function of the prompt.
    noecho();

    // Eliminate the breaks.
    cbreak();

    // Create the window.
    maze_window = newwin(maze_window_rows, maze_window_cols, 2, 1);

    // Associate the keypad to the window.
    keypad(maze_window, TRUE);

    // Print simple information.
    mvprintw(0, 0, "Dynamic Maze using ncurses.h - ESCOM Operating Systems");
    mvprintw(1, 0, "Use the arrow keys to move through the window, reach the goal '%c' to win or press '%c' to exit.", GOAL_CHAR, EXIT_OPTION_LOWER);

    // Refresh the screen to show the changes.
    refresh();

    // We randomly generate the maze by using a custom DFS function which will
    // go through the map and _carve_ its path through it.
    depth_first_search(1, 1, maze_window_rows, maze_window_cols, map);

    // Set the player in the map.
    map[player_row][player_col].content = Player;

    // Set the goal location in the map, the condition is to deal with odd sizes.
    if (map[maze_window_rows - 1][maze_window_cols - 1].content == Empty)
    {
        goal_row = maze_window_rows - 1;
        goal_col = maze_window_cols - 1;
    }
    else
    {
        goal_row = maze_window_rows - 2;
        goal_col = maze_window_cols - 2;
    }
    map[goal_row][goal_col].content = Goal;

    // Print the initial map.
    print_map(maze_window, maze_window_rows, maze_window_cols, map);

    // While the player doesn't reach the goal...
    while (player_row != goal_row || player_col != goal_col)
    {
        // Get the key pressed by the user.
        key_pressed = wgetch(maze_window);

        // Erase the user from it's current position.
        map[player_row][player_col].content = Empty;

        // Act based on the key entered.
        switch (key_pressed)
        {
        case KEY_UP:
            if (player_row > 1 && map[player_row - 1][player_col].content != Wall)
            {
                player_row -= 1;
            }
            break;

        case KEY_DOWN:
            if (player_row < (maze_window_rows - 1) && map[player_row + 1][player_col].content != Wall)
            {
                player_row += 1;
            }
            break;

        case KEY_LEFT:
            if (player_col > 1 && map[player_row][player_col - 1].content != Wall)
            {
                player_col -= 1;
            }
            break;

        case KEY_RIGHT:
            if (player_col < (maze_window_cols - 1) && map[player_row][player_col + 1].content != Wall)
            {
                player_col += 1;
            }
            break;

        case EXIT_OPTION_UPPER:
        case EXIT_OPTION_LOWER:
            // Set the coordinates to win to break the loop.
            player_col = goal_col;
            player_row = goal_row;
            break;
        }

        // Set the position of the player in the map.
        map[player_row][player_col].content = Player;

        // Print the map.
        print_map(maze_window, maze_window_rows, maze_window_cols, map);
    }

    // If the user won or lost, end the window.
    endwin();

    // Free the memory allocated to each row within the map.
    for (current_row = 0; current_row < maze_window_rows; current_row++)
    {
        free(map[current_row]);
    }

    // Now free the map itself.
    free(map);

    // Print a simple message.
    printf("Congratulations! You reached the goal!\n");

    // End the execution.
    return 0;
}

Helper Functions

Remember the functions’ prototypes we declared earlier? It’s time to look at the implementation. Each function is written with simplicity in mind, however you may have to look into the main function section to trace where is everything getting called.

Printing the map

The print_map function does what it’s name says, maybe the only peculiarities regarding this implementation is that we need to map our current map into the size of the maze_window, other than that is read-and-display.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void print_map(WINDOW *maze_window, int rows, int cols, Cell **map)
{
    int row, col;

    // We iterate through each row.
    for (row = 0; row < rows; row++)
    {
        // We iterate throgh each column in each row.
        for (col = 0; col < cols; col++)
        {
            // Switch the content of the Cell.
            switch (map[row][col].content)
            {
            case Wall:
                mvwaddch(maze_window, row, col, WALL_CHAR);
                break;

            case Empty:
                mvwaddch(maze_window, row, col, EMPTY_CHAR);
                break;

            case Player:
                mvwaddch(maze_window, row, col, PLAYER_CHAR);
                break;

            case Goal:
                mvwaddch(maze_window, row, col, GOAL_CHAR);
                break;

            default:
                mvwaddch(maze_window, row, col, '?');
            }
        }
    }

    // We have to refresh the window to show our changes in the screen.
    wrefresh(maze_window);
}

Depth-First Search (DFS)

We use a modified version of the well-known Depth-First Search algorithm (known also as DFS) to get a random maze generated when the game starts. If you see our main program we initialize the map by filling it in with the Wall enumeration value. Our DFS will try to carve into the map the valid pathways for the player to navigate. Let’s see the definition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void depth_first_search(int current_row, int current_col, int rows, int cols, Cell **map)
{
    int available_directions[4] = {0, 0, 0, 0}, // Array for storing the available directions for each cell.
        direction; // The selected direction.

    do
    {
        // Set the current location as Empty.
        map[current_row][current_col].content = Empty;

        // Get the UPDATED and available directions for the current Cell.
        get_available_directions(current_row, current_col, rows, cols, map, available_directions);

        // Check for the base case for our recursion: no available directions.
        if (!available_directions[DIRECTION_UP] && !available_directions[DIRECTION_DOWN] && !available_directions[DIRECTION_LEFT] && !available_directions[DIRECTION_RIGHT]);
        {
            // Bye bye!
            return;
        }

        // Get a random, avilable, direction.
        do
        {
            // Random value between 0 and 4 (0-3).
            direction = rand() % 4;
        } while(availble_directions[direction] == 0); // While the random direction is a not available direction.

        // We move to the next Cell based on the direction.
        switch(direction)
        {
        case DIRECTION_UP:
            // Carve the wall with a space to walk through.
            map[current_row - 1][current_col].content = Empty;

            // Move recursively to the next Cell.
            depth_first_search(current_row - 2, current_col, rows, cols, map);
            break;
        case DIRECTION_LEFT:
            // Carve the wall with a space to walk through.
            map[current_row][current_col - 1].content = Empty;

            // Move recursively to the next Cell.
            depth_first_search(current_row, current_col - 2, rows, cols, map);
            break;
        case DIRECTION_RIGHT:
            // Carve the wall with a space to walk through.
            map[current_row][current_col + 1].content = Empty;

            // Move recursively to the next Cell.
            depth_first_search(current_row, current_col + 2, rows, cols, map);
            break;
        case DIRECTION_DOWN:
            // Carve the wall with a space to walk through.
            map[current_row + 1][current_col].content = Empty;

            // Move recursively to the next Cell.
            depth_first_search(current_row + 2, current_col, rows, cols, map);
            break;
        }
    } while (available_directions[DIRECTION_UP] || available_directions[DIRECTION_DOWN] || available_directions[DIRECTION_LEFT] || available_directions[DIRECTION_RIGHT])
}

So what we are doing here is:

  1. Set the current position as empty.
  2. Get the available_directions for our current Cell.
  3. Check for the base case of recursion: no directions available.
  4. Select pseudo-randomly1 a direction.
  5. Carve an Empty space between our current Cell and our destination.
  6. Repeat from 1 until there are no available directions.

Get available directions

Each Cell can have four available directions (ideally): DIRECTION_UP, DIRECTION_DOWN, DIRECTION_RIGHT and DIRECTION_LEFT. A direction is available if, 2 blocks from our Cell, in the direction we’re testing, the Cell is a Wall.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Get the available directions for a cell.
void get_available_directions(int current_row, int current_col, int rows, int cols, Cell **map, int available_directions[4])
{
    // DIRECTION_UP
    if ((current_row - 2) >= 1 && map[current_row - 2][current_col].content == Wall)
    {
        available_directions[DIRECTION_UP] = 1;
    }
    else
    {
        available_directions[DIRECTION_UP] = 0;
    }

    // DIRECTION_DOWN
    if ((current_row + 2) <= (rows - 2) && map[current_row + 2][current_col].content == Wall)
    {
        available_directions[DIRECTION_DOWN] = 1;
    }
    else
    {
        available_directions[DIRECTION_DOWN] = 0;
    }

    // DIRECTION_RIGHT
    if ((current_col + 2) <= (cols - 2) && map[current_row][current_col + 2].content == Wall)
    {
        available_directions[DIRECTION_RIGHT] = 1;
    }
    else
    {
        available_directions[DIRECTION_RIGHT] = 0;
    }

    // DIRECTION_LEFT
    if ((current_col - 2) >= 1 && map[current_row][current_col - 2].content == Wall)
    {
        available_directions[DIRECTION_LEFT] = 1;
    }
    else
    {
        available_directions[DIRECTION_LEFT] = 0;
    }
}

Compilation

In order to compile this program we need to have the ncurses library installed, for this you can follow the online instructions for your specific operating system.

If you’re on macOS and using Homebrew, you just need to run the following command:brew install libncurses

Once instaled the compilation flag required for linking our project is: -lncurses.

The final command looks like this:

1
$ gcc -o MazeGame.out -lncurses MazeGame.c

Execution

It’s time for the show time! Here are some screen captures of the mazes generated by the game:

Maze generator: 1

Maze generator: 2

Maze generator: 3

And now here’s a GIF of me winning a game:

Maze generator: GIF of last steps to win a game

Final Code

The final code for this simple project is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// Libraries
#include <stdio.h>      // We need standard I/O!
#include <ncurses.h>    // 🪄 magic for our text UI! 🪄
#include <time.h>       // We need to have a random(ish) seed for our map generation!
#include <stdlib.h>     // We use `malloc()` and such.

// Constants
#define WALL_CHAR         '#' // The character to show as a wall.
#define EMPTY_CHAR        ' ' // The character to show as an empty space.
#define PLAYER_CHAR       '@' // The character to show a player.
#define GOAL_CHAR         'G' // The character to show at the finish point!
#define DIRECTION_UP      0   // The int ID for up direction.
#define DIRECTION_LEFT    1   // The int ID for left direction.
#define DIRECTION_RIGHT   2   // The int ID for right direction.
#define DIRECTION_DOWN    3   // The int ID for down direction.
#define EXIT_OPTION_LOWER 'q' // Lowercase letter for exiting the game.
#define EXIT_OPTION_UPPER 'Q' // Uppercase letter for quitting the game.

// Enumerations and Structures.
enum CellContent {
    Wall,
    Empty,
    Player,
    Goal
};

typedef struct Cell{
    int row,    // Y coordinate.
        column; // X coordinate.
    enum CellContent content;
} Cell;

// Function prototypes.
void print_map(WINDOW *maze_window, int rows, int cols, Cell **map);
void depth_first_search(int current_row, int current_col, int rows, int cols, Cell **map);
void get_available_directions(int current_row, int current_col, int rows, int cols, Cell **map, int available_directions[4]);

int main()
{
    WINDOW *maze_window; // ncurses Window.
    int rows,             // The amount of rows in the screen.
        cols,             // The amount of columns in the screen.
        current_row,      // The current row for iterations.
        current_col,      // The current column for iterations.
        maze_window_rows, // The amount of rows in the maze window.
        maze_window_cols; // The amount of columns in the maze window.
    int player_row = 1,   // The  current row of the player.
        player_col = 1,   // The current column of the player.
        goal_row,         // The row for the goal.
        goal_col,         // The column for the goal.
        key_pressed;      // The ID of the key pressed by the user.
    Cell **map;           // The map is a matrix of Cells.

    // Initialize the random seed.
    srand(time(NULL));

    // Initialize the screen.
    initscr();

    // Get the maximum amount of rows and columns for the current screen.
    getmaxyx(stdscr, rows, cols);

    // Calculate the amount of rows and columns available (margin).
    maze_window_rows = rows - 2;
    maze_window_cols = cols - 2;

    // Allocate memory for the array of pointers (arrays).
    map = malloc(maze_window_rows * sizeof(*map));

    // Allocate memory for each row.
    for (current_row = 0; current_row < maze_window_rows; current_row++)
    {
        map[current_row] = malloc(maze_window_cols * sizeof *map[current_row]);
    }

    // Clear the screen.
    clear();

    // Deactivate the "echo" function of the prompt.
    noecho();

    // Eliminate the breaks.
    cbreak();

    // Create the window.
    maze_window = newwin(maze_window_rows, maze_window_cols, 2, 1);

    // Associate the keypad to the window.
    keypad(maze_window, TRUE);

    // Print simple information.
    mvprintw(0, 0, "Dynamic Maze using ncurses.h - ESCOM Operating Systems");
    mvprintw(1, 0, "Use the arrow keys to move through the window, reach the goal '%c' to win or press '%c' to exit.", GOAL_CHAR, EXIT_OPTION_LOWER);

    // Refresh the screen to show the changes.
    refresh();

    // We randomly generate the maze by using a custom DFS function which will
    // go through the map and _carve_ its path through it.
    depth_first_search(1, 1, maze_window_rows, maze_window_cols, map);

    // Set the player in the map.
    map[player_row][player_col].content = Player;

    // Set the goal location in the map, the condition is to deal with odd sizes.
    if (map[maze_window_rows - 1][maze_window_cols - 1].content == Empty)
    {
        goal_row = maze_window_rows - 1;
        goal_col = maze_window_cols - 1;
    }
    else
    {
        goal_row = maze_window_rows - 2;
        goal_col = maze_window_cols - 2;
    }
    map[goal_row][goal_col].content = Goal;

    // Print the initial map.
    print_map(maze_window, maze_window_rows, maze_window_cols, map);

    // While the player doesn't reach the goal...
    while (player_row != goal_row || player_col != goal_col)
    {
        // Get the key pressed by the user.
        key_pressed = wgetch(maze_window);

        // Erase the user from it's current position.
        map[player_row][player_col].content = Empty;

        // Act based on the key entered.
        switch (key_pressed)
        {
        case KEY_UP:
            if (player_row > 1 && map[player_row - 1][player_col].content != Wall)
            {
                player_row -= 1;
            }
            break;

        case KEY_DOWN:
            if (player_row < (maze_window_rows - 1) && map[player_row + 1][player_col].content != Wall)
            {
                player_row += 1;
            }
            break;

        case KEY_LEFT:
            if (player_col > 1 && map[player_row][player_col - 1].content != Wall)
            {
                player_col -= 1;
            }
            break;

        case KEY_RIGHT:
            if (player_col < (maze_window_cols - 1) && map[player_row][player_col + 1].content != Wall)
            {
                player_col += 1;
            }
            break;

        case EXIT_OPTION_UPPER:
        case EXIT_OPTION_LOWER:
            // Set the coordinates to win to break the loop.
            player_col = goal_col;
            player_row = goal_row;
            break;
        }

        // Set the position of the player in the map.
        map[player_row][player_col].content = Player;

        // Print the map.
        print_map(maze_window, maze_window_rows, maze_window_cols, map);
    }

    // If the user won or lost, end the window.
    endwin();

    // Free the memory allocated to each row within the map.
    for (current_row = 0; current_row < maze_window_rows; current_row++)
    {
        free(map[current_row]);
    }

    // Now free the map itself.
    free(map);

    // Print a simple message.
    printf("Congratulations! You reached the goal!\n");

    // End the execution.
    return 0;
}

// Helper functions.

// Print the map.
void print_map(WINDOW *maze_window, int rows, int cols, Cell **map)
{
    int row, col;

    // We iterate through each row.
    for (row = 0; row < rows; row++)
    {
        // We iterate throgh each column in each row.
        for (col = 0; col < cols; col++)
        {
            // Switch the content of the Cell.
            switch (map[row][col].content)
            {
            case Wall:
                mvwaddch(maze_window, row, col, WALL_CHAR);
                break;

            case Empty:
                mvwaddch(maze_window, row, col, EMPTY_CHAR);
                break;

            case Player:
                mvwaddch(maze_window, row, col, PLAYER_CHAR);
                break;

            case Goal:
                mvwaddch(maze_window, row, col, GOAL_CHAR);
                break;

            default:
                mvwaddch(maze_window, row, col, '?');
            }
        }
    }

    // We have to refresh the window to show our changes in the screen.
    wrefresh(maze_window);
}

// Depth-First Search to randomly generate the Maze map.
void depth_first_search(int current_row, int current_col, int rows, int cols, Cell **map)
{
    int available_directions[4] = {0, 0, 0, 0}, // Array for storing the available directions for each cell.
        direction; // The selected direction.

    do
    {
        // Set the current location as Empty.
        map[current_row][current_col].content = Empty;

        // Get the UPDATED and available directions for the current Cell.
        get_available_directions(current_row, current_col, rows, cols, map, available_directions);

        // Check for the base case for our recursion: no available directions.
        if (!available_directions[DIRECTION_UP] && !available_directions[DIRECTION_DOWN] && !available_directions[DIRECTION_LEFT] && !available_directions[DIRECTION_RIGHT]);
        {
            // Bye bye!
            return;
        }

        // Get a random, avilable, direction.
        do
        {
            // Random value between 0 and 4 (0-3).
            direction = rand() % 4;
        } while(availble_directions[direction] == 0); // While the random direction is a not available direction.

        // We move to the next Cell based on the direction.
        switch(direction)
        {
        case DIRECTION_UP:
            // Carve the wall with a space to walk through.
            map[current_row - 1][current_col].content = Empty;

            // Move recursively to the next Cell.
            depth_first_search(current_row - 2, current_col, rows, cols, map);
            break;
        case DIRECTION_LEFT:
            // Carve the wall with a space to walk through.
            map[current_row][current_col - 1].content = Empty;

            // Move recursively to the next Cell.
            depth_first_search(current_row, current_col - 2, rows, cols, map);
            break;
        case DIRECTION_RIGHT:
            // Carve the wall with a space to walk through.
            map[current_row][current_col + 1].content = Empty;

            // Move recursively to the next Cell.
            depth_first_search(current_row, current_col + 2, rows, cols, map);
            break;
        case DIRECTION_DOWN:
            // Carve the wall with a space to walk through.
            map[current_row + 1][current_col].content = Empty;

            // Move recursively to the next Cell.
            depth_first_search(current_row + 2, current_col, rows, cols, map);
            break;
        }
    } while (available_directions[DIRECTION_UP] || available_directions[DIRECTION_DOWN] || available_directions[DIRECTION_LEFT] || available_directions[DIRECTION_RIGHT])
}

// Get the available directions for a cell.
void get_available_directions(int current_row, int current_col, int rows, int cols, Cell **map, int available_directions[4])
{
    // DIRECTION_UP
    if ((current_row - 2) >= 1 && map[current_row - 2][current_col].content == Wall)
    {
        available_directions[DIRECTION_UP] = 1;
    }
    else
    {
        available_directions[DIRECTION_UP] = 0;
    }

    // DIRECTION_DOWN
    if ((current_row + 2) <= (rows - 2) && map[current_row + 2][current_col].content == Wall)
    {
        available_directions[DIRECTION_DOWN] = 1;
    }
    else
    {
        available_directions[DIRECTION_DOWN] = 0;
    }

    // DIRECTION_RIGHT
    if ((current_col + 2) <= (cols - 2) && map[current_row][current_col + 2].content == Wall)
    {
        available_directions[DIRECTION_RIGHT] = 1;
    }
    else
    {
        available_directions[DIRECTION_RIGHT] = 0;
    }

    // DIRECTION_LEFT
    if ((current_col - 2) >= 1 && map[current_row][current_col - 2].content == Wall)
    {
        available_directions[DIRECTION_LEFT] = 1;
    }
    else
    {
        available_directions[DIRECTION_LEFT] = 0;
    }
}

If you’re interested in reading my class project from which all of this is based on, you can visit the GitHub Repository for it:

Thank you for reading!

  1. As you can imagine, generating real random numbers is a bit of a hassle in computer science. We must assume the output of rand() to be a pseudo-random value. If you have more questions about it you can read it’s documentation at: https://en.cppreference.com/w/c/numeric/random/rand