消除星星
标签: 消灭星星小游戏
学习人数: 9.2k

前面我们经过了一系列的铺垫,终于到了最重要的一步,即消除星星。

 

可能有同学没有玩过这款小游戏,我们先百度一下游戏规则:

《消灭星星》是一款消除类游戏。游戏目标是将两个或两个以上颜色相同的方块即可消除,消除掉尽可能更多星星,获得更高的分数。

一款非常容易上瘾的消除类游戏,游戏的规则很简单:只需点击两个或两个以上颜色相同的方块即可消除。没有时间限制,每个阶段都有一个目标,完成即可进入下一关。

 

我们先不考虑积分和关卡的问题,提取其中最重要的玩法信息,只需点击两个或两个以上颜色相同的方块即可消除。

 

第一步:分析消除的规则

当我们消除了一些同色的星星之后,就会空出一些位置出来。

如何填补这些空出来的位置呢?

遵循以下规则:

1、如果中间空了行,从上往下落

2、如果中间空了列,从右往左靠

 

第二步:如何寻找周围的同色星星

我们使用遍历的方法即可,往四个方向去找,一直到找不到即结束。

算法代码实现如下:

int direct[4][2] = { 0, 1, 1, 0, 0, -1, -1, 0 };//上下左右四个方向
void FindStar(int x, int y, int color) {
	for (int i = 0; i < 4; i++) {
		int tx = x + direct[i][0];
		int ty = y + direct[i][1];
		if (mpt[tx][ty] == color) {
			mpt[tx][ty] = 0;//0表示空
			FindStar(tx, ty, color);
		}
	}
}

 

第三步:不断点击,不断消除星星

点击之后对应的数组值置为0,表示空白。

这里要特别注意两个点:

1、空白不能继续点

2、每次重新绘图都要一张新的背景图,因为旧的背景已经花了

3、使用循环刷新的方式来模拟游戏的过程

代码如下:

#include<opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<stdio.h>
using namespace cv;

const int WIDTH = 15;
const int HEIGHT = 20;

int mpt[WIDTH][HEIGHT] = { 0 };
Mat image_background;
Mat image_background_new;
Mat image_red;
Mat image_yellow;
Mat image_green;
Mat image_purple;
Mat image_blue;

enum
{
	CV_EVENT_MOUSEMOVE = 0,//移动
	CV_EVENT_LBUTTONDOWN = 1,//左键按下
	CV_EVENT_RBUTTONDOWN = 2,//右键按下
	CV_EVENT_MBUTTONDOWN = 3,//中键按下
	CV_EVENT_LBUTTONUP = 4,//左键弹起
	CV_EVENT_RBUTTONUP = 5,//右键弹起
	CV_EVENT_MBUTTONUP = 6,//中键弹起
	CV_EVENT_LBUTTONDBLCLK = 7,//左键点击
	CV_EVENT_RBUTTONDBLCLK = 8,//右键点击
	CV_EVENT_MBUTTONDBLCLK = 9 //中键点击
};

void Init() { //初始化加载要用到的图片
	image_background = imread("./Image/background.png");
	image_background_new = imread("./Image/background.png");
	image_red = imread("./Image/red.png");
	image_yellow = imread("./Image/yellow.png");
	image_green = imread("./Image/green.png");
	image_purple = imread("./Image/purple.png");
	image_blue = imread("./Image/blue.png");
	if (image_background.empty()) {
		printf("could not load image...\n");
		exit(1);
	}
}

Mat SwitchColor(int color) {//1对应红色星星 2对应黄色星星
	switch (color)
	{
	case 1:
		return image_red;
	case 2:
		return image_yellow;
	case 3:
		return image_green;
	case 4:
		return image_purple;
	case 5:
		return image_blue;
	default:
		break;
	}
}

void DrawImage(int posX, int posY, int color) {//在背景图片上的偏移量
	if (color == 0) return;//空白
	Mat image_star = SwitchColor(color);
	for (int h = 0; h < image_star.rows; h++) {
		for (int w = 0; w < image_star.cols; w++) {
			Vec3b& carPixel = image_star.at<Vec3b>(h, w);
			uchar* ptr = image_background.ptr<uchar>(posX + h, posY + w);
			if ((carPixel[0] <= 30) && (carPixel[1] <= 30) && (carPixel[2] <= 30))
				continue;//RPG 0,0,0即为黑色 我们需要将星星图片周围偏黑的部分过滤掉
			ptr[0] = carPixel[0];
			ptr[1] = carPixel[1];
			ptr[2] = carPixel[2];
		}
	}
}

void RandomMap() {//初始化地图
	srand(time(NULL));//使用时间做随机种子,每次生成的地图不一样
	for (int i = 0; i < WIDTH; i++) {
		for (int j = 0; j < HEIGHT; j++) {
			int x = rand() % 5 + 1;//在1-5之间随机一个数
			mpt[i][j] = x;
		}
	}
}

int direct[4][2] = { 0, 1, 1, 0, 0, -1, -1, 0 };//上下左右四个方向
void FindStar(int x, int y, int color) {
	for (int i = 0; i < 4; i++) {
		int tx = x + direct[i][0];
		int ty = y + direct[i][1];
		if (mpt[tx][ty] == color) {
			mpt[tx][ty] = 0;//0表示空
			FindStar(tx, ty, color);
		}
	}
}

void CalcPosition(int x, int y) {
	int row = y / 30;
	int col = x / 30;
	printf("click star at row:%d col:%d\n", row + 1, col + 1);
	if (mpt[col][row] != 0)//点击不为空白
		FindStar(col, row, mpt[col][row]);
}

void OnMouse(int event, int x, int y, int flags, void* p) {
	if (event == CV_EVENT_LBUTTONDOWN) {//鼠标左键按下 
		CalcPosition(x, y);
	}
}


void Running() {//游戏引擎
	while (1) {
		image_background_new.copyTo(image_background);//使用新的背景图

		for (int i = 0; i < WIDTH; i++) {
			for (int j = 0; j < HEIGHT; j++) {
				int posY = i * 30;
				int posX = j * 30;
				DrawImage(posX, posY, mpt[i][j]);
			}
		}
		
		imshow("PopStarGame", image_background);
		waitKey(100);//100ms刷新一次
	}
}

int main(int argc, char** argv) {
	Init();
	RandomMap();

	namedWindow("PopStarGame", WINDOW_AUTOSIZE);//WINDOW_AUTOSIZE:系统默认,显示自适应
	setMouseCallback("PopStarGame", OnMouse, 0);

	Running();
	
	waitKey(0);

	return 0;
}

运行之后效果如下:

 

第四步:改进前面的一些坑

1、HEIGHT和WIDTH对应是有问题,应该是

const int WIDTH = 15;
const int HEIGHT = 20;

int mpt[HEIGHT][WIDTH] = { 0 };

2、查找的时候没有判断边界

int direct[4][2] = { 0, 1, 1, 0, 0, -1, -1, 0 };//上下左右四个方向
void FindStar(int x, int y, int color) {
	for (int i = 0; i < 4; i++) {
		int tx = x + direct[i][0];
		int ty = y + direct[i][1];
		if (tx < 0 || ty < 0 || tx >= HEIGHT || ty >= WIDTH) continue;//超边界
		if (mpt[tx][ty] == color) {
			mpt[tx][ty] = 0;//0表示空
			FindStar(tx, ty, color);
		}
	}
}

 

第五步:模拟下落和从右往左靠齐的效果

void CheckFall() {//下落之后的位置计算
	for (int i = 0; i < HEIGHT; i++) {
		for (int j = 0; j < WIDTH; j++) {
			if (mpt[i][j] == 0) {
				for (int k = i; k >= 0; k--) {
					if (k != 0) mpt[k][j] = mpt[k - 1][j];
					else  mpt[k][j] = 0;
				}
			}
		}
	}
}

void CheckRight2Left() {//靠左之后的位置计算
	for (int j = WIDTH - 1; j >= 0; j--) {
		int flag = 0;
		for (int i = 0; i < HEIGHT; i++)
			if (mpt[i][j] != 0) flag = 1;
		if (flag == 0) {
			for (int k = j; k < WIDTH; k++) {
				for (int i = 0; i < HEIGHT; i++) {
					if (k != WIDTH - 1) mpt[i][k] = mpt[i][k + 1];
					else mpt[i][k] = 0;
				}
			}
		}
	}
}

void MovePosition() {
	CheckFall();
	CheckRight2Left();
}

 

完整代码

#include<opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<stdio.h>
using namespace cv;

const int WIDTH = 15;
const int HEIGHT = 20;

int mpt[HEIGHT][WIDTH] = { 0 };
Mat image_background;
Mat image_background_new;
Mat image_red;
Mat image_yellow;
Mat image_green;
Mat image_purple;
Mat image_blue;

enum
{
	CV_EVENT_MOUSEMOVE = 0,//移动
	CV_EVENT_LBUTTONDOWN = 1,//左键按下
	CV_EVENT_RBUTTONDOWN = 2,//右键按下
	CV_EVENT_MBUTTONDOWN = 3,//中键按下
	CV_EVENT_LBUTTONUP = 4,//左键弹起
	CV_EVENT_RBUTTONUP = 5,//右键弹起
	CV_EVENT_MBUTTONUP = 6,//中键弹起
	CV_EVENT_LBUTTONDBLCLK = 7,//左键点击
	CV_EVENT_RBUTTONDBLCLK = 8,//右键点击
	CV_EVENT_MBUTTONDBLCLK = 9 //中键点击
};

void Init() { //初始化加载要用到的图片
	image_background = imread("./Image/background.png");
	image_background_new = imread("./Image/background.png");
	image_red = imread("./Image/red.png");
	image_yellow = imread("./Image/yellow.png");
	image_green = imread("./Image/green.png");
	image_purple = imread("./Image/purple.png");
	image_blue = imread("./Image/blue.png");
	if (image_background.empty()) {
		printf("could not load image...\n");
		exit(1);
	}
}

Mat SwitchColor(int color) {//1对应红色星星 2对应黄色星星
	switch (color)
	{
	case 1:
		return image_red;
	case 2:
		return image_yellow;
	case 3:
		return image_green;
	case 4:
		return image_purple;
	case 5:
		return image_blue;
	default:
		break;
	}
}

void DrawImage(int posX, int posY, int color) {//在背景图片上的偏移量
	if (color == 0) return;//空白
	Mat image_star = SwitchColor(color);
	for (int h = 0; h < image_star.rows; h++) {
		for (int w = 0; w < image_star.cols; w++) {
			Vec3b& carPixel = image_star.at<Vec3b>(h, w);
			uchar* ptr = image_background.ptr<uchar>(posX + h, posY + w);
			if ((carPixel[0] <= 30) && (carPixel[1] <= 30) && (carPixel[2] <= 30))
				continue;//RPG 0,0,0即为黑色 我们需要将星星图片周围偏黑的部分过滤掉
			ptr[0] = carPixel[0];
			ptr[1] = carPixel[1];
			ptr[2] = carPixel[2];
		}
	}
}

void RandomMap() {//初始化地图
	srand(time(NULL));//使用时间做随机种子,每次生成的地图不一样
	for (int i = 0; i < HEIGHT; i++) {
		for (int j = 0; j < WIDTH; j++) {
			int x = rand() % 5 + 1;//在1-5之间随机一个数
			mpt[i][j] = x;
		}
	}
}

int direct[4][2] = { 0, 1, 1, 0, 0, -1, -1, 0 };//上下左右四个方向
void FindStar(int x, int y, int color) {
	for (int i = 0; i < 4; i++) {
		int tx = x + direct[i][0];
		int ty = y + direct[i][1];
		if (tx < 0 || ty < 0 || tx >= HEIGHT || ty >= WIDTH) continue;//超边界
		if (mpt[tx][ty] == color) {
			mpt[tx][ty] = 0;//0表示空
			FindStar(tx, ty, color);
		}
	}
}

void CheckFall() {//下落之后的位置计算
	for (int i = 0; i < HEIGHT; i++) {
		for (int j = 0; j < WIDTH; j++) {
			if (mpt[i][j] == 0) {
				for (int k = i; k >= 0; k--) {
					if (k != 0) mpt[k][j] = mpt[k - 1][j];
					else  mpt[k][j] = 0;
				}
			}
		}
	}
}

void CheckRight2Left() {//靠左之后的位置计算
	for (int j = WIDTH - 1; j >= 0; j--) {
		int flag = 0;
		for (int i = 0; i < HEIGHT; i++)
			if (mpt[i][j] != 0) flag = 1;
		if (flag == 0) {
			for (int k = j; k < WIDTH; k++) {
				for (int i = 0; i < HEIGHT; i++) {
					if (k != WIDTH - 1) mpt[i][k] = mpt[i][k + 1];
					else mpt[i][k] = 0;
				}
			}
		}
	}
}

void MovePosition() {
	CheckFall();
	CheckRight2Left();
}

void CalcPosition(int x, int y) {
	int row = y / 30;
	int col = x / 30;
	printf("click star at row:%d col:%d\n", row + 1, col + 1);
	if (mpt[row][col] != 0) {//点击不为空白
		FindStar(row, col, mpt[row][col]);
		MovePosition();
	}
}

void OnMouse(int event, int x, int y, int flags, void* p) {
	if (event == CV_EVENT_LBUTTONDOWN) {//鼠标左键按下 
		CalcPosition(x, y);
	}
}



void Running() {//游戏引擎
	while (1) {
		image_background_new.copyTo(image_background);//使用新的背景图
		
		for (int i = 0; i < HEIGHT; i++) {
			for (int j = 0; j < WIDTH; j++) {
				int posX = i * 30;
				int posY = j * 30;
				DrawImage(posX, posY, mpt[i][j]);
			}
		}
		
		imshow("PopStarGame", image_background);
		waitKey(100);//100ms刷新一次
	}
}

int main(int argc, char** argv) {
	Init();
	RandomMap();

	namedWindow("PopStarGame", WINDOW_AUTOSIZE);//WINDOW_AUTOSIZE:系统默认,显示自适应
	setMouseCallback("PopStarGame", OnMouse, 0);

	Running();
	
	waitKey(0);

	return 0;
}

运行之后效果如下:

大功告成!!!!!



课后作业

完成本节课的内容


登录后开始许愿

暂无评论,来抢沙发