初识C语言之扫雷游戏

项目思路

经过一段时间认真的学习和沉淀,今天我们今天来个写一个大项目,用c语言所学的知识写一个《扫雷游戏》,那《扫雷游戏》怎么写呢?让我们先来看一下网页上的《扫雷游戏》是怎么做的

图例1

首先《扫雷游戏》

                                1.得有一个扫雷的棋盘,怎么把棋盘显示出来

                                2.我们用什么样的方式来布置雷

                                3.排查雷后在周围有雷的情况下会返回数字,怎么返回

先解决第一条,扫雷的棋盘,我们可以用二维数组来搞定,往里面存字符,然后再打印出来,我们就先叫他show数组吧,最终的效果我们如图例2所示。

图例2

好,现在我们有了一个扫雷的棋盘,但是现在这个扫雷的棋盘只是一个空壳,里面是没有储存雷分布的信息的,那怎么办呢?

        那我们就再创建一个和扫雷棋盘一样大小的二维数组,这个数组是不打印出来的, 我们就先叫他mine数组,这个数组用来储存雷在棋盘上的分布情况,里面只放两种字符,‘1’和‘0’;字符‘1’,就表示该位置有雷,‘0’则表示没有雷,最后把mine数组和打印出来的棋盘进行链接,如图例3。

图例3

        但是现在又出现一个新的问题,如果扫雷的位置是在棋盘的边缘怎么办,如果还要继续硬扫那将会越界访问,这是我们不希望的,所以我们把数组的宽高都多加上2,在棋盘周围加上了一圈可访问的位置,这样就不会越界访问了。

代码实现

        主体程序的实现(text.c)

        讲完了思路后我们开始来写项目了,为了代码更加容易阅读,我们把这个项目划分出三个文件,分别是:game.c(函数的实现)、game.h(函数的声明,或者是叫汇总)、text.c(运行的程序),再说一下这个项目所能实现的功能:

使⽤控制台实现经典的扫雷游戏
游戏可以通过菜单实现继续玩或者退出游戏
扫雷的棋盘是9*9的格⼦
默认随机布置10个雷
可以排查雷
        ◦ 如果位置不是雷,就显示周围有几个雷
        ◦ 如果位置是雷,就炸死游戏结束
        ◦ 把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束
main主函数的实现

        首先我们写主函数内部的代码,一开始先创建input变量,用来储存选择的值;再创建一个do...while循环,一进到循环里打一个菜单,我们用mune();函数来实现,然后是根据输入的整型进行跳转,输入1进入游戏,输入0就结束程序,未定义的就重新输入

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

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

好,页面基本介绍完了,我们现在开始进入游戏函数中。

game函数系列的实现(game.c)

void game()
{
	//创建两个数组
	char mine[ROWS][COLS] = { 0 };//一个是数据源数组,初始化全放“0”
	char show[ROWS][COLS] = { 0 };//一个是棋盘数组,初始化全放“*”
	//初始化棋盘,把数组内的内容都定义为“0”
	//初始化函数
	Intboard(mine,ROWS,COLS,'0');//注意;是单引号(‘’),不是双引号("")
	Intboard(show,ROWS,COLS,'*');
	
	//布置雷
	checkboard(mine,ROW,COL);
	
	//打印扫雷棋盘
	Decboard(show, ROWS, COLS);
	//排查雷
	FindMine(mine, show, ROW, COL);
}

        一进入游戏函数中我们就先创建两个char类型的数组,两个数组的大小都是一样的,至于为什么,上面已经说了,这里就不多赘述了,你问我ROWS和COLS,这两个东西是啥?先别急,等会讲到。还有,创建完数组后记得给它们赋值,养成良好编码习惯。

        创建完数组后,下面就是一系列的函数运用了,在开头我们讲到,为个代码更容易看的懂,不会那么乱,我们把项目分为了三个文件,其中game函数中一系列的函数本体就都放在了game.c这个文件中,下面我们先来一一介绍一下这些函数

Intboard函数【初始化】

//初始化棋盘
void Intboard(char arr[ROWS][COLS], int rows, int cols,char set)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			arr[i][j] = set;
		}
	}
}

        该函数的作用就是来初始化函数的,它所要传的参数从左到右依次为(要初始化的数组,行数,列数,要初始化的字符)

 Decboard函数【打印】

//打印扫雷棋盘
void Decboard(char arr[ROWS][COLS], int rows, int cols)
{
	printf("----------扫雷游戏----------\n");
	//在棋盘打印之前先打印一行列编号,像个瓶盖一样,盖在棋盘上面
	for (int i = 0; i < rows-1; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (int i = 1; i < rows-1; i++)
	{
		//在每一行开始打印之前,先打印行编号
		printf("%d ", i);
		for (int j = 1; j < cols-1; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("\n");
	}
}

他的作用是来打印棋盘和一些界面装饰物的,它所要传的参数从左到右依次为(要打印的数组,打印行数,打印列数)打印效果如下图。

        

checkboard函数【布置雷】

//布置雷
void checkboard(char arr[ROWS][COLS], int row, int col)
{
	int count = esey_t;
	while (count)
	{
		int x = rand() % row + 1;//%row+1,把随机数的范围控制在1~9
		int y = rand() % col + 1;//% col + 1,也是一样
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			count--;
		}
	}
}

          该函数的作用是布置雷,  它所要传的参数从左到右依次为(要放雷的数组,数组的行数,数组的列数)

函数过程讲解

        一进入进入函数,创建变量count,作用是储存布置雷的个数,然后进入while循环,rand函数,生成随机数,%row+1和%col+1的作用是把布置雷的坐标控制在合理的范围内,两个随机数结合,就产生出了一个随机坐标,if语句,判断该数组的随机坐标是否被布置过雷,布置过则为‘1’,没布置过为‘0’,为‘0’就进入if语句,赋值为‘1’,count--,直到count值为0,跳出while循环

FindMine函数【排查雷】

//排查雷
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
	//坐标输入
	int x = 0;
	int y = 0;
	int win = 0;
	//有点缺陷,每次最多只能排查一个雷
	while (win < row*col - esey_t)
	{
		printf("请输入你要排查的坐标:>");
		scanf("%d %d", &y, &x);
		//坐标合法判断
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{ 
			//判断该坐标是否有雷
			if (mine[x][y] == '1')
			{
				printf("抱歉,你被炸死了\n");
				Decboard(mine,ROWS,COLS);
				break;
			}
			/*else if (mine[x][y] != '0')
			{
				printf("你干嘛!已经排查过了,请从新输入。");
			}*/
			else
			{
				int count = GetMineCount(mine, x, y);
				show[x][y] = count+ '0';
				Decboard(show,ROWS,COLS);

				//开挂
				//Decboard(mine, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("你在搞什么飞机!坐标非法请重新输入\n");
		}
	}
	if (win == row * col - esey_t)
	{
		printf("恭喜你,排雷成功\n");
		Decboard(mine, ROW, COL);
	}
}

        该函数的作用就是接收玩家输入的值,并根据规则给出相应的反应;它所要传的参数从左到右依次为(mine数组【存储雷信息的数组】,show数组【扫雷的棋盘数组】,数组的行数,数组的列数)

函数过程讲解

        进入函数,创建三个变量,x、y、win,x和y作用是储存玩家输入的坐标值;win记录次数,作为判断赢得游戏的条件;然后是while循环,判断条件我们等一下再讲,我们进入循环,首先是让玩家输入要排查雷的坐标,然后判断输入坐标的合法性,也就是要输入属于棋盘内的坐标参数,x和y都要大于等于1,且不能超过各自对应的行或列,如果输入不合法,打印语句,重新输入

        接下来就是判断有没有雷了,判断的条件就是判断mine数组【储存雷信息的数组】第y-1行,第x-1个内存入的字符是否为‘1’,如果结果为真,那是就踩到雷了,那就打印语句,打印mine棋盘,游戏结束。

 GetMineCount函数(返回周围有几个雷)

        若结果为假,那就证明没有踩到雷,那就排查周围雷的个数,并返回值到该坐标,说回程序,为假进入else,创建储存返回值的变量count,并赋值GetMineCount函数,那这个GetMineCount函数又是何方神圣呢?下面就是 GetMineCount函数函数的实现过程了  

int GetMineCount(char mine[ROWS][ROWS], int x, int y)
{
	return (mine[x-1][y-1] + mine[x-1][y] + mine[x-1][y+1] + mine[x][y-1] + mine[x][y+1] + mine[x+1][y-1] + mine[x+1][y] + mine[x+1][y+1] - 8 * '0');
}
函数讲解

        这个函数只有一行语句,就是返回了一大串的由mine数组元素相加的式子,那返回的这一大段式子有什么作用呢?原因如上图,因为有雷的坐标里面存放的字符是‘1’,没雷的坐标内存放的是字符‘0’,所以只要我们把x,y周围一圈坐标内的【字符值】ASCII值加起来再减去8个字符的‘0’ASCII值,得出来的数字就是排查坐标周围一圈雷的数量了,当然这个字符也是整型类型的。

如果还是不懂,推荐去b站看up主“比特鹏哥”讲解,链接如下扫雷详解_

        函数讲完了我们继续往下走,把count的值放到show数组中相应坐标当中,当然因为show数组的类型是char,所以还要在后面加上一个字符‘0’,打印出来的时候才会出现相对应的字符数组,后面就是打印出show数组,然后是win++,

再就是判断有没有获得胜利,若条件为真,则进入if,打印语句,打印mine【存放雷信息的数组】,

最后我们再来说game.h这个文件里的语句,

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//这个game.h是放定义的常量和自定义函数名的

//打印扫雷棋盘的大小
#define ROW 9
#define COL 9



//定义数组的大小
#define ROWS ROW+2
#define COLS COL+2

//雷的个数
#define esey_t 10
//初始化棋盘
void Intboard(char arr[ROWS][COLS], int rows, int cols, char set);
//布置雷
void checkboard(char arr[ROWS][COLS], int row, int col);
//打印棋盘
void Decboard(char arr[ROWS][COLS], int rows, int cols);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

首先是(ROW、COL)和(ROWS、COLS)四个常量,它们分别表示的是打印区域的行列大小和数组行列的真实大小,至于为什么?在上面的项目思路里有讲过,这里就不再讲了。然后是下面函数声明,作用就是更好的管理函数,节省开发时间。然后就是esey_t这个常量了,它的作用就是设置雷的个数,除了设置雷的个数外,在FindMine函数中的while循环判断表达式里有用到esey_t常量,

就如下面这堆代码一样,那这个判断表达式具体是什么意思呢?

int win = 0;
while (win < row*col - esey_t)
{

    ........
    
    {
        .....
        win++;
    }
}
if (win == row * col - esey_t)
	{
		//判断为真就获胜了
	}

首先win变量初始值为0,row*col表式整个棋盘可排查的坐标个数,esey_t有雷的坐标的个数,那“row*col - esey_t” 就表示,整个棋盘可排查的坐标个数 - 有雷坐标的个数 = 没有雷坐标的个数,排雷成功一次,没有雷的坐标就少一个,win++,win的变量值和排雷成功的次数相等,当排雷成功的次数等于没有雷坐标的个数时,那就证明所有没雷的坐标都排查过了,排雷成功,跳出while循环,进行后面的if语句判断,结果为真,进入语句,执行获胜语句。

结尾

        好了,这次的c语言项目到此就全部讲解完了,我会把这个项目的源码放在我的gitee仓库里,gitee里搜索“卡非熊”,就可以了。其实这也是我第一次写这么长的文章,如果有什么错误或者是不足的地方也欢迎在评论区指正出来,我是ahsjj,我们青山不改,绿水长流,下次见,拜拜!

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值