From eb8834324e1e67481ecbe00fe212764dec64a71e Mon Sep 17 00:00:00 2001 From: Piotr Kozak Date: Fri, 6 Mar 2026 15:24:33 +0100 Subject: [PATCH] Split the game core and ui code into their respective modules Game state struct ends the variable mess in main.c --- Makefile | 2 +- game.c | 199 +++++++++++++++++++++++++++++++++++++++++ game.h | 19 ++++ main.c | 266 +++---------------------------------------------------- ui.c | 47 ++++++++++ ui.h | 9 ++ 6 files changed, 287 insertions(+), 255 deletions(-) create mode 100644 game.c create mode 100644 game.h create mode 100644 ui.c create mode 100644 ui.h diff --git a/Makefile b/Makefile index b49125b..8b68f6a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CFLAGS = -Wall -Wextra -std=c11 LIBS = -lraylib -lm -lpthread -ldl TARGET = main -SRC = main.c +SRC = main.c game.c ui.c all: $(TARGET) diff --git a/game.c b/game.c new file mode 100644 index 0000000..a7bc070 --- /dev/null +++ b/game.c @@ -0,0 +1,199 @@ +#include "game.h" +#include + +// Depending on the direction, move the tiles and check if we can merge with the +// tiles in that direction. We merge the tiles only if they are of the same value +void move(GameState_t *state, MOVE_DIRECTION direction) { + bool merged[BOARD_HEIGHT][BOARD_WIDTH] = {0}; + switch (direction) { + case UP: { + for (int y = 1; y < BOARD_HEIGHT; y++) { + for (int x = 0; x < BOARD_WIDTH; x++) { + if (state->board[y][x] == 0) + continue; + + // Hard keeping track of this so I'm commenting as much as I can + // Check if we can move up any space, so loop until we find a non-empty tile or the top of the board + + // py is the potential target y that we will settle for and move the tile into + int target_y = y; + for (int py = y - 1; py >= 0; py--) { // Go from the tile's current position up to the top of the board + if (state->board[py][x] == 0) { + target_y = py; + } else if (state->board[py][x] == state->board[y][x] && !merged[py][x]) { + target_y = py; + break; + } else { + break; + } + } + + // Check if we are moving anything + if (target_y == y) + continue; + + // If the target tile is empty, move the current tile there + // If the target tile has the same value, merge them + if (state->board[target_y][x] == 0) { + state->board[target_y][x] = state->board[y][x]; + state->board[y][x] = 0; + } else if (state->board[target_y][x] == state->board[y][x]) { + merged[target_y][x] = true; + state->board[target_y][x] *= 2; + state->board[y][x] = 0; + state->score += state->board[target_y][x]; + } + } + } + break; + } + case DOWN: { + for (int y = BOARD_HEIGHT - 1; y >= 0; y--) { + for (int x = 0; x < BOARD_WIDTH; x++) { + if (state->board[y][x] == 0) + continue; + + int target_y = y; + for (int py = y + 1; py < BOARD_HEIGHT; py++) { + if (state->board[py][x] == 0) { + target_y = py; + } else if (state->board[py][x] == state->board[y][x] && !merged[py][x]) { + target_y = py; + break; + } else { + break; + } + } + + if (target_y == y) + continue; + + if (state->board[target_y][x] == 0) { + state->board[target_y][x] = state->board[y][x]; + state->board[y][x] = 0; + } else if (state->board[target_y][x] == state->board[y][x]) { + merged[target_y][x] = true; + state->board[target_y][x] *= 2; + state->board[y][x] = 0; + state->score += state->board[target_y][x]; + } + } + } + break; + } + case LEFT: { + for (int y = 0; y < BOARD_HEIGHT; y++) { + for (int x = 1; x < BOARD_WIDTH; x++) { + if (state->board[y][x] == 0) + continue; + + int target_x = x; + for (int px = x - 1; px >= 0; px--) { + if (state->board[y][px] == 0) { + target_x = px; + } else if (state->board[y][px] == state->board[y][x] && !merged[y][px]) { + target_x = px; + break; + } else { + break; + } + } + + if (target_x == x) + continue; + + if (state->board[y][target_x] == 0) { + state->board[y][target_x] = state->board[y][x]; + state->board[y][x] = 0; + } else if (state->board[y][target_x] == state->board[y][x]) { + merged[y][target_x] = true; + state->board[y][target_x] *= 2; + state->board[y][x] = 0; + state->score += state->board[y][target_x]; + } + } + } + break; + } + case RIGHT: { + for (int y = 0; y < BOARD_HEIGHT; y++) { + for (int x = BOARD_WIDTH - 1; x >= 0; x--) { + if (state->board[y][x] == 0) + continue; + + int target_x = x; + for (int px = x + 1; px < BOARD_WIDTH; px++) { + if (state->board[y][px] == 0) { + target_x = px; + } else if (state->board[y][px] == state->board[y][x] && !merged[y][px]) { + target_x = px; + break; + } else { + break; + } + } + + if (target_x == x) + continue; + + if (state->board[y][target_x] == 0) { + state->board[y][target_x] = state->board[y][x]; + state->board[y][x] = 0; + } else if (state->board[y][target_x] == state->board[y][x]) { + merged[y][target_x] = true; + state->board[y][target_x] *= 2; + state->board[y][x] = 0; + state->score += state->board[y][target_x]; + } + } + } + break; + } + } + state->moves_made++; +} + +void reset_board(GameState_t *state) { + for (int y = 0; y < BOARD_HEIGHT; y++) { + for (int x = 0; x < BOARD_WIDTH; x++) { + state->board[y][x] = 0; + } + } + state->score = 0; + state->moves_made = 0; + + int tile_count = 0; + while (tile_count != STARTING_TILES) { + int y = rand() % BOARD_HEIGHT; + int x = rand() % BOARD_WIDTH; + + if (state->board[y][x] != 0) + continue; + state->board[y][x] = (rand() % 10 == 0) ? 4 : 2; // 10% chance to spawn a 4 + tile_count++; + } +} + +bool spawn_tile(GameState_t *state) { + int empty_tiles[BOARD_WIDTH * BOARD_HEIGHT][2]; + int empty_count = 0; + + for (int y = 0; y < BOARD_HEIGHT; y++) { + for (int x = 0; x < BOARD_WIDTH; x++) { + if (state->board[y][x] == 0) { + empty_tiles[empty_count][0] = y; + empty_tiles[empty_count][1] = x; + empty_count++; + } + } + } + + if (empty_count == 0) + return false; + + int random_index = rand() % empty_count; + int spawn_y = empty_tiles[random_index][0]; + int spawn_x = empty_tiles[random_index][1]; + state->board[spawn_y][spawn_x] = (rand() % 10 == 0) ? 4 : 2; // 10% chance to spawn a 4 + return true; +} \ No newline at end of file diff --git a/game.h b/game.h new file mode 100644 index 0000000..ce741ae --- /dev/null +++ b/game.h @@ -0,0 +1,19 @@ +#pragma once +#include + +#define BOARD_WIDTH 4 +#define BOARD_HEIGHT 4 +#define STARTING_TILES 3 + +typedef enum { UP = 0, DOWN, LEFT, RIGHT } MOVE_DIRECTION; + +typedef struct{ + int board[BOARD_HEIGHT][BOARD_WIDTH]; + int score; + int moves_made; +} GameState_t; + + +void move(GameState_t *state, MOVE_DIRECTION direction); +void reset_board(GameState_t *state); +bool spawn_tile(GameState_t *state); diff --git a/main.c b/main.c index a5f688f..620b1a8 100644 --- a/main.c +++ b/main.c @@ -4,13 +4,8 @@ #include #include -#define BOARD_WIDTH 4 -#define BOARD_HEIGHT 4 -#define STARTING_TILES 3 - -#define TILE_SIZE 100 -#define TILE_SPACING 10 -#define BOARD_MARGIN 50 +#include "game.h" +#include "ui.h" const Color tile_colors[] = { LIGHTGRAY, // 0 - Empty @@ -29,253 +24,12 @@ const Color tile_colors[] = { MAGENTA, // 8192 }; -typedef enum { UP = 0, DOWN, LEFT, RIGHT } MOVE_DIRECTION; -static int board[BOARD_HEIGHT][BOARD_WIDTH] = {0}; - -bool is_dragging = false; -Vector2 drag_start_pos = {0, 0}; -int score = 0; -int moves_made = 0; - -// Depending on the direction, move the tiles and check if we can merge with the -// tiles in that direction. We merge the tiles only if they are of the same value -void move(MOVE_DIRECTION direction) { - bool merged[BOARD_HEIGHT][BOARD_WIDTH] = {0}; - switch (direction) { - case UP: { - for (int y = 1; y < BOARD_HEIGHT; y++) { - for (int x = 0; x < BOARD_WIDTH; x++) { - if (board[y][x] == 0) - continue; - - // Hard keeping track of this so I'm commenting as much as I can - // Check if we can move up any space, so loop until we find a non-empty tile or the top of the board - - // py is the potential target y that we will settle for and move the tile into - int target_y = y; - for (int py = y - 1; py >= 0; py--) { // Go from the tile's current position up to the top of the board - if (board[py][x] == 0) { - target_y = py; - } else if (board[py][x] == board[y][x] && !merged[py][x]) { - target_y = py; - break; - } else { - break; - } - } - - // Check if we are moving anything - if (target_y == y) - continue; - - // If the target tile is empty, move the current tile there - // If the target tile has the same value, merge them - if (board[target_y][x] == 0) { - board[target_y][x] = board[y][x]; - board[y][x] = 0; - } else if (board[target_y][x] == board[y][x]) { - merged[target_y][x] = true; - board[target_y][x] *= 2; - board[y][x] = 0; - score += board[target_y][x]; - } - } - } - break; - } - case DOWN: { - for (int y = BOARD_HEIGHT - 1; y >= 0; y--) { - for (int x = 0; x < BOARD_WIDTH; x++) { - if (board[y][x] == 0) - continue; - - int target_y = y; - for (int py = y + 1; py < BOARD_HEIGHT; py++) { - if (board[py][x] == 0) { - target_y = py; - } else if (board[py][x] == board[y][x] && !merged[py][x]) { - target_y = py; - break; - } else { - break; - } - } - - if (target_y == y) - continue; - - if (board[target_y][x] == 0) { - board[target_y][x] = board[y][x]; - board[y][x] = 0; - } else if (board[target_y][x] == board[y][x]) { - merged[target_y][x] = true; - board[target_y][x] *= 2; - board[y][x] = 0; - score += board[target_y][x]; - } - } - } - break; - } - case LEFT: { - for (int y = 0; y < BOARD_HEIGHT; y++) { - for (int x = 1; x < BOARD_WIDTH; x++) { - if (board[y][x] == 0) - continue; - - int target_x = x; - for (int px = x - 1; px >= 0; px--) { - if (board[y][px] == 0) { - target_x = px; - } else if (board[y][px] == board[y][x] && !merged[y][px]) { - target_x = px; - break; - } else { - break; - } - } - - if (target_x == x) - continue; - - if (board[y][target_x] == 0) { - board[y][target_x] = board[y][x]; - board[y][x] = 0; - } else if (board[y][target_x] == board[y][x]) { - merged[y][target_x] = true; - board[y][target_x] *= 2; - board[y][x] = 0; - score += board[y][target_x]; - } - } - } - break; - } - case RIGHT: { - for (int y = 0; y < BOARD_HEIGHT; y++) { - for (int x = BOARD_WIDTH - 1; x >= 0; x--) { - if (board[y][x] == 0) - continue; - - int target_x = x; - for (int px = x + 1; px < BOARD_WIDTH; px++) { - if (board[y][px] == 0) { - target_x = px; - } else if (board[y][px] == board[y][x] && !merged[y][px]) { - target_x = px; - break; - } else { - break; - } - } - - if (target_x == x) - continue; - - if (board[y][target_x] == 0) { - board[y][target_x] = board[y][x]; - board[y][x] = 0; - } else if (board[y][target_x] == board[y][x]) { - merged[y][target_x] = true; - board[y][target_x] *= 2; - board[y][x] = 0; - score += board[y][target_x]; - } - } - } - break; - } - } - moves_made++; -} - -bool spawn_tile() { - int empty_tiles[BOARD_WIDTH * BOARD_HEIGHT][2]; - int empty_count = 0; - - for (int y = 0; y < BOARD_HEIGHT; y++) { - for (int x = 0; x < BOARD_WIDTH; x++) { - if (board[y][x] == 0) { - empty_tiles[empty_count][0] = y; - empty_tiles[empty_count][1] = x; - empty_count++; - } - } - } - - if (empty_count == 0) - return false; - - int random_index = rand() % empty_count; - int spawn_y = empty_tiles[random_index][0]; - int spawn_x = empty_tiles[random_index][1]; - board[spawn_y][spawn_x] = (rand() % 10 == 0) ? 4 : 2; // 10% chance to spawn a 4 - - return true; -} - -void handle_mouse_input() { - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { - drag_start_pos = GetMousePosition(); - if (drag_start_pos.x >= 500 && drag_start_pos.x <= 775 && drag_start_pos.y >= 455 && drag_start_pos.y <= 479) { - // Reset button - for (int y = 0; y < BOARD_HEIGHT; y++) { - for (int x = 0; x < BOARD_WIDTH; x++) { - board[y][x] = 0; - } - } - score = 0; - moves_made = 0; - spawn_tile(); - spawn_tile(); - spawn_tile(); - return; - } - - is_dragging = true; - } else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON) && is_dragging) { - Vector2 current_pos = GetMousePosition(); - - float dx = current_pos.x - drag_start_pos.x; - float dy = current_pos.y - drag_start_pos.y; - - float drag_distance = sqrt(dx * dx + dy * dy); - - // We don't want to register single clicks as a move - if (drag_distance >= 30) { - if (fabs(dx) > fabs(dy)) { - if (dx > 0) { - move(RIGHT); - } else { - move(LEFT); - } - } else { - if (dy > 0) { - move(DOWN); - } else { - move(UP); - } - } - spawn_tile(); - } - - is_dragging = false; - } -} +static GameState_t game_state; int main() { srand(time(NULL)); - int tile_count = 0; - while (tile_count != STARTING_TILES) { - int y = rand() % BOARD_HEIGHT; - int x = rand() % BOARD_WIDTH; - - if (board[y][x] != 0) - continue; - board[y][x] = (rand() % 10 == 0) ? 4 : 2; // 10% chance to spawn a 4 - tile_count++; - } + reset_board(&game_state); InitWindow(800, 600, "2048"); SetTargetFPS(30); // So my laptop doesn't burn @@ -286,11 +40,15 @@ int main() { for (int y = 0; y < BOARD_HEIGHT; y++) { for (int x = 0; x < BOARD_WIDTH; x++) { - int tile_value = board[y][x]; + int tile_value = game_state.board[y][x]; int pos_x = BOARD_MARGIN + x * (TILE_SIZE + TILE_SPACING); int pos_y = BOARD_MARGIN + y * (TILE_SIZE + TILE_SPACING); int color_index = (tile_value == 0) ? 0 : (int)log2(tile_value); + if (color_index >= sizeof(tile_colors) / sizeof(tile_colors[0])) { + color_index = sizeof(tile_colors) / sizeof(tile_colors[0]) - 1; // Cap to the last color + } + DrawRectangle(pos_x, pos_y, TILE_SIZE, TILE_SIZE, tile_colors[color_index]); if (tile_value != 0) { const char *text = TextFormat("%d", tile_value); @@ -299,15 +57,15 @@ int main() { } } - DrawText(TextFormat("Score: %d", score), 600, BOARD_MARGIN, 20, BLACK); - DrawText(TextFormat("Moves used: %d", moves_made), 600, BOARD_MARGIN + 20, 20, BLACK); + DrawText(TextFormat("Score: %d", game_state.score), 600, BOARD_MARGIN, 20, BLACK); + DrawText(TextFormat("Moves used: %d", game_state.moves_made), 600, BOARD_MARGIN + 20, 20, BLACK); // Reset button DrawRectangle(500, 455, 275, 24, RED); DrawText("Reset", 500 + 275 / 2 - MeasureText("Reset", 20) / 2, 455 + 4, 20, WHITE); EndDrawing(); - handle_mouse_input(); + handle_mouse_input(&game_state); } CloseWindow(); return 0; diff --git a/ui.c b/ui.c new file mode 100644 index 0000000..902f236 --- /dev/null +++ b/ui.c @@ -0,0 +1,47 @@ +#include "ui.h" +#include "game.h" +#include +#include +#include + +static bool is_dragging; +static Vector2 drag_start_pos; + +void handle_mouse_input(GameState_t *game_state) { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + drag_start_pos = GetMousePosition(); + if (drag_start_pos.x >= 500 && drag_start_pos.x <= 775 && drag_start_pos.y >= 455 && drag_start_pos.y <= 479) { + reset_board(game_state); + return; + } + + is_dragging = true; + } else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON) && is_dragging) { + Vector2 current_pos = GetMousePosition(); + + float dx = current_pos.x - drag_start_pos.x; + float dy = current_pos.y - drag_start_pos.y; + + float drag_distance = sqrt(dx * dx + dy * dy); + + // We don't want to register single clicks as a move + if (drag_distance >= 30) { + if (fabs(dx) > fabs(dy)) { + if (dx > 0) { + move(game_state, RIGHT); + } else { + move(game_state, LEFT); + } + } else { + if (dy > 0) { + move(game_state, DOWN); + } else { + move(game_state, UP); + } + } + spawn_tile(game_state); + } + + is_dragging = false; + } +} diff --git a/ui.h b/ui.h new file mode 100644 index 0000000..9d68c3a --- /dev/null +++ b/ui.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include "game.h" + +#define TILE_SIZE 100 +#define TILE_SPACING 10 +#define BOARD_MARGIN 50 + +void handle_mouse_input(GameState_t *game_state);