Trivial Solutions
                        Happy Programmers Make Us Happy

The LoseThos 64-bit PC Operating System
File:/LT/Apps/KeepAway/KeepAway.CPZ
This, and all LoseThos files, are public domain.  Do whatever you like.
God is watching; God is just; God is never out-done in generosity.  He talks to me!

LoseThos code compiles with the 64-bit LoseThos compiler/assembler I wrote.
Boot the LoseThos CD and you can compile it.  Not in Kansas anymore!

Click here for preliminary compiler information you need.

//The ball and men were generated
//with ::/LT/Apps/GrModels/Run.CPZ.
//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

/* This viscosity is way bigger than
real air viscosity.  In reality
air is approximated by V and V2
terms and is smaller.  However, I use
this to produce a rolling friction
value, too.  If you want to see my
best attempt at realistic physics
download SimStructure.

http://www.losethos.com/files/SimStrSetup.exe
Product Key: L00-W10-M70

This is just a video game. Relax.
*/
#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 NUM_RUNNING_IMGS        4
#define FIRST_RUNNING   0
#define LAST_RUNNING    (FIRST_RUNNING+NUM_RUNNING_IMGS-1)

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

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

#define NUM_STOPPED_DRIBBLING_IMGS      2
#define FIRST_STOPPED_DRIBBLING (LAST_DRIBBLING+1)
#define LAST_STOPPED_DRIBBLING  (FIRST_STOPPED_DRIBBLING+NUM_STOPPED_DRIBBLING_IMGS-1)

Frame man_imgs[LAST_STOPPED_DRIBBLING+1]={
{{<6>,<7>},2*DRIBBLE_T/NUM_RUNNING_IMGS},{{<2>,<3>},2*DRIBBLE_T/NUM_RUNNING_IMGS},
{{<6>,<7>},2*DRIBBLE_T/NUM_RUNNING_IMGS},{{<4>,<5>},2*DRIBBLE_T/NUM_RUNNING_IMGS},
{{<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/NUM_DRIBBLING_IMGS},{{<16>,<17>},2*DRIBBLE_T/NUM_DRIBBLING_IMGS},
{{<20>,<21>},2*DRIBBLE_T/NUM_DRIBBLING_IMGS},{{<18>,<19>},2*DRIBBLE_T/NUM_DRIBBLING_IMGS},
{{<20>,<21>},DRIBBLE_T/NUM_STOPPED_DRIBBLING_IMGS},{{<22>,<23>},DRIBBLE_T/NUM_STOPPED_DRIBBLING_IMGS},
};

AcctRegSetDftEntry("LT/KeepAway","I64 best_score0=0,best_score1=9999;\r\n");
AcctRegExecuteBranch("LT/KeepAway");

F64 game_t_end,foul_t_end;
I64 score0,score1;

#define MAX_OBJS        11
I64 num_men;
BoolI8 someone_shooting;

class Object
{
  I64 team; //-1 is ball
  F64 x,y,z,DxDt,DyDt,DzDt,ay,radius,stolen_t0;
  F64 last_t0,next_t0,foul_t0;
  I64 last_img,next_img;
  BoolI8 stopped,shooting,has_ball,pad[5];
} objs[MAX_OBJS],*ball,*human;


/*
Just to be different, I didn't use the built-in
BMF_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 Ode feature,
just to be different.
*/

U0 DrawObj(GrBitMap *base,Object *o,F64 tt)
{
  U8 *inter;
  F64 r1=Max(9-0.1*o->z,1),r2=Max(r1/4,1);

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

  if (o==ball)
    GrElemsPlot3(base,o->x,(o->y-o->z)/2,GR_Z_ALL-o->y,<1>);
  else {
    inter=GrElemsInterpolate(man_imgs[o->last_img].img[o->team],
        man_imgs[o->next_img].img[o->team],
        (tt-o->last_t0)/(o->next_t0-o->last_t0));
    GrElemsPlotRotY3b(base,o->x,(o->y-o->z)/2,GR_Z_ALL-o->y,inter,o->ay);
    Free(inter);
  }
}

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


U0 UpdateWin(TaskStruct *task)
{
  F64 tt=tP(task),d,d_down,d_up;
  I64 i;
  Object *o_sort[MAX_OBJS],*o;

  GrBitMap *base=GrAlias(gr_refreshed_base,task);
  GrAllocDepthBuf(base);
  base->ls.x=10000;
  base->ls.y=60000;
  base->ls.z=10000;
  d=65535/P3I32Norm(&base->ls);
  base->ls.x*=d;
  base->ls.y*=d;
  base->ls.z*=d;

  base->pen_width=2;
  base->color=RED;
  GrLineRect4(base,COURT_BORDER,COURT_BORDER,
      task->win_pixel_width -1-COURT_BORDER,
      task->win_pixel_height-1-COURT_BORDER);
  for (i=0;i<num_men+1;i++) {
    o=o_sort[i]=&objs[i];
    if (o!=ball) {
      if (o->has_ball) {
        ball->x=o->x+HAND_X_OFFSET*Cos(o->ay-pi/2)+HAND_Y_OFFSET*Cos(o->ay);
        //The factor 2 is because the man is not transformed.
        ball->y=o->y+HAND_X_OFFSET*Sin(o->ay-pi/2)/2+HAND_Y_OFFSET*Sin(o->ay)/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 percentage 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_shooting=o->has_ball=o->shooting=FALSE;
            ball->DxDt=o->DxDt+SHOT_VELOCITY/sqrt2*Cos(o->ay-pi/2);
            ball->DyDt=o->DyDt+SHOT_VELOCITY/sqrt2*Sin(o->ay-pi/2);
            ball->DzDt=o->DzDt+SHOT_VELOCITY/sqrt2;
          } 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+=man_imgs[o->last_img].dt;
        if (o->next_t0<=tt)
          o->next_t0=tt+man_imgs[o->last_img].dt;
      }
    }
  }

  QSortU64(o_sort,num_men+1,&ObjCompare);
  for (i=0;i<num_men+1;i++)
    DrawObj(base,o_sort[i],tt);
  tt=(game_t_end-tP(task))/60;
  if (tt<=0) {
    base->color=RED;
    tt=0;
    if (Blink(,tP(task)))
      GrPrintF(base,(task->win_pixel_width-FONT_WIDTH*9)>>1,
                    (task->win_pixel_height-FONT_HEIGHT)>>1,"Game Over");
  } else {
    if (tP(task)<foul_t_end) {
      base->color=LTRED;
      if (Blink(,tP(task)))
        GrPrintF(base,(task->win_pixel_width-FONT_WIDTH*4)>>1,
                     (task->win_pixel_height-FONT_HEIGHT)>>1,"Foul");
    }
    base->color=BLACK;
  }
  GrPrintF(base,0,0,"Time:%d:%04.1f  Score:",ToI64(tt),(tt-ToI64(tt))*60);
  GrPutS  (base,FONT_WIDTH*27,0,"Best Score:");

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

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

  GrDel(base);
}


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


U0 AnimateTask(TaskStruct *parent_task)
{
  F64 d,dx,dy,dt,dx2,dy2,t0=tP(parent_task);
  I64 i,j,team;
  BoolI8 gets_ball,ball_stolen;
  Object *o;
  while (TRUE) {
    dt=tP(parent_task)-t0;
    t0=tP(parent_task);

    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(2000);Sleep(100); Snd(0);Sleep(100);
        Snd(2000);Sleep(100); Snd(0);Sleep(100);
      }
    }

    if (game_t_end)
      for (i=0;i<num_men+1;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;
                ball_stolen=FALSE;
                for (j=0;j<num_men;j++)
                  if (j!=i && objs[j].has_ball) {
                    if (Rand<2.0*dt) {
                      objs[j].stolen_t0=t0;
                      objs[j].has_ball=FALSE;
                      ball_stolen=TRUE;
                      team=objs[j].team;
                    } else
                      gets_ball=FALSE;
                  }
                if (gets_ball) {
                  o->has_ball=TRUE;
                  if (o==human) {
                    if (ball_stolen && team) {
                      Noise(250,2000,2000,WF_SQUARE);
                      score0+=2;
                    }
                  } else {
                    if (o->team) {
                      Noise(250,1000,1000,WF_SQUARE);
                      score1+=2;
                    } else {
                      Noise(250,2000,2000,WF_SQUARE);
                      score0+=2;
                    }
                  }
                }
              }
            }
          } else if (o!=human && Rand<0.25*dt)
            Shoot(o);
          if (!o->shooting) {
            if (o==human) {
              dx=(ipx-parent_task->win_pixel_left-parent_task->win_scroll_x)-o->x;
              dy=(ipy-parent_task->win_pixel_top-parent_task->win_scroll_y)*2-o->y;
            } else {
              dx=o->DxDt+=RANDOM_MAN_ACCELERATION/sqrt2*RandI16/MAX_I16*dt;
              dy=o->DyDt+=RANDOM_MAN_ACCELERATION/sqrt2*RandI16/MAX_I16*dt;
            }
            d=Sqrt(dx*dx+dy*dy);
            if (d>=1.0) {
              o->ay=Arg(dx,dy)+pi/2;
              dx*=MAN_VELOCITY/sqrt2*dt/d;
              dy*=MAN_VELOCITY/sqrt2*dt/d;
              for (j=0;j<num_men;j++)
                if (j!=i) {
                  dx2=objs[j].x-o->x;
                  dy2=objs[j].y-o->y;
                  if ((d=Sqr(dx2)+Sqr(dy2))<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,500,500,WF_SQUARE);
                        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->win_pixel_width-COURT_BORDER) {
          o->x=parent_task->win_pixel_width-COURT_BORDER-1-o->radius;
          o->DxDt=-COLLISION_DAMP*o->DxDt;
          if (o==ball)
            Noise(10,1000,2000);
        }
        if (o->x-o->radius<COURT_BORDER) {
          o->x=COURT_BORDER+o->radius;
          o->DxDt=-COLLISION_DAMP*o->DxDt;
          if (o==ball)
            Noise(10,1000,2000);
        }

        if (o->y+o->radius*2>=(parent_task->win_pixel_height-COURT_BORDER)*2) {
          o->y=(parent_task->win_pixel_height-COURT_BORDER)*2-1-o->radius*2;
          o->DyDt=-COLLISION_DAMP*o->DyDt;
          if (o==ball)
            Noise(10,1000,2000);
        }
        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,1000,2000);
        }

        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,1000,2000);
          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;
        }
      }

      WinSync;
  }
}

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

U0 CleanUp()
{
}

U0 KeepAway()
{
  I64 msg_code,p1,p2;

  PopUpOk(
    "$FG,RED$Pass or hand-off to score points.$FG$\r\n\r\n"
    "Left-Click to pass.\r\n"
    "Right-Click to jump.\r\n");
  SettingsPush; //See SettingsPush
  WinMax;
  WinBorder(OFF);
  Preempt(OFF);
  LtfCurAttr(BLACK+YELLOW<<4);

  Fs->win_inhibit|=WIF_DBL_CLICK;
  WordStat(OFF);

  MenuPush(
"File {"
"  Abort(,CH_CTRLQ);"
"  Exit(,CH_ESC);"
"}"
"Play {"
"  Restart(,CH_CR);"
"  Shoot(,CH_SPACE);"
"  Jump(,'j');"
"}"
  );

  Init;
  Fs->update_win=&UpdateWin;
  Fs->animate_task=Spawn(&AnimateTask,Fs,"Animate",Fs);
  LtfClear;

  try {
    while (TRUE) {
      msg_code=GetMsg(&p1,&p2,1<<MSG_IP_L_DOWN|1<<MSG_IP_R_DOWN|1<<MSG_KEY_DOWN);
      switch (msg_code) {
        case MSG_IP_L_DOWN:
ka_shoot:
          Shoot(human);
          break;
        case MSG_IP_R_DOWN:
ka_jump:
          human->DzDt=JUMP_VELOCITY;
          break;
        case MSG_KEY_DOWN:
          switch (p1) {
            case CH_CR:
              CleanUp;
              Init;
              break;
            case 'j':
              goto ka_jump;
            case CH_SPACE:
              goto ka_shoot;
            case CH_CTRLQ:
            case CH_ESC:
              goto ka_done;
          }
          break;
      }
    }
ka_done: //Don't goto out of try
  } catch
    Fs->catch_except=TRUE;
  SettingsPop;
  AcctRegWriteBranch("LT/KeepAway","I64 best_score0=%d,best_score1=%d;\r\n",
    best_score0,best_score1);
  CleanUp;
  MenuPop;
}

//This is so I can use this file as a
//stand alone or component of a program.
#if IncludeDepth<2
KeepAway;
#endif