237 lines
6.1 KiB
C
237 lines
6.1 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){
|
|
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);
|
|
}
|
|
}
|
|
|
|
const int screenWidth = 800;
|
|
const int screenHeight = 450;
|
|
float cell_size;
|
|
int mark_count;
|
|
bool dead;
|
|
|
|
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, 100, "%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;
|
|
}
|
|
|
|
|
|
/* putchar(cell_char(*c)); */
|
|
/* fflush(stdout); */
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
while(!WindowShouldClose()){
|
|
|
|
if (!dead){
|
|
handle_click(&mf);
|
|
}
|
|
|
|
BeginDrawing();
|
|
ClearBackground(RAYWHITE);
|
|
draw_minefield(&mf);
|
|
EndDrawing();
|
|
}
|
|
|
|
free_minefield(&mf);
|
|
|
|
return 0;
|
|
}
|
|
|