![]() |
#2
op1232022-07-12 16:29
#include <stdio.h>
#include <windows.h> #include <time.h> #define UP 1 #define DOWN 2 #define LEFT 3 #define RIGHT 4 #define MAX_LEVEL 8 #define BULLET_NUM 20 #define MAX_LIFE 4 //程序中未写入函数参数表中且未说明的变量只有map二维数组,level_info数组和level /* 此程序中涉及的x,y类的坐标值,分为以下两种: 假坐标:这里的坐标指的是以一个■长度为单位的坐标,而不是真正的coord坐标 (用于map数组的坐标) 真坐标:头文件自带的坐标结构coord中的坐标(也可以说是控制台里的真正坐标值) 区别:纵坐标y两值一致,假横坐标x值与真正coord横坐标(真坐标)关系是 x * 2 = coord 横坐标 coord横坐标既指GoTo函数中的x参数,因为本程序游戏界面以一个■长度为基本单位, 可以说涉及的coord横坐标全是偶数。既假坐标要变真坐标(变真坐标才能发挥真正作用),横坐标须乘以2 */ typedef struct //这里的出现次序指的是一个AI_tank变量中的次序,游戏共有四个AI_tank变量 { //∵设定每个AI_tank每种特殊坦克只出现一次 ∴fast_tank & firm_tank 最多出现次数不超过1 int fast_tank_order; //fast_tank出现的次序(在第fast_tank_order次复活出现,从第0次开始),且每个AI_tank只出现一次 int firm_tank_order; //firm_tank出现的次序,同上 } LevInfo; //关卡信息(准确说是该关出现的坦克信息) LevInfo level_info [MAX_LEVEL] = {{-1,-1},{3,-1},{-1,3},{2,3},{2,3},{2,3},{2,3},{2,3}}; //初始化,-1代表没有该类型坦克 typedef struct //子弹结构体 { int x,y; //子弹坐标,假坐标 int direction; //子弹方向变量 bool exist; //子弹存在与否的变量,1为存在,0不存在 bool initial; //子弹是否处于建立初状态的值,1为处于建立初状态,0为处于非建立初状态 bool my; //区分AI子弹与玩家子弹的标记,0为AI子弹,1为玩家(我的)子弹 } Bullet; Bullet bullet [BULLET_NUM]; //考虑到地图上不太可能同时存在20颗子弹,所以数组元素设置20个 typedef struct //坦克结构体 { int x,y; //坦克中心坐标 int direction; //坦克方向 int color; //颜色参方向数,1到6分别代表不同颜色,具体在PrintTank函数定义有说明 int model; //坦克图案模型,值为1,2,3,分别代表不同的坦克图案,0为我的坦克图案,AI不能使用 int stop; //只能是AI坦克使用的参数,非0代表坦克停止走动,0为可以走动 int revive; //坦克复活次数 int num; //AI坦克编号(固定值,为常量,初始化函数中定下)0~3 int CD; //发射子弹冷却计时 bool my; //是否敌方坦克参数,我的坦克此参数为1,为常量 bool alive; //存活为1,不存活为0 } Tank; Tank AI_tank[4] , my_tank; //my_tank为我的坦克,Ai_tank 代表AI坦克 //∵所有的函数都有可能对全局变量map进行读写(改变), //∴函数中不另说明是否会对全局变量map读写 //基本操作与游戏辅助函数 void GoToxy(int x,int y); //光标移动 void HideCursor(); //隐藏光标 void keyboard (); //接受键盘输入 void Initialize(); //初始化(含有对多个数据的读写) void Stop(); //暂停 void Getmap(); //地图数据存放与获取 void Frame (); //打印游戏主体框架 void PrintMap(); //打印地图(地图既地图障碍物)(含对level的读取) void SideScreen (); //副屏幕打印 void GameCheak(); //检测游戏输赢 void GameOver( bool home ); //游戏结束 void ClearMainScreen(); //主屏幕清屏函数∵system("cls")后打印框架有一定几率造成框架上移一行的错误∴单独编写清屏函数 void ColorChoose(int color); //颜色选择函数 void NextLevel(); //下一关(含有对level全局变量的读写) //子弹部分 void BuildAIBullet(Tank *tank); //AI坦克发射子弹(含有对my_tank的读取,只读取了my_tank坐标) void BuildBullet (Tank tank); //子弹发射(建立)(人机共用)(含全局变量bullet的修改)我的坦克发射子弹直接调用该函数,AI通过AIshoot间接调用 void BulletFly (Bullet bullet[BULLET_NUM]); //子弹移动和打击(人机共用), void BulletHit (Bullet* bullet); //子弹碰撞(人机共用)(含Tank全局变量的修改),只通过BulletFly调用,子弹间的碰撞不在本函数,子弹间碰撞已在BulletShoot中检测并处理 void PrintBullet (int x,int y,int T); //打印子弹(人机共用) void ClearBullet (int x,int y,int T); //清除子弹(人机共用) int BulletCheak (int x,int y); //判断子弹前方情况(人机共用) //坦克部分 void BuildAITank (int* position, Tank* AI_tank); //建立AI坦克 void BuildMyTank (Tank* my_tank); //建立我的坦克 void MoveAITank (Tank* AI_tank); //AI坦克移动 void MoveMyTank (int turn); //我的坦克移动,只通过keyboard函数调用,既键盘控制 void ClearTank (int x,int y); //清除坦克(人机共用) void PrintTank (Tank tank); //打印坦克(人机共用) bool TankCheak (Tank tank,int direction); //检测坦克dirtection方向的障碍物,返值1阻碍,0 畅通 int AIPositionCheak (int position); //检测AI坦克建立位置是否有障碍物AIPositionCheak //DWORD WINAPI InputX(LPVOID lpParameter); //声明线程函数,用于检查X键输入并设置X键的输入冷却时间 //注意map数组应是纵坐标在前,横坐标在后,既map[y][x],(∵数组行长度在前,列长度在后) //map里的值: 个位数的值为地图方块部分,百位数的值为坦克,子弹在map上没有值(子弹仅仅是一个假坐标) //map里的值: 0为可通过陆地,1为红砖,2黄砖,5为水,100~103为敌方坦克,200为我的坦克, //全局变量 int map[41][41]; //地图二维数组 int key_x; // X键是否被“读入”的变量,也是子弹是否可以发射的变, int bul_num; //子弹编号 int position; //位置计数,对应AI坦克生成位置,-1为左位置,0为中间,1为右,2为我的坦克位置 int speed=7; //游戏速度,调整用 int level=1; //游戏关卡数 int score=0; //游戏分数 int remain_enemy; //剩余敌人(未出现的敌人) char* tank_figure[4][3][4]= { { {"◢┃◣", "◢━◣", "◢┳◣", "◢┳◣"}, {"┣●┫", "┣●┫", "━●┃", "┃●━"}, {"◥━◤", "◥┃◤", "◥┻◤", "◥┻◤"} }, { {"┏┃┓", "┏┳┓", "┏┳┓", "┏┳┓"}, {"┣●┫", "┣●┫", "━●┫", "┣●━"}, {"┗┻┛", "┗┃┛", "┗┻┛", "┗┻┛"} }, { {"┏┃┓", "◢━◣", "┏┳◣", "◢┳┓"}, {"┣●┫", "┣●┫", "━●┃", "┃●━"}, {"◥━◤", "┗┃┛", "┗┻◤", "◥┻┛"} }, { {"╔┃╗", "╔╦╗", "╔╦╗", "╔╦╗"}, {"╠█╣", "╠█╣", "━█╣", "╠█━"}, {"╚╩╝", "╚┃╝", "╚╩╝", "╚╩╝"} } }; int main () //主函数 { int i; unsigned int interval[12]={1,1,1,1,1,1,1,1,1,1,1,1} ; //间隔计数器数组,用于控制速度 srand(time(NULL)); //设置随机数种子(若不设置种子而调用rand会使每次运行的随机数序列一致)随机数序列指:如首次调用rand得到1,第二次得2,第三次3,则此次随机数序列为1,2,3 HideCursor(); //隐藏光标 system("mode con cols=150 lines=90"); //控制窗口大小 Frame (); //打印游戏主体框架 Initialize(); //初始化,全局变量level初值便是1 // HANDLE h1 , h2 ; //定义句柄变量 for(;;) { if(interval[0]++%speed==0) //速度调整用,假设interval[0]为a, 语句意为 a % 2==0,a=a+1; { GameCheak(); //游戏胜负检测 BulletFly ( bullet ); for(i=0 ; i<=3 ; i++) //AI坦克移动循环 { if(AI_tank[i].model==2 && interval[i+1]++%2==0) //四个坦克中的快速坦克单独使用计数器1,2,3,4 MoveAITank( & AI_tank[i]); if(AI_tank[i].model!=2 && interval[i+5]++%3==0) //四个坦克中的慢速坦克单独使用计数器5,6,7,8 MoveAITank( & AI_tank[i]); } for(i=0;i<=3;i++) //建立AI坦克部分 if(AI_tank[i].alive==0 && AI_tank[i].revive<4 && interval[9]++%90==0) //一个敌方坦克每局只有4条命 { //如果坦克不存活。计时,每次建立有间隔 1750 ms BuildAITank( &position, & AI_tank[i] ); //建立AI坦克(复活) break; //每次循环只建立一个坦克 } for(i=0;i<=3;i++) if(AI_tank[i].alive) BuildAIBullet(&AI_tank[i]); //AIshoot自带int自增计数CD,不使用main中的CD interval if(my_tank.alive && interval[10]++%2==0 ) keyboard (); if(my_tank.alive==0 && interval[11]++%30==0 && my_tank.revive < MAX_LIFE) BuildMyTank( &my_tank ); } Sleep(5); } return 0; } /*//这里的多线程暂时不用 //x键用于子弹发射,x键的冷却时间不能和上下左右一同设置,那样就太快了 DWORD WINAPI InputX(LPVOID lpParameter) //如果不用多线程运行,那么在x键冷却时间内程序会因Sleep将会挂起,暂停运行 { //因为只有一个变量改变,而且变量改变先后顺序是显而易见的,所以不必设置缓冲区 for(;;) { if(GetAsyncKeyState( 88 )& 0x8000) //88为x键键值,当摁下x并且x键处于可输入状态 { key_x=1; // X键是否允许被“读入”的变量,也是子弹是否可以发射的变量 Sleep(600); // 子线程Sleep中,x就不能被"读入",主线程每操作完一次子弹发射,key_x会归零 } Sleep(10); } return 0; }*/ void keyboard () { // kbhit() getch() 用法可用但是不好用 /* 函数功能:该函数判断在此函数被调用时,某个键是处于UP状态还是处于DOWN状态,及前次调用GetAsyncKeyState函数后, 是否按过此键.如果返回值的最高位被置位,那么该键处于DOWN状态;如果最低位被置位,那么在前一次调用此函数后,此键被按过, 否则表示该键没被按过. 这里GetAsyncKeyState比 kbhit() + getch() 好用,操作更顺畅. GetAsyncKeyState的返回值表示两个内容, 一个是最高位bit的值,代表这个键是否被按下。一个是最低位bit的值,代表上次调用GetAsyncKeyState后,这个键是否被按下。 &为与操作,&0x8000就是判断这个返回值的高位字节。如果high-order bit是1,则是按下状态,否则是弹起状态,为0 */ int count=0; if (GetAsyncKeyState(VK_UP)& 0x8000) MoveMyTank( UP ); else if (GetAsyncKeyState(VK_DOWN)& 0x8000) MoveMyTank( DOWN ); else if (GetAsyncKeyState(VK_LEFT)& 0x8000) MoveMyTank( LEFT ); else if (GetAsyncKeyState(VK_RIGHT)& 0x8000) MoveMyTank( RIGHT ); else if (GetAsyncKeyState( 0x1B )& 0x8000) // Esc键 exit(0); //退出程序函数 else if (GetAsyncKeyState( 0x20 )& 0x8000) //空格 Stop(); else if (count++%7==0) //这里添加计数器是为了防止按键粘连不能达到微调效果 { if (speed>1 && GetAsyncKeyState( 0x6B )& 0x8000) // +键 { speed--; GoToxy(102,11); //在副屏幕打印出当前速度 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_INTENSITY|FOREGROUND_BLUE|FOREGROUND_RED); printf("%d ",21-speed); //副屏幕显示的速度为1~10 } else if (speed<20 && GetAsyncKeyState( 0x6D )& 0x8000) // - 键 { speed++; GoToxy(102,11); //在副屏幕打印出当前速度 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_INTENSITY|FOREGROUND_BLUE|FOREGROUND_RED); printf("%d ",21-speed); //副屏幕显示的速度为1~10 } } if(my_tank.CD==7) { if(GetAsyncKeyState( 88 )& 0x8000) { BuildBullet(my_tank); my_tank.CD=0; } } else my_tank.CD++; } void BuildAIBullet(Tank *tank) //AI子弹发射(建立)含有对my_tank的读取 { if(tank->CD==15) { if(!(rand()%11)) //冷却结束后在随后的每个游戏周期中有10分之一的可能发射子弹 { BuildBullet(*tank); tank->CD=0; } } else tank->CD++; if(tank->CD >= 14) //AI强化部分,在冷却到达一定范围即可使用 { if(tank->y==38 ) //如果坦克在底部(这个最优先) { if(tank->x < 20) //在老家左边 { if(tank->direction==RIGHT) //坦克方向朝左 { BuildBullet(*tank); //发射子弹 tank->CD=0; } } else //在老家右边 if(tank->direction==LEFT) //坦克方向朝右 { BuildBullet(*tank); //发射子弹 tank->CD=0; } } else if(tank->x==my_tank.x+1 || tank->x==my_tank.x || tank->x==my_tank.x-1) //AI坦克在纵向上"炮口"对准我的坦克 { if(tank->direction==DOWN && my_tank.y > tank->y || tank->direction==UP && my_tank.y < tank->y) { //若是AI朝下并且我的坦克在AI坦克下方(数值大的在下面)或者AI朝上我的坦克在AI上方 int big=my_tank.y , smal=tank->y , i; if(my_tank.y < tank->y) { big=tank->y; smal=my_tank.y; } for(i=smal+2;i<=big-2;i++) //判断AI炮口的直线上两坦克间有无障碍 if(map[i][tank->x]!=0 || map[i][tank->x]!=5) //若有障碍 break; if(i==big-1) //若i走到big-1说明无障碍 { BuildBullet(*tank); //则发射子弹 tank->CD=0; } } } else if(tank->y==my_tank.y+1 || tank->y==my_tank.y || tank->y==my_tank.y-1) //AI坦克在横向上"炮口"对准我的坦克 { if(tank->direction==RIGHT && my_tank.x > tank->x || tank->direction==LEFT && my_tank.x < tank->x) { //若是AI朝右并且我的坦克在AI坦克右方(数值大的在下面)或者AI朝左我的坦克在AI左方 int big=my_tank.y , smal=tank->y , i; if(my_tank.x < tank->x) { big=tank->x; smal=my_tank.x; } for(i=smal+2;i<=big-2;i++) //判断AI炮口的直线上两坦克间有无障碍 if(map[tank->y][i]!=0 || map[tank->y][i]!=5) //若有障碍 break; if(i==big-1) //若i走到big-1说明无障碍 { BuildBullet(*tank); //则发射子弹 tank->CD=0; } } } } } void BuildBullet(Tank tank) //子弹发射(建立),传入结构体Tank,这里包含改变了全局变量结构体bullet { //∵实现方式为顺序循环建立子弹,每次调用改变的bullet数组元素都不同 switch(tank.direction) //∴为了方便,不将bullet放入参数,bullet作为全局变量使用 { case UP : bullet [bul_num].x = tank.x; bullet [bul_num].y = tank.y-2; bullet [bul_num].direction=1; break; case DOWN : bullet [bul_num].x = tank.x; bullet [bul_num].y = tank.y+2; bullet [bul_num].direction=2; break; case LEFT : bullet [bul_num].x = tank.x-2; bullet [bul_num].y = tank.y; bullet [bul_num].direction=3; break; case RIGHT : bullet [bul_num].x = tank.x+2; bullet [bul_num].y = tank.y; bullet [bul_num].direction=4; break; } bullet [bul_num].exist = 1; //子弹被建立,此值为1则此子弹存在 bullet [bul_num].initial = 1; //子弹处于初建立状态 bullet [bul_num].my=tank.my; //如果是我的坦克发射的子弹bullet.my=1,否则为0 bul_num++; if(bul_num==BULLET_NUM) //如果子弹编号增长到20号,那么重头开始编号 bul_num=0; //考虑到地图上不可能同时存在20颗子弹,所以数组元素设置20个 } void BulletFly(Bullet bullet[BULLET_NUM]) //子弹移动和打击 { //含有全局变量Bullet的改变 for(int i =0; i<BULLET_NUM;i++) { if(bullet [i].exist) //如果子弹存在 { if(bullet [i].initial==0) //如果子弹不是初建立的 { if(map[bullet[i].y] [bullet[i].x]==0 || map[bullet[i].y] [bullet[i].x]==5) //如果子弹坐标当前位置无障碍 ClearBullet( bullet[i].x , bullet[i].y , BulletCheak(bullet[i].x , bullet[i].y )); //抹除子弹图形 switch(bullet [i].direction) //然后子弹坐标变化(子弹变到下一个坐标) { case UP :(bullet [i].y)--;break; case DOWN :(bullet [i].y)++;break; case LEFT :(bullet [i].x)--;break; case RIGHT :(bullet [i].x)++;break; } } int collide = BulletCheak ( bullet [i].x , bullet [i].y ); //判断子弹当前位置情况,判断子弹是否碰撞,是否位于水面上。 if( collide ) //如果检测到当前子弹坐标无障碍(无碰撞)(包括在地面上与在水面上) PrintBullet( bullet[i].x , bullet[i].y , collide); //则打印子弹,若有碰撞则不打印 else BulletHit( & bullet [i] ); //若有碰撞则执行子弹碰撞函数 if(bullet [i].initial) //若子弹初建立,则把初建立标记去除 bullet [i].initial = 0; for(int j=0; j< BULLET_NUM ; j++) //子弹间的碰撞判断,若是我方子弹和敌方子弹碰撞则都删除,若为两敌方子弹则无视 if(bullet [j].exist && j!=i && (bullet[i].my || bullet[j].my) && bullet[i].x==bullet[j].x && bullet[i].y==bullet[j].y) { //同样的两颗我方子弹不可能产生碰撞 bullet [j].exist=0; bullet [i].exist=0; ClearBullet( bullet[j].x , bullet[j].y , BulletCheak(bullet[j].x , bullet[j].y )); //抹除j子弹图形,子弹i图形已被抹除 break; } } } } void BulletHit(Bullet* bullet) //含有Tank全局变量的修改,子弹间的碰撞不在本函数,子弹间碰撞已在BulletShoot中检测并处理 { //∵每次打中的坦克都不一样,不可能把所有坦克放在参数表中 int x=bullet->x; //∴这里的Tank使用全局变量 int y=bullet->y; //这里传入的值是子弹坐标,这两个值不需要改变 int i; if(map[y][x]==1 || map[y][x]==2) //子弹碰到砖块 { if(bullet->direction==UP || bullet->direction==DOWN) //如果子弹是纵向的 for(i = -1 ; i<=1 ; i++) if(map[y][x+i]==1 || map[y][x+i]==2) //如果子弹打中砖块两旁为砖块,则删除砖,若不是(一旁为坦克或其他地形)则忽略 { map[y][x+i]=0; //砖块碎 GoToxy(2*x+2*i,y); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_RED); //背景黑色 printf(" "); } if(bullet->direction==LEFT || bullet->direction==RIGHT) //若子弹是横向的 (与子弹纵向实现同理) for(i = -1 ; i<=1 ; i++) if(map[y+i][x]==1 || map[y+i][x]==2) { map[y+i][x]=0; GoToxy(2*x,y+i); SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_RED); //背景黑色 printf(" "); } bullet->exist=0; //这颗子弹已经不存在了 } else if(map[y][x]==4 || map[y][x]==6 ) //子弹碰到边框或者不可摧毁方块 bullet->exist=0; else if(bullet->my && map[y][x]>=100 && map[y][x]<104 ) //若我的子弹碰到了敌方坦克 { int num = map[y][x]%100; //map[y][x]%100 等同于 tank.num ,可通过map值读取该坦克信息 if(AI_tank[num].model==3 && AI_tank[num].color==2) //若为firm tank,且color==2。该坦克为绿色,表明没有受到伤害 AI_tank[num].color=3; //则变成黄色,color=3为黄色 else if (AI_tank[num].model==3 && AI_tank[num].color==3) AI_tank[num].color=4; //4为红色 else //其他类型的坦克或者firm tank为红色的情况 { AI_tank[num].alive=0; ClearTank(AI_tank[num].x , AI_tank[num].y); //清除该坦克 } bullet->exist=0; score+=100; GoToxy(102,5); //在副屏幕上打印出分数 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),FOREGROUND_INTENSITY|FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE); |
帮忙一下吧