扫雷游戏介绍
扫雷是一款经典的游戏,它有三个规格:9*9(初级),16*16(中级),16*30(高级),初级包含10个雷,中级包含40个雷,高级包含99个雷。
其游戏的规则为:
点击一个方格,若该方格不是雷,则其回返回以它为中心的周围八个方格内包含的雷的个数,如周围有一个雷则返回1,周围没有雷则返回空白。而若该方格是雷,则玩家被雷炸死,游戏结束。
该游戏的胜利条件便为排查出所有不是雷的方格。
而我们将分为三个文件来编写扫雷:
game.h:包含头文件,函数的声明,游戏需要的数据类型等。
main.cpp:包含游戏的测试逻辑,菜单的打印等。
game.cpp:游戏主体,包含实现游戏的函数等。
扫雷游戏设计
棋盘的设置
要进行扫雷,我们首先需要能够扫雷的棋盘。而在扫雷中,无论是设置雷还是排查雷等的信息都需要储存,而二维数组可以很好的满足我们的需求。
所以我们定义两个二维数组,一个用来保存雷设置的信息,另一个用来保存玩家排查雷的信息,使二者数据不相互干扰:
为了方便后期的代码的维护和难度级别的修改,我们将行和列定义成宏 ,我们这里以初级模式9*9为例。
这里我们多定义一个行和列的原因是在排查雷的过程中,若排查的方格在边角位(如下图中1的位置),就会发生越界,未避免越界发生,我们可以上下左右各多增添一行,这样既方便排查又不用担心数组越界。
棋盘的初始化
有了棋盘之后,我们便要将棋盘进行初始化。
保存雷信息的棋盘(不展示给玩家)先全部初始化为0。
而存放玩家排查信息的棋盘(展示给玩家)则全初始化为‘*’。
棋盘打印
有了棋盘之后,我们可以将棋盘打印出来看看,方便排查代码问题。
tip:在排查代码问题阶段,我们可以先将存放雷信息的棋盘打印出来,给自己开“透视”,方便自己调试。
设置雷
要扫雷,那棋盘上要先有雷。为了游戏的趣味性,生成的雷的坐标是要有随机性的,所有要用到rand函数(注意配套srand使用)
注意:生成的雷的坐标应该在1-9之间,不要超过棋盘的范围(设置11*11只是为了方便排查,真正展示实际是9*9);同时注意雷坐标重复问题。
同时为了游戏难度的更改变化,游戏中雷的个数也设置成宏。
排查雷(判断输赢)
在一切准备就绪后,我们就要开始游戏的重点——排查雷。
在此我们要考虑3种情况:1.玩家点到雷了,炸死;
2.玩家没点到雷,游戏继续;
3.玩家输入坐标不在棋盘范围内,重新输入。
而游戏也不可能选择一次就结束,所以我们要用到循环,而循环判断的条件则为剩余非雷的方格个数,若在剩余方格数等于雷的个数前循环停止,则游戏失败,反之则游戏胜利。
所以我写了两个函数,一个Isend来计算剩余的方格数,而另一个则来判断玩家是否获取胜利:
标记雷
在玩扫雷的过程中,标记雷的功能也很重要,能大大减轻我们扫雷的难度。
而有标记,就要有取消标记。该部分代码比较简单我就不做过多赘述:
获取周围雷的个数
该部分只要会运用数学知识+知晓排查坐标周围八个坐标就好
注意:存放棋盘信息的二维数组是字符数组,所以计算的时候要减去七个字符0,才能得到正确的答案。
放上周围八个坐标的图和代码:
递归展开
递归展开能给玩家带来更便捷的游戏体验
首先来判断计算的周围方格内的雷的个数是否为0,若非0则没必要展开,直接返回计算值。
若为0则展开递归,从选择的方格开始,推及自己周围8个方格逐渐展开。
在展开的过程中注意限制,一要判断方格是否已经被计算过,减少递归的次数;二是要限制递归展开的范围在棋盘范围内。
游戏的导入菜单
游戏的主体部分已经完结,要使游戏正常导入自然需要一个菜单
用do-while来保证游戏可以一次进行多次游玩,让玩家自行决定是否退出游戏。
扫雷代码展示
game.h:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <Windows.h>
//定义雷的个数
#define mine 10
//定义行和列
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//函数声明
void Intboard(char board[ROWS][COLS],int rows,int cols,char c);
void Printfboard(char board[ROWS][COLS], int rows, int cols);
void Setmine(char board[ROWS][COLS], int row, int col);
void Play(char inner[ROWS][COLS], char board[ROWS][COLS], int row, int col);
main.cpp:
#include "game.h"
void menu()
{
printf("*******************************\n");
printf("*********** 1.play ************\n");
printf("*********** 0.exit ************\n");
printf("*******************************\n");
}
void game()
{
//初始化+定义棋盘
char my_chessboard[ROWS][COLS];
char chessboard[ROWS][COLS];
Intboard(my_chessboard, ROWS, COLS, '0');
Intboard(chessboard, ROWS, COLS, '*');
Setmine(my_chessboard, ROW, COL);//设置雷
//Printfboard(my_chessboard, ROW, COL);给自己开外挂,方便调试
Printfboard(chessboard, ROW, COL);//打印棋盘
Play(my_chessboard, chessboard, ROW, COL);//排查雷
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
printf("扫雷游戏\n");
menu();
printf("请输入: ");
scanf("%d", &input);
switch (input)
{
case 1:
printf("——————游戏开始——————\n");
game();
break;
case 0:
printf("——————游戏结束——————\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
game.cpp:
#include "game.h"
void Intboard(char board[ROWS][COLS], int row, int col, char c)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = c;//c为放在棋盘内的字符
}
}
}
void Printfboard(char board[ROWS][COLS], int row, int col)
{
system("cls");//实时清除屏幕,使代码运行界面不会过于冗长
printf("——————扫雷游戏——————\n");
for (int i = 0; i <= col; i++)
printf("%d ", i); //输出列序列
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i); //输出行序列
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void Setmine(char board[ROWS][COLS], int row, int col)
{
int count = mine;
while (count)
{
int x = rand() % 9 + 1;//使雷的坐标具有随机性且在棋盘范围内
int y = rand() % 9 + 1;
if (board[x][y] == '0')//防止雷坐标重复
{
board[x][y] = '1';
count--;
}
}
}
char caculate(char inner[ROWS][COLS], int x, int y)
{
char ret = (inner[x - 1][y - 1] + inner[x - 1][y] + inner[x - 1][y + 1] +
inner[x][y - 1] + inner[x][y + 1] +
inner[x + 1][y - 1] + inner[x + 1][y] + inner[x + 1][y + 1]) - 7 * '0';
return ret;
}
void expand(char inner[ROWS][COLS],char board[ROWS][COLS],int x, int y)
{
char ret = caculate(inner, x, y);
if (ret == '0')//判断返回值是否为0,为0则开始展开
{
board[x][y] = ' ';
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (board[i][j] == '*' && i >= 1 && i <= 9 && j >= 1 && j <= 9)//限制扩展范围在棋盘内
expand(inner, board, i, j);
}
}
}
else
board[x][y] = ret;
}
int Isend(char board[ROWS][COLS], int row, int col)
{
int count = 0;
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (board[i][j] == '*')
count++;
}
}
return count - mine;
}
void Markmine(char board[ROWS][COLS],int row,int col)
{
printf("需要标记雷请输入p,不需要则输入其他字符:");
char p = 'p';
getchar();//清除一下字符,防止对scanf造成影响
scanf("%c", &p);
if (p == 'p')
{
int x, y;
printf("请输入要标记or取消标记的坐标:\n");
getchar();
scanf("%d%d", &x, &y);
if (board[x][y] == 'M')//判断玩家是要取消标记还是要标记
{
board[x][y] = '*';
Printfboard(board, row, col);
printf("取消标记成功!\n");
}
else
{
board[x][y] = 'M';
Printfboard(board, row, col);
printf("标记成功!\n");
}
}
}
void Play(char inner[ROWS][COLS], char board[ROWS][COLS], int row, int col)
{
time_t begin = time(NULL);//统计运行时间
int count = row * col - mine;
while(count=Isend(board,row,col))//判断除去雷的剩余的格子
{
Markmine(board,row,col);//用来标记的函数
printf("请输入要排查的坐标:\n");
int x, y;
scanf("%d%d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
if (inner[x][y] == '1')
{
Printfboard(inner, row, col);
printf("很抱歉,你被炸死了!\n");
printf("5秒后进入下一局\n");
Sleep(5000);//使程序暂停5秒,防止跳转过快
system("cls");
break;
}
else if (inner[x][y] == '0')
{
expand(inner, board, x, y);
Printfboard(board, row, col);
continue;
}
}
else
printf("输入错误,请重新输入\n");
}
time_t end = time(NULL);
int duration = end - begin;
if (count == 0)
{
printf("恭喜你,排查出所有的雷!\n");
printf("用时为 %d s\n", duration);
printf("5秒后进入下一局\n");
Sleep(3000);
system("cls");
}
}
感谢您的浏览!