C++入门——实现“祖玛”游戏

参考

  1. 《C和C++游戏趣味编程》 童晶

祖玛游戏

游戏的思路是,各种颜色的小球沿着轨道移动,玩家必须阻止小球进入轨道终点的城堡。玩家移动鼠标控制炮台旋转,按下鼠标右键更换小球颜色,点击鼠标左键发射小球。发射的小球进入轨道,如果周围有连续3个相同颜色的小球即可消除
在这里插入图片描述

顶点类

游戏中小球会沿着固定的轨迹移动,而轨迹可由一些离散的顶点组成,定义顶点类:

class Point
{
public:
	float x, y;
	Point()
	{

	}
	Point(float ix, float iy)
	{
		x = ix;
		y = iy;
	}
};

轨迹类

在Point的基础上,定义轨迹类,并添加draw()成员函数基于关键点绘制圆和连线:

class Path
{
public:
	vector<Point> keyPoints;

	void draw()
	{
		setlinecolor(RGB(0, 0, 0));
		setfillcolor(RGB(0, 0, 0));
		for (int i = 0; i < keyPoints.size(); i++)
		{
			fillcircle(keyPoints[i].x, keyPoints[i].y, 8);
		}
		for (int i = 0; i < keyPoints.size() - 1; i++)
		{
			line(keyPoints[i].x, keyPoints[i].y, keyPoints[i + 1].x, keyPoints[i + 1].y);
		}
	}
};

进一步,在关键点的连线上进行等间隔采样,小球可以在这些稠密的采样点上移动,从而实现运动的效果:

class Path
{
public:
	vector<Point> keyPoints;
	float sampleInterval;                                    // 采样间隔
	vector<Point> allPoints;                                 // 所有采样得到的点

	void getAllPoints()
	{
		int i;
		for (i = 0; i < keyPoints.size() - 1; i++)
		{
			float xd = keyPoints[i + 1].x - keyPoints[i].x;
			float yd = keyPoints[i + 1].y - keyPoints[i].y;
			float length = sqrt(xd * xd + yd * yd);

			int num = length / sampleInterval;
			for (int j = 0; j < num; j++)
			{
				float x_sample = keyPoints[i].x + j * xd / num;
				float y_sample = keyPoints[i].y + j * yd / num;
				allPoints.push_back(Point(x_sample, y_sample));
			}
		}
	}

	void draw()
	{
		setlinecolor(RGB(0, 0, 0));
		setfillcolor(RGB(0, 0, 0));
		for (int i = 0; i < keyPoints.size() - 1; i++)            // 将关键点依次连接形成多条线段
		{
			line(keyPoints[i].x, keyPoints[i].y, keyPoints[i + 1].x, keyPoints[i + 1].y);
		}
		for (int i = 0; i < allPoints.size(); i++)            // 在所有采样点处画一个小圆点
		{
			fillcircle(allPoints[i].x, allPoints[i].y, 3);
		}
	}

绘制出所有的采样点:

#include <graphics.h>
#include <conio.h>
#include <vector>
using namespace std;
#define WIDTH 1000
#define HEIGHT 700

class Point
{
public:
	float x, y;
	Point()
	{

	}
	Point(float ix, float iy)
	{
		x = ix;
		y = iy;
	}
};

class Path
{
public:
	vector<Point> keyPoints;
	float sampleInterval;                                    // 采样间隔
	vector<Point> allPoints;                                 // 所有采样得到的点

	void getAllPoints()
	{
		int i;
		for (i = 0; i < keyPoints.size() - 1; i++)
		{
			float xd = keyPoints[i + 1].x - keyPoints[i].x;
			float yd = keyPoints[i + 1].y - keyPoints[i].y;
			float length = sqrt(xd * xd + yd * yd);

			int num = length / sampleInterval;
			for (int j = 0; j < num; j++)
			{
				float x_sample = keyPoints[i].x + j * xd / num;
				float y_sample = keyPoints[i].y + j * yd / num;
				allPoints.push_back(Point(x_sample, y_sample));
			}
		}
	}

	void draw()
	{
		setlinecolor(RGB(0, 0, 0));
		setfillcolor(RGB(0, 0, 0));
		for (int i = 0; i < keyPoints.size() - 1; i++)            // 将关键点依次连接形成多条线段
		{
			line(keyPoints[i].x, keyPoints[i].y, keyPoints[i + 1].x, keyPoints[i + 1].y);
		}
		for (int i = 0; i < allPoints.size(); i++)            // 在所有采样点处画一个小圆点
		{
			fillcircle(allPoints[i].x, allPoints[i].y, 3);
		}
	}
};

Path path;

void startup()
{
	initgraph(WIDTH, HEIGHT);
	setbkcolor(WHITE);
	cleardevice();

	path.keyPoints.push_back(Point(100, 600));
	path.keyPoints.push_back(Point(900, 600));
	path.keyPoints.push_back(Point(900, 100));
	path.keyPoints.push_back(Point(100, 100));
	path.sampleInterval = 10;
	path.getAllPoints();
	BeginBatchDraw();
}

void show()
{
	cleardevice();
	path.draw();
	FlushBatchDraw();
	Sleep(1);
}

int main()
{
	startup();
	while (1)
	{
		show();
	}
	return 0;
}

在这里插入图片描述

小球类

小球一共有5种颜色,定义数组存储:

#define ColorNum 5
COLORREF colors[ColorNum] = { RED, BLUE, GREEN, YELLOW, MAGENTA };

定义小球类,添加成员函数movetoIndexInPath()让小球移动到轨迹相应位置:

class Ball
{
public:
	Point center;
	float radius;
	int colorId;
	int indexInPath;                              // 小球位置在Path的allPoints中的对应序号

	void draw()
	{
		setlinecolor(colors[colorId]);
		setfillcolor(colors[colorId]);
		fillcircle(center.x, center.y, radius);
	}

	void movetoIndexPath(Path path)
	{
		center = path.allPoints[indexInPath];
	}

	void initiate(Path path)
	{
		radius = Radius;
		indexInPath = 0;
		movetoIndexPath(path);
		colorId = rand() % ColorNum;
	}
};

修改startup(),向轨迹中放置30个小球:

path.sampleInterval = Radius / 5;
path.getAllPoints();

for (int i = 30; i >= 0; i--)
{
	Ball ball;
	ball.initiate(path);
	ball.indexInPath = i * (2 * ball.radius / path.sampleInterval);
	ball.movetoIndexPath(path);
	balls.push_back(ball);
}

在这里插入图片描述

小球自动运动

在Ball类中新增成员变量direction,设定小球沿着轨迹线的移动方向,1向终点运动,-1向起点运动,0暂停。initiate()中让小球初始状态为静止:

int direction;

void initiate(Path path)
{
	radius = Radius;
	indexInPath = 0;
	direction = 0;
	movetoIndexPath(path);
	colorId = rand() % ColorNum;
}

定义changeIndexbyDirection()成员函数,用于判断移动方向并移动:

void changeIndexbyDirection(Path path)
{
	if (direction == 1 && indexInPath + 1 < path.allPoints.size())
	{
		indexInPath++;
	}
	else if (direction == -1 && indexInPath - 1 >= 0)
	{
		indexInPath--;
	}
}

小球运动的源动力来自于最后一个在起点处添加的小球。将最接近起点的小球的direction设为1,如果终点方向前面一个小球和这个小球相切,则其direction也等于1,否则为0。在updateWithoutInput()中实现:

void updateWithoutInput()
{
	if (balls[0].indexInPath >= path.allPoints.size() - 1)   // 第一个求到了终点,游戏失败
	{
		return;
	}

	int i;
	for (i = 0; i < balls.size(); i++)
	{
		balls[i].direction = 0;
	}

	i = balls.size() - 1;                                    // 最后一个小球
	balls[i].direction = 1;                                  // 最后一个小球向前运动

	while (i > 0)
	{
		if (balls[i - 1].indexInPath - balls[i].indexInPath <= 2 * Radius / path.sampleInterval) // 前后两个小球相切
		{
			balls[i - 1].direction = 1;
			balls[i - 1].indexInPath = balls[i].indexInPath + 2 * Radius / path.sampleInterval;
			i--;
		}
		else
		{
			break;
		}
	}

	for (int i = 0; i < balls.size(); i++)                   // 小球根据direction更新位置
	{
		balls[i].movetoIndexPath(path);
		balls[i].changeIndexbyDirection(path);
	}

	Sleep(50);
}

在这里插入图片描述

小球的插入与消除

遍历balls中所有小球,找到被鼠标点击的小球,然后将当前小球复制一份,插入到被点击的小球的位置,前面的小球依次向轨迹线终点方向移动:

void updateWithInput()
{
	int i, j;
	ExMessage e;
	while (peekmessage(&e))
	{
		if (e.message == WM_LBUTTONDOWN)
		{
			for (i = 0; i < balls.size(); i++)
			{
				float distance = Distance(balls[i].center.x, balls[i].center.y, e.x, e.y);
				if (distance < Radius)
				{
					Ball fireball = balls[i];
					balls.insert(balls.begin() + i, fireball);  // 复制一个小球插入
					for (j = i; j >= 0; j--)
					{
						if (balls[j].indexInPath - balls[j + 1].indexInPath <= 0)
						{
							balls[j].indexInPath = balls[j + 1].indexInPath + 2 * Radius / path.sampleInterval;
						}
						else
						{
							break;
						}
					}
					return;
				}
			}
		}
	}
}

对被鼠标点击的小球周围进行判断,如果有位置连续、颜色相同且数目大于等于3的小球,就将这些小球删除:

int eraseSameColorBalls(int i, Ball fireball, Path& path, vector<Ball>& balls)
{
	vector<int> sameColorIndexes;                         // 记录与插入小球颜色一样的序号
	int forward = i;
	int backward = i;
	sameColorIndexes.push_back(i);

	while (forward > 0 && balls[forward].colorId == fireball.colorId)  // 向终点方向寻找
	{
		sameColorIndexes.push_back(forward);
		if (balls[forward - 1].indexInPath - balls[forward].indexInPath > 2 * Radius / path.sampleInterval)
		{
			break;
		}
		forward--;
	}

	if (forward == 0 && balls[0].colorId == fireball.colorId)          // 临界情况
	{
		sameColorIndexes.push_back(forward);
	}

	while (backward < balls.size() - 1 && balls[backward].colorId == fireball.colorId)  // 向起点方向寻找
	{
		sameColorIndexes.push_back(backward);
		if (balls[backward].indexInPath - balls[backward + 1].indexInPath > 2 * Radius / path.sampleInterval)
		{
			break;
		}
		backward++;
	}

	if (backward == balls.size() - 1 && balls[balls.size() - 1].colorId == fireball.colorId)
	{
		sameColorIndexes.push_back(backward);
	}

	sort(sameColorIndexes.begin(), sameColorIndexes.end());
	vector<int>::iterator ite = unique(sameColorIndexes.begin(), sameColorIndexes.end()); // 去重
	sameColorIndexes.erase(ite, sameColorIndexes.end());

	int NumSameColors = sameColorIndexes.size();
	if (NumSameColors >= 3)
	{
		int minIndex = sameColorIndexes[0];
		int maxIndex = sameColorIndexes[NumSameColors - 1];
		balls.erase(balls.begin() + minIndex, balls.begin() + maxIndex + 1);              // 删除同颜色小球
		return NumSameColors;
	}
	return 0;
}

炮台类

定义炮台类:

class Cannon
{
public:
	IMAGE im;
	IMAGE im_rotate;
	float x, y;
	Ball ball;
	float angle;

	void draw()
	{
		rotateimage(&im_rotate, &im, angle, RGB(160, 211, 255), false, false);
		putimage(x - im.getwidth() / 2, y - im.getheight() / 2, &im_rotate);
		ball.draw();
	}

	void setBallPosition()
	{
		ball.center.x = x + 100 * cos(angle);
		ball.center.y = y + 100 * sin(angle);
	}
};

在startup()中初始化背景、炮台等:

loadimage(&im_bk, _T("bk.jpg")); // 导入背景图片
loadimage(&im_role, _T("role.jpg")); // 导入角色图片
loadimage(&im_house, _T("house.jpg")); // 导入家图片

cannon.im = im_role; // 炮台角色图片
cannon.angle = 0; // 初始角度
cannon.x = 500;  // 中心坐标
cannon.y = 350;
cannon.ball.radius = Radius; // 炮台带的小球的半径
cannon.ball.colorId = rand() % ColorNum; // 炮台小球颜色
cannon.setBallPosition(); // 设置炮台小球的坐标

在这里插入图片描述

炮台旋转与更改小球颜色

为炮台类添加成员函数updateWithMouseMOVE(),实现炮台跟着鼠标旋转:

void updateWithMouseMOVE(int mx, int my)
{
	float xs = mx - x;
	float ys = my - y;
	float length = sqrt(xs * xs + ys * ys);
	if (length > 4)
	{
		angle = atan2(-ys, xs);
		ball.center.x = x + 100 * xs / length;
		ball.center.y = y + 100 * ys / length;
	}
}

添加成员函数updateWithButtonDown(),实现鼠标点击时,改变小球颜色:

void updateWithRButtonDown()
{
	ball.colorId += 1;
	if (ball.colorId == ColorNum)
	{
		ball.colorId = 0;
	}
}

然后在updateWithInput()中调用即可

炮台发射小球

当点击鼠标左键时,炮台小球沿着朝向鼠标的射线方向进行移动。判断每帧移动后和balls中的所有小球是否相交,如果没有则继续移动;如果有则将炮台小球插入到当前位置:

if (e.message == WM_LBUTTONDOWN)
		{
			cannon.updateWithMouseMOVE(e.x, e.y);               // 先更新炮台旋转角度、小球坐标
			float vx = (cannon.ball.center.x - cannon.x) / 5;   // 小球移动速度
			float vy = (cannon.ball.center.y - cannon.y) / 5;
			int isCollider = 0;
			while (isCollider == 0 && cannon.ball.center.y > 0 && cannon.ball.center.y < HEIGHT && cannon.ball.center.x > 0 && cannon.ball.center.x < WIDTH) // 没有碰撞和越界
			{
				cannon.ball.center.x += vx;
				cannon.ball.center.y += vy;
				show();                                         // 显示移动轨迹
				for (i = 0; i < balls.size(); i++)
				{
					float distance = Distance(balls[i].center.x, balls[i].center.y, cannon.ball.center.x, cannon.ball.center.y);
					if (distance < Radius)                      // 找到与炮台小球碰撞的小球
					{
						isCollider = 1;
						cannon.updateWithMouseMOVE(e.x, e.y);   // 恢复炮台小球位置

						Ball fireball = balls[i];
						fireball.colorId = cannon.ball.colorId;
						balls.insert(balls.begin() + i, fireball);  // 复制一个小球插入

						int count = eraseSameColorBalls(i, fireball, path, balls);
						if (count == 0)
						{
							for (j = i; j >= 0; j--)
							{
								if (balls[j].indexInPath - balls[j + 1].indexInPath <= 0)
								{
									balls[j].indexInPath = balls[j + 1].indexInPath + 2 * Radius / path.sampleInterval;
								}
								else
								{
									break;
								}
							}
						}
						return;
					}
				}
			}
			
		}

连续出球与胜负判断

定义全局变量gameStatus描述游戏状态,在show()中根据游戏状态显示对应文字:

setbkmode(TRANSPARENT);
settextcolor(RGB(255, 0, 0));
settextstyle(60, 0, _T("宋体"));
if (gameStatus == 1)
{
	outtextxy(WIDTH * 0.35, HEIGHT * 0.35, _T("游戏胜利 :)"));
}
else if (gameStatus == -1)
{
	outtextxy(WIDTH * 0.35, HEIGHT * 0.35, _T("游戏失败 :("));
}

每隔10秒产生一批小球。如果balls中没有小球且运行时间超过100秒,游戏胜利;如果有球跑到终点,游戏失败:

static clock_t start = clock();                          // 第一次运行时刻
clock_t now = clock();                                   // 当前时刻
int nowSencond = (int(now - start) / CLOCKS_PER_SEC);    // 程序目前运行了多少秒
if (nowSencond % 10 == 0 && nowSencond <= 100 && gameStatus == 0)  // 每隔10秒,新增一批小球
{
	Ball ball;
	ball.initiate(path);
	balls.push_back(ball);
}
if (balls.size() == 0)
{
	if (nowSencond > 100)
	{
		gameStatus = 1;
	}
	return;
}
if (balls[0].indexInPath >= path.allPoints.size() - 1)   // 第一个求到了终点,游戏失败
{
	gameStatus = -1;
	return;
}

添加音效

在startup()中循环播放背景音乐:

mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL);//打开背景音乐
mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL);  // 循环播放

在updateWithInput()中,消除小球时播放一次消除音效:

if (count >= 3)
	PlayMusicOnce((TCHAR*)_T("coin.mp3"));          // 播放一次金币音效

添加复杂轨道

path.keyPoints.push_back(Point(50, 300));
path.keyPoints.push_back(Point(50, 600));
path.keyPoints.push_back(Point(100, 650));
path.keyPoints.push_back(Point(700, 650));
path.keyPoints.push_back(Point(700, 550));
path.keyPoints.push_back(Point(250, 550));
path.keyPoints.push_back(Point(200, 500));
path.keyPoints.push_back(Point(200, 200));
path.keyPoints.push_back(Point(250, 150));
path.keyPoints.push_back(Point(800, 150));
path.keyPoints.push_back(Point(850, 200));
path.keyPoints.push_back(Point(850, 650));
path.keyPoints.push_back(Point(950, 650));
path.keyPoints.push_back(Point(950, 100));
path.keyPoints.push_back(Point(900, 50));
path.keyPoints.push_back(Point(150, 50));

在这里插入图片描述

最后对轨道做一些美化:

在这里插入图片描述

完整代码

#include <graphics.h>
#include <conio.h>
#include <time.h>
#include <vector>
#include <algorithm>
#pragma comment(lib, "Winmm.lib")
using namespace std;
#define WIDTH 1000
#define HEIGHT 700
#define Radius 25
#define ColorNum 5
COLORREF colors[ColorNum] = { RED, BLUE, GREEN, YELLOW, MAGENTA };

float Distance(float x1, float y1, float x2, float y2)
{
	float xd = x1 - x2;
	float yd = y1 - y2;
	float length = sqrt(xd * xd + yd * yd);
	return length;
}

void sleep(DWORD ms)  // 精确延时函数
{
	static DWORD oldtime = GetTickCount();
	while (GetTickCount() - oldtime < ms)
	{
		Sleep(1);
	}
	oldtime = GetTickCount();
}

void PlayMusicOnce(TCHAR fileName[80])
{
	TCHAR cmdString1[50];
	swprintf_s(cmdString1, _T("open %s alias tmpmusic"), fileName);    // 生成命令字符串
	mciSendString(_T("close tmpmusic"), NULL, 0, NULL);                // 先把前面一次的音乐关闭
	mciSendString(cmdString1, NULL, 0, NULL);                          // 打开音乐
	mciSendString(_T("play tmpmusic"), NULL, 0, NULL);                 // 仅播放一次
}

class Point
{
public:
	float x, y;
	Point()
	{

	}
	Point(float ix, float iy)
	{
		x = ix;
		y = iy;
	}
};

class Path
{
public:
	vector<Point> keyPoints;
	float sampleInterval;                                    // 采样间隔
	vector<Point> allPoints;                                 // 所有采样得到的点

	void getAllPoints()
	{
		int i;
		for (i = 0; i < keyPoints.size() - 1; i++)
		{
			float xd = keyPoints[i + 1].x - keyPoints[i].x;
			float yd = keyPoints[i + 1].y - keyPoints[i].y;
			float length = sqrt(xd * xd + yd * yd);

			int num = length / sampleInterval;
			for (int j = 0; j < num; j++)
			{
				float x_sample = keyPoints[i].x + j * xd / num;
				float y_sample = keyPoints[i].y + j * yd / num;
				allPoints.push_back(Point(x_sample, y_sample));
			}
		}
		allPoints.push_back(Point(keyPoints[i].x, keyPoints[i].y)); // 最后一个关键点
	}

	void draw()
	{
		setlinecolor(RGB(0, 0, 0));
		setfillcolor(RGB(0, 0, 0));
		for (int i = 0; i < keyPoints.size() - 1; i++)            // 将关键点依次连接形成多条线段
		{
			line(keyPoints[i].x, keyPoints[i].y, keyPoints[i + 1].x, keyPoints[i + 1].y);
		}
		for (int i = 0; i < allPoints.size(); i++)            // 在所有采样点处画一个小圆点
		{
			fillcircle(allPoints[i].x, allPoints[i].y, 2);
		}
	}

	~Path()
	{
		keyPoints.clear();
		allPoints.clear();
	}
};

class Ball
{
public:
	Point center;
	float radius;
	int colorId;
	int indexInPath;                              // 小球位置在Path的allPoints中的对应序号
	int direction;

	void draw()
	{
		setlinecolor(colors[colorId]);
		setfillcolor(colors[colorId]);
		fillcircle(center.x, center.y, radius);
	}

	void movetoIndexPath(Path path)
	{
		center = path.allPoints[indexInPath];
	}

	void initiate(Path path)
	{
		radius = Radius;
		indexInPath = 0;
		direction = 0;
		movetoIndexPath(path);
		colorId = rand() % ColorNum;
	}

	void changeIndexbyDirection(Path path)
	{
		if (direction == 1 && indexInPath + 1 < path.allPoints.size())
		{
			indexInPath++;
		}
		else if (direction == -1 && indexInPath - 1 >= 0)
		{
			indexInPath--;
		}
	}
};

class Cannon
{
public:
	IMAGE im;
	IMAGE im_rotate;
	float x, y;
	Ball ball;                             // 炮台自带的小球
	float angle;

	void draw()
	{
		rotateimage(&im_rotate, &im, angle, RGB(160, 211, 255), false, false);
		putimage(x - im.getwidth() / 2, y - im.getheight() / 2, &im_rotate);
		ball.draw();
	}

	void setBallPosition()
	{
		ball.center.x = x + 100 * cos(angle);
		ball.center.y = y + 100 * sin(angle);
	}

	void updateWithMouseMOVE(int mx, int my)
	{
		float xs = mx - x;
		float ys = my - y;
		float length = sqrt(xs * xs + ys * ys);
		if (length > 4)
		{
			angle = atan2(-ys, xs);
			ball.center.x = x + 100 * xs / length;
			ball.center.y = y + 100 * ys / length;
		}
	}

	void updateWithRButtonDown()
	{
		ball.colorId += 1;
		if (ball.colorId == ColorNum)
		{
			ball.colorId = 0;
		}
	}
};

int eraseSameColorBalls(int i, Ball fireball, Path& path, vector<Ball>& balls)
{
	vector<int> sameColorIndexes;                         // 记录与插入小球颜色一样的序号
	int forward = i;
	int backward = i;
	sameColorIndexes.push_back(i);

	while (forward > 0 && balls[forward].colorId == fireball.colorId)  // 向终点方向寻找
	{
		sameColorIndexes.push_back(forward);
		if (balls[forward - 1].indexInPath - balls[forward].indexInPath > 2 * Radius / path.sampleInterval)
		{
			break;
		}
		forward--;
	}

	if (forward == 0 && balls[0].colorId == fireball.colorId)          // 临界情况
	{
		sameColorIndexes.push_back(forward);
	}

	while (backward < balls.size() - 1 && balls[backward].colorId == fireball.colorId)  // 向起点方向寻找
	{
		sameColorIndexes.push_back(backward);
		if (balls[backward].indexInPath - balls[backward + 1].indexInPath > 2 * Radius / path.sampleInterval)
		{
			break;
		}
		backward++;
	}

	if (backward == balls.size() - 1 && balls[balls.size() - 1].colorId == fireball.colorId)
	{
		sameColorIndexes.push_back(backward);
	}

	sort(sameColorIndexes.begin(), sameColorIndexes.end());
	vector<int>::iterator ite = unique(sameColorIndexes.begin(), sameColorIndexes.end()); // 去重
	sameColorIndexes.erase(ite, sameColorIndexes.end());

	int NumSameColors = sameColorIndexes.size();
	if (NumSameColors >= 3)
	{
		int minIndex = sameColorIndexes[0];
		int maxIndex = sameColorIndexes[NumSameColors - 1];
		balls.erase(balls.begin() + minIndex, balls.begin() + maxIndex + 1);              // 删除同颜色小球
		return NumSameColors;
	}
	return 0;
}

Path path;
vector<Ball> balls;
IMAGE im_role, im_house, im_bk; // 一些图片
Cannon cannon;  // 定义炮台对象
int gameStatus = 0;

void startup()
{
	mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL);//打开背景音乐
	mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL);  // 循环播放
	srand(time(0));
	initgraph(WIDTH, HEIGHT);
	cleardevice();
	loadimage(&im_bk, _T("bk.jpg")); // 导入背景图片
	loadimage(&im_role, _T("role.jpg")); // 导入角色图片
	loadimage(&im_house, _T("house.jpg")); // 导入家图片

	path.keyPoints.push_back(Point(50, 300));
	path.keyPoints.push_back(Point(50, 600));
	path.keyPoints.push_back(Point(100, 650));
	path.keyPoints.push_back(Point(700, 650));
	path.keyPoints.push_back(Point(700, 550));
	path.keyPoints.push_back(Point(250, 550));
	path.keyPoints.push_back(Point(200, 500));
	path.keyPoints.push_back(Point(200, 200));
	path.keyPoints.push_back(Point(250, 150));
	path.keyPoints.push_back(Point(800, 150));
	path.keyPoints.push_back(Point(850, 200));
	path.keyPoints.push_back(Point(850, 650));
	path.keyPoints.push_back(Point(950, 650));
	path.keyPoints.push_back(Point(950, 100));
	path.keyPoints.push_back(Point(900, 50));
	path.keyPoints.push_back(Point(150, 50));

	path.sampleInterval = Radius / 5;
	path.getAllPoints();

	cannon.im = im_role; // 炮台角色图片
	cannon.angle = 0; // 初始角度
	cannon.x = 500;  // 中心坐标
	cannon.y = 350;
	cannon.ball.radius = Radius; // 炮台带的小球的半径
	cannon.ball.colorId = rand() % ColorNum; // 炮台小球颜色
	cannon.setBallPosition(); // 设置炮台小球的坐标

	for (int i = 15; i >= 10; i--)
	{
		Ball ball;
		ball.initiate(path);
		ball.indexInPath = i * (2 * ball.radius / path.sampleInterval);
		ball.movetoIndexPath(path);
		balls.push_back(ball);
	}
	for (int i = 5; i >= 0; i--)
	{
		Ball ball;
		ball.initiate(path);
		ball.indexInPath = i * (2 * ball.radius / path.sampleInterval);
		ball.movetoIndexPath(path);
		balls.push_back(ball);
	}

	BeginBatchDraw();
}

void show()
{
	putimage(0, 0, &im_bk); // 显示背景图片
	putimage(30, 10, &im_house); // 显示房子图片
	//path.draw();
	cannon.draw();
	for (int i = 0; i < balls.size(); i++)
	{
		balls[i].draw();
	}

	setbkmode(TRANSPARENT);
	settextcolor(RGB(255, 0, 0));
	settextstyle(60, 0, _T("宋体"));
	if (gameStatus == 1)
	{
		outtextxy(WIDTH * 0.35, HEIGHT * 0.35, _T("游戏胜利 :)"));
	}
	else if (gameStatus == -1)
	{
		outtextxy(WIDTH * 0.35, HEIGHT * 0.35, _T("游戏失败 :("));
	}
	FlushBatchDraw();
}

void updateWithoutInput()
{
	static clock_t start = clock();                          // 第一次运行时刻
	clock_t now = clock();                                   // 当前时刻
	int nowSencond = (int(now - start) / CLOCKS_PER_SEC);    // 程序目前运行了多少秒
	if (nowSencond % 10 == 0 && nowSencond <= 100 && gameStatus == 0)  // 每隔10秒,新增一批小球
	{
		Ball ball;
		ball.initiate(path);
		balls.push_back(ball);
	}
	if (balls.size() == 0)
	{
		if (nowSencond > 100)
		{
			gameStatus = 1;
		}
		return;
	}
	if (balls[0].indexInPath >= path.allPoints.size() - 1)   // 第一个求到了终点,游戏失败
	{
		gameStatus = -1;
		return;
	}

	int i;
	for (i = 0; i < balls.size(); i++)
	{
		balls[i].direction = 0;
	}

	i = balls.size() - 1;                                    // 最后一个小球
	balls[i].direction = 1;                                  // 最后一个小球向前运动

	while (i > 0)
	{
		if (balls[i - 1].indexInPath - balls[i].indexInPath <= 2 * Radius / path.sampleInterval) // 前后两个小球相切
		{
			balls[i - 1].direction = 1;
			balls[i - 1].indexInPath = balls[i].indexInPath + 2 * Radius / path.sampleInterval;
			i--;
		}
		else
		{
			break;
		}
	}

	for (int i = 0; i < balls.size(); i++)                   // 小球根据direction更新位置
	{
		balls[i].movetoIndexPath(path);
		balls[i].changeIndexbyDirection(path);
	}

	Sleep(50);
}

void updateWithInput()
{
	if (gameStatus != 0)
	{
		return;
	}
	int i, j;
	ExMessage e;
	while (peekmessage(&e))
	{
		if (e.message == WM_MOUSEMOVE)
		{
			cannon.updateWithMouseMOVE(e.x, e.y);
		}
		else if (e.message == WM_RBUTTONDOWN)
		{
			cannon.updateWithRButtonDown();
		}
		else if (e.message == WM_LBUTTONDOWN)
		{
			cannon.updateWithMouseMOVE(e.x, e.y);               // 先更新炮台旋转角度、小球坐标
			float vx = (cannon.ball.center.x - cannon.x) / 5;   // 小球移动速度
			float vy = (cannon.ball.center.y - cannon.y) / 5;
			int isCollider = 0;
			while (isCollider == 0 && cannon.ball.center.y > 0 && cannon.ball.center.y < HEIGHT && cannon.ball.center.x > 0 && cannon.ball.center.x < WIDTH) // 没有碰撞和越界
			{
				cannon.ball.center.x += vx;
				cannon.ball.center.y += vy;
				show();                                         // 显示移动轨迹
				for (i = 0; i < balls.size(); i++)
				{
					float distance = Distance(balls[i].center.x, balls[i].center.y, cannon.ball.center.x, cannon.ball.center.y);
					if (distance < Radius)                      // 找到与炮台小球碰撞的小球
					{
						isCollider = 1;
						cannon.updateWithMouseMOVE(e.x, e.y);   // 恢复炮台小球位置

						Ball fireball = balls[i];
						fireball.colorId = cannon.ball.colorId;
						balls.insert(balls.begin() + i, fireball);  // 复制一个小球插入

						int count = eraseSameColorBalls(i, fireball, path, balls);
						if (count >= 3)
						{
							PlayMusicOnce((TCHAR*)_T("coin.mp3"));  // 播放一次金币音效
						}
						if (count == 0)
						{
							for (j = i; j >= 0; j--)
							{
								if (balls[j].indexInPath - balls[j + 1].indexInPath <= 0)
								{
									balls[j].indexInPath = balls[j + 1].indexInPath + 2 * Radius / path.sampleInterval;
								}
								else
								{
									break;
								}
							}
						}
						return;
					}
				}
			}
			
		}
	}
}

void gameover()
{
	balls.clear();
}
int main()
{
	startup();
	while (1)
	{
		show();
		updateWithoutInput();
		updateWithInput();
	}
	gameover();
	return 0;
}
  • 9
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值