ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[c语言]--实现扫雷小游戏

2021-09-19 18:33:28  阅读:194  来源: 互联网

标签:ROWS -- COLS char int 小游戏 扫雷 boom printf


开门见山

 给出我在写这篇代码时的思维导图,这篇博客也是根据思维导图写的

 

 

游戏结果展示

 //因为本人有点小菜,所以就设置4*4 -->4的规格,即16格子找出4个雷以演示游戏代码,想挑战高难度的小伙伴可自行设置.那么就见笑了

 

游戏思路分析

先展示本次编写代码所用到的头文件和源文件展示,方便后续阅读

test.c用于测试编写的扫雷代码相关功能即对game.c中的函数进行调用
game.h用于对扫雷代码所涉及的主要函数及参数的声明
game.c代码的主体,对game.h中声明的函数进行定义

和三子棋(见前篇)一样,应该有个菜单,你觉得怎么好看就怎么设置----test.c

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

本来menu()应该属于game.c但是由于menu()本来就很简单,就没必要和game.c中的大头"争宠"了

布置雷

先看一张图

 

 

两个问题1.雷该放到哪呢?2.雷该用什么表示呢?

答:1.雷应该放到一个字符数组,不妨设为boom[ROWS][COLS]

2.雷用'1'表示,非雷用'0'表示,注意这里的'1'和'0'是字符1和0. 为什么选择用'1'和'0'?文末总结部分会详细给出

ps:这里的ROWS和COLS对应上图的11行11列,即橙色框.有的小伙伴就会问了我的棋盘不是9*9的嘛,设置成11*11岂不是浪费空间!实则不然,这样设置就可以避免产生数组越界的问题.何出此言?

我们看上面的绿色框,它的作用是去排查(x,y)周围八个格子的字符1(雷)的个数,排查(8,8)当然不会有数组越界,那我要是去排查(1,1) (1,2)...(9,9)也就是最外围的那32个格子,会出现什么情况?绿色框会访问不属于红色框的内容,(不恰当的比喻:你去拿了不该属于你的东西)这当然是不行的.计算机会无情的告诉你error.所以呢,我们应该设置为11*11的,最外围那一圈的元素可以不用,但不能没有!我们只操作中间的9*9就行了.

 排查雷 

 

像上图的绿色框,排查了(8,8),周围有3个雷,排查出来了该怎么办呢?是不是该把这个"3"存起来呀.怎么存呢?当然也是用数组咯.

这时候就有小伙伴跃跃欲试了:int show[ROWS][COLS],创建了一个整形数组;

那我只能说:你这波啊,还在第一层.且往下看

我打算用char show[ROWS][COLS],一个字符类型的数组.为什么要这么设计?这里先卖个关子,在函数定义部分会给出原因.

ok,以上便是我们的思路分析了,真正的大头来了

 

思路程序化

第一步当然是编写我们的test.c了

//游戏测试模块

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

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

void game()
{
	//关于雷的信息存储
	//1. 创建一个数组来存放雷
	char boom[ROWS][COLS] = { 0 };
	//2. 创建一个数组来显示排查出的雷
	char show[ROWS][COLS] = { 0 };
	//初始化两个数组
	InitBoard(boom, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	//打印棋盘
	//DisplayBoard(boom, ROW, COL);
	DisplayBoard(show, ROW, COL);//
	//布置雷
	Setboom(boom, ROW, COL);
	//DisplayBoard(boom, ROW, COL);
	//扫雷
	Findboom(boom, show, ROW, COL);
}

void test()
{
	int choice = 0;
	srand((unsigned int)time(NULL));//时间戳

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &choice);
		switch (choice)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,重新选择!\n");
			break;
		}
	} while (choice);
}

int main()
{
	test();
	return 0;
}

 可以发现是不是很精简且简单

main()调用了test(),test()调用了menu()如果输入1,test()又调用了game(),game()又调用了一系列的函数.然后我们才能玩到扫雷游戏.

细心的小伙伴会发现里面有个  #include "game.h"      这其实是头文件名的一种形式,只不过这个是我们自己定义的罢了,不像 #include<stdio.h>  #include<math.h> 这类是vs自带的.你只需要记住:自己定义的头文件要引用的话要加   "  "

函数要"先声明后使用"

我们来看看game.h里面有什么

    1 	//函数声明模块
    2 	//一些数据的全局定义,方便后续修改
    3 	
    4 	
    5 	
    6 	#define ROW 9
    7 	#define COL 9
    8 	
    9 	#define ROWS ROW+2
   10 	#define COLS COL+2
   11 	//雷的数量
   12 	#define BOOM_COUNT 10
   13 	
   14 	#include <stdio.h>
   15 	#include <stdlib.h>
   16 	#include <time.h>
   17 	//函数返回类型   函数名   函数参数
   18 	void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
   19 	void DisplayBoard(char board[ROWS][COLS], int row, int col);
   20 	void Setboom(char board[ROWS][COLS], int row, int col);
   21 	void Findboom(char boom[ROWS][COLS], char show[ROWS][COLS], int row, int col);

18-21行这些函数是game()反复调用的,声明已经有了,还差定义,我们把他们定义在game.c里面

下面一一介绍

InitBoard()

将数组初始化为你想要的内容

InitBoard() 

返回类型 viod

参数  一个char类型数组,两个整形数,一个字符数

还记得上面卖的关子吗,我把show[][]也定义为char类型,现在来告诉你答案,我把show[][]定义为和boom[][]一样的char类型,就是为了,让InitBoard()一个函数操作两个数组,使代码更精简,避免了我要因为初始化show[][]去重新定义另一个函数

下面的DisplayBoard()也是同样的道理

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)

{

        int i = 0;

        int j = 0;

        for (i = 0; i < rows; i++)

        {

                for (j = 0; j < cols; j++)

                {

                        board[i][j] = set;

                }

        }

}

DisplayBoard()

            打印数组内容

void DisplayBoard(char board[ROWS][COLS], int row, int col)

{

        int i = 0;

        int j = 0;

        //打印列号

        for (i = 0; i <= col; i++)

        {

                printf("%d ", i);

        }

        printf("\n");

        for (i = 0; i <= col; i++)

        {

                printf("--");

        }

        printf("\n");

        //打印行号

        for (i = 1; i <= row; i++)

        {

                printf("%d", i);

                printf("|");//紧接着打印数组内容

                for (j = 1; j <= col; j++)

                {

                        printf("%c ", board[i][j]);

                }

                printf("\n");

        }

}

Setboom()

布置雷->把boom[][]里对应坐标换成字符1 '1'

随机设置雷的位置

void Setboom(char board[ROWS][COLS], int row, int col)

{

        int count = BOOM_COUNT;

        while (count)

        {

                int x = rand() % row + 1;//让x范围是1-9

                int y = rand() % col + 1;//让y范围是1-9

                if (board[x][y] == '0')

                {

                        board[x][y] = '1';

                        count--;

                }

        }

}

Findboom()

找出boom[x][y]周围的字符1 '1'   并把找到的'1'的个数存到show[x][y]

//去找我们想排查的那个坐标周围的8个格子中雷的数量                      

int get_boom_count(char boom[ROWS][COLS], int x, int y)

{

       

        return     boom[x - 1][y] +

                   boom[x - 1][y - 1] +

                   boom[x][y - 1] +

                   boom[x + 1][y - 1] +

                   boom[x + 1][y] +

                   boom[x + 1][y + 1] +

                   boom[x][y + 1] +

                   boom[x - 1][y + 1] - 8 * '0';

}

void Findboom(char boom[ROWS][COLS], char show[ROWS][COLS], int row, int col)//两个数组,相同的坐标,一个表示雷,一个表示坐标周围的雷的个数

{

        int x = 0;

        int y = 0;

        int rest_blank = 0;//用来表示剩余的不是雷的格子的个数

        

        while (rest_blank < row * col - BOOM_COUNT)

        {

                printf("请输入要排查的坐标:>");

                scanf("%d%d", &x, &y);

                //这个是时候应该先判断坐标合法性,即要排查的坐标应该在9*9的格子的范围

                if (x >= 1 && x <= row && y >= 1 && y <= col)

                {

                        //坐标合法

                        //1. 踩雷

                        if (boom[x][y] == '1')

                        {

                                printf("很遗憾,你被炸死了\n");

                                //起码要让玩家"死得明白"

                                DisplayBoard(boom, row, col);//展示一下雷的分布

                                break;

                        }

                        else //不是雷

                        {

                                //计算x,y坐标周围有几个雷

                                int count = get_boom_count(boom, x, y);

                                //把找到的雷的个数存到show数组里

                                show[x][y] = count + '0';//一个整数转换为对应的字符加上'0' 如 3+'0'=='3'

                                DisplayBoard(show, row, col);

                                //找到一个雷rest_blank就自增

                               rest_blank++;

                        }

                }

                else

                {

                        printf("输入坐标非法,请重新输入!\n");

                }

        }

        //判断一下是否等于所有非雷的格子的数量

        if (rest_blank == row * col - BOOM_COUNT)

        {

                printf("恭喜你,排雷成功\n");

                DisplayBoard(boom, row, col);

        }

}

最后来看一下game.c的内容其实就是上面几个函数的汇总

//函数定义模块
#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	//打印列号
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 0; i <= col; i++)
	{
		printf("--");
	}
	printf("\n");
	//打印行号
	for (i = 1; i <= row; i++)
	{
		printf("%d", i);
		printf("|");//紧接着打印数组内容
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}
                                                                  
void Setboom(char board[ROWS][COLS], int row, int col)
{
	int count = BOOM_COUNT;
	while (count)
	{
		int x = rand() % row + 1;//让x范围是1-9
		int y = rand() % col + 1;//让y范围是1-9
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

//去找我们想看的那个坐标周围的8个格子中雷的数量                       
int get_boom_count(char boom[ROWS][COLS], int x, int y)
{
	
	return boom[x - 1][y] +
		   boom[x - 1][y - 1] +
		   boom[x][y - 1] +
		   boom[x + 1][y - 1] +
		   boom[x + 1][y] +
	   	   boom[x + 1][y + 1] +
		   boom[x][y + 1] +
		   boom[x - 1][y + 1] - 8 * '0';
}
//
void Findboom(char boom[ROWS][COLS], char show[ROWS][COLS], int row, int col)//两个数组,相同的坐标,一个表示雷,一个表示坐标周围的雷的个数
{
	int x = 0;
	int y = 0;
	int rest_blank = 0;//用来表示剩余的不是雷的格子的个数
	
	while (rest_blank < row * col - BOOM_COUNT)
	{
		printf("请输入要排查的坐标:>");
		scanf("%d%d", &x, &y);
		//这个是时候应该先判断坐标合法性,即要排查的坐标应该在9*9的格子的范围
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//坐标合法
			//1. 踩雷
			if (boom[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				//起码要让玩家"死得明白"
				DisplayBoard(boom, row, col);//展示一下雷的分布
				break;
			}
			else //不是雷
			{
				//计算x,y坐标周围有几个雷
				int count = get_boom_count(boom, x, y);
				//把找到的雷的个数存到show数组里
				show[x][y] = count + '0';
				DisplayBoard(show, row, col);
				//找到一个雷rest_blank就自增
				rest_blank++;
			}
		}
		else
		{
			printf("输入坐标非法,请重新输入!\n");
		}
	}
	//判断一下是否等于所有非雷的格子的数量
	if (rest_blank == row * col - BOOM_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(boom, row, col);
	}
}

ok,到这大头部分就讲完了

总结注意事项

#define _CRT_SECURE_NO_WARNINGS 1

这其实是为了让编译器在编译scanf时不报错,因为一些编译器(比如我自用的VS2019)会认为scanf不安全,建议你用scanf_s.但是我不听它的建议,我就要用scanf,就在代码前面加上上面那句就行了

关于雷的设置问题

充分利用数字的属性, '1'表示雷, 2表示周围有2个雷 这是实实在在的2,

'2'可以作为数组元素被打印出来,让我们在视觉上认为周围有两个雷,这是字符'2',却有了数字的属性.

可能有点绕,哈哈哈,但应该理解的过来

关于数组类型设置的问题

其实一开始我也没想到把show[][]设置为char类型,我是在编写InitBoard()的时候,突然想到的.我去初始化两个数组,能不能用一个函数啊,但是一个函数只能初始化一个类型的数组,且初始化对象和初始化内容必须是同一种类型(我不想强制类型转换嗷)

初始化 char 类型的boom[][]为 '0' (开始的时候应该没有雷) 要一个函数

初始话 int 类型的show[][] 为 '*' (一开始展示给我们看的应该全部是*,要把雷的位置遮起来,就不让你看,哼) 又有点行不通

所以就干脆把show[][]也定义为char类型,这样一来,数组类型和初始化内容的类型不就统一了嘛.

而且DiaplayBoard()也可以不用为类型发愁

游戏中的不足之处

1.在数周围8个格子的时候,get_boom_count()可以用循环,我为什么不用呢,因为直观,小伙伴们更容易理解

2.在排查雷的时候,不能展开一片.感兴趣的小伙伴可以用递归

排查boom[x][y]周围8个格子--> 1.是雷,就计数存到show[x][y] 2.不是雷,排查它周围的八个格子

到外围的一圈都是雷为止(其实我也不太懂扫雷的游戏规则).为了避免一些格子的重复排查,可以定义一个数组专门来表示某个格子是否被排查过.数组初始化为0,只要排查过,对应坐标就赋值为1.

3.用递归求斐波那契额数列,体现了上述思想,可以看我写的另一篇博客(算是波小广告啦)https://blog.csdn.net/HandsomeDog_L/article/details/120256097

小tips

建议小伙伴在写这种类型的代码时能有一个清晰的思路,怎么才能办到呢-------思维导图

当然大佬除外

上一篇写的三子棋也是有思维导图的,忘发出来了,我把它放到三子棋评论区吧,感兴趣的小伙伴可以去看看

由于编者水平实在有限,有什么错误之处望指正,非常感谢您的反馈

回见啦!

标签:ROWS,--,COLS,char,int,小游戏,扫雷,boom,printf
来源: https://blog.csdn.net/HandsomeDog_L/article/details/120379930

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有