目录
- 在编程的奇妙世界里,我们总是不断探索和挑战自我。今天,我要和大家分享一个令人兴奋的旅程——用 C 语言实现扫雷小游戏。扫雷,这个经典的游戏,承载着无数人的回忆,而通过自己的双手用代码将它重现,更是一种独特的体验。让我们一同走进这个充满逻辑与乐趣的编程之旅吧!
一、游戏分析与设计
1、功能说明
- 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9的格⼦
• 默认随机布置10个雷
• 可以排查雷
◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
2、界面设计
- 参考经典的扫雷界面,实现出菜单选择界面、扫雷操作界面、扫雷失败界面等。
3、数据结构分析
- 在扫雷的过程中,随机布置的雷和我们排查的位置都是需要信息来储存的,所以我们需要一个数据结构来储存这些信息。
- 因为我们需要再99的棋盘上进行雷的布置和排查,所以就先创建出一个99的数组来存放信息。
- 设定如果这个位置有雷,则为1,没有雷,则为0。
- 我们在测试的就会发现有个问题,如果我们排查(4,5)这个坐标时,周围一圈8个位置,会统计出来周围雷的个数是1;但如果我们排查(7,1)这个坐标的时候,同样时访问周围的8个位置,左侧的三个坐标则越界了,怎么解决这个问题呢。
- 为了防止排查越界这个问题,我们在设计的时候,将排雷的数组扩大一圈,编程1111,但雷还是布置在中间的99区域,周围一圈不布置雷就行,这样就解决了排查越界的问题。
- 所以我们将存放数据的数组创建成11*11的比较合适。
- 继续分析,我们布置了雷,棋盘上有着雷的信息(1)和非雷的信息(0),如果我们排查出一个位置,这个位置不是雷,但这个坐标周围有一个雷,那我们就要将这个信息记录并打印出来,但这个信息需要储存在哪里呢,如果放在我们布雷的数组里(因为我们之前设定是雷用(1)表示,非雷用(0)表示),同样都是(1),就会造成信息上的混乱和打印的困难。
- 为了解决这个问题,我们将雷的信息更改为字符,改为‘1’和‘0’,这样就能避免冲突了。
- 这个方案可以解决雷的信息冲突问题,但在这一个数组上同时存在雷的信息、排查出的雷的个数信息,就会比较混乱,不够方便。所以我们采用另一种方案,就是我们专门设计一个棋盘(用mine数组表示)存放布好雷的信息,再用另一个棋盘(用show数组表示),存放排查出雷的信息。这样就互不干扰了。
- 在mine数组中排雷,排查到的信息存到show数组中,并将show数组的信息打印作为排查参考。
- 为了和传统扫雷游戏类似,我们将show数组开始初始化为‘*’,为了保持两个数组的一致性,设计同一个函数进行操作。mine数组也初始化为‘0’,布雷位置改为‘1’。
char mine[11][11]={0};//存放布雷的信息
char show[11][11]={0};//存放排雷的信息
如图:
4、文件设计结构
- 我们计划使用三个文件来进行操作:
test.c//用于游戏中的测试逻辑
game.c//用于游戏功能的实现
game.h//用于游戏数据类型和函数的声明
二、扫雷游戏的代码实现
1、逐步讲解
1-1、打印菜单选择界面
test.c
#include<stdio.h>
void menu()//游戏初始选择界面
{
printf("***********************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("***********************\n");
}
int main(void)
{
int choice = 0;//之所以放到循环外,是因为需要根据choice来判断是否进行游戏
do
{
menu(); //将界面显示出来
printf("是否开始游戏:“1”or“0”\n"); //显示输入信息
scanf("%d", &choice);//输入选项信息
switch (choice) //根据选择信息进行相应选项
{
case 1:printf("扫雷\n"); break; //进行游戏
case 0:printf("游戏结束\n"); break; //退出游戏
default:printf("选择错误,请重新选择\n"); break;//重新进入游戏
}
} while (choice);//如果进行游戏或选择错误,会再次进入选择界面;如果
return 0;
}
运行效果:
1-2、初始化棋盘
test. c
void game()
{
char mine[ROWS][COLS];//存放布雷信息
char show[ROWS][COLS];//存放排雷信息
initboard(mine, ROWS, COLS,'0');//将mine数组初始化为'0'
initboard(show, ROWS, COLS,'*');//将show数组初始化为'*'
}
☟注意:
1.初始化范围是11* 11的数组,不是9* 9,布雷的时候才是9*9。
game. h
#define ROW 9 //方便后期对棋盘进行修改
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//函数声明
//初始化棋盘
void initboard(char board[ROWS][COLS], int rows, int cols, char set);
game. c
#include"game.h"//这里需要包含"game.h",否则无法使用ROWS和COLS
//棋盘初始化函数
void initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
1-3、打印棋盘
☟注意:
因为我们给用户展示的是9 * 9的扫雷界面,所以这里打印只需要打印9 * 9的中间界面即可
test. c
//打印棋盘
displayboard(mine, ROW, COL);//打印mine数组
displayboard(show, ROW, COL);//打印show数组
game. c
//打印棋盘函数
void displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("———扫雷游戏———\n");//打印开头边框
for (i = 0; i <= row; i++)//打印列序号,因为需要和棋盘对齐,所以从0开始
{
printf("%d ", i);
}
printf("\n");//打印完序号后,记得换行
for (i = 1; i <= row; i++)//打印列
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)//打印行
{
printf("%c ", board[i][j]);
}
printf("\n");//打印完一列,进行换行
}
printf("——————————\n");//打印结尾边框
}
game. h
//打印棋盘函数声明
void displayboard(char board[ROWS][COLS], int row, int col);
运行效果:
当然,在游戏过程中,我们只会显示show数组,也就是:
1-4、布置雷
棋盘准备好了,我们可以开始布置雷了。
test .c
//布置雷
setmine(mine, ROW, COL);
//生成随机数种子
srand((unsigned int)time(NULL));//生成随机数,整个函数只需要执行一次即可
game. h
//设置随机值所需要头文件
#include<stdlib.h>
#include<time.h>
//设置雷的个数
#define EASY_COUNT 10//简易模式下,雷的数量设置为10
//布置雷(函数声明)
void setmine(char mine[ROWS][COLS], int row, int col);
game .c
//布置雷
void setmine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;//布置一个雷,count--,直达为0
while (count)//雷需要随机布置
{
int x = rand() % row + 1;//坐标行生成随机数
int y = rand() % row + 1;//坐标列生成随机数
if (mine[x][y] == '0') //开始布置雷,如果该位置没有雷,则布置雷,count--,如果有雷则跳过
{
mine[x][y] = '1';
count--;
}
}
}
☟注意:
雷的位置是随机生成的,随机数的生成请参阅C语言猜数字游戏秘籍:带你轻松玩转一文,有详细的讲解。
- 运行效果:
1-4、排雷
test .c
//从mine数组里找雷,找到的信息传递到show数组里去,所以两个数组都需要用,排查的过程只需要操作中间的布雷区域
findmine(mine,show, ROW, COL);
game. h
//排查雷
void findmine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);
game.c
//计算点击坐标周边雷的个数
int getminecount(char mine[ROWS][COLS], int x, int y)//因为只有findmine函数使用,且在同一文件,不需要另外声明了
{
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');//该坐标值周围所有字符的值加起来,减去8个'0',得到的就是相应的数字
}
//排查雷
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;//排出的雷的个数,排出一个就+1
while (win<(row*col-EASY_COUNT))//需要反复判断是否有雷,所以这里需要一个循环函数
{
printf("请输入坐标:\n");
scanf("%d%*c%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 = getminecount(mine, x, y);
show[x][y] = count + '0';//数字加上'0'就会变成相应的字符型数字;如:1+'0'='1'
displayboard(show, ROW, COL);
win++;
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
if (win == (row * col - EASY_COUNT))//
{
printf("恭喜你,排雷成功!\n");
displayboard(mine, ROW, COL);
}
}
- 在排雷中,输入一个坐标,我们就需要对这个坐标进行判断,是否超出棋盘的边界。
- 其棋盘中的坐标,也需要判断是否被排查过,如果是雷,结束游戏,不是雷,我就对这个坐标的周围的雷的数量进行统计。
- 这时,我调用 getminecount函数进行实现。
- 如上图所示,‘0’、‘1’、‘2’、‘3’、‘4’、‘5’的ASCII码值为48、49、50、51、52、53,如果‘1’-‘0’就等于49-48=1,‘3’-‘0’等于51-48=3;由此,我们看出,一个字符型数字减去‘0’,就能得到该值的整型数字。
- 因为我们坐标中的都是字符型,有雷为‘1’,所以将选中坐标周围的值加起来减去八个‘0’,就能得到雷的个数。
- 通过不断排雷下去,将除雷以外的71个空位全部排除,我们就取得胜利了。
运行效果:
2、完整代码(加详细注释)
2-1、game.h
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9 //方便后期对棋盘进行修改
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10//简易模式下,雷的数量设置为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 mine[ROWS][COLS], int row, int col);
//排查雷
void findmine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);
2-2、game.c
#include"game.h"//这里需要包含"game.h",否则无法使用ROWS和COLS
//棋盘初始化函数
void initboard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
//打印棋盘函数
void displayboard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("———扫雷游戏———\n");//打印开头边框
for (i = 0; i <= row; i++) //打印列序号,因为需要和棋盘对齐,所以从0开始
{
printf("%d ", i);
}
printf("\n"); //打印完序号后,记得换行
for (i = 1; i <= row; i++) //打印列
{
printf("%d ", i);
int j = 0;
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;//布置一个雷,count--,直达为0
while (count)//雷需要随机布置
{
int x = rand() % row + 1;//坐标行生成随机数
int y = rand() % row + 1;//坐标列生成随机数
if (mine[x][y] == '0') //开始布置雷,如果该位置没有雷,则布置雷,count--,如果有雷则跳过
{
mine[x][y] = '1';
count--;
}
}
}
//计算点击坐标周边雷的个数
int getminecount(char mine[ROWS][COLS], int x, int y)//因为只有findmine函数使用,且在同一文件,不需要另外声明了
{
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');//该坐标值周围所有字符的值加起来,减去8个'0',得到的就是相应的数字
}
//排查雷
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;//排出的雷的个数,排出一个就+1
while (win<(row*col-EASY_COUNT))//需要反复判断是否有雷,所以这里需要一个循环函数
{
printf("请输入坐标:\n");
scanf("%d%*c%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 = getminecount(mine, x, y);
show[x][y] = count + '0';//数字加上'0'就会变成相应的字符型数字;如:1+'0'='1'
displayboard(show, ROW, COL);
win++;
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
if (win == (row * col - EASY_COUNT))//
{
printf("恭喜你,排雷成功!\n");
displayboard(mine, ROW, COL);
}
}
2-3、test.c
#include"game.h"
void menu()//游戏初始选择界面
{
printf("***********************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("***********************\n");
}
void game()
{
char mine[ROWS][COLS];//存放布雷信息
char show[ROWS][COLS];//存放排雷信息
initboard(mine, ROWS, COLS,'0');//将mine数组初始化为'0'
initboard(show, ROWS, COLS,'*');//将show数组初始化为'*'
//棋盘打印
//displayboard(mine, ROW, COL);//打印mine数组
displayboard(show, ROW, COL);//打印show数组
//布置雷
setmine(mine, ROW, COL);
//displayboard(mine, ROW, COL);
//排雷
findmine(mine,show, ROW, COL);//从mine数组里找雷,找到的信息传递到show数组里去,所以两个数组都需要用,
//排查的过程只需要操作中间的布雷区域
}
int main(void)
{
int choice = 0;//之所以放到循环外,是因为需要根据choice来判断是否进行游戏
srand((unsigned int)time(NULL));//生成随机数,整个函数只需要执行一次即可
do
{
menu(); //将界面显示出来
printf("是否开始游戏:“1”or“0”\n"); //显示输入信息
scanf("%d", &choice);//输入选项信息
switch (choice) //根据选择信息进行相应选项
{
case 1:printf("游戏开始\n"); game(); break; //进行游戏
case 0:printf("游戏结束\n"); break; //退出游戏
default:printf("选择错误,请重新选择\n"); break;//重新进入游戏
}
} while (choice);//如果进行游戏或选择错误,会再次进入选择界面;如果
return 0;
}
三、结尾
- 经过一番努力和探索,我们成功地用 C 语言打造出了一个简单却充满趣味的扫雷小游戏。当然,随着不断的学习,也会将这个项目进行不断的完善和改进。
- 通过这个项目,不仅深入理解了 C 语言的各种特性和编程技巧,更感受到了创造的乐趣和成就感。希望这篇博客能给对 C 语言和游戏开发感兴趣的朋友们带来一些启发和帮助。让我们继续在编程的道路上不断前行,创造更多精彩的作品!期待下一次与大家分享新的编程冒险。