扫雷游戏(C语言)

C语言实现扫雷

  • 一、多文件整体概括
    • game.h
    • 扫雷.c
    • test.c (main函数实现逻辑)
  • 二、子函数实现
    • (1)初始化棋盘
    • (2)打印棋盘
    • (3)设置雷
    • (4)排查雷
    • (5)计算该位置周围有几个雷
    • (6)爆炸展开一片
    • (7)标记雷
    • (8)取消标记
    • (9)判断胜利
  • 三、运行结果
    • (1)正常排查
    • (2)标记雷获胜
  • 四、总结

一、多文件整体概括

game.h

#pragma once
#include //scanf和printf
#include //系统的清屏和随机数函数
#include //时间函数,主要为了产生随机数
#define ROW 9 //要操作的行
#define COL 9 //要操作的列
#define ROWS ROW+2 //实际的行
#define COLS COL+2 //实际的列
#define Easy_Count 80
void InitBoard(char Board[ROWS][COLS],int row,int col,char set);//初始化棋盘
void PrintBoard(char Board[ROWS][COLS], int row, int col);//打印棋盘
void SetMine(char Board[ROWS][COLS], int row, int col);//设置雷
void FindMine(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col);//排查雷
int GetMineCount(char Board[ROWS][COLS], int row, int col);//计算该位置周围的八个位置有几个雷
void ExtendBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col, int x, int y);//爆炸展开
void MarkBrard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col);//标记雷
void UnMarkBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col);//取消标记
int isWin(char playBoard[ROWS][COLS], int row, int col);//判断输赢


game.h主要是对要用到的函数,头文件进行引入和声明

扫雷.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
int count1 = 0;//判断是否标记正确
int count2 = Easy_Count;//最多标记次数
int win = 0;
//初始化棋盘
void InitBoard(char Board[ROWS][COLS], int row, int col, char set)
{
	for (int i = 0;i < row;i++)
	{
		for (int j = 0;j < col;j++)
		{
			Board[i][j] = set;
		}
	}
}

//打印棋盘
void PrintBoard(char Board[ROWS][COLS], int row, int col)
{
	printf("------扫雷游戏--------\n");
	for (int i = 0;i <=row;i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (int i = 1;i <= row;i++)
	{
		printf("%d ", i);
		for (int j = 1;j <= col;j++)
		{
			printf("%c ",Board[i][j]);
		}
		printf("\n");
	}
}

//设置雷
void SetMine(char Board[ROWS][COLS], int row, int col)
{
	int count = Easy_Count;
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (Board[x][y] == '0')
		{
			Board[x][y] = '1';
			count--;
		}
	}
}

//排查雷
void FindMine(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
	PrintBoard(playBoard, row, col);
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入要排查的位置->(用空格隔开)\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (playBoard[x][y] != '*')
			{
				printf("该位置已经排查过!!!请重新输入->\n");
			}
			else
			{
				if (MineBoard[x][y] == '1')
				{
					//排查的位置是雷
					printf("很遗憾,你被炸死了!\n");
					PrintBoard(MineBoard, row, col);
					break;
				}
				else
				{
					//排查的位置不是雷,显示周围八个有几个雷
					ExtendBoard(MineBoard, playBoard, row, col, x, y);
					break;
				}
			}
		}
		else
		{
			printf("坐标不合法!!!请重新输入->\n");
		}
	}
}

//计算周围有多少个雷
int GetMineCount(char Board[ROWS][COLS], int x, int y)
{
	return (Board[x - 1][y - 1] + Board[x - 1][y] + Board[x - 1][y + 1] + Board[x][y - 1] + Board[x][y + 1] + Board[x + 1][y - 1] + Board[x + 1][y] + Board[x + 1][y + 1] - 8 * '0');
}

//展开一片
void ExtendBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col, int x, int y)
{
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		int count = GetMineCount(MineBoard, x, y);
		if (!count)
		{
			playBoard[x][y] = ' ';
			for (int i = x - 1;i <= x + 1;i++)
			{
				for (int j = y - 1;j <= y + 1;j++)
				{
					if (playBoard[i][j] == '*')
						ExtendBoard(MineBoard, playBoard, ROW, COL, i, j);
				}
			}
		}
		else
		{
			playBoard[x][y] = count + '0';
		}
	}
}

//标记雷
void MarkBrard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
	PrintBoard(playBoard, row, col);
	int x = 0;
	int y = 0;
	printf("请选择要标记的坐标->(用空格隔开)\n");
	scanf("%d %d", &x, &y);
	while (1)
	{
		if(x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
		{
			if (playBoard[x][y] == '*')//可以标记
			{
				playBoard[x][y] = '#';
				count2--;
				if (MineBoard[x][y] == '1')
				{
					count1++;
				}
				break;
			}
			else
			{
				printf("该位置已被标记过,请重新输入!!!\n");
			}
		}
		else//坐标不合法
		{
			printf("非法坐标!!!请重新输入!!!\n");
		}
	}
}

//取消标记

void UnMarkBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	PrintBoard(playBoard, row, col);
	printf("请输入要取消标记的坐标->(中间用空格隔开)\n");
	scanf("%d %d", &x, &y);
	while (1)
	{
		if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
		{
			if (playBoard[x][y] == '#')
			{
				playBoard[x][y] = '*';
				count2++;
				if (MineBoard[x][y] == '1')
				{
					count1--;
				}
				break;
			}
			else
			{
				printf("该位置未曾标记,无法取消,请重新输入->\n");
			}
		}
		else//坐标非法
		{
			printf("非法坐标!!!请重新输入!!!\n");
		}
	}
}
int isWin(char playBoard[ROWS][COLS], int row, int col)
{
	for (int i = 1;i <= row;i++)
	{
		for (int j = 1;j <= col;j++)
		{
			if (playBoard[i][j] == '*')
			{
				win++;
			}
		}
	}
	if (win == Easy_Count)//剩余未排查的坐标等于雷的个数,获胜
	{
		win = 0;
		return 1;
	}
	else if (count1 == Easy_Count)//将雷的位置全部标记,获胜
	{
		win = 0;
		return 1;
	}
	else return 0;
}

扫雷.c主要是对该游戏要用到的函数的实现

test.c (main函数实现逻辑)

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
	printf("***************************\n");
	printf("*********1. 排查雷 ********\n");
	printf("*********2. 标记雷 ********\n");
	printf("*********3. 取消标记 ********\n");
	printf("*********0. 退出游戏********\n");
}
int main()
{
regame:
	{
		int input = 0;
		srand((unsigned int)time(NULL));
		char mineBoard[ROWS][COLS];//存放布置好的雷
		char playBoard[ROWS][COLS];//存放排查出的雷
		//初始化棋盘
		//1.mineBoard数组最开始全是'0'
		//2.playBoard数组最开始全是'*'
		InitBoard(mineBoard, ROWS, COLS, '0');
		InitBoard(playBoard, ROWS, COLS, '*');
		//打印棋盘
		//PrintBoard(mineBoard,ROW,COL);
		//PrintBoard(playBoard, ROW, COL);
		//布置雷
		SetMine(mineBoard, ROW, COL);
		PrintBoard(mineBoard, ROW, COL);
		do
		{
			menu();
			printf("请选择->\n");
			scanf("%d", &input);
			switch (input)
			{
			case 0:
			{
				goto  exit;
				break;
			}
			case 1:
			{
				FindMine(mineBoard, playBoard, ROW, COL);
				if (isWin(playBoard, ROW, COL))
				{
					PrintBoard(playBoard, ROW, COL);
					PrintBoard(mineBoard, ROW, COL);
					printf("恭喜你,排雷成功^ ^\n");
					printf("本轮游戏结束,是否想再玩一局->(Y/N)\n");
					while (getchar() != '\n')//吸收多余回车
					{

					}
					char s = 0;
					while (1)
					{
						scanf("%c", &s);
						if (s == 'Y')
						{
							system("cls");//清空屏幕
							goto regame;
						}
						else if (s == 'N')
						{
							goto  exit;
						}
						else
						{
							printf("输入非法,请重新输入\n");
						}
					}
					break;
				}
				PrintBoard(playBoard, ROW, COL);
				break;
			}
			case 2:
			{
				MarkBrard(mineBoard, playBoard, ROW, COL);
				if (isWin(playBoard, ROW, COL))
				{
					PrintBoard(playBoard, ROW, COL);
					PrintBoard(mineBoard, ROW, COL);
					printf("恭喜你,排雷成功^ ^\n");
					printf("本轮游戏结束,是否想再玩一局->(Y/N)\n");
					while (getchar() != '\n')//吸收多余回车
					{

					}
					char s = 0;
					while (1)
					{
						scanf("%c", &s);
						if (s == 'Y')
						{
							system("cls");//清空屏幕
							goto regame;
						}
						else if (s == 'N')
						{
							goto  exit;
						}
						else
						{
							printf("输入非法,请重新输入\n");
						}
					}
					break;
				}
				PrintBoard(playBoard, ROW, COL);
				break;
			}
			case 3:
			{
				UnMarkBoard(mineBoard, playBoard, ROW, COL);
				if (isWin(playBoard, ROW, COL))
				{
					PrintBoard(playBoard, ROW, COL);
					PrintBoard(mineBoard, ROW, COL);
					printf("恭喜你,排雷成功^ ^\n");
					printf("本轮游戏结束,是否想再玩一局->(Y/N)\n");
					while (getchar() != '\n')//吸收多余回车
					{

					}
					char s = 0;
					while (1)
					{
						scanf("%c", &s);
						if (s == 'Y')
						{
							system("cls");//清空屏幕
							goto regame;
						}
						else if (s == 'N')
						{
							goto  exit;
						}
						else
						{
							printf("输入非法,请重新输入\n");
						}
					}
					break;
				}
				PrintBoard(playBoard, ROW, COL);
				break;
			}
			default:
				printf("非法输入!!!请重新输入->\n");
				break;
			}
		} while (input);
	exit: printf("游戏退出\n");
	}
}

test.c主要是对整个游戏的逻辑进行实现,当中用到了goto语句和system(“cls”)清屏函数,主要是想对之前学过的进行运用,也可以通过其他方法实现;当玩完一局,会让用户输入Y/N决定是否在玩一局

二、子函数实现

(1)初始化棋盘

//初始化棋盘
void InitBoard(char Board[ROWS][COLS], int row, int col, char set)
{
	for (int i = 0;i < row;i++)
	{
		for (int j = 0;j < col;j++)
		{
			Board[i][j] = set;
		}
	}
}

因为用到了两个字符二维数组,一个是给用户看的(playBoard[ROWS][COLS]),另一个是我们设置雷用的(mineBoard[ROWS][COLS]),playBoard[ROWS][COLS]初始值为*,mineBoard[ROWS][COLS]初始值为’0’,数组长度定义为11*11,但实际上我们用到的只是1-9这个9**9的二维数组,这样设计的好处在于我们在计算周围八个位置的时候保证不会越界;

(2)打印棋盘

//打印棋盘
void PrintBoard(char Board[ROWS][COLS], int row, int col)
{
	printf("------扫雷游戏--------\n");
	for (int i = 0;i <=row;i++)
	{
		printf("%d ", i);//第一行加上0-9
	}
	printf("\n");
	for (int i = 1;i <= row;i++)
	{
		printf("%d ", i);//每一列开头分别加上1-9
		for (int j = 1;j <= col;j++)
		{
			printf("%c ",Board[i][j]);
		}
		printf("\n");
	}
}

为了用户输入方便,我们在行和列前加上数字;

(3)设置雷

/设置雷
void SetMine(char Board[ROWS][COLS], int row, int col)
{
	int count = Easy_Count;
	while (count)
	{
		int x = rand() % row + 1;//产生1-9的随机数
		int y = rand() % col + 1;//产生1-9的随机数
		if (Board[x][y] == '0')
		{
			Board[x][y] = '1';
			count--;
		}
	}
}

定义局部变量count初始值为雷的个数,每成功布置一个雷,count减1,直到雷全部布置好 count==0;布置雷的过程是利用随机数产生对应的坐标,所以不用担心坐标不合法;

(4)排查雷

//排查雷
void FindMine(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
	PrintBoard(playBoard, row, col);
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("请输入要排查的位置->(用空格隔开)\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标是否合法
		{
			if (playBoard[x][y] != '*')//判断该位置是否被排查过
			{
				printf("该位置已经排查过!!!请重新输入->\n");
			}
			else
			{
				if (MineBoard[x][y] == '1')//判断该位置是不是雷,是雷炸死
				{
					//排查的位置是雷
					printf("很遗憾,你被炸死了!\n");
					PrintBoard(MineBoard, row, col);
					break;
				}
				else
				{
					//排查的位置不是雷,显示周围八个有几个雷
					ExtendBoard(MineBoard, playBoard, row, col, x, y);
					break;
				}
			}
		}
		else
		{
			printf("坐标不合法!!!请重新输入->\n");
		}
	}
}

大概的过程说明在上面的代码注释中;

(5)计算该位置周围有几个雷

//计算周围有多少个雷
int GetMineCount(char Board[ROWS][COLS], int x, int y)
{
	return (Board[x - 1][y - 1] + Board[x - 1][y] + Board[x - 1][y + 1] + Board[x][y - 1] + Board[x][y + 1] + Board[x + 1][y - 1] + Board[x + 1][y] + Board[x + 1][y + 1] - 8 * '0');
}

可以用这种方式,也可以用循环,这里我用这种方式,下面展开时我用循环;

(6)爆炸展开一片

//展开一片
void ExtendBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col, int x, int y)
{
    //当我能调用到这个函数时,说明该位置一定不是雷,
    //其他八个位置递归时也能保证他们不是雷(因为上一层递归时坐标周围没有雷才能继续递归)
	if (x >= 1 && x <= row && y >= 1 && y <= col)//递归位置合法
	{
		int count = GetMineCount(MineBoard, x, y);
		if (!count)//该坐标周围没有雷
		{
			playBoard[x][y] = ' ';
			for (int i = x - 1;i <= x + 1;i++)
			{
				for (int j = y - 1;j <= y + 1;j++)
				{
					if (playBoard[i][j] == '*')//该位置没有被递归过
						ExtendBoard(MineBoard, playBoard, ROW, COL, i, j);
				}
			}
		}
		else//该位置周围有雷,结束递归
		{
			playBoard[x][y] = count + '0';
		}
	}
}

要清楚展开的条件(递归可以进行的条件)

(1)该坐标不是雷 MineBoard[x][y]!=‘1’
(2)该坐标周围没有雷 count==0
(3)该坐标没有被递归过 playBoard[x][y]=‘*’
(4)坐标合法 (x >= 1 && x <= row && y >= 1 && y <= col)

(7)标记雷

//标记雷
void MarkBrard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
	PrintBoard(playBoard, row, col);
	int x = 0;
	int y = 0;
	printf("请选择要标记的坐标->(用空格隔开)\n");
	scanf("%d %d", &x, &y);
	while (1)
	{
		if(x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
		{
			if (playBoard[x][y] == '*')//可以标记
			{
				playBoard[x][y] = '#';
				count2--;
				if (MineBoard[x][y] == '1')
				{
					count1++;
				}
				break;
			}
			else
			{
				printf("该位置已被标记过,请重新输入!!!\n");
			}
		}
		else//坐标不合法
		{
			printf("非法坐标!!!请重新输入!!!\n");
		}
	}
}

count1是用来确认标记是否正确,也就是说标记的是否是雷,后面用来判断胜利
count2是最多标记次数,等于雷的个数

(8)取消标记

//取消标记

void UnMarkBoard(char MineBoard[ROWS][COLS], char playBoard[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	PrintBoard(playBoard, row, col);
	printf("请输入要取消标记的坐标->(中间用空格隔开)\n");
	scanf("%d %d", &x, &y);
	while (1)
	{
		if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标合法
		{
			if (playBoard[x][y] == '#')
			{
				playBoard[x][y] = '*';
				count2++;
				if (MineBoard[x][y] == '1')
				{
					count1--;
				}
				break;
			}
			else
			{
				printf("该位置未曾标记,无法取消,请重新输入->\n");
			}
		}
		else//坐标非法
		{
			printf("非法坐标!!!请重新输入!!!\n");
		}
	}
}

取消标记主要用来让用户在标记错误的时候可以取消,增加了容错率,与标记的思想刚好相反

(9)判断胜利

int isWin(char playBoard[ROWS][COLS], int row, int col)
{
	for (int i = 1;i <= row;i++)
	{
		for (int j = 1;j <= col;j++)
		{
			if (playBoard[i][j] == '*')
			{
				win++;
			}
		}
	}
	if (win == Easy_Count)//剩余未排查的个数等于雷的个数,获胜
	{
		win = 0;
		return 1;
	}
	else if (count1 == Easy_Count)//将雷的位置全部标记,获胜
	{
		win = 0;
		return 1;
	}
	else return 0;
}

判断胜利的条件
(1)若剩余为排查的个数等于雷的个数,则排雷成功
(2)若将所有是雷的位置标记,则排雷成功
(3)以上条件不满足,游戏继续进行

三、运行结果

(1)正常排查

为了方便设置了80个雷,只有一个位置不是雷
扫雷游戏(C语言)_第1张图片

(2)标记雷获胜

扫雷游戏(C语言)_第2张图片

四、总结

该程序仍然存在许多不足,如下
(1)程序可玩性不高,不能让用户选择难度,要想改变棋盘大小和雷的个数,要通过修改程序中的宏定义完成,由于时间原因,后续会进行修改;
(2)主函数中有许多冗余代码,待改进
优点:除了无法选择难度,游戏功能实现较完善,可供初学者参考;

你可能感兴趣的:(c语言,游戏,c++)