天天酷跑-C语言搭建童年游戏(easyx)

游戏索引

游戏名称:天天酷跑



游戏介绍:

本游戏是在B站博主<程序员Rock>的视频指导下完成

想学的更详细的小伙伴可以移步到<程序员Rock>视频

【程序员Rock】C语言项目:手写天天酷跑丨大一课程设计首选项目,手把手带你用C语言游戏开发
 


游戏效果展示#

<iframe id="E95a3NDL-1705732101185" frameborder="0" src="//i2.wp.com/live.csdn.net/v/embed/360710" allowfullscreen="true" data-mediaembed="csdn"></iframe>

天天酷跑


游戏模块:

<1>安装easyx图形库

<2>创建项目
<3>导入素材
<4>创建游戏开始界面
<5>循环滚动背景实现
<6>创建人物实现奔跑

<7>实现人物的跳跃和下蹲

<8>创建障碍物

<9>实现人物的血条

<10>人物和障碍物的碰撞

<11>实现游戏的积分

<12>游戏输赢的判定


目录

游戏索引

 写代码前的准备工作 

搭建项目环境easyx:

导入游戏素材 : 

修改项目属性:

导入我们的辅助项目: 

tools.h

tools.cpp

导入操作#

实现游戏开始界面 

 游戏的定义与声明

游戏的初始化 

 实现游戏背景展示

图片背景的显示

图片移动的实现

实现用户的按键操作

实现人物的展示 

?编辑

奔跑人物的显示

人物的跳跃和下蹲的实现

渲染障碍物 

显示障碍物

更新障碍物的位置

随机生成小乌龟

人物与障碍物的碰撞检测

人物血量的显示 

游戏分数的显示 

游戏输赢的判定 

游戏赢的判定

游戏输的判定

游戏主函数 

全部代码的实现 


素材已上传至百度网盘:
百度网盘 请输入提取码

提取码:ABCD

 写代码前的准备工作 

搭建项目环境easyx:

要想在我们的窗口实现图片交互

应该给编译器安装 easyx 图形库

这边我用图片展示详细的安装操作

(1)我们先在网页找到官网

(2)然后点击下载

(3)将 easyx 安装到目标编译器

(4)出现安装成功就代表可以使用了

接下来我们就可以写放心写代码啦!!!

导入游戏素材 : 

在当前项目的目录下创建文件夹,并把解压好的文件 res 拷贝进去

修改项目属性:

 <1>点击项目找到项目属性

<2>将字符集改成多字符集

这里图片操作的时候需要

<3>将编译器对SDL的检查关掉

导入我们的辅助项目: 

tools.h
#pragma once
#include <graphics.h>

//返回距离上一次调用间隔的时间(单位:ms),第一次调用时返回0
int getDelay(); 

void putimagePNG(int  picture_x, int picture_y, IMAGE* picture);

// 适用于 y <0 以及y>0的任何情况
void putimagePNG2(int x, int y, IMAGE* picture);
void putimagePNG2(int x, int y, int winWidth, IMAGE* picture);

// 判断两个矩形是否相交
bool rectIntersect(int a1X, int a1Y, int a2X, int a2Y,
    int b1X, int b1Y, int b2X, int b2Y);

void  preLoadSound(const char* name);
void  playSound(const char* name);

void drawBloodBar(int x, int y, int width, int height, int lineWidth, int boardColor, int emptyColor, int fillColor, float percent);
tools.cpp
#include <stdio.h>
#include <Windows.h>
#include "tools.h"

#include <mmsystem.h>//播放音乐
#pragma comment(lib, "winmm.lib")


int getDelay() {
    static unsigned long long lastTime = 0;
    unsigned long long currentTime = GetTickCount();
    if (lastTime == 0) {
        lastTime = currentTime;
        return 0;
    }
    else {
        int ret = currentTime - lastTime;
        lastTime = currentTime;
        return ret;
    }
}

// 载入PNG图并去透明部分
void putimagePNG(int  picture_x, int picture_y, IMAGE* picture) //x为载入图片的X坐标,y为Y坐标
{
    DWORD* dst = GetImageBuffer();    // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
    DWORD* draw = GetImageBuffer();
    DWORD* src = GetImageBuffer(picture); //获取picture的显存指针
    int picture_width = picture->getwidth(); //获取picture的宽度,EASYX自带
    int picture_height = picture->getheight(); //获取picture的高度,EASYX自带
    int graphWidth = getwidth();       //获取绘图区的宽度,EASYX自带
    int graphHeight = getheight();     //获取绘图区的高度,EASYX自带
    int dstX = 0;    //在显存里像素的角标

    // 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
    for (int iy = 0; iy < picture_height; iy++)
    {
        for (int ix = 0; ix < picture_width; ix++)
        {
            int srcX = ix + iy * picture_width; //在显存里像素的角标
            int sa = ((src[srcX] & 0xff000000) >> 24); //0xAArrggbb;AA是透明度
            int sr = ((src[srcX] & 0xff0000) >> 16); //获取RGB里的R
            int sg = ((src[srcX] & 0xff00) >> 8);   //G
            int sb = src[srcX] & 0xff;              //B
            if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
            {
                dstX = (ix + picture_x) + (iy + picture_y) * graphWidth; //在显存里像素的角标
                int dr = ((dst[dstX] & 0xff0000) >> 16);
                int dg = ((dst[dstX] & 0xff00) >> 8);
                int db = dst[dstX] & 0xff;
                draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)  //公式: Cp=αp*FP+(1-αp)*BP  ; αp=sa/255 , FP=sr , BP=dr
                    | ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)         //αp=sa/255 , FP=sg , BP=dg
                    | (sb * sa / 255 + db * (255 - sa) / 255);              //αp=sa/255 , FP=sb , BP=db
            }
        }
    }
}

// 适用于 y <0 以及x<0的任何情况
void putimagePNG2(int x, int y, IMAGE* picture) {
    IMAGE imgTmp;
    if (y < 0) {
        SetWorkingImage(picture);
        getimage(&imgTmp, 0, -y,
            picture->getwidth(), picture->getheight() + y);
        SetWorkingImage();
        y = 0;
        picture = &imgTmp;
    }

    if (x < 0) {
        SetWorkingImage(picture);
        getimage(&imgTmp, -x, 0, picture->getwidth() + x, picture->getheight());
        SetWorkingImage();
        x = 0;
        picture = &imgTmp;
    } 

    putimagePNG(x, y, picture);
}

// 适用于 y <0 以及y>0的任何情况
void putimagePNG2(int x, int y, int winWidth, IMAGE* picture) {
    IMAGE imgTmp;
    if (y < 0) {
        SetWorkingImage(picture);
        getimage(&imgTmp, 0, -y,
            picture->getwidth(), picture->getheight() + y);
        SetWorkingImage();
        y = 0;
        picture = &imgTmp;
    }

    if (x < 0) {
        SetWorkingImage(picture);
        getimage(&imgTmp, -x, 0, picture->getwidth() + x, picture->getheight());
        SetWorkingImage();
        x = 0;
        picture = &imgTmp;
    }
    else if (x >= winWidth) {
        return;
    }
    else if (x > winWidth-picture->getwidth()) {
        SetWorkingImage(picture);
        getimage(&imgTmp, 0, 0, winWidth - x, picture->getheight());
        SetWorkingImage();
        picture = &imgTmp;
    }

    putimagePNG(x, y, picture);
}

//设A[x01,y01,x02,y02]  B[x11,y11,x12,y12].
bool rectIntersect(int x01, int y01, int x02, int y02,
    int x11, int y11, int x12, int y12)
{
    int zx = abs(x01 + x02 - x11 - x12);
    int x = abs(x01 - x02) + abs(x11 - x12);
    int zy = abs(y01 + y02 - y11 - y12);
    int y = abs(y01 - y02) + abs(y11 - y12);
    return  (zx <= x && zy <= y);
}

void  preLoadSound(const char* name) {
    char cmd[512];
    sprintf_s(cmd, sizeof(cmd), "open %s alias %s-1", name, name);
    mciSendString(cmd, 0, 0, 0);
    sprintf_s(cmd, sizeof(cmd), "open %s alias %s-2", name, name);
    mciSendString(cmd, 0, 0, 0);
}

void  playSound(const char* name) {
    static int index = 1;
    char cmd[512];

    if (index == 1) {
        sprintf_s(cmd, sizeof(cmd), "play %s-1", name);
        mciSendString(cmd, 0, 0, 0);
        sprintf_s(cmd, sizeof(cmd), "close %s-2", name);
        mciSendString(cmd, 0, 0, 0);
        sprintf_s(cmd, sizeof(cmd), "open %s alias %s-2", name, name);
        mciSendString(cmd, 0, 0, 0);
        index++;
    }
    else if (index == 2) {
        sprintf_s(cmd, sizeof(cmd), "play %s-2", name);
        mciSendString(cmd, 0, 0, 0);
        sprintf_s(cmd, sizeof(cmd), "close %s-1", name);
        mciSendString(cmd, 0, 0, 0);
        sprintf_s(cmd, sizeof(cmd), "open %s alias %s-1", name, name);
        mciSendString(cmd, 0, 0, 0);
        index = 1;
    }
}

void drawBloodBar(int x, int y, int width, int height, int lineWidth, int boardColor, int emptyColor, int fillColor, float percent) {
    LINESTYLE lineStyle;
    getlinestyle(&lineStyle);
    int lineColor = getlinecolor();
    int fileColor = getfillcolor();

    if (percent < 0) {
        percent = 0;
    }

    setlinecolor(BLUE);
    setlinestyle(PS_SOLID | PS_ENDCAP_ROUND, lineWidth);
    setfillcolor(emptyColor);
    fillrectangle(x, y, x + width, y + height);
    setlinestyle(PS_SOLID | PS_ENDCAP_FLAT, 0);
    setfillcolor(fillColor);
    setlinecolor(fillColor);
    if (percent > 0) {
        fillrectangle(x + 0.5 * lineWidth, y + lineWidth * 0.5, x + width * percent, y + height - 0.5 * lineWidth);
    }
    
    setlinecolor(lineColor);
    setfillcolor(fillColor);
    setlinestyle(&lineStyle);
}
导入操作#

<1>点击项目添加现有项

<2>选定我们要添加的辅助项目选择添加就好啦

实现游戏开始界面 

按钮放置的位置我们可以通过 画图软件 或者 微信截图 来获取

void startUI() {
	// 添加背景音乐
	mciSendString("play res/bg.mp3 repeat", 0, 0, 0);
	IMAGE imgMenu, imgMenu1;
	int	flag = 0;
	loadimage(&imgMenu, "res/Menu1.png");
	loadimage(&imgMenu1, "res/menu.png");
	while (1) {
		BeginBatchDraw();
		putimage(0, 0, &imgMenu);
		putimagePNG(474, 150, &imgMenu1);
		ExMessage	msg;
		if (peekmessage(&msg)) {
			//判断是否有左键按下
			if (msg.message == WM_LBUTTONDOWN &&msg.x > 474 && msg.x < 720 && msg.y>150 && msg.y < 170) {
            //按键点击的范围
				flag = 1;
			}
		}
		    //判断鼠标按下后有没有松开
		else if (msg.message == WM_LBUTTONUP && flag == 1) {
			mciSendString("close res/bg.mp3", 0, 0, 0);
			EndBatchDraw();
			return;
		}
		EndBatchDraw();
	}
}

 游戏的定义与声明

//这里采用宏定义为的是更好的维护
#define WIN_WIDTH 1012     //窗口的宽度
#define	 WIN_HEIGHT 396    //窗口的高度
#define OBSTACLE_COUNT 10  //障碍物数量
#define WIN_SCORE 20       //获胜的分数

IMAGE imaBgs[3];            //图片类型的指针
int bgX[3];		            //背景图片
int bgspeed[3] = { 1,2,4 }; //背景移动的速度进行控制

IMAGE bgHeros[12];          //人物帧数-图片张数
int HeroX;                  //人物坐标
int HeroY;
int HeroIndex;              //玩家奔跑的图片帧序号

//判断人物的跳跃和下蹲
bool heroJump;              
bool herodown;

int jumpHeightMax;
int jumpHeightOff;//跳跃偏移量

int update; //表示是否需要马上刷新画面

int heroBlood;
int score;

//枚举障碍物,计算障碍物的种类
typedef enum {
	TORTOISE,//乌龟	
	LION,    //狮子
	HOOK1,   //挡板
	HOOK2,
	HOOK3,
	HOOK4,
	OBSTACLE_TYPE_COUNT     //直接表示障碍物的个数		
}obstacle_type; 

obstacle_type type;

vector<vector<IMAGE>> obstacleImags; //动态二维数组存放各个障碍物的图片


typedef struct obstacle
{
	obstacle_type type;	       //障碍物类型
	int imaIndex;              //当前显示图片的序号
	int x, y;	               //障碍物的坐标
	int speed;                 //障碍物速度
	int power;                 //杀伤力
	bool exist;                //存在检测
	bool hited;	               //碰撞检测
	bool passed;               //通过检测
} obstacle_t;

obstacle_t obstacles[OBSTACLE_COUNT];

int lastObsIndex;


IMAGE imgHeroDown[2];
IMAGE imgSZ[10];

游戏的初始化 

void init()
{
	//创建游戏界面
	initgraph(WIN_WIDTH, WIN_HEIGHT);

	//循环来读取用数组来接收
	char name[64];
	for (int i = 0; i < 3; i++) {
		sprintf(name, "res/bg%03d.png", i + 1);
		loadimage(&imaBgs[i], name);//加载背景资源 
		bgX[i] = 0;
	}

	//加载人物奔跑的图片帧数
	for (int i = 0; i < 12; i++) {
		sprintf(name, "res/hero%d.png", i + 1);
		loadimage(&bgHeros[i], name);
	}
	//设置人物奔跑的位置
	HeroX = WIN_WIDTH * 0.5 - bgHeros[0].getwidth() * 0.5;
	HeroY = 345 - bgHeros[0].getheight();
	HeroIndex = 0;

	heroJump = false;

	jumpHeightMax = 345 - bgHeros[0].getheight() - 120;//跳跃的最大高度
	jumpHeightOff = -4; 

	update = true;
	//加载小乌龟
	IMAGE imgTort;
	loadimage(&imgTort, "res/t1.png");    //加载乌龟图片,这里只用了一张
	vector<IMAGE>imgTortArray;            //定义乌龟障碍图片空数组
	imgTortArray.push_back(imgTort);      //将imgTort尾插到空数组,若多个图片可利用循环添加见LION
	obstacleImags.push_back(imgTortArray);//将乌龟图片数组尾插到上面的二维数组中
	//加载小狮子
	IMAGE imaLion;
	vector<IMAGE> imaLionArray;
	for (int i = 0; i < 6; i++) {
		sprintf(name, "res/p%d.png", i + 1);
		loadimage(&imaLion, name);
		imaLionArray.push_back(imaLion);
	}
	obstacleImags.push_back(imaLionArray);


	//初始化障碍物池
	for (int i = 0; i < OBSTACLE_COUNT; i++)
	{
		obstacles[i].exist = false;
	}

	//加载下蹲素材
	loadimage(&imgHeroDown[0], "res/d1.png");
	loadimage(&imgHeroDown[1], "res/d2.png");
	herodown = false;

	//柱子挡板
	IMAGE imgH;

	for (int i = 0; i < 4; i++) {
		vector<IMAGE> imgHookArray;
		sprintf(name, "res/sz/h%d.png", i + 1);
		loadimage(&imgH, name, 70, 260, true);
		imgHookArray.push_back(imgH);
		obstacleImags.push_back(imgHookArray);
	}
	//血量
	heroBlood = 100;
	//预加载音效
	preLoadSound("res/hit.mp3");

	lastObsIndex = -1;
	//分数初始化
	score = 0;

	//加载数字图片
	for (int i = 0; i < 10; i++) {
		sprintf(name, "res/sz/%d.png", i);
		loadimage(&imgSZ[i], name);
	}
}

 实现游戏背景展示

图片背景的显示

void updateBg()
{
	putimagePNG2(bgX[0], 0, &imaBgs[0]);
	putimagePNG2(bgX[1], 119, &imaBgs[1]);
	putimagePNG2(bgX[2], 330, &imaBgs[2]);
}

图片移动的实现

void fly()
{
	for (int i = 0; i < 3; i++) 
	{
		bgX[i] -= bgspeed[i];     //调整背景移速
		if (bgX[i] < -WIN_WIDTH) 
		{
			bgX[i] = 0;
		}
	}
}

实现用户的按键操作

void keyEvent()
{
	char ch;
	if (_kbhit())      //如果有按键按下kbhit返回true
	{    
		ch = _getch(); //getch()不需要按空格就可以直接读取
		if (ch == 'w'||ch == 'W') {
			jump();    
		}
		else if (ch == 's' || ch == 'S') {
			down();
		}
	}
}

实现人物的展示 

奔跑人物的显示

void updateHero()
{
	if (!herodown) {
		putimagePNG2(HeroX, HeroY, &bgHeros[HeroIndex]);//显示奔跑人物
	}
	else {
		int y = 345 - imgHeroDown[HeroIndex].getheight();
		putimagePNG2(HeroX, y, &imgHeroDown[HeroIndex]);//显示下蹲人物
	}
}

人物的跳跃和下蹲的实现

void jump()//跳跃分装函数
{
	heroJump = true;  //启动跳跃功能
	update = true;    //若启动跳跃开关则立马刷新启动功能
}

void down()//下蹲分装函数
{
	herodown = true;
	update = true;
	HeroIndex = 0;
}

void fly()
{
	//实现跳跃
	if (heroJump) {
		if (HeroY < jumpHeightMax) {
			jumpHeightOff = 4;              
		}
		HeroY += jumpHeightOff;

		if (HeroY > 345 - bgHeros[0].getheight()) {
			heroJump = false;  
			jumpHeightOff = -4; 
		}
	}

	//实现下蹲
	else if (herodown) {
		static int count = 0;
		count++;
		int delays[2] = { 8,30 };
		if (count >= delays[HeroIndex]) {
			count = 0;
			HeroIndex++;
			if (HeroIndex == 2) {
				HeroIndex = 0;
				herodown = false;
			}
		}

	}
	else {
		HeroIndex = (HeroIndex + 1) % 12;//跳跃时腿不动
	}

渲染障碍物 

显示障碍物

void updateEnemy()
{

	//渲染障碍物
	for (int i = 0; i < OBSTACLE_COUNT; i++)
	{
		if (obstacles[i].exist) 
		{
			putimagePNG2(obstacles[i].x, obstacles[i].y, WIN_WIDTH, &obstacleImags[obstacles[i].type][obstacles[i].imaIndex]);
		}
	}
}

更新障碍物的位置

// 更新障碍物的坐标
for (int i = 0; i < OBSTACLE_COUNT; i++) {
	if (obstacles[i].exist) {
		obstacles[i].x -= obstacles[i].speed + bgspeed[2];
		if (obstacles[i].x < -obstacleImags[obstacles[i].type][0].getwidth() * 2) 
        {
			obstacles[i].exist = false;
		}

		int len = obstacleImags[obstacles[i].type].size(); 
		obstacles[i].imaIndex = (obstacles[i].imaIndex + 1) % len;
	}
}

随机生成小乌龟

	//随机生成小乌龟
	static int frameCount = 0;     //静态出函数不会销毁
	static int torToiseFre = 50;
	frameCount++;
	if (frameCount > torToiseFre) 
	{
    //帧数够计数器,就生成小乌龟
		frameCount = 0; 
		torToiseFre = 50 + rand() % 60;
		creatObstacle();
	}
         

人物与障碍物的碰撞检测

void checkHit()
{
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist && obstacles[i].hited == false) {
			int a1x, a1y, a2x, a2y;
			int off = 30; 
			if (!herodown) {		//非下蹲状态
				a1x = HeroX + off;
				a1y = HeroY + off;
				a2x = HeroX + bgHeros[HeroIndex].getwidth() - off;
				a2y = HeroY + bgHeros[HeroIndex].getheight();
			}
			else
			{
				a1x = HeroX + off;
				a1y = 345 - imgHeroDown[HeroIndex].getheight();
				a2x = HeroX + imgHeroDown[HeroIndex].getwidth() - off;
				a2y = 345;
			}

			//障碍物的区域
			IMAGE img = obstacleImags[obstacles[i].type][obstacles[i].imaIndex];
			int b1x = obstacles[i].x + off;
			int b1y = obstacles[i].y + off;
			int b2x = obstacles[i].x + img.getwidth() - off;
			int b2y = obstacles[i].y + img.getheight() - off;
			if (rectIntersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y)) {
				heroBlood -= obstacles[i].power;
				playSound("res/hit.mp3");
				obstacles[i].hited = true;
			}
		}
	}
}

人物血量的显示 

void updateBloodBar()
{
	drawBloodBar(10, 10, 200, 10, 2, BLUE, DARKGRAY, RED, heroBlood / 100.0);
}

游戏分数的显示 

void checkScore() 
{
    //每躲避一个障碍物就加一分
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist &&obstacles[i].passed == false &&obstacles[i].hited == false &&obstacles[i].x + obstacleImags[obstacles[i].type][0].getwidth() < HeroX)
		{
			score++;
			obstacles[i].passed = true;

		}
	}
}


void updateScore()
{
	char str[8];
	sprintf(str, "%d", score);

	int x = 20;
	int y = 25;

	for (int i = 0; str[i]; i++) {
		int sz = str[i] - '0';
		putimagePNG(x, y, &imgSZ[sz]);
		x += imgSZ[sz].getwidth() + 5;
	}
}

游戏输赢的判定 

游戏赢的判定

void checkWin()
{
	if (score >= WIN_SCORE)
	{
		FlushBatchDraw();
		mciSendString("play res/win.mp3", 0, 0, 0);
		Sleep(2000);
		loadimage(0, "res/Menu1.png");
		FlushBatchDraw(); 
		mciSendString("stop Music.mp3", 0, 0, 0);
		system("pause");
		mciSendString("play Music.mp3 repeat", 0, 0, 0);
		heroBlood = 100;
		score = 0;
	}
}

游戏输的判定

void checkOver()
{
	if (heroBlood <= 0)
	{
		loadimage(0, "res/Maclk.png");
		FlushBatchDraw();
		mciSendString("stop res/Maclk.mp3", 0, 0, 0);
		system("pause");

		//暂停之后,若选择直接开始下一局
		heroBlood = 100;
		score = 0;
		mciSendString("play res/Maclk.mp3 repeat", 0, 0, 0);
	}
}

游戏主函数 

双缓冲绘图

BeginBatchDraw() - 开始双缓冲

EndBatchDraw()    - 结束双缓冲

双缓冲区:打印一个的同时显示另一个,不断重复这个过程,避免了屏幕闪耀问题

int main(void)
{
	init(); 
	startUI();
	mciSendString("play Music.mp3 repeat", 0, 0, 0);
	int timer = 0;

	while (1) {
		keyEvent();         //按键接收
		timer += getDelay();
		if (timer > 30) {
			timer = 0;
			update = true;	//30次刷新一次,帧休眠,不然速度太快
		}

		if (update) {
			update = false; 
			BeginBatchDraw();
			updateBg();       //渲染游戏背景
			updateHero();     //渲染人物图片
			updateEnemy();    //渲染障碍物
			updateBloodBar();
			updateScore();    //渲染分数图片
			checkWin();       //检查游戏是否胜利
			EndBatchDraw();

			checkOver();      //检查游戏是否结束
			checkScore();     //计算得分

			fly();            //实现人物跳跃,下蹲,障碍物的移动效果
		}
	}

	system("pause");
	return 0;
}

全部代码的实现 

#include<stdio.h>
#include<graphics.h>
#include"tools.h"
#include<conio.h>
#include<vector>
using namespace std;

#define WIN_WIDTH 1012     
#define	 WIN_HEIGHT 396    
#define OBSTACLE_COUNT 10  
#define WIN_SCORE 20       

IMAGE imaBgs[3];            
int bgX[3];		            
int bgspeed[3] = { 1,2,4 }; 

IMAGE bgHeros[12];          
int HeroX;                  
int HeroY;
int HeroIndex;            


bool heroJump;              
bool herodown;

int jumpHeightMax;
int jumpHeightOff;

int update; 

int heroBlood;
int score;

typedef enum {
	TORTOISE,
	LION,   
	HOOK1,  
	HOOK2,
	HOOK3,
	HOOK4,
	OBSTACLE_TYPE_COUNT    	
}obstacle_type; 

obstacle_type type;

vector<vector<IMAGE>> obstacleImags; 


typedef struct obstacle
{
	obstacle_type type;	   
	int imaIndex;              
	int x, y;	              
	int speed;                 
	int power;                 
	bool exist;               
	bool hited;	               
	bool passed;            
} obstacle_t;

obstacle_t obstacles[OBSTACLE_COUNT];

int lastObsIndex;


IMAGE imgHeroDown[2];
IMAGE imgSZ[10];

void init()
{
	initgraph(WIN_WIDTH, WIN_HEIGHT);
	char name[64];
	for (int i = 0; i < 3; i++) {
		sprintf(name, "res/bg%03d.png", i + 1);
		loadimage(&imaBgs[i], name);
		bgX[i] = 0;
	}

	
	for (int i = 0; i < 12; i++) {
		sprintf(name, "res/hero%d.png", i + 1);
		loadimage(&bgHeros[i], name);
	}
	HeroX = WIN_WIDTH * 0.5 - bgHeros[0].getwidth() * 0.5;
	HeroY = 345 - bgHeros[0].getheight();
	HeroIndex = 0;

	heroJump = false;

	jumpHeightMax = 345 - bgHeros[0].getheight() - 120;
	jumpHeightOff = -4; 

	update = true;
	IMAGE imgTort;
	loadimage(&imgTort, "res/t1.png");    
	vector<IMAGE>imgTortArray;            
	imgTortArray.push_back(imgTort);      
	obstacleImags.push_back(imgTortArray);
	IMAGE imaLion;
	vector<IMAGE> imaLionArray;
	for (int i = 0; i < 6; i++) {
		sprintf(name, "res/p%d.png", i + 1);
		loadimage(&imaLion, name);
		imaLionArray.push_back(imaLion);
	}
	obstacleImags.push_back(imaLionArray);


	for (int i = 0; i < OBSTACLE_COUNT; i++)
	{
		obstacles[i].exist = false;
	}

	
	loadimage(&imgHeroDown[0], "res/d1.png");
	loadimage(&imgHeroDown[1], "res/d2.png");
	herodown = false;


	IMAGE imgH;

	for (int i = 0; i < 4; i++) {
		vector<IMAGE> imgHookArray;
		sprintf(name, "res/sz/h%d.png", i + 1);
		loadimage(&imgH, name, 70, 260, true);
		imgHookArray.push_back(imgH);
		obstacleImags.push_back(imgHookArray);
	}
	
	heroBlood = 100;
	
	preLoadSound("res/hit.mp3");

	lastObsIndex = -1;
	
	score = 0;

	for (int i = 0; i < 10; i++) {
		sprintf(name, "res/sz/%d.png", i);
		loadimage(&imgSZ[i], name);
	}
}

void creatObstacle()
{
	int i;
	for (i = 0; i < OBSTACLE_COUNT; i++)  
	{
		if (obstacles[i].exist == false)
		{
			break;
		}
	}
	if (i >= OBSTACLE_COUNT) {
		return;
	}

	obstacles[i].exist = true;
	obstacles[i].hited = false;
	obstacles[i].imaIndex = 0;
	obstacles[i].type = (obstacle_type)(rand() % 3);

	if (lastObsIndex >= 0 &&
		obstacles[lastObsIndex].type >= HOOK1 &&
		obstacles[lastObsIndex].type <= HOOK4 &&
		obstacles[i].type == LION &&
		obstacles[lastObsIndex].x > (WIN_WIDTH - 500)) {
		obstacles[i].type = TORTOISE;
	}
	lastObsIndex = i;

	if (obstacles[i].type == HOOK1)
	{
		obstacles[i].type = (obstacle_type)((int)(obstacles[i].type) + rand() % 4);
	}
	obstacles[i].x = WIN_WIDTH; 
	obstacles[i].y = 350 - obstacleImags[obstacles[i].type][0].getheight();

	if (obstacles[i].type == TORTOISE) {
		obstacles[i].speed = 0;
		obstacles[i].power = 5;
	}

	else if (obstacles[i].type == LION) {
		obstacles[i].speed = 4;
		obstacles[i].power = 20;
	}

	else if (obstacles[i].type >= HOOK1 && obstacles[i].type <= HOOK4) {
		obstacles[i].speed = 0;
		obstacles[i].power = 20;
		obstacles[i].y = 0; 
	}

	obstacles[i].passed == false;
}

void checkHit()
{
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist && obstacles[i].hited == false) {
			int a1x, a1y, a2x, a2y;
			int off = 30; 
			if (!herodown) {		
				a1x = HeroX + off;
				a1y = HeroY + off;
				a2x = HeroX + bgHeros[HeroIndex].getwidth() - off;
				a2y = HeroY + bgHeros[HeroIndex].getheight();
			}
			else
			{
				a1x = HeroX + off;
				a1y = 345 - imgHeroDown[HeroIndex].getheight();
				a2x = HeroX + imgHeroDown[HeroIndex].getwidth() - off;
				a2y = 345;
			}

			IMAGE img = obstacleImags[obstacles[i].type][obstacles[i].imaIndex];
			int b1x = obstacles[i].x + off;
			int b1y = obstacles[i].y + off;
			int b2x = obstacles[i].x + img.getwidth() - off;
			int b2y = obstacles[i].y + img.getheight() - off;
			if (rectIntersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y)) {
				heroBlood -= obstacles[i].power;
				playSound("res/hit.mp3");
				obstacles[i].hited = true;
			}
		}
	}
}


void fly()
{
	for (int i = 0; i < 3; i++) 
	{
		bgX[i] -= bgspeed[i];     
		if (bgX[i] < -WIN_WIDTH) 
		{
			bgX[i] = 0;
		}
	}

	if (heroJump) {
		if (HeroY < jumpHeightMax) {
			jumpHeightOff = 4;              
		}
		HeroY += jumpHeightOff;

		if (HeroY > 345 - bgHeros[0].getheight()) {
			heroJump = false;  
			jumpHeightOff = -4; 
		}
	}

	else if (herodown) {
		static int count = 0;
		count++;
		int delays[2] = { 8,30 };
		if (count >= delays[HeroIndex]) {
			count = 0;
			HeroIndex++;
			if (HeroIndex == 2) {
				HeroIndex = 0;
				herodown = false;
			}
		}

	}
	else {
		HeroIndex = (HeroIndex + 1) % 12;
	}

	
	static int frameCount = 0;     
	static int torToiseFre = 50;
	frameCount++;
	if (frameCount > torToiseFre) 
	{
		frameCount = 0; 
		torToiseFre = 50 + rand() % 60;
		creatObstacle();
	}
                    
	
	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist) {
			obstacles[i].x -= obstacles[i].speed + bgspeed[2];
			if (obstacles[i].x < -obstacleImags[obstacles[i].type][0].getwidth() * 2) {
				obstacles[i].exist = false;
			}

			int len = obstacleImags[obstacles[i].type].size(); 
			obstacles[i].imaIndex = (obstacles[i].imaIndex + 1) % len;
		}
	}

	
	checkHit();
}


    
void updateBg()
{
	putimagePNG2(bgX[0], 0, &imaBgs[0]);
	putimagePNG2(bgX[1], 119, &imaBgs[1]);
	putimagePNG2(bgX[2], 330, &imaBgs[2]);
}

void jump()
{
	heroJump = true; 
	update = true;    
}

void down()
{
	herodown = true;
	update = true;
	HeroIndex = 0;
}

void keyEvent()
{
	char ch;
	if (_kbhit())      
	{    
		ch = _getch(); 
		if (ch == 'w'||ch == 'W') {
			jump();    
		}
		else if (ch == 's' || ch == 'S') {
			down();
		}
	}
}

void updateEnemy()
{


	for (int i = 0; i < OBSTACLE_COUNT; i++)
	{
		if (obstacles[i].exist) 
		{
			putimagePNG2(obstacles[i].x, obstacles[i].y, WIN_WIDTH, &obstacleImags[obstacles[i].type][obstacles[i].imaIndex]);
		}
	}
}

void updateHero()
{
	if (!herodown) {
		putimagePNG2(HeroX, HeroY, &bgHeros[HeroIndex]);
	}
	else {
		int y = 345 - imgHeroDown[HeroIndex].getheight();
		putimagePNG2(HeroX, y, &imgHeroDown[HeroIndex]);
	}
}

void updateBloodBar()
{
	drawBloodBar(10, 10, 200, 10, 2, BLUE, DARKGRAY, RED, heroBlood / 100.0);
}

void checkOver()
{
	if (heroBlood <= 0)
	{
		loadimage(0, "res/Maclk.png");
		FlushBatchDraw();
		mciSendString("stop res/Maclk.mp3", 0, 0, 0);
		system("pause");
		heroBlood = 100;
		score = 0;
		mciSendString("play res/Maclk.mp3 repeat", 0, 0, 0);
	}
}


void checkScore() 
{

	for (int i = 0; i < OBSTACLE_COUNT; i++) {
		if (obstacles[i].exist &&obstacles[i].passed == false &&obstacles[i].hited == false &&obstacles[i].x + obstacleImags[obstacles[i].type][0].getwidth() < HeroX)
		{
			score++;
			obstacles[i].passed = true;

		}
	}
}


void updateScore()
{
	char str[8];
	sprintf(str, "%d", score);

	int x = 20;
	int y = 25;

	for (int i = 0; str[i]; i++) {
		int sz = str[i] - '0';
		putimagePNG(x, y, &imgSZ[sz]);
		x += imgSZ[sz].getwidth() + 5;
	}
}

void checkWin()
{
	if (score >= WIN_SCORE)
	{
		FlushBatchDraw();
		mciSendString("play res/win.mp3", 0, 0, 0);
		Sleep(2000);
		loadimage(0, "res/Menu1.png");
		FlushBatchDraw(); 
		mciSendString("stop Music.mp3", 0, 0, 0);
		system("pause");
		mciSendString("play Music.mp3 repeat", 0, 0, 0);
		heroBlood = 100;
		score = 0;
	}
}

void startUI() {
	mciSendString("play res/bg.mp3 repeat", 0, 0, 0);
	IMAGE imgMenu, imgMenu1;
	int	flag = 0;
	loadimage(&imgMenu, "res/Menu1.png");
	loadimage(&imgMenu1, "res/menu.png");
	while (1) {
		BeginBatchDraw();
		putimage(0, 0, &imgMenu);
		putimagePNG(474, 150, &imgMenu1);
		ExMessage	msg;
		if (peekmessage(&msg)) {
			
			if (msg.message == WM_LBUTTONDOWN &&msg.x > 474 && msg.x < 720 && msg.y>150 && msg.y < 170) {
				flag = 1;
			}
		}
		   
		else if (msg.message == WM_LBUTTONUP && flag == 1) {
			mciSendString("close res/bg.mp3", 0, 0, 0);
			EndBatchDraw();
			return;
		}
		EndBatchDraw();
	}
}

int main(void)
{
	init(); 
	startUI();
	mciSendString("play Music.mp3 repeat", 0, 0, 0);
	int timer = 0;

	while (1) {
		keyEvent();
		timer += getDelay();
		if (timer > 30) {
			timer = 0;
			update = true;	
		}

		if (update) {
			update = false; 
			BeginBatchDraw();
			updateBg();    
			updateHero();
			updateEnemy();
			updateBloodBar();
			updateScore();
			checkWin();
			EndBatchDraw();

			checkOver();
			checkScore();

			fly();
		}
	}

	system("pause");

	return 0;
}