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



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







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




















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









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



























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












RegDft("TempleOS/Varoom","F64 best_score=9999;\n");
RegExe("TempleOS/Varoom");

F64 distance,t0,tf;
Bool game_over;

#define BORDER          7500
#define RADIUS          10000
#define WIDTH           2000
#define SHOULDER        200
#define D_theta         (2*pi/360)   //Curve track slice is one degree.
#define D_S             (2*pi*RADIUS/360) //Straight track is degree at 10000.
#define DIPS            5
#define DIP_DEPTH       50
class Track
{
  Track *next,*last;
  I32 num;
  CColorROPU16 c,pad;
  I64 x,z;
  F64 theta,d;
  CD3I32 left[4],center[4],right[4];
} track_head,
  *track_start[MP_PROCESSORS_NUM],*track_end[MP_PROCESSORS_NUM];

CDC *track_map;
#define MAP_BITS        9
I64 t_minx,t_maxx,t_minz,t_maxz;

#define BUSHES_NUM      512
class Bush
{
  CD3I32 p;
  Bool sym,pad[3];
  U8 *img;
} b[BUSHES_NUM];

#define CARS_NUM        8
class Car
{
  CD3I32 p;
  F64 theta,dtheta,speed;
  U8 *img;
  Track *t;
} c[CARS_NUM];

I64 DipY(I64 x,I64 z)
{
  F64 m,a;
  R2P(&m,&a,x,z);
  return DIP_DEPTH*m*Cos(DIPS*a)/RADIUS;
}

#define CAR_LENGTH      400

F64 Diptheta(I64 x,I64 z,F64 theta)
{
  F64 y_front,y_back;
  y_front=DipY(x-CAR_LENGTH/2*Cos(theta),z-CAR_LENGTH/2*Sin(theta));
  y_back =DipY(x+CAR_LENGTH/2*Cos(theta),z+CAR_LENGTH/2*Sin(theta));
  return ASin((y_front-y_back)/CAR_LENGTH);
}

Track *TrackFind(Track *_tmpt,I64 x,I64 z)
{
  Track *res=_tmpt,*tmpt;
  I64 dd,best=SqrI64(res->x-x)+SqrI64(res->z-z);

  tmpt=_tmpt;
  while (TRUE) {
    tmpt=tmpt->next;
    if (tmpt==&track_head)
      tmpt=tmpt->next;
    dd=SqrI64(tmpt->x-x)+SqrI64(tmpt->z-z);
    if (dd<best) {
      best=dd;
      res=tmpt;
    } else
      break;
  }
  tmpt=_tmpt;
  while (TRUE) {
    tmpt=tmpt->last;
    if (tmpt==&track_head)
      tmpt=tmpt->last;
    dd=SqrI64(tmpt->x-x)+SqrI64(tmpt->z-z);
    if (dd<best) {
      best=dd;
      res=tmpt;
    } else
      break;
  }
  return res;
}

U0 TrackSlice(F64 *_x,F64 *_z,F64 theta,F64 d)
{
  F64 x=*_x,z=*_z,c=Cos(theta),s=Sin(theta),dx=d*s,dz=-d*c;
  Track *tmpt,*last=track_head.last;
  if (last==&track_head)
    last=NULL;

  tmpt=CAlloc(sizeof(Track));
  if (last) {
    MemCpy(&tmpt->center[0],&last->center[3],sizeof(CD3I32));
    MemCpy(&tmpt->center[1],&last->center[2],sizeof(CD3I32));
  }
  tmpt->center[2].x=x+(WIDTH/2)*c+dx;
  tmpt->center[2].z=z+(WIDTH/2)*s+dz;
  tmpt->center[2].y=DipY(tmpt->center[2].x,tmpt->center[2].z);
  tmpt->center[3].x=x-(WIDTH/2)*c+dx;
  tmpt->center[3].z=z-(WIDTH/2)*s+dz;
  tmpt->center[3].y=DipY(tmpt->center[3].x,tmpt->center[3].z);

  if (last) {
    MemCpy(&tmpt->left[0],&last->left[3],sizeof(CD3I32));
    MemCpy(&tmpt->left[1],&last->left[2],sizeof(CD3I32));
  }
  tmpt->left[2].x=x-(WIDTH/2)*c+dx;
  tmpt->left[2].z=z-(WIDTH/2)*s+dz;
  tmpt->left[2].y=DipY(tmpt->left[2].x,tmpt->left[2].z);
  tmpt->left[3].x=x-(WIDTH/2+SHOULDER)*c+dx;
  tmpt->left[3].z=z-(WIDTH/2+SHOULDER)*s+dz;
  tmpt->left[3].y=DipY(tmpt->left[3].x,tmpt->left[3].z);

  if (last) {
    MemCpy(&tmpt->right[0],&last->right[3],sizeof(CD3I32));
    MemCpy(&tmpt->right[1],&last->right[2],sizeof(CD3I32));
  }
  tmpt->right[2].x=x+(WIDTH/2+SHOULDER)*c+dx;
  tmpt->right[2].z=z+(WIDTH/2+SHOULDER)*s+dz;
  tmpt->right[2].y=DipY(tmpt->right[2].x,tmpt->right[2].z);
  tmpt->right[3].x=x+(WIDTH/2)*c+dx;
  tmpt->right[3].z=z+(WIDTH/2)*s+dz;
  tmpt->right[3].y=DipY(tmpt->right[3].x,tmpt->right[3].z);

  tmpt->x=x;
  tmpt->z=z;
  tmpt->theta=theta;
  tmpt->num=track_head.last->num+1;
  tmpt->d  =track_head.last->d+d;

  QueIns(tmpt,track_head.last);

  if (tmpt->num&1)
    tmpt->c=RED;
  else
    tmpt->c=WHITE;

  if (x<t_minx) t_minx=x;
  if (x>t_maxx) t_maxx=x;
  if (z<t_minz) t_minz=z;
  if (z>t_maxz) t_maxz=z;

  x+=dx;
  *_x=x;
  z+=dz;
  *_z=z;

  if (x<t_minx) t_minx=x;
  if (x>t_maxx) t_maxx=x;
  if (z<t_minz) t_minz=z;
  if (z>t_maxz) t_maxz=z;
}

U0 CoupleEnds()
{
  Track *first=track_head.next,*last=track_head.last;
  MemCpy(&first->center[0],&last->center[3],sizeof(CD3I32));
  MemCpy(&first->center[1],&last->center[2],sizeof(CD3I32));
  MemCpy(&first->left[0]  ,&last->left[3]  ,sizeof(CD3I32));
  MemCpy(&first->left[1]  ,&last->left[2]  ,sizeof(CD3I32));
  MemCpy(&first->right[0] ,&last->right[3] ,sizeof(CD3I32));
  MemCpy(&first->right[1] ,&last->right[2] ,sizeof(CD3I32));
}

U0 InitTrack()
{
  I64 i,j;
  Track *tmpt;
  F64 x,z,theta,d;

  MemSet(&track_head,0,sizeof(Track));
  QueInit(&track_head);

  t_minx=t_minz=I64_MAX;
  t_maxx=t_maxz=I64_MIN;

  x=0; z=0; theta=0;
  for (d=0;d<6*RADIUS;d+=D_S)
    TrackSlice(&x,&z,theta,D_S);
  for (i=0;i<180;i++,theta+=D_theta)
    TrackSlice(&x,&z,theta,D_theta*RADIUS);
  for (d=0;d<RADIUS;d+=D_S)
    TrackSlice(&x,&z,theta,D_S);
  for (i=0;i<90;i++,theta-=D_theta)
    TrackSlice(&x,&z,theta,D_theta*RADIUS);
  for (i=0;i<180;i++,theta+=D_theta)
    TrackSlice(&x,&z,theta,D_theta*RADIUS);
  for (i=0;i<90;i++,theta-=D_theta)
    TrackSlice(&x,&z,theta,D_theta*RADIUS);
  for (d=0;d<RADIUS;d+=D_S)
    TrackSlice(&x,&z,theta,D_S);
  for (i=0;i<180;i++,theta+=D_theta)
    TrackSlice(&x,&z,theta,D_theta*RADIUS);

  CoupleEnds;

  tmpt=track_head.next;
  for (i=0;i<mp_cnt;i++) {
    j=(i+1)*track_head.last->num/mp_cnt+1;
    track_start[i]=tmpt;
    while (tmpt!=&track_head && tmpt->num!=j)
      tmpt=tmpt->next;
    track_end[i]=tmpt;
  }

  t_minx-=BORDER; t_minz-=BORDER;
  t_maxx+=BORDER; t_maxz+=BORDER;
  track_map=DCNew((t_maxx-t_minx+1<<MAP_BITS-1)>>MAP_BITS,
        (t_maxz-t_minz+1<<MAP_BITS-1)>>MAP_BITS);

  track_map->color=LTGRAY;
  GrRect(track_map,0,0,track_map->width,track_map->height);
  tmpt=track_head.next;
  track_map->color=YELLOW;
  track_map->thick=3;
  while (tmpt!=&track_head) {
    GrPlot3(track_map,track_map->width-(tmpt->x-t_minx)>>MAP_BITS,
          (tmpt->z-t_minz)>>MAP_BITS,0);
    tmpt=tmpt->next;
  }
}

#define HORIZON_DIP     200

Bool PrepPoly(CD3I32 *p,I64 *r,I64 cx,I64 h,CD3I32 *poly)
{
  I64 x,y,z,i;
  F64 s;
  for (i=0;i<4;i++) {
    x=p[i].x-c[0].p.x;
    y=p[i].y-c[0].p.y;
    z=p[i].z-c[0].p.z;
    Mat4x4MulXYZ(r,&x,&y,&z);
    s=100.0/(AbsI64(z)+50);
    poly[i].y=s*y+h;
    if (z<-200 || !(-h<poly[i].y<2*h))
      return FALSE;
    poly[i].x=s*x+cx;
    poly[i].z=z+GR_Z_ALL;
  }
  return TRUE;
}

I64 mp_not_done_flags;

U0 MPUpdateWin(CDC *dc2)
{
  CTask *task=dc2->win_task;
  I64   i,x,y,z,
        w=task->pix_width,h=task->pix_height,r[16],cx=w>>1;
  F64   s,dip_theta=Diptheta(c[0].p.x,c[0].p.z,c[0].theta);
  Car   *tmpc;
  CD3I32 poly[4];
  Track *tmpt,*tmpt1;
  CDC   *dc=DCAlias(gr.dc2,task);

  Mat4x4IdentEqu(r);
  Mat4x4RotY(r,pi-c[0].theta);
  Mat4x4RotX(r,75*pi/180-dip_theta);

  dc->depth_buf=dc2->depth_buf;

  //Track
  tmpt =track_start[Gs->num];
  tmpt1=track_end  [Gs->num];
  while (tmpt!=tmpt1) {
    dc->color=DKGRAY;
    if (PrepPoly(&tmpt->center,r,cx,h,poly)) {
      GrFillPoly3(dc,4,poly);
      dc->color=tmpt->c;
      if (PrepPoly(&tmpt->left,r,cx,h,poly))
        GrFillPoly3(dc,4,poly);
      if (PrepPoly(&tmpt->right,r,cx,h,poly))
        GrFillPoly3(dc,4,poly);
    }
    tmpt=tmpt->next;
  }

  dc->flags|=DCF_TRANSFORMATION;
  for (i=Gs->num;i<BUSHES_NUM;i+=mp_cnt) {
    x=b[i].p.x-c[0].p.x; y=b[i].p.y-c[0].p.y; z=b[i].p.z-c[0].p.z;
    Mat4x4MulXYZ(r,&x,&y,&z);
    if (z>0) {
      s=100.0/(AbsI64(z)+50);
      Mat4x4IdentEqu(dc->r);
      Mat4x4Scale(dc->r,s*2);
      DCMat4x4Set(dc,dc->r);
      if (b[i].sym) {
        dc->flags|=DCF_SYMMETRY|DCF_JUST_MIRROR;
        DCSymmetrySet(dc,s*x+cx,s*y+h,s*x+cx,s*y+h+10);
      }
      Sprite3B(dc,s*x+cx,s*y+h,z+GR_Z_ALL,b[i].img);
      dc->flags&=~(DCF_SYMMETRY|DCF_JUST_MIRROR);
    }
  }
  for (i=Gs->num+1;i<CARS_NUM;i+=mp_cnt) {
    tmpc=&c[i];
    x=tmpc->p.x-c[0].p.x; y=tmpc->p.y-c[0].p.y; z=tmpc->p.z-c[0].p.z;
    Mat4x4MulXYZ(r,&x,&y,&z);
    if (z>0) {
      s=100.0/(AbsI64(z)+50);
      Mat4x4IdentEqu(dc->r);
      Mat4x4Scale(dc->r,s*2);
      Mat4x4RotX(dc->r,Diptheta(tmpc->p.x,tmpc->p.z,-tmpc->theta));
      Mat4x4RotY(dc->r,tmpc->theta-c[0].theta);
      DCMat4x4Set(dc,dc->r);
      Sprite3B(dc,s*x+cx,s*y+h,z+GR_Z_ALL,tmpc->img);
    }
  }
  dc->depth_buf=NULL;
  DCDel(dc);
  LBtr(&mp_not_done_flags,Gs->num);
}

U0 VRTransform(CDC *dc,I64 *x,I64 *y,I64 *z)
{
  I64 zz;
  Mat4x4MulXYZ(dc->r,x,y,z);
  zz=400+*z;
  if (zz<1) zz=1;
  *x=400* *x/zz;
  *y=400* *y/zz;
  *x+=dc->x;
  *y+=dc->y;
  *z+=dc->z;
}

U0 DrawIt(CTask *task,CDC *dc)
{
  I64   i,x,y,z,
        w=task->pix_width,
        h=task->pix_height,r[16],
        cx=w>>1;
  F64   s,dip_theta=Diptheta(c[0].p.x,c[0].p.z,c[0].theta);
  Car   *tmpc=&c[0];

  dc->color=LTCYAN;
  GrRect(dc,0,0,w,HORIZON_DIP*Sin(dip_theta)+FONT_HEIGHT*4.5);

  Mat4x4IdentEqu(r);
  Mat4x4RotY(r,pi-c[0].theta);
  Mat4x4RotX(r,75*pi/180-dip_theta);

  DCDepthBufAlloc(dc);

  //Sun
  x=c[0].p.x; y=0; z=1000000-c[0].p.z;
  Mat4x4MulXYZ(r,&x,&y,&z);
  s=100.0/(AbsI64(z)+50);
  if (y<0) {
    dc->color=BROWN;
    GrCircle(dc,s*x+cx,15+HORIZON_DIP*Sin(dip_theta),15);
    dc->color=YELLOW;
    GrFloodFill(dc,s*x+cx,15+HORIZON_DIP*Sin(dip_theta));
  }

  mp_not_done_flags=1<<mp_cnt-1;
  for (i=0;i<mp_cnt;i++)
    JobQue(&MPUpdateWin,dc,i);
  while (mp_not_done_flags)
    Yield;

  Mat4x4IdentEqu(r);
  Mat4x4RotY(r,tmpc->dtheta);
  Mat4x4RotX(r,0.4-8*dip_theta); //Made this up
  dc->transform=&VRTransform;
  dc->x=task->pix_width>>1;
  dc->y=task->pix_height-150;
  dc->z=GR_Z_ALL;
  Sprite3Mat4x4B(dc,0,0,-100,c[0].img,r);

  //Map
  GrBlot(dc,w-track_map->width,h-track_map->height,track_map);

  dc->thick=2;
  for (i=0;i<CARS_NUM;i++) {
    if (i)
      dc->color=LTPURPLE;
    else
      dc->color=LTCYAN;
    GrPlot3(dc,w-(c[i].p.x-t_minx)>>MAP_BITS,
               h-track_map->height+(c[i].p.z-t_minz)>>MAP_BITS,0);
  }

  if (game_over) {
    dc->color=LTRED;
    if (tf) {
      s=tf-t0;
      if (Blink)
        GrPrint(dc,(w-FONT_WIDTH*14)/2,(h-FONT_HEIGHT)/2,"Game Completed");
    } else {
      s=99.9;
      if (Blink)
        GrPrint(dc,(w-FONT_WIDTH*9)/2,(h-FONT_HEIGHT)/2,"Game Over");
    }
  } else
    s=tS-t0;
  dc->color=BLACK;
  GrPrint(dc,0,0,"%0.1f%% Time:%0.2f Best:%0.2f",
        100.0*distance/track_head.last->d,s,best_score);
}

U0 AnimateTask(I64)
{
  Car  *tmpc;
  I64 i,x,z;
  Bool on_track;
  Track *tmpt,*tmpt2;
  while (TRUE) {
    if (!game_over)
      Snd(12.0*Log2(c[0].speed/500+0.7));
    else
      Snd;
    for (i=0;i<CARS_NUM;i++) {
      tmpc=&c[i];
      tmpc->p.x-=0.01*tmpc->speed*Cos(tmpc->theta-pi/2);
      tmpc->p.z+=0.01*tmpc->speed*Sin(tmpc->theta-pi/2);
      tmpt=TrackFind(tmpc->t,tmpc->p.x,tmpc->p.z);
      if (i) {
        if (tmpt!=tmpc->t) {
          tmpt2=tmpt->next;
          if (tmpt2==&track_head)
            tmpt2=tmpt2->next;
          tmpc->theta=Arg(-tmpt2->z+tmpc->p.z,-tmpt2->x+tmpc->p.x);
        }
      } else {
        tmpc->theta+=0.01*tmpc->dtheta;
        x=track_map->width-(tmpc->p.x-t_minx)>>MAP_BITS;
        z=(tmpc->p.z-t_minz)>>MAP_BITS;
        if (GrPeek(track_map,x,z)!=YELLOW) {
          on_track=FALSE;
          tmpc->speed-=0.01*tmpc->speed;
          if (tmpc->speed<0) tmpc->speed=0;
        } else
          on_track=TRUE;
      }
      tmpc->t=tmpt;
      tmpc->p.y=DipY(tmpc->p.x,tmpc->p.z);
    }
    if (!game_over && on_track) {
      for (i=1;i<CARS_NUM;i++)
        if (D3I32DistSqr(&c[i].p,&c[0].p)<CAR_LENGTH>>1*CAR_LENGTH>>1) {
          game_over=TRUE;
          Noise(500,22,34);
          Sleep(500);
          break;
        }
      if (!game_over) {
        distance+=0.01*c[0].speed;
        if (distance>track_head.last->d&& c[0].t->num<track_head.last->num>>1) {
          tf=tS;
          game_over=TRUE;
          Beep;
          if (tf-t0<best_score) {
            best_score=tf-t0;
            Beep;
          }
        }
      }
    }
    Sleep(10);
  }
}

U8 *imgs[8]={<1>,<1>,<2>,<2>,<3>,<4>,<4>,<4>};


U0 InitBushes()
{
  Bush *tmpb;
  I64 i,j,x,z;
  track_map->color=LTGREEN;
  track_map->thick=1;
  for (i=0;i<BUSHES_NUM;i++) {
    tmpb=&b[i];

ib_restart:
    tmpb->p.x=Rand*(t_maxx-t_minx)+t_minx;
    tmpb->p.z=Rand*(t_maxz-t_minz)+t_minz;
    x=track_map->width-(tmpb->p.x-t_minx)>>MAP_BITS;
    z=(tmpb->p.z-t_minz)>>MAP_BITS;
    for (j=0;j<8;j++)
      if (GrPeek(track_map,x+gr_x_offsets[j],z+gr_y_offsets[j])!=LTGRAY)
        goto ib_restart;

    GrPlot(track_map,x,z);
    tmpb->p.y=DipY(tmpb->p.x,tmpb->p.z);
    tmpb->sym=RandU16&1;
    tmpb->img=imgs[i&7];
  }
}


U0 Init()
{
  Car   *tmpc;
  Track *tmpt;
  F64   d;
  I64   i;

  InitTrack;
  InitBushes;
  tmpt=track_head.next;
  for (i=0;i<CARS_NUM;i++) {
    tmpc=&c[i];
    tmpc->t=tmpt;
    tmpc->p.x=tmpt->x;
    tmpc->p.z=tmpt->z;
    tmpc->p.y=DipY(tmpc->p.x,tmpc->p.z);
    tmpc->theta  =-tmpt->theta;
    tmpc->dtheta=0;
    if (!i) {
      tmpc->img=<5>;
      tmpc->speed=0;
    } else {
      tmpc->img=<6>;
      tmpc->speed=2500.0;
    }
    d=(i+1)*track_head.last->d/CARS_NUM;
    while (tmpt->next!=&track_head && tmpt->d<d)
      tmpt=tmpt->next;
  }
  distance=0;
  tf=0;
  t0=tS;
  game_over=FALSE;
}

U0 CleanUp()
{
  while (mp_not_done_flags)
    Yield;
  QueDel(&track_head,TRUE);
  DCDel(track_map);
}

U0 Varoom()
{
  I64 sc;
  MenuPush(
        "File {"
        "  Abort(,CH_SHIFT_ESC);"
        "  Exit(,CH_ESC);"
        "}"
        "Play {"
        "  Restart(,'\n');"
        "  Accelerator(,,SC_CURSOR_UP);"
        "  Brake(,,SC_CURSOR_DOWN);"
        "  Left(,,SC_CURSOR_LEFT);"
        "  Right(,,SC_CURSOR_RIGHT);"
        "}"
        );
  SettingsPush; //See SettingsPush
  try {
    Fs->text_attr=YELLOW<<4+BLUE;
    Fs->win_inhibit=WIG_TASK_DFT-WIF_SELF_FOCUS
          -WIF_SELF_GRAB_SCROLL-WIF_FOCUS_TASK_MENU;
    AutoComplete;
    WinBorder;
    WinMax;
    DocCursor;
    DocClear;
    Init;
    Fs->draw_it=&DrawIt;
    Fs->animate_task=Spawn(&AnimateTask,NULL,"Animate",,Fs);
    while (TRUE)
      switch (GetKey(&sc)) {
        case 0:
          switch (sc.u8[0]) {
            case SC_CURSOR_LEFT:
              c[0].dtheta-=pi/60;
              break;
            case SC_CURSOR_RIGHT:
              c[0].dtheta+=pi/60;
              break;
            case SC_CURSOR_UP:
              c[0].speed+=300;
              break;
            case SC_CURSOR_DOWN:
              c[0].speed-=900;
              if (c[0].speed<0) c[0].speed=0;
              break;
          }
          break;
        case '\n':
          CleanUp;
          Init;
          break;
        case CH_SHIFT_ESC:
        case CH_ESC:
          goto vr_done;
      }
vr_done: //Don't goto out of try
  } catch
    PutExcept;
  SettingsPop;
  CleanUp;
  MenuPop;
  RegWrite("TempleOS/Varoom","F64 best_score=%5.4f;\n",best_score);
}

Varoom;