C++坦克大战(新手)

程序流程图

流程图

  • 根据流程图我把程序分位四部分:
  1. 第一部分:游戏设置初始化
    :设置窗口名称,大小
    :隐藏光标,切换输入法(控制台赶紧去输入法是中文的)
    :背景音乐设置
  2. 第二部分:游戏菜单设计,选择游戏模式
    :清屏
    :打印开机界面,游戏菜单
    :游戏模式选择(开始游戏,双人模式,自定义地图等等)
  3. 第三部分:游戏初始化
    :清屏
    :初始化随机数种子
    :根据模式初始化坦克
    :初始化子弹
    :初始化地图
    :打印屏幕
  4. 第四部分:游戏主循环
    :游戏胜负检测
    :移动子弹循环
    :移动AI坦克循环
    :AI < 4并且剩余AI数量>0, 创建新的AI, 一次只创建一个
    :AI发射子弹
    :接收键盘输入,控制我方坦克移动
    :检测我方坦克是否死亡,进行复活
先上结果吧

这是我的欢迎界面+菜单
第一关

这是我的想法,因为刚开始学,代码分的比较乱,大家随意看看
  • 因为寻路算法不会写,我的AI比较智障,就只写了思路,大家看看就好
    1.数据Data.h和Data.cpp,主要写全局变量(地图),还有坦克结构体,子弹结构体
//Data.h
#pragma once

#define MAPH 40		//地图高度
#define MAPW 40		//地图宽度
//地形
enum AREA
{
	空地,边界,土墙,钢墙,河流,草地,沙地
};
//方向
enum DIR
{
	UP,DOWN,LEFT,RIGHT
};
//游戏模式
enum OPTION
{
	开始游戏 = 1, 双人模式 , 用户地图 , 载入存档, 退出游戏
};
//颜色
enum COLOR
{
	黑色, 蓝色, 绿色, 浅绿色, 红色, 紫色, 黄色, 白色, 灰色,
	淡蓝色, 淡绿色, 淡浅绿色, 淡红色, 淡紫色, 淡黄色, 亮白色
};
//人物标志
enum FLAG
{
	玩家A = 10, 玩家B = 11, AI_1 = 20, AI_2 = 21, AI_3 = 22, AI_4 = 23, 食物_A = 30, 食物_B = 31
};
//计数器
enum COUNTER
{
	游戏速度,玩家A复活时间, 玩家B复活时间,AI复活时间,AI移动冷却
};
//游戏菜单结构体
typedef struct _MENU
{
	const UINT NUM = 7;	//菜单项数
	COORD Coord[7];	//菜单打印坐标
	char menu[7][50] =
	{
		"*************************************************",
		"                     开始游戏                    ",
		"                     双人模式                    ",
		"                     用户地图                    ",
		"                     载入存档                    ",
		"                     退出游戏                    ",
		"*************************************************"
	};
}MENU,*PMENU;
//坦克结构体
typedef struct _TANK
{
	DWORD         ID : 8;               //坦克ID(0-255)
	DWORD       Type : 2;               //坦克类型(0-3)0是玩家A,1是玩家B,2是普通AI,3是特殊AI
	DWORD      Model : 2;               //坦克图案模型(0-3)0是玩家,AI不能使用
	DWORD      Alive : 1;               //存活为1,死亡为0
	DWORD     Revive : 2;               //复活次数(0-3)
	DWORD  Direction : 2;               //方向(0-3)
	DWORD     Health : 8;               //生命值(255)		
	DWORD SpeedLevel : 2;               //速度等级(0-3)
	DWORD	   color : 8;               //坦克颜色
	DWORD     Score : 24;				//坦克杀敌分数(玩家AB有效)
	DWORD   BulletCD : 5;				//坦克子弹CD
	COORD		 codself;               //中心坐标	
}TANK, *PTANK;

//子弹结构体
typedef struct _BULLET
{
	COORD		 codself;					//中心坐标
	DWORD  Direction : 2;                   //方向(4)
	DWORD     Damage : 6;                   //子弹伤害(64)
	DWORD      Exist : 1;                   //子弹存在与否的变量,1为存在,0不存在
	DWORD       Type : 1;                   //1为AI子弹,0为玩家子弹
	DWORD         ID : 6;                   //子弹ID(64)
}BULLET, *PBULLET;

extern char g_MAP[40][40];
extern char g_TankMap[40][40];
extern DWORD g_counter[12];
//Data.cpp
#include "pch.h"

//全局地图==>记录地形
char g_MAP[40][40] = { 0 };
//坦克坐标地图,0空地,100是玩家A,101是玩家B,200+num是AI
char g_TankMap[40][40] = { 0 };
DWORD g_counter[12] = { 1,1,1,1,1,1,1,1,1,1,1,1 };  //间隔计数器数组,用于控制速度

2.坦克类:坦克的创建,移动,清除等功能

#pragma once
#include "AStar.h"
class Tank
{
public:
	static UINT remain_AI;		//剩余AI(未出现)
	TANK tankData{};			//坦克属性
public:
	Tank();				
	~Tank();
	void InitPlayer(DWORD type);				//初始化玩家
	void InitAI(DWORD type, DWORD id = 0);		//初始化AI
	void CreatePlayer();						//创建玩家坦克
	void CreateAI();							//创建AI坦克
	void Show();								//显示坦克
	void Clear();								//清除坦克
	bool GetPath(COORD end);					//利用AStar算法获取最短路径
	void Move(DIR dire);						//根据输入方向移动
	//void MoveAI(DIR dire);						//移动AI
	void MoveToHome();							//追踪敌方老巢
	void MoveToTank(COORD enemy);				//追踪敌方坦克
	void MoveRandom();							//随机移动
	bool GoCheck();								//通行检测
	bool PosCheck(COORD pos);					//检测当前九宫格能否创建坦克
	void UpdateMapInfo(bool IsClear = false);	//在地图上更新坦克方位
	//void Collision();							//碰撞检测
private:
	AStar Astar;								//寻路算法
	COORD home;									//老家坐标
	char* tank_figure[4][3][4] =				//坦克图案模型
	{
		{
			{"  ■  ", "■  ■", "  ■■", "■■  "},
			{"■●■", "■●■", "■●  ", "  ●■"},
			{"■  ■", "  ▉  ", "  ■■", "■■  "}
		},
		{
			{"┏┃┓", "┏┳┓", "┏┳┓", "┏┳┓"},
			{"┣●┫", "┣●┫", "━●┫", "┣●━"},
			{"┗┻┛", "┗┃┛", "┗┻┛", "┗┻┛"}
		},
		{
			{"┏┃┓", "◢━◣", "┏┳◣", "◢┳┓"},
			{"┣●┫", "┣●┫", "━●┃", "┃●━"},
			{"◥━◤", "┗┃┛", "┗┻◤", "◥┻┛"}
		},
		{
			{"╔┃╗", "╔╦╗", "╔╦╗", "╔╦╗"},
			{"╠█╣", "╠█╣", "━█╣", "╠█━"},
			{"╚╩╝", "╚┃╝", "╚╩╝", "╚╩╝"}
		}
	};
};
//Tank.cpp
#include "pch.h"
#include "Tank.h"

UINT Tank::remain_AI = 16;

Tank::Tank()
{	
	home.X = 19;
	home.Y = 37;
}

Tank::~Tank()
{
}
/********************************************************
函数功能:初始化玩家坦克
参数	:玩家类型0(A)1(B)
返回值	:无
*********************************************************/
void Tank::InitPlayer(DWORD type)
{
	tankData.Type = type;
	tankData.Model = 0;
	tankData.Alive = 1;
	tankData.Revive = 3;
	tankData.Direction = UP;
	tankData.Health = 64;
	tankData.SpeedLevel = 1;
	tankData.color = 紫色;
	tankData.BulletCD = 0;
	if (tankData.Type == 0)   //玩家A
	{
		tankData.codself.X = 14;
		tankData.codself.Y = 37;
	}
	else					//玩家B
	{
		tankData.codself.X = 24;
		tankData.codself.Y = 37;
	}	
}
/********************************************************
函数功能:初始化AI坦克
参数1	:AI类型
参数2	:AI编号
返回值	:无
*********************************************************/
void Tank::InitAI(DWORD type, DWORD id /*= 0*/)
{
	tankData.ID = id;
	tankData.Type = type;
	tankData.Model = rand() % 3 + 1;
	tankData.Alive = 0;
	tankData.Revive = 0;
	tankData.Direction = UP;
	tankData.Health = 64;
	tankData.SpeedLevel = 1;
	tankData.color = rand() % 5 + 4;
	tankData.BulletCD = 0;
	tankData.codself.X = rand() % 36 + 2;
	tankData.codself.Y = 2;	
}
/********************************************************
函数功能:创建玩家坦克
参数1	:无
返回值	:无
*********************************************************/
void Tank::CreatePlayer()
{
	//玩家A
	if (tankData.Type == 0 && tankData.Alive == 0 && tankData.Revive > 0)
	{
		tankData.Alive = 1;
		tankData.Revive--;
		tankData.Direction = 0;
		tankData.Health = 64;
		tankData.SpeedLevel = 1;
		tankData.BulletCD = 0;
		tankData.codself.X = 14;
		tankData.codself.Y = 37;
	}
	//玩家B
	if (tankData.Type == 1 && tankData.Alive == 0 && tankData.Revive > 0)
	{
		tankData.Alive = 1;
		tankData.Revive--;
		tankData.Direction = 0;
		tankData.Health = 64;
		tankData.SpeedLevel = 1;
		tankData.BulletCD = 0;
		tankData.codself.X = 24;
		tankData.codself.Y = 37;
	}
	Show();
}
/********************************************************
函数功能:创建AI坦克
参数1	:无
返回值	:无
*********************************************************/
void Tank::CreateAI()
{
	remain_AI--;
	tankData.Alive = 1;
	tankData.Health = 64;
	tankData.SpeedLevel = 1;
	tankData.BulletCD = 0;
	tankData.Direction = rand() % 4;
	tankData.codself.X = rand() % 36 + 2;
	tankData.codself.Y = 2;
	while (!PosCheck(tankData.codself))		//有障碍物重置坐标
	{
		tankData.codself.X = rand() % 36 + 2;
		tankData.codself.Y = 2;
	}
	Show();
}

/********************************************************
函数功能:画坦克(人机共用)
参数	:无
返回值	:无
*********************************************************/
void Tank::Show()
{
	if (!tankData.Alive)    //坦克死亡不打印
		return;
	char* (*tankF)[4] = tank_figure[tankData.Model];    //选择坦克图案
	for (int i = 0; i < 3; i++)		//打印坦克
	{
		if (g_MAP[tankData.codself.Y - 1 + i][tankData.codself.X - 1] != 草地)
			WriteChar(tankData.codself.X - 1, tankData.codself.Y - 1 + i, tankF[i][tankData.Direction], tankData.color);
	}
	//在地图上更新坦克方位
	UpdateMapInfo();
}
/********************************************************
函数功能:清除坦克(人机共用)
参数	:无
返回值	:无
*********************************************************/
void Tank::Clear()
{
	for (int i = 0; i < 3; i++)
		for (int j = 0; j < 3; j++)	
		{   		
			if (g_MAP[tankData.codself.Y + j - 1][tankData.codself.X + i - 1] == 空地)
				WriteChar(tankData.codself.X + i - 1, tankData.codself.Y + j - 1, "  ", 黑色);
		}
	//清除地图上的坦克方位
	UpdateMapInfo(true);
}

//*******************************************************
//函数功能	:获取最短路径
//参数		:终点坐标
//返回值	:无
//*******************************************************
bool Tank::GetPath(COORD end)
{
	Astar.InitMapInfo((char*)g_MAP, MAPH, MAPW);
	Astar.InitCoordInfo(tankData.codself,end);
	if (Astar.FindPath())
	{
		Astar.GetPath();
		return true;
	}
	return false;
}
/********************************************************
函数功能:坦克移动函数
参数	:方向
返回值	:无
*********************************************************/
void Tank::Move(DIR dire)
{
	tankData.Direction =(DWORD)dire;
	if (!tankData.Alive)
	{
		return;
	}	
	Clear();
	if (GoCheck())
	{
		//检测坦克当前方向可通行
		switch (tankData.Direction)
		{
		case UP:tankData.codself.Y--; break;
		case DOWN:tankData.codself.Y++; break;
		case LEFT:tankData.codself.X--; break;
		case RIGHT:tankData.codself.X++; break;
		}
	}
	Show();
}
/********************************************************
函数功能:AI锁定老巢移动
参数1	:无
返回值	:无
*********************************************************/
void Tank::MoveToHome()
{
	if (!GetPath(home))		//没有找到路径就随机移动
	{
		tankData.Direction = rand() % 4;
		Move((DIR)tankData.Direction);				//移动
		return;
	}
	if (Astar.m_Path.empty())		//路径为空,说明到达目的地,随机切换方向发射子弹
	{
		tankData.Direction = rand() % 3 + 1;
		Move((DIR)tankData.Direction);				//移动
		return;
	}
	if (rand() % 5)//坦克有五分之四的几率选择正确的方向
	{
		int nIndex = Astar.m_Path.size() - 1;		
		tankData.Direction = Astar.m_Path[nIndex].d;
		Move((DIR)tankData.Direction);
	}
	else
	{
		//坦克有五分之一的几率不动
	}	
}
/********************************************************
函数功能:AI锁定敌方坦克移动
参数1	:敌方坦克坐标
返回值	:无
*********************************************************/
void Tank::MoveToTank(COORD enemy)
{
	if (GetPath(enemy))								//获取路径
	{
		if (Astar.m_Path.empty())		//路径为空,说明到达目的地,随机切换方向发射子弹
		{
			tankData.Direction = rand() % 4;
			return;
		}
		int nIndex = Astar.m_Path.size() - 1;		
		tankData.Direction = Astar.m_Path[nIndex].d;//更新方向
		Move((DIR)tankData.Direction);				//移动
		return;
	}
	//没有找到路径就随机移动
	tankData.Direction = rand() % 4;
	Move((DIR)tankData.Direction);				//移动
}
/********************************************************
函数功能:AI随机移动
参数1	:无
返回值	:无
*********************************************************/
void Tank::MoveRandom()
{
	DWORD CurrentDire = tankData.Direction;		//当前方向
	//遇到障碍物重置方向
	if (!GoCheck())
	{
		while (tankData.Direction == CurrentDire)
		{
			tankData.Direction = rand() % 4;
		}
		Move((DIR)tankData.Direction);				//移动
		return;
	}
	//有%10的概率重置方向
	if (rand() % 10 == 5)
	{
		tankData.Direction = rand() % 4;
	}
	Move((DIR)tankData.Direction);				//移动
}

/********************************************************
函数功能:坦克通行检测
参数1	:无
返回值	:返回true可通行,返回0阻挡
*********************************************************/
bool Tank::GoCheck()
{
	switch (tankData.Direction)
	{							//当前方向为空地、草地、沙地并且无坦克时可通行
	case UP:
		if ((g_MAP[tankData.codself.Y - 2][tankData.codself.X - 1] == 0 || g_MAP[tankData.codself.Y - 2][tankData.codself.X - 1] == 5 || g_MAP[tankData.codself.Y - 2][tankData.codself.X - 1] == 6) &&
			(g_MAP[tankData.codself.Y - 2][tankData.codself.X] == 0 || g_MAP[tankData.codself.Y - 2][tankData.codself.X] == 5 || g_MAP[tankData.codself.Y - 2][tankData.codself.X] == 6) &&
			(g_MAP[tankData.codself.Y - 2][tankData.codself.X + 1] == 0 || g_MAP[tankData.codself.Y - 2][tankData.codself.X + 1] == 5 || g_MAP[tankData.codself.Y - 2][tankData.codself.X + 1] == 6)&&
			(g_TankMap[tankData.codself.Y - 2][tankData.codself.X - 1] ==0 && g_TankMap[tankData.codself.Y - 2][tankData.codself.X] == 0 && g_TankMap[tankData.codself.Y - 2][tankData.codself.X + 1] == 0))
			return true;
		else
			return false;
	case DOWN:
		if ((g_MAP[tankData.codself.Y + 2][tankData.codself.X - 1] == 0 || g_MAP[tankData.codself.Y + 2][tankData.codself.X - 1] == 5 || g_MAP[tankData.codself.Y + 2][tankData.codself.X - 1] == 6) &&
			(g_MAP[tankData.codself.Y + 2][tankData.codself.X] == 0 || g_MAP[tankData.codself.Y + 2][tankData.codself.X] == 5 || g_MAP[tankData.codself.Y + 2][tankData.codself.X] == 6) &&
			(g_MAP[tankData.codself.Y + 2][tankData.codself.X + 1] == 0 || g_MAP[tankData.codself.Y + 2][tankData.codself.X + 1] == 5 || g_MAP[tankData.codself.Y + 2][tankData.codself.X + 1] == 6)&&
			(g_TankMap[tankData.codself.Y + 2][tankData.codself.X - 1] == 0 && g_TankMap[tankData.codself.Y + 2][tankData.codself.X] == 0 && g_TankMap[tankData.codself.Y + 2][tankData.codself.X + 1] == 0))
			return true;
		else
			return false;
	case LEFT:
		if ((g_MAP[tankData.codself.Y - 1][tankData.codself.X - 2] == 0 || g_MAP[tankData.codself.Y - 1][tankData.codself.X - 2] == 5 || g_MAP[tankData.codself.Y - 1][tankData.codself.X - 2] == 6) &&
			(g_MAP[tankData.codself.Y][tankData.codself.X - 2] == 0 || g_MAP[tankData.codself.Y][tankData.codself.X - 2] == 5 || g_MAP[tankData.codself.Y][tankData.codself.X - 2] == 6) &&
			(g_MAP[tankData.codself.Y + 1][tankData.codself.X - 2] == 0 || g_MAP[tankData.codself.Y + 1][tankData.codself.X - 2] == 5 || g_MAP[tankData.codself.Y + 1][tankData.codself.X - 2] == 6)&&
			(g_TankMap[tankData.codself.Y - 1][tankData.codself.X - 2] == 0 && g_TankMap[tankData.codself.Y ][tankData.codself.X-2] == 0 && g_TankMap[tankData.codself.Y +1][tankData.codself.X-2] == 0))
			return true;
		else
			return false;
	case RIGHT:
		if ((g_MAP[tankData.codself.Y - 1][tankData.codself.X + 2] == 0 || g_MAP[tankData.codself.Y - 1][tankData.codself.X + 2] == 5 || g_MAP[tankData.codself.Y - 1][tankData.codself.X + 2] == 6) &&
			(g_MAP[tankData.codself.Y][tankData.codself.X + 2] == 0 || g_MAP[tankData.codself.Y][tankData.codself.X + 2] == 5 || g_MAP[tankData.codself.Y][tankData.codself.X + 2] == 6) &&
			(g_MAP[tankData.codself.Y + 1][tankData.codself.X + 2] == 0 || g_MAP[tankData.codself.Y + 1][tankData.codself.X + 2] == 5 || g_MAP[tankData.codself.Y + 1][tankData.codself.X + 2] == 6)&&
			(g_TankMap[tankData.codself.Y - 1][tankData.codself.X + 2] == 0 && g_TankMap[tankData.codself.Y][tankData.codself.X + 2] == 0 && g_TankMap[tankData.codself.Y + 1][tankData.codself.X + 2] == 0))
			return true;
		else
			return false;
	default:return false;
	}
}
/********************************************************
函数功能:障碍物检测(人机共用)
参数1	:坦克中心坐标
返回值	:无障碍返回true
*********************************************************/
bool Tank::PosCheck(COORD pos)
{
	COORD tankCoord[9] = { 0 };
	tankCoord[0] = pos;
	//顶点坐标
	tankCoord[1].X = tankCoord[0].X - 1;		//左上顶点
	tankCoord[1].Y = tankCoord[0].Y - 1;
	tankCoord[2].X = tankCoord[0].X + 1;		//右上顶点
	tankCoord[2].Y = tankCoord[0].Y - 1;
	tankCoord[3].X = tankCoord[0].X - 1;		//左下顶点
	tankCoord[3].Y = tankCoord[0].Y + 1;
	tankCoord[4].X = tankCoord[0].X + 1;		//右下顶点
	tankCoord[4].Y = tankCoord[0].Y + 1;
	//四周坐标
	tankCoord[5].X = tankCoord[0].X;			//上
	tankCoord[5].Y = tankCoord[0].Y - 1;
	tankCoord[6].X = tankCoord[0].X;			//下
	tankCoord[6].Y = tankCoord[0].Y + 1;
	tankCoord[7].X = tankCoord[0].X - 1;		//左
	tankCoord[7].Y = tankCoord[0].Y;
	tankCoord[8].X = tankCoord[0].X + 1;		//右
	tankCoord[8].Y = tankCoord[0].Y;
	for (int i = 0; i < 9; i++)
	{
		if (g_TankMap[tankCoord[i].Y][tankCoord[i].X] != 0 ||
			g_MAP[tankCoord[i].Y][tankCoord[i].X] != 0)
		{
			return false;
		}
	}
	return true;
}


void Tank::UpdateMapInfo(bool IsClear)
{
	COORD tankCoord[9] = { 0 };
	int tankType = 0;						//地图坦克标志
	tankCoord[0] = tankData.codself;		//获取坦克坐标
	tankCoord[1].X = tankCoord[0].X - 1;		//左上顶点
	tankCoord[1].Y = tankCoord[0].Y - 1;
	tankCoord[2].X = tankCoord[0].X + 1;		//右上顶点
	tankCoord[2].Y = tankCoord[0].Y - 1;
	tankCoord[3].X = tankCoord[0].X - 1;		//左下顶点
	tankCoord[3].Y = tankCoord[0].Y + 1;
	tankCoord[4].X = tankCoord[0].X + 1;		//右下顶点
	tankCoord[4].Y = tankCoord[0].Y + 1;
	tankCoord[5].X = tankCoord[0].X;			//上
	tankCoord[5].Y = tankCoord[0].Y - 1;
	tankCoord[6].X = tankCoord[0].X;			//下
	tankCoord[6].Y = tankCoord[0].Y + 1;
	tankCoord[7].X = tankCoord[0].X - 1;		//左
	tankCoord[7].Y = tankCoord[0].Y;
	tankCoord[8].X = tankCoord[0].X + 1;		//右
	tankCoord[8].Y = tankCoord[0].Y;
	switch (tankData.Type)				//获取坦克类型
	{
	case 0:tankType = 10; break;		//玩家A
	case 1:tankType = 11; break;		//玩家B
	case 2:tankType = 20 + tankData.ID; break;	//普通AI
	case 3:tankType = 23; break;		//特殊AI
	default:break;
	}
	if (IsClear)
	{
		for (int i = 0; i < 9; i++)		//清除坦克方位
		{
			g_TankMap[tankCoord[i].Y][tankCoord[i].X] = 0;
		}
	}
	else
	{
		for (int i = 0; i < 9; i++)		//更新坦克方位
		{
			g_TankMap[tankCoord[i].Y][tankCoord[i].X] = tankType;
		}
	}	
}

3.子弹类:子弹的创建,移动,清除,碰撞检测

#pragma once
#include "Tank.h"

class Bullet
{
public:
	BULLET Data = {};		//子弹属性结构体
public:
	Bullet();
	~Bullet();
	void CreateAI(Tank &tank);			//创建AI子弹
	void Create(Tank &tank);			//创建普通子弹
	void Move();						//移动子弹
	bool Hit(Tank (&tank)[6]);			//子弹碰撞处理
	void Show();						//打印子弹
	void Clear();						//清除子弹
	bool GoCheck();						//通行检测
private:

};
//Bullet.cpp
#include "pch.h"
#include "Bullet.h"

Bullet::Bullet()
{
}

Bullet::~Bullet()
{
}
/********************************************************
函数功能:创建AI子弹
参数	:无
返回值	:无
*********************************************************/
void Bullet::CreateAI(Tank &tank)
{
	if (!(rand() % 10))		//冷却结束后在随后的每个游戏周期中有10分之一的可能发射子弹
	{
		Create(tank);
	}
}

//************************************
// Method:    Create
// FullName:  Bullet::Create
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: const Tank & tank
//************************************
void Bullet::Create(Tank &tank)
{
	//更新子弹信息-->|方向|存在状态|类型|坐标|伤害
	SHORT tankx = tank.tankData.codself.X;
	SHORT tanky = tank.tankData.codself.Y;
	Data.Damage = 32;
	Data.Direction = tank.tankData.Direction;			
	Data.Exist = 1;
	if(tank.tankData.Type==0 || tank.tankData.Type ==1)
		Data.Type = 0;
	else
		Data.Type = 1;
	switch (Data.Direction)
	{
	case 0:Data.codself.X = tankx; Data.codself.Y= tanky - 2; break;
	case 1:Data.codself.X = tankx; Data.codself.Y = tanky + 2; break;
	case 2:Data.codself.X = tankx - 2; Data.codself.Y = tanky; break;
	case 3:Data.codself.X = tankx + 2; Data.codself.Y = tanky; break;
	}
}
/********************************************************
函数功能:子弹移动
参数	:无
返回值	:无
*********************************************************/
void Bullet::Move()
{			
	switch (Data.Direction)		
	{
	case UP:Data.codself.Y--; break;
	case DOWN:Data.codself.Y++; break;
	case LEFT:Data.codself.X--; break;
	case RIGHT:Data.codself.X++; break;
	}
}

/********************************************************
函数功能:子弹碰撞函数
参数	:坦克对象数组
返回值	:正常返回0,炸毁老家返回1
*********************************************************/
bool Bullet::Hit(Tank(&tank)[6])
{
	SHORT X = Data.codself.X;
	SHORT Y = Data.codself.Y;
	//碰撞障碍物
	switch (g_MAP[Data.codself.Y][Data.codself.X])
	{
		//可正常通行
	case 沙地:
	case 河流:Show(); break;
		//隐藏通行:
	case 草地:break;
		//同归于尽,一次性打掉三块砖,方便坦克出行
	case 土墙:
	{
		Data.Exist = 0;
		if (Data.Direction == LEFT || Data.Direction == RIGHT)		//纵向删除三块土墙,非土墙忽略
		{
			for (int i = -1; i <= 1; i++)
			{
				if (g_MAP[Data.codself.Y + i][Data.codself.X] == 土墙)
				{
					g_MAP[Data.codself.Y + i][Data.codself.X] = 空地;
					WriteChar(Data.codself.X, Data.codself.Y + i, "  ", 0x00);
				}
			}
		}
		else
		{
			for (int i = -1; i <= 1; i++)
			{
				if (g_MAP[Data.codself.Y][Data.codself.X + i] == 土墙)
				{
					g_MAP[Data.codself.Y][Data.codself.X + i] = 空地;
					WriteChar(Data.codself.X + i, Data.codself.Y, "  ", 0x00);
				}
			}
		}
		break;
	}
		//自杀
	case 边界:
	case 钢墙:Data.Exist = 0; break;
	case 9:Data.Exist = 0;         //老巢炸毁
		WriteChar(18, 36, "      ", 0x0F);
		WriteChar(18, 37, "◢◣  ", 0x0F);
		WriteChar(18, 38, "███", 0x0F);
		return true;
	}
	if (g_TankMap[Y][X] != 0) //碰撞坦克
	{
		//子弹与友方坦克碰撞, 隐藏子弹, 不用处理
		//if (Data.Type == 0 && (g_TankMap[Y][X] == 10 || g_TankMap[Y][X] == 11) ||
		//	Data.Type == 1 && g_TankMap[Y][X] >= 20)
		//子弹与敌方坦克碰撞
		if (Data.Type == 1 && (g_TankMap[Y][X] == 10 || g_TankMap[Y][X] == 11) ||
			Data.Type == 0 && g_TankMap[Y][X] >= 20)
		{
			Data.Exist = 0;      //子弹消失
			//获取坦克信息,坦克消失
			if (g_TankMap[Y][X] == 10)
			{
				tank[0].tankData.Alive = 0;		//玩家A死亡
				tank[0].Clear();
			}
			if (g_TankMap[Y][X] == 11)
			{
				tank[1].tankData.Alive = 0;		//玩家B死亡
				tank[1].Clear();
			}
			if (g_TankMap[Y][X] >= 20)
			{
				int nIndex = g_TankMap[Y][X] % 20 + 2;	//AI下标
				tank[nIndex].tankData.Alive = 0;
				tank[nIndex].Clear();
				tank[0].tankData.Score += 10;			//更新坦克得分分数
				//if (nIndex == 3)	//特殊坦克死亡会留下食物
				//{
				//	WriteChar(tank[3].tankData.codself.X, tank[3].tankData.codself.X, "卐", 黄色);
				//	//更新地图标志
				//}
			}
		}
	}
	//与敌军子弹碰撞
	//if (Data.Type == 1 && g_TankMap[Y][X] == 1 || Data.Type == 0 && g_TankMap[Y][X] == 2)
	//{
	//	Data.Exist = 0;
	//}
	return false;
}
/********************************************************
函数功能:打印子弹
参数	:子弹
返回值	:无
*********************************************************/
void Bullet::Show()
{
	if (Data.Exist)            //子弹存在
	{
		WriteChar(Data.codself.X, Data.codself.Y, "☉", 0x0A);
		//在g_TankMap更新子弹方位
		/*if (Data.Type)
			g_TankMap[Data.codself.Y][Data.codself.X] = 2;
		else
			g_TankMap[Data.codself.Y][Data.codself.X] = 1;*/
	}
}
/********************************************************
函数功能:清除子弹
参数1	:无
返回值	:无
*********************************************************/
void Bullet::Clear()
{
	//如果该位置是坦克,不用清除
	if (g_TankMap[Data.codself.Y][Data.codself.X])
	{
		return;
	}
	switch (g_MAP[Data.codself.Y][Data.codself.X])
	{
	case 空地:WriteChar(Data.codself.X, Data.codself.Y, "  ", 0x0F); break;
	case 河流:WriteChar(Data.codself.X, Data.codself.Y, "~", 0x1F); break;
	case 沙地:WriteChar(Data.codself.X, Data.codself.Y, "▓", 0x06); break;
	default:break;
	}
	//在g_TankMap更新子弹方位
	//g_TankMap[Data.codself.Y][Data.codself.X] = 0;
}
/********************************************************
函数功能:子弹当前位置检测
参数	:无
返回值	:可通行返回true
*********************************************************/
bool Bullet::GoCheck()
{
	return (g_MAP[Data.codself.Y][Data.codself.X] == 空地 &&
		g_TankMap[Data.codself.Y][Data.codself.X] == 0);
}

4.AStar寻路算法类,AI锁定敌人移动算法

#pragma once

/*********************************************************************************
AStar算法:
	openList:保存待检测的点
	closeList:保存已检测的点(包含最短路径)
	G Path:移动损耗(即从起点开始每走一步加1,斜走加根号2)
	H Path:离目的地距离
	F Path:F=G+H
**********************************************************************************/
class AStar
{
public:
	AStar();
	~AStar();
	//自定义的坐标结构体
	typedef struct _MY_COORD :public COORD
	{
		//重载等号运算符,方便比较
		bool operator==(COORD cod)
		{
			return (X == cod.X) && (Y == cod.Y);
		}
		//重载赋值运算符,方便赋值
		void operator=(COORD cod)
		{
			X = cod.X;
			Y = cod.Y;
		}
		short d; //坐标点方向
	}MY_COORD, *PMY_COORD;

	//节点的信息结构体
	typedef struct _NODE_INFO
	{
		int g;//移动损耗(每一动一次加1)
		int h;//距离终点最短距离
		int f;//g+h

		void SetH_F(COORD cod2)
		{
			h = abs(codSelf.X - cod2.X) + abs(codSelf.Y - cod2.Y);
			f = g + h;
		}

		MY_COORD codSelf;  //自身坐标
		MY_COORD codParent;//父坐标点(由谁扩散出来的点的坐标)
	}NODE_INFO, *PNODE_INFO;
	
private:
	void GetTankCoord(COORD tank);		//获取坦克九宫格坐标坐标
	vector<NODE_INFO> m_Open;  //代检测的点的集合
	vector<NODE_INFO> m_Close; //检测过的点(扩散过的点)的集合
	COORD m_tankCoord[4][4];			//坦克中心点坐标+当前方向三点坐标

	int m_Block;  //地图中的障碍物
	int m_MapH;   //地图高度
	int m_MapW;   //地图宽度
	char* m_pMap; //地图首地址

	COORD m_Start; //起点坐标
	COORD m_End;   //终点坐标

	bool m_bInitMapInfo;   //是否初始化地图信息
	bool m_bInitCoordInfo; //是否初始化起始坐标
public:
	void InitMapInfo(char* pMap, int nHigh, int nWidth);
	void InitCoordInfo(COORD codStart, COORD codEnd);
	void GetPath();  //获取最短路径
	bool FindPath(); //寻找包含最短路径的点

	//临时地图,保存该节点是否在open表或close中
	typedef struct _TEMP_MAP
	{
		char isOpen : 1;
		char isClose : 1;
		char recver : 6;
	}TEMP_MAP, *PTEMP_MAP;

	PTEMP_MAP m_pTempMap;		//临时地图
	vector<MY_COORD> m_Path;    //最短路径
};
//算法写的比较乱,没写好,这是思路:这是点到点的,坦克是九个点,得改装有点头疼
//bool FindPath(); //寻找包含最短路径的点
/*********************************************************************************
	寻路过程:
		1.初始化起点
		2.把起点添加到openList
		3.判断open是否为空(说明没找到)
		4.从oprn表中找到F最小值,进行扩散
		5.把扩散过的点,添加到close表中,并从open表中删除
		6.检测扩散出来的点是否存在终点,如果不是,就检测是否可以添加到open表中
			6.1.是否是终点(是,就直接返回true)
			6.2.是否越界
			6.3.是否是障碍物
			6.4.是否在open表中
			6.5.是否在close表中
		7.把检测合格的点添加到open表中
		8.重复3-7步
	**********************************************************************************/

5.游戏类:贴的不全,我的地图是40*40一行一行输的,太长

	void InitGameSet();				//初始化游戏设置
	void GameMenu();				//游戏主菜单
	void InitGame();				//初始化游戏
	void GameLoop();				//游戏主循环
	void GetMap(int level);			//获取关卡地图
private:
	bool SetWindowSize(TCHAR* pszWindowTitle, short x, short y);	//设置窗口大小
	void DrawMainScreen(char(*pMap)[40]);							//画主屏幕地图
	void DrawSideScreen();											//打印副屏幕(游戏信息)
	void UpdateSideScreen();										//更新游戏信息
	void DrawUserSideScreen();										//自定义地图地形选择窗口
	void ClearMainScreen();											//清除主屏幕
	void ClearFullScreen();											//清除全屏
	COLOR SetColor(int No);											//设置字体颜色
	void MouseOp();													//鼠标操作控制
	void GameStop();												//游戏暂停
	void GameOver(bool home);										//游戏结束
	void UserMap();													//自定义地图
	void GameCheck();												//游戏胜负检测
	void MytankRebirth();											//复活玩家坦克
	void CreatBullet(Tank &tank);									//创建子弹
	void MoveBullet();												//移动子弹
	void KeyboardA();												//键盘输入控制
	void KeyboardB();												//控制玩家B
	void SaveInfo();												//存档	
	void ReadInfo();												//读档
	void LoadRecord();												//加载存档信息
	void NextLevel();												//加载下一关卡
private:
	MENU menu;							//游戏菜单
	UINT GameSpeed = 10;				//游戏速度
	bool SingleMode = true;				//单人模式标志
	UINT GameLevel = 1;					//游戏关卡
	UINT MaxLevel = 3;					//最大游戏关卡
	Tank tankArr[6];					//坦克数组
	Tank& tankA = tankArr[0];			//玩家A
	Tank& tankB = tankArr[1];			//玩家B
	Tank* AItank = &tankArr[2];			//AI
	vector<Bullet> m_bullet;			//子弹	
	char obType = 0;					//地形类型

#include "pch.h"
#include "Game.h"
#include <time.h>
#include <mmsystem.h>
#pragma comment(lib, "WINMM.LIB")

Game::Game()
{
	
}
Game::~Game()
{
}
/********************************************************
第一部分:初始化游戏设置
		1.设置窗口大小
		2.隐藏光标
		3.切换英文输入法
		4.背景音乐
*********************************************************/
void Game::InitGameSet()
{
	TCHAR* GameName = L"坦克大战";
	SetWindowSize(GameName, 120, 40);				//设置窗口标题,大小
	//切换输入法
	keybd_event(VK_SHIFT, 0, 0, 0);
	Sleep(100);
	keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
	//隐藏光标
	HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);	//标准输出设备句柄
	CONSOLE_CURSOR_INFO cci;							//鼠标结构体
	cci.dwSize = 1;
	cci.bVisible = false;
	SetConsoleCursorInfo(hStdOut, &cci);
	//设置背景音乐
	PlaySound(L"..\\TankDemo\\Sound\\tank.wav", NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);
	GetMap(GameLevel);		//地图初始化
}
/********************************************************
第二部分:游戏主菜单
		1.清屏
		2.打印游戏菜单
		3.模式选择控制
*********************************************************/
void Game::GameMenu()
{
	ClearFullScreen();		//清屏
	//初始化游戏菜单
	for (UINT i=0;i< menu.NUM;i++)
	{
		menu.Coord[i].X = 16;
	}
	menu.Coord[0].Y = 20;
	menu.Coord[1].Y = 23;
	menu.Coord[2].Y = 25;
	menu.Coord[3].Y = 27;
	menu.Coord[4].Y = 29;
	menu.Coord[5].Y = 31;
	menu.Coord[6].Y = 34;
	//打印游戏菜单
	/****************************************************/
	char* Tank[8] =
	{
		{"          ★★█〓███████▇▅▅▅▅▅▅▅▄▄▄▄▄▅▇▇▇    ●    ●"},
		{"                         █●█"},
		{"       ◢▄██●★●█████●★●███▄◣"},
		{"     ◢〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓◣"},
		{"  ▄▅██████████████████████▅▄▃"},
		{"◢〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓〓◣"},
		{"◥███████████████████████████◤"},
		{"  ◥●▲●▲●▲●▲●▲●▲●▲●▲●▲●▲●▲●▲●◤"}
	};
	for (UINT i = 0; i < 8; i++)
	{
		WriteChar(14, 2 + 2 * i, Tank[i], 0x02);
	}
	for (UINT i = 0; i < menu.NUM; i++)
	{
		WriteChar(menu.Coord[i].X, menu.Coord[i].Y, menu.menu[i], 蓝色);
	}
	//游戏模式选择
	/****************************************************/
	WriteChar(menu.Coord[1].X, menu.Coord[1].Y, menu.menu[1], 红色);	//默认选择第一项
	int No = 1;
	while (1)
	{
		if (GetAsyncKeyState(VK_UP) & 0x8000)		//↑按键处理
		{
			WriteChar(menu.Coord[No].X, menu.Coord[No].Y, menu.menu[No], 蓝色);
			if (No == 1)
				No = 5;
			else
				No--;
			WriteChar(menu.Coord[No].X, menu.Coord[No].Y, menu.menu[No], 红色);
		}
		else if (GetAsyncKeyState(VK_DOWN) & 0x8000)//↓按键处理
		{
			WriteChar(menu.Coord[No].X, menu.Coord[No].Y, menu.menu[No], 蓝色);
			if (No == 5)
				No = 1;
			else
				No++;
			WriteChar(menu.Coord[No].X, menu.Coord[No].Y, menu.menu[No], 红色);
		}
		else if (GetAsyncKeyState(0xD) & 0x8000)      //回车确定
		{
			switch (No)									//进入游戏
			{
			case 开始游戏: SingleMode = true; return;
			case 双人模式: SingleMode = false; return;
			case 用户地图: UserMap(); return;
			case 载入存档: LoadRecord(); return;
			case 退出游戏: exit(0);
			}
		}
		Sleep(100);
	}
}
/********************************************************
函数功能:接收键盘输入,控制坦克
参数	:无
返回值	:无
*********************************************************/
void Game::KeyboardA()
{
	int nCount = 0;	//计数器
	if (GetAsyncKeyState('W') & 0x8000)
		tankA.Move(UP);
	else if (GetAsyncKeyState('S') & 0x8000)
		tankA.Move(DOWN);
	else if (GetAsyncKeyState('A') & 0x8000)
		tankA.Move(LEFT);
	else if (GetAsyncKeyState('D') & 0x8000)
		tankA.Move(RIGHT);
	else if (GetAsyncKeyState('J') & 0x8000)
	{
		CreatBullet(tankA);
	}
	else if (GetAsyncKeyState(VK_SPACE) & 0x8000)   //空格
	{
		GameStop();
	}	
	else if (GetAsyncKeyState(VK_ESCAPE) & 0x8000)  // Esc键
		exit(0);
	else if (nCount++ % 5 == 0)		//防止按键粘连,达到微调速度
	{
		if (GameSpeed > 1 && GetAsyncKeyState(VK_ADD) & 0x8000)
			GameSpeed--;
		if (GameSpeed < 20 && GetAsyncKeyState(VK_SUBTRACT) & 0x8000)
			GameSpeed++;
	}									//退出程序函数	
	else
	{
		//return;
	}
}

void Game::KeyboardB()
{
	if (GetAsyncKeyState(VK_UP) & 0x8000)
	tankB.Move(UP);
	else if (GetAsyncKeyState(VK_DOWN) & 0x8000)
	tankB.Move(DOWN);
	else if (GetAsyncKeyState(VK_LEFT) & 0x8000)
	tankB.Move(LEFT);
	else if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
	tankB.Move(RIGHT);
	else if (GetAsyncKeyState(VK_NUMPAD5) & 0x8000)
	{
	CreatBullet(tankB);
	}
	else
	{ }
}

/********************************************************
函数功能:存档
参数	:无
返回值	:无
*********************************************************/
void Game::SaveInfo()
{
	FILE *fp;
	errno_t err;
	err = fopen_s(&fp, "..\\TankDemo\\GameInfo\\Info.txt", "wb");
	if (err)
	{
		WriteChar(20, 20, "存档失败…",红色);
		return;
	}
	//保存地图
	fwrite(g_MAP, sizeof(char), 40 * 40, fp);
	//保存坦克
	fwrite(tankArr, sizeof(Tank), 6, fp);
	//保存子弹数量
	UINT bulletNum = m_bullet.size();
	fwrite(&bulletNum, sizeof(UINT), 1, fp);
	fwrite(&m_bullet, sizeof(vector<Bullet>), bulletNum, fp);
	//保存游戏信息-->|关卡|单人/双人模式标志|游戏速度
	fwrite(&GameLevel, sizeof(UINT), 1, fp);
	fwrite(&SingleMode, sizeof(bool), 1, fp);
	fwrite(&GameSpeed, sizeof(UINT), 1, fp);
	fclose(fp);
}
/********************************************************
函数功能:读档
参数	:无
返回值	:无
*********************************************************/
void Game::ReadInfo()
{
	FILE *fp;
	errno_t err;
	err = fopen_s(&fp, "..\\TankDemo\\GameInfo\\Info.txt", "rb");
	if (err)
	{
		WriteChar(20, 20, "读档失败…", 红色);
		return;
	}
	//读取地图
	fread(g_MAP, sizeof(char), 40 * 40, fp);
	//读取坦克
	fread(tankArr, sizeof(Tank), 6, fp);
	//读取子弹/数量
	UINT bulletNum = 0;
	fread(&bulletNum, sizeof(UINT), 1, fp);
	fread(&m_bullet, sizeof(vector<Bullet>), bulletNum, fp);
	//读取游戏信息-->|关卡|单人/双人模式标志|游戏速度
	fread(&GameLevel, sizeof(UINT), 1, fp);
	fread(&SingleMode, sizeof(bool), 1, fp);
	fread(&GameSpeed, sizeof(UINT), 1, fp);
	fclose(fp);
	//初始化游戏
	DrawMainScreen(g_MAP);		//打印主屏幕
	DrawSideScreen();			//打印副屏幕
	for (UINT i = 0; i < 6; i++)			//打印坦克
	{
		tankArr[i].Show();
	}
	for (UINT i = 0; i < bulletNum; i++)	//打印子弹
	{
		m_bullet[i].Show();
	}
}
/********************************************************
函数功能:加载存档,进行游戏
参数	:无
返回值	:无
*********************************************************/
void Game::LoadRecord()
{
	//清屏
	ClearFullScreen();
	//初始化随机数种子
	srand((unsigned int)(time(NULL)));
	ReadInfo();
	GameLoop();
}
/********************************************************
函数功能:游戏胜利进入下一关
参数	:无
返回值	:无
*********************************************************/
void Game::NextLevel()
{
	int timing = 0, ColorNo = 1;
	COLOR Color;
	GameLevel++;
	if (GameLevel <= MaxLevel)
		while (1)
		{
			if (timing++ % 30 == 0)        //30次打印一次
			{
				Color = SetColor(ColorNo);

				WriteChar(18, 20, "恭喜过关!", Color);     //主屏幕中心打印
				WriteChar(50, 15, "等待下关", Color);      //副屏幕打印
				WriteChar(45, 18, "请按回车键进入下一关卡!", Color);
				WriteChar(45, 19, "或按 Esc键退出游戏!", Color);
				if (++ColorNo == 8)
					ColorNo = 1;
			}
			if (GetAsyncKeyState(0xD) & 0x8000)  //回车键
			{
				WriteChar(50, 15, "正在进行", 0x01);			//清除副屏幕提示
				WriteChar(45, 18, "                       ", 0x00);
				WriteChar(45, 19, "                       ", 0x00);
				ClearMainScreen();			//主屏清屏函数
				GetMap(GameLevel);			//获取关卡地图
				InitGame();					//从本关重新开始
				break;
			}
			else if (GetAsyncKeyState(0x1B) & 0x8000)  //Esc键退出	
				exit(0);
			Sleep(20);
		}
	else   // 通关
		while (1)
		{
			if (timing++ % 5 == 0)
			{
				Color = SetColor(ColorNo);

				WriteChar(18, 20, "恭喜通过全部关卡!", Color);		//主屏幕中心打印
				WriteChar(50, 15, "全部通关", Color);				//副屏幕打印
				WriteChar(45, 18, "恭喜通过全部关卡!", Color);
				WriteChar(45, 19, "按 Esc键退出游戏!", Color);
				if (++ColorNo == 8)
					ColorNo = 1;
			}
			if (GetAsyncKeyState(0x1B) & 0x8000)  //Esc键退出	
				exit(0);
			Sleep(20);
		}
}

/********************************************************
函数功能:自定义地图副屏幕(地形选择)
参数	:无
返回值	:无
*********************************************************/
void Game::DrawUserSideScreen()
{
	for (int i = 0; i < 40; i++)
		for (int j = 0; j < 20; j++)
		{
			switch (UserSideScreen[i][j])
			{
			case 1:WriteChar(j + 40, i, "■", 0x0F); break;
			case 2:WriteChar(j + 40, i, "▄ 土墙", 0x04); break;        //红色的土墙
			case 3:WriteChar(j + 40, i, "■ 钢墙", 0x0F); break;        //白色的钢墙
			case 4:WriteChar(j + 40, i, "~ 河流", 0x1F); break;        //蓝色的河流
			case 5:WriteChar(j + 40, i, "▓ 草地", 0x02); break;        //绿色的草地
			case 6:WriteChar(j + 40, i, "▓ 沙地", 0x06); break;        //黄色的沙地
			case 7:WriteChar(j + 40, i, "  保存地图", 0x09); break;     
			case 8:WriteChar(j + 40, i, "**", 0x0D); break;					
			case 9:WriteChar(j + 40, i, "返回主菜单", 0x09); break;       
			}
		}
}
/********************************************************
函数功能:设置字体颜色
参数	:颜色编号
返回值	:颜色
*********************************************************/
COLOR Game::SetColor(int No)
{
	switch (No)
	{
	case 0:return 黑色;
	case 1:return 蓝色;
	case 2:return 绿色;
	case 3:return 浅绿色;
	case 4:return 红色;
	case 5:return 紫色;
	case 6:return 黄色;
	case 7:return 白色;
	case 8:return 灰色;
	case 9:return 淡蓝色;
	case 10:return 淡绿色;
	case 11:return 淡浅绿色;
	case 12:return 淡红色;
	case 13:return 淡紫色;
	case 14:return 淡黄色;
	default:return 亮白色;
	};
}

/********************************************************
函数功能:鼠标按键处理函数
参数	:无
返回值	:无
*********************************************************/
void Game::MouseOp()
{
	HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
	INPUT_RECORD stcRecord = { 0 };     //定义输入事件结构体 
	MOUSE_EVENT_RECORD mer;    //鼠标事件
	DWORD dwRead = 0;          //用于存储读取记录  
	COORD pos = { 0 };         //用于存储鼠标当前位置 
	//重设接受模式;避免受cls影响;
	SetConsoleMode(hStdIn, ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
	while (1)
	{
		//等待事件
		ReadConsoleInput(hStdIn, &stcRecord, 1, &dwRead);
		//处理事件
		if (stcRecord.EventType == MOUSE_EVENT)
		{
			mer = stcRecord.Event.MouseEvent;
			if (mer.dwButtonState == FROM_LEFT_1ST_BUTTON_PRESSED)
			{	//鼠标左击,绘制障碍物
				if (mer.dwMousePosition.X / 2 >= 18 && mer.dwMousePosition.X / 2 <= 20 && mer.dwMousePosition.Y >= 36)//鼠标在在老巢范围内
					continue;
				if (mer.dwMousePosition.X / 2 >= 1 && mer.dwMousePosition.X / 2 <= 38 &&
					mer.dwMousePosition.Y >= 1 && mer.dwMousePosition.Y <= 38)        //鼠标在地图范围内    
				{
					switch (obType)
					{
					case 土墙:WriteChar(mer.dwMousePosition.X / 2, mer.dwMousePosition.Y, "▄", 0x04); break;        //红色的土墙
					case 钢墙:WriteChar(mer.dwMousePosition.X / 2, mer.dwMousePosition.Y, "■", 0x0F); break;        //白色的钢墙
					case 河流:WriteChar(mer.dwMousePosition.X / 2, mer.dwMousePosition.Y, "~", 0x1F); break;        //蓝色的河流
					case 草地:WriteChar(mer.dwMousePosition.X / 2, mer.dwMousePosition.Y, "▓", 0x02); break;        //绿色的草地
					case 沙地:WriteChar(mer.dwMousePosition.X / 2, mer.dwMousePosition.Y, "▓", 0x06); break;        //黄色的沙地
					default:break;
					}
					//保存坐标
					obMap[mer.dwMousePosition.Y][mer.dwMousePosition.X / 2] = obType;
				}
			}
			if (mer.dwButtonState == RIGHTMOST_BUTTON_PRESSED)
			{	//鼠标右击,读取信息
				int obymin = 3, obymax = 5;
				if (mer.dwMousePosition.X >= 46 * 2 && mer.dwMousePosition.X <= 52 * 2 &&
					mer.dwMousePosition.Y >= obymin && mer.dwMousePosition.Y <= obymax)
				{
					obType = 土墙;
				}
				else if (mer.dwMousePosition.X >= 46 * 2 && mer.dwMousePosition.X <= 52 * 2 &&
					mer.dwMousePosition.Y >= obymin + 5 && mer.dwMousePosition.Y <= obymax + 5)
				{
					obType = 钢墙;
				}
				else if (mer.dwMousePosition.X >= 46 * 2 && mer.dwMousePosition.X <= 52 * 2 &&
					mer.dwMousePosition.Y >= obymin + 10 && mer.dwMousePosition.Y <= obymax + 10)
				{
					obType = 河流;
				}
				else if (mer.dwMousePosition.X >= 46 * 2 && mer.dwMousePosition.X <= 52 * 2 &&
					mer.dwMousePosition.Y >= obymin + 15 && mer.dwMousePosition.Y <= obymax + 15)
				{
					obType = 草地;
				}
				else if (mer.dwMousePosition.X >= 46 * 2 && mer.dwMousePosition.X <= 52 * 2 &&
					mer.dwMousePosition.Y >= obymin + 20 && mer.dwMousePosition.Y <= obymax + 20)
				{
					obType = 沙地;
				}
				else if (mer.dwMousePosition.X >= 46 * 2 && mer.dwMousePosition.X <= 52 * 2 &&
					mer.dwMousePosition.Y >= obymin + 25 && mer.dwMousePosition.Y <= obymax + 25)
				{	//保存地图
					for (int i = 1; i < 39; i++)
						for (int j = 1; j < 39; j++)
						{
							g_MAP[i][j] = obMap[i][j];
						}
				}
				else if (mer.dwMousePosition.X >= 46 * 2 && mer.dwMousePosition.X <= 52 * 2 &&
					mer.dwMousePosition.Y >= obymin + 30 && mer.dwMousePosition.Y <= obymax + 30)
				{	//返回主菜单
					GameMenu();
					return;
				}
				else
				{
					obType = 空地;
				}
			}
			if (mer.dwEventFlags == MOUSE_MOVED)
			{
				char szBuf[100] = { 0 };
				sprintf_s(szBuf, sizeof(szBuf), "x=%2d,y=%2d  ", mer.dwMousePosition.X / 2, mer.dwMousePosition.Y);
				SetConsoleTitleA(szBuf);
			}
		}
	}
}
/********************************************************
函数功能:游戏暂停处理
参数	:无
返回值	:无
*********************************************************/
void Game::GameStop()
{
	int counter = 0, No = 1;
	COLOR color;
	while (1)
	{
		color = SetColor(No);
		if (counter++ % 30 == 0)
		{
 			WriteChar(50, 15, "游戏暂停", color);
			WriteChar(45, 18, "请按回车键继续游戏!", color);
			WriteChar(45, 19, "或按 Esc键退出游戏!", color);
			WriteChar(45, 20, "或按右Shift保存游戏!", color);
			if (++No == 8)
				No = 1;
		}
		if (GetAsyncKeyState(VK_RETURN) & 0x8000)     //回车键
		{			
			WriteChar(50, 15, "正在进行", 0x01);
			WriteChar(45, 18, "                     ", 0x00);
			WriteChar(45, 19, "                     ", 0x00);
			WriteChar(45, 20, "                     ", 0x00);
			break;
		}
		else if (GetAsyncKeyState(VK_RSHIFT) & 0x8000) //右Shift键保存
		{
			SaveInfo();	
			Sleep(1000);
			exit(0);		//保存玩退出游戏
		}
		else if (GetAsyncKeyState(0x1B) & 0x8000) //Esc键退出	
			exit(0);
		Sleep(100);
	}
}

/********************************************************
第三部分:游戏初始化
		1.初始化随机数种子
		2.根据模式初始化坦克
		3.初始化子弹
		4.初始化地图
		5.初始化屏幕
*********************************************************/
void Game::InitGame()
{
	//清屏
	ClearFullScreen();
	//初始化随机数种子
	srand((unsigned int)(time(NULL)));
	//初始化坦克地图
	memset(g_TankMap, 0, sizeof(char) * 40 * 40);
	//初始化我的坦克复活次数
	tankA.InitPlayer(0);
	tankA.CreatePlayer();
	if (!SingleMode)			//双人游戏模式
	{
		tankB.InitPlayer(1);
		tankB.CreatePlayer();
	}
	//AI坦克初始化
	switch (GameLevel)		//根据关卡初始化AItank数量
	{
	case 1:AItank[0].remain_AI = 4; break;
	case 2:AItank[0].remain_AI = 8; break;
	case 3:AItank[0].remain_AI = 16; break;
	default:
		break;
	}			
	for(int i=0;i<3;i++)				
	{
		AItank[i].InitAI(2, i);
	}
	AItank[3].InitAI(3, 3);					//创建特殊坦克
	AItank[0].CreateAI();
	AItank[1].CreateAI();
	//子弹清空
	if(!m_bullet.empty())
		m_bullet.clear();
	DrawMainScreen(g_MAP);		//打印主屏幕
	DrawSideScreen();			//打印副屏幕	
}
/********************************************************
第四部分:游戏主循环
	1.游戏胜负检测
	2.移动子弹
	3.移动AI坦克
	4.AI < 4并且剩余AI数量>0, 创建新的AI, 一次只创建一个
	5.AI发射子弹
	6.接收键盘输入
	7.控制我方坦克
	8.我方坦克死亡, 复活(死亡瞬间复活, 不必放在主函数)
********************************************************/
void Game::GameLoop()
{
	while (1)							//游戏主循环
	{
		if (g_counter[游戏速度]++%GameSpeed == 0)
		{
			GameCheck();				//游戏胜负检测
			UpdateSideScreen();			//更新游戏信息			
			MoveBullet();				//移动子弹
			//移动AI坦克--四种AI坦克-->|锁定老巢|锁定玩家A|锁定玩家B|随机AI|			
			AItank[0].MoveToHome();
			AItank[1].MoveToTank(tankA.tankData.codself);
			if (!SingleMode)
			{
				AItank[2].MoveToTank(tankB.tankData.codself);
			}
			else
			{
				AItank[2].MoveRandom();
			}
			AItank[3].MoveToTank(tankA.tankData.codself);
			//AI<4并且剩余AI数量>0,创建新的AI,一次只创建一个
			for (int i = 0; i < 4; i++)
			{
				if (AItank[i].tankData.Alive == 0 && AItank[i].remain_AI > 0 &&
					g_counter[AI复活时间]++ % 90 == 0)
				{	//如果坦克不存活。计时,每次建立有间隔1800ms
					AItank[i].CreateAI();
				}
			}
			//AI发射子弹
			for (int i = 0; i < 4; i++)
			{
				if (AItank[i].tankData.Alive && AItank[i].tankData.BulletCD == 10)
				{
					CreatBullet(AItank[i]);
					AItank[i].tankData.BulletCD = 0;
				}
				else
				{
					AItank[i].tankData.BulletCD++;
				}
			}
			//接收键盘输入,控制我方坦克
			KeyboardA();
			KeyboardB();
			//我方坦克死亡,复活
			MytankRebirth();
		}
		Sleep(5);
	}
}
/********************************************************
函数功能:获取关卡地图
参数	:无
返回值	:无
*********************************************************/
void Game::GetMap(int level)
{
	int i, j;
	//int GameMap[3][40][40] =
	//我把数组删了,太长了
	for (i = 0; i < 40; i++)
		for (j = 0; j < 40; j++)
			g_MAP[i][j] = GameMap[level-1][i][j];	//获取关卡地图	
}
/********************************************************
函数功能:创建子弹
参数	:坦克对象
返回值	:无
*********************************************************/
void Game::CreatBullet(Tank &tank)
{
	Bullet bullet = {};
	//判断是AI子弹还是玩家子弹
	if (tank.tankData.Type > 1 && !(rand()%11))
	{
		bullet.CreateAI(tank);
	}		
	else
		bullet.Create(tank);
	//添加子弹,显示子弹
	m_bullet.push_back(bullet);
	int nIndex = m_bullet.size() - 1;	//添加子弹的下标
	if (nIndex < 0)
	{
		cout << "子弹添加失败!" << endl;
		system("pause");
		return;
	}
	if (m_bullet[nIndex].GoCheck())
	{
		m_bullet[nIndex].Show();
	}
	else
	{	//判断子弹碰撞
		if (m_bullet[nIndex].Hit(tankArr))
		{
			GameOver(1);				//1表示老家炸毁
		}
		if (m_bullet[nIndex].Data.Exist == 0 )		//子弹碰撞消失
		{
			m_bullet.pop_back();
		}
	}
}
/********************************************************
函数功能:移动子弹
参数	:
返回值	:无
*********************************************************/
void Game::MoveBullet()
{
	bool Ret = false;
	for (UINT i = 0; i < m_bullet.size(); i++)
	{		
		m_bullet[i].Clear();	//清除子弹
		m_bullet[i].Move();		//移动子弹
		if (m_bullet[i].GoCheck())	//检测障碍物
		{
			m_bullet[i].Show();		//正常通行
		}
		else
		{
			Ret = m_bullet[i].Hit(tankArr);	//碰撞处理
			if (Ret)
			{
				GameOver(1);	 //1表示老家炸毁
			}
			if (m_bullet[i].Data.Exist == 0 && m_bullet.size()>0)		//子弹碰撞消失
			{
				m_bullet.erase(m_bullet.begin() + i);
			}
		}
	}	
}

/********************************************************
函数功能:打印主屏幕地图
参数	:地图
返回值	:无
*********************************************************/
void Game::DrawMainScreen(char(*pMap)[40])
{
	for (int i = 0; i < 40; i++)
	{
		for (int j = 0; j < 40; j++)
		{
			switch (pMap[i][j])
			{
			case 边界:WriteChar(j, i, "■", 0x0F); break;        //白色的边界				
			case 土墙:WriteChar(j, i, "▄", 0x04); break;        //红色的土墙
			case 钢墙:WriteChar(j, i, "■", 0x0F); break;        //白色的钢墙
			case 河流:WriteChar(j, i, "~", 0x1F); break;        //蓝色的河流
			case 草地:WriteChar(j, i, "▓", 0x02); break;        //绿色的草地
			case 沙地:WriteChar(j, i, "▓", 0x06); break;        //黄色的沙地
			default: break;
			}
		}
		WriteChar(18, 36, "◣★◢", 0x0F);                     //老家
		WriteChar(18, 37, "███", 0x0F);                     //老家
		WriteChar(18, 38, "◢█◣", 0x0F);                     //老家
	}
}
/********************************************************
函数功能:清除主屏幕
参数	:无
返回值	:无
*********************************************************/
void Game::ClearMainScreen()
{
	for (int i = 1; i < 39; i++)
	{
		WriteChar(1, i, "                                                \
                           ", 0x00);
	}
}
/********************************************************
函数功能:打印副屏幕
参数	:无
返回值	:无
*********************************************************/
void Game::DrawSideScreen()
{
	for (int i = 0; i < 40; i++)
	{
		for (int j = 40; j < 60; j++)
		{
			if (i == 0 || i == 39 || j == 59)
				WriteChar(j, i, "█", 0x0F);
		}
	}
	int basey = 2;
	WriteChar(47, basey, "第      关", 0x0F);
	WriteChar(46, basey + 3, "分  数: ", 0x0F);
	WriteChar(46, basey + 5, "生  命: ", 0x0F);
	WriteChar(46, basey + 7, "生  命: ", 0x0F);
	WriteChar(43, basey + 9, "剩余敌方坦克: ", 0x0F);
	WriteChar(43, basey + 11, "当前游戏速度: ", 0x0F);
	WriteChar(43, basey + 13, "当前游戏状态: ", 0x0F);
	WriteChar(40, basey + 15, "════════════════════", 0x01);
	WriteChar(48, basey + 21, "帮  助", 0x02);
	WriteChar(43, basey + 23, "坦克A: 方向键  w a s d ", 0x02);
	WriteChar(43, basey + 25, "     : 射击键  j 键 ", 0x02);
	WriteChar(43, basey + 27, "坦克B: 方向键  ←↑→↓", 0x02);
	WriteChar(43, basey + 29, "     : 射击键  5 键 ", 0x02);
	WriteChar(45, basey + 31, "+ - 调整游戏速度", 0x02);
	WriteChar(45, basey + 33, "空格键 暂停游戏", 0x02);
	WriteChar(45, basey + 35, "Esc键  退出游戏", 0x02);		
}
/********************************************************
函数功能:更新游戏信息
参数1	:无
返回值	:无
*********************************************************/
void Game::UpdateSideScreen()
{
	int basey = 2;
	//清除上一次信息
	WriteChar(49, basey, "  ", 黑色);
	WriteChar(50, basey + 3, "   ", 黑色);
	WriteChar(50, basey + 5, "   ", 黑色);
	WriteChar(50, basey + 7, "   ", 黑色);
	WriteChar(50, basey + 9, "   ", 黑色);
	WriteChar(50, basey + 11, "   ", 黑色);
	//更新	
	WriteChar(49, basey, "", 黄色);
	printf("%d", GameLevel);				//游戏关卡
	WriteChar(50, basey + 3, "", 红色);
	printf("%d", tankA.tankData.Score + tankB.tankData.Score);//更新游戏分数
	WriteChar(50, basey + 5, "", 绿色);
	printf("%d", tankA.tankData.Revive);	//更新玩家A生命值(初始3条命)
	WriteChar(50, basey + 7, "", 绿色);
	printf("%d", tankB.tankData.Revive);	//更新玩家B生命值
	WriteChar(50, basey + 9, "", 红色);
	printf("%d", tankA.remain_AI);			//剩余AI数
	WriteChar(50, basey + 11, "", 黄色);
	printf("%d", 21-GameSpeed);				//游戏速度
	WriteChar(50, basey + 13, "正在进行", 黄色);	//游戏状态	
}

/********************************************************
函数功能:设置窗口信息
参数1	:窗口名称
参数2	:窗口宽度
参数3	:窗口高度
返回值	:成功返回真
*********************************************************/
bool Game::SetWindowSize(TCHAR* pszWindowTitle, short x, short y)
{
	HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
	HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTitle(pszWindowTitle);                  //设置窗口名称
	//获取最大控制台窗口大小
	COORD pos = GetLargestConsoleWindowSize(hStdOut);
	COORD BuffSize = { pos.X + 1,pos.Y + 1 };
	//设置控制台缓冲区大小
	if (!SetConsoleScreenBufferSize(hStdOut, BuffSize))
	{	//设置失败
		printf("buffer err(%d,%d)%d\n", BuffSize.X, BuffSize.Y, GetLastError());
		return false;
	}
	SMALL_RECT srctWindow = { 0,0,x,y };             //矩形,保存左上角坐标和右下角坐标
	if (!SetConsoleWindowInfo(hStdOut, true, &srctWindow))
	{	//设置控制台窗口大小失败
		printf("size err%d\n", GetLastError());
		return false;
	}
	//设置控制台缓冲区大小
	COORD Buff = { x + 1,y + 1 };
	if (!SetConsoleScreenBufferSize(hStdOut, Buff))
	{	//设置失败
		printf("buffer err(%d,%d)%d\n", Buff.X, Buff.Y, GetLastError());
		return false;
	}
	return true;
}

/********************************************************
函数功能:游戏结束处理
参数	:0:我方死光1:老家炸毁
返回值	:无
*********************************************************/
void Game::GameOver(bool home)
{
	int timing = 0, ColorNo = 1;
	COLOR Color;
	while (1)
	{
		if (timing++ % 30 == 0)        //30次打印一次
		{
			Color = SetColor(ColorNo);
			if(home)
			{
				WriteChar(18, 20, "老家炸毁!", Color);     //主屏幕中心打印
				
			}
			WriteChar(18, 21, "游戏结束!", Color);         //主屏幕中心打印
			WriteChar(50, 15, "游戏结束!", Color);         //副屏幕打印
			WriteChar(45, 18, "请按回车键继续游戏!", Color); 
			WriteChar(45, 19, "或按 Esc键退出游戏!", Color);
			if (++ColorNo == 8)
				ColorNo = 1;
		}
		if (GetAsyncKeyState(0xD) & 0x8000)  //回车键
		{
			ClearMainScreen();			//主屏清屏函数
			GetMap(GameLevel);			//获取关卡地图
			InitGame();					//从本关重新开始
			break;
		}
		if (GetAsyncKeyState(0x1B) & 0x8000)  //Esc键退出	
			exit(0);
		Sleep(20);
	}
}

/********************************************************
函数功能:用户自定义地图
参数1	:无
返回值	:无
*********************************************************/
void Game::UserMap()
{
	ClearFullScreen();				//清屏
	DrawMainScreen(obMap);			//打印主屏幕
	DrawUserSideScreen();			//打印副屏幕
	MouseOp();						//鼠标操作处理
}
/********************************************************
函数功能:游戏胜负检测
参数	:无
返回值	:无
*********************************************************/
void Game::GameCheck()
{
	//胜利条件:AI坦克死光
	//AItank死光,胜利
	if (tankA.remain_AI == 0 && AItank[0].tankData.Alive == 0 && AItank[1].tankData.Alive == 0 &&
		AItank[2].tankData.Alive == 0 && AItank[3].tankData.Alive == 0)
	{
		NextLevel();
	}
	//胜利处理函数
	//失败条件:我方坦克死光或者老家炸毁(老家炸毁瞬间调用GameOver函数,在这不做判断)
	if (SingleMode)		//单人模式	
	{
		if (tankA.tankData.Revive == 0 && tankA.tankData.Alive == 0)
			GameOver(0);		
	}
	else
	{
		if (tankA.tankData.Revive == 0 && tankA.tankData.Alive == 0 && 
			tankB.tankData.Revive == 0 && tankB.tankData.Alive == 0)
			GameOver(0);	
	}		
}
/********************************************************
函数功能:清屏函数(全屏)
参数	:无
返回值	:无
*********************************************************/
void Game::ClearFullScreen()
{
	for (int i = 0; i < 40; i++)
	{
		WriteChar(0, i, "                                                           \
                                                            ", 0x00);
	}
}
/********************************************************
函数功能:玩家坦克复活
参数	:无
返回值	:无
*********************************************************/
void Game::MytankRebirth()
{
	if (SingleMode)		//单人模式
	{
		while (1)
		{
			if (tankA.tankData.Alive == 0 && g_counter[玩家A复活时间]++ % 10 == 0)		//复活时间1s
				tankA.CreatePlayer();
			else
				return;
			Sleep(100);
		}
	}
	else
	{		//双人模式
		while (1)
		{
			if (tankA.tankData.Alive == 0 && g_counter[玩家A复活时间]++ % 10 == 0)		//复活时间1s
				tankA.CreatePlayer();
			else if (tankB.tankData.Alive == 0 && g_counter[玩家B复活时间]++ % 10 == 0)
				tankB.CreatePlayer();
			else
				return;
			Sleep(100);
		}
	}
}

c++人机猜拳需求分析》 一、游戏规则说明 本次开发的程序将是一个简单的人机互动的游戏——“石头剪刀布”。其规则如下: 1. 玩家可以选择出“石头”、“剪刀” 或 “布”,分别对应数字 1、2 和 3; 2. 计算机将会随机选择一种手势; 3. 根据选定的手势,计算机会对玩家的选择作出判断,并给出结果:“胜”、“败”或“平手”。 二、功能模块设计 基于上述规则,我们需要设计三个主要功能模块: 1. **用户界面模块**:负责接收用户的输入并展示游戏结果。 2. **计算机生成动作模块**:负责模拟电脑随机选择的动作。 3. **逻辑判断模块**:处理玩家与电脑之间的比较,并提供最终的游戏结果。 三、技术选型及语言特性应用 考虑到C++的强大特性和性能优势,本项目计划使用C++作为开发语言。以下是几个关键的技术点和特性: 1. **控制流程**:利用分支结构(如 if-else 语句)实现条件判断,确定输赢状态。 2. **数据结构**:可以采用简单的数组或者枚举类型来表示三种手势及其对应的编号,便于后续的操作。 3. **随机数生成**:使用 C++ 内置的 `<random>` 库中的 `std::mt19937` 类进行随机数生成,用于模拟电脑的选择。 4. **异常处理**:虽然这个游戏相对简单,但在实际应用中,适当的错误处理会使得程序更加健壮。 四、注意事项及优化方向 为了提升用户体验和程序的稳定性,在设计过程中应考虑以下几个方面: 1. **异常处理**:对于非法输入或特殊情况,需要有合理的错误提示机制。 2. **用户交互体验**:优化输入反馈,增强游戏趣味性,例如增加声音效果或动画等元素。 3. **代码复用和模块化**:尽量减少重复代码,通过封装功能到独立的函数或类中,提高代码的可维护性和可读性。 五、总结 通过以上的需求分析,我们可以看到构建一款基本的人机互动猜拳游戏涉及用户界面、算法逻辑以及系统稳定性等多个层面的设计工作。C++ 的强大功能为其提供了良好的基础环境,能够有效地实现预期的功能,并在实现过程中注重用户体验和代码质量。随着项目的推进,我们还可以进一步探索更丰富的游戏玩法和视觉效果,使其成为一个有趣且互动性强的娱乐工具。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值