C语言EasyX库开发鸡了个鸡(羊了个羊改编)第二关小游戏

b站有羊了个羊第二关开发视频的教学:

https://www.bilibili.com/video/BV1ZL411e7J4/?spm_id_from=333.337.search-card.all.click&vd_source=cee8251180ab48399817d81d5f11ffd1

开发所需图片与音乐(提取码为1234)

https://pan.baidu.com/s/1qlHjPPxsPuFwl_xLAmI_6w?pwd=1234

代码讲解 

首先要介绍几个概念:

1.图块

        该游戏中一共有15种图块。因为每种图块都有明暗两种类型,因此我使用了IMAGE二维数组来存储他们的图片。

        每个图块的具体数据在代码中使用了一个结构体来定义,内部的变量具体请查看代码,代码中有详细注释。

2.图层

        其实这与盖房子很类似。盖房子都是从底层盖到高层的,每一层就类似于图片的图层,上面的图层会覆盖下面图层的内容。一个图层可以有多个图块,也可以只有一个图块。被覆盖的图块会被设置成暗色。

        在代码中使用了二级结构体指针数组来定义。也许有人会疑问为什么不是用一级结构体指针数组来定义呢?每个一级指针代表一个图层,指针指向一个存储若干个图块的结构体数组,这样不是也可以吗?确实是可以的,但这样只能完成存储功能。要知道我们定义了图层与图块后是要进行调用的。图层的图块数量不是唯一的,有些图层可能只有一个,有些图层可能有六个,对一个图层的图块数量不确定我们是无法进行调用的。因此我们可以考虑使用NULL作为图层遍历完毕的一个标志。遍历时遇到NULL说明该图层的图块都已遍历完毕。而要使用NULL就必须使用一级指针数组。上述视频中有详细图解。

 函数功能讲解: 

void InitLayer(int row, int col, int init_x, int init_y, int layer) {
	/*
	参数解释:
	1.row:表示图层有几行
	2.col:表示图层有几列
	3.init_x:表示图层的第一个图块的x坐标
	4.init_y:表示图层的第一个图块的y坐标
	5.layer:表示该图层为整个地图的第几层

	函数功能:
	用于初始化一个图层
	*/

	int i, j;

	//为maps数组中的每个元素分配内存
	maps[layer] = (struct block**)malloc(sizeof(struct block*) * (row * col + 1));//+1是为了在最后一位存入空指针
	if (maps[layer] == NULL) {
		exit(0);//申请内存失败
	}
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			maps[layer][i * col + j] = (struct block*)malloc(sizeof(struct block));
			if (maps[layer][i * col + j] == NULL) {//申请内存失败
				exit(0);
			}
			maps[layer][i * col + j]->init_x = init_x;
			maps[layer][i * col + j]->init_y = init_y;
			maps[layer][i * col + j]->row = row;
			maps[layer][i * col + j]->col = col;
			maps[layer][i * col + j]->type = 1;//初始化种类为1
			maps[layer][i * col + j]->x = init_x + j * BLOCK_WIDTH;
			maps[layer][i * col + j]->y = init_y + i * BLOCK_HEIGHT;
			maps[layer][i * col + j]->is_not_cover = 1;//初始化图层未被覆盖
		}
	}
	maps[layer][col * row] = NULL;//每个二级指针所指向的一级指针数组的最后一个元素设为空指针
}

1.为maps数组中的某一个图层申请row*col+1个连续内存,用于存储图层中图块数据的地址,最后的+1是为了存储NULL。

2.遍历所申请的连续内存,并为图块数据赋值。

​
void InitMap() {
	int layer = 0;
	for (; layer < 12; layer++) {
		InitLayer(1, 1, 55 + layer * 10, 500, layer);
	}
	for (; layer < 24; layer++) {
		InitLayer(1, 1, 350 - (layer - 12) * 10, 500, layer);
	}
	InitLayer(6, 1, 15, 90, layer++);
	InitLayer(3, 1, 77, 130, layer++);
	InitLayer(6, 1, 138, 45, layer++);
	InitLayer(6, 1, 202, 45, layer++);
	InitLayer(6, 1, 264, 45, layer++);
	InitLayer(3, 1, 326, 130, layer++);
	InitLayer(6, 1, 388, 90, layer++);
	InitLayer(4, 1, 48, 120, layer++);
	InitLayer(4, 1, 360, 120, layer++);
	InitLayer(2, 2, 174, 20, layer++);
	InitLayer(2, 2, 174, 345, layer++);
	InitLayer(1, 2, 170, 220, layer++);
	InitLayer(1, 2, 15, 20, layer++);
	InitLayer(1, 2, 326, 20, layer++);
	InitLayer(1, 1, 109, 120, layer++);
	InitLayer(1, 1, 109, 324, layer++);
	InitLayer(1, 1, 299, 120, layer++);
	InitLayer(1, 1, 299, 324, layer++);
	InitLayer(1, 1, 109, 431, layer++);
	InitLayer(1, 1, 299, 431, layer++);
	InitLayer(1, 1, 77, 395, layer++);
	InitLayer(1, 1, 326, 395, layer++);



	Correct_Layer();//修正图块的数据类型,使它们最终能被全部消去

}

​

1.对图层的布局进行构建,可以尝试DIY布局,我这里是尽可能按照原版的布局去构建的。

2.最后需要对图块的种类进行修正。在InitLayer函数中我只是将类型初始化为1,为了保证最后所有图块都能被消去,需要调用Correct_Layer函数。(视频中并没有介绍如何保证所有图块都能被消去,该函数功能为自己所构想)

void Correct_Layer() {
	/*
	函数功能:修正图层的图块种类,确保最终能被全部消去
	*/

	int rand_type[MAX_BLOCK];//记录图块类型
	int i, j, index = 0;
	int temp = MAX_BLOCK;//临时变量为最大图块数
	int rand_num;//随机数

	srand((unsigned int)time(NULL));

	for (i = 0; i < 15; i++) {
		freq[i].count = 0;
	}//计数

	i = 0;

	while (temp != 0 && i < 15) {
		freq[i++].count += 3;
		temp -= 3;//先让每个种类的数量都至少有3种
	}

	while (temp != 0) {
		freq[rand() % 15].count += 3;//然后再随机选择一个种类数量加3
		temp -= 3;
	}

	for (i = 0; i < 15; i++) {
		freq[i].flag = 1;//标志变量初始化为1,表示数量不为0
	}

	temp = MAX_BLOCK;//重置临时变量
	i = 0;//重置i为0

	while (temp > 0) {

		rand_num = Rand_not_zero(freq, 15);//freq计数器中一个数量不为0的随机元素
		rand_type[i++] = rand_num;
		freq[rand_num].count--;
		temp--;
		for (j = 0; j < 15; j++) {
			if (freq[j].count == 0) {
				freq[j].flag = 0;//若计数器的值为0,则令标志变量为0
			}
		}

	}//生成一个随机种类数组

	for (i = 0; i < MAX_LAYER; i++) {
		for (j = 0; maps[i][j] != NULL; j++) {
			maps[i][j]->type = rand_type[index++];//将随机种类数组依次赋值给maps中的type
		}
	}



}

1.freq为一个全局变量,是一个结构体数组,其定义为:

struct count {
	int count;
	int flag;//判断count是否为0
}freq[15];//结构体数组

1.对freq数组进行初始化,count表示的是此类型图块的最终数量。首先让所有种类图块都至少有3个,再让随机种类的图块加3,直到temp为0。temp的初始值即为最大图块数,每次加3,temp就会减3。

2.然后再生成一个rand_type随机种类数组,里面存储的是最大图块数个图块对应的种类数字。通过调用Rand_not_zero函数得到freq数组中随机的count不为0的元素,并将该元素存入rand_type数组中。

 完整代码

​
#include <graphics.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <conio.h>
#include <Windows.h>//播放音乐 头文件
#pragma comment(lib,"winmm.lib")//播放音乐 库文件


#define MAX_LAYER 46//最大图层数
#define MAX_BLOCK 90//最大图块数
#define WIDTH 464
#define HEIGHT  691//窗口的宽度与高度
#define BLOCK_WIDTH 61
#define BLOCK_HEIGHT 68//图块的宽度与高度

void LoadIMG();
void LoadMusic();
void Game();
void InitMap();
void InitLayer(int row, int col, int init_x, int init_y, int layer);
int Rand_not_zero(struct count arr[], int n);
void Correct_Layer();
void Set_Cover();
int Is_Cover(struct block* b1, struct block* b2);
void Update_Window();
void Update_Cover();
void Update_groov(int groov[], int num);
int User_Click();
int Fail(int groov[], int num);
void Work();







IMAGE img_bk;//背景图片
IMAGE img_item[15][2];//图块数组,15种物品,每种物品根据是否被覆盖而有明暗两种图片
IMAGE img_fail;//失败画面
IMAGE img_success;//通关画面

struct block {
	int type;//图块的种类,共有15种,当type为-1时表示被消除

	int x;
	int y;//图块坐标

	int is_not_cover;//图块是否被覆盖

	int row;
	int col;//图块处于所在图层的第几行,第几列

	int init_x;
	int init_y;//图块所在图层的第一个图块的坐标
};

struct count {
	int count;
	int flag;
}freq[15];//结构体数组

struct block** maps[MAX_LAYER];//图层数组,每个二级结构体指针指向一个图层
struct block* clickBlock = NULL;
int groov[7];//槽位数组




void LoadIMG() {//加载程序所需图片
	int i, j;
	char filename[256];
	loadimage(&img_bk, "images/蒙娜丽坤.png");//加载背景图片
	for (i = 0; i < 15; i++) {
		for (j = 0; j < 2; j++) {
			sprintf_s(filename, sizeof(filename), "images/%d_%d.png", i + 1, j);
			loadimage(&img_item[i][j], filename);//加载图块
		}
	}
	loadimage(&img_fail, "images/失败画面.png");
	loadimage(&img_success, "images/通关画面.png");

}

void LoadMusic() {//加载音乐
	mciSendString("open music/背景音乐.mp3", 0, 0, 0);
	mciSendString("play music/背景音乐.mp3 repeat", 0, 0, 0);//循环播放
	mciSendString("open music/鸡.mp3", 0, 0, 0);
	mciSendString("open music/唱跳rap篮球.mp3", 0, 0, 0);
	mciSendString("open music/你干嘛哎哟.mp3", 0, 0, 0);
	mciSendString("open music/厉不厉害你坤哥.mp3", 0, 0, 0);
}

void InitLayer(int row, int col, int init_x, int init_y, int layer) {
	/*
	参数解释:
	1.row:表示图层有几行
	2.col:表示图层有几列
	3.init_x:表示图层的第一个图块的x坐标
	4.init_y:表示图层的第一个图块的y坐标
	5.layer:表示该图层为整个地图的第几层

	函数功能:
	用于初始化一个图层
	*/

	int i, j;

	//为maps数组中的每个元素分配内存
	maps[layer] = (struct block**)malloc(sizeof(struct block*) * (row * col + 1));//+1是为了在最后一位存入空指针
	if (maps[layer] == NULL) {
		exit(0);//申请内存失败
	}
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			maps[layer][i * col + j] = (struct block*)malloc(sizeof(struct block));
			if (maps[layer][i * col + j] == NULL) {//申请内存失败
				exit(0);
			}
			maps[layer][i * col + j]->init_x = init_x;
			maps[layer][i * col + j]->init_y = init_y;
			maps[layer][i * col + j]->row = row;
			maps[layer][i * col + j]->col = col;
			maps[layer][i * col + j]->type = 1;//初始化种类为1
			maps[layer][i * col + j]->x = init_x + j * BLOCK_WIDTH;
			maps[layer][i * col + j]->y = init_y + i * BLOCK_HEIGHT;
			maps[layer][i * col + j]->is_not_cover = 1;//初始化图层未被覆盖
		}
	}
	maps[layer][col * row] = NULL;//每个二级指针所指向的一级指针数组的最后一个元素设为空指针
}
int Rand_not_zero(struct count arr[], int n) {
	int i;
	int rand_arr[1000];
	static int rand_xishu = 0;//随机系数
	srand((unsigned)time(NULL) + rand_xishu);//随机数种子
	rand_xishu+=rand();//改变随机系数
	for (i = 0; i < 1000; i++) {
		rand_arr[i] = rand() % 15;//生成随机数表
	}
	i = 0;
	while (1) {
		if (arr[rand_arr[i]].count != 0) {
			return rand_arr[i];//找到一个随机数使得对应的count不为0
		}
		i++;
	}

}
void Correct_Layer() {
	/*
	函数功能:修正图层的图块种类,确保最终能被全部消去
	*/

	int rand_type[MAX_BLOCK];//记录图块类型
	int i, j, index = 0;
	int temp = MAX_BLOCK;//临时变量为最大图块数
	int rand_num;//随机数

	srand((unsigned int)time(NULL));

	for (i = 0; i < 15; i++) {
		freq[i].count = 0;
	}//计数

	i = 0;

	while (temp != 0 && i < 15) {
		freq[i++].count += 3;
		temp -= 3;//先让每个种类的数量都至少有3种
	}

	while (temp != 0) {
		freq[rand() % 15].count += 3;//然后再随机选择一个种类数量加3
		temp -= 3;
	}

	for (i = 0; i < 15; i++) {
		freq[i].flag = 1;//标志变量初始化为1,表示数量不为0
	}

	temp = MAX_BLOCK;//重置临时变量
	i = 0;//重置i为0

	while (temp > 0 && index < MAX_BLOCK) {

		rand_num = Rand_not_zero(freq, 15);//freq计数器中一个数量不为0的随机元素
		rand_type[i++] = rand_num;
		freq[rand_num].count--;
		temp--;
		for (j = 0; j < 15; j++) {
			if (freq[j].count == 0) {
				freq[j].flag = 0;//若计数器的值为0,则令标志变量为0
			}
		}

	}//生成一个随机种类数组

	for (i = 0; i < MAX_LAYER; i++) {
		for (j = 0; maps[i][j] != NULL; j++) {
			maps[i][j]->type = rand_type[index++];//将随机种类数组依次赋值给maps中的type
		}
	}



}
int User_Click() {
	ExMessage msg;
	msg = getmessage(EX_MOUSE | EX_KEY);
	int count = 0,i;
	for (i = 0; i < 7; i++) {
		if (groov[i] != -1) {
			count++;//槽计数器
		}
	}

	switch (msg.message) {
	case WM_LBUTTONDOWN://用户点下鼠标左键
		if (msg.ctrl) {
			mciSendString("play music/唱跳rap篮球.mp3 from 0 ", 0, 0, 0);//彩蛋音乐
		}
		for (int i = 0; i < MAX_LAYER; i++) {
			for (int j = 0; maps[i][j] != NULL; j++) {
				struct block* p = maps[i][j];
				if (p->type != -1 && p->is_not_cover == 1
					&& msg.x > p->x && msg.x < p->x + BLOCK_WIDTH
					&& msg.y > p->y && msg.y < p->y + BLOCK_HEIGHT) {//点击的是未被消除且未被覆盖的图块
					
					mciSendString("play music/鸡.mp3 from 0 ", 0, 0, 0);//有效点击音效,每次有效点击都会重新播放
					
					if (count < 7) {//槽数组中图块不满7个

						clickBlock = p;//记录有效点击所指向方块,并在Work函数继续调用
						return 1;//有效点击
					}
					else {
						return 0;
					}
				}

			}
		}
	}
	return 0;
}

int Fail(int groov[], int num) {//判断游戏是否失败
	int sum = 0, i, flag = 1;//初始化标志变量为1,表示失败
	int count[15] = { 0 };//计数器

	for (i = 0; i < num && groov[i] != -1; i++) {
		count[groov[i]]++;//统计数量
	}

	for (i = 0; i < 15; i++) {
		sum += count[i];//统计槽中总块数
	}

	for (i = 0; i < 15; i++) {
		if (count[i] == 3) {//计数器存在某个元素数量为3,满足消去条件
			flag = 0;
			break;
		}
	}

	if (sum == 7 && flag) {//总共7个图块并且满足失败条件
		return 1;
	}
	else {
		return 0;
	}
}


void Work() {
	/*
	函数功能:点击后移动图块
	*/

	int i = 0;
	while (groov[i] != -1 && i < 7) {
		i++;//找到槽数组中为-1的第一位
	}

	groov[i] = clickBlock->type;//在此位置填入clickBlock所指向图块的类型
	
	if (i == 6) {
		clickBlock->type = -1;//将最后图块设置为被消除

		Update_Window();//更新,该函数内部会对槽数组进行更新

		if (Fail(groov, 7)) {//若更新之后仍满足失败条件,说明游戏已经失败了
			MOUSEMSG m;//鼠标信息

			mciSendString("stop music/背景音乐.mp3", 0, 0, 0);//暂停背景音乐
			mciSendString("play music/你干嘛哎哟.mp3", 0, 0, 0);//播放失败音乐
			Sleep(1000);//停止程序
			putimage(0, 0, &img_fail);//输出失败画面

			while (1) {
				m = GetMouseMsg();//获取鼠标信息
				switch (m.uMsg)
				{
				case WM_LBUTTONDOWN:
				case WM_RBUTTONDOWN:
					exit(0);
					// 按鼠标左键或右键退出程序
				}
			}
			
		}
		
	}
	//槽数组记录有效点击图块的类型
	


	clickBlock->type = -1;//设置该图块被清除
}


int Is_Cover(struct block* b1, struct block* b2) {//判断两个图块是否相交
	if (fabs(b1->x - b2->x) < BLOCK_WIDTH && fabs(b1->y - b2->y) < BLOCK_HEIGHT) {
		return 1;
	}
	else {
		return 0;
	}
}

void Update_Cover() {
	//将所有图块的is_not_cover更新为1
	int i, j;
	for (i = 0; i < MAX_LAYER; i++) {
		for (j = 0; maps[i][j] != NULL; j++) {
			maps[i][j]->is_not_cover = 1;
		}
	}
}

void Set_Cover() {
	/*
	函数功能:将有重叠的图块设置成明暗重叠
	*/
	for (int i = MAX_LAYER - 1; i >= 0; i--) {
		for (int j = 0; maps[i][j] != NULL; j++) {
			if (maps[i][j]->type == -1) {//该图块已经被消除
				continue;
			}
			for (int k = 0; k < i; k++) {
				for (int l = 0; maps[k][l] != NULL; l++) {
					if (maps[k][l]->type == -1) {//该图块已经被消除
						continue;
					}
					if (Is_Cover(maps[i][j], maps[k][l])) {//两图块发生覆盖
						maps[k][l]->is_not_cover = 0;//将图块设置为被覆盖
					}
				}
			}
		}
	}
}
void Update_groov(int groov[], int num) {
	//更新槽数组,即去掉槽数组中出现次数大于等于3的元素

	int count[15] = { 0 };//计数器数组
	int i, j = 0;

	for (i = 0; i < 7 && groov[i] != -1; i++) {
		count[groov[i]]++;//计数器进行计数
	}

	for (i = 0; i < 7 && groov[i] != -1; i++) {
		if (count[groov[i]] < 3 && count[groov[i]] != 0) {
			groov[j++] = groov[i];//将出现次数小于3且大于0的元素填入
		}
	}

	while (j < 7) {
		groov[j++] = -1;//后面填补-1
	}
}

void Update_Window() {
	/*
	函数功能:更新窗口信息
	*/
	BeginBatchDraw();//这个函数用于开始批量绘图。执行后,任何绘图操作都将暂时不输出到绘图窗口上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出。
	int i, j;
	Update_Cover();//图块的is_not_cover全部重置为1,即未被覆盖
	Set_Cover();//再确立重叠关系
	
	putimage(0, 0, &img_bk);//输出背景图片覆盖上次的结果
	
	for (i = 0; i < MAX_LAYER; i++) {
		for (j = 0; maps[i][j] != NULL; j++) {
			struct block* p = maps[i][j];
			if (p->type != -1) {
				putimage(p->x, p->y, &img_item[p->type][p->is_not_cover]);//输出图层
			}
		}
	}


	for (i = 0; groov[i] != -1 && i < 7; i++) {
		putimage(15 + i * BLOCK_WIDTH, 585, &img_item[groov[i]][1]);//输出槽
	}
	
	Update_groov(groov, 7);//更新槽数组,槽中图块是否可以消去

	EndBatchDraw();//缓冲防止图像抖动
}

void InitMap() {
	int layer = 0;
	for (; layer < 12; layer++) {
		InitLayer(1, 1, 55 + layer * 10, 500, layer);
	}
	for (; layer < 24; layer++) {
		InitLayer(1, 1, 350 - (layer - 12) * 10, 500, layer);
	}
	InitLayer(6, 1, 15, 90, layer++);
	InitLayer(3, 1, 77, 130, layer++);
	InitLayer(6, 1, 138, 45, layer++);
	InitLayer(6, 1, 202, 45, layer++);
	InitLayer(6, 1, 264, 45, layer++);
	InitLayer(3, 1, 326, 130, layer++);
	InitLayer(6, 1, 388, 90, layer++);
	InitLayer(4, 1, 48, 120, layer++);
	InitLayer(4, 1, 360, 120, layer++);
	InitLayer(2, 2, 174, 20, layer++);
	InitLayer(2, 2, 174, 345, layer++);
	InitLayer(1, 2, 170, 220, layer++);
	InitLayer(1, 2, 15, 20, layer++);
	InitLayer(1, 2, 326, 20, layer++);
	InitLayer(1, 1, 109, 120, layer++);
	InitLayer(1, 1, 109, 324, layer++);
	InitLayer(1, 1, 299, 120, layer++);
	InitLayer(1, 1, 299, 324, layer++);
	InitLayer(1, 1, 109, 431, layer++);
	InitLayer(1, 1, 299, 431, layer++);
	InitLayer(1, 1, 77, 395, layer++);
	InitLayer(1, 1, 326, 395, layer++);



	Correct_Layer();//修正图块的数据类型,使它们最终能被全部消去

}

void Game() {//游戏初始化
	int i, j;

	for (i = 0; i < 7; i++) {
		groov[i] = -1;//槽数组初始化
	}
	initgraph(WIDTH, HEIGHT);

	InitMap();//初始化图层

	Set_Cover();//初始化明暗重叠关系

	Update_Window();//初始化界面

	int update_flag = 1;//更新标记
	while (1) {

		if (User_Click()) {//用户点击有效,则移动图块到槽中
			Work();
		}

		if (update_flag) {
			Update_Window();//更新,该函数不是只在用户点击后才能运行。在循环中程序会不断检测用户的点击操作,也就是说这个函数实际上是在不断被调用的。
		}

		update_flag = 0;//检测图块是否都被消除,若没有全部被消除,则继续更新,update_flag=1
		for (i = 0; i < MAX_LAYER; i++) {
			for (j = 0; maps[i][j] != NULL; j++) {
				if (maps[i][j]->type != -1) {
					update_flag = 1;
				}
			}
		}

		if (!update_flag) {//游戏通关
			MOUSEMSG m;

			mciSendString("stop music/背景音乐.mp3", 0, 0, 0);//暂停背景音乐
			mciSendString("play music/厉不厉害你坤哥.mp3", 0, 0, 0);//通关成功音乐
			
			putimage(0, 0, &img_success);

			while (1) {
				m = GetMouseMsg();
				switch (m.uMsg)
				{
				case WM_LBUTTONDOWN:
				case WM_RBUTTONDOWN:
					Sleep(10);
					exit(0);
					// 按鼠标左键或右键退出程序
				}
			}

		}
	}
}
int main()
{
	LoadIMG();
	LoadMusic();
	Game();

}

​

效果演示

后面有空再更新讲解。。。。。

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值