C语言实现扫雷小游戏
C语言实现扫雷
之前闲来无趣,就自己模仿网上视频写了一个扫雷玩玩(主要是我女朋友让我去下一个扫雷,不晓得我怎么脑袋一热就说我给她写一个。。)这里记录分享一下,主要是用C语言进行,然后用了一点MFC的图形化界面窗口。
先分析一下,对于扫雷,我们应该要实现的是哪些东西?
首先,对于游戏地图的处理上,使用的是二维数组,然后对整个二维数组进行赋初值为0;
然后对于雷,我们用-1来进行表示,想要随机生成雷,那就直接用随机生成数的函数进行实现,用便利的方式把雷放到里面去就行。(先分小块进行讲,最后贴全部代码)
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<graphics.h>
using namespace std;
#define ROW 33 //列
#define COL 16 //行
#define NUM 99 //雷的个数
#define SIZE 30//图片的大小;
int map[ROW + 2][COL + 2];//地图
void Initmap()
{
int n = 0;
srand((unsigned int)time(NULL));//随机数种子;
for (int i = 0; i < ROW + 2; i++)
{
for (int j = 0; j < COL + 2; j++)
{
map[i][j] = 0;
}
}
while (n < NUM)//放置炸弹
{
int r = rand() % ROW + 1;//用生成随机数函数实现生成随机数
int c = rand() % COL + 1;
if (map[r][c] == 0)//放置雷;
{
map[r][c] = -1;
n++;
}
}
for (int i = 1; i <=ROW; i++)//遍历某个位置的九宫格,将该位置的数变成九宫格中雷的总数;
{
for (int j = 1; j <=COL; j++)
{
if (map[i][j] != -1)
{
for (int m = i - 1; m <= i + 1; m++)
{
for (int n = j - 1; n <= j + 1; n++)
{
if (map[m][n] == -1)
{
map[i][j]++;
}
}
}
}
}
}
那么对于一些特殊的位置该怎么处理呢?比如我们在玩扫雷的时候,对于某一个没有被翻起来的位置,我们鼠标左键单击之后,可能是空白,也可能是数字,那么这个数字是怎么得到的呢?我们讲一下关于扫雷的游戏规则:
如图:
当我们点击某一个没有被点击过的位置的时候,对于该位置的数字的求法,是以该位置为中心,周围形成的一个3x3的矩阵,然后便利除了他自己本身以外的格子,每遇到一个类,该位置的数字就加1,所以对应的数字就是这么得到的。但是在进行程序处理的时候,我们会发现一个问题,那如果我在边缘进行处理呢?也就是整个地图的最右上角或者左上角进行处理呢?不是越界了嘛?所以,我们在二维数组初始化的时候,将整个地图应该设置为(n+1)(m+1)的格式,后面进行地图更新和打印的时候,在nm内进行操作就行了;
for (int i = 1; i <=ROW; i++)//遍历某个位置的九宫格,将该位置的数变成九宫格中雷的总数;
{
for (int j = 1; j <=COL; j++)
{
if (map[i][j] != -1)
{
for (int m = i - 1; m <= i + 1; m++)
{
for (int n = j - 1; n <= j + 1; n++)
{
if (map[m][n] == -1)
{
map[i][j]++;
}
}
}
}
}
然后我们再考虑一点,按照目前我们所对二位数组初始化的数据中,只有-1和0这两个数据,但是我们想要在鼠标点击之后显示1-8和小红旗(鼠标右击之后插红旗的动作)还有炸弹该如何处理呢?对于数字其实还好,我们可以直接进行判断,但是又有一个问题,在我鼠标没有任何动作的时候,地图所展现的每一个小方格都是没有被翻起的,就是空白的,那空白图片该怎么显示呢?还有小红旗该如何显示?那么我们可以在数据上进行一些操作,我这里是把所有的都加上20,使其范围变到19-28,然后凡是在这个范围内的都输出空白图片,也就是没有被翻起来,只要某一处被鼠标进行动作,比如被鼠标进行左击,假设是在进行翻起动作,就把当前该位置的值-20,得到的数字进行判断,是-1就显示雷(结束游戏),是数字就显示数字。再来说一下红旗的解决,同样的,当数据进行操作之后,我仅仅是对鼠标的左击进行了监控,而右击还没有,所以当鼠标在某个位置进行右击的时候,我再把这个位置的书加上30,让其变得更大,然后如果像取消小红旗,则就判断一下,如果该位置的值>50,那么就把这个位置的值-50即可。
//-------------------------------------------------------------------------------------------
for (int i = 1; i <= ROW; i++)//对每个数据进行加20,对数据进行优化,方便后面对空白,红旗的处理;
{
for (int j = 1; j <= COL; j++)
{
map[i][j] += 20;
}
}
//-----------------------------------------------------------------------------------------
int PlayGame()//对鼠标动作进行监控
{
int r ; int c ;
MOUSEMSG msg = { 0 };//定义一个鼠标消息;
while(1)
{
msg = GetMouseMsg();
switch (msg.uMsg)
{
case WM_LBUTTONDOWN://翻开空白图片;
r = msg.x / SIZE + 1;
c = msg.y / SIZE + 1;
if (map[r][c] >= 19 && map[r][c] <= 28)
{
if (map[r][c] == 20)
{
OpenZ(r, c);
}
else
{
map[r][c] -= 20;
counts++;
}
}
return map[r][c];
break;
case WM_RBUTTONDOWN://标志小红旗
r = msg.x / SIZE + 1;
c = msg.y / SIZE + 1;
if (map[r][c] >= 19 && map[r][c] <= 28)//放置小红旗
{
map[r][c] += 50;
}
else if(map[r][c] > 30)//取消放置的小红旗
{
map[r][c] -= 50;
}
return map[r][c];
break;
}
}
}
最后还有一个小问题,就是在游玩扫雷的时候,你会发现有时候你点击了某一处,可能显示的不仅仅是你那个3x3的小方格的所有内容,可能还显示了其他的一些,这个就涉及到如果你点击的位置是空白,也就是周围一个雷都没有。那么也就是说,如果你点击到了一个周围一个类都没有的矩阵中心,然后就去遍历这个矩阵中其他小方格,看是否有也为0的,然后再以其为中心遍历其自身的矩阵。很明显,递归。
void OpenZ(int r,int c)//递归实现输出对应能输出的空位
{
map[r][c] -= 20;
counts++;
for (int m = r - 1; m <= r + 1; m++)
{
for (int n = c - 1; n <= c + 1; n++)
{
if (m >= 1 && m <= ROW && n >= 1 && n <= COL)//保证在游戏区
{
if (map[m][n] >=19 && map[m][n] <=28)//保证遍历的是空白的,也就是没有被翻起的
{
if (map[m][n] == 20)//如果遍历到的位置是空白的话,进行递归
{
OpenZ(m, n);
}
else//如果不是,那么就把他翻起来,然后把监控翻起来数值的变量加1,这里不用考虑雷的问题,因为本身遍历的这个矩阵中心是0,所以这个矩阵中没有雷
{
map[m][n] -= 20;
counts++;
}
}
}
}
}
}
最后我把整体代码贴一下:
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<graphics.h>
using namespace std;
#define ROW 33 //列
#define COL 16 //行
#define NUM 99 //雷的个数
#define SIZE 30//图片的大小;
int map[ROW + 2][COL + 2];//地图
int counts = 0;//判断游戏胜利的条件;
IMAGE img[12];//存放对应数字的图片和对应雷和红旗的图片;
void OpenZ(int r,int c)//递归实现输出对应能输出的空位
{
map[r][c] -= 20;
counts++;
for (int m = r - 1; m <= r + 1; m++)
{
for (int n = c - 1; n <= c + 1; n++)
{
if (m >= 1 && m <= ROW && n >= 1 && n <= COL)//保证在游戏区
{
if (map[m][n] >=19 && map[m][n] <=28)//保证遍历的是空白的,也就是没有被翻起的
{
if (map[m][n] == 20)//如果遍历到的位置是空白的话,进行递归
{
OpenZ(m, n);
}
else//如果不是,那么就把他翻起来,然后把监控翻起来数值的变量加1,这里不用考虑雷的问题,因为本身遍历的这个矩阵中心是0,所以这个矩阵中没有雷
{
map[m][n] -= 20;
counts++;
}
}
}
}
}
}
void Initmap()
{
int n = 0;
srand((unsigned int)time(NULL));//随机数种子;
for (int i = 0; i < ROW + 2; i++)
{
for (int j = 0; j < COL + 2; j++)
{
map[i][j] = 0;
}
}
while (n < NUM)//放置炸弹
{
int r = rand() % ROW + 1;
int c = rand() % COL + 1;
if (map[r][c] == 0)//放置雷;
{
map[r][c] = -1;
n++;
}
}
for (int i = 1; i <=ROW; i++)//遍历某个位置的九宫格,将该位置的数变成九宫格中雷的总数;
{
for (int j = 1; j <=COL; j++)
{
if (map[i][j] != -1)
{
for (int m = i - 1; m <= i + 1; m++)
{
for (int n = j - 1; n <= j + 1; n++)
{
if (map[m][n] == -1)
{
map[i][j]++;
}
}
}
}
}
}
for (int i = 1; i <= ROW; i++)//对每个数据进行加20,对数据进行优化,方便后面对雷,空白,红旗的处理;
{
for (int j = 1; j <= COL; j++)
{
map[i][j] += 20;
}
}
}
int PlayGame()//对鼠标动作进行监控
{
int r ; int c ;
MOUSEMSG msg = { 0 };//定义一个鼠标消息;
while(1)
{
msg = GetMouseMsg();
switch (msg.uMsg)
{
case WM_LBUTTONDOWN://翻开空白图片;
r = msg.x / SIZE + 1;
c = msg.y / SIZE + 1;
if (map[r][c] >= 19 && map[r][c] <= 28)
{
if (map[r][c] == 20)
{
OpenZ(r, c);
}
else
{
map[r][c] -= 20;
counts++;
}
}
return map[r][c];
break;
case WM_RBUTTONDOWN://标志小红旗
r = msg.x / SIZE + 1;
c = msg.y / SIZE + 1;
if (map[r][c] >= 19 && map[r][c] <= 28)//放置小红旗
{
map[r][c] += 50;
}
else if(map[r][c] > 30)//取消放置的小红旗
{
map[r][c] -= 50;
}
return map[r][c];
break;
}
}
}
void Showmap()
{
for (int i = 1; i <= ROW; i++)
{
for (int j = 1; j <= COL; j++)
{
printf("%2d ", map[i][j]);
if (map[i][j] == -1)
{
putimage((i - 1)*SIZE, (j - 1)*SIZE, &img[9]);//打印雷的图片
}
else if (map[i][j] >= 0 && map[i][j] <= 8)
{
putimage((i - 1)*SIZE, (j - 1)*SIZE, &img[map[i][j]]);//打印数字图片
}
else if (map[i][j] >= 19 && map[i][j] <= 28)
{
putimage((i - 1)*SIZE, (j - 1)*SIZE, &img[10]);//打印空白图片
}
else if (map[i][j] > 30)
{
putimage((i - 1)*SIZE, (j - 1)*SIZE, &img[11]);//打印小红旗;
}
}
cout << endl;
}
}
int main()
{
HWND hwnd = initgraph(ROW*SIZE, COL*SIZE);
loadimage(&img[0], "0.jpg", SIZE, SIZE);
loadimage(&img[1], "1.jpg", SIZE, SIZE);
loadimage(&img[2], "2.jpg", SIZE, SIZE);
loadimage(&img[3], "3.jpg", SIZE, SIZE);
loadimage(&img[4], "4.jpg", SIZE, SIZE);
loadimage(&img[5], "5.jpg", SIZE, SIZE);
loadimage(&img[6], "6.jpg", SIZE, SIZE);
loadimage(&img[7], "7.jpg", SIZE, SIZE);
loadimage(&img[8], "8.jpg", SIZE, SIZE);
loadimage(&img[9], "9.jpg", SIZE, SIZE);
loadimage(&img[10], "10.jpg", SIZE, SIZE);
loadimage(&img[11], "11.jpg", SIZE, SIZE);
STAR: Initmap();
while (1)
{
Showmap();
if (PlayGame() == -1)
{
Showmap();
int result=MessageBox(hwnd, "踩到雷啦!游戏失败!是否需要重新开始呢?", "", MB_YESNO);
if (result == IDYES)
{
MessageBox(hwnd, "您选择了重新开始!", "", MB_OK);
counts = 0;
goto STAR;
break;
}
else
{
MessageBox(hwnd, "那么感谢您的游玩!", "", MB_OK);
}
break;
}
if (ROW*COL - NUM == counts)
{
int result=MessageBox(hwnd, "很棒哦!恭喜你找出了所有雷!是否需要再来一局呢?", "", MB_YESNO);
if (result == IDYES)
{
MessageBox(hwnd, "那么在此预祝你本次游玩愉快!", "", MB_OK);
counts = 0;
goto STAR;
break;
}
else
{
MessageBox(hwnd, "那么感谢您的游玩!", "", MB_OK);
}
break;
}
}
closegraph();
system("pause");
return 0;
}
实现的样子:
注:关于图片,图片好似只能用.jpg格式,然后要存放到项目目录下(就是放.cpp的里面),不然显示不出来。还有游戏胜利的判断是用一个变量记录所有翻起来的格子数,当变量的值=总的数-雷的数,那就是胜利了。