C language for minesweeper (can be expanded automatically)

  • 2020-11-26 18:55:39
  • OfStack

preface

This blog focuses on how to implement minesweeper using the C language.

1. Rules of the game

There are MINE_COUNT mines on a map with ROW row, COL column. Players input coordinates to turn over the grid, if there is no stepping on the mines, then calculate the total number of mines in the surrounding 8 grids, and replace the asterisk of this grid with a number. If the number is 0, the recursion continues to calculate the mine situation around the four cells adjacent to the cell (that is, the automatic expansion). If you step on thunder, the game is over. When the sum of the Numbers and asterisks on the local map equals the total number of squares, the player is declared the winner. The macro definition is as follows:


#define ROW 9
#define COL 9
#define MINE_COUNT 10

2. Game flow

1. Initialize the map

To simplify the logic, initialize two maps, one for the player and one for the distribution of mines. Initializes two character arrays, assigning all elements in the first array to "*" and all elements in the second array to "0". Given a random number seed to "mine", use a loop statement to randomly place MINE_COUNT mines in the second array, represented by "1". If a location is already mined, it is skipped, generating the next random location. The code is as follows:


void init(char gameMap[ROW][COL], char mineMap[ROW][COL]){
 srand((unsigned int)time(0));
 for (int i = 0; i < ROW; i++){
 for (int j = 0; j < COL; j++){
  gameMap[i][j] = '*';
 }
 }
 for (int i = 0; i < ROW; i++){
 for (int j = 0; j < COL; j++){
  mineMap[i][j] = '0';
 }
 }
 int n = 0;
 while (n < MINE_COUNT){
 int row = rand() % ROW;
 int col = rand() % COL;
 if (mineMap[row][col] != '1'){
  mineMap[row][col] = '1';
  n++;
 }
 else{
  continue;
 }
 }
}

After the map is initialized, one openedBlocksCount integer variable is defined and assigned with the value of 0, which is used to count the number of squares that have been opened to determine whether the player has won or not.

2. Print the map

Similar to 3 children, map styles and array elements are printed with circular statements. All elements are "*". The code is as follows:


void printMap(char Map[ROW][COL]){
 printf(" |");
 for (int i = 0; i < COL; i++){
 printf("%d ", i);
 }
 printf("\n");
 printf("--+------------------\n");
 for (int i = 0; i < ROW; i++){
 printf(" %d|", i);
 for (int j = 0; j < COL; j++){
  printf("%c ", Map[i][j]);
 }
 printf("\n");
 }
}

3. The player opens the grid

The player inputs the coordinates and turns over the grid. If the coordinates cross the boundary, it will prompt the wrong input and re-input; If this coordinate has been opened, also re-input; If you step on a mine, the game will fail and the printMap() function will be used to print out the mine map.

4. Update the map and expand it automatically

If no mines are stepped on, the game will continue, update the map, according to the mine distribution map, using the circular statement, calculate the number of mines around this grid, and replace the number of "*" on the player's map. If the number is 0, the mine distribution around the adjacent grid is calculated recursively. Take 1 cell on the left of the grid as an example, first judge whether the position of the left grid is out of bounds, ** and then judge whether this grid has been opened; If it has been turned over there is no need to calculate again. This point is worth noting that when I was writing code, I initially missed turning over this constraint, causing infinite recursion and stack overflow. ** code is as follows:


void updateGameMap(char gameMap[ROW][COL],char mineMap[ROW][COL],int row,int col){
 int count = 0;
 for (int i = row-1; i <=row+1 ; i++){
 for (int j = col-1; j <= col+1; j++){
  if (i >= ROW || i < 0 || j >= COL || j < 0){
  continue;
  }
  if (mineMap[i][j] == '1'){
  count++;
  }
 }
 }
 gameMap[row][col] = '0' + count;
 // Automatically open 
 if (gameMap[row][col] == '0'){
 if (row < ROW && row >= 0 && col < COL && col - 1 >= 0
  && gameMap[row][col - 1] == '*'){
  // At first there was an omission '*' Then expand this condition, resulting in repeated recursion of the opened cell, the final stack overflow 
  updateGameMap(gameMap, mineMap, row, col - 1);
 }
 if (row < ROW && row >= 0 && col + 1 < COL && col >= 0
  && gameMap[row][col + 1] == '*'){
  updateGameMap(gameMap, mineMap, row, col + 1);
 }
 if (row < ROW && row - 1 >= 0 && col < COL && col >= 0
  && gameMap[row - 1][col] == '*'){
  updateGameMap(gameMap, mineMap, row - 1, col);
 }
 if (row + 1 < ROW && row >= 0 && col < COL && col >= 0
  && gameMap[row + 1][col] == '*'){
  updateGameMap(gameMap, mineMap, row + 1, col);
 }
 }
}

After the completion of this step, go back to step 3, let the player input opened grid coordinates, the game continues.
When all non-mine coordinates are turned over, the game wins.

3. game function, menu function and main function

Similar to 3 sub-chess pieces, 1 game function is implemented to series the game flow, and 1 menu function is also written to increase user friendliness. The code is as follows:


int menu(){
 printf("======================\n");
 printf("====== 1. start ======\n");
 printf("====== 0. exit ======\n");
 printf("======================\n");
 int option = -1;
 scanf("%d", &option);
 return option;
}

void game(){
 char gameMap[ROW][COL] = { ' ' };
 char mineMap[ROW][COL] = { ' ' };
 srand((unsigned int)time(0));
 init(gameMap, mineMap);
 int openedBlockCount = 0;
 while (1){
 printMap(mineMap);
 printf("=================================\n");
 printMap(gameMap);
 int row = 0;
 int col = 0;
 printf(" Please enter coordinates (row col)# ");
 scanf("%d %d", &row, &col);
 if (row < 0 || row >= ROW || col < 0 || col >= COL) {
  printf(" Input is wrong !\n");
  continue;
 }
 if (gameMap[row][col] != '*') {
  printf(" This coordinate has been flipped !\n");
  continue;
 }
 if (mineMap[row][col] == '1') {
  printf(" Step on the ray ! Game over ...\n");
  printMap(mineMap);
  break;
 }
 else{
  updateGameMap(gameMap, mineMap, row, col);
  openedBlockCount++;
  if (openedBlockCount == ROW*COL - MINE_COUNT){
  printf(" You win! \n");
  printMap(mineMap);
  break;
  }
 }
 }
}


int main(){

 while (1){
 system("cls");
 int option = menu();
 if (option == 1){
  game();
  break;
 }
 else if (option == 0){
  break;
 }
 else{ 
  printf(" Input is wrong !\n");
 }
 }

 system("pause");
 return 0;
}

conclusion

The focus of minesweeper is still on the "modeling" process, which translates the rules of the game into the logic of the program. There are two main difficulties with writing code. First of all, how to represent a map grid "thunder", "no thunder", "open", "not opened" these four states. Obviously, it is difficult to solve this problem with one map, hence the two maps described above. Second, when debugging automatic expansion, I directly encountered the problem of stackoverflow, and no problem was found when setting breakpoints to check the running logic of the code. Later, after 3 checks, it was found that has not been opened the lattice skipped. In this case, there is no end condition for recursion, resulting in stack overflow.


Related articles: