// 坦克大战.cpp : Defines the entry point for the console application.
//思路简介:坦克大战采用的是面向对象的编程思想,其中离不开对类及线程的理解和使用
#include "stdafx.h"
#include<windows.h>
#include <process.h>
#include"pic_.h"
#define uchar unsigned char //坐标类型 屏幕上所有的坐标均为非负
#define uint unsigned int
//----全局变量
#define TK 20//最大坦克数量
HANDLE hThread[TK];//多线程句柄
//------------
uint Base_coefficient=0;//当前基址系数
uchar vanish=0;//坦克消失消息,用来改变基址
//----pic请求变量,当每一个类装好pic之后,则改变对应变量值,发出请求,等到请求满后便调用picture(),显示一帧图片
uchar tk_pic[TK]={0};
uchar bt_pic[TK]={0};
//------------
class BULLET;
class TANK//坦克总类
{
public:
uint BASE; //内部基址
uint base_coefficient; //外部基址系数
uchar x,y;//坦克中心位置
uchar a,b;//a是炮筒的九宫格代码,b是空格的九宫格代码
uchar tankcode[8];//借助坦克代码(九宫格旋转公式),控制坦克形状(转向)
TANK(uchar tx,uchar ty)
{
x=tx;y=ty;
a=2;b=8;
base_coefficient=Base_coefficient++;//将全局量转化为类体私有量,再将该系数加一抛出
BASE=9*base_coefficient;
shape();
}//构造函数 给出坦克中心点,绘出坦克图形
friend BULLET;//将子弹类声明为坦克类的友元类,这样的话友元类BULLET中的所有函数都是TANK的友元函数,可以访问TANK的所有公共成员
void shape(void)// shape-塑形
{
uchar i,j;
tankcode[0]=a;
for(i=1,j=1;i<10;i++)//i=1,2,...,9
{
if(i==a||i==b) continue;
tankcode[j++]=i;
}
}
void tank_vanish(void)
{
uint i=base_coefficient;
//for()//修改其后tank的基址
//{
// }
Base_coefficient--;
//vanish=1;//发送消影消息
}
void tank_dis(uchar f)
{
uint i;
//---------------------
//if(vanish&&BASE>=9) {BASE-=9;vanish=0;}
//if(vanish&&BASE==0) {vanish=0;}
p0.pnum=BASE;
switch(tankcode[0])//炮筒
{
case 2: p0.x[BASE]=x-1;p0.y[BASE]=y;p0.z[BASE]=2;break;
case 4: p0.x[BASE]=x;p0.y[BASE]=y-1;p0.z[BASE]=3;break;
case 6: p0.x[BASE]=x;p0.y[BASE]=y+1;p0.z[BASE]=3;break;
case 8: p0.x[BASE]=x+1;p0.y[BASE]=y;p0.z[BASE]=2;break;
}
p0.pnum++;
for(i=1;i<8;i++)
{
switch(tankcode[i])
{
case 1: p0.x[BASE+i]=x-1;p0.y[BASE+i]=y-1;break;
case 2: p0.x[BASE+i]=x-1;p0.y[BASE+i]=y;break;
case 3: p0.x[BASE+i]=x-1;p0.y[BASE+i]=y+1;break;
case 4: p0.x[BASE+i]=x;p0.y[BASE+i]=y-1;break;
case 5: p0.x[BASE+i]=x;p0.y[BASE+i]=y;break;
case 6: p0.x[BASE+i]=x;p0.y[BASE+i]=y+1;break;
case 7: p0.x[BASE+i]=x+1;p0.y[BASE+i]=y-1;break;
case 8: p0.x[BASE+i]=x+1;p0.y[BASE+i]=y;break;
case 9: p0.x[BASE+i]=x+1;p0.y[BASE+i]=y+1;break;
}
p0.z[BASE+i]=1;//设置显示的样式
p0.pnum++;
}
//---------------------
if(f)
{
tk_pic[base_coefficient]=1;//发出请求---设置一个专门的线程来满足这些请求
while(tk_pic[base_coefficient]);
}
}
void tank_rotate(uchar direction)
{
a=direction;//2\8\6\4 上 下左 右
b=10-a;
shape();
return;
}
void tank_con(char c)
{
if(c==0x50)//向下
{
if(x>=30) x=30;
else x++;
if(a!=8) tank_rotate(8);
tank_dis(1);
}
else if(c==0x4b)//向左
{
if(y<=1) y=1;
else y--;
if(a!=4) tank_rotate(4);
tank_dis(1);
}
else if(c==0x4d)//向右
{
if(y>=72) y=72;
else y++;
if(a!=6) tank_rotate(6);
tank_dis(1);
}
else if(c==0x48)//向上
{
if(x<=1) x=1;
else x--;
if(a!=2) tank_rotate(2);
tank_dis(1);
}
c='\0';
}
};
class TANK_derive_1:public TANK//第二控制坦克
{
public:
TANK_derive_1(uchar tx,uchar ty):TANK(tx,ty) { }
void tank_con(char c) //重载基类中的该函数
{
if(c=='s'||c=='S')//向下
{
if(x>=30) x=30;
else x++;
if(a!=8) tank_rotate(8);
tank_dis(1);
}
else if(c=='a'||c=='A')//向左
{
if(y<=1) y=1;
else y--;
if(a!=4) tank_rotate(4);
tank_dis(1);
}
else if(c=='d'||c=='D')//向右
{
if(y>=72) y=72;
else y++;
if(a!=6) tank_rotate(6);
tank_dis(1);
}
else if(c=='w'||c=='W')//向上
{
if(x<=1) x=1;
else x--;
if(a!=2) tank_rotate(2);
tank_dis(1);
}
c='\0';
}
};
class TANK_derive_2:public TANK//敌人坦克
{
public:
TANK_derive_2(uchar tx,uchar ty):TANK(tx,ty) { }
void enemy_autogo(void)
{
if(y>=72) y=5;
TANK::tank_con(0x4d);Sleep(20);
}
};
uchar Thread_Exit[TK]={0};//线程退出数组
class BULLET//子弹类
{
public:
uchar X,Y;//子弹坐标
void bullet_destroy(uchar x,uchar y,TANK &tank_t)//子弹线程关闭其他线程
{
int i;int t=0;
for(i=0;i<p0.pnum;i++)
if(p0.x[i]==x&&p0.y[i]==y) {t++; if(t==2) {tank_t.tank_vanish();Thread_Exit[0]=1;t=0;return;} }
}
void bullet_dis(TANK &tank_t)
{
uint i,j;
j=tank_t.BASE;tank_t.BASE++;
if(tank_t.a==2)//前射子弹
{
X=tank_t.x-2;Y=tank_t.y;//初始位置
for(i=X;i>0;i--)
{
p0.x[j]=i;p0.y[j]=Y; p0.z[j]=4;//子弹属性
Sleep(10);
tank_t.tank_dis(1);
bullet_destroy(i,Y,tank_t);
}
}
if(tank_t.a==8)//后射子弹
{
X=tank_t.x+2;Y=tank_t.y;
for(i=X;i<30;i++)
{
p0.x[j]=i;p0.y[j]=Y;p0.z[j]=4;//子弹属性
Sleep(10);
tank_t.tank_dis(1);
bullet_destroy(i,Y,tank_t);
}
}
if(tank_t.a==4)//左射子弹
{
X=tank_t.x;Y=tank_t.y-2;
for(i=Y;i>0;i--)
{
p0.x[j]=X;p0.y[j]=i; p0.z[j]=4;//子弹属性
Sleep(10);
tank_t.tank_dis(1);
bullet_destroy(X,i,tank_t);
}
}
if(tank_t.a==6)//右射子弹
{
X=tank_t.x;Y=tank_t.y+2;
for(i=Y;i<72;i++)
{
p0.x[j]=X;p0.y[j]=i; p0.z[j]=4;//子弹属性
Sleep(10);
tank_t.tank_dis(1);
bullet_destroy(X,i,tank_t);
}
}
tank_t.BASE--;
tank_t.tank_dis(1);
}
};
//-----------全局变量------------
TANK tank0(8,8);
//TANK_derive_1 tank1(20,20);
TANK_derive_2 tank_0(4,20);
uchar tkbt[TK]={0};//用于坦克线程和子弹线程间通信
uchar tkbt_[TK]={0};
//---------------线程入口函数
UINT WINAPI pic_ex(LPVOID lpParam)
{//显示输出控制
while(1)
{
if((Thread_Exit[0]||tk_pic[1])&&(tk_pic[0]))
{ //坦克大炮位置数组首地址,BASE基址数组首地址
tank0.tank_dis(0);//不产生申请只装值,此时是被动式显示,如发送请求则是主动式显示
if(!Thread_Exit[0]) tank_0.tank_dis(0);
picture();
tk_pic[0]=0;tk_pic[1]=0;
}
}
return 0;
}
UINT WINAPI bullet_con(LPVOID lpParam)
{
BULLET bullet0,bullet1;
while(1)
{
if(tkbt[0]) { tkbt[0]=0;bullet0.bullet_dis(tank0); }
//if(tkbt[1]) { tkbt[1]=0;bullet1.bullet_dis(tank1);}
}
return 0;
}
UINT WINAPI tank_enemy(LPVOID lpParam)
{
while(!Thread_Exit[0])
{
tank_0.enemy_autogo();
}
return 0;
}
UINT WINAPI tank_soldier(LPVOID lpParam)
{
tkbt[0]=tkbt[1]=0;
tank0.tank_dis(1);
//tank1.tank_dis(1);
while(!Thread_Exit[1])
{
tank0.tank_dis(1);
}
return 0;
}
UINT WINAPI global_con(LPVOID lpParam)
{
Thread_Exit[1]=1;
while(1)
{
uchar c;
if(kbhit())
{
c=getch();
if(c==0X0D) { tkbt[0]=1; }
//if(c=='q'||c=='Q') {tkbt[1]=1;}
tank0.tank_con(c);
//tank1.tank_con(c);
c='\0';
}
/*if(Thread_Exit[0])
{
Sleep(30);Thread_Exit[0]=0;
hThread[3]=(HANDLE)::_beginthreadex(NULL,NULL,tank_enemy,NULL,0,NULL);
} */
tank0.tank_dis(1);
}
return 0;
}
int main(int argc, char* argv[])
{
//---------------
hThread[0]=(HANDLE)::_beginthreadex(NULL,NULL,pic_ex,NULL,0,NULL);//立即执行的线程
hThread[1]=(HANDLE)::_beginthreadex(NULL,NULL,global_con,NULL,0,NULL);
hThread[2]=(HANDLE)::_beginthreadex(NULL,NULL,bullet_con,NULL,0,NULL);
hThread[3]=(HANDLE)::_beginthreadex(NULL,NULL,tank_enemy,NULL,0,NULL);
hThread[4]=(HANDLE)::_beginthreadex(NULL,NULL,tank_soldier,NULL,0,NULL);
//-----------------------
::WaitForMultipleObjects(5,hThread,TRUE,INFINITE);
::CloseHandle(hThread[0]);
::CloseHandle(hThread[1]);
::CloseHandle(hThread[2]);
::CloseHandle(hThread[3]);
::CloseHandle(hThread[4]);
//---------------------
return 0;
}
//下面是另外一个包含文件,我个人感觉他的功能很像console下的一个“显卡”,是我自己呕心沥血写成的,
//我写的控制台游戏都使用了这个头文件,当然除了自娱自乐没什么实际的意义。
//pic.h是从picture.h提取出使用的部分代码组成的头文件,主要因为picture.h太乱,不方便在别的程序里引用
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<windows.h>
#define MAX 3200
typedef unsigned char uchar;
//----------------------结构体化------------------------------------------
typedef struct Pic{
uchar x[MAX];
uchar y[MAX]; //x,y为坐标
uchar z[MAX];// pixel形状及颜色信息
int pnum;
}PIC;//pixel的坐标为非负,但排序的序号i,j应设为int,插入排序时,负数为退出循环的界限
//-----------------------------------------------------------------------
PIC p0={
{0},
{0},
{0},
0}; //未赋值的内存自动赋0
PIC pt={
{0},
{0},
{0},
0}; //采用本地备份数据显示方式,不破坏原数据
//-----------------
void point_ex(int x,int y,char*p)//起始点(0,0)
{
int i,dx=0,dy=0;
static int x0=0,y0=0,j=1;//x0,y0表示当前行和当前列
if(x==-1&&y==-1) {x0=y0=0;j=1;}//用point(-1,-1)刷函数-相对于(0,0)点开始打点,未刷屏则相对于
//当前点,一般用于重新绘图时
if(x==0&&y==0&&j==1) { j=0; printf("%s",p);return;}
if(x==0&&y==0&&j==0) return;//打两次(0,0)时略去第二个
dx=x-x0;
if(!dx) dy=y-y0; //dx=0,同一行
else dy=y;
if(dy<0||dx<0) return;
if(x==0&&y!=0&&dy==y&&j) printf(" ");//这个if语句的意思是:锁定第零行,除去第零列
//所打的第一个点且保证未打(0,0)时,执行后面的语句
if(dy==0&&y!=0) return;//滤去空行以及相同的点
for(i=0;i<dx;i++) putchar('\n'); //i=0第一行为空白行
if(dx&&y!=0) printf(" ");//换行时如果这一行的第零列没打的话,则自动加一列
for(i=1;i<dy;i++) printf(" ");//打完一个点后光标已移到下一个点,因此dy=1时,这条语句应该不执行
//putchar(4);
//--------判断蛇头---------
printf("%s",p);
//---------------
x0=x;
y0=y;
}//注:每次打的点必须在前一个点后面,且第一次调用时必须先清屏
// ⊙★○▲△▽○◇□■☆♀♂㊣╲▕╱╲▏▏卐卍♀◢◥◤
//------------------------------------------
//-----------------
void pic_copy(uchar*,uchar* ,uchar *,int);
void pic_sort(uchar*,uchar*,uchar*,int);
void pic_debug()
{
int i;
//-----debug view----------
printf("\n---------debug---------------\n");
for(i=0;i<p0.pnum;i++)
printf(i%18==17?"%d\n":"%d\t",p0.x[i]);
printf("\n----------------------------\n");
for(i=0;i<p0.pnum;i++)
printf(i%18==17?"%d\n":"%d\t",p0.y[i]);
putchar(10);
}
void picture(void)
{
int i;
pic_copy(p0.x,p0.y,p0.z,p0.pnum);
pic_sort(pt.x,pt.y,pt.z,pt.pnum);
//------刷新--------
point_ex(-1,-1,NULL);
system("cls");
for(i=0;i<pt.pnum;i++)
{
switch(pt.z[i])
{
case 1: point_ex(pt.x[i],pt.y[i],"■");
case 2: point_ex(pt.x[i],pt.y[i],"||");
case 3: point_ex(pt.x[i],pt.y[i],"--");
case 4:
{
::SetConsoleTextAttribute(::GetStdHandle(STD_OUTPUT_HANDLE),7);
point_ex(pt.x[i],pt.y[i],"○");
::SetConsoleTextAttribute(::GetStdHandle(STD_OUTPUT_HANDLE),14);
}
}
}
putchar(10);
//pic_debug();
}
void pic_copy(uchar*tx,uchar*ty,uchar *tz,int pnum)
{
int i;
if(pnum==0) return;
pt.pnum=pnum;
for(i=0;i<pnum;i++)
{
pt.x[i]=tx[i];
pt.y[i]=ty[i];
pt.z[i]=tz[i];
}
return;
}
//--------整理pic-------
void pic_sort(uchar*tx,uchar*ty,uchar *tz,int pnum)//在原来的插入排序法上,使ty的值跟着tx的值移动,对于pixel进行整理
{
int h;
int i,j,t,k;
int m,n,l;
if(pnum==0) return;
//----第一步排tx(并保持tx与ty之间的对应关系)---
for(i=1;i<pnum;i++)//只对数组赋值的区域排序
{
t=tx[i];
k=ty[i];h=tz[i];
for(j=i-1;j>=0;j--)
if(t<tx[j]) { tx[j+1]=tx[j];ty[j+1]=ty[j];tz[j+1]=tz[j]; }
else break;
tx[j+1]=t;
ty[j+1]=k;tz[j+1]=h;
}
//--------第二步逐行排ty----------
for(i=tx[0],j=0;i<=tx[pnum-1];i++)//从第tx[0]到tx[p0.pnum-1]行
{
l=0; //j的最大值不能超过p0.pnum
while(tx[j]==i)
//此处只依靠tx[*]的值去做判断,容易出现超出数组寻址范围的情况,因为如果对于未定义的内存空间(此处是明确的,tx后的地址就是ty的地址),其值虽然相对
//不变,但总体(理论上)应该是不确定的(这可能取决于各自的机器),因此尽管超出规定的范围,仍然有可能
//满足这个判别式,从而造成寻址越界的情况
{
if(j==p0.pnum) break;//后来加的限制条件,没有它会出现一系列的奇葩现象
j++;l++;
}//得到tx[*]行的点数l
//当tx[*]全赋0且ty[0]=0,此处寻址越界,由于tx[*]寻址越界后,寻到了ty[*]处,他们地址是连续的,
//而此时的ty[0]正好满足ty[0]=tx[0]=0,这就造成了寻址越界,因此加上一条限制语句(如上),
//也证明上面的猜想是正确的
if(l==1||l==0) continue;
//*************************************
for(m=j-l;m<j-1;m++)
{
k=m;
for(n=m+1;n<j;n++)
if(ty[n]<ty[k]) k=n;
if(k!=m)
{
t=ty[k];ty[k]=ty[m];ty[m]=t;//ty移动
t=tz[k];tz[k]=tz[m];tz[m]=t;//随ty移动tz
}
}
//************************************
//必须说明被*号拦起来的代码相当奇葩,其中明显可以发现,程序并没有对p0.pnum进行赋值,
//但其值其出现了奇葩式的间断连续改变,这个或许和上面寻址越界有关,毕竟po0.x[*]、p0.y[*]、p0.pnum
//定义在一起(同一个结构体中),它们的地址应该是连续的
}
//当p0.pnum=3200时,由于寻址越界,它被当作了ty来进行排序,由上面分析可知,当时j=3021,越界一位正好
//p0.pnum被加进来了,由于是用ty[3200]的形式加进来的,所以它的类型就由int变为了char,可以计算3200/256=12.5
//也就是说,经过12次循环后其值变为0.5*256=128,而由于是char型,其正数最大为127,对应于-128,此时最小
//这也就是为什么会在pic_debug()中出现一个负数的原因,同时也解释了为什么将结构体PIC中数组改为uchar后,
//又出现了正确的pic_debug()界面(其值最大被放到了最后,也就是没有变动)。
//最后int型的p0.pnum排到了char型里,char型的9排到了int型里,由于p0.[3200]的方式只调走了int型内存区域的256以内(即一个字节内)的数值
//还有12*256保留在原处,所以会出现p0.pnum=12*256+9=3081的情况,,到此所有的谜团都解开了。
//而问题的关键仅仅是一个寻址越界造成的,现做两点修改:
//1.加入如上所示的限制条件
//2.将PIC结构体中的数组改为uchar,因为我们不打出负点
}
//----使用帮助
//1.先为结构体PIC赋值,将一个个要显示的点装入数组,不分先后无需排序,只要x,y对应正确即可
//2.更新p0.pnum为当前总共要显示的pixel数
//3.直接调用picture()即可
//4.对于picture()的调试,一般采用pic_debug()函数找出问题所在