Trivial Solutions Corp.
                        Happy Programmers Make Us Happy

The LoseThos 64-bit PC Operating System
File:/LT/Demo/GameStarters/FPS.CPZ
This, and all LoseThos files, are public domain.  Do whatever you like.


AcctRegSetDefaultEntry("LT/FPS","F8 best_score=9999;\r\n");
AcctRegExecuteBranch("LT/FPS");

//Set snap to 4 and width to 4
//if you edit this map.

//Don't forget to change the
//starting position.
#define MAN_START_X     0
#define MAN_START_Y     4.5

<1>/* Graphics Not Rendered in HTML */















#define MONSTER_SCALE   2.0


                     <2>/* Graphics Not Rendered in HTML */
 









                     <3>/* Graphics Not Rendered in HTML */
 








                     <4>/* Graphics Not Rendered in HTML */
 






#define PLANT_SCALE     2.0



                     <5>/* Graphics Not Rendered in HTML */





#define SCREEN_SCALE            512
#define PLOT_GRID_WIDTH         24
#define PLOT_GRID_HEIGHT        24
#define MAN_HEIGHT              125

#define MAP_SCALE       4
I8 map_width,map_height;
U1 *map=NULL,
*panels_processed_bitmap=NULL;

I8 man_xx,man_yy;
F8 man_theta;

F8 t0,tf;

#define NUM_MONSTERS    16
I8 monsters_left;
class monster
{
  I8 x,y;
  BoolI1 dead,pad[15];
} monsters[NUM_MONSTERS];

U0 FPSTransform(GrBitMap *base,I8 *x,I8 *y,I8 *z)
{
  I8 zz;
  GrRot(base->r,x,y,z);
  zz=SCREEN_SCALE/3+*z;
  if (zz<1) zz=1;
  *x=SCREEN_SCALE/2* *x/zz;
  *y=SCREEN_SCALE/2* (*y+MAN_HEIGHT)/zz;
  *x+=base->x;
  *y+=base->y;
  *z+=base->z;
}

#define LOS_SCALE       4

BoolI8 LOSPlot(U0 *dummy,I8 x,I8 y,I8 z)
{
  nounusedwarn dummy,z;
  if (!map[(y/LOS_SCALE)*map_width+(x/LOS_SCALE)])
    return FALSE;
  else
    return TRUE;
}

BoolI8 LOS(I8 x1,I8 y1,I8 x2,I8 y2)
{ //Line of sight
  return Line(NULL,x1*LOS_SCALE/SCREEN_SCALE,y1*LOS_SCALE/SCREEN_SCALE,0,
               x2*LOS_SCALE/SCREEN_SCALE,y2*LOS_SCALE/SCREEN_SCALE,0,&LOSPlot);
}

U0 UpdateWin(TaskStruct *task)
{
  GrBitMap *base=GrAlias(gr_refreshed_base,task);
  I8 i,j,*r1,*r2,*r3,*s2w,xx,yy,zz,x,y,x1,y1,z1,
      x1w,y1w,x1h,y1h,xh,yh,zh,
      cx=task->win_pixel_width/2,
      cy=task->win_pixel_height/2;
  U1 *inter;
  F8 tt;
  P3I4 poly[4];
  GrAllocDepthBuf(base);
  MemSet(panels_processed_bitmap,0,(map_width*map_height+7)>>3);

  //World to screen
  GrRotZEqu(base->r,man_theta+pi/2);
  GrRotXEqu(base->r,pi/2);
  GrSetRotMat(base,base->r);

  xh=-man_xx/SCREEN_SCALE; yh=-man_yy/SCREEN_SCALE; zh=0;
  GrRot(base->r,&xh,&yh,&zh);
  GrSetTranslation(base->r,xh,yh,zh);

  //Screen to world
  s2w=GrRotX(-pi/2,task);
  GrRotZEqu(s2w,-man_theta-pi/2);
  xh=0; yh=0; zh=SCREEN_SCALE;
  GrRot(s2w,&xh,&yh,&zh);

  //Rotate light source
  xx=base->ls.x; yy=base->ls.y; zz=-base->ls.z;
  base->transform(base,&xx,&yy,&zz);
  base->ls.x=xx; base->ls.y=yy; base->ls.z=zz;

  base->flags|=BMF_TRANSFORMATION;
  base->transform=&FPSTransform;
  base->x=cx;
  base->y=cy;
  r1=GrRotX(-pi/2,task);
  GrRotZEqu(r1,tP(task));
  GrScaleMatEqu(r1,MONSTER_SCALE);

  r2=GrIdent(task);
  GrScaleMatEqu(r2,MONSTER_SCALE);

  r3=GrRotX(-pi/2,task);
  GrScaleMatEqu(r3,PLANT_SCALE);

  x1h=man_xx+yh*PLOT_GRID_WIDTH/2+xh*PLOT_GRID_HEIGHT;
  y1h=man_yy-xh*PLOT_GRID_WIDTH/2+yh*PLOT_GRID_HEIGHT;
  xh>>=1; yh>>=1;
  for (j=0;j<PLOT_GRID_HEIGHT*2;j++) {
    x1w=x1h;
    y1w=y1h;
    for (i=0;i<PLOT_GRID_WIDTH*4;i++) {
      xx=x1w/SCREEN_SCALE; yy=y1w/SCREEN_SCALE;
      x=xx*SCREEN_SCALE-man_xx; y=yy*SCREEN_SCALE-man_yy;
      if (1<=xx<map_width-1 && 1<=yy<map_height-1 &&
          !LBts(panels_processed_bitmap,yy*map_width+xx)) {
        if ((base->color=map[yy*map_width+xx]) &&
            LOS(xx*SCREEN_SCALE+SCREEN_SCALE/2,yy*SCREEN_SCALE+SCREEN_SCALE/2,
                man_xx,man_yy)) {
          poly[0].x=x;
          poly[0].y=y;
          poly[0].z=0;
          poly[1].x=x+SCREEN_SCALE;
          poly[1].y=y;
          poly[1].z=0;
          poly[2].x=x+SCREEN_SCALE;
          poly[2].y=y+SCREEN_SCALE;
          poly[2].z=0;
          poly[3].x=x;
          poly[3].y=y+SCREEN_SCALE;
          poly[3].z=0;
          GrFillPolygon3(base,4,poly);
          if (base->color==GREEN) {
            x1=x+SCREEN_SCALE/2;
            y1=y+SCREEN_SCALE/2;
            z1=0;
            GrTransform(base,&x1,&y1,&z1);
            if (z1>0)
              GrElemsPlotRotMat(base,x+SCREEN_SCALE/2,y+SCREEN_SCALE/2,0,<5>,r3);
          }

          if (!map[(yy+1)*map_width+xx]) {
            base->color=WHITE;
            poly[0].x=x;
            poly[0].y=y+SCREEN_SCALE;
            poly[0].z=0;
            poly[1].x=x+SCREEN_SCALE;
            poly[1].y=y+SCREEN_SCALE;
            poly[1].z=0;
            poly[2].x=x+SCREEN_SCALE;
            poly[2].y=y+SCREEN_SCALE;
            poly[2].z=SCREEN_SCALE;
            poly[3].x=x;
            poly[3].y=y+SCREEN_SCALE;
            poly[3].z=SCREEN_SCALE;
            GrFillPolygon3(base,4,poly);
          }
          if (!map[yy*map_width+xx+1]) {
            base->color=YELLOW;
            poly[0].x=x+SCREEN_SCALE;
            poly[0].y=y;
            poly[0].z=0;
            poly[1].x=x+SCREEN_SCALE;
            poly[1].y=y+SCREEN_SCALE;
            poly[1].z=0;
            poly[2].x=x+SCREEN_SCALE;
            poly[2].y=y+SCREEN_SCALE;
            poly[2].z=SCREEN_SCALE;
            poly[3].x=x+SCREEN_SCALE;
            poly[3].y=y;
            poly[3].z=SCREEN_SCALE;
            GrFillPolygon3(base,4,poly);
          }
          if (!map[(yy-1)*map_width+xx]) {
            base->color=WHITE;
            poly[0].x=x;
            poly[0].y=y;
            poly[0].z=0;
            poly[1].x=x+SCREEN_SCALE;
            poly[1].y=y;
            poly[1].z=0;
            poly[2].x=x+SCREEN_SCALE;
            poly[2].y=y;
            poly[2].z=SCREEN_SCALE;
            poly[3].x=x;
            poly[3].y=y;
            poly[3].z=SCREEN_SCALE;
            GrFillPolygon3(base,4,poly);
          }
          if (!map[yy*map_width+xx-1]) {
            base->color=YELLOW;
            poly[0].x=x;
            poly[0].y=y;
            poly[0].z=0;
            poly[1].x=x;
            poly[1].y=y+SCREEN_SCALE;
            poly[1].z=0;
            poly[2].x=x;
            poly[2].y=y+SCREEN_SCALE;
            poly[2].z=SCREEN_SCALE;
            poly[3].x=x;
            poly[3].y=y;
            poly[3].z=SCREEN_SCALE;
            GrFillPolygon3(base,4,poly);
          }
        }
      }
      x1w-=yh;
      y1w+=xh;
    }
    x1h-=xh;
    y1h-=yh;
  }

  //Draw Monsters
  for (i=0;i<NUM_MONSTERS;i++) {
    x=monsters[i].x;
    y=monsters[i].y;
    if (LOS(x,y,man_xx,man_yy)) {
      x-=man_xx;
      y-=man_yy;
      xx=x;
      yy=y;
      zz=0;
      GrTransform(base,&xx,&yy,&zz);
      if (zz>0) {
        if (monsters[i].dead)
          GrElemsPlotRotMat(base,x,y,0,<2>,r2);
        else {
          tt=Tri(tP(task),1.0);
          inter=GrElemsInterpolate(<3>,<4>,tt);
          GrElemsPlotRotMat(base,x,y,0,inter,r1);
          Free(inter);
        }
      }
    }
  }
  Free(r1);
  Free(r2);
  Free(r3);

  //Draw Map heads-up display, scaled 2 pixels
  Free(base->r);
  GrSetRotMat(base,GrIdent(task));
  base->x=task->win_pixel_width -2*map_width;
  base->y=task->win_pixel_height-2*map_height;
  base->z=0;
  base->transform=&GrTransform;
  base->pen_width=2;
  for (i=0;i<map_height;i++)
    for (j=0;j<map_width;j++) {
      base->color=map[(map_height-1-i)*map_width+j];
      GrPlot3(base,2*j,2*i,0);
    }

    //Draw Things on heads-up Map
    base->color=LTPURPLE;
  for (i=0;i<NUM_MONSTERS;i++)
    if (!monsters[i].dead)
      GrPlot3(base,2*(monsters[i].x/SCREEN_SCALE),2*(map_height-1-monsters[i].y/SCREEN_SCALE),0);
  base->color=LTCYAN;
  GrPlot3(base,2*(man_xx/SCREEN_SCALE),2*(map_height-1-man_yy/SCREEN_SCALE),0);


  if (tf) {
    base->color=LTRED;
    if (Blink(,tP(task)))
      GrPutS(base,cx-(FONT_WIDTH*14)/2,cy-FONT_HEIGHT/2,"Game Completed");
    tt=tf;
  } else {
    base->color=LTGREEN;
    GrLine(base,cx-5,cy,cx+5,cy);
    GrLine(base,cx,cy-5,cx,cy+5);
    tt=tP(task);
  }
  GrPrintF(base,0,0,"Enemy:%d Time:%3.2f Best:%3.2f",monsters_left,tt-t0,best_score);
  Free(s2w);
  GrDel(base);
}

U0 Fire()
{
  I8 i,x,y;
  F8 d,dx,dy,xx=Cos(man_theta),yy=Sin(man_theta);
  Noise(100,300,1000);
  for (i=0;i<NUM_MONSTERS;i++) {
    x=monsters[i].x;
    y=monsters[i].y;
    if (!monsters[i].dead &&
        LOS(x,y,man_xx,man_yy)) {
      dx=x-man_xx;
      dy=man_yy-y;
      if (d=Sqrt(dx*dx+dy*dy)) {
        dx/=d;
        dy/=d;
        if (dx*xx+dy*yy>0.995) {
          monsters[i].dead=TRUE;
          if (!--monsters_left) {
            tf=tP;
            if (tf-t0<best_score)
              best_score=tf-t0;
          }
        }
      }
    }
  }
}


U0 Init()
{
  I8 i,x,y;
  I8 min_map_x,max_map_x,min_map_y,max_map_y;
  GrBitMap *base;
  GrElemsExtents(<1>,&min_map_x,&max_map_x,&min_map_y,&max_map_y);
  map_width =(max_map_x-min_map_x+1)/MAP_SCALE+2;
  map_height=(max_map_y-min_map_y+1)/MAP_SCALE+2;
  Free(map);
  Free(panels_processed_bitmap);
  map=CAlloc(map_width*map_height*sizeof(U1));
  panels_processed_bitmap=MAlloc((map_width*map_height+7)>>3);
  base=GrNew(BMT_COLOR4,map_width*MAP_SCALE,map_height*MAP_SCALE);
  GrElemsPlot(base,-min_map_x+MAP_SCALE,-min_map_y+MAP_SCALE,0,<1>);
  for (y=1;y<map_height-1;y++)
    for (x=1;x<map_width-1;x++)
      map[(map_height-1-y)*map_width+x]=GrPeek(base,x*MAP_SCALE,y*MAP_SCALE);
    GrDel(base);
  man_xx=(1+MAN_START_X)*SCREEN_SCALE;
  man_yy=(map_height-1-MAN_START_Y)*SCREEN_SCALE;
  man_theta=0;

  for (i=0;i<NUM_MONSTERS;i++) {
    monsters[i].dead=FALSE;
    do {
      monsters[i].x=RandU8%((map_width-2)*SCREEN_SCALE)+SCREEN_SCALE;
      monsters[i].y=RandU8%((map_height-2)*SCREEN_SCALE)+SCREEN_SCALE;
    } while (!map[(monsters[i].y/SCREEN_SCALE)*map_width+monsters[i].x/SCREEN_SCALE]);
  }
  monsters_left=NUM_MONSTERS;
  tf=0;
  t0=tP;
}

U0 AnimateTask()
{
  I8 i,x,y,dd;
  while (TRUE) {
    dd=0.25*SCREEN_SCALE*Sin(tP(Fs->parent_task)/2);
    for (i=0;i<NUM_MONSTERS;i++)
      if (!monsters[i].dead) {
        x=monsters[i].x;
        y=monsters[i].y;
        if (i&1)
          x+=dd;
        else
          y+=dd;
        if (0<=x<=map_width*SCREEN_SCALE &&
            0<=y<=map_height*SCREEN_SCALE &&
            map[(y/SCREEN_SCALE)*map_width+x/SCREEN_SCALE]) {
          if (!map[(y/SCREEN_SCALE)*map_width+x/SCREEN_SCALE+1] &&
               x-RoundDownI8(x,SCREEN_SCALE)>SCREEN_SCALE/2 ||
              !map[(y/SCREEN_SCALE)*map_width+x/SCREEN_SCALE-1] &&
               x-RoundDownI8(x,SCREEN_SCALE)<SCREEN_SCALE/2)
            x=RoundDownI8(x,SCREEN_SCALE)+SCREEN_SCALE/2;
          if (!map[(y/SCREEN_SCALE+1)*map_width+x/SCREEN_SCALE] &&
               y-RoundDownI8(y,SCREEN_SCALE)>SCREEN_SCALE/2 ||
              !map[(y/SCREEN_SCALE-1)*map_width+x/SCREEN_SCALE] &&
               y-RoundDownI8(y,SCREEN_SCALE)<SCREEN_SCALE/2)
            y=RoundDownI8(y,SCREEN_SCALE)+SCREEN_SCALE/2;
          monsters[i].x=x;
          monsters[i].y=y;
        }
      }
    Sleep(20);
  }
}

U0 CleanUp()
{
  Free(map);
  Free(panels_processed_bitmap);
  map=NULL;
  panels_processed_bitmap=NULL;
}

U0 SongTask()
{ //Song by Terry A. Davis
  Fs->task_end_cb=&SndTaskEndCB;
  MusicSettingsReset;
  while (TRUE) {
    Play("W12q.A#1eG2AeA#qAq.A#1eG2eAeA#qA");
    Play("q.A#1eG2A#A1qG2q.A#1eG2A#A1qG");
    Play("3eA#A2qG3eA#A2qG3eA#A2G3AA#A2qG");
  }
}

U0 MoveMan(F8 theta)
{
  I8 x,y,color,step=SCREEN_SCALE/2;
  do {
    x=man_xx+step*Cos(theta);
    y=man_yy-step*Sin(theta);
    x=Limit(x,0,map_width*SCREEN_SCALE);
    y=Limit(y,0,map_height*SCREEN_SCALE);
    color=map[y/SCREEN_SCALE*map_width+x/SCREEN_SCALE];
    if (color==RED || color==GREEN) {
      man_xx=x;
      man_yy=y;
      break;
    } else
      step>>=1;
  } while (step);
}

U0 FPS()
{
  I8 msg_code,p1,p2,ch,sc;

  PopUpOk(
    "I refuse to rip-off the original\r\n"
    "so this is intentionally crappy\r\n"
    "and included for demonstration\r\n"
    "purposes.\r\n\r\n"
    "Write games, don't play them.\r\n");

  MenuPush(
  "File {"
  "  Abort(,CH_CTRLQ);"
  "  Exit(,CH_ESC);"
  "}"
  "Play {"
  "  Restart(,CH_CR);"
  "  Forward(,,SC_CURSOR_UP);"
  "  Backward(,,SC_CURSOR_DOWN);"
  "  Left(,,SC_CURSOR_LEFT);"
  "  Right(,,SC_CURSOR_RIGHT);"
  "  Fire(,CH_SPACE);"
      "}"
      );

  SettingsPush; //See SettingsPush
  WinMax;
  Init;
  Fs->animate_task=Spawn(&AnimateTask,NULL,"Animate",Fs);
  Fs->song_task=Spawn(&SongTask,NULL,"Song",Fs);
  Fs->text_attr=WHITE+BLACK<<4;
  WordStat(OFF);

  //The text layer under the graphics lags a frame
  //therefore the fill operations screw-up without this.
  WinTextClear;

  try {
  Fs->update_win=&UpdateWin;
  do {
    Init;
    ch=0;
    do {
      while (msg_code=ScanMsg(&p1,&p2,1<<MSG_KEY_DOWN|1<<MSG_KEY_UP)) {
        ch=p1; sc=p2;
        if (msg_code==MSG_KEY_DOWN) {
          switch (ch) {
            case CH_SPACE:
              Fire;
              break;
            case 0:
              switch (sc.u1[0]) {
                case SC_CURSOR_RIGHT:
                  man_theta+=pi/32;
                  break;
                case SC_CURSOR_LEFT:
                  man_theta-=pi/32;
                  break;
                case SC_CURSOR_UP:
                  MoveMan(man_theta);
                  break;
                case SC_CURSOR_DOWN:
                  MoveMan(man_theta+pi);
                  break;
              }
              break;
          }
        }
      }
      WinSync; //msgs are only queued by winmngr
    } while (ch!=CH_ESC && ch!=CH_CR && ch!=CH_CTRLQ);
  } while (ch!=CH_ESC && ch!=CH_CTRLQ);
  } catch
    Fs->catch_except=TRUE;
  SettingsPop;
  CleanUp;
  MenuPop;
  AcctRegWriteBranch("LT/FPS","F8 best_score=%5.4f;\r\n",best_score);
}

FPS;