[C language implementation] a comprehensive minesweeping game (including blank expansion, marking, etc.) with specific steps and code analysis

preface

Minesweeping is a very classic game. I believe everyone played it as a child. In the process of implementation, you will have a great sense of achievement. Now let's realize it together,.

1, Problem description

Realize all functions of minesweeping game except the interface. include

Implement a simple interface
Realize the function of mine detection
Realize the function of marking mine
Realize the display of the number of surrounding mines
Realize the first troubleshooting without encountering thunder
Realize that if there is no thunder around, it will expand

2, Basic framework conception

Before writing code, we must play minesweeping and get familiar with its various rules and mechanisms, which will help us form clearer code logic. At the same time, after writing a function, test it and compare it with the real mine sweeping function. In order to write code, I played more than ten times.

After the trial, the first thing we think of is how to display the interface and how to set ray. For two-dimensional planes, the most commonly used array is to store different data in the array to indicate whether there is thunder or not.

  1. Suppose we use a two-dimensional array, with data 0 indicating that there are no mines and 1 indicating that there are mines, but when we check a point, we need to display the number of surrounding mines, assuming that it is also 1, there will be a conflict, and it is not convenient to count the number of surrounding mines.
  2. Another problem is that the array is easy to be out of bounds. It needs to be judged every time it is accessed. It is troublesome and error prone.

terms of settlement:

  1. We use two arrays. One indicates whether there is thunder or not, and the other shows it to the user. It is not used for troubleshooting*
    ’Indicates that it has been checked, which is expressed by the number of surrounding mines. For unification, we use character arrays, which are converted into characters when encountering integers.

  2. We can only use the inner part of the set array, that is, the outermost part is no longer used for judgment.

As shown below:

Next, let's implement the main function, the simple menu

#define _CRT_SECURE_NO_WARNINGS 1

void Menu()
{
	printf("********************************\n");
	printf("*********   1. play     ********\n");
	printf("*********   0. exit     ********\n");
	printf("********************************\n");
}
int main()
{
	int input = 0;
	int count = 0;
	srand((unsigned int)time(NULL));
	do
	{
		Menu();

		//Clear the buffer and do not use it for the first time
		if(count!=0)
		{
			char ch;
			while ((ch = getchar()) != EOF && ch != '\n')
			{
				;
			}			
		}
		count++;
		
		printf("Please select:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			//Reset the correct number of tags to 0
			mark_count = 0;
			MineSweeper();
			break;
		case 0:
			printf("Exit the game\n");
			break;
		default:
			printf("Wrong selection, reselect!\n");
			break;
		}
	} while (input);

	return 0;
}

Set the purpose of clearing buffer Code:
Prevent entering a valid number with a space, and then entering a number to store in the buffer, resulting in the next direct access to the number in the buffer, which is not in line with the original intention.
As shown in the figure:

After addition:

3, Concrete implementation

1. Implementation of mine sweeping interface

Define related macro definitions first, and modify later

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>


#define ROW 9 / / row of minesweeping map array
#define COL 9 / / rows of minesweeping map array

#define ROWS 11 / / the rows of the real array. In order to find the number of mines, you don't have to judge whether the access array is out of bounds
#define COLS 11 / / add an extra circle to the rows of the real array

#define MINE_COUNT 9 / / sets the number of Mines

//Set global variables
int find_count;//It is used to judge whether it is the first demining to prevent being cheated for the first time
int mark_count;//Mark the correct quantity

Game interface, call each function to realize mine sweeping.

void MineSweeper()
{
	//To unify symbols, use character arrays

	char mine[ROWS][COLS] = { 0 };//An array used to store mines. 0 means no mines and 1 means there are mines
	char show[ROWS][COLS] = { 0 };//An array for storing the number of mines around the point printed to the user,
	                             //The default is *, and the content is the number of surrounding mines after entering coordinates
								 // ! Mark points for
	//Array initialization
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');


	//Set mine
	SetMine(mine, ROW, COL);

	system("cls");
	//After debugging, you can view the location of the mine
	//DisplayBoard(mine, ROW, COL);
	
	//Display array layout
	DisplayBoard(show, ROW, COL);

	//Game entrance
	PlayGame(mine, show, ROW, COL);
}

2. Map initialization

Because both arrays initially store only one character, '0' and '*', all characters can be directly passed in as parameters, so that a function can be used in general.

//Array initialization
void InitBoard(char board[][COLS], int row, int col, char ch)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			board[i][j] = ch;
		}
	}
}

3. Set lightning

Use the random function to set Lei, that is, the part of the mine array is randomly set to '1'. It should be noted that we only focus on the really effective part inside the array, regardless of the outermost circle, so the traversal starts from 1.

//Set mine
void SetMine(char board[][COLS], int row, int col)
{
	int count = MINE_COUNT;
	while (count)
	{
		//Obtain the coordinates of the mine at random
		int x = rand() % row + 1;//From 1 to row-1
		int y = rand() % col + 1;
		//Judge whether it is ray
		if (board[x][y] != '1')
		{
			//If it's not ray, let it go
			board[x][y] = '1';
			count--;
		}
	}
}

4. Display interface

Print the incoming array and print the line number. Note that only the inside of the array is printed, and the traversal starts from 1.
If marked, use '!' express.

//Display array layout
void DisplayBoard(char board[][COLS], int row, int col)
{
	printf("------------------------\n");
	printf("  ");
	for (int i = 1; i <= col; i++)
	{
		printf("%2d", i);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%2d", i);
		for (int j = 1; j <= col; j++)
		{
			printf(" %c", board[i][j]);
		}
		printf("\n");
	}
	printf("------------------------\n");
}

5. Start mine clearance

The number of checked mines is equal to the number of non mines, or the number of correctly marked mines is equal to the number of set mines. After that, the demining is successful.
Print the show array after each selection.

//Game entry, select check or mark
void PlayGame(char mine[][COLS], char show[][COLS], int row, int col)
{
	int win_count = 0;//Checked quantity

	//When the number of checked mines is equal to the number of '0', or the number of correctly marked mines is equal to the set number of mines, the demining is successful
	while (win_count < (row * col - MINE_COUNT) && mark_count < MINE_COUNT)
	{

		printf("################################\n");
		printf("#########   1. Check thunder   ########\n");
		printf("#########   2. Mark mine   ########\n");
		printf("#########   3. Unmark ########\n");
		printf("################################\n");
		int choice;
		printf("Please select:>");
		
		//Clear buffer
		char ch;
		while ((ch = getchar()) != EOF && ch != '\n')
		{
			;
		}
		scanf("%d", &choice);
		if (choice != 1 && choice != 2 && choice != 3)
		{
			printf("Input error, please re-enter\n");
			//Skip this cycle
			continue;
		}
		if (choice == 1)
		{
			//Check thunder
			int judge = FindMine(mine, show, row, col, &win_count);
			if (!judge)
			{
				//Killed by a mine, print the array of hidden mines and end
				DisplayBoard(mine, row, col);
				return;
			}
		}
		else
		{			
			if (choice == 2)
			{
				//Mark mine
				MarkMine(mine, show, row, col);
				system("cls");
				DisplayBoard(show, row, col);
			}
			else
			{
				//Unmark mine				
				CancelMark(mine, show, row, col);
				system("cls");
				DisplayBoard(show, row, col);
			}
		}

	}
	if (win_count == row * col - MINE_COUNT||mark_count==MINE_COUNT)
	{
		system("cls");
		printf("Congratulations on your success\n");
		DisplayBoard(mine, ROW, COL);
		return;
	}
}

6. Calculate the number of surrounding mines

Count the eight directions, up, down, left, right, top left, bottom left, top right and bottom right. Because the stored characters are characters, subtract '0' after adding them to get an integer.

//Find the number of surrounding mines
int GetMineCount(char mine[][COLS], int x, int y)
{
	return (mine[x - 1][y] +
		mine[x + 1][y] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x - 1][y - 1] +
		mine[x + 1][y - 1] +
		mine[x - 1][y + 1] +
		mine[x + 1][y + 1] - 8 * '0');
}

7. Mine detection

It is necessary to set the mine not to be killed for the first time. At the same time, if it is marked, it can not be checked again. When it is blown up or the input coordinates are wrong, it returns.

//Check thunder
//Returning 0 means ending, and returning 1 means continuing
int FindMine(char mine[][COLS], char show[][COLS], int row, int col, int* pwin)
{
	int x, y;
	
	printf("Please enter the coordinates you want to check:>");
	scanf("%d %d", &x, &y);
	//If it is the first time, after resetting, if it is still thunder, continue the cycle
	while (find_count == 0)
	{
		if (mine[x][y] == '1')//It's ray
		{
			//Now empty the mine array, that is, initialize
			InitBoard(mine, row, col, '0');
			//Re mine
			SetMine(mine, row, col);
		}
		else
		{
			break;
		}
	}
	
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		if (mine[x][y] == '0')//Not ray
		{
			//If it has been marked, it cannot be checked
			if (show[x][y] == '!')
			{
				printf("This point has been marked, please re-enter\n");
				return 1;
			}
			system("cls");
			SpreadBlank(mine, show, x, y, pwin);
			DisplayBoard(show, row, col);
		}
		else
		{			
			printf("I'm sorry you were killed!\n");			
			return 0;
		}
	}
	else
	{
		printf("Input coordinate error, please re-enter\n");
		return 1;
	}
	
}

Can no longer be checked after marking:

Won't be killed the first time:

After selection:

8. Blank expansion

Purpose: if there is no thunder around, continue to expand (recursion) and stop when there is thunder
This is a function that is difficult to implement. It needs to be implemented recursively, and the termination conditions of recursion need to be determined when using recursion. Here are three

  1. The outermost circle is returned directly without calculation, which is the advantage of setting one more circle
  2. If the show array is not *, that is, it has been explored, it will be returned directly to prevent dead recursion and stack overflow.
  3. Stop if there's thunder around.
//If there are no mines around, expand them all
//Expand the blank area
void SpreadBlank(char mine[][COLS], char show[][COLS], int x, int y, int* pwin)
{
	//The outermost circle is returned directly without calculation, which is the advantage of setting one more circle
	if (x==0||y==0||x==ROWS-1||y==COLS-1)
		return;

	//If the show array is not *, that is, it has been explored, it will be returned directly to prevent dead recursion and stack overflow
	if (show[x][y] != '*')
		return;
		
	int count = GetMineCount(mine, x, y);
	if (count > 0)
	{
		show[x][y] = count + '0';
		//Increase the number of troubleshooting
		(*pwin)++;
		return ;
	}
	else
	{
		//Eight directions, up, down, left, right, top left, bottom left, top right, bottom right
		
		show[x][y] = '0';
		//Increase the number of troubleshooting
		(*pwin)++;
		SpreadBlank(mine, show, x - 1, y, pwin);
		SpreadBlank(mine, show, x + 1, y, pwin);
		SpreadBlank(mine, show, x, y - 1, pwin);
		SpreadBlank(mine, show, x, y + 1, pwin);
		SpreadBlank(mine, show, x - 1, y - 1, pwin);
		SpreadBlank(mine, show, x + 1, y - 1, pwin);
		SpreadBlank(mine, show, x - 1, y + 1, pwin);
		SpreadBlank(mine, show, x + 1, y + 1, pwin);
	}
}

9. Marking mine

Use '!' Is a marker symbol. If the mine point is correctly marked, the correct number of marks is + 1

//Mark mine point
void MarkMine(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x;
	int y;
	printf("Please enter the coordinates you want to mark:>");
	scanf("%d%d", &x, &y);
	//This point must not be probed, that is, at the point where the show array is' * '
	if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='*')
	{
		if (mine[x][y] == '1')
		{
			//Correctly mark lightning points
			mark_count++;
		}
		show[x][y] = '!';
	}
	else
	{
		printf("Input coordinate error, please re-enter\n");
	}

}

10. Unmark

Similar to marking, just change the marking point to '*'. If the point is a correctly marked mine point, the correct number of marks is - 1

//Unmark mine points
void CancelMark(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x;
	int y;
	printf("Please enter the coordinates you want to unmark:>");
	scanf("%d%d", &x, &y);
	//This point needs to be marked
	if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='!')
	{
		if (mine[x][y] == '1')
		{
			//If the mine point is correctly marked, the quantity shall be reduced by one
			mark_count--;
		}
		show[x][y] = '*';
	}
	else
	{
		printf("The input coordinates are incorrect or not marked points, please re-enter\n");
	}

}

4, Result demonstration

We set the number of mines to 1 and display the mine array to let us know the location of mines for testing.

#define MINE_COUNT 1 / / set the number of Mines
DisplayBoard(mine, ROW, COL);



Expand the presentation:

All launched, demining succeeded.

Of course, there are the following situations.

5, Complete code

MineSweeper.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>


#define ROW 9 / / row of minesweeping map array
#define COL 9 / / rows of minesweeping map array

#define ROWS 11 / / the rows of the real array. In order to find the number of classes, you don't have to judge whether the access array is out of bounds
#define COLS 11 / / add an extra circle to the rows of the real array

#define MINE_COUNT 1 / / set the number of Mines


int find_count;//It is used to judge whether it is the first demining to prevent being cheated for the first time
int mark_count;//Mark the correct quantity



//Array initialization
void InitBoard(char board[][COLS], int row, int col, char ch);

//Display array layout
void DisplayBoard(char board[][COLS], int row, int col);

//Set mine
void SetMine(char board[][COLS], int row, int col);

//Game entry, select check or mark
void PlayGame(char mine[][COLS], char show[][COLS], int row, int col);

//Check thunder
//Returning 0 means ending, and returning 1 means continuing
int FindMine(char mine[][COLS], char show[][COLS], int row, int col, int* pwin);

//Find the number of surrounding mines
int GetMineCount(char mine[][COLS], int x, int y);

//If there are no mines around, expand them all
//Expand the blank area
void SpreadBlank(char mine[][COLS], char show[][COLS], int x, int y, int* pwin);

//Mark mine point
void MarkMine(char mine[][COLS], char show[][COLS], int row, int col);
//Unmark mine points
void CancelMark(char mine[][COLS], char show[][COLS], int row, int col);

MineSweeper.c

#pragma once

#include"MineSweeper.h"


//Array initialization
void InitBoard(char board[][COLS], int row, int col, char ch)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			board[i][j] = ch;
		}
	}
}

//Display array layout
void DisplayBoard(char board[][COLS], int row, int col)
{
	printf("------------------------\n");
	printf("  ");
	for (int i = 1; i <= col; i++)
	{
		printf("%2d", i);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%2d", i);
		for (int j = 1; j <= col; j++)
		{
			printf(" %c", board[i][j]);
		}
		printf("\n");
	}
	printf("------------------------\n");
}

//Set mine
void SetMine(char board[][COLS], int row, int col)
{
	int count = MINE_COUNT;
	while (count)
	{
		//Obtain the coordinates of the mine at random
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//Judge whether it is ray
		if (board[x][y] != '1')
		{
			//If it's not ray, let it go
			board[x][y] = '1';
			count--;
		}
	}
}

//Game entry, select check or mark
void PlayGame(char mine[][COLS], char show[][COLS], int row, int col)
{
	int win_count = 0;//Checked quantity

	//When the number of checked mines is equal to the number of '0', or the number of correctly marked mines is equal to the set number of mines, the demining is successful
	while (win_count < (row * col - MINE_COUNT) && mark_count < MINE_COUNT)
	{

		printf("################################\n");
		printf("#########   1. Check thunder   ########\n");
		printf("#########   2. Mark mine   ########\n");
		printf("#########   3. Unmark ########\n");
		printf("################################\n");
		int choice;
		printf("Please select:>");
		//Clear buffer
		char ch;
		while ((ch = getchar()) != EOF && ch != '\n')
		{
			;
		}
		scanf("%d", &choice);
		if (choice != 1 && choice != 2 && choice != 3)
		{
			printf("Input error, please re-enter\n");
			//Skip this cycle
			continue;
		}
		if (choice == 1)
		{
			//Check thunder
			int judge = FindMine(mine, show, row, col, &win_count);
			if (!judge)
			{
				//Killed by a mine, print the array of hidden mines and end
				DisplayBoard(mine, row, col);
				return;
			}
		}
		else
		{			
			if (choice == 2)
			{
				//Mark mine
				MarkMine(mine, show, row, col);
				system("cls");
				DisplayBoard(show, row, col);
			}
			else
			{
				//Unmark mine				
				CancelMark(mine, show, row, col);
				system("cls");
				DisplayBoard(show, row, col);
			}
		}

	}
	if (win_count == row * col - MINE_COUNT||mark_count==MINE_COUNT)
	{
		system("cls");
		printf("Congratulations on your success\n");
		DisplayBoard(mine, ROW, COL);
		return;
	}
}


//Check thunder
//Returning 0 means ending, and returning 1 means continuing
int FindMine(char mine[][COLS], char show[][COLS], int row, int col, int* pwin)
{
	int x, y;
	
	printf("Please enter the coordinates you want to check:>");
	scanf("%d %d", &x, &y);
	//If it's the first time
	while (find_count == 0)
	{
		if (mine[x][y] == '1')//It's ray
		{
			//Now empty the mine array, that is, initialize
			InitBoard(mine, row, col, '0');
			//Re mine
			SetMine(mine, row, col);
		}
		else
		{
			break;
		}
	}
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		if (mine[x][y] == '0')//Not ray
		{
			//If it has been marked, it cannot be checked
			if (show[x][y] == '!')
			{
				printf("This point has been marked, please re-enter\n");
				return 1;
			}

			//int count = GetMineCount(mine, x, y);
			//show[x][y] = count + '0';

			system("cls");
			SpreadBlank(mine, show, x, y, pwin);
			DisplayBoard(show, row, col);
			//win++;
		}
		else
		{			
			printf("I'm sorry you were killed!\n");			
			return 0;
		}
	}
	else
	{
		printf("Input coordinate error, please re-enter\n");
		return 1;
	}
	
}

//Find the number of surrounding mines
int GetMineCount(char mine[][COLS], int x, int y)
{
	return (mine[x - 1][y] +
		mine[x + 1][y] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x - 1][y - 1] +
		mine[x + 1][y - 1] +
		mine[x - 1][y + 1] +
		mine[x + 1][y + 1] - 8 * '0');
}


//If there are no mines around, expand them all
//Expand the blank area
void SpreadBlank(char mine[][COLS], char show[][COLS], int x, int y, int* pwin)
{
	//Do not calculate the outermost circle
	if (x==0||y==0||x==ROWS-1||y==COLS-1)
		return;

	//If the show array is not *, that is, it has been explored, it will be returned directly to prevent dead recursion and stack overflow
	if (show[x][y] != '*')
		return;
	int count = GetMineCount(mine, x, y);
	if (count > 0)
	{
		show[x][y] = count + '0';
		//Increase the number of troubleshooting
		(*pwin)++;
		return ;
	}
	else
	{
		//mine[x][y] = '2';
		//Eight directions, up, down, left, right, top left, bottom left, top right, bottom right
		
		show[x][y] = '0';
		//Increase the number of troubleshooting
		(*pwin)++;
		SpreadBlank(mine, show, x - 1, y, pwin);
		SpreadBlank(mine, show, x + 1, y, pwin);
		SpreadBlank(mine, show, x, y - 1, pwin);
		SpreadBlank(mine, show, x, y + 1, pwin);
		SpreadBlank(mine, show, x - 1, y - 1, pwin);
		SpreadBlank(mine, show, x + 1, y - 1, pwin);
		SpreadBlank(mine, show, x - 1, y + 1, pwin);
		SpreadBlank(mine, show, x + 1, y + 1, pwin);
	}
}

//Mark mine point
void MarkMine(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x;
	int y;
	printf("Please enter the coordinates you want to mark:>");
	scanf("%d%d", &x, &y);
	//This point must not be probed, that is, at the point where the show array is' * '
	if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='*')
	{
		if (mine[x][y] == '1')
		{
			//Correctly mark lightning points
			mark_count++;
		}
		show[x][y] = '!';
	}
	else
	{
		printf("Input coordinate error, please re-enter\n");
	}

}

//Unmark mine points
void CancelMark(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x;
	int y;
	printf("Please enter the coordinates you want to unmark:>");
	scanf("%d%d", &x, &y);
	//This point needs to be marked
	if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='!')
	{
		if (mine[x][y] == '1')
		{
			//If the mine point is correctly marked, the quantity shall be reduced by one
			mark_count--;
		}
		show[x][y] = '*';
	}
	else
	{
		printf("The input coordinates are incorrect or not marked points, please re-enter\n");
	}

}

MineSweeperTest.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"MineSweeper.h"

void Menu()
{
	printf("********************************\n");
	printf("*********   1. play     ********\n");
	printf("*********   0. exit     ********\n");
	printf("********************************\n");
}

void MineSweeper()
{
	//To unify symbols, use character arrays

	char mine[ROWS][COLS] = { 0 };//An array used to store mines. 0 means no mines and 1 means there are mines
	char show[ROWS][COLS] = { 0 };//An array for storing the number of mines around the point printed to the user,
	                             //The default is *, and the content is the number of surrounding mines after entering coordinates
								 // ! Mark points for
	//Array initialization
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');


	//Set mine
	SetMine(mine, ROW, COL);

	system("cls");
	//Trial adjustment
	DisplayBoard(mine, ROW, COL);

	
	//Display array layout
	DisplayBoard(show, ROW, COL);

	//Game entrance
	PlayGame(mine, show, ROW, COL);
}

int main()
{
	int input = 0;
	int count = 0;
	srand((unsigned int)time(NULL));
	do
	{
		Menu();

		//Clear the buffer and do not use it for the first time
		if(count!=0)
		{
			char ch;
			while ((ch = getchar()) != EOF && ch != '\n')
			{
				;
			}			
		}
		count++;
		printf("Please select:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			//Reset the correct number of tags to 0
			mark_count = 0;
			MineSweeper();
			break;
		case 0:
			printf("Exit the game\n");
			break;
		default:
			printf("Wrong selection, reselect!\n");
			break;
		}
	} while (input);

	return 0;
}

summary

For the improvement of mine sweeping, all functions except the interface are realized. The most difficult point is to expand the blank area, because it involves recursion and there are many restricted conditions in the array, so it needs to be debugged many times. If there is any mistake, you are welcome to correct it.

Tags: C

Posted on Fri, 24 Sep 2021 03:31:49 -0400 by stoop