- 🍁 个人主页:爱编程的Tom
- 🎐 CSDN新晋作者
- 💫 本篇博文收录专栏:c系列小游戏
- ✨ 目前其它专栏:c语言系列--万物的开始
- 🎉 欢迎 👍点赞✍评论⭐收藏💖三连支持一下博主🤞
- 🧨上山的人注定要去看看那山顶的风景,敬你我🎨
目录
前言
还记得小时候在电脑上,每次必玩的游戏必然得算扫雷一个,虽然从来没玩明白过,但依旧乐在其中。相信大家也一定玩过这款小游戏,那么今天,我们就用我们现学的知识来亲手创造一个扫雷游戏,明白其中的思维逻辑,那么你也会成为扫雷大神!
关于本篇内容,主要实现扫雷游戏,与之前写过的三子棋游戏思路相似,可以参考我已经写过的文章【c语言系列】三子棋实例(详解版) ,接下来,让我来手把手教你如何实现一个扫雷游戏。为了方便初学者使用,我们这里采用9*9的扫雷区域为例。
1.游戏的相关说明
扫雷是一款益智轻松的单人小游戏,我们的任务就是尽可能用最短的时间,在有限的区域内找出所有的雷,从而不被炸死获得胜利,感兴趣的朋友可以先玩一玩,体会其中的思维逻辑,加深对这款小游戏的理解。扫雷游戏网页版 - Minesweeper ,下面就是我亲自体验之后的结果,感觉还不错,很好玩的样子!
那么要想玩好这个游戏,我们必先熟悉它的游戏规则:
1.雷被提前布置好在设定的雷场中,我们的任务就是在不触雷的条件下找出或标记所有雷。
2.雷场中的方格可以是未排查,已排查,已标记三种状态。
3.排查过的方格会显示自身周围8个方格中雷的个数,若没有,则会自动排查相邻区域的空方格。
4.若玩家不幸踩雷,则游戏立马结束,玩家排雷失败。
那么这个游戏的玩法又是什么呢?
玩家随机点开方格,根据所给的数字来判断周围八个方格是否有雷的存在,若确定该位置是雷,可标记此处位置,根据逻辑和推理,从而快速的排查出所有雷的位置,获得游戏胜利!
2.游戏的设计思路
1.创造雷场环境:提前随机布置好雷的个数,设定格子状态。
2.遍历整个雷场:统计非雷格子周围的8个格子雷的个数,并可设定状态。
3.进行游戏操作
👀显示雷场方格
👀玩家进行排雷操作
👀选择是否标记
👀实施雷场信息状态更新
👀判断当前输赢状态
4.输赢判断形式
👀当玩家成功把所有雷排出即可获胜
👀当玩家排查到雷,即被炸死,游戏失败并结束
5.标记方式
👀在每一次排雷之后询问是否需要标记
👀已标记的雷不参与下次排查范围
6.方格展开形式
由于我们这里适用于初学者,所以我们这里采用简单的递归思想进行排雷
👀方格若已被标记,则不能排查
👀方格中若是地雷,则被炸死,游戏结束
👀方格中若是有数字,则显示当前方格数字状态,若周围没有数字,则会直接展开,直到遇到有数字的方格停下(即平时我们玩扫雷时,点击一个方格,若不是雷,则将会展开一片都不是雷的区域,增强用户的游戏体验感)
7.游戏结束:当雷被全部排完或者玩家不小心踩到雷,游戏结束,返回到初始化菜单界面
3.游戏文件的创建
这里由于实现这个游戏需要一定的代码量,所以我们采用之前的实现三子棋的方法【c语言系列】三子棋实例(详解版) 即多文件的形式,为代码的可读性和复写性以及维护代码提供便利。这里我们设定三个文件,分别是:
🍳test.c测试文件 🍳game.c函数实现文件 🍳game.h头文件
3.1 test.c文件
✨菜单函数
void menu()
{
printf("********************************\n");
printf("******** 1. play ********\n");
printf("******** 0. exit ********\n");
printf("********************************\n");
}
展示结果如下:
✨game()函数
void game()
{
char mine[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS,'*');
system("cls");
/*DisplayBoard(mine, ROW, COL);*/
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
🧐在这里,我们创建了两个数组:
一个用来布置雷的个数,提前展示的雷场,在自己调试的时候可以将函数放开,便于观察和布置;另一个用来生成已经包装好的雷场,向玩家展示的隐藏雷场,即覆盖第一个雷场。
🧐此外,我们还需注意,雷场我们初步设定为9*9的大小,但由于雷场可排查玩家点击的方格周围八个格子的状态,因此对于边角的雷场来说,我们还需将数组扩展至11*11的大小,从而避免数组越界访问,注意:这里虽然设定大小为11*11,但根据实际需要,我们可以只取9*9的雷场范围。
注:这里设置的两个雷场,一个命名为mine,用来存放雷的信息,一个命名为show,用来存放玩家排查雷的信息。我们将雷场mine初始化为"0",雷场show初始化为”*“。
展示结果如下:
✨main()函数
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
//打印菜单
menu();
printf("请选择>:");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("您的选择有误,请重新选择:\n");
break;
}
system("cls");
} while (input);
return 0;
}
跟我们之前设计三子棋采用的是一个思维逻辑:使用do.....while循环控制玩家输入的选择,选择1:进入游戏,选择0:退出游戏,选择其它:返回错误,请重新选择。
函数打印结果:
3.2 game.c文件
💦雷场初始化函数
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;//初始化设置
}
}
}
这里我们要针对不同的棋盘给出不同的数据,所以这里赋值不能单一化,所以我们设置一个char类型的变量set来帮助我们控制给mine雷场赋0,show雷场赋*。
💦雷场打印函数
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 = EASY_COUNT;//雷的个数
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
我们将雷表示为1,初始化雷的个数。在这里,我们依旧利用了srand函数在雷场中随机布置雷的随机坐标。
//test.c文件中
srand((unsigned int)time(NULL));//设置雷的随机生成点
由于这里是电脑随机设置布雷点,所以我们要限制雷生成点的范围,让雷的生成坐标达到预期值。
int x = rand() % row + 1;
int y = rand() % col + 1;//设置从1-9的随机雷点
💦周围坐标雷的统计函数
int GetMineCount(char mine[ROWS][COLS],int x,int y)
{
int count = 0;
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
count++;
}
}
return count;
}
注:假定我们排查雷的位置为(x.y)时,会自动的排查如上图所示的周围八个坐标,有雷则加1。
💦展开周围无雷区域函数
void Exp_unfolding(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win)
{
if (x <= 0 || x >= ROWS || y <= 0 || y >= COLS)
{
return;
}
int count = GetMineCount(mine, x, y);//得到坐标周围雷的个数
if (count == 0)
{
show[x][y] = ' ';
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (i >= 0 && i < ROWS && j >= 0 && j < COLS && show[i][j] == '*')
{
Exp_unfolding(mine, show, i, j, win);
}
}
}
}
else
{
show[x][y] = count + '0';
}
(*win)++;
}
核心思想:
这里我们需要同时控制两个雷场,当探雷点周围没有雷,则对于周围八个坐标进行探测,若如没有雷,即雷的个数为0;则在我们所规定的雷场范围里继续向各自周围的八个位置再次进行探测(此处给大家提供了一种递归的思想)。如果我们在递归时探测到了雷,则将雷的个数输出在show的雷场中。此外,我们还需要将整形雷的个数转化到字符型的show雷场中,即数字加上‘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)
{
int ch = 0;
while ((ch = getchar()) != '\n')//清理缓冲区,getchar返回值类型是int
;
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//符合棋盘大小
{
if (mine[x][y] == '1')
{
system("cls");
printf("很遗憾,您被炸死了!!!\n");
DisplayBoard(mine, ROW, COL);//展示雷的效果
break;
}
else
{
if (show[x][y] != '*' && show[x][y] != '#') //判断是否重复排查
{
printf("该坐标已被排查,请重新输入!\n");
continue; //直接进入下一次循环
}
else
{
//不是雷,需要统计x,y坐标周围有几个雷
Exp_unfolding(mine, show, x, y, &win);
system("cls");
DisplayBoard(show, ROW, COL);
win++;
printf("需要标记雷的位置请输入Y,否则请按任意键->");
while ((ch = getchar()) != '\n')//清理缓冲区
;
char c;
scanf("%c", &c);
while (c == 'Y')
{
MarkMine(show, ROW, COL); //标记雷
system("cls");
DisplayBoard(show, ROW, COL);
printf("继续标记请输入Y,否则请按任意键->");
c = getchar();
scanf("%c", &c);
}
}
}
}
else
{
printf("输入的坐标不合法,请重新输入:\n");
}
}
if (win == row * col - EASY_COUNT)
{
system("cls");
printf("恭喜你,排查成功\n");
DisplayBoard(mine, ROW, COL);
}
}
对上述函数的几点说明:
1.实现了一般的基础功能+拓展功能
2.拓展功能增加了无雷展开以及标记雷的位置
3.当排查下一个x,y坐标时,需要满足以下三个要求:
(1)该坐标不是雷 (2)该坐标周围没有雷 (3)该坐标没有被排查过
当排查雷时,玩家输入想要查询的坐标,判断当前坐标是不是雷,若是,则被炸死,若不是,则存储信息到show雷场中,继续游戏。此外,判断当前坐标是否被标记过,若是,则返回已标记,没有则标记。
💦标记雷的函数
void MarkMine(char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你想要标记位置的坐标->");
int ch;
while ((ch=getchar()) != '\n')//清理缓冲区
;
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断该坐标是否合法
{
if (show[x][y] == '*') //判断该坐标是否被排查
{
show[x][y] = '#';
break;
}
else
{
system("cls");
DisplayBoard(show, ROW, COL);
printf("该位置不能被标记,请重新输入!\n");
}
}
else
{
system("cls");
DisplayBoard(show, ROW, COL);
printf("不在范围内,请重新输入!\n");
}
}
}
这里我们利用循环结构来进行标记,此处未标记,且被确定为雷的时候,将其位置标记为#,输入Y触发标记功能,若不想标记,则按任意键进行下一步操作,循环往复,十分方便。
此外,我们判断输赢的时候,将此函数运用到上述排查雷的函数当中,两者结合即可判断。
if (win == row * col - EASY_COUNT)
{
system("cls");
printf("恭喜你,排查成功\n");
DisplayBoard(mine, ROW, COL);
}
函数输出结果如下:
3.3 game.h文件
💛所需的头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>//清屏操作
💛定义雷场范围
#define ROW 9 //行数
#define COL 9 //列数
#define ROWS ROW+2
#define COLS COL+2
说明:这里初始化雷场范围使用9*9,其余的一系列操作在雷场范围11*11上进行。
💛定义雷的个数
#define EASY_COUNT 10//设置雷的个数
这里可自由布置雷的个数,相应的修改雷场范围即可。
💛初始化雷场
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 board[ROWS][COLS], int row, int col);
//布置雷
💛排查雷数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//排查雷
💛标记雷的位置
void MarkMine(char show[ROWS][COLS], int row, int col);
//标记雷
4.完整代码示例
💞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,'*');
system("cls");
/*DisplayBoard(mine, ROW, COL);*/
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
//打印菜单
menu();
printf("请选择>:");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("您的选择有误,请重新选择:\n");
break;
}
system("cls");
} while (input);
return 0;
}
💞game.c文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
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 = EASY_COUNT;//雷的个数
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int GetMineCount(char mine[ROWS][COLS],int x,int y)
{
int count = 0;
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
count++;
}
}
return count;
}
void Exp_unfolding(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win)
{
if (x <= 0 || x >= ROWS || y <= 0 || y >= COLS)
{
return;
}
int count = GetMineCount(mine, x, y);//得到坐标周围雷的个数
if (count == 0)
{
show[x][y] = ' ';
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (i >= 0 && i < ROWS && j >= 0 && j < COLS && show[i][j] == '*')
{
Exp_unfolding(mine, show, i, j, win);
}
}
}
}
else
{
show[x][y] = count + '0';
}
(*win)++;
}
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)
{
int ch = 0;
while ((ch = getchar()) != '\n')//清理缓冲区,getchar返回值类型是int
;
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//符合棋盘大小
{
if (mine[x][y] == '1')
{
system("cls");
printf("很遗憾,您被炸死了!!!\n");
DisplayBoard(mine, ROW, COL);//展示雷的效果
break;
}
else
{
if (show[x][y] != '*' && show[x][y] != '#') //判断是否重复排查
{
printf("该坐标已被排查,请重新输入!\n");
continue; //直接进入下一次循环
}
else
{
//不是雷,需要统计x,y坐标周围有几个雷
Exp_unfolding(mine, show, x, y, &win);
system("cls");
DisplayBoard(show, ROW, COL);
win++;
printf("需要标记雷的位置请输入Y,否则请按任意键->");
while ((ch = getchar()) != '\n')//清理缓冲区
;
char c;
scanf("%c", &c);
while (c == 'Y')
{
MarkMine(show, ROW, COL); //标记雷
system("cls");
DisplayBoard(show, ROW, COL);
printf("继续标记请输入Y,否则请按任意键->");
c = getchar();
scanf("%c", &c);
}
}
}
}
else
{
printf("输入的坐标不合法,请重新输入:\n");
}
}
if (win == row * col - EASY_COUNT)
{
system("cls");
printf("恭喜你,排查成功\n");
DisplayBoard(mine, ROW, COL);
}
}
void MarkMine(char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入你想要标记位置的坐标->");
int ch;
while ((ch=getchar()) != '\n')//清理缓冲区
;
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断该坐标是否合法
{
if (show[x][y] == '*') //判断该坐标是否被排查
{
show[x][y] = '#';
break;
}
else
{
system("cls");
DisplayBoard(show, ROW, COL);
printf("该位置不能被标记,请重新输入!\n");
}
}
else
{
system("cls");
DisplayBoard(show, ROW, COL);
printf("不在范围内,请重新输入!\n");
}
}
}
💞game.h文件代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>//清屏操作
#define ROW 9 //行数
#define COL 9 //列数
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10//设置雷的个数
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 board[ROWS][COLS], int row, int col);
//布置雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//排查雷
void MarkMine(char show[ROWS][COLS], int row, int col);
//标记雷
5.部分效果展示
🆗那么今天的学习就到此为止了,感谢您的认真阅读和支持,希望这篇博客能够带给您帮助和启迪,加强对c语言部分知识的理解和认知,希望在去往山顶的途中,能够克服困难,最终登顶看见属于自己的风景和未来!期待与您探索更多的知识和内容,如果您有任何问题,也欢迎随时在评论区与我互动哦,再次感谢您的阅读!