目录
前言
扫雷游戏是一种益智类的游戏,目标是通过揭示方块上的数字来找到不带雷的方块,避免触雷。
游戏规则
玩家的目标是根据已翻开的方块信息来推测出地雷的位置,并避开它们。每个方块要么是空白,要么显示数字。数字表示该方块周围八个方向上的地雷数量。通过使用数字信息和逻辑推理,玩家可以确定哪些方块是安全的,哪些是地雷。当玩家翻开所有非地雷方块时,游戏胜利。如果玩家不慎踩到地雷,游戏失败。
游戏结构的分析
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些 信息。
所以我们首先会想到一个9*9的矩阵。
我们将有雷的设置为**"1"** ,没雷的地方设置为**"0"**。
示例
|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
| 4 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
| 8 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 9 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
假设我们排查(5,3),在(5,3)周围的一圈有雷,则会在屏幕上记起来。
这就是我们想要设计的样子,但是当我们想访问(6,9)的时候,我访问就会越界,到了界外去了,为了解决这一问题,我们将表格变成11*11的模型,这就能很好的解决这个问题。
|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
| 4 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
| 8 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 9 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
继续分析,我们已经设置了数字"1"为雷,"0"为非雷,当我们排查到一个雷时,需要将这个雷的信息储存起来,然后将它打印出来,作为排雷的重要参考信息的。。那这个雷的个数信息存放在哪⾥呢?如果存放在布 置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难。
将雷和⾮雷的信息不要使⽤数字,使⽤某些字符就⾏,这样就避免冲 突了,但是这样做棋盘上有雷和⾮雷的信息,还有排查出的雷的个数信息,就⽐较混杂,不够⽅便。
这⾥我们采⽤另外⼀种⽅案,我们专⻔给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再 给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不⼲扰了,把雷布置到 mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期 排查参考。
mine数组
|--------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|--------|--------|
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 0 | | | | | | | | | | | | |
| 1 | | '1' | '0' | '0' | '0' | '0' | '0' | '0' | '1' | '0' | '0' | |
| 2 | | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '1' | |
| 3 | | '0' | '0' | '0' | '0' | '1' | '0' | '0' | '0' | '0' | '0' | |
| 4 | | '0' | '1' | '0' | '0' | '0' | '0' | '0' | '0' | '1' | '0' | |
| 5 | | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | |
| 6 | | '0' | '0' | '0' | '0' | '1' | '0' | '0' | '0' | '0' | '0' | |
| 7 | | '1' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '1' | '0' | |
| 8 | | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | |
| 9 | | '0' | '0' | '0' | '0' | '1' | '0' | '0' | '0' | '0' | '0' | |
| 10 | | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | '0' | |
| 11 | | | | | | | | | | | | |
cpp
char mine[11][11] = {0};//⽤来存放布置好的雷的信息
char show[11][11] = {0};//⽤来存放排查出的雷的个数信息
对于show数组就是将所有'0'和'1'换成'*'这里就不展示了,不水字数了。
游戏的设计
使用多文件的好处有以下几点:
- 组织性好:将代码划分到不同的文件中,可以更好地组织和管理代码。每个文件可以分别负责不同的功能模块,使代码结构更清晰。
- 可维护性强:多文件的编程方式使得修改或更新某个功能模块变得更加简单。只需修改相应的文件,不需要修改整个程序。
- 可重用性高:将一些常用的函数或功能封装到单独的文件中,可以在多个项目中复用这些代码。
- 编译效率高:当调用某个函数时,编译器只需要编译包含该函数的文件,而不需要重新编译整个程序,提高了编译速度。
- 可扩展性强:如果需要添加新的功能模块,只需添加一个新的文件,不会对原有代码造成影响。
- 可测试性好:每个文件可以独立地进行测试,便于定位和修复问题。
总之,使用多文件的编程方式可以提高代码的组织性、可维护性、可重用性和扩展性,提高编译效率和测试效率。
在这个扫雷中我们需要:
game.h ⽂件中写游戏需要的数据类型和函数声明等
game.c ⽂件中写游戏中函数的实现等
text.c ⽂件中写游戏的测试逻辑
游戏代码实现
框架(test.c)
我们写个基础的框架。将头文件都放在game.h里面,就需要包含头文件,而我们自己的头文件要用""。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
int main()
{
text();
return 0;
}
我们进入text函数是,打印菜单menu(),让玩家进行选择,是进入游戏,还是退出游戏。
cpp
void menu()
{
printf("******************\n");
printf("***** 1.game *****\n");
printf("***** 0.exit *****\n");
printf("******************\n");
}
函数进来是直接do-while进行打印菜单,然后供玩家选择,选择1进入游戏,0退出游戏,若输入以外的内容则会提示出错误,需要重新输入。
cpp
void text()
{
int input = 0;
srand((unsigned int)time(NULL));//这是播种由函数 rand 使用的随机数发生器。
//我们先放在这里
do
{
menu();
printf("请输入你的选择:");
scanf("%d", &input);
switch(input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入:");
break;
}
} while (input);
}
game函数**(test.c)**
我们整体思路是将数组初始化,mine初始化为'0',show初始化为'*';随后布置地雷,我们初始化和布置雷好后都能打印一下看看是否符合我们的要求。最后进行排查地雷,将所有的雷找出来就行了。
cpp
void game()
{
char mine[ROWS][COLS] ;
char show[ROWS][COLS] ;
//初始化
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
Print(show, ROWS, COLS);
/*Print(mine, ROWS, COLS);*/
//布置地雷
Setmine(mine, ROW ,COL);
//排查地雷
Findmine(mine,show,ROW,COL);
}
InitBoard初始化**(game.c)**
对数组mine和show进行初始化,将mine的数组全部初始化为'0',show初始化为'*'。如果只是简单的在数组中初始化化'0','*'。我们传入应该char set就能够让代码更加灵活,不用在写一样的代码浪费时间和空间。
cpp
void InitBoard(char arr[ROWS][COLS], int rols, int cols,char set)
{
for (int i = 0; i < rols; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = set;//'0' , '*'
}
}
}
Print打印棋盘**(game.c)**
**对数组mine和数组show进行打印。**虽然我们设置的是11*11的棋盘,但是对于玩家来说他们只需要再9*9的棋盘里面进行排查雷就行了,所以我们打印的是9*9的棋盘。在加上坐标,能让玩家更精确的定位。
cpp
void Print(char arr[ROWS][COLS], int rols, int cols)
{
printf("-----扫雷游戏------------------\n");//让界面整体更加美观
for (int i = 0; i < rols - 1 ; i++)
{
printf("%d ", i);
}
printf("\n");
for (int i = 1; i < rols - 1; i++)
{
printf("%d ", i);
for (int j = 1; j < cols - 1; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
Setmine设置雷(game.c)
我们只需要在9*9的棋盘随机布置雷就行了,这时需要用到rand这个函数rand()%row = 8 再加1。
cpp
void Setmine (char board[ROWS][COLS], int row, int col)
{
int count = minebox;
while(count)
{
//设置行数和列数的随机
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
需要的头文件
rand----> #inlcude<stdlib.h>
time----> #include<time.h>
Findmine排查雷(game.c)
开始我们需要踹按创建变量x和y,供玩家输入坐标,我们的坐标只有1~9所以需要 0<x<10,0<y<10作为条件,这个坐标之外的为非法最标,需要重新输入如果玩家排查的坐标是雷,那么游戏结束,并打印棋盘。如果不是雷,我们需要统计一下它周围有几个雷,这就需要统计雷GetMine,将雷的信息传到数组show上,个数统计出来还要加上'0';如果仅仅是这样的话游戏是不会结束的,还需要给whle加上条件win< row * col - minebox(minebox是雷的数量),每次没排查到雷级win++。
如果有耐心的坚持去玩,那么肯定是能玩完的,前提是没被炸死哈。
cpp
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 - minebox)
{
printf("请输入要扫的坐标:");
scanf("%d %d", &x, &y);
if (x > 0 && x < row && y >0 && y < col)
{
system("cls");
if (mine[x][y] == '1')
{
printf("你被炸死了,游戏失败\n");
Print(show, ROWS, COLS);
break;
}
else
{
int count = GetMine(mine, x, y);
show[x][y] = count + '0';
Print(show, ROWS, COLS);
win++;
}
}
else
{
printf("非法输入\n");
}
}
if (win == row * col - minebox)
{
printf("恭喜你,排雷成功\n");
Print(mine, ROW, COL);
}
}
system("cls")的头文件跟rand的一样
GetMine
static是静态的意思,静态函数只能在声明它的文件中可见,其他文件不能引用该函数。
'1'的值是49;'0'的值是48;'1'-'0'=1是个整数。我们将周边的数都加起来-8*'0'就能得到雷的数。
cpp
static int GetMine(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');
}
game.h
cpp
#define ROW 9 //行数
#define COL 9 //列数
#define ROWS ROW+2
#define COLS COL+2
#define minebox 10 //雷的个数
#include<stdio.h> //main函数的头文件
#include<time.h> //time函数的头文件
#include<stdlib.h> //system,rand函数的头文件
//初始化棋盘
void InitBoard(char arr[ROWS][COLS], int rols, int cols, char set);
//打印棋盘
void Print(char arr[ROWS][COLS], int rols, int cols);
//设置雷
void Setmine(char arr[ROWS][COLS], int x, int y);
//找雷
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
同理,game.c要使用game.h的东西也得包含头文件。
到这里,我们这个简易版的扫雷就实现了。
怎么把代码编程可发布的exe程序呢?
exe程序
-
将debug模式改为Release模式。
-
打开"项目"点击属性。
-
打开"C/C++"下的"代码生成"将"运行库"改为多线程(/MT)
-
"Ctrl+F5"运行程序,这里exe的程序就出现在文件夹里面了。
-
打开我们存放代码的文件夹找到x64里面会有两个文件夹,一个是Debug,一个是Relase,点开,我们就能看到一个exe的程序后缀,这样就能直接发给其他人。
Debug和Release的区别
Debug和release是软件开发中常见的两种构建(build)模式,它们在编译、优化和调试方面有以下区别:
- 编译方式:Debug模式通常会生成包含调试信息的可执行文件,以便在调试过程中能够准确定位代码的问题。而release模式会采用更高级别的优化和压缩,生成体积更小、性能更高的可执行文件。
- 优化级别:Debug模式通常会使用较低级别的优化,以便更好地保留源代码的结构和逻辑,使得调试更容易。而release模式会使用更高级别的优化,以提高程序的运行性能。
- 调试信息:Debug模式会保留更多的调试信息,如变量名、函数名等,以便在调试器中能够查看和修改这些信息。而release模式会舍弃部分调试信息,以减小可执行文件的体积。
- 异常处理:Debug模式通常会提供更多的异常信息,使得在程序出错时能够更方便地定位问题。而release模式会舍弃部分异常信息,以提高程序的性能和稳定性。
总之,Debug模式适用于开发和调试阶段,能够提供更多的调试信息和更好的可读性;而release模式适用于发布阶段,能够提供更高的性能和较小的体积。在实际开发中,通常会进行Debug模式的开发和调试,然后再切换到release模式进行最终的发布。