一、游戏简介
游戏简介:
《疯狂打地鼠》是一款经典的单机休闲益智类小游戏,调皮的小地鼠们又出来活动了,你需要做的就是将他们砸回洞中去!机械风+复古风的游戏画面,不一样的体验,趣味性十足!眼手并用消灭地鼠,不仅要求速度快,而且要准,能够锻炼反应能力和大脑协调能力。
游戏规则:
欢乐有趣的疯狂打地鼠游戏,拥有暴风锤,轻轻一点,即可以把地鼠打得满地找牙!首先可以选择游戏的难易程度,游戏开始时有一个目标分数和生命值,打中一只地鼠得1分,错过则生命 值减1,达到目标分游戏成功,生命值为0时游戏失败。
游戏策略:
根据想要的游戏体验,可以选择不同的难度,难度大的模式下地鼠速度会有所提升,因此在游戏过程中要保持全神贯注,不仅要求速度快,而且要准。
二、游戏玩法
游戏玩法:
地鼠会从一个个地洞中不经意的探出一个脑袋,我们要做的就是在地鼠出来的时候拖动鼠标控制击打的锤子,去打它,当打到地鼠时,会加分,地鼠逃走则会扣除生命值。达到目标分游戏成功,生命值为0时游戏失败。
三、游戏设计实现
重要的数据结构介绍:
设计了两个结构体
set()函数为初始化全局变量所用的函数,在整体的游戏程序之前(因为要设置游戏运行所要用的全局变量的默认值),但在讲解规则函数,选择难度函数之后,选择难度虽然会初始化target,max_miss,speed变量的值,但是并不需要set后面,因为set()是程序初始化时所用的默认变量。而我们选择页面是会改变这三个全局变量的。
这个是实现时比较困难的函数之一,先总体来看,最外层的if,else分别是展示锤子的普通状态,展示锤子的落下状态。
if里面则是一直刷新锤子图,锤子跟随鼠标移动(锤子坐标的改变在逻辑函数中);else则用来展示锤子锤下时的图(20帧),并且击中的话还会显示击中效果图。
详细解释:
地鼠结构体内的hitted变量用于描述地鼠有没有被击中,但是这样会有一个问题,游戏时的某一个时间点上,只记录并操作了一只地鼠,所以当我们锤到地鼠时,ds[m]的m就已经发生变化了,但是我们用了一个变量flag来记录并持久化ds[m].hitted,然后在对flag进行判断和操作即可。如果没有flag变量,我们在按下锤子时锤子会垂下来,但是无论锤没锤到地鼠,击中效果图都会显示出来。
hitx,hity则是在游玩过程中,发现锤子锤下去后,移动锤子,击中效果也跟着锤子移动(改进前,击中效果的坐标始终等于锤子坐标)但这并不是我们的期望,所以增加这两个变量用于记录锤子锤中的坐标,并且锤子移动不会在改变这两个坐标的值,只有锤子再次击中地鼠时,才会改变这两个坐标的值。
展示锤子落下图并不在二级的if判断中是因为无论是否击中地鼠,只要按下鼠标左键,都应该展示锤子的落下效果图。
hitx,hity是全局变量,但是却用static声明在函数内部则是因为杨老师在上课讲到过的规范。这样用不仅符合代码规范,也在逻辑上行得通。
count则也是用到了上课学到的思想,记录循环跑的次数(也是帧数),来延长一个状态(锤子锤下的状态),如果不用count的话锤子锤下状态只显示一次(一帧)几乎无法察觉到锤子落下过,所以不可取,延迟锤子落下如果不用count方法而用sleep方法的话则会锤下时一切静止,等待锤子升起来后程序再运行,这并不是我们的初衷。
要注意的是初始化的是地鼠的绝对坐标(其实就是与坑绑定),然后地鼠再次移动就是改变地鼠自己的的坐标,而不用到初始化时的坐标,这样就达到了了相对于坑移动的效果.
设定地鼠绝对坐标时比较麻烦,但是有规律可循,相邻地鼠之间的距离在同一行时固定的,所以可以每一行用一个for循环来赋值。最后3行9个地鼠,用三个for循环即可。
这个展示函数的前半部分,展示地鼠和背景这里需要技巧,需要先显示这行的地鼠再显示这行的背景图,这样一来,背景图就能将地鼠挡住,所以地鼠从洞中出来,其实只是地鼠遮住他上部分的背景图增多(露头部分),并且他自己被遮住的部分减少(藏在地里的部分)。
该函数的后半部分就是执行锤子的draw()方法(展示锤子两种状态和击中效果图),和展示文字部分(目标分,当前分,总血量,剩余血量),hammer.draw()上面已经讲过注意事项,和实现所用算法,文字部分也很简单,此处不再赘述。
判断是否无法向上或向下移动,需要注意的是边界判断,不应该只做边界的等值判断,而应该用>,<这种范围判断,如果用等值判断范围的话,那边界值必须是初始值+移动值的倍数,而这款游戏又可以选择速度,等值判断只会限制程序的拓展性,而且容易隐藏bug,所以我们用了范围判断。
老师上课讲到的判断,也都是一个范围判断,判断“大于或小于边界”时执行代码,而非“正好到达边界时”。
if,else依据地鼠移动方向进入代码块,地鼠移动方向为上时只需移动后判断是否到顶即可,是的话方向取反向下即可。
外层else的情况,地鼠向下移动时,情况比较复杂,因为向下的情况有两种,
第一种是地鼠自然移动,向上到顶后又向下移动,缩回坑中的过程。
第二种情况是地鼠被被锤后位置被重置,且方向被置为向下
进入内层else后,判断地鼠是否被击中,如果没有击中,生命值减少,若击中了,不做多余操作并将记录值重置。
最后。无论上面哪种情况,都再刷新一个地鼠,要注意的是为了避免再次刷新在原位置(增加游戏难度),讲刷新写入循环中,只有刷新到新的位置才退出循环。
锤子击中的区域如下图所示,虽然理论上不是十分准确,(游戏是以玩家角度来看是三维的,而我们只做了二维判断(而且还是把锤子落下的椭圆外接长方形的区域,所以不是十分准确))
但是在视觉是契合的,锤子落下的区域击中了地鼠,那我们就认为是打到了地鼠,毕竟应该以玩家体验为前提和核心。
if里面运用了两矩形碰撞判断,减少了代码量,提高了可读性,当锤子落下并且该地鼠并未被击中时,我们讲地鼠的位置重置(即锤下地鼠它直接消失),并且讲该地鼠移动方向置为向下,方便一会进入ctolGame()判断。refreshDiShu()则是讲地鼠的位置重置,因为共九个地鼠,如果写判断的话要写9个(优化后可以写成3个),但这依然不符合上课杨老师讲到的代码的可读性,复用性。拓展性。logic()只需逻辑部分,地鼠位置重置只需交给另一个函数即可。并且下次其他函数如果需要地鼠刷新,例如后续增加某功能,需要用到该函数,就重用了该部分代码,增加了程序的扩展性,一举三得。
最下面的三个if,完全可以只用一个变量,Win,结束时判断是否胜利即可,但是这并不符合代码的可读性,并且如果想添加后续功能,例如进入下一阶段,这既不是胜利,也不是失败,所以这又限制了拓展性,所以我们还是使用了这种写法。
readRecordFile()需要注意的不多,按照倒数第二堂课讲的迷宫存储读取方法使用即可,
但是WriteRecordFile()则需要在原有掌握的基础上加入排行榜功能。
排行榜采用了传统的三个位置,Top1,Top2,Top3,排行榜的排行规则是击中地鼠数量最多的排名最高,地鼠击中数量相同的,血量高的排名更高。符合通常的游戏排行榜规则。
具体算法:
第一个for循环:
用两个数组分别存储排行榜前三名的分数与血量,游玩结束后,将本次分数与剩余血量存于两数组的第四个(下标3),然后先进行遍历数组,分数相同的情况下,如果血量还比排行榜上的血量高,那么变成为排行榜上的记录。break也要注意添加,否则替换多个。
第二个for循环:
用了一个简单的冒泡排序,没有用快速排序或者其他时间复杂度低的排序,因为数组本身只有四个元素,时间上几乎没有差别,其他间复杂度低的排序通常代码量比较大,写在这里会显得“头重脚轻”,所以此处使用冒泡排序。
冒泡排序通过分数将四个人排序,注意此处不可以写等号,写等号的话分数上没什么影响,可能会导致血量上排名变乱。
这个函数只需要注意增加一个while循环用_getch()方法读取缓冲区中的字符,,防止通过键盘方式跳过规则时,缓冲区里留下字符,在后面选择难度时又自动被读进去。导致程序出错,或者难度选择出错。
用到了上课学到的键盘输入模型,即循环中不断检测缓冲区里是否有字符,有的话则进入switch判断。
其实这个函数也可以用桶(哈希)的思想,用三个数组保存目标分,血量,速度,然后直接
变量=对应数组名输入这样可以空间换时间,将时间复杂度从O(n)->O(1),但是这样代码的可读性变差,不直观,并且n本身只有4,所以我们并不这样做,但也确实是经过思考,权衡利弊后使用了下面的这种写法。
四、游戏实现
见cpp
3)开始游戏(该模式是疯狂模式,可以观察到目标得分和初始血量比较苛刻)
4)击中地鼠(该模式是简单模式,可以看到目标得分和初始血量比较客观)
图片解释:
地鼠被击中后图片直接归位(地鼠图片被置于下方,被背景图覆盖),但锤子下降状态和锤子击中的反馈效果还继续存在,所以这张截图中没有地鼠,只能看到锤子下降状态和锤子击中的效果,实际的游戏过程中,因为这两种效果存留的时间也比较短,地鼠被锤没后,因为视觉残留效果,玩家能明显的感受锤子是锤到了地鼠的,并且因为地鼠直接被锤没了而感受到“到打击感”,但如果地鼠被锤后依然存在,然后与这两种效果存在时间相同(默认20帧中间帧),玩家体验会差很多。因为锤完后地鼠没有快速消失,这样就失去了“打击感”。并且是“尸体”存在一段时间后自动消失,而不是被锤时候消失,显得很违和。
图片会在一定时间后自动消失,没有像菜单那样引入鼠标或键盘判断来确认玩家是否跳过这个结果画面,因为以我的游玩经验,绝大部分玩家是不喜欢结算页面需要确认的,几乎都是在游戏结束时就连续的按按键来结束这个画面,所以为了避免结果画面的审美疲劳,和减轻玩家负担,我则让这个页面显示一段时间后自动结束,另一个结果画面同理,排行榜画面也是同理,因为排行榜上的分大概率是很久不变的,所以也是类似于“多次看到,厌倦了的”画面,但当玩家需要看时(自己打破了纪录),还是停留了一段时间来给玩家参考。(排行榜画面停留的时间大于结果画面的停留时间,符合玩家需求。)
(为什么固定显示一段时间 ,在(5)解释过,不再赘述;排行榜算法,在上面解释过,不再赘述。)
文字颜色黄色则是显示当前页面为排行榜(醒目),绿色有正向,积极的韵味所以用来描述排行第几比较合适,分数则是用了红色,因为红色不仅有醒目的作用,而且有厉害,强者的感觉,所以用来展示排行榜上大神们的分数,和剩余血量。
文字颜色黄色则是显示当前页面为排行榜(醒目),绿色有正向,积极的韵味所以用来描述排行第几比较合适,分数则是用了红色,因为红色不仅有醒目的作用,而且有厉害,强者的感觉,所以用来展示排行榜上大神们的分数,和剩余血量。
六、分工及总结
2.总结
项目日志:
Day1 各自搜集资料,寻找合适的游戏项目
Day2 讨论选题,最终敲定《疯狂打地鼠》这一游戏项目,明确分工
Day3 完成素材的搜集,并对处理加工素材
Day4 实现游戏框架的底层代码,实现动态地鼠
Day5 实现锤子敲击,重置地鼠的功能
Day6 添加记录并显示得分,生命值,目标分,初始生命值的功能
Day7 添加游戏规则和菜单页面,实现难度选择功能
Day8 添加排行榜功能
Day9 测试并改善代码,提高代码复用性,优化各功能
Day10 书写,并完成实验报告
Day11 修改实验报告,完成视频讲解的录制。
本次小组作业,我们首先积极讨论了游戏的选题,及选题的难度是否合适,最后我们根据时间(因为在布置之前就开始准备,所以有两周多的时间),小组能力(ACM副会长,ACM技术部部员,和会P图找素材的大佬),所以我们认为我们完全有能力实现一个原创(原创代码与算法)的游戏,最后确定了做“打地鼠”这款具有童年回忆的游戏,于是我们在网上找到素材后,第一时间打好了框架,并开始了具体代码的编写,但是整个过程也并不是一帆风顺,在很多地方也陷入过困难,(比如地鼠显示,需要用背景图遮盖的方式来隐藏地鼠,并且再通过地鼠隐藏背景图的方式体现地鼠出来,在锤子落下图和击中效果图,击中地鼠得分,地鼠重置上,也几次碰壁,总是实现了一个功能后,另一个变出了问题,但好在最后都解决了)。正是一次次的debug才使我们成长,正所谓“不能杀死你的,会使你更强大”,最后在各个组员的努力下终于完成了一个有完整功能的打地鼠游戏。
心得与收获:
1)通过这个课程学习到了面向过程逐步分解的方式思考问题,数据、逻辑、控制相分离,降低函数内代码耦合性,尽量一个函数一个功能等思想,着重代码可读性,复用性,拓展性等,大作业也是通过这样的方式编写了渐进式代码。
2)根据上课学习到的各种思想与方法,和实现某些功能的具体代码,在这些的基础上,我们又添加了自己的东西,既巩固了习得的知识,又提高了自己的代码能力和思考能力。
3)在完成大作业的过程中,可以说是困难重重,但是成功没有捷径,通过我们的齐心协力,成功完成了此次大作业,这不仅锻炼了我们的代码能力,更锻炼了我们的沟通协作,小组协同能力,很理解老师要求至少两人一组的良苦用心。
最后附上源码:
#include //图形界面支持
#include
#include
#include
#include
#pragma comment(lib,"Winmm.lib")
#include
#define _CRT_SECURE_NO_DEPRECATEd
#define _CRT_SECURE_NO_WARNINGS
#define TEST 1
#define SPEED 20//地鼠移动间隔时间 ms
/*********************************************************/
#define WINDOW_WIDTH 640 //窗口宽
#define WINDOW_HEITH 478 //窗口高
#define RULE_WIDTH 800//规则窗口宽
#define RULE_HEITH 601//规则窗口高
#define TOP_HEITH 192 //背景图top高
#define MID_UP_HEITH 120 //背景图mid_up高
#define MID_DOWN_HEITH 167 //背景图mid_down高
#define BOTTOM_HEITH 116 //背景图bottom高
#define MID_UP_BEGIN_Y 164 //背景图mid_up起始y坐标
#define MID_DOWN_BEGIN_Y 247 //背景图mid_down起始y坐标
#define BOTTOM_BEGIN_Y 364 //背景图bottom起始y坐标
#define LITTLE_MOUSE_WIDTH 90 //小地鼠宽
#define LITTLE_MOUSE_HEITH 74 //小地鼠高
#define MID_MOUSE_WIDTH 102 //中地鼠宽
#define MID_MOUSE_HEITH 86 //中地鼠高
#define BIG_MOUSE_WIDTH 122 //大地鼠宽
#define BIG_MOUSE_HEITH 106 //大地鼠高
#define LITTLE_MOUSE_BEGIN_X 135 //小地鼠起始位置
#define LITTLE_MOUSE_BEGIN_Y 191
#define LITTLE_MOUSE_SPACE 137 //小地鼠间距
#define MID_MOUSE_BEGIN_X 106 //中地鼠起始位置
#define MID_MOUSE_BEGIN_Y 283
#define MID_MOUSE_SPACE 156 //中地鼠间距
#define BIG_MOUSE_BEGIN_X 65 //大地鼠起始位置
#define BIG_MOUSE_BEGIN_Y 410
#define BIG_MOUSE_SPACE 185 //大地鼠间距
#define UP 1 //地鼠运动方向向上
#define DOWN 0 //地鼠运动方向向下
#define HAMMER_BEGIN_X 320 //锤子初始位置
#define HAMMER_BEGIN_Y 220
#define SCORE_WORDS_X 10 //"得分"文字描述与数字的位置
#define SCORE_NUMBER_X 90
#define SCORE_Y 30
#define TARGET_WORDS_X 5 //"目标"文字描述与数字的位置
#define TARGET_NUMBER_X 90
#define TARGET_Y 10
#define MISSED_WORDS_X 520 //"错过的"文字描述与数字的位置
#define MISSED_NUMBER_X 610
#define MISSED_Y 10
#define MAX_MISSED_WORDS_X 525//"生命值"文字描述与数字的位置
#define MAX_MISSED_NUMBER_X 610
#define MAX_MISSED_Y 30
#define EFFECTIVE_ZONE_X 36
#define EFFECTIVE_ZONE_Y 101
#define EFFECTIVE_ZONE_HEIGHT 33
#define EFFECTIVE_ZONE_WIDTH 64
#define FRAME 20 //锤子砸下状态转换持起状态的间隔帧
#define time_internal 100//从地鼠被打倒后,到再次刷新地鼠的时间间隔
#define SIMPLE_TARGET 20//简单模式下需要锤的个数
#define MIDDLE_TARGET 30//中等模式下需要锤的个数
#define HARD_TARGET 45//困难模式下需要锤的个数
#define HEEL_TARGET 70//地狱模式下需要锤的个数
#define SIMPLE_MAXMISS 20//简单模式下最大可以错过地鼠的个数
#define MIDDLE_MAXMISS 15//中等模式下最大可以错过地鼠的个数
#define HARD_MAXMISS 10//困难模式下最大可以错过地鼠的个数
#define HEEL_MAXMISS 5//地狱模式下最大可以错过地鼠的个数
#define SIMPLE_SPEED 3//简单模式下地鼠的速度
#define MIDDLE_SPEED 5//中等模式下地鼠的速度
#define HARD_SPEED 9//困难模式下地鼠的速度
#define HEEL_SPEED 11//地狱模式下地鼠的速度
/*********************************************************/
//图片数据
IMAGE top, mid_up, mid_down, bottom;//四张背景图原图
IMAGE top_y, mid_up_y, mid_down_y, bottom_y;//遮罩图
IMAGE little_mouse, mid_mouse, big_mouse;//老鼠原图
IMAGE little_mouse_y, mid_mouse_y, big_mouse_y;//遮罩图
IMAGE img_hammer, img_hammer_down;//锤子原图
IMAGE img_hammer_mask, img_hammer_down_mask;//锤子遮罩图
IMAGE img_hitted,img_hitted_mask;//击中图与遮罩图
IMAGE img_win;IMAGE img_lose;//获胜或者失败的图片
HWND hWnd;//窗口句柄
//全局变量
bool isOver;/* = false*///判断是否结束
bool isWin = false;//是否获胜
bool isLose = false;//是否失败
bool menu_exit = false;//是否在菜单页面退出
int score;//初始分数
int target;//设置目标
int missed;//成功逃离的地鼠d
int max_miss;//最多可miss的地鼠数量
int life;//剩余生命
int m;//记录当前是哪个地鼠动
int speed;//地鼠的速度,不同难度下不同
int tops[10];//排名前三位的分数
int top_life[10];//排名前三位的分数
void set() {
//全局变量初始化函数
isOver = false;//是否结束赋值为false
isWin = false;//是否获胜赋值为否
isLose = false;//是否失败赋值为否
score = 0;//初始分数设置为0
//target = SIMPLE;//设置目标为简单
//max_miss = SIMPLE;
missed = 0;
//speed = HARD_SPEED;
life = max_miss;
srand(time(0));//播种
m = rand() % 9; //0-8
}
struct MyDiShu {
int x, y;//地鼠坐标
IMAGE img, img_y;//地鼠图
int dir;//记录地鼠运动方向 1-UP 0-DOWN
bool hitted;//地鼠是否击中过
void draw()//显示地鼠
{
putimage(x, y, &img_y, SRCAND);
putimage(x, y, &img, SRCPAINT);
}
void moveUp()
{
y -= speed;//每次移动speed个像素
}
void moveDown()
{
y += speed;//每次移动speed个像素
}
};//地鼠结构体
MyDiShu ds[9];//地鼠数组初始化
struct Hammer {
int x, y;
bool hit;
IMAGE img, img_y;
IMAGE img_hit, img_hit_y;
void draw()//显示锤子
{
bool flag = false;
bool musicPlay;
if (hit == false) {
putimage(x, y, &img_y, SRCAND);
putimage(x, y, &img, SRCPAINT);
}
else /*(hit == true)*/ {
//锤子向下打的状态
//static bool flag;//标志是否打击中了,实则用持久化ds[m].hitted(该值会因为地鼠的改变而无法查询,所以用flag来记录)
static int hitx, hity;//用于击中效果坐标的持久化,位置等于锤子锤下去时一瞬间的位置,为了击中效果图不随着锤子位置变化而变化,故加入此两个变量用于持久化坐标。
if (ds[m].hitted == true) {
flag = true;
hitx = x - 5;//5为横坐标偏移量;
hity = y + 40;//40为纵坐标坐标偏移量;
}
if (flag == true) {
//如果打中了,锤子是向下状态时,那么播放击中图片
mciSendString(_T("close hitmusic"), NULL, 0, NULL);//先关闭之前播放的本音乐
mciSendString(_T("play hitmusic"), NULL, 0, NULL);//播放音乐文件
putimage(hitx,hity, &img_hitted_mask, NOTSRCERASE);
putimage(hitx,hity, &img_hitted, SRCINVERT);
}
putimage(x, y, &img_hit_y, SRCAND);
putimage(x, y, &img_hit, SRCPAINT);
static int count = 0;//count技术,每
count++;
if (count == FRAME) {
count = 0;
hit = false;//hit,flag同步改变(播放相同数量的帧数)
flag = false;
}
}
}
};//锤子结构体
Hammer hammer;//锤子结构体初始化
void initGame() //游戏框架初始化
{
//1 设置窗口
hWnd = initgraph(WINDOW_WIDTH, WINDOW_HEITH);
//2 图片资源引入,背景音乐资源引入
loadimage(&top, _T("picture\\top.bmp"));
loadimage(&mid_up, _T("picture\\mid_up.bmp"));
loadimage(&mid_down, _T("picture\\mid_down.bmp"));
loadimage(&bottom, _T("picture\\bottom.bmp"));
loadimage(&top_y, _T("picture\\top_y.bmp"));
loadimage(&mid_up_y, _T("picture\\mid_up_y.bmp"));
loadimage(&mid_down_y, _T("picture\\mid_down_y.bmp"));
loadimage(&bottom_y, _T("picture\\bottom_y.bmp"));
loadimage(&little_mouse, _T("picture\\little_mouse.bmp"));
loadimage(&mid_mouse, _T("picture\\mid_mouse.bmp"));
loadimage(&big_mouse, _T("picture\\big_mouse.bmp"));
loadimage(&little_mouse_y, _T("picture\\little_mouse_y.bmp"));
loadimage(&mid_mouse_y, _T("picture\\mid_mouse_y.bmp"));
loadimage(&big_mouse_y, _T("picture\\big_mouse_y.bmp"));
loadimage(&little_mouse_y, _T("picture\\little_mouse_y.bmp"));
loadimage(&mid_mouse_y, _T("picture\\mid_mouse_y.bmp"));
loadimage(&big_mouse_y, _T("picture\\big_mouse_y.bmp"));
loadimage(&img_hammer, _T("picture\\hammer.bmp"));
loadimage(&img_hammer_mask, _T("picture\\hammer_y.bmp"));
loadimage(&img_hammer_down, _T("picture\\hammer_down.bmp"));
loadimage(&img_hammer_down_mask, _T("picture\\hammer_down_y.bmp"));
loadimage(&img_hitted, _T("picture\\died.png"));
loadimage(&img_hitted_mask, _T("picture\\died_mask.png"));
loadimage(&img_win, _T("picture\\win.png"));
loadimage(&img_lose, _T("picture\\lose.png"));
mciSendString(_T("open music\\bkmusic.mp3 alias bkmusic"), NULL, 0, NULL);
mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL);
mciSendString(_T("open music\\hammerDown.wav alias hitmusic"), NULL, 0, NULL);//重新打开读取音乐文件
//3 结构体数据初始化
for (int i = 0; i < 9; i++)
{
if (i >= 0 && i < 3)
{
ds[i].img = little_mouse;
ds[i].img_y = little_mouse_y;
ds[i].x = LITTLE_MOUSE_BEGIN_X + i * LITTLE_MOUSE_SPACE;
ds[i].y = LITTLE_MOUSE_BEGIN_Y;
}
if (i >= 3 && i < 6)
{
ds[i].img = mid_mouse;
ds[i].img_y = mid_mouse_y;
ds[i].x = MID_MOUSE_BEGIN_X + (i - 3) * MID_MOUSE_SPACE;
ds[i].y = MID_MOUSE_BEGIN_Y;
}
if (i >= 6 && i < 9)
{
ds[i].img = big_mouse;
ds[i].img_y = big_mouse_y;
ds[i].x = BIG_MOUSE_BEGIN_X + (i - 6) * BIG_MOUSE_SPACE;
ds[i].y = BIG_MOUSE_BEGIN_Y;
}
ds[i].dir = 1;//1-up 0down
ds[i].hitted = false;//地鼠是否被击过中赋值为否
}
hammer.img = img_hammer;
hammer.img_y = img_hammer_mask;
hammer.img_hit = img_hammer_down;
hammer.img_hit_y = img_hammer_down_mask;
hammer.x = HAMMER_BEGIN_X;
hammer.y = HAMMER_BEGIN_Y;
hammer.hit = false;
}
//画面显示
void drawGame() {
BeginBatchDraw();
putimage(0, 0, &top_y, SRCAND);//显示上部分背景图
putimage(0, 0, &top, SRCPAINT);
for (int i = 0; i < 3; i++)//显示小地鼠
{
ds[i].draw();
}
putimage(0, MID_UP_BEGIN_Y, &mid_up_y, SRCAND);//显示上半部分背景
putimage(0, MID_UP_BEGIN_Y, &mid_up, SRCPAINT);
for (int i = 3; i < 6; i++)//显示中地鼠
{
ds[i].draw();
}
putimage(0, MID_DOWN_BEGIN_Y, &mid_down_y, SRCAND);//显示中间部分背景
putimage(0, MID_DOWN_BEGIN_Y, &mid_down, SRCPAINT);
for (int i = 6; i < 9; i++)//显示大地鼠
{
ds[i].draw();
}
putimage(0, BOTTOM_BEGIN_Y, &bottom_y, SRCAND);//显示下部分背景
putimage(0, BOTTOM_BEGIN_Y, &bottom, SRCPAINT);
hammer.draw();//显示锤子
//绘制当前得分与目标
settextcolor(GREEN);
TCHAR s1[] = _T("当前得分:");
outtextxy(SCORE_WORDS_X, SCORE_Y, s1);
TCHAR s_12[5];
_stprintf_s(s_12, _T("%d"), score);
outtextxy(SCORE_NUMBER_X, SCORE_Y, s_12);
settextcolor(YELLOW);
TCHAR s2[] = _T(" 目标得分:");
outtextxy(TARGET_WORDS_X, TARGET_Y, s2);
_stprintf_s(s_12, _T("%d"), target);
outtextxy(TARGET_NUMBER_X, TARGET_Y, s_12);
settextcolor(RED);
TCHAR s3[] = _T("剩余生命:");
outtextxy(MAX_MISSED_WORDS_X, MAX_MISSED_Y, s3);
_stprintf_s(s_12, _T("%d"), life);
outtextxy(MAX_MISSED_NUMBER_X, MAX_MISSED_Y, s_12);
settextcolor(YELLOW);
TCHAR s4[] = _T(" 初始血量:");
outtextxy(MISSED_WORDS_X, MISSED_Y, s4);
_stprintf_s(s_12, _T("%d"), max_miss);
outtextxy(MAX_MISSED_NUMBER_X, MISSED_Y, s_12);
EndBatchDraw();
}
bool canNotMoveUp()//检查是否往上走到顶了
{
if (m >= 0 && m < 3) {
if (ds[m].y <= LITTLE_MOUSE_BEGIN_Y - LITTLE_MOUSE_HEITH)
return true;
}
if (m >= 3 && m < 6) {
if (ds[m].y <= MID_MOUSE_BEGIN_Y - MID_MOUSE_HEITH)
return true;
}
if (m >= 6 && m < 9) {
if (ds[m].y <= BIG_MOUSE_BEGIN_Y - BIG_MOUSE_HEITH)
return true;
}
return false;
}
bool canNotMoveDown()//检查是否往下走到底了
{
if (m >= 0 && m < 3) {
if (ds[m].y >= LITTLE_MOUSE_BEGIN_Y)
return true;
}
if (m >= 3 && m < 6) {
if (ds[m].y >= MID_MOUSE_BEGIN_Y)
return true;
}
if (m >= 6 && m < 9) {
if (ds[m].y >= BIG_MOUSE_BEGIN_Y)
return true;
}
return false;
}
void ctolGame()//控制
{
if (ds[m].dir == UP) {
ds[m].moveUp();
if (canNotMoveUp())
ds[m].dir = DOWN;
}
else {
ds[m].moveDown();
if (canNotMoveDown()) {
ds[m].dir = UP;
if (ds[m].hitted == false) {
missed++;
life--;
}
else {
ds[m].hitted = false;
}
int tmp = m;
while (tmp == m)
m = rand() % 9;//刷新地鼠
}
}
}
void refreshDiShu() {
if (m >= 0 && m < 3)
ds[m].y = LITTLE_MOUSE_BEGIN_Y + 1;
else if (m >= 3 && m < 6)
ds[m].y = MID_MOUSE_BEGIN_Y + 1;
else if (m >= 6 && m < 9)
ds[m].y = BIG_MOUSE_BEGIN_Y + 1;
}
void control() {
ExMessage m;//新版写法
while (peekmessage(&m, EM_MOUSE, PM_NOREMOVE))
{
m = getmessage();
if (m.message == WM_MOUSEMOVE)//更新锤子坐标
{
hammer.x = m.x - (img_hammer.getwidth() / 2);
hammer.y = m.y - (img_hammer.getheight() / 2);
}
if (m.message == WM_LBUTTONDOWN && hammer.hit == false)//左键垂下去
{
hammer.hit = true;
}
}
}
void logic() {
int effectiveZoneX, effectiveZoneY, effectiveZoneHeight, effectiveZoneWidth;//锤子的有效击中区域
effectiveZoneX = hammer.x + EFFECTIVE_ZONE_X;
effectiveZoneY = hammer.y + EFFECTIVE_ZONE_Y;
effectiveZoneHeight = EFFECTIVE_ZONE_HEIGHT;
effectiveZoneWidth = EFFECTIVE_ZONE_WIDTH;
if (hammer.hit == true && ds[m].hitted == false) {
if (abs(effectiveZoneX - ds[m].x) < effectiveZoneWidth / 2 + ds[m].img.getwidth() / 2 &&
abs(effectiveZoneY - ds[m].y) < effectiveZoneHeight / 2 + ds[m].img.getheight() / 2) {
score++;
ds[m].hitted = true;
ds[m].dir = DOWN;
refreshDiShu();
}
}
if (score == target) {
isWin = true;
}
if (life == 0) {
isLose = true;
}
if (isWin == true || isLose == true) {
isOver = true;
}
}
void readRecordFile()
{
//读取存档
FILE* fp;
int temp;
fp = fopen(".\\record.dat", "r");
fscanf(fp,"%d %d %d %d %d %d",&tops[0],&top_life[0],&tops[1],&top_life[1],&tops[2],&top_life[2]);
fclose(fp);
}
void WriteRecordFile()
{
//保存存档
FILE* fp;
int temp;
fp = fopen(".\\record.dat", "w");
tops[3] = score;
top_life[3] = life;
int i = 0,j = 0;
for (i = 0; i < 4; i++) {
if (score == tops[i] && life > top_life[i]) {
top_life[i] = life;
break;
}
}
for (int i = 0; i < 4 - 1; i++) {
for (int j = 0; j < 4 - 1 - i; j++) {
if (tops[j] < tops[j + 1]) {
int temp = tops[j];
int temp2 = top_life[i];
tops[j] = tops[j + 1];
top_life[j] = top_life[j + 1];
tops[j + 1] = temp;
top_life[j + 1] = temp2;
}
}
}
fprintf(fp,"%d %d %d %d %d %d",tops[0],top_life[0],tops[1],top_life[1],tops[2],top_life[2]);
fclose(fp);
}
void draw_rank() {
readRecordFile();
WriteRecordFile();
readRecordFile();
settextcolor(YELLOW);
TCHAR s0[] = _T("Rank!!!");
outtextxy(SCORE_WORDS_X, SCORE_Y-30, s0);
TCHAR s_12[5];
//initgraph(WIDTH,HEIGHT);
settextcolor(GREEN);
TCHAR s1[] = _T("Top1 score: life:");
outtextxy(SCORE_WORDS_X, SCORE_Y, s1);
settextcolor(RED);
_stprintf_s(s_12, _T("%d"), tops[0]);
outtextxy(SCORE_NUMBER_X,SCORE_Y, s_12);
_stprintf_s(s_12, _T("%d"), top_life[0]);
outtextxy(SCORE_NUMBER_X+43,SCORE_Y, s_12);
settextcolor(GREEN);
TCHAR s2[] = _T("Top2 score: life:");
outtextxy(SCORE_WORDS_X, SCORE_Y+50, s2);
settextcolor(RED);
_stprintf_s(s_12, _T("%d"), tops[1]);
outtextxy(SCORE_NUMBER_X, SCORE_Y+50, s_12);
_stprintf_s(s_12, _T("%d"), top_life[1]);
outtextxy(SCORE_NUMBER_X + 43, SCORE_Y+50, s_12);
settextcolor(GREEN);
TCHAR s3[] = _T("Top3 score: life:");
outtextxy(SCORE_WORDS_X, SCORE_Y+100, s3);
settextcolor(RED);
_stprintf_s(s_12, _T("%d"), tops[2]);
outtextxy(SCORE_NUMBER_X, SCORE_Y+100, s_12);
_stprintf_s(s_12, _T("%d"), top_life[2]);
outtextxy(SCORE_NUMBER_X + 43, SCORE_Y+100, s_12);
Sleep(5000);
closegraph();
}
void drawRules() {
//显示规则
ExMessage em;
IMAGE img_rules;
initgraph(RULE_WIDTH,RULE_HEITH);
loadimage(&img_rules, _T("picture\\rules.jpg"));
putimage(0,0,&img_rules);
bool exit = false;
while (true) {
while (peekmessage(&em, EM_MOUSE)) {
em = getmessage();
if (em.message == WM_KEYDOWN || em.message == WM_LBUTTONDOWN || em.message == WM_RBUTTONDOWN) {
//按下键盘任意键,鼠标左右键时,退出规则,开始游戏。
exit = true;
closegraph();
while (_kbhit())
_getch();//清空缓冲区,防止键盘跳过规则的情况下键盘缓冲区中存留一个字符导致选择难度出错的问题。
break;
}
}
if (exit == true) {
break;
}
}
}
void choose() {
//选择难度
IMAGE img_choose;
initgraph(RULE_WIDTH, RULE_HEITH);
loadimage(&img_choose, _T("picture\\choose.png"));
putimage(0, 0, &img_choose);
while (true) {
if (_kbhit())
{
char input = _getch();
switch (input)
{
case '1': target = SIMPLE_TARGET; max_miss = SIMPLE_MAXMISS; speed = SIMPLE_SPEED; break;
//case '2': target = MIDDLE_TARGET; max_miss = MIDDLE_MAXMISS; speed = MIDDLE_SPEED; break;
case '2': target = HARD_TARGET; max_miss = HARD_MAXMISS; speed = HARD_SPEED; break;
case '3': target = HARD_TARGET; max_miss = HEEL_MAXMISS; speed = HEEL_SPEED; break;
case '4': closegraph(); menu_exit = true; break;
default:continue;
}
break;
}
}
closegraph();
}
void showResult() {
closegraph();
initgraph(RULE_WIDTH, RULE_HEITH);
if (isWin == true)
putimage(0, 0, &img_win);
else
putimage(0, 0, &img_lose);
Sleep(1000);
cleardevice();//关闭画面
}
int main()
{
drawRules();//规则
choose();//选择难度
if (menu_exit == true) return 0;
set();//全局变量初始化
initGame();//游戏框架初始化
SetTimer(hWnd,2333, SPEED, (TIMERPROC)ctolGame);//生成计时器
while (!isOver)//未结束则循环
{
logic();//逻辑部分
drawGame();//画面显示
control();//控制部分
}
showResult();//展示结果(win/lose)
draw_rank();//展示排行榜
return 0;
}