C语言扫雷游戏
(1)初始化二维数组
#include<stdio.h>
#include<graphics.h>//包含图形库头文件
#define ROW 10 //行
#define COL 10 //列
int map[ROW][COL];//初始化数据
void GameInit()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){map[i][k] = 0;}}
}//打印二维数组
void show()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){printf("%2d ", map[i][k]);}putchar('\n');}
}int main()
{GameInit();show();
}
(2)埋雷
玩法规则:例如以上绿色九宫格,中间数字为1,表示这个九宫格存在一颗雷。【由于该数字形成的九宫格只有左上角没被翻开,那么左上角必定是雷】
//埋雷 -1:表示此处有雷 【总共10个雷】
for (int i = 0; i < ROW; ) {int r = rand() % 10;int c = rand() % 10;if (map[r][c]==0){map[r][c] = -1;i++;//只有成功埋雷才算数,确保10个雷}}
- 对雷周围的数字进行设置【把以雷为中心的九宫格的数据都加上1,除了雷之外】
//把以雷为中心的九宫格的数据都加上1,除了雷之外
for (int i = 0; i < ROW; i++)
{for (int k = 0; k < COL; k++){//如果当前位置是雷if (map[i][k]==-1){//九宫格for (int r = i - 1; r <= i + 1; r++){for (int c = k - 1; c <= k + 1; c++){//判断是否越界if (r >= 0 && r < ROW && c >= 0 && c < COL && map[r][c] != -1){map[r][c]++;}}}}}
}
- 完整代码:
#include<stdio.h>
#include<graphics.h>//包含图形库头文件
#include<stdlib.h>
#include<time.h>
#define ROW 10 //行
#define COL 10 //列
int map[ROW][COL];//初始化数据
void GameInit()
{srand((unsigned int)time(NULL));//设置随机数种子for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){map[i][k] = 0;}}//埋雷 -1:表示此处有雷 【总共10个雷】for (int i = 0; i < ROW; ) {int r = rand() % 10;int c = rand() % 10;if (map[r][c]==0){map[r][c] = -1;i++;//只有成功埋雷才算数,确保10个雷}}//把以雷为中心的九宫格的数据都加上1,除了雷之外for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){//如果当前位置是雷if (map[i][k]==-1){//九宫格for (int r = i - 1; r <= i + 1; r++){for (int c = k - 1; c <= k + 1; c++){//判断是否越界if (r >= 0 && r < ROW && c >= 0 && c < COL && map[r][c] != -1){map[r][c]++;}}}}}}
}//打印二维数组
void show()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){printf("%2d ", map[i][k]);}putchar('\n');}
}int main()
{GameInit();show();
}
(3)贴图【把矩阵数字贴上对应图片】
- 假设随机生成的矩阵地图如下,我们的目标是将“-1”对应的位置粘贴上雷的图片
- 创建窗口
// 创建窗口:一行有十张图片每张图片40px
initgraph(IMG_SIZE * ROW, IMG_SIZE * COL);
- 引入图片(GameInit函数中添加)
【加载位于程序当前目录下image文件夹中的.jpg图像文件, //并将这些图像按照它们的编号依次存储在img数组中】
//加载图片(赋值)for (int i = 0; i < IMAGENUM; i++){char file[20] = "";sprintf(file, "./image/%d.jpg", i);loadimage(&img[i], file, IMG_SIZE, IMG_SIZE);}
- GameDraw 绘画函数
使用 双缓冲(Double Buffering) 双缓冲技术可以完全消除闪烁。
- 其原理是:先在一个“隐藏的缓冲区”中绘制画面。 绘制完成后,一次性将整个画面“贴”到屏幕上。
//绘制界面
void GameDraw()
{BeginBatchDraw();//开始双缓冲【开始批量绘制】for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == -1){putimage(k* IMG_SIZE, i * IMG_SIZE, &img[9]);}else {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[map[i][k]]);}}}EndBatchDraw();//结束双缓冲【结束批量绘制(一次性显示到屏幕)】
}
- 完整代码:
#include<stdio.h>//标准输入输出库,用于printf等函数。
#include<graphics.h>//包含图形库头文件
#include<stdlib.h>//标准库,主要用于srand、rand等函数。
#include<time.h>//时间库,用于获取当前时间作为随机数种子。
#define _CRT_SECURE_NO_WARNINGS//关闭安全警告,防止sprintf等函数报错
#define ROW 10 //行
#define COL 10 //列
#define IMAGENUM 12 //图片数量
#define IMG_SIZE 50 //图片大小
int map[ROW][COL];
IMAGE img[IMAGENUM];//定义图片数组,存储所有图片//初始化数据
void GameInit()
{srand((unsigned int)time(NULL));//设置随机数种子for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){map[i][k] = 0;}}//埋雷 -1:表示此处有雷 【总共10个雷】for (int i = 0; i < ROW; ) {int r = rand() % 10;int c = rand() % 10;if (map[r][c]==0){map[r][c] = -1;i++;//只有成功埋雷才算数,确保10个雷}}//把以雷为中心的九宫格的数据都加上1,除了雷之外for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){//如果当前位置是雷if (map[i][k]==-1){//九宫格for (int r = i - 1; r <= i + 1; r++){for (int c = k - 1; c <= k + 1; c++){//判断是否越界if (r >= 0 && r < ROW && c >= 0 && c < COL && map[r][c] != -1){map[r][c]++;}}}}}}//加载图片(赋值)for (int i = 0; i < IMAGENUM; i++){char file[20] = "";sprintf(file, "./image/%d.jpg", i);loadimage(&img[i], file, IMG_SIZE, IMG_SIZE);}
}//绘制界面
void GameDraw()
{BeginBatchDraw();//开始双缓冲【开始批量绘制】for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == -1){putimage(k* IMG_SIZE, i * IMG_SIZE, &img[9]);}else {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[map[i][k]]);}}}EndBatchDraw();//结束双缓冲【结束批量绘制(一次性显示到屏幕)】
}//打印二维数组
void show()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){printf("%2d ", map[i][k]);}putchar('\n');}
}int main()
{// 创建窗口:一行有十张图片每张图片40pxinitgraph(IMG_SIZE * ROW, IMG_SIZE * COL);GameInit();show();// 游戏主循环while (true) {GameDraw();}return 0;
}
(4)鼠标点击打开格子
- 加密格子
//对数据进行加密
for (int i = 0; i < ROW; i++)
{for (int k = 0; k < COL; k++) {map[i][k]+= 20;}
}
- 修改GameDraw
void GameDraw()
{BeginBatchDraw();//开始双缓冲【开始批量绘制】for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == -1){putimage(k* IMG_SIZE, i * IMG_SIZE, &img[9]);}else if(map[i][k] >= 0 && map[i][k] <= 8){putimage(k * IMG_SIZE, i * IMG_SIZE, &img[map[i][k]]);}else if (map[i][k]>=19 && map[i][k]<=28) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[10]);}else if (map[i][k] >= 39) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[11]);}}}EndBatchDraw();//结束双缓冲【结束批量绘制(一次性显示到屏幕)】
}
- 添加鼠标事件
void MouseEvent()
{//检测是否有鼠标消息if (MouseHit()){//获取鼠标消息,坐标,左键还是右键MOUSEMSG msg = GetMouseMsg();//把坐标转化成数组下标int openr = msg.y / IMG_SIZE;int openc = msg.x / IMG_SIZE;//判断是左键还是右键switch (msg.uMsg){case WM_LBUTTONDOWN://左键//如果格子没有打开,就打开格子,否则不做处理if (map[openr][openc] >= 19){map[openr][openc] -= 20;}break;case WM_RBUTTONDOWN://右键【标记】//如果格子没有打开,且没有标记,就标记if (map[openr][openc] >= 19 && map[openr][openc] <= 28){map[openr][openc] += 20;}else if (map[openr][openc] >= 39)//如果格子没有打开,且有标记,才可以取消标记{map[openr][openc] -= 20;}break;}}
}
- 完整代码
#include<stdio.h>//标准输入输出库,用于printf等函数。
#include<graphics.h>//包含图形库头文件
#include<stdlib.h>//标准库,主要用于srand、rand等函数。
#include<time.h>//时间库,用于获取当前时间作为随机数种子。
#define _CRT_SECURE_NO_WARNINGS//关闭安全警告,防止sprintf等函数报错
#define ROW 10 //行
#define COL 10 //列
#define IMAGENUM 12 //图片数量
#define IMG_SIZE 50 //图片大小
int map[ROW][COL];
IMAGE img[IMAGENUM];//定义图片数组,存储所有图片//初始化数据
void GameInit()
{srand((unsigned int)time(NULL));//设置随机数种子for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){map[i][k] = 0;}}//埋雷 -1:表示此处有雷 【总共10个雷】for (int i = 0; i < ROW; ) {int r = rand() % 10;int c = rand() % 10;if (map[r][c]==0){map[r][c] = -1;i++;//只有成功埋雷才算数,确保10个雷}}//把以雷为中心的九宫格的数据都加上1,除了雷之外for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){//如果当前位置是雷if (map[i][k]==-1){//九宫格for (int r = i - 1; r <= i + 1; r++){for (int c = k - 1; c <= k + 1; c++){//判断是否越界if (r >= 0 && r < ROW && c >= 0 && c < COL && map[r][c] != -1){map[r][c]++;}}}}}}//加载图片(赋值)for (int i = 0; i < IMAGENUM; i++){char file[20] = "";sprintf(file, "./image/%d.jpg", i);loadimage(&img[i], file, IMG_SIZE, IMG_SIZE);}//对数据进行加密for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++) {map[i][k]+= 20;}}
}//绘制界面
void GameDraw()
{BeginBatchDraw();//开始双缓冲【开始批量绘制】for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == -1){putimage(k* IMG_SIZE, i * IMG_SIZE, &img[9]);}else if(map[i][k] >= 0 && map[i][k] <= 8){putimage(k * IMG_SIZE, i * IMG_SIZE, &img[map[i][k]]);}else if (map[i][k]>=19 && map[i][k]<=28) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[10]);}else if (map[i][k] >= 39) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[11]);}}}EndBatchDraw();//结束双缓冲【结束批量绘制(一次性显示到屏幕)】
}//打印二维数组
void show()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){printf("%2d ", map[i][k]);}putchar('\n');}
}//鼠标事件
void MouseEvent()
{//检测是否有鼠标消息if (MouseHit()){//获取鼠标消息,坐标,左键还是右键MOUSEMSG msg = GetMouseMsg();//把坐标转化成数组下标int openr = msg.y / IMG_SIZE;int openc = msg.x / IMG_SIZE;//判断是左键还是右键switch (msg.uMsg){case WM_LBUTTONDOWN://左键//如果格子没有打开,就打开格子,否则不做处理if (map[openr][openc] >= 19){map[openr][openc] -= 20;}break;case WM_RBUTTONDOWN://右键【标记】//如果格子没有打开,且没有标记,就标记if (map[openr][openc] >= 19 && map[openr][openc] <= 28){map[openr][openc] += 20;}else if (map[openr][openc] >= 39)//如果格子没有打开,且有标记,才可以取消标记{map[openr][openc] -= 20;}break;}}
}
int main()
{// 创建窗口:一行有十张图片每张图片50pxinitgraph(IMG_SIZE * ROW, IMG_SIZE * COL);GameInit();show();// 游戏主循环while (true) {GameDraw();MouseEvent();}return 0;
}
(5)炸开连续区域
- 添加openNull函数
//炸开连续区域
void openNull(int x,int y) {//如果当前位置为0,就把九宫格都打开 if (map[x][y] == 0){//九宫格for (int r = x - 1; r <= x + 1; r++){for (int c = y - 1; c <= y + 1; c++){if ((r >= 0 && r < ROW && c >= 0 && c < COL) && //判断是否越界(map[r][c] > 19) &&//判断没有打开(map[r][c] != 19) //判断不是雷){map[r][c] -= 20;//打开openNull(r, c);//如果当前位置为0,就把九宫格都打开}}}}
}
openNull 函数是一个利用递归策略来自动展开空白区域的实用工具,在扫雷类游戏中,它有助于简化玩家的操作流程,提高游戏的流畅性和趣味性。
- 完整代码
#include<stdio.h>//标准输入输出库,用于printf等函数。
#include<graphics.h>//包含图形库头文件
#include<stdlib.h>//标准库,主要用于srand、rand等函数。
#include<time.h>//时间库,用于获取当前时间作为随机数种子。
#define _CRT_SECURE_NO_WARNINGS//关闭安全警告,防止sprintf等函数报错
#define ROW 10 //行
#define COL 10 //列
#define IMAGENUM 12 //图片数量
#define IMG_SIZE 50 //图片大小
int map[ROW][COL];
IMAGE img[IMAGENUM];//定义图片数组,存储所有图片//初始化数据
void GameInit()
{srand((unsigned int)time(NULL));//设置随机数种子for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){map[i][k] = 0;}}//埋雷 -1:表示此处有雷 【总共10个雷】for (int i = 0; i < ROW; ) {int r = rand() % 10;int c = rand() % 10;if (map[r][c]==0){map[r][c] = -1;i++;//只有成功埋雷才算数,确保10个雷}}//把以雷为中心的九宫格的数据都加上1,除了雷之外for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){//如果当前位置是雷if (map[i][k]==-1){//九宫格for (int r = i - 1; r <= i + 1; r++){for (int c = k - 1; c <= k + 1; c++){//判断是否越界if (r >= 0 && r < ROW && c >= 0 && c < COL && map[r][c] != -1){map[r][c]++;}}}}}}//加载图片(赋值)for (int i = 0; i < IMAGENUM; i++){char file[20] = "";sprintf(file, "./image/%d.jpg", i);loadimage(&img[i], file, IMG_SIZE, IMG_SIZE);}//对数据进行加密for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++) {map[i][k]+= 20;}}
}//绘制界面
void GameDraw()
{BeginBatchDraw();//开始双缓冲【开始批量绘制】for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == -1){putimage(k* IMG_SIZE, i * IMG_SIZE, &img[9]);}else if(map[i][k] >= 0 && map[i][k] <= 8){putimage(k * IMG_SIZE, i * IMG_SIZE, &img[map[i][k]]);}else if (map[i][k]>=19 && map[i][k]<=28) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[10]);}else if (map[i][k] >= 39) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[11]);}}}EndBatchDraw();//结束双缓冲【结束批量绘制(一次性显示到屏幕)】
}//打印二维数组
void show()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){printf("%2d ", map[i][k]);}putchar('\n');}
}//炸开连续区域
void openNull(int x,int y) {//如果当前位置为0,就把九宫格都打开 if (map[x][y] == 0){//九宫格for (int r = x - 1; r <= x + 1; r++){for (int c = y - 1; c <= y + 1; c++){if ((r >= 0 && r < ROW && c >= 0 && c < COL) && //判断是否越界(map[r][c] > 19) &&//判断没有打开(map[r][c] != 19) //判断不是雷){map[r][c] -= 20;//打开openNull(r, c);//如果当前位置为0,就把九宫格都打开}}}}
}//鼠标事件
void MouseEvent()
{//检测是否有鼠标消息if (MouseHit()){//获取鼠标消息,坐标,左键还是右键MOUSEMSG msg = GetMouseMsg();//把坐标转化成数组下标int openr = msg.y / IMG_SIZE;int openc = msg.x / IMG_SIZE;//判断是左键还是右键switch (msg.uMsg){case WM_LBUTTONDOWN://左键//如果格子没有打开,就打开格子,否则不做处理if (map[openr][openc] >= 19){map[openr][openc] -= 20;openNull(openr, openc);//如果当前位置为0,就把九宫格都打开}break;case WM_RBUTTONDOWN://右键【标记】//如果格子没有打开,且没有标记,就标记if (map[openr][openc] >= 19 && map[openr][openc] <= 28){map[openr][openc] += 20;}else if (map[openr][openc] >= 39)//如果格子没有打开,且有标记,才可以取消标记{map[openr][openc] -= 20;}break;}}
}
int main()
{// 创建窗口:一行有十张图片每张图片50pxinitgraph(IMG_SIZE * ROW, IMG_SIZE * COL);GameInit();show();// 游戏主循环while (true) {GameDraw();MouseEvent();}return 0;
}
(6)游戏输赢判定
- 添加 游戏胜利/失败的判断机制,并使用你定义的 flag 变量来控制游戏状态。
int flag = 0; // 游戏状态:0-进行中,1-胜利,-1-失败
1️⃣ 修改 MouseEvent:检测是否点到雷(失败)
//鼠标事件
void MouseEvent()
{if (flag != 0) return; // 游戏已结束,不再处理if (MouseHit()) //检测是否有鼠标消息{MOUSEMSG msg = GetMouseMsg();int openr = msg.y / IMG_SIZE; //把坐标转化成数组下标int openc = msg.x / IMG_SIZE;switch (msg.uMsg) //获取鼠标消息,坐标,左键还是右键{case WM_LBUTTONDOWN: // 左键点击if (map[openr][openc] >= 19) //如果格子没有打开,就打开格子,否则不做处理{// 如果是雷(加密后为19)if (map[openr][openc] == 19){map[openr][openc] = -1; // 显示雷flag = -1; // 标记失败GameOver();}else{map[openr][openc] -= 20; // 打开openNull(openr, openc); //如果当前位置为0,就把九宫格都打开// 检查是否胜利if (CheckWin()){flag = 1;GameOver();}}}break;case WM_RBUTTONDOWN: // 右键标记if (map[openr][openc] >= 19 && map[openr][openc] <= 28){map[openr][openc] += 20; // 标记为39+}else if (map[openr][openc] >= 39) //如果格子没有打开,且有标记,才可以取消标记{map[openr][openc] -= 20; // 取消标记}break;}}
}
2️⃣ 添加 CheckWin() 函数:检查是否所有安全格子都已翻开(胜利)
// 检查是否所有非雷格子都已翻开(胜利条件)
int CheckWin()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){// 如果是未打开的非雷格子(加密状态 20~28),说明还没赢if (map[i][k] >= 20 && map[i][k] <= 28){return 0; // 还没赢}}}return 1; // 所有安全格子都打开了,胜利!
}
3️⃣ 添加 GameOver() 函数:显示结果并暂停游戏
// 游戏结束:显示结果,停止交互
void GameOver()
{// === 第一步:游戏结束,无论胜利还是失败,都显示所有雷 ===for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == 19) // 未翻开的雷(加密状态){map[i][k] = -1; // 显示为雷}else if (map[i][k] == 39) // 玩家标记为旗子的雷(加密+标记){map[i][k] = -1; // 也显示为雷(表示正确标记)}}}// === 第二步:强制刷新画面,让用户看到结果 ===EndBatchDraw(); // 结束双缓冲,强制刷新GameDraw(); // 再画一次,确保雷显示出来FlushBatchDraw(); // 确保画面立即更新到屏幕// === 第三步:弹出对话框(此时用户已看到雷) ===int isok;if (flag == 1){isok = MessageBox(GetHWnd(), _T("恭喜!你赢了!是否再来一局?"), _T("胜利"), MB_OKCANCEL | MB_ICONINFORMATION);}else // flag == -1{isok = MessageBox(GetHWnd(), _T("踩到雷了!游戏结束!是否再来一局?"), _T("失败"), MB_OKCANCEL | MB_ICONERROR);}// === 第四步:处理用户选择 ===if (isok == IDOK){flag = 0;GameInit(); // 重新初始化/*system("cls");show();*/ // 控制台打印数组,仅调试用BeginBatchDraw(); // 重新开启双缓冲}else{closegraph();// 关闭图形窗口exit(0);// 退出程序代码}
}
- 完整代码
#include<stdio.h>//标准输入输出库,用于printf等函数。
#include<graphics.h>//包含图形库头文件
#include<stdlib.h>//标准库,主要用于srand、rand等函数。
#include<time.h>//时间库,用于获取当前时间作为随机数种子。
#define _CRT_SECURE_NO_WARNINGS//关闭安全警告,防止sprintf等函数报错
#define ROW 10 //行
#define COL 10 //列
#define IMAGENUM 12 //图片数量
#define IMG_SIZE 50 //图片大小
int map[ROW][COL];
IMAGE img[IMAGENUM];//定义图片数组,存储所有图片
int flag = 0; // 游戏状态:0-进行中,1-胜利,-1-失败//初始化数据
void GameInit()
{srand((unsigned int)time(NULL));//设置随机数种子for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){map[i][k] = 0;}}//埋雷 -1:表示此处有雷 【总共10个雷】for (int i = 0; i < ROW; ) {int r = rand() % 10;int c = rand() % 10;if (map[r][c] == 0){map[r][c] = -1;i++;//只有成功埋雷才算数,确保10个雷}}//把以雷为中心的九宫格的数据都加上1,除了雷之外for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){//如果当前位置是雷if (map[i][k] == -1){//九宫格for (int r = i - 1; r <= i + 1; r++){for (int c = k - 1; c <= k + 1; c++){//判断是否越界if (r >= 0 && r < ROW && c >= 0 && c < COL && map[r][c] != -1){map[r][c]++;}}}}}}//加载图片(赋值)for (int i = 0; i < IMAGENUM; i++){char file[20] = "";sprintf(file, "./image/%d.jpg", i);loadimage(&img[i], file, IMG_SIZE, IMG_SIZE);}//对数据进行加密for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++) {map[i][k] += 20;}}
}//绘制界面
void GameDraw()
{BeginBatchDraw();//开始双缓冲【开始批量绘制】for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == -1){putimage(k * IMG_SIZE, i * IMG_SIZE, &img[9]);}else if (map[i][k] >= 0 && map[i][k] <= 8) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[map[i][k]]);}else if (map[i][k] >= 19 && map[i][k] <= 28) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[10]);}else if (map[i][k] >= 39) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[11]);}}}EndBatchDraw();//结束双缓冲【结束批量绘制(一次性显示到屏幕)】
}//打印二维数组
void show()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){printf("%2d ", map[i][k]);}putchar('\n');}
}//炸开连续区域
void openNull(int x, int y) {//如果当前位置为0,就把九宫格都打开 if (map[x][y] == 0){//九宫格for (int r = x - 1; r <= x + 1; r++){for (int c = y - 1; c <= y + 1; c++){if ((r >= 0 && r < ROW && c >= 0 && c < COL) && //判断是否越界(map[r][c] > 19) &&//判断没有打开(map[r][c] != 19) //判断不是雷){map[r][c] -= 20;//打开openNull(r, c);//如果当前位置为0,就把九宫格都打开}}}}
}// 检查是否所有非雷格子都已翻开(胜利条件)
int CheckWin()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){// 如果是未打开的非雷格子(加密状态 20~28),说明还没赢if (map[i][k] >= 20 && map[i][k] <= 28){return 0; // 还没赢}}}return 1; // 所有安全格子都打开了,胜利!
}// 游戏结束:显示结果,停止交互
void GameOver()
{// === 第一步:游戏结束,无论胜利还是失败,都显示所有雷 ===for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == 19) // 未翻开的雷(加密状态){map[i][k] = -1; // 显示为雷}else if (map[i][k] == 39) // 玩家标记为旗子的雷(加密+标记){map[i][k] = -1; // 也显示为雷(表示正确标记)}}}// === 第二步:强制刷新画面,让用户看到结果 ===EndBatchDraw(); // 结束双缓冲,强制刷新GameDraw(); // 再画一次,确保雷显示出来FlushBatchDraw(); // 确保画面立即更新到屏幕// === 第三步:弹出对话框(此时用户已看到雷) ===int isok;if (flag == 1){isok = MessageBox(GetHWnd(), _T("恭喜!你赢了!是否再来一局?"), _T("胜利"), MB_OKCANCEL | MB_ICONINFORMATION);}else // flag == -1{isok = MessageBox(GetHWnd(), _T("踩到雷了!游戏结束!是否再来一局?"), _T("失败"), MB_OKCANCEL | MB_ICONERROR);}// === 第四步:处理用户选择 ===if (isok == IDOK){flag = 0;GameInit(); // 重新初始化/*system("cls");show();*/ // 控制台打印数组,仅调试用BeginBatchDraw(); // 重新开启双缓冲}else{closegraph();// 关闭图形窗口exit(0);// 退出程序代码}
}//鼠标事件
void MouseEvent()
{if (flag != 0) return; // 游戏已结束,不再处理if (MouseHit()) //检测是否有鼠标消息{MOUSEMSG msg = GetMouseMsg();int openr = msg.y / IMG_SIZE; //把坐标转化成数组下标int openc = msg.x / IMG_SIZE;switch (msg.uMsg) //获取鼠标消息,坐标,左键还是右键{case WM_LBUTTONDOWN: // 左键点击if (map[openr][openc] >= 19) //如果格子没有打开,就打开格子,否则不做处理{// 如果是雷(加密后为19)if (map[openr][openc] == 19){map[openr][openc] = -1; // 显示雷flag = -1; // 标记失败GameOver();}else{map[openr][openc] -= 20; // 打开openNull(openr, openc); //如果当前位置为0,就把九宫格都打开// 检查是否胜利if (CheckWin()){flag = 1;GameOver();}}}break;case WM_RBUTTONDOWN: // 右键标记if (map[openr][openc] >= 19 && map[openr][openc] <= 28){map[openr][openc] += 20; // 标记为39+}else if (map[openr][openc] >= 39) //如果格子没有打开,且有标记,才可以取消标记{map[openr][openc] -= 20; // 取消标记}break;}}
}int main()
{// 创建窗口:一行有十张图片每张图片50pxinitgraph(IMG_SIZE * ROW, IMG_SIZE * COL);GameInit();show();// 游戏主循环while (true) {GameDraw();MouseEvent();}return 0;
}
(7)添加音效
- 加载多媒体库,支持声音播放
#include<mmsystem.h>
#pragma comment(lib,"winmm.lib") //加载多媒体库,支持声音播放
- GameInit函数中添加 背景音乐
//初始化数据
void GameInit()
{mciSendString("open ./image/start.mp3 alias BGM", 0, 0, 0); //加载背景音乐mciSendString("play BGM", 0, 0, 0); //循环播放背景音乐
- GameOver函数中关闭音乐
mciSendString("close BGM", 0, 0, 0); //关闭背景音乐
- 添加点击鼠标左键 音效
mciSendString("close CLICK", 0, 0, 0); //停止上一次的点击音效
mciSendString("open ./image/click.wav alias CLICK", 0, 0, 0); //加载点击音效
mciSendString("play CLICK", 0, 0, 0); //播放点击音效
- 添加点击鼠标右键 音效
mciSendString("close CLICK", 0, 0, 0); //停止上一次的点击音效
mciSendString("open ./image/rightClick.wav alias CLICK", 0, 0, 0); //加载点击音效
mciSendString("play CLICK", 0, 0, 0); //播放点击音效
- 完整代码
#include<stdio.h>//标准输入输出库,用于printf等函数。
#include<graphics.h>//包含图形库头文件
#include<stdlib.h>//标准库,主要用于srand、rand等函数。
#include<time.h>//时间库,用于获取当前时间作为随机数种子。
#include<mmsystem.h>
#pragma comment(lib,"winmm.lib") //加载多媒体库,支持声音播放
#define _CRT_SECURE_NO_WARNINGS//关闭安全警告,防止sprintf等函数报错
#define ROW 10 //行
#define COL 10 //列
#define IMAGENUM 12 //图片数量
#define IMG_SIZE 50 //图片大小
int map[ROW][COL];
IMAGE img[IMAGENUM];//定义图片数组,存储所有图片
int flag = 0; // 游戏状态:0-进行中,1-胜利,-1-失败//初始化数据
void GameInit()
{mciSendString("open ./image/start.mp3 alias BGM", 0, 0, 0); //加载背景音乐mciSendString("play BGM", 0, 0, 0); //循环播放背景音乐srand((unsigned int)time(NULL));//设置随机数种子for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){map[i][k] = 0;}}//埋雷 -1:表示此处有雷 【总共10个雷】for (int i = 0; i < ROW; ) {int r = rand() % 10;int c = rand() % 10;if (map[r][c] == 0){map[r][c] = -1;i++;//只有成功埋雷才算数,确保10个雷}}//把以雷为中心的九宫格的数据都加上1,除了雷之外for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){//如果当前位置是雷if (map[i][k] == -1){//九宫格for (int r = i - 1; r <= i + 1; r++){for (int c = k - 1; c <= k + 1; c++){//判断是否越界if (r >= 0 && r < ROW && c >= 0 && c < COL && map[r][c] != -1){map[r][c]++;}}}}}}//加载图片(赋值)for (int i = 0; i < IMAGENUM; i++){char file[20] = "";sprintf(file, "./image/%d.jpg", i);loadimage(&img[i], file, IMG_SIZE, IMG_SIZE);}//对数据进行加密for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++) {map[i][k] += 20;}}
}//绘制界面
void GameDraw()
{BeginBatchDraw();//开始双缓冲【开始批量绘制】for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == -1){putimage(k * IMG_SIZE, i * IMG_SIZE, &img[9]);}else if (map[i][k] >= 0 && map[i][k] <= 8) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[map[i][k]]);}else if (map[i][k] >= 19 && map[i][k] <= 28) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[10]);}else if (map[i][k] >= 39) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[11]);}}}EndBatchDraw();//结束双缓冲【结束批量绘制(一次性显示到屏幕)】
}//打印二维数组
void show()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){printf("%2d ", map[i][k]);}putchar('\n');}
}//炸开连续区域
void openNull(int x, int y) {//如果当前位置为0,就把九宫格都打开 if (map[x][y] == 0){//九宫格for (int r = x - 1; r <= x + 1; r++){for (int c = y - 1; c <= y + 1; c++){if ((r >= 0 && r < ROW && c >= 0 && c < COL) && //判断是否越界(map[r][c] > 19) &&//判断没有打开(map[r][c] != 19) //判断不是雷){map[r][c] -= 20;//打开openNull(r, c);//如果当前位置为0,就把九宫格都打开}}}}
}// 检查是否所有非雷格子都已翻开(胜利条件)
int CheckWin()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){// 如果是未打开的非雷格子(加密状态 20~28),说明还没赢if (map[i][k] >= 20 && map[i][k] <= 28){return 0; // 还没赢}}}return 1; // 所有安全格子都打开了,胜利!
}// 游戏结束:显示结果,停止交互
void GameOver()
{// === 第一步:游戏结束,无论胜利还是失败,都显示所有雷 ===for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == 19) // 未翻开的雷(加密状态){map[i][k] = -1; // 显示为雷}else if (map[i][k] == 39) // 玩家标记为旗子的雷(加密+标记){map[i][k] = -1; // 也显示为雷(表示正确标记)}}}// === 第二步:强制刷新画面,让用户看到结果 ===EndBatchDraw(); // 结束双缓冲,强制刷新GameDraw(); // 再画一次,确保雷显示出来FlushBatchDraw(); // 确保画面立即更新到屏幕// === 第三步:弹出对话框(此时用户已看到雷) ===int isok;if (flag == 1){isok = MessageBox(GetHWnd(), _T("恭喜!你赢了!是否再来一局?"), _T("胜利"), MB_OKCANCEL | MB_ICONINFORMATION);}else // flag == -1{isok = MessageBox(GetHWnd(), _T("踩到雷了!游戏结束!是否再来一局?"), _T("失败"), MB_OKCANCEL | MB_ICONERROR);}// === 第四步:处理用户选择 ===if (isok == IDOK){flag = 0;GameInit(); // 重新初始化/*system("cls");show();*/ // 控制台打印数组,仅调试用BeginBatchDraw(); // 重新开启双缓冲}else{closegraph();// 关闭图形窗口exit(0);// 退出程序代码}mciSendString("close BGM", 0, 0, 0); //关闭背景音乐
}//鼠标事件
void MouseEvent()
{if (flag != 0) return; // 游戏已结束,不再处理if (MouseHit()) //检测是否有鼠标消息{MOUSEMSG msg = GetMouseMsg();int openr = msg.y / IMG_SIZE; //把坐标转化成数组下标int openc = msg.x / IMG_SIZE;switch (msg.uMsg) //获取鼠标消息,坐标,左键还是右键{case WM_LBUTTONDOWN: // 左键点击if (map[openr][openc] >= 19) //如果格子没有打开,就打开格子,否则不做处理{mciSendString("close CLICK", 0, 0, 0); //停止上一次的点击音效mciSendString("open ./image/click.wav alias CLICK", 0, 0, 0); //加载点击音效mciSendString("play CLICK", 0, 0, 0); //播放点击音效// 如果是雷(加密后为19)if (map[openr][openc] == 19){map[openr][openc] = -1; // 显示雷flag = -1; // 标记失败GameOver();}else{map[openr][openc] -= 20; // 打开openNull(openr, openc); //如果当前位置为0,就把九宫格都打开// 检查是否胜利if (CheckWin()){flag = 1;GameOver();}}}break;case WM_RBUTTONDOWN: // 右键标记if (map[openr][openc] >= 19 && map[openr][openc] <= 28){mciSendString("close CLICK", 0, 0, 0); //停止上一次的点击音效mciSendString("open ./image/rightClick.wav alias CLICK", 0, 0, 0); //加载点击音效mciSendString("play CLICK", 0, 0, 0); //播放点击音效map[openr][openc] += 20; // 标记为39+}else if (map[openr][openc] >= 39) //如果格子没有打开,且有标记,才可以取消标记{mciSendString("close CLICK", 0, 0, 0); //停止上一次的点击音效mciSendString("open ./image/rightClick.wav alias CLICK", 0, 0, 0); //加载点击音效mciSendString("play CLICK", 0, 0, 0); //播放点击音效map[openr][openc] -= 20; // 取消标记}break;}}
}int main()
{// 创建窗口:一行有十张图片每张图片50pxinitgraph(IMG_SIZE * ROW, IMG_SIZE * COL);GameInit();show();// 游戏主循环while (true) {GameDraw();MouseEvent();}return 0;
}
(8)添加计时功能
- 新增变量
gameTime: 记录游戏已进行的时间(秒)
startTime: 游戏开始的时间戳
timerRunning: 计时器运行状态
// 添加倒计时相关变量
int gameTime = 0; // 游戏已进行的时间(秒)
int startTime = 0; // 游戏开始的时间戳
int timerRunning = 0; // 计时器状态:0-停止,1-运行
- GameInit初始化
//初始化数据
void GameInit()
{//....// 初始化计时器gameTime = 0;startTime = (int)time(NULL); // 获取当前时间戳timerRunning = 1; // 开始计时
- 新增函数
UpdateTimer(): 更新游戏时间
// 更新游戏时间
void UpdateTimer()
{if (timerRunning) {gameTime = (int)time(NULL) - startTime;}
}
DrawTimer(): 在屏幕上绘制时间显示
// 绘制时间显示
void DrawTimer()
{// 设置文字颜色和样式settextcolor(WHITE);settextstyle(20, 0, _T("宋体"));setbkmode(TRANSPARENT); // 透明背景// 格式化时间显示char timeStr[20];int minutes = gameTime / 60;int seconds = gameTime % 60;sprintf(timeStr, "时间: %02d:%02d", minutes, seconds);// 在窗口右上角显示时间outtextxy(COL * IMG_SIZE - 120, 10, timeStr);
}
- 调用函数
void GameDraw()
{//....// 绘制计时器UpdateTimer();DrawTimer();
}
StopTimer(): 停止计时器
// 停止计时器
void StopTimer()
{timerRunning = 0;
}
- 调用函数
// 游戏结束:显示结果,停止交互
void GameOver()
{// 停止计时器StopTimer();
- 功能特点
实时显示: 在窗口右上角显示格式化的时间(分:秒)
自动停止: 游戏结束时自动停止计时
结果显示: 在胜利/失败对话框中显示最终用时
重新开始: 重新开始游戏时会重置计时器
- 完整代码
#include<stdio.h>//标准输入输出库,用于printf等函数。
#include<graphics.h>//包含图形库头文件
#include<stdlib.h>//标准库,主要用于srand、rand等函数。
#include<time.h>//时间库,用于获取当前时间作为随机数种子。
#include<mmsystem.h>
#pragma comment(lib,"winmm.lib") //加载多媒体库,支持声音播放
#define _CRT_SECURE_NO_WARNINGS//关闭安全警告,防止sprintf等函数报错
#define ROW 10 //行
#define COL 10 //列
#define IMAGENUM 12 //图片数量
#define IMG_SIZE 50 //图片大小
int map[ROW][COL];
IMAGE img[IMAGENUM];//定义图片数组,存储所有图片
int flag = 0; // 游戏状态:0-进行中,1-胜利,-1-失败
// 添加倒计时相关变量
int gameTime = 0; // 游戏已进行的时间(秒)
int startTime = 0; // 游戏开始的时间戳
int timerRunning = 0; // 计时器状态:0-停止,1-运行//初始化数据
void GameInit()
{mciSendString("open ./image/start.mp3 alias BGM", 0, 0, 0); //加载背景音乐mciSendString("play BGM", 0, 0, 0); //循环播放背景音乐srand((unsigned int)time(NULL));//设置随机数种子// 初始化计时器gameTime = 0;startTime = (int)time(NULL); // 获取当前时间戳timerRunning = 1; // 开始计时for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){map[i][k] = 0;}}//埋雷 -1:表示此处有雷 【总共10个雷】for (int i = 0; i < ROW; ) {int r = rand() % 10;int c = rand() % 10;if (map[r][c] == 0){map[r][c] = -1;i++;//只有成功埋雷才算数,确保10个雷}}//把以雷为中心的九宫格的数据都加上1,除了雷之外for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){//如果当前位置是雷if (map[i][k] == -1){//九宫格for (int r = i - 1; r <= i + 1; r++){for (int c = k - 1; c <= k + 1; c++){//判断是否越界if (r >= 0 && r < ROW && c >= 0 && c < COL && map[r][c] != -1){map[r][c]++;}}}}}}//加载图片(赋值)for (int i = 0; i < IMAGENUM; i++){char file[20] = "";sprintf(file, "./image/%d.jpg", i);loadimage(&img[i], file, IMG_SIZE, IMG_SIZE);}//对数据进行加密for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++) {map[i][k] += 20;}}
}// 更新游戏时间
void UpdateTimer()
{if (timerRunning) {gameTime = (int)time(NULL) - startTime;}
}// 绘制时间显示
void DrawTimer()
{// 设置文字颜色和样式settextcolor(WHITE);settextstyle(20, 0, _T("宋体"));setbkmode(TRANSPARENT); // 透明背景// 格式化时间显示char timeStr[20];int minutes = gameTime / 60;int seconds = gameTime % 60;sprintf(timeStr, "时间: %02d:%02d", minutes, seconds);// 在窗口右上角显示时间outtextxy(COL * IMG_SIZE - 120, 10, timeStr);
}//绘制界面
void GameDraw()
{BeginBatchDraw();//开始双缓冲【开始批量绘制】for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == -1){putimage(k * IMG_SIZE, i * IMG_SIZE, &img[9]);}else if (map[i][k] >= 0 && map[i][k] <= 8) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[map[i][k]]);}else if (map[i][k] >= 19 && map[i][k] <= 28) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[10]);}else if (map[i][k] >= 39) {putimage(k * IMG_SIZE, i * IMG_SIZE, &img[11]);}}}// 绘制计时器UpdateTimer();DrawTimer();EndBatchDraw();//结束双缓冲【结束批量绘制(一次性显示到屏幕)】
}//打印二维数组
void show()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){printf("%2d ", map[i][k]);}putchar('\n');}
}//炸开连续区域
void openNull(int x, int y) {//如果当前位置为0,就把九宫格都打开 if (map[x][y] == 0){//九宫格for (int r = x - 1; r <= x + 1; r++){for (int c = y - 1; c <= y + 1; c++){if ((r >= 0 && r < ROW && c >= 0 && c < COL) && //判断是否越界(map[r][c] > 19) &&//判断没有打开(map[r][c] != 19) //判断不是雷){map[r][c] -= 20;//打开openNull(r, c);//如果当前位置为0,就把九宫格都打开}}}}
}// 检查是否所有非雷格子都已翻开(胜利条件)
int CheckWin()
{for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){// 如果是未打开的非雷格子(加密状态 20~28),说明还没赢if (map[i][k] >= 20 && map[i][k] <= 28){return 0; // 还没赢}}}return 1; // 所有安全格子都打开了,胜利!
}// 停止计时器
void StopTimer()
{timerRunning = 0;
}// 游戏结束:显示结果,停止交互
void GameOver()
{// 停止计时器StopTimer();// === 第一步:游戏结束,无论胜利还是失败,都显示所有雷 ===for (int i = 0; i < ROW; i++){for (int k = 0; k < COL; k++){if (map[i][k] == 19) // 未翻开的雷(加密状态){map[i][k] = -1; // 显示为雷}else if (map[i][k] == 39) // 玩家标记为旗子的雷(加密+标记){map[i][k] = -1; // 也显示为雷(表示正确标记)}}}// === 第二步:强制刷新画面,让用户看到结果 ===EndBatchDraw(); // 结束双缓冲,强制刷新GameDraw(); // 再画一次,确保雷显示出来FlushBatchDraw(); // 确保画面立即更新到屏幕// === 第三步:弹出对话框(此时用户已看到雷) ===int isok;if (flag == 1){isok = MessageBox(GetHWnd(), _T("恭喜!你赢了!是否再来一局?"), _T("胜利"), MB_OKCANCEL | MB_ICONINFORMATION);}else // flag == -1{isok = MessageBox(GetHWnd(), _T("踩到雷了!游戏结束!是否再来一局?"), _T("失败"), MB_OKCANCEL | MB_ICONERROR);}// === 第四步:处理用户选择 ===if (isok == IDOK){flag = 0;GameInit(); // 重新初始化/*system("cls");show();*/ // 控制台打印数组,仅调试用BeginBatchDraw(); // 重新开启双缓冲}else{closegraph();// 关闭图形窗口exit(0);// 退出程序代码}mciSendString("close BGM", 0, 0, 0); //关闭背景音乐
}//鼠标事件
void MouseEvent()
{if (flag != 0) return; // 游戏已结束,不再处理if (MouseHit()) //检测是否有鼠标消息{MOUSEMSG msg = GetMouseMsg();int openr = msg.y / IMG_SIZE; //把坐标转化成数组下标int openc = msg.x / IMG_SIZE;switch (msg.uMsg) //获取鼠标消息,坐标,左键还是右键{case WM_LBUTTONDOWN: // 左键点击if (map[openr][openc] >= 19) //如果格子没有打开,就打开格子,否则不做处理{mciSendString("close CLICK", 0, 0, 0); //停止上一次的点击音效mciSendString("open ./image/click.wav alias CLICK", 0, 0, 0); //加载点击音效mciSendString("play CLICK", 0, 0, 0); //播放点击音效// 如果是雷(加密后为19)if (map[openr][openc] == 19){map[openr][openc] = -1; // 显示雷flag = -1; // 标记失败GameOver();}else{map[openr][openc] -= 20; // 打开openNull(openr, openc); //如果当前位置为0,就把九宫格都打开// 检查是否胜利if (CheckWin()){flag = 1;GameOver();}}}break;case WM_RBUTTONDOWN: // 右键标记if (map[openr][openc] >= 19 && map[openr][openc] <= 28){mciSendString("close CLICK", 0, 0, 0); //停止上一次的点击音效mciSendString("open ./image/rightClick.wav alias CLICK", 0, 0, 0); //加载点击音效mciSendString("play CLICK", 0, 0, 0); //播放点击音效map[openr][openc] += 20; // 标记为39+}else if (map[openr][openc] >= 39) //如果格子没有打开,且有标记,才可以取消标记{mciSendString("close CLICK", 0, 0, 0); //停止上一次的点击音效mciSendString("open ./image/rightClick.wav alias CLICK", 0, 0, 0); //加载点击音效mciSendString("play CLICK", 0, 0, 0); //播放点击音效map[openr][openc] -= 20; // 取消标记}break;}}
}int main()
{// 创建窗口:一行有十张图片每张图片50pxinitgraph(IMG_SIZE * ROW, IMG_SIZE * COL);GameInit();show();// 游戏主循环while (true) {GameDraw();MouseEvent();}return 0;
}
(9)打包程序
打包教程
下载打包工具
(10)我的记录
目前个人最高记录,48秒,有没有来挑战我的?