QUOTE: Life is tough, but so are you.

cmine

A tiny terminal-based Minesweeper

commit b234ed5039dfd96005138828ece341da3638d216
parent 7a164b1a54368a57b92b5e920bcb2f74db2d9c5f
Author: Sophie <info@soophie.de>
Date:   Tue, 13 May 2025 13:07:42 +0000

feat: Windows support & small improvements

Diffstat:
MMakefile | 27++++++++++++++++++++-------
Msrc/main.c | 102+++++++++++++++++++++++++++++++++++++++++--------------------------------------
2 files changed, 73 insertions(+), 56 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,10 +1,23 @@ -CC=cc -CLFAGS=-Wall -Wextra -Werror -pendatic -LIBS=-lm -TARGET=cmine +TARGET = cmine +SOURCE := $(wildcard src/*.c) -build: src/*.c - $(CC) $(CFLAGS) -o $(TARGET) $^ $(LIBS) +POSIX_CC = cc +POSIX_CFLAGS = -Wall -Wextra -Werror -pedantic +POSIX_LIBS = -lm + +WINDOWS_CC = x86_64-w64-mingw32-gcc +WINDOWS_CFLAGS = -Wall -Wextra -Werror -pedantic +WINDOWS_LIBS = -static -lm -I/usr/local/include/ + +.PHONY: all build-posix build-windows clean + +all: build-posix + +build-posix: + $(POSIX_CC) $(POSIX_CFLAGS) -o $(TARGET) -DLIB_TERM_POSIX $(SOURCE) $(POSIX_LIBS) + +build-windows: + $(WINDOWS_CC) $(WINDOWS_CFLAGS) -o $(TARGET).exe -DLIB_TERM_WINDOWS $(SOURCE) $(WINDOWS_LIBS) clean: - rm ./$(TARGET) + rm -f ./$(TARGET) ./$(TARGET).exe diff --git a/src/main.c b/src/main.c @@ -1,5 +1,6 @@ #include <stdlib.h> #include <getopt.h> +#include <time.h> #define LIB_TERM_IMPL #include <libterm.h> @@ -59,7 +60,6 @@ static level_t LEVEL_EASY = { "EASY", 9, 9, 10 }; static level_t LEVEL_MEDIUM = { "MEDIUM", 16, 16, 40 }; static level_t LEVEL_HARD = { "HARD", 16, 30, 99 }; -static const u32_t COLOR_NONE = 0x000000; static const u32_t COLOR_CURSOR = 0xffffff; static const u32_t COLOR_COVER = 0xcccccc; static const u32_t COLOR_BOMB = 0xff0000; @@ -76,11 +76,11 @@ static const u32_t COLOR_DIGITS[9] = { 0xaaaaaa, }; -int is_valid(cmine_t *self, int y, int x) { +int cm_is_valid(cmine_t *self, int y, int x) { return y >= 0 && y < self->size.y && x >= 0 && x < self->size.x; } -int is_flag(u8_t cell, int flag) { +int cm_is_flag(u8_t cell, int flag) { return (cell & flag) != 0; } @@ -88,16 +88,16 @@ u8_t *cell_at(cmine_t *self, int y, int x) { return &self->board[y * self->size.x + x]; } -void victory(cmine_t *self) { +void cm_victory(cmine_t *self) { self->state = STATE_VICTORY; // flag all bombs for (int y = 0; y < self->size.y; y++) { for (int x = 0; x < self->size.x; x++) { u8_t *cell = cell_at(self, y, x); - if (!is_flag(*cell, FLAG_BOMB)) { + if (!cm_is_flag(*cell, FLAG_BOMB)) { continue; } - if (is_flag(*cell, FLAG_FLAG)) { + if (cm_is_flag(*cell, FLAG_FLAG)) { continue; } *cell |= FLAG_FLAG; @@ -106,16 +106,16 @@ void victory(cmine_t *self) { } } -void defeat(cmine_t *self) { +void cm_defeat(cmine_t *self) { self->state = STATE_DEFEAT; // reveal all bombs for (int y = 0; y < self->size.y; y++) { for (int x = 0; x < self->size.x; x++) { u8_t *cell = cell_at(self, y, x); - if (!is_flag(*cell, FLAG_BOMB)) { + if (!cm_is_flag(*cell, FLAG_BOMB)) { continue; } - if (is_flag(*cell, FLAG_OPEN)) { + if (cm_is_flag(*cell, FLAG_OPEN)) { continue; } *cell |= FLAG_OPEN; @@ -124,18 +124,18 @@ void defeat(cmine_t *self) { } } -void open(cmine_t *self, int y, int x) { +void cm_open(cmine_t *self, int y, int x) { if (self->state != STATE_PLAYING) { return; } u8_t *cell = cell_at(self, y, x); // ignore flags - if (is_flag(*cell, FLAG_FLAG)) { + if (cm_is_flag(*cell, FLAG_FLAG)) { return; } // handle open cells // chord opening - if (is_flag(*cell, FLAG_OPEN)) { + if (cm_is_flag(*cell, FLAG_OPEN)) { int digit = *cell & MASK_DIGIT; if (digit == 0) { return; @@ -143,14 +143,14 @@ void open(cmine_t *self, int y, int x) { int flags = 0; for (int ly = -1; ly <= 1; ly++) { for (int lx = -1; lx <= 1; lx++) { - if (!is_valid(self, y + ly, x + lx)) { + if (!cm_is_valid(self, y + ly, x + lx)) { continue; } if (ly == 0 && lx == 0) { continue; } u8_t *l_cell = cell_at(self, y + ly, x + lx); - if (is_flag(*l_cell, FLAG_FLAG)) { + if (cm_is_flag(*l_cell, FLAG_FLAG)) { flags++; } } @@ -158,15 +158,15 @@ void open(cmine_t *self, int y, int x) { if (flags == digit) { for (int ly = -1; ly <= 1; ly++) { for (int lx = -1; lx <= 1; lx++) { - if (!is_valid(self, y + ly, x + lx)) { + if (!cm_is_valid(self, y + ly, x + lx)) { continue; } if (ly == 0 && lx == 0) { continue; } u8_t *l_cell = cell_at(self, y + ly, x + lx); - if (!is_flag(*l_cell, FLAG_OPEN)) { - open(self, y + ly, x + lx); + if (!cm_is_flag(*l_cell, FLAG_OPEN)) { + cm_open(self, y + ly, x + lx); } } } @@ -177,8 +177,8 @@ void open(cmine_t *self, int y, int x) { *cell |= FLAG_OPEN; *cell |= FLAG_DIRTY; // check if bomb - if (is_flag(*cell, FLAG_BOMB)) { - defeat(self); + if (cm_is_flag(*cell, FLAG_BOMB)) { + cm_defeat(self); return; } int digit = *cell & MASK_DIGIT; @@ -186,13 +186,13 @@ void open(cmine_t *self, int y, int x) { if (digit == 0) { for (int ly = -1; ly <= 1; ly++) { for (int lx = -1; lx <= 1; lx++) { - if (!is_valid(self, y + ly, x + lx)) { + if (!cm_is_valid(self, y + ly, x + lx)) { continue; } if (ly == 0 && lx == 0) { continue; } - open(self, y + ly, x + lx); + cm_open(self, y + ly, x + lx); } } } @@ -201,26 +201,26 @@ void open(cmine_t *self, int y, int x) { for (int y = 0; y < self->size.y; y++) { for (int x = 0; x < self->size.x; x++) { u8_t *l_cell = cell_at(self, y, x); - if (!is_flag(*l_cell, FLAG_BOMB) && !is_flag(*l_cell, FLAG_OPEN)) { + if (!cm_is_flag(*l_cell, FLAG_BOMB) && !cm_is_flag(*l_cell, FLAG_OPEN)) { return; } } } - victory(self); + cm_victory(self); } -void flag(cmine_t *self, int y, int x) { +void cm_flag(cmine_t *self, int y, int x) { if (self->state != STATE_PLAYING) { return; } u8_t *cell = cell_at(self, y, x); // ignore open - if (is_flag(*cell, FLAG_OPEN)) { + if (cm_is_flag(*cell, FLAG_OPEN)) { return; } *cell |= FLAG_DIRTY; // unset flag - if (is_flag(*cell, FLAG_FLAG)) { + if (cm_is_flag(*cell, FLAG_FLAG)) { *cell &= ~FLAG_FLAG; self->count++; } @@ -231,11 +231,11 @@ void flag(cmine_t *self, int y, int x) { } } -void move(cmine_t *self, int dy, int dx) { +void cm_move(cmine_t *self, int dy, int dx) { if (self->state != STATE_PLAYING) { return; } - if (!is_valid(self, self->cursor.y + dy, self->cursor.x + dx)) { + if (!cm_is_valid(self, self->cursor.y + dy, self->cursor.x + dx)) { return; } *cell_at(self, self->cursor.y, self->cursor.x) |= FLAG_DIRTY; @@ -243,7 +243,7 @@ void move(cmine_t *self, int dy, int dx) { *cell_at(self, self->cursor.y, self->cursor.x) |= FLAG_DIRTY; } -void generate(cmine_t *self, level_t level, int seed) { +void cm_generate(cmine_t *self, level_t level, int seed) { // init self (*self) = (cmine_t) { .state = STATE_PLAYING, @@ -271,7 +271,7 @@ void generate(cmine_t *self, level_t level, int seed) { int y = rand() % self->size.y; int x = rand() % self->size.x; u8_t *cell = cell_at(self, y, x); - if (!is_flag(*cell, FLAG_BOMB)) { + if (!cm_is_flag(*cell, FLAG_BOMB)) { *cell |= FLAG_BOMB; bombs--; } @@ -282,14 +282,14 @@ void generate(cmine_t *self, level_t level, int seed) { u8_t *cell = cell_at(self, y, x); for (int ly = -1; ly <= 1; ly++) { for (int lx = -1; lx <= 1; lx++) { - if (!is_valid(self, y + ly, x + lx)) { + if (!cm_is_valid(self, y + ly, x + lx)) { continue; } if (ly == 0 && lx == 0) { continue; } u8_t *l_cell = cell_at(self, y + ly, x + lx); - if (is_flag(*l_cell, FLAG_BOMB)) { + if (cm_is_flag(*l_cell, FLAG_BOMB)) { (*cell)++; } } @@ -303,7 +303,7 @@ void generate(cmine_t *self, level_t level, int seed) { for (int x = 0; x < self->size.x; x++) { u8_t *cell = cell_at(self, y, x); int digit = *cell & MASK_DIGIT; - if (is_flag(*cell, FLAG_BOMB) || digit != 0) { + if (cm_is_flag(*cell, FLAG_BOMB) || digit != 0) { continue; } count++; @@ -319,15 +319,15 @@ void generate(cmine_t *self, level_t level, int seed) { } self->cursor = sel; // uncover starting position - open(self, self->cursor.y, self->cursor.x); + cm_open(self, self->cursor.y, self->cursor.x); } -void update(term_t *term, cmine_t *self) { +void cm_update(term_t *term, cmine_t *self) { // check window resize size_t new_height; size_t new_width; term_read_window(term, &new_height, &new_width); - int is_resize = new_height != self->window.y || new_width != self->window.x; + int is_resize = (int) new_height != self->window.y || (int) new_width != self->window.x; term_write(term, "\x1b[?25l"); if (is_resize) { self->window = (vec2_t) { new_height, new_width }; @@ -354,17 +354,17 @@ void update(term_t *term, cmine_t *self) { for (int x = 0; x < self->size.x; x++) { u8_t *cell = cell_at(self, y, x); // ignore non-dirty cells - if (!is_resize && !is_flag(*cell, FLAG_DIRTY)) { + if (!is_resize && !cm_is_flag(*cell, FLAG_DIRTY)) { continue; } *cell &= ~ FLAG_DIRTY; // determine color and symbol - int is_bomb = is_flag(*cell, FLAG_BOMB); - int is_open = is_flag(*cell, FLAG_OPEN); + int is_bomb = cm_is_flag(*cell, FLAG_BOMB); + int is_open = cm_is_flag(*cell, FLAG_OPEN); int digit = *cell & MASK_DIGIT; char symbol = '?'; u32_t color = COLOR_COVER; - if (!is_open && is_flag(*cell, FLAG_FLAG)) { + if (!is_open && cm_is_flag(*cell, FLAG_FLAG)) { symbol = 'F'; color = COLOR_FLAG; } @@ -434,41 +434,45 @@ int main(int argc, char *argv[]) { term_t term = term_init(); term_rawmode_enable(&term); cmine_t self; - generate(&self, *level, seed); + cm_generate(&self, *level, seed); term_write(&term, "\x1b[?1049h"); term_flush(&term); int quit = 0; while (!quit) { - update(&term, &self); + cm_update(&term, &self); int key = term_poll_key(&term, 100); switch (key) { case 'q': quit = 1; break; case KEY_ESC: - generate(&self, *level, ++seed); + cm_generate(&self, *level, ++seed); break; case 'j': - move(&self, 1, 0); + case KEY_ARROW_DOWN: + cm_move(&self, 1, 0); break; case 'k': - move(&self, -1, 0); + case KEY_ARROW_UP: + cm_move(&self, -1, 0); break; case 'l': - move(&self, 0, 1); + case KEY_ARROW_RIGHT: + cm_move(&self, 0, 1); break; case 'h': - move(&self, 0, -1); + case KEY_ARROW_LEFT: + cm_move(&self, 0, -1); break; case ' ': // start timer if (self.time == -1) { self.time = time(NULL); } - open(&self, self.cursor.y, self.cursor.x); + cm_open(&self, self.cursor.y, self.cursor.x); break; case 'f': - flag(&self, self.cursor.y, self.cursor.x); + cm_flag(&self, self.cursor.y, self.cursor.x); break; default: break;