前言
要实现扫雷,我们可以将其分为这样几个不同的部分来一一实现:
- 引入游戏所需文件
- 游戏界面的初始化
- 打印游戏界面
- 游戏机制的实现
- 玩家与游戏之间的互动
- 游戏运行以及胜负判定
一、引入游戏所需文件
在扫雷中我们需要用到EasyX绘图库中包含的部分功能
附上官网链接:https://easyx.cn/
下面列出需要包含的头文件:
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<graphics.h>
#include<windows.h>
#include<Mmsystem.h>
#pragma comment(lib,"winmm.lib")
初始化需要用到的图片以及声音资源(我的资源是从别的博主那里下载来的,就是一二三四五和红旗的等,故不附上):
int o = 0;
int init[row + 2][col + 2];
IMAGE img[11];
void load()
{
loadimage(&img[0], L"0.jpg", size, size);
loadimage(&img[1], L"1.jpg", size, size);
loadimage(&img[2], L"2.jpg", size, size);
loadimage(&img[3], L"3.jpg", size, size);
loadimage(&img[4], L"4.jpg", size, size);
loadimage(&img[5], L"5.jpg", size, size);
loadimage(&img[6], L"6.jpg", size, size);
loadimage(&img[7], L"7.jpg", size, size);
loadimage(&img[8], L"8.jpg", size, size);
loadimage(&img[9], L"9.jpg", size * row, size * col);
loadimage(&img[10], L"10.jpg", size * row, size * col);
}
二、游戏界面初始化
1.宏定义游戏行列数,图片的尺寸以及雷的数量
#define row 10 //定义行数
#define col 10 //定义列数
#define boom 10 //定义炸弹数
#define size 50 //定义图片长宽
2.InitGame函数,实现游戏初始化功能
int init[row + 2][col + 2];
void InitGame()//初始化游戏、
{
for (int i = 0; i < row + 2; i++) //设置缓冲区,并使缓冲区内全为0,缓冲区是指在游戏界面外一圈区域,不设置缓冲区的话会有一些麻烦
{
for (int j = 0; j < col + 2; j++)
{
init[i][j] = 0;
}
}
srand((int)time(0));
int a = boom;//初始化雷的数量
while (a > 0)//设置循环,初始化雷的位置,雷的位置是随机出现的
{
int row1 = rand() % row + 1; //缓冲区不可出现雷,rand%10是指[0,10),加一使得雷在界面内出现
int col1 = rand() % col + 1;
if (init[row1][col1] == 0)
{
init[row1][col1] = 6;//将雷所在数组元素设为6
a--;
}
}
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (init[i][j] == 0)
{
for (int m = i - 1; m <= i + 1; m++)//设置两个循环,获取某一位置周围雷的数量
{
for (int n = j - 1; n <= j + 1; n++)
{
if (init[m][n] == 6)
{
init[i][j]++;
}
}
}
}
}
}
for (int i = 1; i <= row ; i++)//把数组的每个元素都+100,通过加载图片成为初始状态
{
for (int j = 1; j <= col ; j++)
{
init[i][j] = init[i][j] + 100;
}
}
}
三、PaintGame函数打印游戏界面
当二维数组元素值处于不同区间时,我们打印不同的图片,就可以实现每个格子不同的状态,注意一定要控制好值和范围的关系,否则可能会出现插了棋子却又可以左键点击出炸弹等尴尬的Bug,我所定义的范围肯定没有问题,可以放心用
void PaintGame()
{
for (int i = 1; i <= row ; i++)
{
for (int j = 1; j <= col; j++)
{
if (init[i][j] >= 0 && init[i][j] <= 6)
{
putimage((i - 1) * size, (j - 1) * size, &img[init[i][j]]);
}
if (init[i][j] >= 100 && init[i][j] <= 106 || init[i][j] >= 300 && init[i][j] % 200 > 10)//初始时为100到106,大于300以后有插旗与取消状态,除以200取余大于10,则为插旗后取消旗子
{
putimage((i - 1) * size, (j - 1) * size, &img[8]);
}
if (init[i][j] >= 200 && init[i][j] <= 206 || init[i][j] >= 300 && init[i][j] % 200 < 10)//同上,小于10则为插旗
{
putimage((i - 1) * size, (j - 1) * size, &img[7]);
}
if (init[i][j] == -101)
{
putimage((i - 1) * size, (j - 1) * size, &img[0]);
}
}
}
}
四、Expand函数实现游戏机制
玩扫雷时我们发现,当点击目标周围没有炸弹时,我们点击一下会点开一片,可以称之为游戏独特的机制,用Expand函数来实现
void Expand(int m, int n)
{
if (m >= 1 && n >= 1 && m <= row && n <= col)//设置好,小心超出范围
{
for (int i = m - 1; i <= m + 1; i++)
{
for (int j = n - 1; j <= n + 1; j++)
{
if (init[i][j] % 100 == 0)
{
init[i][j] = -101; //换一个新的独特的值,避免套娃造成bug
Expand(i, j); //逢0递归
}
if (init[i][j] % 100 >= 1 && init[i][j] % 100 <= 5)
{
init[i][j] = init[i][j] % 100;
}
else
{
init[i][j] = init[i][j];
}
}
}
}
else
{
init[m][n] = init[m][n];
}
}
五、PlayGame函数实现玩家与游戏的互动
总体来看很好理解,通过鼠标的点击,来改变点击位置对应数组元素的值,点击方式的不同,即左键点击和右键点击,值的改变也不同,一定要设置好改变的值,否则会出现插了棋子却又可以左键点击等Bug
void PlayGame()
{
MOUSEMSG msg = { 0 };
msg = GetMouseMsg();
int m, n;
m = msg.x / size + 1; //加一使该函数的操作作用于init[1][1]及之后的元素,因为打印时外圈的全为0的行列不打印
n = msg.y / size + 1;
if (msg.uMsg == WM_LBUTTONDOWN)
{
PlaySound(TEXT("b.wav"), NULL, SND_FILENAME | SND_ASYNC);
if ( init[m][n] >= 100 && init[m][n] <= 106 || init[m][n] >= 300 && init[m][n] % 200 > 10)//当前格子赋值为100到106(初始),或者300以上并且除以200余数小于10时,因为旗子状态不能变为数
{
init[m][n] = init[m][n] % 100;//取除以10的余数,即可得原值
if (init[m][n] == 0)//当选中位置元素为0时,扩散
{
Expand(m, n);
}
}
else
{
init[m][n] = init[m][n];
}
}
if (msg.uMsg == WM_RBUTTONDOWN)
{
PlaySound(TEXT("b.wav"), NULL, SND_FILENAME | SND_ASYNC);
if (init[m][n] > 6)//因为所有值都大于0,所以小于等于6时是12345和炸
{
init[m][n] = init[m][n] + 100;
}
else
{
init[m][n] = init[m][n];
}
}
}
六、JudgeMent函数实现游戏运行以及胜负的判定
void JudgeMent()
{
while (1)
{
PaintGame();
PlayGame();
int boomnum = 0;//初始化踩中炸弹数量
int boomleft = boom;//初始化剩余炸弹数量
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (init[i][j] == 6)
{
boomnum++;//踩中炸弹
}
if (init[i][j] == 206 || init[i][j] >= 300 && init[i][j] % 200 == 6)
{
boomleft--;
}
}
}
if (boomnum == 1)//踩中炸弹即为失败
{
PlaySound(TEXT("a.wav"), NULL, SND_FILENAME | SND_ASYNC);
putimage(0, 0, &img[9]);
break;
}
if (boomleft == 0)
{
PlaySound(TEXT("c.wav"), NULL, SND_FILENAME | SND_ASYNC);
putimage(0, 0, &img[10]);
break;
}
}
}
七、主函数
int main()
{
load();
initgraph(row * size, col * size);
InitGame();
JudgeMent();
getchar();
return 0;
}
总结
以上即为扫雷小游戏的全部实现过程,难点在于选择的值范围对格子状态的影响以及递归函数的构造,总体难度不大。