From 98a655d315e10df57f823179f7a86d897e1b8fbb Mon Sep 17 00:00:00 2001 From: bronku Date: Mon, 23 Dec 2024 14:43:48 +0100 Subject: [PATCH] seems to be working --- .gitignore | 5 + CmakeLists.txt | 13 +++ README.md | 9 ++ include/board.h | 36 ++++++++ include/graphics.h | 12 +++ src/board.c | 226 +++++++++++++++++++++++++++++++++++++++++++++ src/graphics.c | 178 +++++++++++++++++++++++++++++++++++ src/main.c | 115 +++++++++++++++++++++++ 8 files changed, 594 insertions(+) create mode 100644 .gitignore create mode 100644 CmakeLists.txt create mode 100644 README.md create mode 100644 include/board.h create mode 100644 include/graphics.h create mode 100644 src/board.c create mode 100644 src/graphics.c create mode 100644 src/main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..534a227 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.cache +build +.zed +.clang-format +.DS_Store diff --git a/CmakeLists.txt b/CmakeLists.txt new file mode 100644 index 0000000..0fbb28c --- /dev/null +++ b/CmakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.10) + +project(minesweeper_sdl) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(EXECUTABLE_NAME bin) + +file(GLOB SOURCES "src/*.c") +add_executable(${EXECUTABLE_NAME} ${SOURCES}) +target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) + +find_package(SDL3 REQUIRED COMPONENTS SDL3) +target_link_libraries(${EXECUTABLE_NAME} SDL3::SDL3) diff --git a/README.md b/README.md new file mode 100644 index 0000000..546edcb --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +A simple minesweeper game I made, 'cause train rides to school are boring. + +Requires cmake and SDL3 to compile. + +Compile, and run with: + +```bash + cmake -B build/ && cmake --build build/ && ./build/bin +``` diff --git a/include/board.h b/include/board.h new file mode 100644 index 0000000..5769112 --- /dev/null +++ b/include/board.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +typedef struct { + int x; + int y; +} Vector2; +Vector2 random_vector2(Vector2 bounds); +// returns size of the output, doesn't check if the output will fit +int get_neighbours(Vector2 input, Vector2 bounds, Vector2* output); + +typedef struct { + bool has_mine; + bool has_flag; + bool is_revealed; + // -1 means it hasn't been calculated + char revealed_value; +} Cell; +Cell new_cell(); +// returns true if there isn't a mine, cells with flags can't be revealed + +typedef struct { + // x, y + Cell** cells; + Vector2 bounds; + Vector2 hover; + bool game_over; + bool game_started; +} Board; +Board generate_board(Vector2 bounds, int mine_count); +Cell* get_cell(Board* board, Vector2* position); +void shift_board(Board* board, Vector2* direction); +void destroy_board(Board); +void right_click(Board*); +void left_click(Board*); diff --git a/include/graphics.h b/include/graphics.h new file mode 100644 index 0000000..7087503 --- /dev/null +++ b/include/graphics.h @@ -0,0 +1,12 @@ +#pragma once +#include "board.h" +#include + +void draw_circle(SDL_Renderer* target, Vector2 position, int radius); +void draw_grid(SDL_Renderer* target, Vector2 size); +void draw_mines(SDL_Renderer* target, Board board); +void draw_flags(SDL_Renderer* target, Board board); +void draw_hover(SDL_Renderer* targer, Board board); +void draw_revealed(SDL_Renderer* target, Board board); +void draw_numbers(SDL_Renderer* target, Board* board); +Vector2 get_resolution(SDL_Renderer* renderer, Vector2 size); diff --git a/src/board.c b/src/board.c new file mode 100644 index 0000000..9d1756f --- /dev/null +++ b/src/board.c @@ -0,0 +1,226 @@ +#include "board.h" +#include + +Vector2 random_vector2(Vector2 bounds) +{ + Vector2 output = { -1, -1 }; + output.x = SDL_rand(bounds.x); + output.y = SDL_rand(bounds.y); + return output; +} + +int get_neighbours(Vector2 input, Vector2 bounds, Vector2* output) +{ + int length = 0; + Vector2 tmp = { -1, -1 }; + if (input.x > 0 && input.y > 0) { + tmp.x = input.x - 1; + tmp.y = input.y - 1; + output[length] = tmp; + length++; + } + if (input.x > 0 && input.y < bounds.y - 1) { + tmp.x = input.x - 1; + tmp.y = input.y + 1; + output[length] = tmp; + length++; + } + if (input.x > 0) { + tmp.x = input.x - 1; + tmp.y = input.y; + output[length] = tmp; + length++; + } + if (input.x < bounds.x - 1 && input.y > 0) { + tmp.x = input.x + 1; + tmp.y = input.y - 1; + output[length] = tmp; + length++; + } + if (input.x < bounds.x - 1 && input.y < bounds.y - 1) { + tmp.x = input.x + 1; + tmp.y = input.y + 1; + output[length] = tmp; + length++; + } + if (input.x < bounds.x - 1) { + tmp.x = input.x + 1; + tmp.y = input.y; + output[length] = tmp; + length++; + } + if (input.y > 0) { + tmp.x = input.x; + tmp.y = input.y - 1; + output[length] = tmp; + length++; + } + if (input.y < bounds.y - 1) { + tmp.x = input.x; + tmp.y = input.y + 1; + output[length] = tmp; + length++; + } + return length; +} + +Cell new_cell() +{ + Cell output; + output.has_mine = false; + output.has_flag = false; + output.is_revealed = false; + output.revealed_value = -1; + return output; +} + +char cell_value(Board* board, Vector2 position) +{ + char out = 0; + Vector2 neighbours[8]; + if (get_cell(board, &position)->has_mine) { + return -1; + } + int n = get_neighbours(position, board->bounds, neighbours); + for (int i = 0; i < n; i++) { + if (get_cell(board, &neighbours[i])->has_mine) { + out++; + } + } + return out; +} + +Vector2 find_empty_cell(Board* board) +{ + for (int i = 1; i < board->bounds.x - 1; i++) { + for (int j = 1; j < board->bounds.y - 1; j++) { + Vector2 position = { i, j }; + if (cell_value(board, position) == 0) { + return position; + } + } + } + Vector2 out = { -1, -1 }; + return out; +} +Board generate_board(Vector2 bounds, int mine_count) +{ + Cell** cells = malloc(sizeof(Cell*) * bounds.x); + for (int i = 0; i < bounds.x; i++) { + cells[i] = malloc(sizeof(Cell) * bounds.y); + for (int j = 0; j < bounds.y; j++) { + cells[i][j] = new_cell(); + } + } + Board output = { cells, bounds }; + output.hover.x = -1; + output.hover.y = -1; + output.game_over = false; + output.game_started = false; + while (mine_count > 0) { + const Vector2 mine_position = random_vector2(bounds); + if (cells[mine_position.x][mine_position.y].has_mine) { + continue; + } + mine_count--; + cells[mine_position.x][mine_position.y].has_mine = true; + } + return output; +} + +Cell* get_cell(Board* board, Vector2* position) +{ + return &board->cells[position->x][position->y]; +} + +void destroy_board(Board board) +{ + for (int i = 0; i < board.bounds.x; i++) { + free(board.cells[i]); + } + free(board.cells); +} + +void reveal_cell(Board* board, Vector2* position) +{ + Cell* cell = get_cell(board, position); + if (cell->is_revealed == true) { + return; + } + cell->is_revealed = true; + cell->revealed_value = cell_value(board, *position); + + if (cell->revealed_value != 0) { + return; + } + Vector2 neighbours[8]; + int n = get_neighbours(*position, board->bounds, neighbours); + for (int i = 0; i < n; i++) { + Cell* neighbour = get_cell(board, &neighbours[i]); + if (neighbour->has_mine) { + continue; + } + reveal_cell(board, &neighbours[i]); + } +} + +void right_click(Board* board) +{ + board->cells[board->hover.x][board->hover.y].has_flag + = !board->cells[board->hover.x][board->hover.y].has_flag; +} + +void left_click(Board* board) +{ + if (board->cells[board->hover.x][board->hover.y].has_flag) { + return; + } + if (!board->game_started) { + board->game_started = true; + if (cell_value(board, board->hover) == 0) { + reveal_cell(board, &board->hover); + return; + } + Vector2 anchor_position = find_empty_cell(board); + anchor_position.x = board->hover.x - anchor_position.x; + anchor_position.y = board->hover.y - anchor_position.y; + shift_board(board, &anchor_position); + reveal_cell(board, &board->hover); + return; + } + if (board->cells[board->hover.x][board->hover.y].has_mine) { + board->game_over = true; + return; + } + reveal_cell(board, &board->hover); +} + +Vector2 sub_modulo(Vector2 a, Vector2 b, Vector2 bounds) +{ + Vector2 out = { + (bounds.x + a.x - b.x) % bounds.x, + (bounds.y + a.y - b.y) % bounds.y + }; + return out; +} + +void shift_board(Board* board, Vector2* direction) +{ + Cell** new_board = malloc(sizeof(Cell*) * board->bounds.x); + for (int i = 0; i < board->bounds.x; i++) { + new_board[i] = malloc(sizeof(Cell) * board->bounds.y); + for (int j = 0; j < board->bounds.y; j++) { + Vector2 position = { i, j }; + position = sub_modulo(position, *direction, board->bounds); + new_board[i][j] = *get_cell(board, &position); + } + } + + for (int i = 0; i < board->bounds.x; i++) { + for (int j = 0; j < board->bounds.y; j++) { + board->cells[i][j] = new_board[i][j]; + } + free(new_board[i]); + } + free(new_board); +} diff --git a/src/graphics.c b/src/graphics.c new file mode 100644 index 0000000..c4dd2c4 --- /dev/null +++ b/src/graphics.c @@ -0,0 +1,178 @@ +#include "graphics.h" +#include "board.h" + +const SDL_Color light_green = { 170, 215, 81, SDL_ALPHA_OPAQUE }; +const SDL_Color dark_green = { 162, 209, 73, SDL_ALPHA_OPAQUE }; +const SDL_Color light_yellow = { 229, 194, 159, SDL_ALPHA_OPAQUE }; +const SDL_Color dark_yellow = { 215, 184, 153, SDL_ALPHA_OPAQUE }; +const SDL_Color hover_dark_green = { 185, 221, 119, SDL_ALPHA_OPAQUE }; +const SDL_Color black = { 0, 0, 0, SDL_ALPHA_OPAQUE }; +const SDL_Color red = { 255, 0, 0, SDL_ALPHA_OPAQUE }; + +void set_color(SDL_Renderer* target, SDL_Color color) +{ + SDL_SetRenderDrawColor(target, color.r, color.g, color.b, color.a); +} + +void draw_circle(SDL_Renderer* target, Vector2 position, int radius) +{ + int x = radius; + int y = 0; + int radius_error = 1 - x; + + while (x >= y) { + for (int i = position.x - x; i < position.x + x; i++) { + SDL_RenderPoint(target, i, position.y + y); + SDL_RenderPoint(target, i, position.y - y); + } + for (int i = position.x - y; i < position.x + y; i++) { + SDL_RenderPoint(target, i, position.y + x); + SDL_RenderPoint(target, i, position.y - x); + } + y++; + if (radius_error < 0) { + radius_error += 2 * y + 1; + } else { + x--; + radius_error += 2 * (y - x + 1); + } + } +} + +Vector2 get_resolution(SDL_Renderer* renderer, Vector2 size) +{ + SDL_Rect viewport; + Vector2 resolution; + bool darker_color = false; + SDL_GetRenderViewport(renderer, &viewport); + resolution.x = viewport.w / size.x; + resolution.y = viewport.h / size.y; + return resolution; +} + +void draw_grid(SDL_Renderer* target, Vector2 size) +{ + Vector2 resolution = get_resolution(target, size); + bool darker_color = false; + for (int i = 0; i < size.x; i++) { + for (int j = 0; j < size.y; j++) { + SDL_FRect rectangle; + rectangle.x = resolution.x * i; + rectangle.y = resolution.y * j; + rectangle.w = resolution.x; + rectangle.h = resolution.y; + if (darker_color) { + set_color(target, dark_green); + } else { + set_color(target, light_green); + } + SDL_RenderFillRect(target, &rectangle); + darker_color = !darker_color; + } + darker_color = !darker_color; + } +} + +void draw_mines(SDL_Renderer* target, Board board) +{ + set_color(target, black); + Vector2 resolution = get_resolution(target, board.bounds); + int radius = resolution.x / 4; + for (int i = 0; i < board.bounds.x; i++) { + // SDL_Log("i: %d", i); + for (int j = 0; j < board.bounds.y; j++) { + // SDL_Log("j: %d", j); + if (!board.cells[i][j].has_mine) { + continue; + } + Vector2 position = { + resolution.x * i + resolution.x / 2, + resolution.y * j + resolution.y / 2 + }; + draw_circle(target, position, radius); + } + } +} + +void draw_flags(SDL_Renderer* target, Board board) +{ + set_color(target, red); + Vector2 resolution = get_resolution(target, board.bounds); + int radius = resolution.x / 4; + for (int i = 0; i < board.bounds.x; i++) { + // SDL_Log("i: %d", i); + for (int j = 0; j < board.bounds.y; j++) { + // SDL_Log("j: %d", j); + if (!board.cells[i][j].has_flag) { + continue; + } + Vector2 position = { + resolution.x * i + resolution.x / 2, + resolution.y * j + resolution.y / 2 + }; + draw_circle(target, position, radius); + } + } +} + +void draw_hover(SDL_Renderer* target, Board board) +{ + Vector2 resolution = get_resolution(target, board.bounds); + set_color(target, hover_dark_green); + SDL_FRect rectangle; + rectangle.x = resolution.x * board.hover.x; + rectangle.y = resolution.y * board.hover.y; + rectangle.w = resolution.x; + rectangle.h = resolution.y; + SDL_RenderFillRect(target, &rectangle); +} + +void draw_revealed(SDL_Renderer* target, Board board) +{ + set_color(target, red); + Vector2 resolution = get_resolution(target, board.bounds); + int radius = resolution.x / 4; + bool darker_color = true; + for (int i = 0; i < board.bounds.x; i++) { + // SDL_Log("i: %d", i); + for (int j = 0; j < board.bounds.y; j++) { + // SDL_Log("j: %d", j); + darker_color = !darker_color; + if (!board.cells[i][j].is_revealed) { + continue; + } + SDL_FRect rectangle; + rectangle.x = resolution.x * i; + rectangle.y = resolution.y * j; + rectangle.w = resolution.x; + rectangle.h = resolution.y; + if (darker_color) { + set_color(target, dark_yellow); + } else { + set_color(target, light_yellow); + } + SDL_RenderFillRect(target, &rectangle); + } + darker_color = !darker_color; + } +} + +void draw_numbers(SDL_Renderer* target, Board* board) +{ + set_color(target, black); + Vector2 resolution = get_resolution(target, board->bounds); + int radius = resolution.x / 4; + for (int i = 0; i < board->bounds.x; i++) { + for (int j = 0; j < board->bounds.y; j++) { + Vector2 position = { i, j }; + char text[2] = { get_cell(board, &position)->revealed_value, '\0' }; + if (text[0] < 1) { + continue; + } + text[0] += '0'; + int x = resolution.x * i + resolution.x / 2; + int y = resolution.y * j + resolution.y / 2; + SDL_RenderDebugText(target, x, y, text); + } + } +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..85d15f7 --- /dev/null +++ b/src/main.c @@ -0,0 +1,115 @@ +#include "board.h" +#include "graphics.h" + +#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */ +#include +#include + +const int GRID_WIDTH = 18; +const int GRID_HEIGHT = 14; +const int CELL_RESOLUTION = 30; +const Vector2 BOARD_SIZE = { 18, 14 }; +const int MINE_COUNT = 40; + +static SDL_Window* window = NULL; +static SDL_Renderer* renderer = NULL; +static Board board; +static Vector2 resolution = { -1, -1 }; +static bool debug_draw_mines = false; + +SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) +{ + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Couldn't initialize SDL: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_CreateWindowAndRenderer("minesweeper", GRID_WIDTH * CELL_RESOLUTION, GRID_HEIGHT * CELL_RESOLUTION, 0, &window, &renderer); + + SDL_Log("created renderer"); + SDL_Rect vp; + bool status = SDL_GetRenderViewport(renderer, &vp); + if (status) + SDL_Log("x: %d, y: %d", vp.w, vp.h); + else + SDL_Log("couldn't get vp"); + + Vector2 grid_bounds = { GRID_WIDTH, GRID_HEIGHT }; + SDL_Log("generating board"); + board = generate_board(grid_bounds, MINE_COUNT); + SDL_Log("generated board"); + + resolution = get_resolution(renderer, board.bounds); + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) +{ + switch (event->type) { + case SDL_EVENT_QUIT: + return SDL_APP_SUCCESS; + case SDL_EVENT_MOUSE_MOTION: + board.hover.x = event->motion.x / resolution.x; + board.hover.y = event->motion.y / resolution.y; + // SDL_Log("%d %d", mouse_position.x, mouse_position.y); + break; + case SDL_EVENT_MOUSE_BUTTON_DOWN: + if (event->button.button == 3) { + right_click(&board); + } else if (event->button.button == 1) { + left_click(&board); + } + break; + case SDL_EVENT_KEY_DOWN: + if (event->key.key == SDLK_H) { + // debug_draw_mines = !debug_draw_mines; + } + if (event->key.key == SDLK_UP) { + Vector2 position = { 0, -1 }; + shift_board(&board, &position); + } + if (event->key.key == SDLK_DOWN) { + Vector2 position = { 0, 1 }; + shift_board(&board, &position); + } + if (event->key.key == SDLK_LEFT) { + Vector2 position = { -1, 0 }; + shift_board(&board, &position); + } + if (event->key.key == SDLK_RIGHT) { + Vector2 position = { 1, 0 }; + shift_board(&board, &position); + } + if (event->key.key == SDLK_R) { + destroy_board(board); + Vector2 grid_bounds = { GRID_WIDTH, GRID_HEIGHT }; + board = generate_board(grid_bounds, MINE_COUNT); + } + break; + } + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppIterate(void* appstate) +{ + SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); + SDL_RenderClear(renderer); + + draw_grid(renderer, BOARD_SIZE); + draw_hover(renderer, board); + draw_revealed(renderer, board); + draw_flags(renderer, board); + if (board.game_over || debug_draw_mines) { + draw_mines(renderer, board); + } + draw_numbers(renderer, &board); + + SDL_RenderPresent(renderer); + + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void* appstate, SDL_AppResult result) +{ + destroy_board(board); +}