IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++小游戏---坦克大战 -> 正文阅读

[C++知识库]C++小游戏---坦克大战

?刚开始写的时候想想这个应该是非常好写的,但是写到后面,尤其是遇到很多莫名其妙的bug之后,发现似乎没那么简单。以下是开发过程中的一些想法,在这里做个笔记。

目录

游戏介绍

素材引入

?初始化

全局初始化

关卡初始化

?初始化效果

对于坦克的操作?

坦克队列初始化

自身坦克操作?

?敌方坦克的操作

对于子弹的操作?

子弹生成

子弹出界

子弹经过空地

子弹打中家

子弹打中坦克?

击中障碍物

对于道具的操作?

道具的生成

炸弹

冰冻

修复

升级?

最终效果?

总结

完整代码

游戏介绍

坦克大战大家都很熟悉,就是守护自己的老家不要被炸掉,同时也要保证自身死亡次数在一定范围之内,消灭所有敌人。

下面是一个gif:

https://ts1.cn.mm.bing.net/th/id/R-C.4015145422ab96da92232f86813541e4?rik=I7AWR37N6ZQIoQ&riu=http%3a%2f%2fimg0.xinmin.cn%2f2020%2f11%2f13%2f20201113153532276658.gif&ehk=w9TeEKSyUUjt5Y68Xw%2fc9GRNcjGX6GDDn5t%2bUC2Usf0%3d&risl=&pid=ImgRaw&r=0

素材引入

游戏的图片素材分为四类,一类是场景,一类是坦克,一类是道具,还有其他类,包括家的图,爆炸,子弹等图案,整理之后我决定用ppt制作坦克,然后找了一些网上的图片,整合之后如下。

?初始化

?走完基本流程,我们应该可以利用initgraph()函数生成一个空的画布,那么首先就要进行初始化工作,为了可扩展性,我把初始化分为了两种,一种是游戏初始化,一种是关卡初始化(虽然我自己就做了一关),游戏初始化主要针对全局初始化,尤其每一关都是一样的,包括导入图片,对不同类型坦克的参数设置,关卡初始化就是对生命值等初始化,包括生成地图,标记初始化。

全局初始化

全局初始化主要针对一些乱七八糟的东西,一些定义在最外面的全局变量就不说了,其实最好还是放在里面,在这里我定义了type结构体数组,来存放每种类型坦克对应的一些属性,同时开辟了tank结构体数组,存放当前在场活动的所有坦克的相关信息,?而ZIdan结构体队列就存放当前活动的所有子弹信息。

具体就不放出,就在完整代码的最前面。

关卡初始化

/*关卡初始化*/
void ini_every(int mp[][5], int num) {
	rest_num = 20;//敌方坦克数量
	my_lives = 3;
	tool.exist = 0;
	level = 1;//一级
	putimage(MAXX + 100, 100, &img[1]);
	putimage(MAXX + 100, 200, &img[4]);
	PrintTankNum();
	srand((unsigned)time(NULL));//调整种子
	rand_seed = rand();
	line(MAXX + 1, 0, MAXX + 1, MAXY + 1);//分割线
	putimage(7 * SIZE * 4, 12 * SIZE * 4, &img[14]);
	for (int i = 0; i <= NUMY; i++){
		for (int j = 0; j <= NUMX; j++) {
			mat[i][j] = 0;
			flag[i][j]= -1;//先初始化为-1 
			tool.flag[i][j] = 0;
		}
	}
	for (int i = 12 * 4; i <= 12 * 4 + 8; i++) {
		for (int j = 7 * 4; j <= 7 * 4 + 8; j++) {
			flag[i][j] = 10;//标记为老家位置
		}
	}
	for (int i = 0; i < num; i++) {
		int k = mp[i][0];
		int x1 = mp[i][1]*4;
		int y1 = mp[i][2]*4;
		int x2 = mp[i][3]*4-1;
		int y2 = mp[i][4]*4-1;//右下角方块的左上角坐标
		for (int xx = x1; xx <= x2; xx++) {
			for (int yy = y1; yy <= y2; yy++) {
				mat[yy][xx] = k;
			}
		}
		if (k == 2) {//泥土块最小单位SIZE
			for (int xx = x1; xx <= x2; xx++) {
				for (int yy = y1; yy <= y2; yy++) {
					putimage(xx*SIZE, yy*SIZE, &img[k + 10]);
				}
			}
		}
		else {//铁块和草方块最小单位2*SIZE
			for (int xx = mp[i][1]*2; xx <= mp[i][3]*2-1; xx++) {
				for (int yy = mp[i][2]*2; yy <= mp[i][4]*2-1; yy++) {
					putimage(xx*SIZE*2 ,yy*SIZE*2, &img[k + 10]);
				}
			}
		}
	}
}//关卡初始化

我自己是把15个像素作为一个最小单位的长度SIZE,后面的移动和方块的消除也是基于最小单位长度来更新,基于消除的特殊性,一次泥土方块的消除是SIZE,因此最小的单位是SIZE,而铁块和的消除基本是以2*SIZE,所以最小单位也就是2*SIZE,至于怎么给出这张地图,只要给定每个区块的类型和左上角右下角坐标就可以确定一处障碍物,所以我就自己以2*SIZE为最小单位画了图,然后一个个给出,数组如下,每个大括号是一处,第一个元素是类型。还有几个二维数组,是用来标记的,都是以SIZE为单位的下标,flag[][]标记坦克位置,mat[][]标记障碍物位置,tool结构体内存储道具信息,tool.exist()代表厂商是否有道具,tool.flag[][]存储道具在地图上的标记。同时在程序中因为需要一直读取用户的操作,所以我就把10MS作为了更新的最小周期,这在后面就是时间的最小单位,是来定义移速和攻速的。

// 第一关地图,1表示铁块,2表示红砖,3表示草地
//后面是x1 y1 x2 y2 是在最小单元为2*SIZE的地图上的每个类型方块的对角线坐标
int map_1[][5] = { {2,0,2,2,8} ,{2,4,2,6,3},{1,7,0,9,2},{1,12,2,14,4},
				  {3,4,4,6,6},{2,6,4,14,6},{2,14,4,16,6},{1,2,6,4,10},
				  {2,1,10,3,11},{3,4,7,5,8},{1,5,7,7,9},{2,7,7,11,9},
				  {1,11,7,13,9},{2,13,7,15,14},{2,1,11,3,14},{1,4,3,6,4},
				  {2,6,12,7,14},{2,6,11,10,12},{2,9,12,10,14} };

?初始化效果

下面从对于坦克的操作,对于子弹的操作和对于道具的操作来展开。?

对于坦克的操作?

首先是坦克,坦克分为自己的和对方的,因为游戏本身是个单机游戏,所以不会有什么其他人来操作,所以还是得要自己来控制敌方坦克,本质还是用随机数来控制,当然也要让敌方坦克更聪明一点才行。

坦克队列初始化

先把自己坦克和对方坦克生成好。这里我用了几个函数,首先自身的出生点就是家的左边,对方的出生点我设置在了最上面四个,一开始我就没有用随机数选定生成位置,就直接生成在了前三个,为了提高程序复用性,我就把生成一个坦克单独拉出来写了一段,然后生成多个无非就是不断调用函数。其中标记随机种子链的是因为程序执行速度很快,用随机时间种子最后出来的几个是一样的,所以我就用了生成的随机数做种子,最后出来的效果还可以。

/*产生一个坦克*/
Tank add_tank(int add_place,int wz) {
	//add_place表示产生位置,取值1234表示敌方坦克的出生位置,wz表示在坦克数组中的位置
	Tank t;
	t.dir = 3;
	t.bef_fight = 0;
	t.bef_move = 0;
	t.stop_time = rand()%5;
	t.dir_stop_time = 0;
	if (add_place) {//敌方坦克
		srand(rand_seed);//重置时间种子
		rand_seed = rand();//随机种子链
		int index = rand() % all_tank_num + 4;
		t.move_type = rand() % 2 + 1;
		t.type = index;
		t.HP = type[index].HP;
		t.x = birth_wz[add_place][0];
		t.y = birth_wz[add_place][1];
		t.state = 1;//表示活着
		mark_tank(t.x, t.y, wz);
	}
	else {//自己
		t.HP = type[level].HP;
		t.type = level;//通过type索引可以得到速度等信息
		t.x = birth_wz[0][0];
		t.y = birth_wz[0][1];
		mark_tank(t.x, t.y, 0);//0号标记是自己
	}
	return t;
}//产生一个坦克

/*初始化坦克队列*/
void ini_tank() {
	num_tank = 0;
	tank[0]=add_tank(0,0);//0表示产生己方坦克,其他表示其他坦克,编号index
	//在1 2 3号出生点设置敌方坦克
	for (int i = 1; i <= 3; i++) {
		num_tank++;
		tank[i]=add_tank(i, num_tank);
	}
}//初始化坦克队列

自身坦克操作?

?坦克自身的操作就是移动和发射子弹,移动需要判定位置是否合法,发射子弹也需要判定子弹的合法性,主要就是调用函数,当然函数也要写的,具体见完整项目。(感觉写了很多不必要的)

下面是针对自身的主控函数部分:

	int num_t = 0;
	while (1) {
		if (KEY_DOWN(VK_UP)) {
			control_dir(0);
		}
		if (KEY_DOWN(VK_RIGHT)) {
			control_dir(1);
		}
		if (KEY_DOWN(VK_DOWN)) {
			control_dir(2);
		}
		if (KEY_DOWN(VK_LEFT)) {
			control_dir(3);
		}
		if (KEY_DOWN(VK_SPACE)) {
			if (tank[0].bef_fight >= type[tank[0].type].Fight_det) {//至少要过几个时间段才可以发射子弹
				tank[0].bef_fight = 0;
				add_zidan(tank[0]);//添加一个子弹
			}
		}
		if (KEY_DOWN(VK_ESCAPE)) {
			stop();
		}
		if (stop_epoch) {
			stop_epoch--;
		}
		else {
			check_tank();//其他坦克
		}
		Sleep(10);//更新频率
		tank[0].bef_fight = (tank[0].bef_fight + 1) % 10000;//距离上次发射时间
		tank[0].bef_move = (tank[0].bef_move + 1) % 10000;//距离上次移动时间
	}

?敌方坦克的操作

敌方坦克最大的任务其实就是打掉我们自己的家,在一开始我尝试了每个更新周期去随机调整坦克方向,就是非常盲目的乱动,最后出来的效果非常差,坦克像个陀螺一直在转,所以后来我就改进了一下坦克的路径更新算法,改成了每次更新坦克在当前方向上移动的次数,也就是确定了现在的方向是向下,那么接下来我给你一个范围内的随机数x,你接下来x次都向下走,并且为了避免坦克卡在障碍前面不动,我在坦克遇到障碍物时自动转向。为了真实一点模拟,我也让坦克有时候在遇到障碍物之后改变方向后不动,发射子弹,模拟破墙举动。最后最重要的就是坦克需要来打掉我们的家,所以必须要让坦克有向着家走的趋势,因此我利用和x和y轴和家的对应xy轴距离占总距离? ? ? MAXX,MAXY的比例来作为概率,引导坦克向着中间下面走,或者向这边发射子弹。简单来说就是坦克的y坐标距离家的y坐标越远,那么向下的概率也就越大。

至于射击,我用了概率方法,有50%概率发射子弹。

当然坦克的发射子弹和移动同样要受到更新周期的限制,并不是每个更新周期都能移动,而是隔几个之后才可以移动。

代码如下:

/*控制其他坦克*/
void check_tank()
{
	for (int i = 1; i <= num_tank; i++) {//依次取出坦克
		if (!tank[i].state)continue;//已经没有了
		if (tank[i].bef_move >= type[tank[i].type].Speed) {
			//可能停留在原地的
			if (tank[i].stop_time) {
				tank[i].stop_time--;
				if (tank[i].bef_fight >= type[tank[i].type].Fight_det) {
					srand(rand_seed);//重置时间种子
					rand_seed = rand();//随机种子链
					int state = rand() % 100;
					if (state <= 50) {
						tank[i].bef_fight = 0;
						add_zidan(tank[i]);//添加一个子弹
					}
					else {
						tank[i].bef_fight = 0;
					}
				}
			}
			//表示要移动的
			else if (tank[i].dir_stop_time) {
				int xx = tank[i].x + dirx[tank[i].dir] * SIZE;
				int yy = tank[i].y + diry[tank[i].dir] * SIZE;//坐标
				if (check_wz(xx, yy, i))
				{
					draw_a_tank(tank[i], xx, yy);//xx,yy表示更新后的位置
					update_mark(tank[i].dir, xx, yy, i);//更新坦克位置信息
					tank[i].x = xx;
					tank[i].y = yy;
					tank[i].bef_move = 0;//更新时间
					tank[i].dir_stop_time--;
				}
				else 
				{
					//先打一发
					if (tank[i].bef_fight >= type[tank[i].type].Fight_det) {
						tank[i].bef_fight = 0;
						add_zidan(tank[i]);//添加一个子弹
					}
					srand(rand_seed);//重置时间种子
					rand_seed = rand();//随机种子链
					int state = rand() % 100;
					tank[i].dir_stop_time = state % 10+1;//方向锁定次数
					if (state % 3==1) {
						tank[i].dir = (tank[i].dir + 1) % 4;
						if(state%2)
							tank[i].stop_time = state*2;//可能几个回合之内停在原地
					}
					else if(state % 3 == 2){
						tank[i].dir = (tank[i].dir - 1 + 4) % 4;
						if(state%2)
							tank[i].stop_time = state*2;//可能几个回合之内停在原地
					}
					else {
						tank[i].dir = (tank[i].dir + 2) % 4;//反向
					}
					draw_a_tank(tank[i], tank[i].x, tank[i].y);
				}
			}
			else {
				//换方向默认优先向着中间目标
				srand(rand_seed);//重置时间种子
				rand_seed = rand();//随机种子链
				int state = rand() % 100;
				//根据移动类型优化路径
				if (tank[i].move_type == 1) {
					if (abs(tank[i].x - MAXX / 2) * 2 / MAXX * 100 > state) {//距离中间远
						if (tank[i].x - MAXX / 2 >= 0) {
							tank[i].dir = 3;//左边
						}
						else {
							tank[i].dir = 1;//右边
						}
					}
					else if (abs(tank[i].y - MAXY) / MAXY * 100 > state) {//距离下面远
						tank[i].dir = 2;//向下走
					}
					else {
						tank[i].dir = state % 4;//随机换向
					}
				}
				else {
					if (abs(tank[i].y - MAXY) / MAXY * 100 > state) {//距离下面远
						tank[i].dir = 2;//向下走
					}
					else if (abs(tank[i].x - MAXX / 2) * 2 / MAXX * 100 > state) {//距离中间远
						if (tank[i].x - MAXX / 2 >= 0) {
							tank[i].dir = 3;//左边
						}
						else {
							tank[i].dir = 1;//右边
						}
					}
					else {
						tank[i].dir = state % 4;//随机换向
					}
				}
				tank[i].dir_stop_time = state % 10+1;//方向锁定次数
				draw_a_tank(tank[i], tank[i].x, tank[i].y);
			}
		}
		//控制射击
		if (tank[i].bef_fight >= type[tank[i].type].Fight_det) {
			srand(rand_seed);//重置时间种子
			rand_seed = rand();//随机种子链
			int state = rand() % 100;
			if (state <= 50) {
				tank[i].bef_fight = 0;
				add_zidan(tank[i]);//添加一个子弹
			}
			else {
				tank[i].bef_fight = 0;
			}
		}
		tank[i].bef_fight = (tank[i].bef_fight + 1) % 10000;//距离上次发射时间
		tank[i].bef_move = (tank[i].bef_move + 1) % 10000;//距离上次移动时间
	}
}//控制其他坦克

对于子弹的操作?

子弹自我感觉是最难做的一部分,因为涉及到很多东西,尤其是子弹设计障碍物的部分,不知道写了多少废话代码,下面就几种情况展开。

子弹生成

?子弹的生成我想了两种方案,一种是生成在坦克外面,但是这样刚射出就要考虑是不是已经撞到障碍物,加上这段代码跟后面的兼容性并不好,所以我就考虑生成在坦克坐标之内,然后弹头坐标是当前坦克方向的中间线位置,所以需要做个标记count,如果第一次清除子弹那么就不需要把子弹标记这一块清除,防止把坦克也清除了。

/*根据坦克产生一个子弹*/
void add_zidan(Tank t) {
	int tmp_type = t.type;//可以取到子弹的伤害
	Zidan d;
	d.dir = t.dir;
	int xx, yy;
	switch (t.dir) {//根据方向和坦克的左上角坐标确定子弹占据空间
	case 0:
		xx = t.x + 2 * SIZE;
		yy = t.y;
		break;
	case 1:
		xx = t.x + 4 * SIZE;
		yy = t.y + 2 * SIZE;
		break;
	case 2:
		xx = t.x + 2 * SIZE;
		yy = t.y + 4 * SIZE;
		break;
	case 3:
		xx = t.x;
		yy = t.y + 2 * SIZE;
		break;
	}
	d.x = xx;
	d.y = yy;
	d.who = t.type;//123都是自己的形态,代表是自己,否则是对方的
	d.count = 0;
	qz.push(d);
}//根据坦克产生一个子弹

接下去是对于子弹的更新,子弹实际上是存在一个队列中的(但实际上是当做数组在用),每一轮对没有更新过的子弹进行更新,对于无效的子弹就不再入队,否则更新后重新插入队列。?

子弹出界

子弹出界直接就清除掉就好了。,也不用重新入队。

子弹经过空地

这种情况最简单,只要直接把子弹清空然后就可以,在子弹的下一个位置重新画一个,不要忘记

子弹打中家

这种就意味着游戏结束,直接返回一个值,然后标志游戏结束。

子弹打中坦克?

?子弹打中坦克稍微麻烦一点,坦克的存储坐标是在左上角方块的左上角点,是一个4*4的点阵,表示了存放坦克的16个方块的左上角坐标,考虑到子弹从坦克的四周穿过不应该打中坦克,所以我就如下图的几个点标记为坦克的占据点,放在flag中,图中的方框就是坦克占据的空格,flag中存放的是在坦克队列tank[]中的下标位置,自己就是0,敌方坦克是一个大于0的数字,初始值是-1。

?在判断时,就需要判断子弹的下一步位置是不是会击中如上位置,如果会那么就执行清空坦克的操作。

要注意的是,也需要查看子弹的性质,在我自己的设定中,敌方是不会互殴的,所以队友打到队友,只要把子弹清除就可以。

击中障碍物

可能是我想复杂了,这个模块是我写的最麻烦的,个人觉得,因为感觉要要考虑的实在太多,写了非常多的判断。每次击中方块可以消除四个并排单位大小的方块,其中的组合大致分为如下几类:打中铁块,打中草丛,打中泥土,还有打中其中几种的混合。由于一般的坦克打中铁块是不能消除的,所以遇到铁块和泥土这样的组合,你要保证消除是有效的,也就是消除泥土而不消除铁块,暂时也没有想到很好的方法,所以就是缝缝补补,最后差不多可以运行。具体的代码见最后。

子弹控制部分的代码:

/*检验子弹合法性*/
int check_zidan() 
{
	Zidan d;
	for (int i = 1; i <= qz.size(); i++) //这么多个子弹要检查
	{
		d = qz.front();
		qz.pop();
		//注意xy实际上是弹头坐标,左上角坐标要加上偏移量(最上面)
		if (d.x == 0 || d.y == 0) {
			if (d.count) {
				clear_zidan(d);
			}
			else {
				d.count = 1;
			}//出界                                 
			continue;
		}
		int xx = d.x + SIZE * dirx[d.dir];
		int yy = d.y + SIZE * diry[d.dir];//确定下一步位置
		if (xx > MAXX || yy > MAXY) {
			if (d.count) {
				clear_zidan(d);
			}
			else {
				d.count = 1;
			}//出界
			continue;
		}
		int checkX1 = xx / SIZE + check_if_movex[0][d.dir];
		int checkY1 = yy / SIZE + check_if_movey[0][d.dir];//确定检查点单位位置1
		int checkX2 = xx / SIZE + check_if_movex[1][d.dir];
		int checkY2 = yy / SIZE + check_if_movey[1][d.dir];//确定检查点单位位置2
		if (flag[yy/SIZE][xx/SIZE] ==-1 &&!mat[checkY1][checkX1]&& !mat[checkY2][checkX2]) {//都为空位置,不用管
			if (d.count) {
				clear_zidan(d);
			}
			else {
				d.count = 1;
			}
			if (!tool.flag[checkY1][checkX1] && !tool.flag[checkY2][checkX2]) {//不能打在道具上
				draw_zidan(d, xx, yy);//画子弹
				//这里要画出旋转后的,这个里面的左上角坐标正确
				d.x = xx;
				d.y = yy;//更新坐标
				qz.push(d);
			}
		}
		else if(mat[checkY1][checkX1]>0 || mat[checkY2][checkX2]>0){//打到方块
			if (!((mat[checkY1][checkX1] == 1 || mat[checkY2][checkX2] == 1) && d.who != 3)) 
			{//有一个铁块并且等级不够就不消除,代表被挡住了
			    //消除方块
				switch (d.dir) {
				case 0:
					remove_any(-2, -1, 1, -1, d);//传递的参数是子弹信息和相对于弹头的位置(四个方块),子弹分界的左右两个
					break;
				case 1:
					remove_any(0, -2, 0, 1, d);
					break;
				case 2:
					remove_any(-2, 0, 1, 0, d);
					break;
				case 3:
					remove_any(-1, -2, -1, 1, d);
					break;
				}
			}
			if (d.count) {
				if (d.count) {
					clear_zidan(d);
				}
				else {
					d.count = 1;
				}
			}
			else {
				d.count = 1;
			}
		}
		else if (flag[yy / SIZE][xx / SIZE] == 10) {
			clearrectangle(7 * SIZE * 4, 12 * SIZE * 4, 7 * SIZE * 4 + 8 * SIZE, 12 * SIZE * 4 + 8 * SIZE);
			putimage(7 * SIZE * 4, 12 * SIZE * 4, &img[17]);
			return 0;
		}
		else {//代表炸到了坦克
		//根据编号看谁挂掉了
			if (d.who>3&&flag[yy / SIZE][xx / SIZE] == 0|| d.who <= 3 && flag[yy / SIZE][xx / SIZE]!=10) 
			{
				//代表两股势力
				if (clear_tank(xx, yy, 0)) {
					//test();
					return 2;//代表游戏结束
				}
				if (d.count) {
					clear_zidan(d);
				}
				else {
					d.count = 1;
				}
			}
			else {
				if (d.count) {
					clear_zidan(d);
				}
				else {
					d.count = 1;
				}
			}
		}
	}
	return 1;
}//检验子弹合法性

对于道具的操作?

?道具的引入主要是为了增加游戏乐趣,思考之后我决定加入以下几种:炸弹(随机炸掉一个敌方坦克),冰冻(冻住敌方几秒),修复(让家的防护补满),升级(自身坦克升级)。

道具的生成

首先对于道具生成,一定是在多少个单更新单位时间之后,然后也不能生成在障碍物上面(我试过,会覆盖,所以就没尝试),生成原理就是随机数。?

/*添加道具*/
void add_tool() 
{
	int x, y;
	bool check = 0;
	while (!check) {
		srand(rand_seed);//重置时间种子
		rand_seed = rand();//随机种子链
		x = rand() % NUMX - 2;
		y = rand() % NUMY - 2;//左上角单位坐标
		check = 1;
		for (int i = 0; i < 2; i++) {
			for (int j = 0; j < 2; j++) {
				if (mat[y + i][x + j]||flag[y+i][x+j]!=-1)
					check=0;//不能落在有东西的上面
			}
		}
	}
	int index = rand() % 4 + 1;//道具种类
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			tool.flag[y + i][x + j] = index;
		}
	}
	putimage(x*SIZE+2, y*SIZE+2, &img[18+index]);
	tool.exist = 1;
}//添加道具

炸弹

炸弹就是不断在地图中生成随机数并查看是否有坦克,有就直接炸毁,考虑到程序一直找不到可能会崩溃,所以我设定了最高查找一万次,所以最终的效果就是有时候可能找不到坦克,但是找到的概率还是比较大的。

冰冻

冰冻的效果就是敌方坦克不动,上文我们其实已经把敌方坦克的更新封装在了函数之内,那么就只要在设定更新时间周期内不执行就好。

修复

修复就是把家周围的墙单独拉出来,然后重新像初始化一样执行一次,就可以完成修复。

升级?

升级就是把自身的level+1,并且把代表自身的0号tank位置的type类型改为level+1所对应的,再次更新就可以变样。

/*检查道具*/
int check_tool(int index) {
	//炸弹停止升级修复
	int x=0, y=0;
	int max_num = 10000;//最多寻找次数
	switch (index) {
	case 1:
		while (max_num--) {
			srand(rand_seed);//重置时间种子
			rand_seed = rand();//随机种子链
			x = rand() % NUMX;
			y = rand() % NUMY;
			if (flag[y][x]>0 && flag[y][x] != 10)
				break;
		}
		if(max_num)//表示可以找到坦克
			if (clear_tank(x*SIZE, y*SIZE, 1)) {
				return 1;
			}
		break;
	case 2:
		stop_epoch = 300;//3秒
		break;
	case 3:
		if (level < 3) {
			up_date_type(level+1,0);//更换类型
			level++;
		}
		break;
	case 4:
		repair_map();
		break;
	}
	return 0;//游戏继续
}//检查道具

最终效果?

?

总结

大部分的主要的程序就在上面,但很多部分都封装了一些细节,所以并不完整,只能表述大致的思路。做的不太好的就是坦克之间经常有重叠的情况,大致就是因为标记点的原因,也确实不知道该如何解决,而最终的实现代码写了千行,其中还是有不少可以优化的空间,由于水平原因,有一些功能如穿过草丛还是没有实现,不过有机会还是希望可以试试。

完整代码

main.cpp

#include"head.h"

int main() {

	game();
	return 0;
}

?head.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<algorithm>
#include <stdlib.h>
#include <time.h>
#include<graphics.h>//图形绘制库
#include<string>
#include<iostream>
#include<queue>
#include<conio.h>
#include<vector>
#include<math.h>
#include<stack>
using namespace std;

#define SIZE 15 //定义最小单元
#define NUMX 64
#define NUMY 56//定义xy轴的最小单元个数
#define MAXX NUMX*SIZE
#define MAXY NUMY*SIZE
#define PI 3.1415926
#define RIGHT 77
#define LEFT 75
#define UP 72
#define DOWN 80
#define ENTER 13
#define W 119
#define A 97
#define S 115
#define D 100
#define KONG 32
#define ESC 27//获取键值
#define KEY_DOWN(vKey) ((GetAsyncKeyState(vKey) & 0x8000) ? 1:0)//检测键盘按下函数

void game();//游戏函数入口
void PrintTankNum();

?tool.cpp

#include"head.h"

int dirx[] = {0,1,0,-1};
int diry[] = {-1,0,1,0};//四个方向
int img_zidan_detx1[] = {-SIZE/2,-SIZE,-SIZE/2,0};
int img_zidan_dety1[] = {0,-SIZE/2,-SIZE,-SIZE/2};//子弹左上角相对弹头偏移量,画子弹要用
int check_if_movex[][4] = { {-1,-1,-1,0},{0,-1,0,0} };
int check_if_movey[][4] = { {0,-1,-1,-1 },{0,0,-1,0} };//表示子弹合法检查位置相对弹头偏移单位数,相邻两个都检查
int all_tank_num = 3;//其他类型坦克数量
int rest_num;//剩余坦克数量
int my_lives;//自己的坦克数量
int rand_seed;//随机种子
int level;//自身当前等级
int mat[NUMY+2][NUMX+2];//存储游戏地图
int flag[NUMY + 2][NUMX + 2];//存储坦克标记
int stop_epoch;//表示停止回合
//123分别存储三种障碍物,(-10)存储自己坦克所在位置,(-1)-(-9)存储敌方坦克位置,一个坦克占据16个单元格,左上坐标标记
int num_tank;//当前场上的坦克
int birth_wz[][2] = { {MAXX / 2 - SIZE * 12,MAXY - 4 * SIZE},			//第一个是自己的出生点,剩下的是其他坦克出生点
					{0,0},{MAXX / 2 - 16 * SIZE,0},{MAXX / 2 + 16 * SIZE,0},{MAXX - 4 * SIZE,0} }; 
// 第一关地图,1表示铁块,2表示红砖,3表示草地,后面是x1 y1 x2 y2 是在最小单元为2*SIZE的地图上的每个类型方块的对角线坐标
int map_1[][5] = { {2,0,2,2,8} ,{2,4,2,6,3},{1,7,0,9,2},{1,12,2,14,4},
				  {3,4,4,6,6},{2,6,4,14,6},{2,14,4,16,6},{1,2,6,4,10},
				  {2,1,10,3,11},{3,4,7,5,8},{1,5,7,7,9},{2,7,7,11,9},
				  {1,11,7,13,9},{2,13,7,15,14},{2,1,11,3,14},{1,4,3,6,4},
				  {2,6,12,7,14},{2,6,11,10,12},{2,9,12,10,14} };
int rep_map[][5] = { {2,6,12,7,14},{2,6,11,10,12},{2,9,12,10,14} };

struct Zidan {
	int who;//记录的是在坦克系列,1-3是自己,其他是其他人
	int dir;//方向
	int x;
	int y;//坐标
	bool count;//为了标记是否是第一次被更新
};
queue<Zidan>qz;

struct Tool {
	bool exist;//是否存在
	int flag[NUMY + 2][NUMX + 2];//存储道具信息
}tool;

struct Tank {
	int type;//类型
	int HP;//血量
	int dir;//方向,上下左右0123
	int x, y;//坐标
	int bef_fight;//上一次攻击到现在的间隔
	int bef_move;//上一次移动到现在的间隔
	int stop_time;//表示该坦克在几个周期内不移动
	int dir_stop_time;//表示该坦克在几个周期内不改变方向
	int move_type;//1代表优先向中间,2代表优先向下面
	int state;//当前状态
}tank[10];//最多其实就4个,0号始终是自己

// 不同坦克类型属性
struct Type {
	/*自身属性*/
	int HP;//血量
	int Speed;//速度,单位是更新周期,一个更新周期子弹移动一次
	int Power;//伤害
	int Fight_det;//攻击间隔,单位是更新周期
}type[10];
IMAGE img[40];//存储图片

/*调试*/
void test() {
	clearrectangle(0, 0, 500, 500);
}//调试

/*程序停止*/
void stop() {
	system("pause");
}//程序停止

/*全局初始化*/
void ini_all() {
	/*自身属性状态*/
	//一级
	type[1].HP = 1;
	type[1].Speed = 8;
	type[1].Power = 10;
	type[1].Fight_det = 30;
	//二级
	type[2].HP = 2;
	type[2].Speed = 7;
	type[2].Power = 15;
	type[2].Fight_det = 25;
	//三级
	type[3].HP = 3;
	type[3].Speed = 6;
	type[3].Power = 20;
	type[3].Fight_det = 20;
	/*其他坦克状态*/
	//第一种血量高的
	type[4].HP = 2;
	type[4].Speed = 10;
	type[4].Power = 10;
	type[4].Fight_det = 40;
	//第二种速度快的
	type[5].HP = 1;
	type[5].Speed = 5;
	type[5].Power = 10;
	type[5].Fight_det = 40;
	//第三种攻击快
	type[6].HP = 1;
	type[6].Speed = 10;
	type[6].Power = 10;
	type[6].Fight_det = 20;
	//第四种是的一种被打了之后的样子
	type[7].HP = 1;
	type[7].Speed = 10;
	type[7].Power = 10;
	type[7].Fight_det = 40;
	/*图片*/
	loadimage(&img[1], "./imgs/type1.png");
	loadimage(&img[2], "./imgs/type2.png");
	loadimage(&img[3], "./imgs/type3.png");
	loadimage(&img[4], "./imgs/type4.png");
	loadimage(&img[5], "./imgs/type5.png");
	loadimage(&img[6], "./imgs/type6.png");
	loadimage(&img[7], "./imgs/type7.png");//坦克图片
	loadimage(&img[11], "./imgs/铁块.png");//草地
	loadimage(&img[12], "./imgs/红砖.png");//红砖
	loadimage(&img[13], "./imgs/草地.png");//铁块
	loadimage(&img[14], "./imgs/家.png");//家
	loadimage(&img[15], "./imgs/敌方子弹.png");
	loadimage(&img[16], "./imgs/我方子弹.png");//子弹
	loadimage(&img[17], "./imgs/家爆炸.png");//家被炸掉了
	loadimage(&img[18], "./imgs/坦克爆炸.png");//坦克被炸掉了
	loadimage(&img[19], "./imgs/炸弹.png");//随机炸掉一个坦克
	loadimage(&img[20], "./imgs/停止.png");//其他坦克停止操作几个周期
	loadimage(&img[21], "./imgs/升级.png");//坦克升级
	loadimage(&img[22], "./imgs/修复.png");//修复老家
}//全局初始化

/*关卡初始化*/
void ini_every(int mp[][5], int num) {
	rest_num = 20;//敌方坦克数量
	my_lives = 3;
	tool.exist = 0;
	level = 1;//一级
	putimage(MAXX + 100, 100, &img[1]);
	putimage(MAXX + 100, 200, &img[4]);
	PrintTankNum();
	srand((unsigned)time(NULL));//调整种子
	rand_seed = rand();
	line(MAXX + 1, 0, MAXX + 1, MAXY + 1);//分割线
	putimage(7 * SIZE * 4, 12 * SIZE * 4, &img[14]);
	for (int i = 0; i <= NUMY; i++){
		for (int j = 0; j <= NUMX; j++) {
			mat[i][j] = 0;
			flag[i][j]= -1;//先初始化为-1 
			tool.flag[i][j] = 0;
		}
	}
	for (int i = 12 * 4; i <= 12 * 4 + 8; i++) {
		for (int j = 7 * 4; j <= 7 * 4 + 8; j++) {
			flag[i][j] = 10;//标记为老家位置
		}
	}
	for (int i = 0; i < num; i++) {
		int k = mp[i][0];
		int x1 = mp[i][1]*4;
		int y1 = mp[i][2]*4;
		int x2 = mp[i][3]*4-1;
		int y2 = mp[i][4]*4-1;//右下角方块的左上角坐标
		for (int xx = x1; xx <= x2; xx++) {
			for (int yy = y1; yy <= y2; yy++) {
				mat[yy][xx] = k;
			}
		}
		if (k == 2) {//泥土块最小单位SIZE
			for (int xx = x1; xx <= x2; xx++) {
				for (int yy = y1; yy <= y2; yy++) {
					putimage(xx*SIZE, yy*SIZE, &img[k + 10]);
				}
			}
		}
		else {//铁块和草方块最小单位2*SIZE
			for (int xx = mp[i][1]*2; xx <= mp[i][3]*2-1; xx++) {
				for (int yy = mp[i][2]*2; yy <= mp[i][4]*2-1; yy++) {
					putimage(xx*SIZE*2 ,yy*SIZE*2, &img[k + 10]);
				}
			}
		}
	}
}//关卡初始化

/*转换数字为字符串*/
void deal(int x, char *a) {
	stack <int>sst;
	while (x) {
		sst.push(x % 10);
		x /= 10;
	}
	int l = 0;
	while (!sst.empty()) {
		a[l++] = sst.top() + '0';
		sst.pop();
	}
	a[l] = '\0';
	if (a[0] == '\0') {
		a[0] = '0';
		a[1] = '\0';
	}
}//转换数字为字符串

/*打印游戏结束*/
void PrintGameOver() {
	settextcolor(WHITE);
	int size = 100;//字体大小
	settextstyle(size, size, 0);
	outtextxy(90,200,"游戏结束");
}//打印游戏结束

/*通关*/
void PrintSucess() {
	settextcolor(WHITE);
	int size = 100;//字体大小
	settextstyle(size, size, 0);
	outtextxy(90, 200, "成功通关");
}//通关

/*打印坦克数量*/
void PrintTankNum() {
	char a[100];

	//计算自己坦克数量
	settextcolor(WHITE);
	settextstyle(40, 40, 0);
	outtextxy(MAXX+100+4*SIZE, 120, "X");
	deal(my_lives, a);
	//这是为了防止倒计时显示错误
	clearrectangle(MAXX + 100 + 4 * SIZE + 50, 120, MAXX + 100 + 4 * SIZE + 50 + 70, 120 + 50);
	outtextxy(MAXX + 100 + 4*SIZE+50, 120, a);

	//计算敌方坦克数量
	settextcolor(WHITE);
	settextstyle(40, 40, 0);
	outtextxy(MAXX + 100 + 4 * SIZE, 220, "X");
	deal(rest_num, a);
	clearrectangle(MAXX + 100 + 4 * SIZE + 50, 220, MAXX + 100 + 4 * SIZE + 50 + 70, 220 + 50);
	outtextxy(MAXX + 100 + 4 * SIZE + 50, 220, a);
}//打印坦克数量

/*标记坦克占据空间*/
void mark_tank(int x,int y,int mark_num) {
	for (int i = 1; i <= 3; i++) {
		for (int j = 1; j <= 3; j++) {
			flag[y / SIZE + i][x / SIZE + j] = mark_num;
		}
	}
}//标记坦克占据空间

/*更新坦克标记点*/
void update_mark(int dir, int x, int y,int index) {//新的方向和新的坐标定位点
	switch (dir) {//四种方向,上右下左
		//坦克标记点有5个
	case 0:
		for (int i = 1; i < 4; i++) {
			flag[y / SIZE + 1][x / SIZE + i] = index;
			flag[y / SIZE + 4][x / SIZE + i] = -1;//清空后面的
		}
		break;
	case 1:
		for (int i = 1; i < 4; i++) {
			flag[y / SIZE + i][x / SIZE + 3] = index;
			flag[y / SIZE + i][x / SIZE ] = -1;//清空后面的
		}
		break;
	case 2:
		for (int i = 1; i < 4; i++) {
			flag[y / SIZE ][x / SIZE + i] = -1;
			flag[y / SIZE + 3][x / SIZE + i] = index;
		}
		break;
	case 3:
		for (int i = 1; i < 4; i++) {
			flag[y / SIZE + i][x / SIZE+1] = index;
			flag[y / SIZE + i][x / SIZE + 4] = -1;//清空后面的
		}
		break;
	}
}//更新坦克标记点

/*产生一个坦克*/
Tank add_tank(int add_place,int wz) {
	//add_place表示产生位置,取值1234表示敌方坦克的出生位置,wz表示在坦克数组中的位置
	Tank t;
	t.dir = 3;
	t.bef_fight = 0;
	t.bef_move = 0;
	t.stop_time = rand()%5;
	t.dir_stop_time = 0;
	if (add_place) {//敌方坦克
		srand(rand_seed);//重置时间种子
		rand_seed = rand();//随机种子链
		int index = rand() % all_tank_num + 4;
		t.move_type = rand() % 2 + 1;
		t.type = index;
		t.HP = type[index].HP;
		t.x = birth_wz[add_place][0];
		t.y = birth_wz[add_place][1];
		t.state = 1;//表示活着
		mark_tank(t.x, t.y, wz);
	}
	else {//自己
		t.HP = type[level].HP;
		t.type = level;//通过type索引可以得到速度等信息
		t.x = birth_wz[0][0];
		t.y = birth_wz[0][1];
		mark_tank(t.x, t.y, 0);//0号标记是自己
	}
	return t;
}//产生一个坦克

/*初始化坦克队列*/
void ini_tank() {
	num_tank = 0;
	tank[0]=add_tank(0,0);//0表示产生己方坦克,其他表示其他坦克,编号index
	//在1 2 3号出生点设置敌方坦克
	for (int i = 1; i <= 3; i++) {
		num_tank++;
		tank[i]=add_tank(i, num_tank);
	}
}//初始化坦克队列

/*表示从旧的变成新的*/
void up_date_type(int new_type, int wz) {
	tank[wz].type = new_type;
	tank[wz].HP = type[new_type].HP;
}//表示从旧的变成新的

/*画一个坦克*/
void draw_a_tank(Tank t,int xx,int yy) {
	IMAGE ans;
	clearrectangle(t.x+3, t.y+3, t.x + 4 * SIZE-1, t.y + 4 * SIZE-1);
	rotateimage(&ans, &img[t.type], -PI / 2 * t.dir);//顺时针旋转dir个90度,然后赋值给ans
	putimage(xx+4, yy+4, &ans);
}//画一个坦克

/*画全部坦克*/
void draw_all_tank() {
	for (int i = 0; i <= num_tank; i++) {
		draw_a_tank(tank[i],tank[i].x,tank[i].y);
	}
}//画全部坦克

/*检查坦克位置合法性*/
int check_wz(int xx, int yy, int wz) {
	//检查是否出界
	if (xx<0 || yy<0 || xx + 4 * SIZE>MAXX || yy + 4 * SIZE>MAXY||wz&&tool.flag[yy/SIZE][xx/SIZE]) {
		return 0;
	}
	//检查坦克所在位置是否有障碍物以及道具
	int tool_x = -1, tool_y = -1;
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			if (mat[yy / SIZE + i][xx / SIZE + j] > 0
				|| flag[yy / SIZE + i][xx / SIZE + j] >= 0 && flag[yy / SIZE + i][xx / SIZE + j] != wz)//已经有障碍物或者有其他坦克
				return 0;
			if (tool.flag[yy / SIZE + i][xx / SIZE + j]) {
				if (wz)return 0;
				else {
					tool_x = xx / SIZE + j;
					tool_y = yy / SIZE + i;
				}
			}
		}
	}
	//检查无障碍物并且有道具
	int index = tool.flag[tool_y][tool_x];
	if (tool_x != -1) {
		//清空标记
		if (tool_x > 0 && tool.flag[tool_y][tool_x - 1])
			tool_x--;
		if (tool_y > 0 && tool.flag[tool_y - 1][tool_x])
			tool_y--;
		for (int i = 0; i < 2; i++) {
			for (int j = 0; j < 2; j++) {
				tool.flag[tool_y + i][tool_x + j] = 0;
			}
		}
		clearrectangle(tool_x*SIZE, tool_y*SIZE, tool_x*SIZE + 2 * SIZE, tool_y*SIZE + 2 * SIZE);
		draw_a_tank(tank[0], tank[0].x, tank[0].y);//重画坦克
		return index;
	}
	return 10;
}//检查坦克位置合法性

/*清除坦克*/
int clear_tank(int x,int y,int type_of) {
	//test();
	int xx = x / SIZE;
	int yy = y / SIZE;//记录消除单位坐标
	int bh = flag[yy][xx];//坦克在坦克队列中的编号,0是自己
	if (type_of) {//表示是炸弹的魔法伤害
		tank[bh].HP = 0;
	}
	else {//否则就是普通子弹
		tank[bh].HP--;
	}
	if (tank[bh].HP) {
		if (bh) {
			up_date_type(7, bh);//两条命的敌人
		}
		else {
			up_date_type(level-1, bh);//自身
		}
		return 0;//表示还没挂掉,直接返回
	}
	//向左上角追溯
	while (flag[yy][xx] == bh) {
		xx--;
	}
	xx++;//为了接下来还可以查找y,因为找到的位置x实际上是没有flag标记的
	while (flag[yy][xx] == bh) {
		yy--;
	}
	yy++;//统一,这样左上角坐标就是[y-1][x-1]
	//清除坦克
	clearrectangle((xx-1)*SIZE, (yy-1)*SIZE, (xx-1)*SIZE + 4 * SIZE - 1, (yy-1)*SIZE + 4 * SIZE - 1);
	putimage((xx - 1)*SIZE + 2, (yy - 1)*SIZE + 2, &img[18]);
	Sleep(100);
	clearrectangle((xx - 1)*SIZE, (yy - 1)*SIZE, (xx - 1)*SIZE + 4 * SIZE - 1, (yy - 1)*SIZE + 4 * SIZE - 1);
	//清空标记点
	for (int i = yy ; i <yy+ 3; i++) {
		for (int j = xx ; j < xx + 3; j++) {
			flag[i][j] = -1;
		}
	}
	if (bh == 0) {//代表自己被打死了
		level = 1;
		tank[0]=add_tank(0, 0);
		draw_a_tank(tank[0], tank[0].x, tank[0].y);
		my_lives--;
	}
	else {//其他坦克
		rest_num--;
		if (!rest_num)//坦克清空
		{
			return 1;//表示游戏结束(打完了)
		}
		else {
			if (rest_num < 3) {
				tank[bh].state = 0;//当前位置没有坦克
			}
			else {
				//产生一个新坦克
				srand(rand_seed);//重置时间种子
				rand_seed = rand();//随机种子链
				int rand_place;
				while (1) {
					srand(rand_seed);//重置时间种子
					rand_seed = rand();//随机种子链
					rand_place = rand() % 4 + 1;
					if (check_wz(birth_wz[rand_place][0], birth_wz[rand_place][1], bh))
						break;
				}
				tank[bh] = add_tank(rand_place, bh);
			}
		}
	}
	PrintTankNum();
	return 0;//游戏继续
}//清除坦克

/*消除该区域内的物体*/
void remove_any(int detx1,int dety1, int detx2, int dety2,Zidan d)
{
	int x = d.x;
	int y = d.y;
	//计算起始xy坐标
	int stx = d.x / SIZE;
	int sty = d.y / SIZE;
	//控制区间
	if (dety1 == dety2) //横向排列
	{
		//先判断有空的情况
		int index = 2;
		for (int i = -1; i <= 1; i++)//找到跳变点并退出
		{
			if (mat[sty + dety1][stx + i] != mat[sty + dety1][stx + i - 1])
			{
				//要保证不是跟空比较
				index = i;
				break;
			}
		}
		if (!(mat[sty + dety1][stx + index] * mat[sty + dety1][stx + index - 1])) {
			//有空
			if (mat[sty + dety1][stx + index] + mat[sty + dety1][stx + index - 1] == 3)//一边是草
			{
				return;
			}
			index++;
			while (mat[sty + dety1][stx + index] == mat[sty + dety1][stx + index - 1]&&index<2)
			{
				index++;
			}
		}
		if (index!=2) {//表明有变化,那么可能是铁+草,铁+泥土,草+泥土任意组合
			//逐个讨论
			//带草 3
			if (mat[sty + dety1][stx + index] == 3 || mat[sty + dety1][stx + index - 1] == 3)//修正消除位置,草不能被打掉
			{
				if (mat[sty + dety1][stx + index-1] == 3)//代表左边是草
				{
					detx1 = index;//修改左边界
				}
				else 
				{
					detx2 = index-1;//修改右边界
				}
			}
			else //一般坦克打的,不能消除铁,要判掉
			{
				if (mat[sty + dety1][stx + index-1] == 1)//代表左边是铁
				{
					detx1 = index;//修改左边界
				}
				else
				{
					detx2 = index - 1;//修改右边界
				}
			}
		}
		else if(mat[sty + dety1][stx + detx1] == 3){
			return;//全是草不考虑
		}
	}
	else //纵向排列 
	{
		int index = 2;
		for (int i = -1; i <= 1; i++)//找到跳变点并退出
		{
			if (mat[sty + i][stx + detx1] != mat[sty + i-1][stx + detx1])
			{
				index = i;
				break;
			}
		}
		if (!(mat[sty + index - 1][stx + detx1] * mat[sty + index][stx + detx1])) {
			//有空
			if (mat[sty + index][stx + detx1] + mat[sty + index - 1][stx + detx1] == 3)//空+草
				return;
			index++;
			while (mat[sty + index][stx + detx1] == mat[sty + index - 1][stx + detx1] && index < 2)
			{
				index++;
			}
		}
		if (index!=2) {//表明有变化,那么可能是铁+草,铁+泥土,草+泥土任意组合
			//逐个讨论
			//带草 3
			if (mat[sty + index -1][stx + detx1] == 3 || mat[sty + index][stx + detx1] == 3)//修正消除位置,草不能被打掉
			{
				if (mat[sty + index-1][stx + detx1] == 3)//代表上是草
				{
					dety1 = index;//修改上边界
				}
				else
				{
					dety2 = index - 1;//修改下边界
				}
			}
			else //一般坦克打的,不能消除铁,要判掉
			{
				if (mat[sty + index -1][stx + detx1] == 1)//代表上边是铁
				{
					dety1 = index;//修改上边界
				}
				else
				{
					dety2 = index - 1;//修改下边界
				}
			}
		}
		else if (mat[sty + dety1][stx + detx1] == 3) {
			return;//全是草不考虑
		}
	}
	if (d.who != 3 && mat[y / SIZE + dety1][x / SIZE + detx1] == 1) {//修正之后指向铁,是铁+草的情况,上面未修正完全
		return;//无法消除
	}
	//计算消除矩阵坐标
	int x1 = x + detx1*SIZE;
	int y1 = y + dety1*SIZE;
	int x2 = x + detx2*SIZE+SIZE;
	int y2 = y + dety2*SIZE+SIZE;
	clearrectangle(x1, y1, x2-1, y2-1);
	for (int i = x / SIZE + detx1; i <= x / SIZE + detx2; i++) 
	{
		for (int j = y / SIZE + dety1; j <= y / SIZE + dety2; j++) {
			mat[j][i] = 0;//清除标记
		}
	}
}//消除该区域内的物体

/*画一个子弹*/
void draw_zidan(Zidan d, int xx, int yy) {
	IMAGE ans;
	rotateimage(&ans, &img[(d.who<=3)+15], -PI / 2 * d.dir);//顺时针旋转dir个90度,然后赋值给ans
	putimage(xx + img_zidan_detx1[d.dir]+1, yy + img_zidan_dety1[d.dir]+1, &ans);//旋转后传参
}//画一个子弹

/*清除子弹*/
void clear_zidan(Zidan d) {
	int x1 = d.x + img_zidan_detx1[d.dir];
	int y1 = d.y + img_zidan_dety1[d.dir];
	int x2 = d.x + img_zidan_detx1[d.dir] + SIZE-1;
	int y2 = d.y + img_zidan_dety1[d.dir] + SIZE-1;
	clearrectangle(x1, y1, x2, y2);
	//先算出左上角坐标然后推算右下角坐标
}//清除子弹

/*检验子弹合法性*/
int check_zidan() 
{
	Zidan d;
	for (int i = 1; i <= qz.size(); i++) //这么多个子弹要检查
	{
		d = qz.front();
		qz.pop();
		//注意xy实际上是弹头坐标,左上角坐标要加上偏移量(最上面)
		if (d.x == 0 || d.y == 0) {
			if (d.count) {
				clear_zidan(d);
			}
			else {
				d.count = 1;
			}//出界                                 
			continue;
		}
		int xx = d.x + SIZE * dirx[d.dir];
		int yy = d.y + SIZE * diry[d.dir];//确定下一步位置
		if (xx > MAXX || yy > MAXY) {
			if (d.count) {
				clear_zidan(d);
			}
			else {
				d.count = 1;
			}//出界
			continue;
		}
		int checkX1 = xx / SIZE + check_if_movex[0][d.dir];
		int checkY1 = yy / SIZE + check_if_movey[0][d.dir];//确定检查点单位位置1
		int checkX2 = xx / SIZE + check_if_movex[1][d.dir];
		int checkY2 = yy / SIZE + check_if_movey[1][d.dir];//确定检查点单位位置2
		if (flag[yy/SIZE][xx/SIZE] ==-1 &&!mat[checkY1][checkX1]&& !mat[checkY2][checkX2]) {//都为空位置,不用管
			if (d.count) {
				clear_zidan(d);
			}
			else {
				d.count = 1;
			}
			if (!tool.flag[checkY1][checkX1] && !tool.flag[checkY2][checkX2]) {//不能打在道具上
				draw_zidan(d, xx, yy);//画子弹
				//这里要画出旋转后的,这个里面的左上角坐标正确
				d.x = xx;
				d.y = yy;//更新坐标
				qz.push(d);
			}
		}
		else if(mat[checkY1][checkX1]>0 || mat[checkY2][checkX2]>0){//打到方块
			if (!((mat[checkY1][checkX1] == 1 || mat[checkY2][checkX2] == 1) && d.who != 3)) 
			{//有一个铁块并且等级不够就不消除,代表被挡住了
			    //消除方块
				switch (d.dir) {
				case 0:
					remove_any(-2, -1, 1, -1, d);//传递的参数是子弹信息和相对于弹头的位置(四个方块),子弹分界的左右两个
					break;
				case 1:
					remove_any(0, -2, 0, 1, d);
					break;
				case 2:
					remove_any(-2, 0, 1, 0, d);
					break;
				case 3:
					remove_any(-1, -2, -1, 1, d);
					break;
				}
			}
			if (d.count) {
				if (d.count) {
					clear_zidan(d);
				}
				else {
					d.count = 1;
				}
			}
			else {
				d.count = 1;
			}
		}
		else if (flag[yy / SIZE][xx / SIZE] == 10) {
			clearrectangle(7 * SIZE * 4, 12 * SIZE * 4, 7 * SIZE * 4 + 8 * SIZE, 12 * SIZE * 4 + 8 * SIZE);
			putimage(7 * SIZE * 4, 12 * SIZE * 4, &img[17]);
			return 0;
		}
		else {//代表炸到了坦克
		//根据编号看谁挂掉了
			if (d.who>3&&flag[yy / SIZE][xx / SIZE] == 0|| d.who <= 3 && flag[yy / SIZE][xx / SIZE]!=10) 
			{
				//代表两股势力
				if (clear_tank(xx, yy, 0)) {
					//test();
					return 2;//代表游戏结束
				}
				if (d.count) {
					clear_zidan(d);
				}
				else {
					d.count = 1;
				}
			}
			else {
				if (d.count) {
					clear_zidan(d);
				}
				else {
					d.count = 1;
				}
			}
		}
	}
	return 1;
}//检验子弹合法性

/*根据坦克产生一个子弹*/
void add_zidan(Tank t) {
	int tmp_type = t.type;//可以取到子弹的伤害
	Zidan d;
	d.dir = t.dir;
	int xx, yy;
	switch (t.dir) {//根据方向和坦克的左上角坐标确定子弹占据空间
	case 0:
		xx = t.x + 2 * SIZE;
		yy = t.y;
		break;
	case 1:
		xx = t.x + 4 * SIZE;
		yy = t.y + 2 * SIZE;
		break;
	case 2:
		xx = t.x + 2 * SIZE;
		yy = t.y + 4 * SIZE;
		break;
	case 3:
		xx = t.x;
		yy = t.y + 2 * SIZE;
		break;
	}
	d.x = xx;
	d.y = yy;
	d.who = t.type;//123都是自己的形态,代表是自己,否则是对方的
	d.count = 0;
	qz.push(d);
}//根据坦克产生一个子弹

/*修复老家*/
void repair_map() {
	int num = sizeof(rep_map) / sizeof(rep_map[0]);
	for (int i = 0; i < num; i++) {
		int k = rep_map[i][0];
		int x1 = rep_map[i][1] * 4;
		int y1 = rep_map[i][2] * 4;
		int x2 = rep_map[i][3] * 4 - 1;
		int y2 = rep_map[i][4] * 4 - 1;//右下角方块的左上角坐标
		for (int xx = x1; xx <= x2; xx++) {
			for (int yy = y1; yy <= y2; yy++) {
				mat[yy][xx] = k;
			}
		}
		for (int xx = x1; xx <= x2; xx++) {
			for (int yy = y1; yy <= y2; yy++) {
				putimage(xx*SIZE, yy*SIZE, &img[k + 10]);
			}
		}
	}
}//修复老家

/*检查道具*/
int check_tool(int index) {
	//炸弹停止升级修复
	int x=0, y=0;
	int max_num = 10000;//最多寻找次数
	switch (index) {
	case 1:
		while (max_num--) {
			srand(rand_seed);//重置时间种子
			rand_seed = rand();//随机种子链
			x = rand() % NUMX;
			y = rand() % NUMY;
			if (flag[y][x]>0 && flag[y][x] != 10)
				break;
		}
		if(max_num)//表示可以找到坦克
			if (clear_tank(x*SIZE, y*SIZE, 1)) {
				return 1;
			}
		break;
	case 2:
		stop_epoch = 300;//3秒
		break;
	case 3:
		if (level < 3) {
			up_date_type(level+1,0);//更换类型
			level++;
		}
		break;
	case 4:
		repair_map();
		break;
	}
	return 0;//游戏继续
}//检查道具

/*四个方向*/
void control_dir(int now_dir) {
	if (tank[0].dir != now_dir) //原来不是在这个
	{
		tank[0].dir = now_dir;
		draw_a_tank(tank[0], tank[0].x, tank[0].y);
		tank[0].bef_move = now_dir;//更新
	}
	else if (tank[0].bef_move >= type[tank[0].type].Speed)//可以移动
	{
		int xx = tank[0].x + dirx[tank[0].dir] * SIZE;
		int yy = tank[0].y + diry[tank[0].dir] * SIZE;//坐标
		int check = check_wz(xx, yy, 0);
		if (check) { //检查位置
			draw_a_tank(tank[0], xx, yy);//xx,yy表示更新后的位置
			update_mark(tank[0].dir,xx,yy,0);//更新坦克位置信息
			tank[0].x = xx;
			tank[0].y = yy;
			tank[0].bef_move = 0;//更新时间
			if (check != 10) {
				check_tool(check);//检查道具
				tool.exist = 0;
			}
		}
	}
}//四个按钮

/*控制其他坦克*/
void check_tank()
{
	for (int i = 1; i <= num_tank; i++) {//依次取出坦克
		if (!tank[i].state)continue;//已经没有了
		if (tank[i].bef_move >= type[tank[i].type].Speed) {
			//可能停留在原地的
			if (tank[i].stop_time) {
				tank[i].stop_time--;
				if (tank[i].bef_fight >= type[tank[i].type].Fight_det) {
					srand(rand_seed);//重置时间种子
					rand_seed = rand();//随机种子链
					int state = rand() % 100;
					if (state <= 50) {
						tank[i].bef_fight = 0;
						add_zidan(tank[i]);//添加一个子弹
					}
					else {
						tank[i].bef_fight = 0;
					}
				}
			}
			//表示要移动的
			else if (tank[i].dir_stop_time) {
				int xx = tank[i].x + dirx[tank[i].dir] * SIZE;
				int yy = tank[i].y + diry[tank[i].dir] * SIZE;//坐标
				if (check_wz(xx, yy, i))
				{
					draw_a_tank(tank[i], xx, yy);//xx,yy表示更新后的位置
					update_mark(tank[i].dir, xx, yy, i);//更新坦克位置信息
					tank[i].x = xx;
					tank[i].y = yy;
					tank[i].bef_move = 0;//更新时间
					tank[i].dir_stop_time--;
				}
				else 
				{
					//先打一发
					if (tank[i].bef_fight >= type[tank[i].type].Fight_det) {
						tank[i].bef_fight = 0;
						add_zidan(tank[i]);//添加一个子弹
					}
					srand(rand_seed);//重置时间种子
					rand_seed = rand();//随机种子链
					int state = rand() % 100;
					tank[i].dir_stop_time = state % 10+1;//方向锁定次数
					if (state % 3==1) {
						tank[i].dir = (tank[i].dir + 1) % 4;
						if(state%2)
							tank[i].stop_time = state*2;//可能几个回合之内停在原地
					}
					else if(state % 3 == 2){
						tank[i].dir = (tank[i].dir - 1 + 4) % 4;
						if(state%2)
							tank[i].stop_time = state*2;//可能几个回合之内停在原地
					}
					else {
						tank[i].dir = (tank[i].dir + 2) % 4;//反向
					}
					draw_a_tank(tank[i], tank[i].x, tank[i].y);
				}
			}
			else {
				//换方向默认优先向着中间目标
				srand(rand_seed);//重置时间种子
				rand_seed = rand();//随机种子链
				int state = rand() % 100;
				//根据移动类型优化路径
				if (tank[i].move_type == 1) {
					if (abs(tank[i].x - MAXX / 2) * 2 / MAXX * 100 > state) {//距离中间远
						if (tank[i].x - MAXX / 2 >= 0) {
							tank[i].dir = 3;//左边
						}
						else {
							tank[i].dir = 1;//右边
						}
					}
					else if (abs(tank[i].y - MAXY) / MAXY * 100 > state) {//距离下面远
						tank[i].dir = 2;//向下走
					}
					else {
						tank[i].dir = state % 4;//随机换向
					}
				}
				else {
					if (abs(tank[i].y - MAXY) / MAXY * 100 > state) {//距离下面远
						tank[i].dir = 2;//向下走
					}
					else if (abs(tank[i].x - MAXX / 2) * 2 / MAXX * 100 > state) {//距离中间远
						if (tank[i].x - MAXX / 2 >= 0) {
							tank[i].dir = 3;//左边
						}
						else {
							tank[i].dir = 1;//右边
						}
					}
					else {
						tank[i].dir = state % 4;//随机换向
					}
				}
				tank[i].dir_stop_time = state % 10+1;//方向锁定次数
				draw_a_tank(tank[i], tank[i].x, tank[i].y);
			}
		}
		//控制射击
		if (tank[i].bef_fight >= type[tank[i].type].Fight_det) {
			srand(rand_seed);//重置时间种子
			rand_seed = rand();//随机种子链
			int state = rand() % 100;
			if (state <= 50) {
				tank[i].bef_fight = 0;
				add_zidan(tank[i]);//添加一个子弹
			}
			else {
				tank[i].bef_fight = 0;
			}
		}
		tank[i].bef_fight = (tank[i].bef_fight + 1) % 10000;//距离上次发射时间
		tank[i].bef_move = (tank[i].bef_move + 1) % 10000;//距离上次移动时间
	}
}//控制其他坦克

/*添加道具*/
void add_tool() 
{
	int x, y;
	bool check = 0;
	while (!check) {
		srand(rand_seed);//重置时间种子
		rand_seed = rand();//随机种子链
		x = rand() % NUMX - 2;
		y = rand() % NUMY - 2;//左上角单位坐标
		check = 1;
		for (int i = 0; i < 2; i++) {
			for (int j = 0; j < 2; j++) {
				if (mat[y + i][x + j]||flag[y+i][x+j]!=-1)
					check=0;//不能落在有东西的上面
			}
		}
	}
	int index = rand() % 4 + 1;//道具种类
	//y = 40;
	//x = 30;
	//index = 4;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 2; j++) {
			tool.flag[y + i][x + j] = index;
		}
	}
	putimage(x*SIZE+2, y*SIZE+2, &img[18+index]);
	tool.exist = 1;
}//添加道具

/*开始游戏*/
void begin() {
	int num_t = 0;
	while (1) {
		if (KEY_DOWN(VK_UP)) {
			control_dir(0);
		}
		if (KEY_DOWN(VK_RIGHT)) {
			control_dir(1);
		}
		if (KEY_DOWN(VK_DOWN)) {
			control_dir(2);
		}
		if (KEY_DOWN(VK_LEFT)) {
			control_dir(3);
		}
		if (KEY_DOWN(VK_SPACE)) {
			if (tank[0].bef_fight >= type[tank[0].type].Fight_det) {//至少要过几个时间段才可以发射子弹
				tank[0].bef_fight = 0;
				add_zidan(tank[0]);//添加一个子弹
			}
		}
		if (KEY_DOWN(VK_ESCAPE)) {
			stop();
		}
		if (stop_epoch) {
			stop_epoch--;
		}
		else {
			check_tank();//其他坦克
		}
		Sleep(10);//更新频率
		int now_state = check_zidan();
		if (now_state == 2) //成功通关
		{
			PrintSucess();
			return;
		}
		else if (!now_state || my_lives == 0)//清除无效子弹,检查是否有坦克炸毁
		{
			PrintGameOver();
			return;
		}
		tank[0].bef_fight = (tank[0].bef_fight + 1) % 10000;//距离上次发射时间
		tank[0].bef_move = (tank[0].bef_move + 1) % 10000;//距离上次移动时间
		num_t = (num_t + 1) % 10001;
		if (num_t % 1000 == 0) {//要考虑刷新道具了
			num_t = 0;
			if (!tool.exist) {
				add_tool();
				tool.exist = 1;
			}
		}
	}
}//开始游戏

void game() {
	initgraph(MAXX + 1 + 400, MAXY + 1,SHOWCONSOLE);
	ini_all();//全局初始化函数,一次游戏只初始化一次(区别于关卡初始化)

	while (1) {
		Sleep(10);
		settextcolor(WHITE);
		settextstyle(400, 40,0);
		outtextxy(300, 300, "点击ENTER开始游戏...");
		if (KEY_DOWN(13)) {//点击enter进入
			//下面先写第一关
			cleardevice();
			int num = sizeof(map_1) / sizeof(map_1[0]);
			ini_every(map_1, num);//关卡初始化
			ini_tank();//初始化出坦克
			draw_all_tank();//画坦克
			begin();//游戏开始
			while (1) {
				Sleep(10);
				if (KEY_DOWN(VK_ESCAPE)) {
					cleardevice();
					break;
				}
			}
		}
	}
}

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-30 00:33:07  更:2022-09-30 00:33:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 5:58:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码