Skip to main content
deleted 809 characters in body
Source Link
betseg
  • 255
  • 2
  • 10
/* Minestest - a simple mine game
 * Copyright (C) 2018  Arda Ünlü
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see  < https://www.gnu.org/licenses/>.
 */

#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;
}
/* Minestest - a simple mine game
 * Copyright (C) 2018  Arda Ünlü
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see  < https://www.gnu.org/licenses/>.
 */

#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;
}
#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;
}
Tweeted twitter.com/StackCodeReview/status/966340048949055488
Source Link
betseg
  • 255
  • 2
  • 10

Minesweeper in C

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.

/* Minestest - a simple mine game
 * Copyright (C) 2018  Arda Ünlü
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see  < https://www.gnu.org/licenses/>.
 */

#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;
}