【C语言实战项目】扫雷游戏

目录

一.了解扫雷游戏

二.分析游戏实现逻辑

三.逐步实现游戏及其逻辑详解

1.实现菜单功能:

2.实现游戏可循环玩:

3.初始化地图:

4.打印地图:

5.埋放地雷:

6.排查地雷:

四.整合代码测试及总结

game.c:

game.h:

test.c:


一.了解扫雷游戏

众所周知,扫雷是一项军事行动的代称,是指搜索和清除地雷、水雷及其他爆炸物的行动...哈哈,开玩笑的啦!扫雷使我们快乐:>!我们今天来学习如何使用C语言编写一个简易的扫雷游戏,如果还有不太了解扫雷游戏的同学推荐在https://minesweeper.online/cn扫雷游戏网站上了解扫雷的游戏规则,也可以在上面选择一个难度玩上几局体验一下。那我们废话不多说,下面开始我们扫雷之旅》》》

二.分析游戏实现逻辑

要编写一个游戏程序,首先要明确我们想要达到的效果是什么样,下面我将用vs2022编译器来为大家演示一下游戏运行时的样子:

首先,我们来到菜单界面,提醒用户选择玩游戏还是退出游戏


 当玩家选择'0'时,退出游戏,结束程序


玩家选择'1'时,开始游戏,并打印地图提醒玩家输入要排查的雷的坐标


玩家输入要排查的坐标时,如果该坐标下没有埋放雷则该坐标被排查并显示周围8个格子中雷的数量,如下图被排查坐标显示‘0’的意思即附近8个格子中雷的数量为‘0’


 当玩家输入要排查的坐标时,如果该坐标下埋放了雷,则玩家被炸死游戏结束并打印出该局游戏中所有雷的方位

 


 当玩家成功排查出所有的雷时,游戏胜利,游戏结束


 注意,当玩家输入排查过的坐标时,提醒玩家已排查过,重新输入


 当玩家输入地图外坐标时,提醒玩家坐标非法,重新输入


三.逐步实现游戏及其逻辑详解

      通过第二部分对流程的介绍,我们已经对游戏的流程有了大致的了解,虽然看似需要实现的功能很多,貌似一时间不知该如何下手,但我们可以分布分模块来分析这个游戏的流程,最后再将各各部分进行整合,所以大家不用担心,跟着我一步一步分析吧!


!!!注意,该部分的代码只是为了详细介绍某一部分的游戏实现逻辑,故可能会删减一些与该部分不相关的代码以便大家理解,需要查看完整详细代码可以移步本文第四部分。


1.实现菜单功能:

       菜单部分的逻辑比较简单,就是利用C语言printf函数打印出这个菜单界面即可。基础问题就不过多赘述了,代码如下:

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

2.实现游戏可循环玩:

      由于我们要实现玩不够可以继续玩的游戏逻辑,因此选择do...while的循环语句来实现这一部分的逻辑,每步的详细解释见代码注释:

void menu()//菜单函数实现打印菜单
{
	printf("*********************************************\n");
	printf("*********************************************\n");
	printf("*****************   1.play   ****************\n");
	printf("*****************   0.exit   ****************\n");
	printf("*********************************************\n");
	printf("*********************************************\n");
}

void test()//测试游戏运行
{
	//srand((unsigned int) time(NULL));
    //利用时间戳生成随机数以达到电脑可以实现随机布雷

	int input = 0;

	do//使用do  while语句来实现游戏可以连续一直玩
	{
		menu();//菜单函数,打印菜单供玩家选择

		printf("请选择:>\n");//提醒玩家选择

		scanf("%d", &input);//用scanf接收玩家选择存入变量input中

		switch (input)//利用分支语句实现玩家的选择
		{
		case 1://当玩家输入1,运行游戏
			game();
			break;
		case 0://当玩家输入0,提醒玩家游戏结束
			printf("游戏结束\n");
			break;
		default://当玩家输入了非选项数字时,提醒玩家重新输入
			printf("输入错误,请重新选择\n");
			break;
		}

	} while (input);//用变量input的值作为while循环的判定执行条件
                    //当input不为0时,该循环都可一直运行下去。
}

3.初始化地图:

       实现了打印菜单供玩家选择和一直玩后,我们就要来到游戏的核心部分了,即第一步,由于我们是利用二维数组实现在“地图”上排雷的,因此当每局游戏开始时,我们应该先将地图全部初始化为‘0’(注意我们创建的是字符数组,因此0是字符0!),以便在后续埋雷时与雷做区分。

       如图,我们先来分析一下一个地图上的格子在一局游戏中会有几种状态

      ①初始时代表未解密的"*"②埋雷时与‘1’(雷)区别的”0“③安放雷时代表雷的”1“④被排查之后代表周围雷数的数字”n“

      即一个格子最多可能会有四种状态,而我们一个二维数组要写兼顾四种的状态的函数是非常复杂的,并且很容易出错,导致露馅,因此我们不妨创建两个二维数组来分别存放格子的四种状态:首先,第一个棋盘用来存放没埋雷的"0"和埋了雷的"1"。其次,第二个棋盘用来存放未解密的"*"和排查后的数字"n"。这样分别存放恰好可以让我们的后台埋放雷数组和玩家显示数组分开,因此我们先根据设想,将后台埋放雷数组全部初始化为”0“,其次再将玩家显示数组全部初始化为”*“


       这里有一点需要注意:由于我们在排查棋盘最外围的那一圈格子时只能排查到六个,甚至四个角只能排查四个,因此我们不妨将原定的9*9数组上下左右各多加一行(或一列),但不在这一圈埋放雷或显示,仅用来防止我们后续排查雷时越界访问数组

       如图,蓝线所画的格子即为我们加上防止越界的格子

      初始化二维数组的函数很简单,上节三子棋中我们也有提到,但这次我们需要一次性初始化两个同样大小但不同内容的二位数组,可能有些同学会想:如果不好判断的话,要不写两个初始化函数分别初始化地图算了。但既然这两个数组一模一样大,我们不如在传参时多加一个参数达到分别初始化的效果,该部分代码如下:

   //调用函数时传参多传一个参数
   //以下时函数调用示范
    InitBoard(mine, ROWS, COLS,'0');
	InitBoard(show, ROWS, COLS,'*');


//以下是函数体
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

4.打印地图:

      这里有三个点需要注意:

1.前面创建二维数组时我们为了不使数组出现越界访问因此使用的是11*11大小的数组,但是在向玩家打印时要注意只能打印中间的9*9的地图!。因此我们选择只打印每行每列下标为1-9下标的元素即可。并且只能打印show数组,不能将mine数组也打印出来,否则会露馅。

 2.如图,我们还需要在第一行和第一列前面加上序号方便玩家选择,该部分实现逻辑较简单,就是在打印每一行前打印一个数字变量即可,详情见下方代码。

3.如图,为了使棋盘与棋盘间很好的分割辨识,我们会在每次打印棋盘前后打印“--------扫雷---------”的分割线来分割,该部分也较为简单,详见代码。

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("--------扫雷--------\n");
	//控制列号
	for (i = 0;i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <=row; i++)
	{
		int j = 0;
		printf("%d ", i);//控制行号
		for (j = 1; j <=col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("--------扫雷--------\n");
}

5.埋放地雷:

       埋放地雷的实现也非常简单,即利用rand函数随机生成n(雷数)个坐标然后将初始化的字符0改为字符1即可。

如果还有对rand函数不了解的同学,请先移步:rand()函数详解。

//布置雷
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--;
	    }
	}
}

6.排查地雷:

    接下来来到最难的排查地雷,输入坐标,然后排查其周围八个坐标有几个“1”即可,代码实现只需将这8个区域的数值相加,然后减去8个字符“0”的大小再返回该数值即可。

八个坐标的代数表示如下:

        但有几点需要注意:

1.我们是利用排查次数来判断玩家是否排查完地雷取得胜利的,因此就需要保证排查过的坐标不能被二次排查,否则可能会导致程序误判玩家胜利

2.其次,由于我们创建的是字符数组,因此放入数组的是字符的“0”和“1”,所以不能使用简单的整形加减法来返回数字,而应该使用周围八个字符“0”和“1”的总值来减去八个字符‘0’的值,最后返回的数字才是周围的含雷数。

3.因为是初级阶段的扫雷,因此暂不增加递归展开的功能,有兴趣的同学可以自己探索。

    代码如下:

//排查1个坐标
int get_mine_count(char mine[ROWS][COLS], 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');
}

//排查雷
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-EASY_COUNT))
	{
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);

		if (x > 0 && x <=row && y>0 && y <=col)
		{
			if (show[x][y] != '*')
			{
				printf("该坐标已被排查过!\n");
				continue;

			}
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else
			{
				int n=get_mine_count(mine,x,y);

				show[x][y] = n + '0';//数字2怎么变成字符2?

				DisplayBoard(show, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("输入坐标非法,请重新输入!");
		}
	}
	if (win == (row * col - EASY_COUNT))
		{
            printf("恭喜你,扫雷成功!\n");
		}
	
}

四.整合代码测试及总结

      我们同样将游戏运行的代码分为三个模块分开书写,完整代码如下:

game.c:

#include"game.h"

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


//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	printf("--------扫雷--------\n");
	//控制列号
	for (i = 0;i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <=row; i++)
	{
		int j = 0;
		printf("%d ", i);//控制行号
		for (j = 1; j <=col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	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--;
	    }
	}
}

//排查1个坐标
int get_mine_count(char mine[ROWS][COLS], 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');
}

//排查雷
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-EASY_COUNT))
	{
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);

		if (x > 0 && x <=row && y>0 && y <=col)
		{
			if (show[x][y] != '*')
			{
				printf("该坐标已被排查过!\n");
				continue;

			}
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else
			{
				int n=get_mine_count(mine,x,y);

				show[x][y] = n + '0';//数字2怎么变成字符2?

				DisplayBoard(show, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("输入坐标非法,请重新输入!");
		}
	}
	if (win == (row * col - EASY_COUNT))
		{
            printf("恭喜你,扫雷成功!\n");
		}
	
}


game.h:

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include<stdio.h>

#define ROW 9
#define COL 9


#define ROWS ROW+2
#define COLS COL+2


#define EASY_COUNT 10


#include<stdlib.h>
#include<time.h>

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);

//打印棋盘
void DisplayBoard(char board[ROWS][COLS],int row,int col);
//你传了一个11*11的数组,就要拿一个11*11的数组接收!


//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row,int col);

test.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"

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

void game()
{
    //需要俩个二维数组,一个存放布置好的雷的信息,一个存放排查出的雷的信息
	//排查坐标时,为了防止坐标越界,我们给数组的行增加两行,列增加两列
	char mine[ROWS][COLS] = { 0 };//存放布置的雷
	char show[ROWS][COLS] = { 0 };//存放排查雷的信息

	//初始化棋盘
	InitBoard(mine, ROWS, COLS,'0');
	InitBoard(show, ROWS, COLS,'*');

	//打印棋盘
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);

	//布置雷
	SetMine(mine,ROW,COL);
    
	//排查雷
	//DisplayBoard(mine, ROW, COL);
	FindMine(mine,show,ROW,COL);

}



void test()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
        menu();
	    printf("请选择:>\n");
		scanf("%d", &input);
		switch (input)
		{
          case 1:
			  game();
            break;
				
		  case 0:
			  printf("退出游戏\n");
			  break;

		  default:
			  printf("输入错误,请重新输入:");
			  break;
		}
	} while (input);

}


int main()
{
	test();

	return 0;
}


     结语: 

       扫雷与三子棋游戏都是比较经典的电脑游戏,通过这些游戏的设计,我感受到了程序设计的巧思之处,或许我们不会设计好看的界面,或许我们也不会非常厉害的编程技术,但就即使利用最基础的字符打印,随机数生成,顺序,分支,循环,就可以设计组合出这样有模有样的游戏了,编程当真是奇妙。

       当然在这次尝试中我也发现了很多自己的不足,以及因为能力而做的妥协与阉割,但这我还愿意将这次探索认为是一个好的开始,希望以后的自己能就这样变得越来越厉害!

       最后的最后:Hello,world!

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
关于C语言一些简单的实例,里面有些思想值得借鉴 1 一个价值“三天”的BUG  2 灵活使用递增(递减)操作符  3 算术运算符计算器  4 逻辑运算符计算器 5 IP地址解析  6 用if…else语句解决奖金发放问题  7 用for循环模拟自由落体  8 用while语句求n!  9 模拟银行常用打印程序  10 使用一维数组统计选票  11 使用二维数组统计学生成绩  12 简单的计算器  13 时钟程序  14 华氏温度和摄氏温度的相互转换  15 SimpleDebug函数应用  16 常用的几种排序方法  17 广度优先搜索及深度优先搜索  18 实现基本的串操作  19 计算各点到源点的最短距离  20 储油问题  21 中奖彩球问题  22 0-1背包问题  24 二叉树算法集  25 模拟LRU页面置换算法  26 大整数阶乘新思路  27 银行事件驱动模拟程序  28 模拟迷宫探路  29 实现高随机度随机序列  30 停车场管理系统 31 菜单实现 32 窗口制作  33 模拟屏幕保护程序  34 文件读写基本操作  35 格式化读写文件  36 成块读写操作  37 随机读写文件  38 文件的加密和解密  39 实现两个文件的连接  40 实现两个文件信息的合并  41 文件信息统计  42 文件分割   43 同时显示两个文件的内容  44 模拟Linux环境下的vi编辑器  45 文件操作综合应用——银行账户管理  46 实用内存清理程序  47 如何检测Sniffer   48 加密DOS批处理程序  49 使用栈实现密码设置  50 远程缓冲区溢出漏洞利用程序  51 简易漏洞扫描器  52 文件病毒检测程序  53 监测内存泄露与溢出  54 实现traceroute命令  55 实现ping程序功能  56 获取Linux本机IP地址  57 实现扩展内存的访问  58 随机加密程序  59 MD5加密程序  60 RSA加密   61 制作表格  62 用画线函数作出的图案  63 多样的椭圆  64 多变的立方体 65 简易时钟  66 跳动的小球  67 用柱状图表示学生成绩各分数段比率  68 EGA/VGA屏幕存储  69 按钮制作  70 三维视图制作  71 红旗图案制作  72 火焰动画制作  73 模拟水纹扩散  74 彩色的Photo Frame   75 火箭发射演示  76 恢复内存文本 77 挽救磁盘数据 78 建立和隐藏多个PRI DOS分区 79 简单的DOS下的中断服务程序 80 文件名分析程序  81 鼠标中断处理  82 实现磁盘数据的整体加密  83 揭开CMOS密码  84 获取网卡信息  85 创建自己的设备  86 设置应用程序启动密码  87 获取系统配置信息  88 硬件检测  89 管道通信  90 程序自杀技术实现  91 连续击键游戏  92 掷骰子游戏  93 弹力球  94 俄罗斯方块  95 24点扑克牌游戏  96 贪吃蛇  97 潜水艇大战  98 机器人大战  99 图形模式下的搬运工  100 十全十美游戏  101 强大的通信录  102 模拟Windows下UltraEdit程序  103 轻松实现个人理财  104 竞技比赛打分系统  105 火车订票系统 

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修修修也

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值