seems to be working

This commit is contained in:
bronku 2024-12-23 14:43:48 +01:00
commit 98a655d315
8 changed files with 594 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.cache
build
.zed
.clang-format
.DS_Store

13
CmakeLists.txt Normal file
View file

@ -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)

9
README.md Normal file
View file

@ -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
```

36
include/board.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <SDL3/SDL.h>
#include <stdbool.h>
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*);

12
include/graphics.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include "board.h"
#include <SDL3/SDL.h>
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);

226
src/board.c Normal file
View file

@ -0,0 +1,226 @@
#include "board.h"
#include <stdlib.h>
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);
}

178
src/graphics.c Normal file
View file

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

115
src/main.c Normal file
View file

@ -0,0 +1,115 @@
#include "board.h"
#include "graphics.h"
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
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);
}