阅读建议
扫雷游戏
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;
}