C语言小游戏:扫雷;展开、标记雷、第一步不死,踩到雷后游戏结束并且打印出雷区

电脑自带的扫雷游戏大家都玩过吧,首先呢我先简单介绍一下扫雷这个游戏是怎样玩的:
一开局是一个9*9的棋盘,系统随机布置一定个数的雷,然后玩家开始点击棋盘开始像下棋一样扫雷,第一次必定不死,不然就没法玩;或者是没有踩到雷,然后如果走的棋盘格子周围八个格子里都没有雷,那么直接展开成空格,然后继续扫描这八个格子各自周围的八个格子有没有雷,如果有雷就显示雷的个数;如果没有雷继续展开,直到展开的过程中某一个空格周围有雷,则停止展开并且显示雷的个数;当然了,如果踩到雷,那么游戏结束,并且打印出存放雷的信息的棋盘,让玩家直到雷都分布到了哪里
还有一个功能得加上,就是玩家每走一步,给个选择是否标记已经排查出来的雷,标记只是玩家自己排查的,所以说标记的格子是不是雷区,无法知道,并不是标记出系统自己分配的雷的位置
OK,接下来我想说一下这个小游戏的难点在哪里,当然,是我认为的难点,毕竟,每个人的看法不同嘛;
难点就只有一个,就是数组传参,你每一次使用被调函数都必须明确的知道你想要操作的棋盘是哪个;因为初始化的时候会初始化两个棋盘,一个9乘9的,一个11乘11的,因为当你在统计某个坐标周围雷的个数的时候需要统计周围八个格子,如果是9乘9的,那么当你统计边缘格子的时候会发生数组越界问题,所以可以再加一圈,到时候扫雷的话只在中间的9乘9的棋盘里操作就行,统计个数的时候也不会出现问题;所以,注意了,你每次传的实参和被调函数里的形参,以及他们所代表的含义,必须搞清楚,不然会乱套
其他的一些都基本问题不大;
首先呢,我们要创建一个专门存放函数声明跟头文件的game.h文件,这样显得思路清晰,其次呢,再创建一个test.c文件跟game.c文件,前者放一个测试函数即可,后者主要用来实现游戏功能

代码详细解释都在代码块里,大家可以按流程看,有什么问题可以在下面留言

下面是game.h文件,存放一些声明 :

#ifndef _GAME_H
#define _GAME_H
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define ROW 9//小棋盘行
#define COL 9//小棋盘列
#define ROWS ROW+2//大棋盘行
#define COLS COL+2//大棋盘列
#define COUNT 10//定义随机布置的雷的个数

void InitBoard(char board[ROWS][COLS], int rows, int cols, char ret);//初始化棋盘
void DisplayBoard(char board[ROWS][COLS], int rows, int cols);//打印棋盘
void SetMine(char board[ROWS][COLS], int row, int col);//布雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//扫雷
int GetMine(char a[ROWS][COLS], int x, int y);//获得周围八个格子雷的个数
void SpreadBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y);//展开函数
void RemarkBoard(char show[ROWS][COLS], int row, int col);//标记函数

void test();//测试函数
void game();//游戏功能实现
void menu();//菜单函数

#endif //_GAME_H

下面是test.c文件 :

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

//扫雷
//简单分析:首先要做一个9*9的棋盘,就是放雷的棋盘。可是,当你要排查雷的时候,肯定是要找某个坐标的周围
//八个坐标是不是雷,那么问题来了,如果是要排查棋盘边边上的坐标,排查的话,数组的下标肯定会越界
//所以可以在外边多打印一圈,置为0,排查的话就当做是没有雷对待;
//所以,初始化一个11*11的大棋盘,玩家跟电脑随机布置的雷都在这个小的棋盘里进行
//注意!!!:函数传参的时候注意传的是大棋盘还是小棋盘的参数!!!
int main()
{
	test();//游戏检测函数
	system("pause");
	return 0;
}

下面是game.c文件 :
这个文件里的函数太多,我分布描述吧,首先是第一个测试函数,功能:打印菜单;玩家选择开始或退出游戏;

void test()
{
	srand((unsigned int)time(NULL));//调用rand()函数之前调用的,srand是一个产生随机数种子的函数
	int input = 0;///time(NULL)的意思是以现在的系统时间作为随机的种子来产生随机数
	menu();/至于NULL这个参数,只有设置成NULL才能获得系统的时间
	printf("请选择1或者0开始或者结束游戏:->  ");
	do
	{
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏!\n");
			break;
		default:
			printf("输入错误,请重新输入! ->");
			break;
		}
	} while (input);
}
void menu()//打印菜单函数
{
	printf("*****************************************\n");
	printf("***************    1.play   *************\n");
	printf("***************    0.exit   *************\n");
	printf("*****************************************\n");
}

接下来就是游戏函数game()了
游戏函数里面包含了很多函数,我详细说一下:首先,定义两个二维数组,也就是大小棋盘;然后,初始化棋盘,一个棋盘将要存放雷的信息,不向玩家展示;另一个全部初始化为星号,准备向玩家展示;接下来随机布置雷;接着开始扫雷,扫雷函数里面还有几个函数,后面我接着解释吧;

void game()
{
	char mine[ROWS][COLS] = { 0 };//用来存放雷的信息
	char show[ROWS][COLS] = { 0 };//用来向玩家展示的棋盘
	InitBoard(mine, ROWS, COLS, '0');//mine棋盘81个字符全部初始化为字符0;
	InitBoard(show, ROWS, COLS, '*');//show棋盘全部初始化为字符‘*’(玩家看到的只有*)
	SetMine(mine, ROW, COL);//放入雷的信息,雷只保存在mine棋盘中;
	//DisplayBoard(mine, ROW, COL);正式游戏不必打印存放雷的信息;此条语句不用打印
	DisplayBoard(show, ROW, COL);
	printf("--------------------------------\n");
	FindMine(mine, show, ROW, COL);//开始扫雷
}

初始化棋盘

void InitBoard(char board[ROWS][COLS], int rows, int cols, char ret)//这里的形参rows\cols,区别:
//初始化是初始化11*11的棋盘,全部初始化为接收到的字符
{
	memset(&(board[0][0]),ret, rows*cols*sizeof(board[0][0]));//memset()函数需要#include<stdlib.h>这个头文件
}

打印棋盘,顺便看看棋盘有木有初始化好

void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘,row和col,用来确定打印的字符,仅仅在中间
//9*9的棋盘打印字符,所以这里接受9用row和col;
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for ( i = 1; i <= row; i++)
	{
		printf("%d", i);
		for ( j = 1; j <= col; j++)
		{
			printf(" %c", board[i][j]);
		}
		printf("\n");
	}	
}

接下来布雷;注意这是给哪个棋盘布雷(不给玩家展示的那个棋盘)

void SetMine(char board[ROWS][COLS], int row, int col)//布雷
{
	int x = 0;
	int y = 0;
	int count = COUNT;
	while (count)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (board[x][y] == '0')//每成功布下一个雷,count就自减一
		{
			board[x][y] = '1';
			count--;
		}
	}
}

下面这个是扫雷函数
我简单分析以下吧。系统随机布雷后,玩家就开始扫雷了;这个函数要具有这些功能:1、第一次不能被炸死 2、走的位置四周如果没有雷就 展开,有雷就显示雷的个数 3、每走一步都要选择是否继续走或者是标记一下;标记功能可以写一个函数,统计周围雷的个数也可以写一个函数

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//开始排雷
{
	int x = 0;
	int y = 0;
	int win = 0;
	int input = 0;
	printf("请输入要走的第一个坐标:");
	while (win<row*col-COUNT)//循环终止条件:扫出的雷等于布下的雷;
	{
		scanf("%d%d", &x, &y);
		printf("--------------------------------\n");
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1'&& win == 0)//此入口只有一个功能:第一次不被炸死;
				//第一次如果是雷,就随机在棋盘别的地方加一个雷,把当前坐标的雷改为无雷
				//这样雷的总数没变,随机分布也没变,第一次也没有炸死
			{
				int num = 1;
				while (num)
				{
					int i = rand() % row + 1;
					int j = rand() % col + 1;
					if (mine[i][j] == '0')
					{
						mine[i][j] = '1';
						num--;
					}
				}
				mine[x][y] = '0';
				SpreadBoard(mine, show,row,col,x,y);
				int count = GetMine(mine, x, y);
				show[x][y] = count + '0';
				DisplayBoard(show, ROW, COL);
				//DisplayBoard(mine, ROW, COL);//正式游戏不必打印存放雷的信息;此条语句不用打印
				printf("--------------------------------\n");
				do
				{
					printf("请选择1或者0选择标记雷点或者接着走下一步->\n");
					scanf("%d", &input);
						switch (input)
					{
						case 1:
							RemarkBoard(show, row, col);
							DisplayBoard(show, ROW, COL);
							break;
						case 0:
							printf("请选择走下一步 --> ");
							break;
						default:
							printf("输入错误,请重新输入! ->");
							break;
					}	
				} while (input);	
				win++;
			}
		    else if (mine[x][y] == '1')
			{
				printf("You was died !!!!\n");
				DisplayBoard(mine, ROW, COL);
				printf("请重新选择1或者0开始或者结束游戏:->  ");
				break;
			}
			else
			{
				//如果没有踩中雷,排查该坐标周围的雷
				int count = GetMine(mine, x, y);
				show[x][y] = count + '0';
				SpreadBoard(mine, show,row,col,x,y);
				DisplayBoard(show, ROW, COL);
				//DisplayBoard(mine, ROW, COL); 正式游戏不必打印存放雷的信息;此条语句不用打印
				printf("--------------------------------\n");
				//printf("请选择走下一步 --> ");
				do
				{
					printf("请选择1或者0选择标记雷点或者接着走下一步->\n");
					scanf("%d", &input);
					switch (input)
					{
					case 1:
						RemarkBoard(show, row, col);
						DisplayBoard(show, ROW, COL);
						break;
					case 0:
						printf("请选择走下一步 --> ");
						break;
					default:
						printf("输入错误,请重新输入! ->");
						break;
					}
				} while (input);
				win++;
			}
		}
	}
	if (win == row*col - COUNT)
	{
		printf("You are Winner!!!!!\n");
		DisplayBoard(show, ROW, COL);
		DisplayBoard(mine, ROW, COL);
		printf("--------------------------------\n");
		printf("请重新选择1或者0开始或者结束游戏:->  ");
	}
}

统计雷个数

nt GetMine(char a[ROWS][COLS], int x, int y)//函数GetMine()统计改坐标周围的雷的个数
{
	return
		a[x - 1][y - 1] + a[x - 1][y] + a[x - 1][y + 1]
		+ a[x][y - 1] + a[x][y + 1]
		+ a[x + 1][y - 1] + a[x + 1][y] + a[x + 1][y + 1] - 8*'0';
		//数字字符减去数字字符‘0’就等于对应的数字
}

棋盘展开

//展开函数SpreadBoard(),如果某个坐标周围没有雷,则展开包括改坐标在内的九个坐标
//然后,继续以该座标周围的八个没有雷的坐标为起点继续排查,如果周围八个坐标中,
//有雷存在,那么不展开,直接输出雷的个数;依次类推;
//这种思想为递归的思想,一层一层的展开;
//递归的终止条件就是count!=0输出雷的个数,就不会调用了;
void SpreadBoard(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col, int x, int y)
{
	int count = 0;
	count = GetMine(mine, x, y);
	int i = 0;
	int j = 0;
	if (count == 0)
	{
		show[x][y] = ' ';
		if (x + 1 <= row && y - 1 > 0 && show[x + 1][y - 1] == '*')
		{
			SpreadBoard(mine, show, row, col, x + 1, y - 1);
		}
		if (x + 1 <= row && show[x + 1][y] == '*')
		{
			SpreadBoard(mine, show, row, col, x + 1, y);
		}
		if (x + 1 <= row && y + 1 <= col && show[x + 1][y + 1] == '*')
		{
			SpreadBoard(mine, show, row, col, x + 1, y + 1);
		}
		if (y - 1 > 0 && show[x][y - 1] == '*')
		{
			SpreadBoard(mine, show, row, col, x, y - 1);
		}
		if (y + 1 <= col &&show[x][y + 1] == '*')
		{
			SpreadBoard(mine, show, row, col, x, y + 1);
		}
		if (x - 1 > 0 && y - 1 > 0 && show[x - 1][y - 1] == '*')
		{
			SpreadBoard(mine, show, row, col, x - 1, y - 1);
		}
		if (x - 1 > 0 && show[x - 1][y] == '*')
		{
			SpreadBoard(mine, show, row, col, x - 1, y);
		}
		if (x - 1 > 0 && y + 1 <= col && show[x - 1][y + 1] == '*')
		{
			SpreadBoard(mine, show, row, col, x - 1, y + 1);
		}
	}
	else
	{
		show[x][y] = count + '0';
	}
}
//标记函数RamarkBoard(),这个函数传参只传要打印给玩家的那个棋盘就行
//也就是初始化全是字符'*'的那个棋盘,玩家自己标记了后该坐标变成感叹号

标记函数;注意,这里标记的是玩家给看到的棋盘

//标记函数RamarkBoard(),这个函数传参只传要打印给玩家的那个棋盘就行
//也就是初始化全是字符'*'的那个棋盘,玩家自己标记了后该坐标变成感叹号
void RemarkBoard(char show[ROWS][COLS], int row, int col)//标记函数;
{
	int x = 0;
	int y = 0;
	int flag = 1;
	printf("请输入您要标记的坐标:-> ");
	scanf("%d %d", &x, &y);
	while (flag)
	{
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
		{
			if (show[x][y] == '*')
			{
				show[x][y] = '!';
				flag = 0;
			}
		}
		else
		{
			printf("输入错误,重新输入!");
		}
	}
}
//本程序还是有个bug:就是玩家可以继续走已经标记的那一步棋,游戏依旧会判断是否踩雷
//游戏依旧按照mine棋盘,也就是存储雷的信息的那个棋盘;
//这个bug......游戏未完待续......

到这里整个扫雷游戏基本上就算是完成了,各个功能都有了
下面是运行实例
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值