C/C++ 扫雷游戏制作

发布于:2023-01-18 ⋅ 阅读:(577) ⋅ 点赞:(0)

扫雷游戏制作过程

一、创作工具 

   1、visual studio 2022

   2、easyx图形库

二、准备工作

   1、图片

   2、背景音乐   

    整合在一个文件夹里,与源文件放在一起,重命名方便操作

    

三、创作思路

   1、设置雷(-1)代表雷,空白格子为0,数字[0-8](雷周围整体加一,可叠加)雷周围数字出现情况:1—8
  运用二维数组知识
  静态数组int map[ ](不能改变)
  动态数组int **map = malloc(10,sizeof(int):
  生成的数组中每一个格子装了一个int*的指针
  for(int i =0;i<10;i++)
  {
      map[i] = malloc(10,sizeof(int);
  }
  以此完成动态数组的申请;优势:可以随时扩容

   2、利用easyx图形库函数创建一个窗口,创建一个存储图片数组,将准备好的图片放入指定位置(利用坐标实现)
   3、排雷:

         鼠标左击消除对应小格,右击标记(可用别的按键实现)
   4、判定输赢条件

         点到地雷,游戏结束;所有地雷被红旗标记,获得胜利,游戏结束

四、代码实现

    头文件设置,宏观设置

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<easyx.h>//图形库
//动态数组分配需要用到的头文件
#include<mmsystem.h>//添加音乐用到的头文件
#pragma comment(lib,"winmm.lib")//取消警告(库文件)
#define SIZE 20//设置图片尺寸大小
#define num  100//设置雷的数量
//加载资源,图片
IMAGE imgs[12];//存储图片数组

加载填充图片

void loadpng()
{
	for (int i = 0;i < 12;i++)
	{
		char buf[50] = { 0 };
		sprintf(buf,"./photos/%d.png", i);//使得下面的图片也能跟着变换,注意要用到spintf完成
		//用sprintf时1.项目2.c/c++3.常规4.SDL检查5.选择否,软件问题可以去掉
		loadimage(imgs + i, buf, SIZE, SIZE);//用这个加图片时,1.项目2.属性3.高级4.字符集5.选则使用多节字符集
	}
	
};

初始化Map,内存申请

struct Map
{
	int** arr;//二维数组指针
	int rows;//行数
	int cols;//列数
};
void map_init(Map* map, int rows, int cols)
{
	map->rows = rows;
	map->cols = cols;
	map->arr = (int**)calloc(map->rows,sizeof(int*));//calloc是malloc的兄弟函数
	if (!map->arr)
	{
		printf("内存申请失败\n");
		return;
	}
	for (int i = 0;i < map->rows;i++)
	{
		map->arr[i] = (int*)calloc(map->cols, sizeof(int));//(int*)强转为int
		if (!map->arr[i])
		{
			printf("内存申请失败");//一般来说必须返回确认是否成功
			return;
		}
	}
}

设置雷(-1)

//设置雷  -1表示在0处随机设置-1形成雷
void map_setMine(Map* map)
{
	//设置随机数种子
	srand(time(NULL));//时间头文件
	for (int i = 0;i < num;)
	{
		//随机产生下标[0-10]
		int r = rand() % map->rows;
		int c = rand() % map->cols;
		if (map->arr[r][c] == 0)
		{
			map->arr[r][c] = -1;//雷设置完成,注意:此过程可能会使-1位置重复,
                                //导致设置雷小于指定数,解决方法:加循环
			i++; //从for里把i++拿出来表示只有成功设置雷,才自增
		}
		
	}
	//把以雷为中心的九宫格的数字都加一(雷除外)形成数字0-8
	for (int i = 0; i < map->rows; i++)
	{
		for (int k = 0; k < map->cols; k++)
		{
			if (map->arr[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 < map->rows && c >= 0 && c < map->cols)
							&& map->arr[r][c] != -1)//此时加入边界判断即可恢复代码中断问题
						{
							map->arr[r][c] += 1;//此时代码有问题,可能会导致代码运行中断,
                                     //因为会越界(雷在边界),九宫格出现行超出列超出的情况
						}
					}
				}
			}
		}
	}
}

 输出一下所有数组,用于观察纠错,修正,

添加排查操作,用于检测标记方格是否为雷,如果排查标记出所有雷,则胜利,游戏结束

//输出一下所有数组
void map_show(Map* map)
{
    int cir=0;//设置变量
	for (int i = 0;i < map->rows;i++)
	{
		for (int k = 0;k < map->cols;k++)
		{
			printf(" %2d  ", map->arr[i][k]);
			if (map->arr[i][k] == 59)//判断标记的红旗是否为雷
			{
				cir += 1;
				if (cir == num)//如果排查出雷的数量与设置雷的数量一致则胜利
			{
				{
					WinWindow();//输出成功窗口,游戏结束
					getchar();
				}
			}
		}
		printf("\n\n");
	}
}

 覆盖起来,进行加密操作,将(-1—8)数字加20变为(19—28)进行加密

//覆盖起来,加密操作
void map_cover(Map* map)
{
	//让现在数组里的数据变得不一样
	for (int i = 0; i < map->rows; i++)
	{
		for (int k = 0; k < map->cols; k++)
		{
			map->arr[i][k] += 20;
		}
	}
}

绘制界面,图形窗口

//绘制界面(图形窗口)
void map_draw(Map* map)
{
	for (int i = 0;i < map->rows;i++)
	{
		for (int k = 0; k < map->cols; k++)
		{
			//求每个格子的坐标
			int x = k * SIZE;
			int y = i * SIZE;
			if (map->arr[i][k] >= 0 && map->arr[i][k] <= 8)
			{
				putimage(x, y, imgs + map->arr[i][k]);//放置图片
			}
			else if (map->arr[i][k] == -1)
			{
				putimage(x, y, imgs + 11);//防置雷的图片
			}//加密后最小为19,最大为28
			else if (map->arr[i][k] >= 19 && map->arr[i][k] <= 28)
			{
				putimage(x, y, imgs + 9);//放置蓝色格子
			}
			else if (map->arr[i][k] >= 59 && map->arr[i][k] <= 68)
			{
				putimage(x, y, imgs + 10);//插旗标记
			}
			if (map->arr[i][k] == -1)
			{
				DieWindow();//点到雷了,生成游戏结束窗口,游戏结束
				getchar();//防止闪屏
			}
			
		}

	}
}

左击格子打开与右击格子标记插旗,二次加密

void map_mouseMsg(Map* map, ExMessage* msg)//消息参数,用鼠标需要的参数
{
	//根据鼠标坐标,获得点击的格子的下标
	int r = msg->y / SIZE;
	int c = msg->x / SIZE;
	//判断鼠标左键是否点击
	if (msg->message == WM_LBUTTONDOWN&& map->arr[r][c] >= 19 && map->arr[r][c] <= 68)
	{
		if (map->arr[r][c] != 59)
		{
			map->arr[r][c] -= 20;//左击减数字,消除加密;
			map_recOpen(map, r, c);
			system("cls");
			map_show(map);//换进来不需要取地址&
		}
	}
	//判断是否为右键点击
	if (msg->message == WM_RBUTTONDOWN && map->arr[r][c] >= 19 && map->arr[r][c] <= 28)
	{

		map->arr[r][c] += 40;//右键标记雷,插旗子二次加密
		map_recOpen(map, r, c);
		system("cls");
		map_show(map);//换进来不需要取地址&
		
	}
	
}

用递归法实现:若点击为空白格子则打开周围所有空白格子和数字

//如果点到空白格子,递归打开周围的所有的空白格子和数字格子
void map_recOpen(Map* map,int row,int col)
{
	//先判断一下是否为空白格子
	if (map->arr[row][col] != 0)
	{
		return;
	}
	//延申到以空白格子为中心的九宫格
	for (int r = row - 1; r <= row + 1; r++)
	{
		for (int c = col - 1; c <= col + 1; c++)
		{
			if ((r >= 0 && r < map->rows && c >= 0 && c < map->cols)
				&&map->arr[r][c]>=19&&map->arr[r][c]<=28)
			{
				map->arr[r][c] -= 20;
				map_recOpen(map, r, c);//递归,自己调用自己
			}
		}
	}
}

绘制游戏失败窗口,要用到easyx图形库

void DieWindow()
{
	BeginBatchDraw();//双缓冲绘图
	initgraph(400, 400);
	setbkmode(TRANSPARENT);
	settextcolor(LIGHTBLUE);
	outtextxy(150, 150, "BOOM!BOOM!");

	outtextxy(150, 120, "GameOver");
	EndBatchDraw();
}

绘制游戏胜利窗口

void WinWindow()
{
	BeginBatchDraw();//双缓冲绘图
	initgraph(400, 400);
	setbkmode(TRANSPARENT);
	settextcolor(LIGHTBLUE);
	outtextxy(150, 150, "WOW!COOL!");
	outtextxy(150, 120, "恭喜你通关了");
	EndBatchDraw();
}

主函数

int main()
{
	//创建图形窗口
	initgraph(800, 600,EW_SHOWCONSOLE);//长,宽,显示控制台
	//播放背景音乐
	mciSendString("open ./photos/trip.mp3 alias bgm", NULL, 0, NULL);
	mciSendString("play bgm repeat", NULL, 0, NULL);//循环播放
	loadpng();
	Map map;
	map_init(&map,30,40);//行,列
	map_setMine(&map);//&取地址
	map_cover(&map);
	//消息循环
	while (true)
	{
		ExMessage msg = {0};
		while (peekmessage(&msg, EM_MOUSE))
		{
			map_mouseMsg(&map, &msg);
			//map_show(&map);
			map_draw(&map);
			//WinGame(&map,m,n);
		}//用while循环因为括号内消息为真则继续循环
	}
	getchar();
	return 0;
}

五、代码整体预览

注意函数调用问题和逻辑问题

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<easyx.h>//图形库
//动态数组分配需要用到的头文件
#include<mmsystem.h>//添加音乐用到的头文件
#pragma comment(lib,"winmm.lib")//取消警告(库文件)
#define SIZE 20//图片尺寸
#define num  10//雷的数量
//加载资源,图片
IMAGE imgs[12];//存储图片数组

void loadpng()
{
	for (int i = 0;i < 12;i++)
	{
		char buf[50] = { 0 };
		sprintf(buf,"./photos/%d.png", i);//使得下面的图片也能跟着变换,注意要用到spintf完成
		//用sprintf时1.项目2.c/c++3.常规4.SDL检查5.选择否,软件问题可以去掉
		loadimage(imgs + i, buf, SIZE, SIZE);//用这个加图片时,1.项目2.属性3.高级4.字符集5.选则使用多节字符集
	}
};
struct Map
{
	int** arr;//二维数组指针
	int rows;//行数
	int cols;//列数
};
void map_recOpen(Map* map, int row, int col);
void DieWindow();
void WinWindow();
//初始化MAP
void map_init(Map* map, int rows, int cols)
{
	map->rows = rows;
	map->cols = cols;
	map->arr = (int**)calloc(map->rows,sizeof(int*));//calloc是malloc的兄弟函数
	if (!map->arr)
	{
		printf("内存申请失败\n");
		return;
	}
	for (int i = 0;i < map->rows;i++)
	{
		map->arr[i] = (int*)calloc(map->cols, sizeof(int));//(int*)强转为int
		if (!map->arr[i])
		{
			printf("内存申请失败");//一般来说必须返回确认是否成功
			return;
		}
	}
}
//输出一下所有数组
void map_show(Map* map)
{
    int cir=0;
	for (int i = 0;i < map->rows;i++)
	{
		for (int k = 0;k < map->cols;k++)
		{
			printf(" %2d  ", map->arr[i][k]);
			if (map->arr[i][k] == 59)
			{
				cir += 1;
				if (cir == num)
				{
					WinWindow();
					getchar();
				}
			}
		}
		printf("\n\n");
	}
}
//设置雷  -1表示在0处随机设置-1形成雷
void map_setMine(Map* map)
{
	//设置随机数种子
	srand(time(NULL));//时间头文件
	for (int i = 0;i < num;)
	{
		//随机产生下标[0-10]
		int r = rand() % map->rows;
		int c = rand() % map->cols;
		if (map->arr[r][c] == 0)
		{
			map->arr[r][c] = -1;//雷设置完成,但是这样会使=1位置重复,导致小于10个,方法:加循环
			i++;//从for里把i++拿出来表示只有成功设置雷,才自增
		}
	}
	//把以雷为中心的九宫格的数字都加一(雷除外)形成数字0-8
	for (int i = 0; i < map->rows; i++)
	{
		for (int k = 0; k < map->cols; k++)
		{
			if (map->arr[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 < map->rows && c >= 0 && c < map->cols)
							&& map->arr[r][c] != -1)//此时加入边界判断即可恢复代码中断问题
						{
							map->arr[r][c] += 1;//此时代码有问题,可能会导致代码运行中断,因为会越界(雷在边界),九宫格出现行超出列超出的情况
						}
					}
				}
			}
		}
	}
}
//绘制界面(图形窗口)
void map_draw(Map* map)
{
	for (int i = 0;i < map->rows;i++)
	{
		for (int k = 0; k < map->cols; k++)
		{
			//求每个格子的坐标
			int x = k * SIZE;
			int y = i * SIZE;
			if (map->arr[i][k] >= 0 && map->arr[i][k] <= 8)
			{
				putimage(x, y, imgs + map->arr[i][k]);
			}
			else if (map->arr[i][k] == -1)
			{
				putimage(x, y, imgs + 11);
			}//加密后最小为19,最大为28
			else if (map->arr[i][k] >= 19 && map->arr[i][k] <= 28)
			{
				putimage(x, y, imgs + 9);
			}
			else if (map->arr[i][k] >= 59 && map->arr[i][k] <= 68)
			{
				
				putimage(x, y, imgs + 10);
			}
			if (map->arr[i][k] == -1)
			{
				DieWindow();
				getchar();
			}
		}
	}
}
//覆盖起来,加密操作
void map_cover(Map* map)
{
	//让现在数组里的数据变得不一样
	for (int i = 0; i < map->rows; i++)
	{
		for (int k = 0; k < map->cols; k++)
		{
			map->arr[i][k] += 20;
		}
	}
}

//点击格子打开
void map_mouseMsg(Map* map, ExMessage* msg)//消息参数,用鼠标需要的参数
{
	//根据鼠标坐标,获得点击的格子的下标
	int r = msg->y / SIZE;
	int c = msg->x / SIZE;
	//判断鼠标左键是否点击
	if (msg->message == WM_LBUTTONDOWN&& map->arr[r][c] >= 19 && map->arr[r][c] <= 68)
	{
		if (map->arr[r][c] != 59)
		{
			map->arr[r][c] -= 20;//左击减数字,消除加密;
			map_recOpen(map, r, c);
			system("cls");
			map_show(map);//换进来不需要取地址&
		}
	}
	//判断是否为右键点击
	if (msg->message == WM_RBUTTONDOWN && map->arr[r][c] >= 19 && map->arr[r][c] <= 28)
	{

		map->arr[r][c] += 40;//右键标记雷,插旗子二次加密
		map_recOpen(map, r, c);
		system("cls");
		map_show(map);//换进来不需要取地址&
	}
}
//如果点到空白格子,递归打开周围的所有的空白格子和数字格子
void map_recOpen(Map* map,int row,int col)
{
	//先判断一下是否为空白格子
	if (map->arr[row][col] != 0)
	{
		return;
	}
	//遍历空白格子为中心的九宫格
	for (int r = row - 1; r <= row + 1; r++)
	{
		for (int c = col - 1; c <= col + 1; c++)
		{
			if ((r >= 0 && r < map->rows && c >= 0 && c < map->cols)
				&&map->arr[r][c]>=19&&map->arr[r][c]<=28)
			{
				map->arr[r][c] -= 20;
				map_recOpen(map, r, c);//递归,自己调用自己
			}
		}
	}
}

void DieWindow()
{
	BeginBatchDraw();//双缓冲绘图
	initgraph(400, 400);
	setbkmode(TRANSPARENT);
	settextcolor(LIGHTBLUE);
	outtextxy(150, 150, "BOOM!BOOM!");
	outtextxy(150, 120, "GameOver");
	EndBatchDraw();
}
void WinWindow()
{
	BeginBatchDraw();//双缓冲绘图
	initgraph(400, 400);
	setbkmode(TRANSPARENT);
	settextcolor(LIGHTBLUE);
	outtextxy(150, 150, "WOW!COOL!");
	outtextxy(150, 120, "恭喜你通关了");
	EndBatchDraw();
}
int main()
{
	//创建图形窗口
	initgraph(800, 600,EW_SHOWCONSOLE);//长,宽,显示控制台
	
	//播放背景音乐
	mciSendString("open ./photos/trip.mp3 alias bgm", NULL, 0, NULL);
	mciSendString("play bgm repeat", NULL, 0, NULL);//循环播放
	loadpng();
	Map map;
	map_init(&map,30,40);//行列
	map_setMine(&map);//&取地址
	map_cover(&map);
	//消息循环
	while (true)
	{
		ExMessage msg = {0};
		while (peekmessage(&msg, EM_MOUSE))
		{
			map_mouseMsg(&map, &msg);
			//map_show(&map);
			map_draw(&map);
			//WinGame(&map,m,n);
		}//用while循环因为括号内消息为真则继续循环
	}
	getchar();
	return 0;
}

六、运行结果预览

生成游戏

 点击空白格子,递归打开周围空白格子和数字

  点击到雷,游戏结束,失败

标记出所有雷,游戏胜利


网站公告

今日签到

点亮在社区的每一天
去签到