C语言实现“扫雷”游戏;拆分完整过程;

3 篇文章 0 订阅

阅读建议

在这里插入图片描述

扫雷游戏

Windows系统下的扫雷游戏想必大家耳熟能详!规则想必不用多说,我们通过下面的分析思路来写这个游戏

分析

我们来看一下扫雷的界面,这是一个已经玩到一半的游戏
在这里插入图片描述
图中有两个部分,一个是已经翻开的(有地雷的),另一个是还没有翻开的(没有地雷的),我们可以理解为两张地图。那么我们要设计一个扫雷游戏的话,是不是第一步就要创建两个二位数组来表示这两张地图呢?
那地图创建好了,我们来观察,地图上有小格子,每个格子是不是要记录不同的信息?而格子是不是也有两种不同的状态(翻开和未翻开)?

那着手点我们找到了,是不是就可以下手了!

流程:

接下来我们来简单梳理一下游戏流程:
在这里插入图片描述

拆分细节

第一步:创建地图并初始化~

          1.创建showMap,初始化成全是‘ * 。
          2.创建mineMap,先全部初始化成‘0’,随机生成十个位置作为地雷,设为字符‘1’。

我们来看操作:

#include<stdio.h>
#include<time.h>
#define MINE_COUNT 10
#define MAX_ROW 9
#define MAX_COL 9                      //这里设置宏的目的是避免魔幻数字的出现,让代码功能更加清晰

void init(char showMap[MAX_ROW][MAX_COL], char mineMap[MAX_ROW][MAX_COL])
{
	for (int row = 0; row < MAX_ROW; row++)                         //除了循环赋值我们还可以使用库函数memset(ar,n,size)对数组的全部变量进行赋值,注意引入头文件<string.h>哦
	{
		for (int col = 0; col < MAX_COL; col++)
		{
			showMap[row][col] = '*';                              	//memset(showMap, '*', MAX_ROW * MAX_COL);        
		}
	}
	for (int row = 0; row < MAX_ROW; row++)
	{
		for (int col = 0; col < MAX_COL; col++)
		{
			mineMap[row][col] = '0';                               	//memset(mineMap, '0', MAX_ROW * MAX_COL);
		}
	}
	
	//随机产生十个位置作为地雷
	//先设置随机数种子
	srand((unsigned int)time(NULL));
	int mineCount = 0;                     //这里我们需要对雷的数量进行定义,以便作下面循环的判断
	while(mineCount<MINE_COUNT)
	{
		int row = rand() % MAX_ROW;
		int col = rand() % MAX_COL;        //这里又有可能出现问题,如果这十个随机数重复呢?十个地雷位置必须是不同的,那我们就有下面的操作
		//判断一下当前的位置是否已经有雷了
		if (mineMap[row][col] == '1')
		{
			continue;
		}
		mineMap[row][col] = '1';
		mineCount++;
	}
}

int main()
{
	//1.创建地图并初始化
	char showMap[MAX_ROW][MAX_COL] = { 0 };
	char mineMap[MAX_ROW][MAX_COL] = { 0 };

	init(showMap, mineMap);

	return 0;
}

第二步:打印地图showMap~

       我们要创建一个打印函数,最好能让他同时具备打印两种地图的功能

我们来看操作:

void Print(char theMap[MAX_ROW][MAX_COL])
{
	for (int row = 0; row < MAX_ROW; row++)
	{
		for (int col = 0; col < MAX_COL; col++)
		{
			printf("*", theMap[row][col]);                       
		}
		printf("\n");
	}
}

第三步:让玩家输入坐标,表示要翻开的位置~

 输入坐标这里我们会遇到的问题是: 
                  1.玩家输入的位置会不会超出数组的范围?
                  2.玩家输入的位置会不会已经翻开了?
                 
        那么我们就需要进行合法性判定了!我们来看一下操作:
int main()	
{
	//1.创建地图并初始化
	char showMap[MAX_ROW][MAX_COL] = { 0 };
	char mineMap[MAX_ROW][MAX_COL] = { 0 };

	init(showMap, mineMap);
	while (1)
	{
		//2.打印地图
		Print(showMap);

		// 3. 玩家输入坐标
		int row = 0;
		int col = 0;
		printf("请输入坐标(row, col):");
		scanf("%d %d", &row, &col);                 // 玩家会不会输错(超出范围  或者  输入的位置已经翻开过了)?  所以我们接下来就要进行合法性判定
		//进行合法性判定                           
		if (row<0 || row>MAX_ROW || col<0 || col>MAX_COL)
		{
			printf("您输入的坐标有误!请重新输入:\n");
			continue;
		}
		if (showMap[MAX_ROW][MAX_COL] != '*')
		{
			printf("您输入的坐标已经翻开啦,请重新输入:\n");
			continue;
		}
	}
	return 0;
}

第四步:判定是否踩雷~

  这部很简单,用if条件句判断一下当前的mineMap[row][col]是不是1就好了;
		//4.判定是否踩雷了
		if (mineMap[row][col] == '1')
		{
			printf("您踩雷了,再接再厉哦!\n");
			break;
		}

第五步:更新showMap,再翻开位置显示周围有多少个地雷~

 这里我们遇到的问题是:
     1.这个位置的周围是什么范围呢? 通过扫雷游戏我们知道是该位置的周围8个格子
     2.那周围的位置如何表示呢?
     3.那这个位置如果出现在地图的边线和角落呢?是不是周围就有位置越界了?

我把周围位置的坐标表示在下面这个图上:
在这里插入图片描述
是不是一目了然呢? 我们还得进行条件判断,来看下面这个更新函数:

void update(char showMap[MAX_ROW][MAX_COL],
	char mineMap[MAX_ROW][MAX_COL], int row, int col)
{
	//定义一下雷数
	int count = 0;
	//这里我们需要作八个判断,这样显然很难受,我们只需要一个循环判定,遍历[row][col]周围的八个位置即可
	for (int r = row - 1; r <= row + 1; r++)
	{
		for (int c = col - 1; c <= col + 1; c++)
		{
			if (r == row&&c == col)
			{
				//此时[r][c]在[row][col]的位置,不用判断,直接下次循环
				continue;
			}
			if (r<0 || r>MAX_ROW || c<0 || c>MAX_COL)
			{
				//此时r,c坐标超出数组范围,越界了就直接跳过了
				continue;
			}
			if (mineMap[r][c] == '1')
			{
				//r,c位置的mineMap是地雷,count就加,循环结束,把count的值传给showMap[row][col]即可;
				count++;
			}
		}
	}
	//如果写成	showMap[row][col] ==count;  假社此时count=2,当前 row, col 位置的元素就被设置成了 ASCII 值为 2 的“字符”,而不是 2
	//但ASCII码表中,ASCII 值为 n 的“字符”和 n刚好相差48个字节,而字符‘0’刚好是48个字节
	//我们就可以通过  '0'+ n 得到 n
	//但注意这仅仅是c语言里的操作,其他的主流编程语言中, 一般是不允许 字符类型 和 整数类型 进行混合运算的
	showMap[row][col] = '0' + count;
}

第六步:判定玩家是否翻开了所有的位置~ 如果没有翻完,就回到第二部的流程

 那我们如何判定呢?  
 最简单粗暴的方式就是,设置一个计数器,每翻一次记录一下。
 我们总共有9*9=81个格子,十个地雷,是不是计数器到达71的时候就只剩下地雷没有被翻开了,这时候游戏胜利!
        int main()	
{
	while (1)
	{
        //6.进行游戏胜利的判定
		opendCount++;
		if (opendCount == MAX_ROW*MAX_COL-MINE_COUNT)
		{
			printf("恭喜你获胜了,真厉害!\n");
			break;
		}
	}


	return 0;
}

完整代码

那所有的步骤我们都做完了,来整理一下代码吧,可供娱乐哦!

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

#define _CRT_SECURE_NO_WARNINGS
#define MAX_ROW 9
#define MAX_COL 9                      //这里设置宏的目的是避免魔幻数字的出现,让代码功能更加清晰,改动更加方便
#define MINE_COUNT 10

void init(char showMap[MAX_ROW][MAX_COL], char mineMap[MAX_ROW][MAX_COL])
{
	for (int row = 0; row < MAX_ROW; row++)                         //除了循环赋值我们还可以使用库函数memset(ar,n,size)对数组的全部变量进行赋值
	{
		for (int col = 0; col < MAX_COL; col++)
		{
			showMap[row][col] = '*';                              	//memset(showMap, '*', MAX_ROW * MAX_COL);        
		}
	}
	for (int row = 0; row < MAX_ROW; row++)
	{
		for (int col = 0; col < MAX_COL; col++)
		{
			mineMap[row][col] = '0';                               	//memset(mineMap, '0', MAX_ROW * MAX_COL);
		}
	}

	//随机产生十个位置作为地雷
	//先设置随机数种子
	srand((unsigned int)time(0));
	int mineCount = 0;
	while (mineCount<MINE_COUNT)
	{
		int row = rand() % MAX_ROW;
		int col = rand() % MAX_COL;        //这里又有可能出现问题,如果这十个随机数重复呢?十个地雷位置必须是不同的,那我们就有下面的操作

		//判断一下当前的位置是否已经有雷了
		if (mineMap[row][col] == '1')
		{
			continue;
		}
		mineMap[row][col] = '1';
		mineCount++;
	}
}

//希望这一个可以有同时打印两种地图的功能
//取决于实参填啥
void Print(char theMap[MAX_ROW][MAX_COL])
{
	for (int row = 0; row < MAX_ROW; row++)
	{
		for (int col = 0; col < MAX_COL; col++)
		{
			printf("%c ", theMap[row][col]);                       
		}
		printf("\n");
	}
}

void update(char showMap[MAX_ROW][MAX_COL],
	char mineMap[MAX_ROW][MAX_COL], int row, int col)
{
	//定义一下雷数
	int count = 0;
	//这里我们需要作八个判断,这样显然很难受,我们只需要一个循环判定,遍历[row][col]周围的八个位置即可
	for (int r = row - 1; r <= row + 1; r++)
	{
		for (int c = col - 1; c <= col + 1; c++)
		{
			if (r == row&&c == col)
			{
				//此时[r][c]在[row][col]的位置,不用判断,直接下次循环
				continue;
			}
			if (r<0 || r>MAX_ROW || c<0 || c>MAX_COL)
			{
				//此时r,c坐标超出数组范围,越界了就直接跳过了
				continue;
			}
			if (mineMap[r][c] == '1')
			{
				//r,c位置的mineMap是地雷,count就加,循环结束,把count的值传给showMap[row][col]即可;
				count++;
			}
		}
	}
	//如果写成	showMap[row][col] ==count;  假社此时count=2,当前 row, col 位置的元素就被设置成了 ASCII 值为 2 的“字符”,而不是 2
	//但ASCII码表中,ASCII 值为 n 的“字符”和 n刚好相差48个字节,而字符‘0’刚好是48个字节
	//我们就可以通过  '0'+ n 得到 n
	//但注意这仅仅是c语言里的操作,其他的主流编程语言中, 一般是不允许 字符类型 和 整数类型 进行混合运算的
	showMap[row][col] = '0' + count;
}

int main()	
{
	int opendCount = 0;
	//1.创建地图并初始化
	char showMap[MAX_ROW][MAX_COL] = { 0 };
	char mineMap[MAX_ROW][MAX_COL] = { 0 };

	init(showMap, mineMap);
	while (1)
	{
		//2.打印地图
		Print(showMap);
		// 3. 玩家输入坐标
		int row = 0;
		int col = 0;
		printf("请输入坐标(row, col):");
		scanf("%d %d", &row, &col);                // 玩家会不会输错(超出范围  或者  输入的位置已经翻开过了)?  所以我们接下来就要进行合法性判定
		//进行合法性判定                           
		if (row<0 || row>MAX_ROW || col<0 || col>MAX_COL)
		{
			printf("您输入的坐标有误!请重新输入:\n");
			continue;
		}
		if (showMap[row][col] != '*')
		{
			printf("您输入的坐标已经翻开啦,请重新输入:\n");
			continue;
		}

		//4.判定是否踩雷了
		if (mineMap[row][col] == '1')
		{
			printf("您踩雷了,再接再厉哦!\n");
			break;
		}

		//5.那如果没有踩雷呢?  是不是要更新showMap让他在翻开位置显示出周围有多少个雷 
		//我们来做一个update()函数;  那都要什么参数呢?  我们要显示,所以showMap是必须的,但是我们还得知道有多少个雷,那么mineMap也是要的,行列就不用多说了
		update(showMap,mineMap, row, col);
		//清一下屏,让屏幕显示清楚一些
		system("cls");

		//其实到这里我们已经可以开始玩了,但是不要忘记一个重要的步骤,
		//那就是判断游戏的胜利,也就是判定是否翻开了所有的不是雷的位置

        //6.进行游戏胜利的判定
		opendCount++;
		if (opendCount == MAX_ROW*MAX_COL-MINE_COUNT)
		{
			printf("恭喜你获胜了,真厉害!\n");
			break;
		}
	}


	return 0;
}

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值