注:除计时器和剩余雷数显示外,其他功能完美还原。
目录
一、程序演示
二、程序信息
1.基础信息
2.前言
3.所需文件
三、代码解析
1.头文件
2.变量声明
3.随机生成雷
4.生成雷位置矩阵
5.生成雷数矩阵
6.绘制界面
7.输赢检测
8.鼠标交互逻辑
9.检测相邻空元素
10.拓展空元素区域
11.按照状态贴图
12.游戏成功
13.游戏失败
14.展示地雷
一、程序演示 二、程序信息 1.基础信息
程序名:扫雷
开发语言:C语言
程序作者:YYYwaiwai
开发工具:VS2019
需求环境:Easy-X Graphics.h 图形库
源码下载:扫雷源码及图片素材
2.前言本人小白一枚,目前大一
该程序只是自己写着玩的一个小游戏,因此并没有完全写完
计时器功能和剩余雷显示功能懒得做了,所以只是半成品
但是其他功能几乎完美复刻经典版扫雷游戏
本程序为本人原创,没有参考任何其他资料或博客
图片素材也为本人绘制,图片素材包含在源码压缩包中
如有问题,请大佬们指正!
3.所需文件如上图,pic文件夹内包含了该程序所需的图片素材
请将问文件夹置于程序根目录下
三、代码解析 1.头文件#include2.变量声明#include #include #include #include #include #pragma comment(lib,"winmm.lib") #pragma warning(disable:4996)
int matrix[16][16] = { 0 }; int number[16][16] = { 0 }; int status[16][16] = { 0 }; int num = 0; int history[40] = { 0 }; int i = 0; int j = 0; int k = 0; int m = 0; int n = 0; int counter = 0; int boom = 0; int his = 0; int select; int wincount = 0; int break_flag = 0; int fail_flag = 0;3.随机生成雷
此处的想法是,建立一个16*16的矩阵,从左到右,从上到下由1~256编号
通过生成40个范围处于【1,256】的随机数,通过对应矩阵的位置,将对应的编号设为雷
为防止生成的随机数有重复的,每生成一个随机数,就将这个数存入his[40]这个数组中
之后生成的随机数需与数组中的元素进行比较,若重复则不会保存在数组中
srand((unsigned int)time(0)); //通过用时间播种生成随机数 for (boom = 0; boom < 40; boom++) { select = rand() % 256 + 1; //生成1~256范围内随机数 if (select < 0 || select > 256) boom--; else { for (his = 0; his < 40; his++) { if (history[his] == select) //与his数组内元素比较 { boom--; break; } if (history[his] == 0) //若没有出现过,则存入his数组 { history[his] = select; break; } } } } //雷位置调试
雷位置调试打印:
4.生成雷位置矩阵通过his数组中的随机数,通过对应关系写入雷矩阵matrix[16][16]中
雷的位置表示为1,非雷的位置表示为0
for(his = 0; his < 40; his++ ) { for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { counter++; if (history[his] == counter) { matrix[i][j] = 1; break_flag = 1; break; } } if (break_flag == 1) break; } break_flag = 0; counter = 0; } //雷矩阵调试
雷矩阵调试打印:
5.生成雷数矩阵通过对matrix矩阵每个元素的周围八个元素计算雷数,并将雷数存入number[16][16]矩阵中
number矩阵中1~8表示雷数,9表示该元素为雷
此处的检测方法并不好,但本人懒得改了。更优的检测方法请参考9.检测相邻空元素。
for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { num = 0; if (matrix[i][j] == 1) { number[i][j] = 9; continue; } else { if (i == 0 && j == 0) { if (matrix[0][1] == 1) num++; if (matrix[1][0] == 1) num++; if (matrix[1][1] == 1) num++; } else if (i == 0 && j == 15) { if (matrix[0][14] == 1) num++; if (matrix[1][15] == 1) num++; if (matrix[1][14] == 1) num++; } else if (i == 15 && j == 0) { if (matrix[15][1] == 1) num++; if (matrix[14][0] == 1) num++; if (matrix[14][1] == 1) num++; } else if (i == 15 && j == 15) { if (matrix[15][14] == 1) num++; if (matrix[14][15] == 1) num++; if (matrix[14][14] == 1) num++; } else if (i == 0) { if (matrix[i][j-1] == 1) num++; if (matrix[i][j + 1] == 1) num++; if (matrix[i + 1][j - 1] == 1) num++; if (matrix[i + 1][j] == 1) num++; if (matrix[i + 1][j + 1] == 1) num++; } else if (i == 15) { if (matrix[i][j - 1] == 1) num++; if (matrix[i][j + 1] == 1) num++; if (matrix[i - 1][j - 1] == 1) num++; if (matrix[i - 1][j] == 1) num++; if (matrix[i - 1][j + 1] == 1) num++; } else if (j == 0) { if (matrix[i - 1][j] == 1) num++; if (matrix[i + 1][j] == 1) num++; if (matrix[i - 1][j + 1] == 1) num++; if (matrix[i][j + 1] == 1) num++; if (matrix[i + 1][j + 1] == 1) num++; } else if (j == 15) { if (matrix[i - 1][j] == 1) num++; if (matrix[i + 1][j] == 1) num++; if (matrix[i - 1][j - 1] == 1) num++; if (matrix[i][j - 1] == 1) num++; if (matrix[i + 1][j - 1] == 1) num++; } else { if (matrix[i - 1][j - 1] == 1) num++; if (matrix[i - 1][j] == 1) num++; if (matrix[i - 1][j + 1] == 1) num++; if (matrix[i][j - 1] == 1) num++; if (matrix[i][j + 1] == 1) num++; if (matrix[i + 1][j - 1] == 1) num++; if (matrix[i + 1][j] == 1) num++; if (matrix[i + 1][j + 1] == 1) num++; } } number[i][j] = num; } } //数字阵调试 for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (number[i][j] == 9) colour(12); else colour(15); printf("%d ", number[i][j]); } colour(15); printf("n"); }
数字阵调试打印:
6.绘制界面此处通过两个循环嵌套来绘制扫雷的矩阵,循环中的i和j也同时对应matrix, number 和 status矩阵中元素坐标。这样做的好处是方便后面的鼠标机交互坐标检测。
initgraph(745, 850); //载入素材 IMAGE cube; IMAGE cube_trigger; IMAGE background; IMAGE mine_eliminate; IMAGE mine_trigger; IMAGE mine_flag; IMAGE question; IMAGE question_trigger; IMAGE smile; IMAGE smile_trigger; IMAGE caution; IMAGE dead; IMAGE dead_trigger; IMAGE mine_0; IMAGE mine_1; IMAGE mine_2; IMAGE mine_3; IMAGE mine_4; IMAGE mine_5; IMAGE mine_6; IMAGE mine_7; IMAGE mine_8; IMAGE mine_9; loadimage(&cube, "./pic/cube.jpg"); loadimage(&cube_trigger, "./pic/cube_trigger.jpg"); loadimage(&background, "./pic/background.jpg"); loadimage(&mine_eliminate, "./pic/mine_eliminate.jpg"); loadimage(&mine_trigger, "./pic/mine_trigger.jpg"); loadimage(&mine_flag, "./pic/mine_flag.jpg"); loadimage(&question, "./pic/question.jpg"); loadimage(&question_trigger, "./pic/question_trigger.jpg"); loadimage(&smile, "./pic/smile.jpg"); loadimage(&smile_trigger, "./pic/smile_trigger.jpg"); loadimage(&caution, "./pic/caution.jpg"); loadimage(&dead, "./pic/dead.jpg"); loadimage(&dead_trigger, "./pic/dead_trigger.jpg"); loadimage(&mine_0, "./pic/mine_0.jpg"); loadimage(&mine_1, "./pic/mine_1.jpg"); loadimage(&mine_2, "./pic/mine_2.jpg"); loadimage(&mine_3, "./pic/mine_3.jpg"); loadimage(&mine_4, "./pic/mine_4.jpg"); loadimage(&mine_5, "./pic/mine_5.jpg"); loadimage(&mine_6, "./pic/mine_6.jpg"); loadimage(&mine_7, "./pic/mine_7.jpg"); loadimage(&mine_8, "./pic/mine_8.jpg"); //贴图 putimage(0, 0, &background); putimage(333, 17, &smile); for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { putimage(15 + 45 * j, 118 + 45 * i, &cube); } }7.输赢检测
在游戏逻辑大循环前进行输赢检测
通过遍历status矩阵中的值进行计数
若值4的数量到达216即判定为胜利
status矩阵内元素值的含义:
0 = 未触发的元素
1 = 鼠标左键点击某元素后与其相邻的number矩阵中值为0的,但还未检测周边元素的元素
2 = 鼠标左键点击某元素后触发拓展后与number矩阵中值为0的元素相邻的number矩阵中值为1~8的元素
3 = 鼠标左键点击某元素后与其相邻的number矩阵中值为0的,且已检测周边元素的元素
4 = 界面上已被贴图位置(空贴图,及1~8数字贴图)所对应的元素
5 = 鼠标右键点击某元素后,该元素位置被贴为红旗的元素
6 = 鼠标右键点击某元素后,该元素位置被贴为问号的元素
while (1) { for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (status[i][j] == 5) if (matrix[i][j] == 1) wincount++; } } if (wincount == 40) { fail_flag = 2; //游戏成功旗帜 break; } wincount = 0; for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (status[i][j] == 4) wincount++; } } if (wincount == 216) { fail_flag = 2; //游戏成功旗帜 break; } wincount = 0;8.鼠标交互逻辑
MOUSEMSG mouse = GetMouseMsg(); for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (15 + 45 * j <= mouse.x && mouse.x <= 15 + 45 * j + 42 && 118 + 45 * i <= mouse.y && mouse.y <= 118 + 45 * i + 42 && (status[i][j] == 0 || status[i][j] == 5 || status[i][j] == 6)) //检测鼠标处于哪个元素范围内,已被贴图的元素不再参与交互监测 { if (mouse.uMsg == WM_LBUTTonDOWN && status[i][j] != 5) //如果左键点击 { putimage(333, 17, &caution); //界面上方笑脸样式改变 if (number[i][j] == 9) //如果点击的元素为雷 { putimage(15 + 45 * j, 118 + 45 * i, &mine_trigger); fail_flag = 1; //触发游戏失败的旗 break_flag = 1; break; } else if (number[i][j] != 0) //如果点击的元素不为雷且不为空 { switch (number[i][j]) //按照相邻元素中雷的数量进行贴图 { case 1: putimage(15 + 45 * j, 118 + 45 * i, &mine_1); break; case 2: putimage(15 + 45 * j, 118 + 45 * i, &mine_2); break; case 3: putimage(15 + 45 * j, 118 + 45 * i, &mine_3); break; case 4: putimage(15 + 45 * j, 118 + 45 * i, &mine_4); break; case 5: putimage(15 + 45 * j, 118 + 45 * i, &mine_5); break; case 6: putimage(15 + 45 * j, 118 + 45 * i, &mine_6); break; case 7: putimage(15 + 45 * j, 118 + 45 * i, &mine_7); break; case 8: putimage(15 + 45 * j, 118 + 45 * i, &mine_8); break; } status[i][j] = 4; //转换状态为已贴图 } else //该元素不为雷,且相邻元素也没有雷 { status[i][j] = 1; detect_null(i, j, number, status); //扫描周围所有的空元素 expand_null(status); //对空元素周围的数字元素进行拓展 view(number, status, mine_0, mine_1, mine_2, mine_3, mine_4, mine_5, mine_6, mine_7, mine_8); //对空元素及拓展出的元素进行贴图 } Sleep(250); putimage(333, 17, &smile); //界面上方笑脸 } else if (mouse.uMsg == WM_RBUTTONDOWN) //如果右键点击 { if (status[i][j] == 0) { status[i][j] = 5; putimage(15 + 45 * j, 118 + 45 * i, &mine_flag); } else if (status[i][j] == 5) status[i][j] = 6; else if (status[i][j] == 6) status[i][j] = 0; } //将元素状态在未触发,插旗与问号之间切换 else { if(status[i][j] == 0) putimage(15 + 45 * j, 118 + 45 * i, &cube_trigger); else if(status[i][j] == 6) putimage(15 + 45 * j, 118 + 45 * i, &question_trigger); } } else if (status[i][j] == 0 || status[i][j] == 6) //鼠标交互反应贴图 { if (status[i][j] == 0) putimage(15 + 45 * j, 118 + 45 * i, &cube); else putimage(15 + 45 * j, 118 + 45 * i, &question); } } if (break_flag == 1) break; } if (break_flag == 1) { break_flag = 0; break; } if (333 <= mouse.x && mouse.x <= 413 && 15 <= mouse.y && mouse.y <= 95) { if (mouse.uMsg == WM_LBUTTONDOWN) //如果点击笑脸,重新开始游戏 { putimage(333, 17, &smile_trigger); Sleep(200); goto start; //比较懒,所以才用了goto,请别喷我 :) } } }9.检测相邻空元素
此处运用递归方法,实现了对触发空元素的所有相邻空元素的检测
检测完成的空元素,status矩阵中的状态值会被调整为3;而被检测出但还未检测其本身的元素状态值将会被设为1
当status矩阵中不存在值为1的元素时,即表示所有相邻空元素已检测完成,递归停止
void detect_null(int i, int j, int number[16][16], int status[16][16]) { if( i != 0 ) if (number[i - 1][j] == 0 && status[i - 1][j] != 3 && status[i - 1][j] != 5 && status[i - 1][j] != 6) status[i - 1][j] = 1; if( i != 15 ) if (number[i + 1][j] == 0 && status[i + 1][j] != 3 && status[i + 1][j] != 5 && status[i + 1][j] != 6) status[i + 1][j] = 1; if( j != 0 ) if (number[i][j - 1] == 0 && status[i][j - 1] != 3 && status[i][j - 1] != 5 && status[i][j - 1] != 6) status[i][j - 1] = 1; if( j != 15 ) if (number[i][j + 1] == 0 && status[i][j + 1] != 3 && status[i][j + 1] != 5 && status[i][j + 1] != 6) status[i][j + 1] = 1; status[i][j] = 3; for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (status[i][j] == 1) detect_null(i, j, number, status); } } }10.拓展空元素区域
经上一步检测出的空元素后,要将其相邻的数值为1~8的元素也贴上图
因此将status矩阵中值为3的元素的周围的值不为3的元素的状态值变为2,即表示即将要被贴图的不为空的元素
void expand_null(int status[16][16]) { int i = 0; int j = 0; for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (status[i][j] == 3) { if (i != 0) if(status[i - 1][j] != 3 && status[i - 1][j] != 5 && status[i - 1][j] != 6) status[i - 1][j] = 2; if (i != 15) if (status[i + 1][j] != 3 && status[i + 1][j] != 5 && status[i + 1][j] != 6) status[i + 1][j] = 2; if (j != 0) if (status[i][j - 1] != 3 && status[i][j - 1] != 5 && status[i][j - 1] != 6) status[i][j - 1] = 2; if (j != 15) if (status[i][j + 1] != 3 && status[i][j + 1] != 5 && status[i][j + 1] != 6) status[i][j + 1] = 2; if(i != 0 && j != 0) if (status[i - 1][j - 1] != 3 && status[i - 1][j - 1] != 5 && status[i - 1][j - 1] != 6) status[i - 1][j - 1] = 2; if (i != 0 && j != 15) if (status[i - 1][j + 1] != 3 && status[i - 1][j + 1] != 5 && status[i - 1][j + 1] != 6) status[i - 1][j + 1] = 2; if (i != 15 && j != 0) if (status[i + 1][j - 1] != 3 && status[i + 1][j - 1] != 5 && status[i + 1][j - 1] != 6) status[i + 1][j - 1] = 2; if (i != 15 && j != 15) if (status[i + 1][j + 1] != 3 && status[i + 1][j + 1] != 5 && status[i + 1][j + 1] != 6) status[i + 1][j + 1] = 2; } } } }11.按照状态贴图
经过上两步的检测,所有要被贴图的元素状态已被设为2和3
因此仅需遍历status矩阵,找到状态为2和3的元素,在对照number矩阵中的值贴上空以及1~8的图片素材
void view(int number[16][16], int status[16][16], IMAGE mine_0, IMAGE mine_1, IMAGE mine_2, IMAGE mine_3, IMAGE mine_4, IMAGE mine_5, IMAGE mine_6, IMAGE mine_7, IMAGE mine_8) { int i = 0; int j = 0; for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (status[i][j] == 2 || status[i][j] == 3 ) { switch (number[i][j]) { case 0: putimage(15 + 45 * j, 118 + 45 * i, &mine_0); break; case 1: putimage(15 + 45 * j, 118 + 45 * i, &mine_1); break; case 2: putimage(15 + 45 * j, 118 + 45 * i, &mine_2); break; case 3: putimage(15 + 45 * j, 118 + 45 * i, &mine_3); break; case 4: putimage(15 + 45 * j, 118 + 45 * i, &mine_4); break; case 5: putimage(15 + 45 * j, 118 + 45 * i, &mine_5); break; case 6: putimage(15 + 45 * j, 118 + 45 * i, &mine_6); break; case 7: putimage(15 + 45 * j, 118 + 45 * i, &mine_7); break; case 8: putimage(15 + 45 * j, 118 + 45 * i, &mine_8); break; } status[i][j] == 4; } } } }12.游戏成功
if (fail_flag == 2) { settextcolor(RED); setbkmode(TRANSPARENT); settextstyle(80, 0, "黑体"); outtextxy(110, 10, "Congragulate!"); putimage(333, 17, &smile); Sleep(10); view_mine(matrix, mine_eliminate); system("pause"); return 0; }13.游戏失败
if (fail_flag == 1) { settextcolor(RED); setbkmode(TRANSPARENT); settextstyle(100, 0, "黑体"); outtextxy(122, 5, "GAME OVER"); putimage(333, 17, &dead); Sleep(10); view_mine(matrix, mine_eliminate); putimage(15 + 45 * j, 118 + 45 * i, &mine_trigger); while (1) { MOUSEMSG mouse = GetMouseMsg(); if (333 <= mouse.x && mouse.x <= 413 && 15 <= mouse.y && mouse.y <= 95) //点击哭脸可重新开始游戏 { if (mouse.uMsg == WM_LBUTTONDOWN) { putimage(333, 17, &dead_trigger); Sleep(200); goto start; //本人偷懒,所以用了goto,不喜勿喷 } } } }14.展示地雷
游戏失败后,需向玩家展示所有地雷的位置
根据雷位置所在元素进行贴图即可
void view_mine(int matrix[16][16], IMAGE mine_eliminate) { int i = 0; int j = 0; for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (matrix[i][j] == 1) { putimage(15 + 45 * j, 118 + 45 * i, &mine_eliminate); } } } }
详尽代码请下载源码查看!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)