前言
在我们学习c语言一段时间过后,会遇到自己的劫难,会对自己所学的知识感到迷茫,我学了这些东西有什么用?难道只能那它来做题吗?与我一开始学习的初衷完全相反(这时候我们就要渡劫了hhh)其实不然,相信大家都学完了分支,顺序结构,函数,循环以及数组了吧
那么我们今天就可以用这些来做出我们的第一个小项目——扫雷!
相信许多小伙伴对扫雷这款游戏并不陌生,毕竟是童年的回忆啊~,那接下来由本采花贼带领大家用c语言简易写一个扫雷游戏出来玩耍,在我们度过此次劫难过后,相信大家的学习能更进一步!
注意:全文以9*9的扫雷图来讲解
准备工作
1.分模块
在做项目的时候,我们不能一股脑把所有的代码全部放在一个文件中,这样不仅显的代码冗长,而且倘若我们代码出错或者我们要修改代码的时候会给我们造成不便。
所以我们要将其分为三个模块,text.c,game.c,game.h
text.c //代表的是游戏中函数测试的逻辑
game.h //负责对游戏中函数和数据的声明
game.c //负责实现txet.c中的函数
2.用宏定义图的大小和雷的个数
宏定义的优点:在后续我们想改变数据,如雷的个数,我们只需要改变宏就行,不需要一个一个改,大大提高了效率。
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
///实际打印出来的是9*9的格子
#define ROW 9
#define COL 9
///定义11*11的格子,防止越界
#define ROWS ROW+2
#define COLS COL+2
///定义雷的个数
#define mine_count 10
ROW代表行,COL代表列。
注意:定义了9*9和11*11的原因,因为扫雷是会中心点以九宫格的形式扫描,为了防止数组越界的情况,我们初始化的图是11*11大小,但是能够暂时字符图案的是9*9大小的图。
3.text.c的实现
#include"game.h"
void meau()///打印游戏菜单
{
printf("================================\n");
printf(" 1.开始游戏 \n");
printf(" 2.结束游戏 \n");
printf("================================\n");
}
void game()///完成扫雷游戏
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };///初始化表格
///mine布置雷的信息
Initboard(mine, ROWS, COLS,'0');///数组传参,记得声明函数
///show数组存放排查出雷的信息
Initboard(show, ROWS, COLS,'*');
///打印棋盘
displayboard(show, ROW, COL);///打印棋盘
///布置雷
Setmine(mine, ROW, COL);
///打印布置雷的棋盘
displayboard(mine, ROW, COL);
///排查雷
findmine(mine, show, ROW, COL);///在排查雷的过程中会涉及mine和show数组,所以这里传参两个都要传
}
void text()
{
srand((unsigned int)time(NULL));
int input = 0;
do///因为我们要先进入游戏,并且要反复游戏,所以这里用do-while循环
{
meau();///进来先打印菜单
printf("请选择:>");
scanf_s("%d", &input);///我们输入的input为1或者0
switch (input)
{
case 1:
game();
break;
case 2:
printf("退出游戏");
break;
default:
printf("选择错误,请重新选择");
break;
}
} while (input);///input为1或0,在c语言中1和0表示真和假,从而能够控制循环的开始和结束
}
int main()
{
text();
return 0;
}
注意,这里涉及到了srand函数,因为后期要随机产生雷的个数,对其不了解的可以去看看这篇博客
srand((unsigned int)time(NULL))的理解(C语言)_srand((unsigned int)time(null))-CSDN博客
4.game.h声明实现
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
///实际打印出来的是9*9的格子
#define ROW 9
#define COL 9
///定义11*11的格子,防止越界
#define ROWS ROW+2
#define COLS COL+2
///定义雷的个数
#define mine_count 10
void Initboard(char arr[ROWS][COLS], int rows, int cols, char ret);
void displayboard(char arr[ROWS][COLS], int row, int col);
void Setmine(char arr[ROWS][COLS], int row, int col);
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
5.game函数的代码实现
1.初始化扫雷图
扫雷,扫雷,没图怎么扫。
这里我们要打印两个图,mine图(英语中mine是地雷的意思)和show图(展示)
mine图负责展示布置雷的信息,只能给自己看,不能给玩家看,不然就是破解版了
show图则是负责将扫雷的界面展示给玩家
注意:1.mine图中1代表雷,0代表未布置雷,show图中*表示被隐藏的位置。
2.show图中*是字符,为了方便,我们的mine图中的1和0也要变成字符类型。
代码实现
不难看出扫雷图是个二维数组,我们通过定义Initboard函数来初始化我们的扫雷图。
Initboard函数中传入的参数。
实现代码如下
void Initboard(char arr[ROWS][COLS], int rows, int cols, char ret)///初始化地图
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = ret;
}
}
}
注意:1.我们初始化的棋盘是11*11的,但打印出来的不一定(前面已经讲过避免越界)
2.mine和show初始化的符号不同,这里我们需要传入一个字符参数ret来分别接收。
2.打印棋盘
定义displayboard函数我们在打印棋盘的时候可以将我们的坐标打印出来,便于我们进行游戏以及查看棋盘。
displayboard函数中传入的参数如下
实现代码如下
void displayboard(char arr[ROWS][COLS], int row, int col)///打印地图
{
printf("====扫雷游戏开始===\n");
for (int m = 0; m <= col; m++)///打印列
{
printf("%2d ", m);///这里改成%2d,%2d是按照两个字符对齐
///原因是当我们在修改ROW和COL的值的时候,可以避免行和列无法对齐方格的情况
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%2d ", i);///打印行
for (int j = 1; j <= col; j++)
{
printf("%2c ", arr[i][j]);
}
printf("\n");
}
}
注意:1.我们这里printf里面传入的是%2d,原因是让我们的值按照两个字符对齐,当我们的行或者列大于10的时候能够避免出现错位的情况。
2.我们这里所打印出来的是9*9的扫雷图。
3.我们这里所打印的是show图,如果你想将布置雷的图也打印出来,只要在写一个display函数,将传入的show改为mine即可
ps:这里给大家演示下未使用%2d所出现的情况
接下来是使用%2d的情况
3.布置雷
通过定义Setmine函数来在二维数组内布置雷,在坐标为(x,y)处布置雷
Setmine函数中传入的参数
实现代码如下
void Setmine(char arr[ROWS][COLS], int row, int col)///放置雷
{
int count = mine_count;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
这里我们定义了一个count来储存雷的值,通过控制雷的个数来控制循环结束,每布置一个雷,count都会减1,直到count=0,循环结束。
注意我们的打印出来的扫雷图是9*9的,所以我们的x,y也要是在这个范围内,但我们布置雷使用了
rand函数,位置是随机的,那我们如何控制想x,y在9*9的范围内呢?
这里我们要补充个小知识
当我们要得到[a,b)的值可以用:
(rand()%(b-a))+a;(当a为0时,可以用rand()%b);
这样我们便可以控制x,y的范围了~
4.统计一个位置周围雷的个数
我们在游玩扫雷时,会以我们所排查的位置为中心以九宫格的方式扫描其周围的雷的个数,并且打印出来给玩家看,所以我们这里要定义一个getminecount函数扫描以当前位置为九宫格中雷的个数
位置坐标为
getminecount函数中传入的参数
实现代码如下
int getminecount(char mine[ROWS][COLS], int x, int y)///得到周围雷的个数
{
return mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[x - 1][y + 1] - 8 * '0';
}
注意:getminecount函数返回的是一个数字,而我们扫雷图中储存的是字符,我们如何将其变为字符呢?
注意这里也要补充一个小知识
任意数+字符0等于字符任意数:原因与字符相加减的时候,字符会先转化为ascll值在进行加减
如
2 +'0' = '2'
字符0的ascll值为48,字符2的ascll值为50
2+48 = 50 = '2'
所以count + '0' = 'count'
任意数等于字符任意数减去字符0:原因同上
'2' - '0' =2
'count' - '0' = count
5.排查雷
这里我们定义了findmine函数来排查雷
传入参数如下
实现代码如下
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y = 0;///初始化要排查的坐标
int win = 0;///判断胜利的条件
while (win<row*col- mine_count)
{
printf("请输入您要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)///控制排查的位置在图内
{
if (mine[x][y] == '1')
{
printf("你被炸死了");
displayboard(mine, ROW, COL);///打印出我们布置雷的图,让玩家失败后知道雷的位置
break;
}
else///该坐标不是雷,所以要统计周围有几个雷
{
int count = getminecount(mine, x, y);
show[x][y] = count + '0';///任意数字+字符0可转化为相应的字符数字
displayboard(show, ROW, COL);///要再次打印出我们的show图,因为我们排查的位置的数字已经发生变化。
win++;
}
}
else
{
printf("坐标错误,请重新输入\n");
}
}
if (win == row * col - mine_count)///游戏结束的条件
{
printf("排雷成功\n");
displayboard(show, ROW, COL);
}
}
这里定义了win来判断游戏的条件,因为我们这是建议版的扫雷,一次只能得出一个位置的值,无法将不是雷的位置全部显示。
所以win的条件便是将所有不是雷的地方全部点一遍,剩下的位置就是雷,即扫雷成功,也就是win=row*col-mine_count。
注意:1.我们所输入的坐标要在图内,所以我们要控制x,y的值。
6.game函数整合代码
#include"game.h"
void Initboard(char arr[ROWS][COLS], int rows, int cols, char ret)///初始化地图
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = ret;
}
}
}
void displayboard(char arr[ROWS][COLS], int row, int col)///打印地图
{
printf("====扫雷游戏开始===\n");
for (int m = 0; m <= col; m++)///打印列
{
printf("%2d ", m);///这里改成%2d,%2d是按照两个字符对齐
///原因是当我们在修改ROW和COL的值的时候,可以避免行和列无法对齐方格的情况
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%2d ", i);///打印行
for (int j = 1; j <= col; j++)
{
printf("%2c ", arr[i][j]);
}
printf("\n");
}
}
void Setmine(char arr[ROWS][COLS], int row, int col)///放置雷
{
int count = mine_count;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count--;
}
}
}
int getminecount(char mine[ROWS][COLS], int x, int y)///得到周围雷的个数
{
return mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[x - 1][y + 1] - 8 * '0';
}
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y = 0;///初始化要排查的坐标
int win = 0;///判断胜利的条件
while (win<row*col- mine_count)
{
printf("请输入您要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)///控制排查的位置在图内
{
if (mine[x][y] == '1')
{
printf("你被炸死了");
displayboard(mine, ROW, COL);///打印出我们布置雷的图,让玩家失败后知道雷的位置
break;
}
else///该坐标不是雷,所以要统计周围有几个雷
{
int count = getminecount(mine, x, y);
show[x][y] = count + '0';///任意数字+字符0可转化为相应的字符数字
displayboard(show, ROW, COL);///要再次打印出我们的show图,因为我们排查的位置的数字已经发生变化。
win++;
}
}
else
{
printf("坐标错误,请重新输入\n");
}
}
if (win == row * col - mine_count)///游戏结束的条件
{
printf("排雷成功\n");
displayboard(show, ROW, COL);
}
}
7.游戏效果
8.总结
通过建议扫雷游戏的模拟使我们对代码的认识程度更加深刻,也能意识到自己所学的东西不简简单单是书面上的文字,不简简单单只是能用来做题,使能够创造一些东西的,是有趣的东西。其实大家看完这个过程,其实也会发现我们做一个项目其实也没有我们想象的那么困难。
采花贼相信只要大家坚持学习下去,无论是为了找工作,还是为了自己的理想,总会达到的!
如果大家有任何意见,可以在评论区提出,博主会认真采纳的,大家一起进步,也希望大家能给博智点个赞或者关注一下博主,给采花贼鼓励。
只要花不谢,采花贼会一直在!!!我们下期再见!!!
ps:明天会更新进阶版本扫雷,也就是能扫出一大片的那种哦~