Files
games-collection/minesweeper/mine.c
2024-12-21 13:58:45 +03:00

333 lines
8.5 KiB
C

#include "raylib.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "../util.h"
typedef char cell;
#define MINE 0b10000000
#define MARKED 0b01000000
#define EXPOSED 0b00100000
#define NUMBERED 0b00010000
#define CELL_NUMBER 0b00000111
/*
cell is a char (8 bits)
0000 0000
^^^^ ^^^
|||| number of mine neigbours
||||> 1 if is a number
|||> 1 if exposed (revealed)
||> 1 if marked
|> 1 if mine
*/
typedef struct minefield_t {
int row;
int col;
cell* cells;
int mine_count;
} Minefield;
static inline cell get_cell(Minefield* mf, int r, int c){
return mf->cells[r * mf->col + c];
}
static inline cell set_cell(Minefield* mf, int r, int c, cell cell){
return mf->cells[r * mf->col + c] = cell;
}
Minefield make_minefield(int row, int col, int mine_count);
void free_minefield(Minefield* mf);
char cell_char(cell c);
void print_minefield(Minefield *mf, FILE *stream);
void draw_minefield(Minefield* mf);
void draw_time(Minefield* mf, float time);
cell* clicked_cell(Minefield* mf, int x, int y);
void expose_cell(Minefield* mf, int r, int c);
bool handle_click(Minefield* mf);
typedef enum screens {
DEFAULT_SCREEN,
GAME_LOOP,
GAME_OVER,
HELP,
} Screen;
Screen game_loop(Minefield* mf);
Screen game_over_loop(Minefield* mf);
Screen help_loop(Minefield *mf);
Screen (*screen_fn(Screen scr))(Minefield *);
Screen last_screen;
const int screenWidth = 800;
const int screenHeight = 450;
float cell_size;
int mark_count;
bool dead;
float end_time;
Screen (*loop)(Minefield*);
int main(){
srand(time(NULL));
Minefield mf = make_minefield(10, 20, 30);
/* print_minefield(&mf, stdout); */
dead = false;
mark_count = 0;
{
float width = (float) screenWidth / mf.col;
float height = (float) screenHeight / mf.row;
cell_size = width > height ? height : width;
}
InitWindow(screenWidth, screenHeight, "Minesweeper");
SetTargetFPS(60);
Screen screen = GAME_LOOP;
Screen temp_screen;
while(!WindowShouldClose()){
loop = screen_fn(screen);
temp_screen = (*loop)(&mf);
last_screen = screen;
screen = temp_screen;
}
free_minefield(&mf);
return 0;
}
Screen game_loop(Minefield* mf){
while(!WindowShouldClose()){
if (IsKeyPressed(KEY_H)){
return HELP;
}
if (handle_click(mf)){
return GAME_OVER;
}
BeginDrawing();
ClearBackground(RAYWHITE);
draw_minefield(mf);
draw_time(mf, GetTime());
EndDrawing();
}
return DEFAULT_SCREEN;
}
Screen help_loop(Minefield *mf) {
while (!WindowShouldClose() && IsKeyDown(KEY_H)){
BeginDrawing();
ClearBackground(RAYWHITE);
DrawText("Left Mouse Click - Open tile.", 100, 100, 30, BLACK);
DrawText("Right Mouse Click - Mark tile.", 100, 150, 30, BLACK);
EndDrawing();
}
return last_screen;
}
Screen game_over_loop(Minefield* mf){
char* end_text = dead ? "You Died!" : "You Win!";
while(!WindowShouldClose()){
if (IsKeyPressed(KEY_H)){
return HELP;
}
BeginDrawing();
ClearBackground(RAYWHITE);
draw_minefield(mf);
draw_time(mf, end_time);
DrawText(end_text, screenWidth / 2, screenHeight / 2, 30, BLACK);
EndDrawing();
}
return DEFAULT_SCREEN;
}
Screen (*screen_fn(Screen scr))(Minefield *) {
switch (scr) {
case GAME_OVER:
return game_over_loop;
case HELP:
return help_loop;
case GAME_LOOP:
default:
return game_loop;
}
}
Minefield make_minefield(int row, int col, int mine_count){
Minefield mf = {.row = row, .col = col};
int size = mf.row * mf.col;
mf.cells = (cell *)calloc(size, sizeof(cell));
mf.mine_count = mine_count;
// 0 initialization
for (int i = 0; i < size; i++) {
mf.cells[i] = 0;
}
// Place mines
/* float prob = (float) mine_count / f.row * f.col; */
for (int m = mine_count; m > 0;){
for (int i = 0; i < size; i++){
if (!(mf.cells[i] & MINE) && (rand() % size) < mine_count) {
mf.cells[i] = MINE;
if (--m <= 0) {
break;
}
}
}
}
// Place numbers
for (int i = 0; i < mf.row; i++){
for (int j = 0; j < mf.col; j++){
if (!(get_cell(&mf, i, j) & MINE)){
cell c = 0;
for (int ii = imax(i - 1, 0); ii < imin(i + 2, mf.row); ii++){
for (int jj = imax(j - 1, 0); jj < imin(j + 2, mf.col); jj++){
if (get_cell(&mf, ii, jj) & MINE){
c++;
}
}
}
set_cell(&mf, i, j, c | NUMBERED);
}
}
}
return mf;
}
void free_minefield(Minefield* mf){
if (mf){
free(mf->cells);
mf->cells = NULL;
}
}
char cell_char(cell c){
if (c & MARKED) {
return 'X';
} else if (c & MINE) {
return '*';
} else {
c &= CELL_NUMBER;
return c > 0 ? c + '0' : ' ';
}
}
void print_minefield(Minefield *mf, FILE *stream){
for (int i = 0; i < mf->row; i++) {
for (int j = 0; j < mf->col; j++) {
fputc('|', stream);
cell c = mf->cells[i * mf->col + j];
fputc(cell_char(c), stream);
}
fputs("|\n", stream);
}
}
void draw_minefield(Minefield* mf){
char str[2] = "0";
for (int i = 0; i < mf->row; i++){
for (int j = 0; j < mf->col; j++){
int x = cell_size * j;
int y = cell_size * i;
DrawRectangleLines(x, y, cell_size, cell_size, BLACK);
cell c = get_cell(mf, i, j);
if (c & EXPOSED){
str[0] = cell_char(c);
DrawText(str, x + cell_size / 3, y + cell_size / 3, 20, BLACK);
} else {
DrawRectangle(x + 2, y + 2 , cell_size - 4, cell_size - 4,
c & MARKED ? RED : GRAY);
}
}
}
}
void draw_time(Minefield* mf, float time){
char time_str[20];
float t = time;
snprintf(time_str, 20, "%02d.%02d - %d/%d",
(int) t / 60, (int) t % 60,
mark_count, mf->mine_count);
DrawText(time_str, screenWidth / 2 - 20, screenHeight - 30, 20, BLACK);
}
cell* clicked_cell(Minefield* mf, int x, int y){
int r = y / cell_size;
int c = x / cell_size;
if (r >= 0 && r < mf->row && c >= 0 && c < mf->col) {
return &mf->cells[r * mf->col + c];
}
return NULL;
}
void expose_cell(Minefield* mf, int r, int c){
cell* cel = &mf->cells[r * mf->col + c];
*cel |= EXPOSED;
if (*cel & NUMBERED && (*cel & CELL_NUMBER) == 0) {
for (int i = imax(r - 1, 0); i < imin(r + 2, mf->row); i++){
for (int j = imax(c - 1, 0); j < imin(c + 2, mf->col); j++){
cell tcell = get_cell(mf, i, j);
if (tcell & EXPOSED ||
!(tcell & NUMBERED)){
continue;
}
expose_cell(mf, i, j);
}
}
}
}
bool handle_click(Minefield* mf){
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)){
int r = GetMouseY() / cell_size;
int c = GetMouseX() / cell_size;
if (!(r >= 0 && r < mf->row && c >= 0 && c < mf->col)) {
printf("Click outside range, (%d, %d)\n", r, c);
return false;
}
cell cell = get_cell(mf, r, c);
if (cell & MARKED){
return false;
} else if (cell & MINE){
printf("DIED\n");
expose_cell(mf, r, c);
end_time = GetTime();
return dead = true;
} else {
expose_cell(mf, r, c);
}
} else if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)){
int r = GetMouseY() / cell_size;
int c = GetMouseX() / cell_size;
if (!(r >= 0 && r < mf->row && c >= 0 && c < mf->col)) {
printf("Click outside range, (%d, %d)\n", r, c);
return false;
}
mark_count += mf->cells[r * mf->col + c] & MARKED ? -1 : 1;
mf->cells[r * mf->col + c] ^= MARKED;
if (mark_count == mf->mine_count){
for (int i = 0; i < mf->row * mf->col; i++){
cell cel = mf->cells[i] & (MARKED | MINE);
if (cel & MARKED && !(cel & MINE)){
return false;
}
}
end_time = GetTime();
return true;
}
}
return false;
}