C语言游戏超详解扫雷游戏完整版,细节满满!!
Posted Do
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言游戏超详解扫雷游戏完整版,细节满满!!相关的知识,希望对你有一定的参考价值。
目录
扫雷
实现扫雷的算法有很多种,我在这里给大家最详细的代码介绍以及思考方法,细节满满哦!!
扫雷游戏规则介绍
每个格子有两种状态,有地雷或者没有地雷。玩家点到地雷游戏结束,玩家标记出所有地雷游戏胜利。
每个没有地雷的格子点开后显示相邻8个格子里面存在地雷的数目,周边没有地雷则可以递归地打开与空相邻的方块;如果不幸触雷,则游戏结束。
如何将扫雷游戏实现代码
与上次三子棋游戏模块一致,分类创建:
game.h:相关游戏函数的声明,变量的宏定义等;
game.c:游戏相关函数的功能实现;
test.c:游戏的测试,游戏的主题体;
基本思路
1.创建和打印游戏菜单
2.创建两个棋盘数组,一个是布置雷的棋盘数组,一个是排查雷的棋盘数组
3.初始化两个棋盘,为了防止后期统计排查雷的个数出现矛盾,所以我这里把布置雷的那个棋盘全部初始为'0',把排查雷的棋盘全部初始化为'*'
4.打印棋盘
5.布置雷,由电脑自主完成随机布置雷的个数,个数可以自己在头文件中定义
6.排查雷,在布置雷的数组里排查,如果是雷则打印被炸死,并退出游戏,打印排查雷的棋盘;如果不是雷,则统计雷的个数,是0则展开空白,不是0则将雷的个数传给排查雷的那个数组
7.判断输赢,如果空格的总的个数于行和列的乘积减去布雷的个数,则表示排雷成功
分步代码实现
创建和打印游戏菜单
void menu()
{
printf("**********************\\n");
printf("******* 1.play *******\\n");
printf("******* 0.exit *******\\n");
printf("**********************\\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//用于随机函数rand的调用
do
{
menu();
scanf_s("%d", &input);
switch (input)
{
case 1:
printf("扫雷游戏开始\\n");
game();
break;
case 0:
printf("退出游戏\\n");
break;
default:
printf("输入错误,请重新选择!\\n");
}
} while (input);
return 0;
}
初始化棋盘
//两个数组初始化数组的实现,char board[ROWS][COLS]接收mine数组和show数组
//这里设置一个字符set接收mine和show数组传参过来的‘0’和‘*’
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;
}
}
}
这里的初始化函数调用两次,分别初始化布雷数组和排雷数组,字符set分别接收'0'和'*';
打印棋盘
void Displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("————扫雷游戏 ————\\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i); //为了方便用户判断行数和列数,将列数打印出来
}
printf("\\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i); //每行开头打印行数
for (j = 1; j <=col; j++)
{
printf("%c ", board[i][j]);
}
printf("\\n");
}
printf("————扫雷游戏————\\n");
}
打印棋盘结果如下:
这样打印出来的棋盘好看也方便用户写入坐标
布置雷
void Setmine(char mine[ROWS][COLS], int row, int col)
{
int count = SETCOUNT; //为了灵活变通,可以自己设置布置雷的个数,在头文件中自定义个数
while (count) //直到count为0才退出循环,并且每次都是随机坐标布置雷,count是几,
{ x和y就要随机几次
int x = rand() % row + 1;//用户输入的坐标的范围就是行数列数的范围,应该是row+1才是正确
的坐标范围
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;//每布置一个,雷的个数就减一;
}
}
}
排查雷
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 - SETCOUNT)
{
int count_blank = 0;//空白个数,每次进入循环都要重置,如果放在循环外面依旧是上一次的值会重复叠加空白;
printf("请输入排查雷的坐标:\\n");
scanf_s("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断坐标合法性
{
if (mine[x][y] == '1') //遍历到了雷
{
printf("很遗憾,你被炸死了!\\n");
Displayboard(mine, row, col);
break;
}
else
{
int count_mine = get_mine_count(mine, x, y);
if (count_mine == 0 && show[x][y]=='*')//周围雷的个数为0,且没有被遍历
{
count_blank=get_showblank(mine, show, x, y);//递归展开空白,返回空白个数
win += count_blank;//空白的个数加到不是雷个数的总数中
}
else if(show[x][y]=='*')//这里用else不太合适,这里控制的条件是当周围个数不是0,但是没有被遍历
{
show[x][y] = count_mine + '0';//不是雷,则统计周围有几个雷,放入show数组对应坐标
win++;//他统计了周围雷的个数,但本身不是雷,加1
}
Displayboard(show, row, col);
}
}
else
{
printf("坐标不合法,请重新输入!\\n");
}
}
if (win== row * col - SETCOUNT)
{
printf("恭喜你,排雷成功!\\n");
Displayboard(show, row, col);
}
}
当排查雷时,(x,y)处不是雷且周围雷的个数不为0则需要统计周围雷的个数:
static int get_mine_count(char mine[ROWS][COLS], int x,int y) //static修饰函数,那这个函数
就只能在当前源文件里使用了
{ //统计这些坐标周围有几个雷
return mine[x][y - 1] +
mine[x - 1][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] +
mine[x + 1][y] - 8 * '0'; //这里返回的是数字,num+'0'='num';即一个数字加上字符0等于数字代表的字符
}
当(x,y)周围雷的个数为0时展开为空格,去递归遍历周围四个坐标雷的个数,并且统计空格的个数:
int get_showblank(char mine[ROWS][COLS], char show[ROWS][COLS], int x , int y)//当雷的个数为0时计算展开的空白个数
{
int count_mine = get_mine_count(mine, x,y);
int count_blank = 0;//初始化空白的个数
if(count_mine == 0)
{
show[x][y] = ' ';
count_blank++;//只要是空白就加1;下次递归也一样
//判断周围四个坐标合法性,并且要满足没有被遍历,依旧是*号,以防重复遍历
if (x - 1 >= 1 && x - 1 <= ROW && y >= 1 && y <= COL && show[x - 1][y] == '*')
{
count_blank+=get_showblank(mine, show, x - 1, y);//每递归一次,空白个数要累加,包括了x,y的空白和周围四个坐标的空白;
}
if (x >= 1 && x <= ROW && y - 1 >= 1 && y - 1 <= COL && show[x][y - 1] == '*')
{
count_blank += get_showblank(mine, show, x, y - 1);
}
if (x + 1 >= 1 && x + 1 <= ROW && y >= 1 && y <= COL && show[x + 1][y] == '*')
{
count_blank += get_showblank(mine, show, x + 1, y);
}
if (x >= 1 && x <= ROW && y + 1 >= 1 && y + 1 <= COL && show[x][y + 1] == '*')
{
count_blank += get_showblank(mine, show, x, y + 1);
}
}
return count_blank;//返回空白个数
}
游戏主体——game()函数
void game()
{
char mine[ROWS][COLS] = { 0 }; //创建存放布置雷的数组
char show[ROWS][COLS] = { 0 }; //创建存放排查雷的数组
Initboard(mine, ROWS, COLS,'0'); //初始化布置雷的棋盘,mine表示布置雷数组的首地址
Initboard(show, ROWS, COLS, '*'); //初始化排查雷的数组,show表示排查雷数组的首地址
//Displayboard(mine, ROW, COL); //打印布置雷的棋盘,这里不用打印扩展的两行两列,只需在中间的棋盘布雷,扫雷也同样
Displayboard(show, ROW, COL);
Setmine(mine, ROW, COL); //布置雷
//Displayboard(mine, ROW, COL);
Findmine(mine,show, ROW, COL); //排查雷
}
扫雷游戏主体函数则相比三子棋简单,只需要调用游戏相关函数即可,这里需要注意的是在调用这些函数进行数组传参时,要注意参数的顺序,这里创建mine和show数组都是用的ROWS和COLS,所以在实现相关函数的功能时用的依然是ROWS和COLS。
总代码实现
game.h
#pragma once
#define ROW 9
#define COL 9
#define SETCOUNT 10
#define ROWS ROW+2 //为了能成功遍历边界的棋盘,所以需要创建两个扩展的数组将布雷和扫雷一一对应,初始化时也一样要扩展
#define COLS COL+2
void Initboard(char board[ROWS][COLS], int rows, int cols,char set);
void Displayboard(char board[ROWS][COLS], int row, int col); //打印棋盘只打印中间的棋盘,不需要打印扩列的
void Setmine(char mine[ROWS][COLS], int row, int col);
void Findmine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);
test.c
#include<stdio.h>
#include"game.h"
#include<stdlib.h>
#include<time.h>
void game()
{
char mine[ROWS][COLS] = { 0 }; //创建存放布置雷的数组
char show[ROWS][COLS] = { 0 }; //创建存放排查雷的数组
Initboard(mine, ROWS, COLS,'0'); //初始化布置雷的棋盘,mine表示布置雷数组的首地址
Initboard(show, ROWS, COLS, '*'); //初始化排查雷的数组,show表示排查雷数组的首地址
//Displayboard(mine, ROW, COL); //打印布置雷的棋盘,这里不用打印扩展的两行两列,只需在中间的棋盘布雷,扫雷也同样
Displayboard(show, ROW, COL);
Setmine(mine, ROW, COL); //布置雷
//Displayboard(mine, ROW, COL);
Findmine(mine,show, ROW, COL); //排查雷
}
void menu()
{
printf("**********************\\n");
printf("******* 1.play *******\\n");
printf("******* 0.exit *******\\n");
printf("**********************\\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//用于随机函数rand的调用
do
{
menu();
scanf_s("%d", &input);
switch (input)
{
case 1:
printf("扫雷游戏开始\\n");
game();
break;
case 0:
printf("退出游戏\\n");
break;
default:
printf("输入错误,请重新选择!\\n");
}
} while (input);
return 0;
}
game.c
#include<stdio.h>
#include"game.h"
//两个数组初始化数组的实现,char board[ROWS][COLS]接收mine数组和show数组
//这里设置一个字符set接收mine和show数组传参过来的‘0’和‘*’
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;
printf("————扫雷游戏 ————\\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i); //为了方便用户判断行数和列数,将列数打印出来
}
printf("\\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i); //每行开头打印行数
for (j = 1; j <=col; j++)
{
printf("%c ", board[i][j]);
}
printf("\\n");
}
printf("————扫雷游戏————\\n");
}
void Setmine(char mine[ROWS][COLS], int row, int col)
{
int count = SETCOUNT; //为了灵活变通,可以自己设置布置雷的个数,在头文件中自定义个数
while (count)
{ //直到count为0才退出循环,并且每次都是随机坐标布置雷,count是几,x和y就要随机几次
int x = rand() % row + 1;//用户输入的坐标的范围就是行数列数的范围,应该是row+1才是正确的坐标范围
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;//每布置一个,雷的个数就减一;
}
}
}
static int get_mine_count(char mine[ROWS][COLS], int x,int y) //static修饰函数,那这个函数就只能在当前源文件里使用了
{ //统计这些坐标周围有几个雷
return mine[x][y - 1] +
mine[x - 1][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] +
mine[x + 1][y] - 8 * '0'; //这里返回的是数字,num+'0'='num';即一个数字加上字符0等于数字代表的字符
}
int get_showblank(char mine[ROWS][COLS], char show[ROWS][COLS], int x , int y)//当雷的个数为0时计算展开的空白个数
{
int count_mine = get_mine_count(mine, x,y);
int count_blank = 0;//初始化空白的个数
if(count_mine == 0)
{
show[x][y] = ' ';
count_blank++;//只要是空白就加1;下次递归也一样
//判断周围四个坐标合法性,并且要满足没有被遍历,依旧是*号,以防重复遍历
if (x - 1 >= 1 && x - 1 <= ROW && y >= 1 && y <= COL && show[x - 1][y] == '*')
{
count_blank+=get_showblank(mine, show, x - 1, y);//每递归一次,空白个数要累加,包括了x,y的空白和周围四个坐标的空白;
}
if (x >= 1 && x <= ROW && y - 1 >= 1 && y - 1 <= COL && show[x][y - 1] == '*')
{
count_blank += get_showblank(mine, show, x, y - 1);
}
if (x + 1 >= 1 && x + 1 <= ROW && y >= 1 && y <= COL && show[x + 1][y] == '*')
{
count_blank += get_showblank(mine, show, x + 1, y);
}
if (x >= 1 && x <= ROW && y + 1 >= 1 && y + 1 <= COL && show[x][y + 1] == '*')
{
count_blank += get_showblank(mine, show, x, y + 1);
}
}
return count_blank;//返回空白个数
}
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 - SETCOUNT)
{
int count_blank = 0;//空白个数,每次进入循环都要重置,如果放在循环外面依旧是上一次的值会重复叠加空白;
printf("请输入排查雷的坐标:\\n");
scanf_s("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断坐标合法性
{
if (mine[x][y] == '1') //遍历到了雷
{
printf("很遗憾,你被炸死了!\\n");
Displayboard(mine, row, col);
break;
}
else
{
int count_mine = get_mine_count(mine, x, y);
if (count_mine == 0 && show[x][y]=='*')//周围雷的个数为0,且没有被遍历
{
count_blank=get_showblank(mine, show, x, y);//递归展开空白,返回空白个数
win += count_blank;//空白的个数加到不是雷个数的总数中
}
else if(show[x][y]=='*')//这里用else不太合适,这里控制的条件是当周围个数不是0,但是没有被遍历
{
show[x][y] = count_mine + '0';//不是雷,则统计周围有几个雷,放入show数组对应坐标
win++;//他统计了周围雷的个数,但本身不是雷,加1
}
Displayboard(show, row, col);
}
}
else
{
printf("坐标不合法,请重新输入!\\n");
}
}
if (win== row * col - SETCOUNT)
{
printf("恭喜你,排雷成功!\\n");
Displayboard(show, row, col);
}
}
总结
这是扫雷优化也叫完整版的扫雷游戏,博主花了一天时间思考和修改代码,整理博客,实属不易。这让我感觉到每一个游戏的实现离不开每一个细节的把控,如果忽略一个细节,整个游戏就无法完成。扫雷游戏细节颇多,对于我们的思考能力和代码实现能力有一定考验,彻底掌握则需要我们反复去琢磨,去动手实现,只有这样我们才能变得更强。如果这篇文章能帮助到你,请给我一键三连,有了大家的鼓励和指教我才能更进一步!!谢谢大家
以上是关于C语言游戏超详解扫雷游戏完整版,细节满满!!的主要内容,如果未能解决你的问题,请参考以下文章
C语言实现小游戏篇我接触的第一款电脑游戏,你可以永远相信 “ 扫雷 ” 。[ C语言实现 ] [ 超详细,超清楚 ] [ 有代码 ]