//The ball and men were generated
//with ::/Apps/GrModels/Run.HC.
//They were cut-and-pasted here.


                <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 */






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






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






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






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






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






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





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






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





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





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






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






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






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






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






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






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






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



class Frame
{
  U8 *img[2];
  F64 dt;
};

#define COURT_BORDER            10
#define COLLISION_DAMP          0.8
#define AIR_VISCOSITY           0.1

#define GRAVITY_ACCELERATION    500
#define SHOT_VELOCITY           400
#define DRIBBLE_T               0.25
#define MAN_VELOCITY            150
#define MAN_SQR_RADIUS          (20*20)
#define FOUL_VELOCITY_THRESHOLD 50
#define JUMP_VELOCITY           250
#define ROLL_VELOCITY_THRESHOLD 100
#define RANDOM_MAN_ACCELERATION 30

#define HEAD_Z_OFFSET   200
#define HAND_X_OFFSET   30
#define HAND_Y_OFFSET   20
#define HAND_SQR_OFFSET \
        (HAND_X_OFFSET*HAND_X_OFFSET+HAND_Y_OFFSET*HAND_Y_OFFSET)
#define HAND_Z_OFFSET   110

#define FIRST_STANDING          0

#define RUNNING_IMGS_NUM        4
#define FIRST_RUNNING   0
#define LAST_RUNNING    (FIRST_RUNNING+RUNNING_IMGS_NUM-1)

#define SHOOTING_IMGS_NUM       5
#define FIRST_SHOOTING  (LAST_RUNNING+1)
#define LAST_SHOOTING   (FIRST_SHOOTING+SHOOTING_IMGS_NUM-1)

#define DRIBBLING_IMGS_NUM      4
#define FIRST_DRIBBLING (LAST_SHOOTING+1)
#define LAST_DRIBBLING  (FIRST_DRIBBLING+DRIBBLING_IMGS_NUM-1)

#define STOPPED_DRIBBLING_IMGS_NUM      2
#define FIRST_STOPPED_DRIBBLING (LAST_DRIBBLING+1)
#define LAST_STOPPED_DRIBBLING  \
        (FIRST_STOPPED_DRIBBLING+STOPPED_DRIBBLING_IMGS_NUM-1)

Frame imgs[LAST_STOPPED_DRIBBLING+1]={
  {{<6>,<7>},2*DRIBBLE_T/RUNNING_IMGS_NUM},
  {{<2>,<3>},2*DRIBBLE_T/RUNNING_IMGS_NUM},
  {{<6>,<7>},2*DRIBBLE_T/RUNNING_IMGS_NUM},
  {{<4>,<5>},2*DRIBBLE_T/RUNNING_IMGS_NUM},
  {{<8>,<9>},0.1},{{<10>,<11>},0.2},
  {{<12>,<13>},0.2},{{<12>,<13>},0.1},{{<14>,<15>},0.1},
  {{<20>,<21>},2*DRIBBLE_T/DRIBBLING_IMGS_NUM},
  {{<16>,<17>},2*DRIBBLE_T/DRIBBLING_IMGS_NUM},
  {{<20>,<21>},2*DRIBBLE_T/DRIBBLING_IMGS_NUM},
  {{<18>,<19>},2*DRIBBLE_T/DRIBBLING_IMGS_NUM},
  {{<20>,<21>},DRIBBLE_T/STOPPED_DRIBBLING_IMGS_NUM},
  {{<22>,<23>},DRIBBLE_T/STOPPED_DRIBBLING_IMGS_NUM},
};

RegDft("TempleOS/KeepAway","I64 best_score0=0,best_score1=9999;\n");
RegExe("TempleOS/KeepAway");

F64 game_t_end,foul_t_end;
I64 score0,score1;

#define PER_SIDE_NUM    3
#define OBJS_NUM        (PER_SIDE_NUM*2+1)
Bool someone_shooting,someone_has_ball;
F64 shot_land_t;
        
class Obj
{
  I64 team; //-1 is ball
  F64 x,y,z,DxDt,DyDt,DzDt,theta,radius,stolen_t0;
  F64 get_ball_dt,get_ball_theta,nearest_man_dd,last_t0,next_t0,foul_t0;
  I64 last_img,next_img;
  Bool stopped,shooting,has_ball,nearest_ball,pad[4];
} objs[OBJS_NUM],*ball,*human,*last_owner;

/*Just to be different, I didn't use the built-in
DCF_TRANSFORMATION flag in this game.
Instead, I chose a 45 degree angle
between Y and Z as the view point.
If I had used the transform, I would
have to make all my men taller.
This is a little simpler, and faster,
but adds lots of factor 2 vals.

I also didn't use the CMathODE feat,
just to be different.
*/

U0 DrawObj(CDC *dc,Obj *o,F64 tt)
{
  U8 *tmps;
  F64 r1=Max(9-0.1*o->z,1),r2=Max(r1/4,1);

  if (o==human)
    dc->color=LTRED;
  else
    dc->color=BLACK;
  GrEllipse(dc,o->x,o->y/2,r1,r2);
  GrFloodFill(dc,o->x,o->y/2);

  if (o==ball)
    Sprite3(dc,o->x,(o->y-o->z)/2,GR_Z_ALL-o->y,<1>);
  else {
    tmps=SpriteInterpolate((tt-o->last_t0)/(o->next_t0-o->last_t0),
          imgs[o->last_img].img[o->team],
          imgs[o->next_img].img[o->team]);
    Sprite3YB(dc,o->x,(o->y-o->z)/2,GR_Z_ALL-o->y,tmps,o->theta);
    Free(tmps);
  }
}

I64 ObjCompare(Obj *o1,Obj *o2)
{
  return o1->y-o2->y;
}

U0 DrawIt(CTask *task,CDC *dc)
{
  F64 tt=tS,d,d_down,d_up;
  I64 i;
  Obj *o_sort[OBJS_NUM],*o;

  DCDepthBufAlloc(dc);
  dc->ls.x=10000;
  dc->ls.y=60000;
  dc->ls.z=10000;
  d=65535/D3I32Norm(&dc->ls);
  dc->ls.x*=d;
  dc->ls.y*=d;
  dc->ls.z*=d;

  dc->thick=2;
  dc->color=RED;
  GrBorder(dc,COURT_BORDER,COURT_BORDER,
        task->pix_width -1-COURT_BORDER,
        task->pix_height-1-COURT_BORDER);
  for (i=0;i<OBJS_NUM;i++) {
    o=o_sort[i]=&objs[i];
    if (o!=ball) {
      if (o->has_ball) {
        ball->x=o->x+HAND_X_OFFSET*Cos(o->theta-pi/2)+HAND_Y_OFFSET*Cos(o->theta);
//The factor 2 is because the man is not transformed.
        ball->y=o->y+HAND_X_OFFSET*Sin(o->theta-pi/2)/2+HAND_Y_OFFSET*Sin(o->theta)/2;
        if (ball->z+ball->radius*2>o->z+HAND_Z_OFFSET)
          ball->z=o->z+HAND_Z_OFFSET-ball->radius*2;
      } else if (o->shooting) {
        ball->x=o->x;
        ball->y=o->y;
        ball->z=o->z+HEAD_Z_OFFSET;
      }
      if (tt>o->next_t0) {
        if (o->has_ball && (ball->z+ball->radius*2>=o->z+HAND_Z_OFFSET ||
              Abs(ball->DzDt)<30)) {
//This is an approximation.  My instinct tells me the viscosity term
          //needs an Exp().  However, we should be syncronized to img frames,
          //so we don't have to be perfect.
          d_down=1.0;
          d_up  =1.0/COLLISION_DAMP;
//Up bounce takes higher % because speed lost in collision.
          ball->DzDt=-((d_down+d_up)*
                (o->z+HAND_Z_OFFSET-ball->radius*4)/(1.0-AIR_VISCOSITY)+
                0.5*GRAVITY_ACCELERATION*(
                Sqr(DRIBBLE_T*d_up/(d_down+d_up))-
                Sqr(DRIBBLE_T*d_down/(d_down+d_up)) ))/DRIBBLE_T;
        }
        o->last_t0=tt;
        o->last_img=o->next_img++;
        if (o->stopped) {
          if (o->has_ball) {
            if (!(FIRST_STOPPED_DRIBBLING<=o->next_img<=LAST_STOPPED_DRIBBLING))
              o->next_img=FIRST_STOPPED_DRIBBLING;
          } else
            o->next_img=FIRST_STANDING;
          o->stopped=FALSE;
        } else if (o->shooting) {
          if (!(FIRST_SHOOTING<=o->last_img<=LAST_SHOOTING))
            o->next_img=FIRST_SHOOTING;
          if (o->next_img>LAST_SHOOTING) {
            o->next_img=FIRST_STANDING;
            someone_has_ball=someone_shooting=o->has_ball=o->shooting=FALSE;
            ball->DxDt=o->DxDt+SHOT_VELOCITY/sqrt2*Cos(o->theta-pi/2);
            ball->DyDt=o->DyDt+SHOT_VELOCITY/sqrt2*Sin(o->theta-pi/2);
            ball->DzDt=o->DzDt+SHOT_VELOCITY/sqrt2;
            shot_land_t=tt+(ball->DzDt+Sqrt(Sqr(ball->DzDt)+
                2*GRAVITY_ACCELERATION*ball->z))/GRAVITY_ACCELERATION;
          } else {
            ball->DxDt=0;
            ball->DyDt=0;
            ball->DzDt=0;
          }
        } else if (o->has_ball) {
          if (FIRST_RUNNING<=o->next_img<=LAST_RUNNING)
            o->next_img+=FIRST_DRIBBLING-FIRST_RUNNING;
          if (!(FIRST_DRIBBLING<=o->next_img<=LAST_DRIBBLING))
            o->next_img=FIRST_DRIBBLING;
        } else {
          if (FIRST_DRIBBLING<=o->next_img<=LAST_DRIBBLING)
            o->next_img+=FIRST_RUNNING-FIRST_DRIBBLING;
          if (!(FIRST_RUNNING<=o->next_img<=LAST_RUNNING))
            o->next_img=FIRST_RUNNING;
        }
        o->next_t0+=imgs[o->last_img].dt;
        if (o->next_t0<=tt)
          o->next_t0=tt+imgs[o->last_img].dt;
      }
    }
  }

  QSortI64(o_sort,OBJS_NUM,&ObjCompare);
  for (i=0;i<OBJS_NUM;i++)
    DrawObj(dc,o_sort[i],tt);
  tt=(game_t_end-tS)/60;
  if (tt<=0) {
    dc->color=RED;
    tt=0;
    if (Blink)
      GrPrint(dc,(task->pix_width-FONT_WIDTH*9)>>1,
            (task->pix_height-FONT_HEIGHT)>>1,"Game Over");
  } else {
    if (tS<foul_t_end) {
      dc->color=LTRED;
      if (Blink)
        GrPrint(dc,(task->pix_width-FONT_WIDTH*4)>>1,
              (task->pix_height-FONT_HEIGHT)>>1,"Foul");
    }
    dc->color=BLACK;
  }
  GrPrint(dc,0,0,"Time:%d:%04.1f  Score:",ToI64(tt),(tt-ToI64(tt))*60);
  GrPrint  (dc,FONT_WIDTH*27,0,"Best Score:");

  dc->color=LTCYAN;
  GrPrint(dc,FONT_WIDTH*20,0,"%02d",score0);
  dc->color=LTPURPLE;
  GrPrint(dc,FONT_WIDTH*23,0,"%02d",score1);

  dc->color=LTCYAN;
  GrPrint(dc,FONT_WIDTH*39,0,"%02d",best_score0);
  dc->color=LTPURPLE;
  GrPrint(dc,FONT_WIDTH*42,0,"%02d",best_score1);
}

U0 Shoot(Obj *o)
{
  if (!someone_shooting && o->has_ball) {
    someone_shooting=o->stopped=o->shooting=TRUE;
    o->has_ball=FALSE;
  }
}

U0 AnimateTask(CTask *parent_task)
{
  F64 d,dx,dy,dt,dx2,dy2,t0=tS;
  I64 i,j;
  Bool gets_ball;
  Obj *o,*nearest_ball[2];
  while (TRUE) {
    dt=tS-t0;
    t0=tS;

    if (game_t_end && game_t_end<t0) {
      game_t_end=0;
      Beep;
      if (score0-score1>best_score0-best_score1) {
        best_score0=score0;
        best_score1=score1;
        Snd(86);Sleep(100); Snd;Sleep(100);
        Snd(86);Sleep(100); Snd;Sleep(100);
      }
    }

    if (game_t_end) {
      MemSet(&nearest_ball,0,sizeof(nearest_ball));
      for (i=0;i<OBJS_NUM;i++) {
        o=&objs[i];
        o->nearest_ball=FALSE;
        if (o!=ball) {
          d=0;
          for (j=0;j<5;j++) //Iterative estimate of how long to get ball.
            d=Sqrt(Sqr(ball->DxDt*d+ball->x-o->x)+
                  Sqr(ball->DyDt*d+ball->y-o->y))/MAN_VELOCITY;
          o->get_ball_dt=d;
          o->get_ball_theta=Arg(ball->DxDt*d+ball->x-o->x,
                ball->DyDt*d+ball->y-o->y);
          if (o!=last_owner && !nearest_ball[o->team] ||
                o->get_ball_dt<nearest_ball[o->team]->get_ball_dt)
            nearest_ball[o->team]=o;
        }
      }
      nearest_ball[0]->nearest_ball=TRUE;
      nearest_ball[1]->nearest_ball=TRUE;

      for (i=0;i<OBJS_NUM;i++) {
        o=&objs[i];
        if (o==ball) {
          o->x+=dt*o->DxDt;
          o->y+=dt*o->DyDt;
          if (!someone_shooting)
            o->z+=dt*(o->DzDt-0.5*GRAVITY_ACCELERATION*dt);
        } else {
          if (!o->has_ball) {
            if (t0-o->stolen_t0>2.0 && !someone_shooting) {
              dx=ball->x-o->x;
              dy=ball->y-o->y;
              if (dx*dx+dy*dy<HAND_SQR_OFFSET && ball->z<o->z+HAND_Z_OFFSET) {
                gets_ball=TRUE;
                for (j=0;j<PER_SIDE_NUM*2;j++)
                  if (j!=i && objs[j].has_ball) {
                    if (Rand<2.0*dt) {
                      objs[j].stolen_t0=t0;
                      objs[j].has_ball=FALSE;
                    } else
                      gets_ball=FALSE;
                  }
                if (gets_ball) {
                  someone_has_ball=o->has_ball=TRUE;
                  if (o!=last_owner) {
                    if (o->team) {
                      if (t0<shot_land_t+0.1)
                        score1+=6;
                      else
                        score1+=2;
                      Noise(250,74,74);
                    } else {
                      if (t0<shot_land_t+0.1)
                        score0+=6;
                      else
                        score0+=2;
                      Noise(250,86,86);
                    }
                    last_owner=o;
                  }
                }
              }
            }
          } else if (o!=human && Rand<0.25*dt)
            Shoot(o);
          if (!o->shooting) {
            if (o==human) {
              dx=(ms.pos.x-parent_task->pix_left-parent_task->scroll_x)-o->x;
              dy=(ms.pos.y-parent_task->pix_top-parent_task->scroll_y)*2-o->y;
            } else {
              if (!someone_has_ball && o->nearest_man_dd>4*MAN_SQR_RADIUS &&
                    o->nearest_ball) {
                dx=o->DxDt=MAN_VELOCITY*Cos(o->get_ball_theta);
                dy=o->DyDt=MAN_VELOCITY*Sin(o->get_ball_theta);
              } else {
                dx=o->DxDt+=RANDOM_MAN_ACCELERATION/sqrt2*RandI16/I16_MAX*dt;
                dy=o->DyDt+=RANDOM_MAN_ACCELERATION/sqrt2*RandI16/I16_MAX*dt;
              }
            }
            d=Sqrt(dx*dx+dy*dy);
            if (d>=1.0) {
              o->theta=Arg(dx,dy)+pi/2;
              dx*=MAN_VELOCITY/sqrt2*dt/d;
              dy*=MAN_VELOCITY/sqrt2*dt/d;
              o->nearest_man_dd=F64_MAX;
              for (j=0;j<PER_SIDE_NUM*2;j++)
                if (j!=i) {
                  dx2=objs[j].x-o->x;
                  dy2=objs[j].y-o->y;
                  d=Sqr(dx2)+Sqr(dy2);
                  if (d<o->nearest_man_dd)
                    o->nearest_man_dd=d;
                  if (d<MAN_SQR_RADIUS) {
                    if (d) {
                      d=Sqrt(d);
                      dx2/=d;
                      dy2/=d;
                    }
                    if (t0>o->foul_t0+0.15) {
                      d=(dx-objs[j].DxDt)*dx2+(dy-objs[j].DyDt)*dy2;
                      if (o==human && t0>o->foul_t0+1.0 &&
                            dt && d/dt>FOUL_VELOCITY_THRESHOLD &&
                            objs[j].team) {
                        Noise(250,62,62);
                        score1+=1;
                        foul_t_end=t0+1.0;
                      }
                      o->foul_t0=t0;
                    }
                  }
                }
              if (t0<o->foul_t0+0.15) {
                dx=-dx;
                dy=-dy;
              }
              o->x+=dx;
              o->y+=dy;
              o->stopped=FALSE;
            } else
              o->stopped=TRUE;
          }
          if (o->DzDt)
            o->z+=dt*(o->DzDt-0.5*GRAVITY_ACCELERATION*dt);
        }

        if (o->x+o->radius>=parent_task->pix_width-COURT_BORDER) {
          o->x=parent_task->pix_width-COURT_BORDER-1-o->radius;
          o->DxDt=-COLLISION_DAMP*o->DxDt;
          if (o==ball)
            Noise(10,74,86);
        }
        if (o->x-o->radius<COURT_BORDER) {
          o->x=COURT_BORDER+o->radius;
          o->DxDt=-COLLISION_DAMP*o->DxDt;
          if (o==ball)
            Noise(10,74,86);
        }

        if (o->y+o->radius*2>=(parent_task->pix_height-COURT_BORDER)*2) {
          o->y=(parent_task->pix_height-COURT_BORDER)*2-1-o->radius*2;
          o->DyDt=-COLLISION_DAMP*o->DyDt;
          if (o==ball)
            Noise(10,74,86);
        }
        if (o->y-o->radius*2<2*COURT_BORDER) {
          o->y=COURT_BORDER*2+o->radius*2;
          o->DyDt=-COLLISION_DAMP*o->DyDt;
          if (o==ball)
            Noise(10,74,86);
        }

        if (o->z-o->radius*2<0) {
          o->z=o->radius*2;
          o->DzDt=-COLLISION_DAMP*o->DzDt;
          if (o->DzDt>ROLL_VELOCITY_THRESHOLD)
            Noise(10,74,86);
          if (o!=ball)
            o->DzDt=0;
        } else if (o->z-o->radius*2>0)
          o->DzDt-=GRAVITY_ACCELERATION*dt;
        if (o==ball) {
          d=Exp(-AIR_VISCOSITY*dt);
          o->DxDt*=d;
          o->DyDt*=d;
          o->DzDt*=d;
        }
      }
    }
    Refresh;
  }
}

U0 Init()
{
  I64 i;
  someone_shooting=FALSE;
  shot_land_t=0;
  MemSet(&objs,0,sizeof(objs));
  for (i=0;i<PER_SIDE_NUM*2;i++) {
    objs[i].team=i&1;
    objs[i].x=Fs->pix_width/2;
    objs[i].y=2*Fs->pix_height/2;
    objs[i].next_img=objs[i].last_img=FIRST_RUNNING;
  }
  last_owner=NULL;
  human=&objs[0];
  ball =&objs[i];
  ball->team=-1;
  ball->x=0.5*Fs->pix_width/2;
  ball->y=0.5*2*Fs->pix_height/2;
  ball->radius=11;
  ball->z=ball->radius;
  score0=score1=0;
  game_t_end=tS+3*60;
  foul_t_end=0;
}

U0 KeepAway()
{
  I64 msg_code,arg1,arg2;

  PopUpOk(
        "Pass or hand-off to your team to score points.$FG$\n\n"
        "\t2 points for successful hand-off.\n"
        "\t6 points for successful pass.\n"
        "\t1 point penalty for foul.\n\n"
        "Left-Click\tto pass.\n\n"
        "Right-Click\tto jump.\n");
  SettingsPush; //See SettingsPush
  Fs->text_attr=BLACK+YELLOW<<4;
  Fs->win_inhibit|=WIG_DBL_CLICK;
  AutoComplete;
  WinBorder;
  WinMax;
  DocCursor;
  DocClear;

  MenuPush(
        "File {"
        "  Abort(,CH_SHIFT_ESC);"
        "  Exit(,CH_ESC);"
        "}"
        "Play {"
        "  Restart(,'\n');"
        "  Shoot(,CH_SPACE);"
        "  Jump(,'j');"
        "}"
        );

  Init;
  Fs->draw_it=&DrawIt;
  Fs->animate_task=Spawn(&AnimateTask,Fs,"Animate",,Fs);

  try {
    while (TRUE) {
      msg_code=GetMsg(&arg1,&arg2,
            1<<MSG_MS_L_DOWN|1<<MSG_MS_R_DOWN|1<<MSG_KEY_DOWN);
      switch (msg_code) {
        case MSG_MS_L_DOWN:
ka_shoot:
          Shoot(human);
          break;
        case MSG_MS_R_DOWN:
ka_jump:
          human->DzDt=JUMP_VELOCITY;
          break;
        case MSG_KEY_DOWN:
          switch (arg1) {
            case '\n':
              Init;
              break;
            case 'j':
              goto ka_jump;
            case CH_SPACE:
              goto ka_shoot;
            case CH_SHIFT_ESC:
            case CH_ESC:
              goto ka_done;
          }
          break;
      }
    }
ka_done: //Don't goto out of try
    GetMsg(,,1<<MSG_KEY_UP);
  } catch
    PutExcept;
  SettingsPop;
  MenuPop;
  RegWrite("TempleOS/KeepAway","I64 best_score0=%d,best_score1=%d;\n",
        best_score0,best_score1);
}