I was bored at school, and it was forbidden to use phones in classes; so I began writing a minesweeper game on my book and tested it on a simple compiler on my phone and copied into my computer after I got home.
I think my code is OK, but I'm afraid that I might be getting into the Dunning-Kruger effect.
#include <stdio.h>
#include <stdlib.h>
// seed functions
#include <time.h>
#include <string.h>
// timing functions
#include <sys/time.h>
// functions to catch signals
#include <signal.h>
#include <unistd.h>
// for the field itself
#define MINE 9
// for the mask
#define FLAG 9
#define EMPTY 0
#define IS_MINE(_Tile) (_Tile == MINE)
// the actual field
int **field;
// shows what the player knows
int **mask;
// sizes of the field
int height;
int width;
// mine count of the current field
int mine_cnt;
void help(char *prog, int h) {
puts("Minestest Copyright (C) 2018 Arda Ünlü");
puts("This program comes with ABSOLUTELY NO WARRANTY.");
puts("This is free software, and you are welcome to redistribute it");
puts("under certain conditions.");
printf("Run \"%s --help\" for help.\n\n", prog);
if(h) {
printf("Usage: %s [difficulty | height width mine_count]\n", prog);
puts("Difficulty can be easy, medium, or hard.");
puts("Input format while playing:");
puts("a b c");
puts("a: x coordinate");
puts("b: y coordinate");
puts("c: 0 or 1: step on cell or flag cell (default: 0)");
}
}
// handling signals and freeing malloc'd areas
// so we won't leak memory
// except a sigkill, then rip
void handle(int sig) {
printf("Caught signal %d! Exiting.\n", sig);
for(int i = 0; i < height; i++) {
free(field[i]);
free(mask[i]);
}
free(field);
free(mask);
exit(1);
}
// setup for the signal handler
// the actual handler is the function above, void handle(int sig)
void setupsig() {
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = handle;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
}
void parse_args(int argc, char *argv[]) {
if(argc > 1) {
// predefined difficulty levels
// and calling help
if(argc == 2) {
if((strcmp(argv[1], "-h") == 0)
|| (strcmp(argv[1], "--help") == 0)) {
help(argv[0], 1);
exit(0);
}
else if(strcmp(argv[1], "easy") == 0) {
height = 8;
width = 8;
mine_cnt = 10;
}
else if(strcmp(argv[1], "medium") == 0) {
height = 16;
width = 16;
mine_cnt = 40;
}
// "hard" lands here, as well as anything else
else {
height = 16;
width = 30;
mine_cnt = 99;
}
}
// allow player to define their own difficulty levels
else if(argc == 4) {
height = (atoi(argv[1]) ? : 16);
width = (atoi(argv[2]) ? : 30);
mine_cnt = (atoi(argv[3]) ? : 99);
}
// arguments are invalid
else {
help(argv[0], 1);
exit(1);
}
}
// no arguments
else {
height = 16;
width = 30;
mine_cnt = 99;
}
// padding the field so that
// we have zeroes all around
height += 2;
width += 2;
}
void print_field() {
char *cell[12] = {
// cell labels, 0 to 8
"\x1b[0m0",
"\x1b[94m1",
"\x1b[32m2",
"\x1b[91m3",
"\x1b[34m4",
"\x1b[31m5",
"\x1b[36m6",
"\x1b[35m7",
"\x1b[37m8",
"\x1b[41;30mO\x1b[0m", //mine
"\x1b[41;30mP\x1b[0m", //flag
};
printf("%d mine%s left.\n", mine_cnt, (mine_cnt == 1 ? "" : "s"));
putchar(' '); // pad the x coordinates by 1 character
// print x coordinates
for(int x = 1; x < width-1; x++) {
printf("%d", x%10);
}
putchar('\n');
for(int y = 1; y < height-1; y++) {
// reset color sequence and print the y coordinate
printf("\x1b[0m%d", y%10);
// print every cell on that y line
for(int x = 1; x < width-1; x++) {
// if the player knows anything about the cell
// print it
if(mask[y][x] != EMPTY) {
printf("%s", mask[y][x] == FLAG ? cell[10] : cell[field[y][x]]);
}
// if they don't, just print a dot
else {
printf("\x1b[0m.");
}
}
putchar('\n');
}
// reset colors after finishing printing
printf("\x1b[0m\n");
}
int fill_field() {
// be sure that there can be empty cells
if(height * width < mine_cnt) {
puts("Too many mines.");
return 1;
}
// allocate our fields
field = malloc(height * sizeof(int*));
mask = malloc(height * sizeof(int*));
// let's not segfault
if(!field || !mask)
return 1;
// allocate every line
for(int i = 0; i < height; i++) {
field[i] = malloc(width * sizeof(int));
mask[i] = malloc(width * sizeof(int));
if(!field[i] || !mask[i])
return 1;
}
// fill the mask
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
mask[y][x] = !EMPTY;
}
}
#ifndef DEBUG
for(int y = 1; y < height-1; y++) {
for(int x = 1; x < width-1; x++) {
mask[y][x] = EMPTY;
}
}
#endif
// temporary variables for mine locations
int coord_a, coord_b;
// initialize main field
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
field[y][x] = EMPTY;
}
}
srand(time(NULL));
//fill mines
for(int i = 0; i < mine_cnt; i++) {
coord_a = (rand() % (height-2)) + 1;
coord_b = (rand() % (width-2)) + 1;
//don't put mines in the same cell twice
if(IS_MINE(field[coord_a][coord_b])) {
i--;
continue;
}
field[coord_a][coord_b] = MINE;
}
//fill numbers one by one
for(int y = 1; y < height-1; y++) {
for(int x = 1; x < width-1; x++) {
// don't put a number if the current cell is a mine
if(!IS_MINE(field[y][x])) {
// looping the 3x3 adjacent cells
for(int dy = -1; dy <= 1; dy++) {
for(int dx = -1; dx <= 1; dx++) {
// skip the current cell
if(dy == 0 && dx == 0) continue;
field[y][x] += IS_MINE(field[y + dy][x + dx]);
}
}
}
}
}
return 0;
}
// this function has 2 uses that basically use the same algorithm.
// why == 1) if the cell player inputted was 0, extend that field
// until we hit a cell which is adjacent to a mine
// why == 2) if the player inputs a cell which they already know,
// assume they want to step on every adjacent cell
int explore_neighbors(int x, int y, int why) {
// don't remove the flag, if there is one, if we are being
// called as a recursion from the same function
// if there is no flag, just remove the mask
if(mask[y][x] != FLAG)
mask[y][x] = !EMPTY;
// counter for adjacent mines
int cnt = 0;
// loop the 3x3 adjacent cells
for(int dy = -1; dy <= 1; dy++) {
for(int dx = -1; dx <= 1; dx++) {
// skip the current cell
if(dy == 0 && dx == 0) continue;
// why == 1) don't expand if there are mines in adjacent cells
if(why == 1) {
if(IS_MINE(field[y + dy][x + dx])) return 1;
}
// why == 2) count the mines in adjacent cells
else
cnt += IS_MINE(mask[y + dy][x + dx]);
}
}
// if the user inputted a cell they certainly
// know not to be empty
if(why == 2 && cnt != 0) {
for(int dy = -1; dy <= 1; dy++) {
for(int dx = -1; dx <= 1; dx++) {
// if there is a mine that was placed wrongly
if(IS_MINE(mask[y + dy][x + dx])
!= IS_MINE(field[y + dy][x + dx])) {
// exit game
return 1;
}
// if not, do nothing
}
}
}
// why == 1) do this unconditionally
// why == 2) if the number of adjacent mines correct
if((why == 1) || (cnt == field[y][x])) {
// loop the adjacent 3x3 block
for(int dy = -1; dy <= 1; dy++) {
for(int dx = -1; dx <= 1; dx++) {
if(why == 1) {
// skip the current cell
if(dy == 0 && dx == 0) continue;
// expand if there are more empty areas
if(mask[y + dy][x + dx] == EMPTY)
explore_neighbors(x + dx, y + dy, 1);
}
// why == 2
else {
// skip the current cell
if(dy == 0 && dx == 0) continue;
// unmask the adjacent cells
// while leaving the flags
if(mask[y + dy][x + dx] != FLAG)
mask[y + dy][x + dx] = !EMPTY;
// if there is a filed with no mines nearby,
// expand to there
if(field[y + dy][x + dx] == EMPTY)
explore_neighbors(x + dx, y + dy, 1);
}
}
}
}
return 0;
}
int play_game() {
int ret = 0;
// internal variables for the loop
int flag, finished, w;
// input variables
int in_x, in_y, in_f;
char input_buf[99];
print_field();
while(1) {
// prompt
printf("> ");
fgets(input_buf, 99, stdin);
flag = sscanf(input_buf, "%d %d %d", &in_x, &in_y, &in_f);
// if the user didn't enter a flag
if(flag == 2) {
//set the internal variable to 0
in_f = 0;
// increment the mine count variable if the
// current cell was flagged previously
if(IS_MINE(mask[in_y][in_x])) {
mine_cnt++;
}
}
// if the user entered an invalid input
// just skip it
else if(flag != 3) {
continue;
}
puts("");
// if the input is out of bounds, skip it
if(in_x > width - 2 || in_y > height - 2 || in_x < 1 || in_y < 1) {
continue;
}
// if the inputted cell is a mine
// and the user didn't enter a flag
// end game
if(IS_MINE(field[in_y][in_x]) && in_f == 0) {
ret = 1;
break;
}
// if the current cell wasn't flagged before
// and the user flagged now,
// decrement the mine count variable
if(in_f && !IS_MINE(mask[in_y][in_x])) mine_cnt--;
// the reason to call explore_neighbors
w = 0;
// if the current cell is 0, we want to
// expand into the zero-mine area
if(field[in_y][in_x] == EMPTY) w = 1;
// if the user know the cell they entered was empty,
// then they thnk they know all the adjacent mines.
// they want to expand to the adjacet 3x3 area.
else if(mask[in_y][in_x] == !EMPTY) w = 2;
// if the variable w is set and the user didn't enter a flag,
// call explore_neighbors with the current cell coordinates
// and the reason why we want to explore
if(w && in_f == 0) {
ret = explore_neighbors(in_x, in_y, w);
}
if(w != 2) ret = 0;
// if ret is set, that means the user entered
// a known cell with wrong adjacent mines.
// exit game.
else if(ret) break;
// unmask or flag the current cell according to the input
mask[in_y][in_x] = in_f ? FLAG : !EMPTY;
// print the field after the operation
print_field();
finished = 0;
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
// the variable starts as 0 and increments
// only if the current cell is masked,
// i.e. the user doesn't know about it yey
finished += !mask[y][x];
}
}
// if that variable is still zero that means the user
// has unmasked/flagged every cell.
// but we still have to check if they just randomly put
// flags everywhere.
if(!finished && mine_cnt == 0) {
puts("Congratulations! You've beaten the game!");
break;
}
}
// if the user failed, control flow gets here
if(ret == 1) {
puts("Better luck next time!");
// unmask ever cell and print
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
mask[y][x] = !EMPTY;
}
}
print_field();
}
// hopefully we won't leak any memory
for(int i = 0; i < height; i++) {
free(field[i]);
free(mask[i]);
}
free(field);
free(mask);
return ret;
}
int main(int argc, char *argv[]) {
// set up the signal handling code
setupsig();
int ret = 0;
parse_args(argc, argv);
help(argv[0], 0);
// exit if an error occures while trying to set up the field
if((ret = fill_field()) != 0) {
return ret;
}
struct timeval begin, end;
// the time when the game actually starts
gettimeofday(&begin, NULL);
ret = play_game();
// the time after the game ends
gettimeofday(&end, NULL);
printf("Game lasted %.2f seconds.\n",
(double) (end.tv_usec - begin.tv_usec) / 1000000
+ (double) (end.tv_sec - begin.tv_sec));
return ret;
}
if(height * width < mine_cnt)
should beif((height-2) * (width-2) < mine_cnt)
. \$\endgroup\$