c语言写扫雷游戏(完整版详解,含展开)

c语言写扫雷是一个很不错的练习方式,接下来由我为大家介绍一下关于扫雷的代码

扫雷这款游戏的代码逻辑包括创建棋盘,打印棋盘,埋雷,找雷。

游戏规则:

在一个棋盘内排查地雷,但是地雷不会让玩家看到,并且每个地雷的位置都不重复,如果玩家踩到了地雷,则游戏结束并让玩家选择是否重新开始,如果玩家没有踩到雷并且该坐标周围也没有雷,则展开一片区域,并在这片区域的边缘标记上周围雷的数量,但是玩家不能走已走过的区域,玩家也不能输入在棋盘之外的坐标。最后还要判断游戏的输赢,如果除了有雷的位置其他全部都排查完毕,那么游戏获胜

注意!

注意!

注意!

多看注释!

如果你们对于下面的内容有什么不懂的地方可以提问,我尽量为你们解答

main函数内第一行先不要管,一会儿会讲,要使用一个do...while循环开始游戏,这样做的好处谁用谁知道,你还需要定义一个开始菜单,这个菜单的样式你们可以自己决定,如果你觉得玩完一把不过瘾想要再玩一把就一定要把它放在循环里,循环的判断条件要使用你自己定义的变量,我这里是input,用它接收你输入的值 ,再使用switch语句,输入1就进入game函数,输入0就退出,输入其他数字就接着循环。

​#include"mine.h"
void meau()
{
	printf("*******************\n");
	printf("****** 1.play *****\n");
	printf("****** 0.exit *****\n");
	printf("*******************\n");
}

​
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		meau();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				printf("扫雷\n");
				game();
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("输入错误,请重新输入\n");
				break;
		}
	} while (input);
	return 0;
}

game函数这里是写出游戏游戏逻辑的地方,以下是游戏的所有逻辑

void game()
{
	char mine[ROWS][COLS] = { 0 };
	char check[ROWS][COLS] = { 0 };
	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');//'0'和'*'表示mine和check棋盘分别被初始化为'0'和'*'
	InitBoard(check, ROWS, COLS, '*');
	
	//埋雷
	Burying_a_mine(mine,ROW,COL);
	//打印棋盘
	DisplayBoard(check, ROW, COL);
	DisplayBoard(mine, ROW, COL);//只打印中间的9*9的棋盘
	//排雷
	FineMine(mine, check, ROW, COL);
}

我把game里的函数的实现也都放在这里了,后面有关于这些函数的讲解

#include"mine.h"


void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)//set接收'0'和'*'
{
	int i = 0, j = 0;
	for (i = 0; i < rows; i++)//注意一下这两个for如果i<=rows就会导致栈损坏,也就是数组访问越界
	{
		for (j = 0; j < cols; j++)//同上
		{
			board[i][j] = set;
		}
	}
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;

	for (j = 0; j <= col; j++)
	{
		printf("%d ",j);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ",i);//打印纵坐标
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------扫 雷-------\n");
}

void Burying_a_mine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY_MINE;//要布置的雷的总个数
 
	while (count)
	{
		int x = rand() % row + 1;//+1的原因是rand()%row生成的余数为0—8,加上个1,就变成1—9了
		int y = rand() % row + 1;
		if (mine[x][y]=='0')//防止雷的位置放置重复
		{
			mine[x][y] = '1';//用字符1表示雷
			count--;//每放置一个雷就减1
		}
	}
}

int Number_of_mines(char mine[ROWS][COLS],char check[ROWS][COLS], int x, int y)
{
	int i = 0, j = 0;
	int count = 0;//记录周围雷的数量
	for (i = -1; i <= 1; i++)//遍历输入坐标的周围8个坐标
	{
		for (j = -1; j <= 1; j++)
		{
			if (mine[x - i][y - j] == '1')
				count++;
		}
	}
	return count;
}

void Spread_out_a_piece(char mine[ROWS][COLS], char check[ROWS][COLS], int x, int y)
{
	int ret = Number_of_mines(mine, check, x, y);
	if (ret == 0)//如果该坐标周围没有雷
	{
		int i = 0, j = 0;
		for (i = -1; i <= 1; i++)//遍历该坐标周围的8个坐标
		{
			for (j = -1; j <= 1; j++)
			{
				if ((x + i) > 0 && (x + i) < ROWS && (y + j) > 0 && (y + j) < COLS && check[x + i][y + j] == '*')//划定一个边界,并判断该坐标是否被排查过
				{
					check[x][y] = ' ';
					Spread_out_a_piece(mine, check, x + i, y + j);//创造一个新的起点
				}
			}
		}

	}
	else//如果有雷
		check[x][y] = ret + '0';
}

void FineMine(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0;
	int win = 0;
	int num = EASY_MINE;
	while (win < row * col - num)//判断输赢
	{

        win = 0;//防止win被重复计算
		for (int i = 1; i <=ROW; i++)//判断输赢,当所有的非雷格子全部被排查完
		{
			for (int j = 1; j <= COL; j++)
			{
				if (check[i][j] != '*')
				{
					win++;
					if (win == row * col - EASY_MINE)
						goto The_end;
				}
			}
		}
	sb:
		printf("请输入要排查的坐标:>");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//判断输入坐标是否在棋盘范围内
		{
			if (mine[x][y] == '1')//判断该坐标是否为雷
			{
				printf("恭喜你,你被炸死了\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			if (check[x][y] != '*')
			{
				printf("输入重复,请重新输入\n");
				goto sb;
			}
			int count = Number_of_mines(mine,check, x, y);//判断改坐标周围雷的数量
			//显示出该坐标周围地雷的数量,并转换为字符
			check[x][y] = count + '0'; 
			Spread_out_a_piece(mine, check, x, y);//每次输入坐标后如果周围没有地雷,就展开一片区域
			//注意!!!上面的两个表达式
			DisplayBoard(check, ROW, COL);//每次输入坐标后就更新一次棋盘
			win++;
		}
		else
			printf("坐标不合法,请重新输入\n");
	}
	if (win == row * col - EASY_MINE)//设置这个条件就是单纯的不让别人进来,例如说踩雷的那个,跳出循环后不让它进入
	{
		The_end:
		printf("很遗憾,你排雷成功了\n");
		DisplayBoard(mine, ROW, COL);//获胜之后给玩家看一下雷的情况
	}
}

这里是所有的头文件

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_MINE 10

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void Burying_a_mine(char mine[ROWS][COLS], int row, int col);
void FineMine(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col);

接下来我会把他们分开一一去讲,不要忘了引用你的头文件呦

数组定义

这一步也可以叫做创建棋盘,在创建棋盘的时候,你要确定棋盘的大小,棋盘的样式,和棋盘要存储的东西。同时你还要保证你排雷时不会越界

char mine[ROWS][COLS] = { 0 };
char check[ROWS][COLS] = { 0 };
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2

mine这个数组是存放地雷的数组,而check这个数组则是展示给玩家看的,ROW和COL是数组展示出来的大小,这里使用了宏定义,方便以后修改,而把他们定义成11*11的则是因为排雷的时候要防止越界,因为排雷的时候需要遍历你输入坐标周围的8个格子,如果你输入(1 ,1)那么该坐标左边的和上边的三个格子就会越界

初始化棋盘

//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');//'0'和'*'表示mine和check棋盘分别被初始化为'0'和'*'
	InitBoard(check, ROWS, COLS, '*');
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)//set接收'0'和'*'
{
	int i = 0, j = 0;
	for (i = 0; i < rows; i++)//注意一下这两个for如果i<=rows就会导致栈损坏,也就是数组访问越界
	{
		for (j = 0; j < cols; j++)//同上
		{
			board[i][j] = set;
		}
	}
}

我想要把mine数组初始化为'0'所以把字符0传递过去,而check数组我想要把它初始化为'*',所以要把'*'传递过去。而InitBoard函数实现这里就需要创建一个char类型的变量set用于接收'0'和'*'这两个字符,这样可以使用一个函数初始化两个数组。下面使用两个for循环把这两个数组全部初始化。后面有一个加上打印棋盘后的整体效果图

打印棋盘

打印棋盘时我们只打印中间的9*9,外面那一圈是为了防止越界而准备的

DisplayBoard(check, ROW, COL);
DisplayBoard(mine, ROW, COL);//只打印中间的9*9的棋盘

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;

	for (j = 0; j <= col; j++)//打印横坐标
	//j如果不从0开始,那么棋盘左上角就会空出一格,很不好看
    {
		printf("%d ",j);
	}
	printf("\n");//打印完横坐标就换行
	for (i = 1; i <= row; i++)
	{
		printf("%d ",i);//打印纵坐标
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
   //因为初始化时,是初始化成了字符,所以这里要用%c来打印,为了美观一些%c后面最好加一个空格
		}
		printf("\n");//每打印完一行就换行一次
	}
	printf("-------扫 雷-------\n");//为了美观而设计的
}

这里传输参数的时候,我们传递了一个11*11的数组,但是后面的参数却只传输了9*9的数据,因为我们只操作中间那9*9的格子,而不是操作那11*11的,11*11它只是防止我们越界的。还是需要一个嵌套的for循环来打印我们的棋盘。

 效果图:

 布置地雷

布置地雷也只布置在中间的那9*9里,并且你还要防止地雷布置重复

//埋雷
	Burying_a_mine(mine,ROW,COL);
void Burying_a_mine(char mine[ROWS][COLS], int row, int col)
{
    int count = EASY_MINE;//要布置的雷的总个数
    while (count)
    {
        int x = rand() % row + 1;//+1的原因是rand()%row生成的余数为0—8,加上个1,就变成1—9了
        int y = rand() % row + 1;//说一下srand放的地方和原因
        if (mine[x][y]=='0')//防止雷的位置放置重复
        {
            mine[x][y] = '1';//用字符1表示雷
            count--;//每放置一个雷就减1
        }
    }
}
#define EASY_MINE 10

EASY_MINE是需要布置的雷的总数量,在头文件里定义即可,rand%row+1就规范了设置雷的范围,把它放在循环里是因为每次循环都要随机布置一次雷

还记得我在开头说的那句话吗,这里的rand()对应的正是main函数里的srand(),因为如果想要使用rand就必须先使用srand,然后在srand里使用时间戳设置随机数,time(time里面是空指针)是设置随机数生成的函数,想要使用rand,srand和time就必须先引用他们的头文件,就是下面那俩,因为这个main函数一局游戏只会使用一次所以把srand设置在那里

srand((unsigned int)time(NULL));
#include<time.h>
#include<stdlib.h>

排雷

排雷的这个就比较复杂了,但也没有这么难,你要把你输入的坐标划定在棋盘之内和如果不在范围内就怎样,还要防止坐标输入重复,以及如果踩雷就退出游戏

//排雷
	FineMine(mine, check, ROW, COL);




void FineMine(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0;
	int win = 0;
	while (1)
	{
        win = 0;//防止win被重复计算
		for (int i = 1; i <=ROW; i++)//判断输赢,当所有的非雷格子全部被排查完
		{
			for (int j = 1; j <= COL; j++)
			{
				if (check[i][j] != '*')
				{
					win++;
					if (win == row * col - EASY_MINE)
						goto The_end;
				}
			}
		}
	sb:
		printf("请输入要排查的坐标:>");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//判断输入坐标是否在棋盘范围内
		{
			if (mine[x][y] == '1')//判断该坐标是否为雷
			{
				printf("恭喜你,你被炸死了\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			if (check[x][y] != '*')
			{
				printf("输入重复,请重新输入\n");
				goto sb;//输入重复就会回去重新输入
			}
			int count = Number_of_mines(mine,check, x, y);//判断改坐标周围雷的数量
			//显示出该坐标周围地雷的数量,并转换为字符
			check[x][y] = count + '0'; 
			Spread_out_a_piece(mine, check, x, y);//每次输入坐标后如果周围没有地雷,就展开一片区域
			//注意!!!上面的两个表达式
			DisplayBoard(check, ROW, COL);//每次输入坐标后就更新一次棋盘
			win++;
		}
		else
			printf("坐标不合法,请重新输入\n");
	}
	if (win == row * col - num)
	{
		printf("很遗憾,你排雷成功了\n");
		DisplayBoard(mine, ROW, COL);//获胜之后给玩家看一下雷的情况
	}
}

先介绍最初始的这个,稍后介绍里面的两个函数。 Number_of_mines(mine,check, x, y)函数的作用是判断改坐标周围雷的数量,Spread_out_a_piece(mine, check, x, y)函数的作用是展开

这里传递参数要把两个数组都传递过来,因为我们要在mine数组里找雷,在check数组里展示

先定义两个变量,这是你的坐标,之后既然是排雷,而你又不可能一下子就排完吧,所以要用一个循环,先用一个if语句划定你输入坐标的范围,把它划定再你的棋盘之内,再判断输入坐标是否为雷,是雷就重新来过,不是就接着执行下一步,也要判断是否输入重复。在Number_of_mines函数下面的那个表达式是,接收count的返回值并把数字转换为数字字

排查坐标周围是否有雷

然后就是Number_of_mines这个函数

int Number_of_mines(char mine[ROWS][COLS],char check[ROWS][COLS], int x, int y)
{
	int i = 0, j = 0;
	int count = 0;//记录周围雷的数量
	for (i = -1; i <= 1; i++)//遍历输入坐标的周围8个坐标
	{
		for (j = -1; j <= 1; j++)
		{
			if (mine[x - i][y - j] == '1')
				count++;
		}
	}
	return count;//注意这里返回的是数字,前面打印用的是%c,所以还要转换为字符
}

也是一个嵌套的for循环使用if判断周围8个坐标是否有雷,如果有就count+1最后再返回count的值。

展开

再接着就是Spread_out_a_piece这个展开函数,这里是我认为在整个排雷过程中最重要的内容

void Spread_out_a_piece(char mine[ROWS][COLS], char check[ROWS][COLS], int x, int y)
{
    int ret = Number_of_mines(mine, check, x, y);
    if (ret == 0)//如果该坐标周围没有雷
    {
        int i = 0, j = 0;
        for (i = -1; i <= 1; i++)//以该坐标为起点重新判断周围是否有雷
        {
            for (j = -1; j <= 1; j++)
            {
                if ((x + i) > 0 && (x + i) < ROWS && (y + j) > 0 && (y + j) < COLS && check[x + i][y + j] == '*')//划定一个边界(1——10),并判断该坐标是否被排查过
                {
                    check[x][y] = ' ';//作用是标记该坐标已被展开过
                    Spread_out_a_piece(mine, check, x + i, y + j);//创造一个新的起点
                }
            }
        }

    }
    else//如果有雷
        check[x][y] = ret + '0';//标出周围雷的数量
}

先创建一个变量ret用于接收Number_of_mines函数返回的值,再使用if语句判断ret是否等于0(等于0就证明该坐标周围没有雷),如果等于0就进入。再创建两个变量用于遍历该坐标周围的八个坐标是否为'*',如果为'*'就说明未被排查过,然就就是让check数组的该坐标变为空格,读者~你也不想让你的栈溢出吧~

再者就是使用递归了一定要把Spread_out_a_piece函数里的x,y变为x+i,y+j一定!一定!一定!我就搁这吃过亏,我甚至把x+i,y+j写成过x+1,y+1我检查了好久才发现,他们长得实在是太像了,最后就是else把ret不等于0时的情况写好

check[x][y] = count + '0'; 
Spread_out_a_piece(mine, check, x, y);//每次输入坐标后如果周围没有地雷,就展开一片区域
			//注意!!!上面的两个表达式

这个把数字转换为数字字符的表达式与Spread_out_a_piece函数的先后顺续也很有讲究,如果你把Spread_out_a_piece放前面,把check[x][y] = count + '0';后面的话,我想你也不想看到下面对的 

情况发生吧~桀桀桀~ 

判断输赢

因为棋盘是9*9的,如果想要赢就需要把9*9-10,也就是71个无雷的格子全部都排查完,使用嵌套的for循环排查所有的格子,就像前面的打印一样,这里你打印了多少个格子你就要排查多少个格子


		win = 0;//防止win被重复计算
		for (int i = 1; i <=ROW; i++)//判断输赢,当所有的非雷格子全部被排查完
		{
			for (int j = 1; j <= COL; j++)
			{
				if (check[i][j] != '*')
				{
					win++;
					if (win == row * col - EASY_MINE)
						goto The_end;
				}
			}
		}
if (win == row * col - EASY_MINE)//设置这个条件就是单纯的不让别人进来,例如说踩雷的那个,跳出循环后不让它进入
	{
		The_end:
		printf("很遗憾,你排雷成功了\n");
		DisplayBoard(mine, ROW, COL);//获胜之后给玩家看一下雷的情况
	}

对于以上的内容如果有什么不懂的地方可以提问,我尽量为你们解答

以上就是我为大家带来的关于用c语言写扫雷的全部内容了,感谢大家收听...咳...不,观看

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值