#include "raylib.h" #include #include #include #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); cell* clicked_cell(Minefield* mf, int x, int y); void expose_cell(Minefield* mf, int r, int c); void handle_click(Minefield* mf); typedef enum screens { GAME_LOOP, GAME_OVER, HELP, } Screen; Screen game_loop(Minefield* mf); /* Screen game_over(Minefield* mf); */ Screen help_screen(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; 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 (*loop)(Minefield*); while(!WindowShouldClose()){ loop = screen_fn(screen); screen = (*loop)(&mf); } free_minefield(&mf); return 0; } Screen game_loop(Minefield* mf){ while(!WindowShouldClose()){ if (IsKeyPressed(KEY_H)){ break; } if (!dead){ handle_click(mf); } BeginDrawing(); ClearBackground(RAYWHITE); draw_minefield(mf); EndDrawing(); } last_screen = GAME_LOOP; return HELP; } Screen help_screen(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 (*screen_fn(Screen scr))(Minefield *) { switch (scr) { case GAME_LOOP: return game_loop; /* case GAME_OVER: */ /* return game_loop; */ case HELP: return help_screen; 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); } } } char time_str[20]; float time = GetTime(); snprintf(time_str, 20, "%02d.%02d - %d/%d", (int) time / 60, (int) time % 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); } } } } void 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; } cell cell = get_cell(mf, r, c); if (cell & MARKED){ return; } else if (cell & MINE){ printf("DIED\n"); expose_cell(mf, r, c); 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; } mark_count += mf->cells[r * mf->col + c] & MARKED ? -1 : 1; mf->cells[r * mf->col + c] ^= MARKED; } }