/* Fool fly game v1.0 by skywind
 * 空战摸拟源程序，林伟 1999年7月30日
 *
 * 这是一个空战程序的模式例子，并没有做任何优化与扩充
 * 仅仅向人说明编写游戏程序的若干功能实现或常用处理方法
 * 程序是DOS版的，现在写游戏的平台早已变化了许多，但是
 * 我尽量容入了一些不变理论，希望才入门的游戏设计者能受
 * 到一些启发，通过它也可以看出当日DOS下设计的困难重重。
 *
 * 整个程序不足300行，请用BC31或者TC20的Large模式编译
 */

#include <stdio.h>
#include <dos.h>
#include <stdlib.h>
#include <conio.h>
#include <mem.h>
#include <time.h>
#include <math.h>
#include <alloc.h>

#define  MAX_STAR   80					/* 最多星星数目         */
#define  MAX_OBJ    30					/* 最多物体(子弹和敌人) */
#define  MAXY      169  				/* y坐标的最大值        */
typedef unsigned long ulong;
char far *VideoBuf=(char far*)0xa0000000L, *MemBuf;     /* MemBuf 图形二级缓存 */
char  keys[128];                                        /* keys[0..100] 键盘上各键的状态   */
const KEY_UP=72,KEY_DOWN=80,KEY_LEFT=75,KEY_RIGHT=77,KEY_ESC=1,KEY_CTRL=29;
												/* 键盘扫描码 */
void interrupt far (*OldInt9)(void);                            /* 老的键盘中断程序地址 */
void interrupt NewInt9(void);                                   /* 新的键盘中断服务程序 */
char GameStart(void);
struct TStars { int x,y,v; } Stars[MAX_STAR];   /* 星星的描述 */
struct TObj { int mode,type,index,next,x,y; } Objs[MAX_OBJ]; /* 子弹和敌人的描述 */
long GameScore=0;                               /* 游戏分数 */

unsigned char fly_pic[260]={  	/* 飞机的图形，我是用程序将PCX图形转换过来的 */
16,0,16,0,0,0,0,29,25,0,0,0,0,25,29,196,0,0,0,0,0,0,0,25,25,0,0,0,0,25,0,196,0,
0,0,0,112,112,0,25,196,40,40,29,112,112,40,112,0,112,112,112,40,40,112,112,40,40,
25,25,112,40,40,112,112,112,112,112,112,40,112,25,40,40,112,112,112,40,40,25,
40,112,112,0,0,112,112,25,40,40,40,112,40,40,40,25,40,112,0,0,0,0,112,25,40,
112,40,112,112,112,40,25,112,0,0,0,0,0,0,29,112,0,40,0,112,0,112,29,0,0,0,0,
0,0,0,29,112,0,40,54,112,0,112,29,0,0,0,0,0,0,0,29,0,0,40,54,112,0,0,29,0,0,
0,0,0,0,0,0,0,40,112,25,112,112,0,0,0,0,0,0,0,0,0,0,0,40,112,25,112,112,0,0,
0,0,0,0,0,0,0,0,0,40,112,112,112,112,0,0,0,0,0,0,0,0,0,0,0,0,40,112,112,0,0,
0,0,0,0,0,0,0,0,0,0,0,40,25,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,0,0,0,0,0,0,0,
0};	/* 数据头四个字节分别由两个16位数据表示图片的长和宽，后面是图片8bit的颜色数据 */

void copyright(void)
{ //printf("Game Over. your score %ld\n",GameScore);
  //printf("Thank you for play it, if you have any question please call me\n");
  //printf("(0871)7167710 or lwwind@yeah.net, Lin Wei\n");

}

int game_init(void);
int game_restore(void);

void main(void)
{
  if (!game_init()) return;                    /* 游戏初始化 */
  //printf("           - STAR WAR -\r");         /*  打印标题  */
  GameStart();                                 /* 游戏主循环 */
  game_restore();                              /* 还原初始化 */
  copyright();                                 /* 显示说明文 */
}

int game_init(void)
{
  long i;
  union REGS regs;
  MemBuf=(char far*)farmalloc(320L*(MAXY+1));
  if (!MemBuf) {
     //printf("not enough memory\n"); return 0;
  }
  memset(MemBuf,0,320L*(MAXY+1));
  randomize();                                          /* 初始化随机函数 */
  for (i=0;i<128;i++) keys[i]=0;                        /* 键盘码初始化 */
  for (i=0;i<MAX_STAR;i++) {                            /* 星空初始化   */
      Stars[i].x=random(320);
      Stars[i].y=-20+random(250);
      Stars[i].v=1;
      if (i<MAX_STAR/3) Stars[i].v++;
  }
  for (i=0;i<MAX_OBJ;i++) Objs[i].mode=0;	/* 物体初始化 */

  /* 键盘中断初始化：设置新的中断 */
  OldInt9=getvect(9);
  disable();
  setvect(9,NewInt9);
  enable();

  /* 设置 320x200x256c图形模式 */
  regs.x.ax=0x13;
  int86(0x10,&regs,&regs);
  return 1;
}

int game_restore(void)
{
  union REGS regs;
  /* 还原设置：还原老的中断 */
  disable();
  setvect(9,OldInt9);
  enable();

  regs.x.ax=3;
  int86(0x10,&regs,&regs);
  farfree(MemBuf);
  return 0;
}

/* 键盘服务程序 */
void interrupt NewInt9(void)
{
  unsigned char key;
  key=inportb(0x60);                                    /* 读键盘扫描码 */
  if (key<0x80) keys[key]=1;                            /* 如果最高位是0,则为按下 */
    else keys[key&0x7f]=0;                              /* 如果最高位是1,则为放开 */
  key=inportb(0x61); key|=0x80; outportb(0x61,key);     /* 告诉键盘已接收 */
  outportb(0x61,key&0x7f);
  outportb(0x20,0x20);                                  /* 发送中断结束信号 */
}

/*---------------------------- 游戏图形引擎 ---------------------------------*/
void pixel(unsigned x,unsigned y,char c)                /* 画点 */
{
  if (x>=320||y>MAXY) return;                           /* 判断范围 */
  MemBuf[(y<<8)+(y<<6)+x]=c;                            /* MemBuf[y*320+x]=c */
}

/* 将二级缓存的内容显示出来 */
void show(void)
{
  int offset=(199-MAXY)*160;
  memcpy(VideoBuf+offset,MemBuf,320L*(MAXY+1));
}

/* 清屏 */
void clear(void)
{
  memset(MemBuf,0,320L*(MAXY+1));
}

/* 就是绘制图块，(x,y)是坐标b是内存地址mode表示是否上下颠倒
 * 敌人和主人是同样的图片做颠倒
 */
void putimage(int x,int y,char *b,int mode)
{
  int len, wid, i, j;
  len = b[0] + (int)b[1] * 256;
  wid = b[2] + (int)b[3] * 256;
  x-=len/2; y-=wid/2;                                   /* 中心对称 */
  for (j=0,b=b+4;j<wid;j++) for (i=0;i<len;i++,b++)
    { if (*b&&!mode) pixel(x+i,y+j,*b);                 /* 是否镜面翻转 */
      if (*b&&mode)  pixel(x+i,y+wid-j-1,*b);
    }
}

void drawfire(int x,int y)                              /* 画出激光 */
{ int i,j;
  for (i=-6;i<6;i++) { pixel(x-5,y+i,9); pixel(x+3,y+i,9); }
}
/*---------------------------- 游戏控制引擎 ---------------------------------*/
unsigned long timepass;					/* 游戏进行的时间 */
int  GameOver, Sound=0;
int  fly_x=160,fly_y=MAXY*2/3,fly_flag=1,fire_flag=1;
void drivers(void); 					/* 控制产生敌人的函数 */
void control(void); 					/* 对象控制(事件处理) */

int  AllocObj(void) 					/* 分配空余对象 */
{ int i=0;
  while (i<MAX_OBJ&&Objs[i].mode) i++;
  if (i>=MAX_OBJ) i=MAX_OBJ-1;
  return i;
}

int  CheckHit(int x1,int y1,int x2,int y2,int r)	/* 检查碰撞 */
{
  if (abs(x1-x2)<=r&&abs(y1-y2)<=r) return 1;
  return 0;
}

ulong fclock(void)					/* 读取 1.19MHz的32位系统时钟 */
{
  ulong t;
  disable(); outportb(0x43,0); t=inportb(0x40);
  t+=(inportb(0x40)<<8); t=0xffff^t; enable();		/* (not t) + (clock()<<16) */
  return (clock()<<16)+t;
}

char GameStart(void)
{
  ulong start=0;
  timepass=0;
  while (!GameOver)
   {
     while (fclock()-start<=45000L); start=fclock(); 	/* 时间控制 */
     clear();						/* 1.清屏. 以下5点为游戏的主循环 */
     timepass++; 					/* 2.时间基数++ */
     control();  					/* 3.事件处理   */
     drivers();  					/* 4.事件引擎   */
     show();     					/* 5.显示       */
     if (--Sound<=0) nosound(); 			/* 声音处理 */
     if ((timepass&3)==0) //printf("%d\r",GameScore);	/* 到一定的时间更新显示分数 */
     if (keys[KEY_ESC]) GameOver=1;
   }
  sound(105); delay(700); nosound();                    /* Game Over发音 */
  return 1;
}


/* 游戏的事件处理主程序分别处理星空和物体还有主角控制
 * 通过扫描对象数组：Objs[MAX_OBJ]来完成，根据扫描到的对象属性Objs[i].mode来判断
 * 到底是什么对象：敌机或者子弹。并进入相应的处理程序：分析对象的状态，然后作出
 * 调整，并重新更改状态，这就是游戏编程同时处理众多物体的核心思想。
 */
void control(void)
{ 
  int i,j,x,y, ok;
  
  /* 星星处理 */
  for (i=0;i<MAX_STAR;i++) { 
    Stars[i].y+=Stars[i].v;       /* 向下移动 */
    if (Stars[i].y>MAXY) {        /* 如果移出屏幕就让它重新出现在屏幕上方某位置 */
	Stars[i].x=random(320);
	Stars[i].y=-random(60);
    }
    if (Stars[i].v==1) pixel(Stars[i].x,Stars[i].y,23);	/* 速度不一样颜色也不一样 */
      else pixel(Stars[i].x,Stars[i].y,28);
  }

  /* 对象处理 */
  for (i=0;i<MAX_OBJ;i++)				/* 扫描对象数组 */
   { x=Objs[i].x; y=Objs[i].y; ok=1;
     switch (Objs[i].mode)				/* mode表示敌人，子弹等 */
      {
	/* 敌人处理 */
	case 1: if (Objs[i].index==0) {
		 switch (Objs[i].type)                  /* 检测敌人的三种状态 */
		 { case 0: if (!random(30)) Objs[i].type=1; break; /* 3种动作状态 */
		   case 1: if (x<fly_x) x++; if (x>fly_x) x--;
			   if (!random(40)) Objs[i].type=2; break;
		   case 2:
		   default:if (!random(3)) y+=3;
			   break;
		 }
		 if (CheckHit(x,y,fly_x,fly_y,15)) fly_flag=0;	/* 与主角相撞 */
		 putimage(x,y,fly_pic,0);
		} else { /* 如果被击中就闪烁地绘制飞机，然后消失 */
		 if ((Objs[i].index&3)==0) putimage(x,y,fly_pic,0);
		 if (++Objs[i].index>50) Objs[i].mode=0;
		}
		y++; if (y>MAXY) Objs[i].mode=0;	/* 移出屏幕就清除 */
		break;
	/* 激光处理 */
	case 2: y-=4; if (y<-20) Objs[i].mode=0; drawfire(x,y);
		for (j=0;j<MAX_OBJ;j++)                 /* 检查与敌机相碰 */
		if (Objs[j].mode==1&&CheckHit(x,y,Objs[j].x,Objs[j].y,15)&&
		    !Objs[j].index&&ok)			/* 击中敌机 */
		 {
		   Objs[j].index=1; ok=0; Objs[i].mode=0;
		   sound(220); Sound=10; GameScore+=10;
		 }
		break;
      }
     Objs[i].x=x; Objs[i].y=y;				/* 更新坐标 */
   }

  /* 主角控制 */
  if (keys[KEY_UP])  if (--fly_y<0) fly_y=0; 		/* 如果上键被按下 */
  if (keys[KEY_DOWN]) if (++fly_y>MAXY) fly_y=MAXY;	/* 如果下键被按下 */
  if (keys[KEY_LEFT]) if (--fly_x<0) fly_x=0;		/* 如果左键被按下 */
  if (keys[KEY_RIGHT]) if (++fly_x>319) fly_x=319;	/* 如果右键被按下 */
  if (keys[KEY_CTRL]&&fire_flag) { 			/* 如果按下CTRL */
     i=AllocObj(); Objs[i].x=fly_x,
     Objs[i].y=fly_y-10, Objs[i].mode=2; fire_flag=0;
  }
  if (!keys[KEY_CTRL]) fire_flag=1;			/* 如果放开CTRL */
  putimage(fly_x,fly_y,fly_pic,1);			/* 画出主角     */
  if (!fly_flag) GameOver=1;				/* 判断主角状态 */
}

/* 控制产生敌人的部分 */
void drivers(void)
{
  if (random(30)==0)
  { int i=AllocObj(); Objs[i].x=random(320); Objs[i].y=-random(20);
    Objs[i].mode=1; Objs[i].type=Objs[i].index=0;
    if (!random(20)) Objs[i].type=2;
  }
}
