C语言初学者复刻经典扫雷小游戏(图形界面,非黑白窗口)(含源码)

注:除计时器和剩余雷数显示外,其他功能完美还原。

 

目录

一、程序演示

二、程序信息

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.头文件

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
#include <graphics.h>
#include <time.h>
#pragma comment(lib,"winmm.lib")
#pragma warning(disable:4996)

2.变量声明

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;
				}
			}
		}
		
	}

	//雷位置调试
    /*
	for (i = 0; i < 40; i++)
		printf("%d\n", history[i]);
    */

雷位置调试打印: 

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;
	}

	//雷矩阵调试
    /*
	//雷矩阵调试
	for (i = 0; i < 16; i++)
	{
		for (j = 0; j < 16; j++)
		{
			if (matrix[i][j] == 1)
				colour(12);
			else
				colour(15);
			printf("%d ", matrix[i][j]);
		}
		colour(15);
		printf("\n");
	}
    */

雷矩阵调试打印:

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);
			}
		}
	}
}

详尽代码请下载源码查看!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值