Trivial Solutions Corp.
                        Happy Programmers Make Us Happy

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


#define MT_HOSE         1
#define MT_DROPLET      2
class MyMass
{
  MassStruct m;
  I8 type;
  F8 radius;
};

#define ST_HOSE         1
class MySpring
{
  SpringStruct s;
  I8 type;
};




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





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



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



#define HOSE_RADIUS     3
#define LINK_SIZE       6
#define NOZZLE_START_Y  (GR_HEIGHT-15*FONT_HEIGHT)
#define NOZZLE_LEN      18
#define FAUCET_X        (5*HOSE_RADIUS)
#define FAUCET_Y        (GR_HEIGHT-12*FONT_HEIGHT)
#define GROUND_Y        (GR_HEIGHT-3*FONT_HEIGHT)
MyMass *faucet,*nozzle;
F8 nozzle_theta;

Ode *ode=NULL;
I8 start_up_timeout;


U0 DrawIt(TaskStruct *task,GrBitMap *base)
{
  BoolI1 old_suspend,first;
  F8 dx,dy,d;
  I8  x1b,y1b,x2b,y2b,
      x1a,y1a,x2a,y2a;
  MyMass   *tempm,*tempm1;
  MySpring *temps;
  P3I4 poly[4];

  if (start_up_timeout>GetTimeStamp) {
    ode->drag_v2=0.01; //Let hose settle during start-up
    ode->drag_v3=0.0001;
    base->color=RED;
    GrPutS(base,(GR_WIDTH-FONT_WIDTH*6)>>1,GR_HEIGHT>>1,"Squirt");
    return;
  } else {
    ode->drag_v2=0.0005;
    ode->drag_v3=0.0000025;
  }

  if (TaskValidate(task))
    old_suspend=LBts(&task->task_flags,TASKf_SUSPENDED);

  tempm=faucet;
  base->color=BLACK;
  GrRect(base,tempm->m.x+8,tempm->m.y,8,GROUND_Y-FAUCET_Y);
  GrElemsPlot(base,tempm->m.x,tempm->m.y,0,<1>);
  base->color=BLACK;
  GrCircle(base,tempm->m.x,tempm->m.y,tempm->radius);
  base->color=GREEN;
  GrFloodFill(base,tempm->m.x,tempm->m.y);

  tempm=nozzle;
  tempm1=nozzle->m.last;
  dx=tempm->m.x-tempm1->m.x;
  dy=tempm->m.y-tempm1->m.y;
  nozzle_theta=Unwrap(Arg(dx,dy));
  GrElemsPlotRotZ(base,tempm->m.x,tempm->m.y,0,<2>,nozzle_theta);
  base->color=BLACK;
  GrCircle(base,tempm->m.x,tempm->m.y,tempm->radius);
  base->color=GREEN;
  GrFloodFill(base,tempm->m.x,tempm->m.y);

  first=TRUE;
  tempm=ode->next_mass;
  while (tempm!=&ode->next_mass) {
    if (tempm->type==MT_HOSE) {
      tempm1=tempm->m.last;
      dx=tempm->m.x-tempm1->m.x;
      dy=tempm->m.y-tempm1->m.y;
      d=HOSE_RADIUS/Max(Sqrt(dx*dx+dy*dy),0.001);
      dx*=d;
      dy*=d;
      x2a=tempm->m.x-dy;
      y2a=tempm->m.y+dx;
      x2b=tempm->m.x+dy;
      y2b=tempm->m.y-dx;

      if (first)
        first=FALSE;
      else {
        base->color=GREEN;
        poly[0].x=x1a;
        poly[0].y=y1a;
        poly[0].z=0;
        poly[1].x=x2a;
        poly[1].y=y2a;
        poly[1].z=0;
        poly[2].x=x2b;
        poly[2].y=y2b;
        poly[2].z=0;
        poly[3].x=x1b;
        poly[3].y=y1b;
        poly[3].z=0;
        GrFillPolygon3(base,4,poly);
      }

      //Fill gaps
      GrLine(base,x2a,y2a,x2b,y2b);

      x1a=x2a;
      y1a=y2a;
      x1b=x2b;
      y1b=y2b;
    } else if (tempm->type==MT_DROPLET)
      GrElemsPlot(base,tempm->m.x,tempm->m.y,0,<3>);
    tempm=tempm->m.next;
  }

  temps=ode->next_spring;
  while (temps!=&ode->next_spring) {
    if (temps->type==ST_HOSE) {
      dx=temps->s.end1->x-temps->s.end2->x;
      dy=temps->s.end1->y-temps->s.end2->y;
      d=HOSE_RADIUS/Max(Sqrt(dx*dx+dy*dy),0.001);
      dx*=d;
      dy*=d;
      base->color=BLACK;
      GrLine(base,temps->s.end1->x-dy,temps->s.end1->y+dx,
          temps->s.end2->x-dy,temps->s.end2->y+dx);
      GrLine(base,temps->s.end1->x+dy,temps->s.end1->y-dx,
          temps->s.end2->x+dy,temps->s.end2->y-dx);
    }
    temps=temps->s.next;
  }

  if (TaskValidate(task))
    LBEqu(&task->task_flags,TASKf_SUSPENDED,old_suspend);
}


U0 MyDerivative(Ode *ode,F8 t,Order2D3 *state,Order2D3 *DstateDt)
{
//The forces due to springs and drag are
//automatically handled by the
//ode code.  We can add new forces
//here.
  nounusedwarn t,state,DstateDt;
//  F8 d,dd;
//  D3 p;
  MyMass *tempm1,*tempm2;
  nounusedwarn tempm2;

  tempm1=ode->next_mass;
  while (tempm1!=&ode->next_mass) {
    if (tempm1->type==MT_HOSE) {
      if (tempm1->m.state->y+tempm1->radius>GROUND_Y)
        tempm1->m.DstateDt->DyDt-=Sqr(Sqr(tempm1->m.state->y+tempm1->radius-GROUND_Y))*tempm1->m.mass;
      else
        tempm1->m.DstateDt->DyDt+=500*tempm1->m.mass;
      if (tempm1==nozzle || tempm1==faucet) {
        tempm1->m.DstateDt->DxDt=0;
        tempm1->m.DstateDt->DyDt=0;
      }
    } else if (tempm1->type==MT_DROPLET)
      tempm1->m.DstateDt->DyDt=500*tempm1->m.mass;
    tempm1=tempm1->m.next;
  }
}

MyMass *PlaceMass(I8 type,I8 x, I8 y,F8 r,F8 dx,F8 dy,F8 mass)
{
  MyMass *tempm=CAlloc(sizeof(MyMass));
  tempm->type=type;
  tempm->m.mass=mass;
  tempm->m.drag_profile_factor=250.0;
  tempm->m.x=x;
  tempm->m.y=y;
  tempm->m.DxDt=dx;
  tempm->m.DyDt=dy;
  tempm->radius=r;
  InsQue(tempm,ode->last_mass);
  return tempm;
}

MySpring PlaceSpring(MyMass *tempm1,MyMass *tempm2)
{
  MySpring *temps=CAlloc(sizeof(MySpring));
  temps->s.end1=tempm1;
  temps->s.end2=tempm2;
  temps->s.constant=20000;
  InsQue(temps,ode->last_spring);
  return temps;
}

U0 HoseNew()
{
  I8 i;
  MyMass *tempm1=NULL,*tempm;
  MySpring *temps;
  for (i=FAUCET_X;i<GR_WIDTH;i+=LINK_SIZE) {
    tempm=PlaceMass(MT_HOSE,i/2,GROUND_Y-HOSE_RADIUS,HOSE_RADIUS,0,0,1.0);
    if (tempm1) {
      temps=PlaceSpring(tempm,tempm1);
      temps->s.rest_len=LINK_SIZE;
      temps->type=ST_HOSE;
      nozzle=tempm;
    } else
      faucet=tempm;
    tempm1=tempm;
  }
  faucet->m.y=FAUCET_Y;
  nozzle->m.y=NOZZLE_START_Y;
  nozzle_theta=0;
}

U0 AnimateTask()
{
//Preempt is off by default for new tasks.
  MyMass   *tempm,*tempm1;
  F8 dx,dy;
  while (TRUE) {

//The WinMgr task suspends our parent when it
//updates the ODE's.  We use this to know when
//the ODE's are being updated.
    if (!Bt(&Fs->parent_task->task_flags,TASKf_SUSPENDED)) {
      dx=Cos(nozzle_theta),
      dy=Sin(nozzle_theta),
      PlaceMass(MT_DROPLET,
                nozzle->m.x+NOZZLE_LEN*dx,nozzle->m.y+NOZZLE_LEN*dy,
                HOSE_RADIUS,
                500*dx,500*dy,
                100.0);
      if (Rand<0.05) //faucet drip
        PlaceMass(MT_DROPLET,
                faucet->m.x,faucet->m.y,
                HOSE_RADIUS,
                0,0,
                100.0);

      tempm=ode->next_mass;
      while (tempm!=&ode->next_mass) {
        tempm1=tempm->m.next;
        if (tempm->type==MT_DROPLET &&
            tempm->m.y+tempm->radius>GROUND_Y) {
          RemQue(tempm);
          Free(tempm);
        }
        tempm=tempm1;
      }
    }
    WinSync;
  }
}

#define NOZZLE_MOVE_STEPS       5
#define NOZZLE_MOVE             15.0
U0 MoveNozzleTaskX(I8 sign)
{
  I8 i;
  for (i=0;i<NOZZLE_MOVE_STEPS;i++) {
    nozzle->m.x=Limit(nozzle->m.x+
      sign*NOZZLE_MOVE/NOZZLE_MOVE_STEPS,
      HOSE_RADIUS*3,GR_WIDTH-HOSE_RADIUS*3);
    WinSync;
  }
}
U0 MoveNozzleTaskY(I8 sign)
{
  I8 i;
  for (i=0;i<NOZZLE_MOVE_STEPS;i++) {
    nozzle->m.y=Limit(nozzle->m.y+
      sign*NOZZLE_MOVE/NOZZLE_MOVE_STEPS,
      HOSE_RADIUS*3,GROUND_Y);
    WinSync;
  }
}


U0 DrawBackGroundTextLayer()
{
  LtfBottom;
  LtfClear;
  PutS("$$BG,LTCYAN$$");
  CrLf(GROUND_Y/FONT_HEIGHT);
  PutS("$$BG,YELLOW$$");
}

U0 Init()
{
  DrawBackGroundTextLayer;

//Allow hose to settle.
  start_up_timeout=GetTimeStamp+time_stamp_freq>>1;

  ode=OdeNew(0,5e-2,ODEF_HAS_MASSES);
  ode->derivative=&MyDerivative;
  ode->acceleration_limit=5e3;

  HoseNew;
  InsQue(ode,Fs->last_ode);
}

U0 CleanUp()
{
  MyMass   *tempm,*tempm1;
  MySpring *temps,*temps1;
  I8 i;

  for (i=0;i<NOZZLE_MOVE_STEPS;i++)
    WinSync;    //Let nozzle move tasks die

  RemQue(ode);

  tempm=ode->next_mass;
  while (tempm!=&ode->next_mass) {
    tempm1=tempm->m.next;
    RemQue(tempm);
    Free(tempm);
    tempm=tempm1;
  }

  temps=ode->next_spring;
  while (temps!=&ode->next_spring) {
    temps1=temps->s.next;
    RemQue(temps);
    Free(temps);
    temps=temps1;
  }

  OdeDel(ode);
  LtfBottom;
  LtfClear;
  PutS("$$BG$$");
}

U0 SongTask()
{
  Fs->task_end_cb=&SndTaskEndCB;
  MusicSettingsReset;
  while (TRUE) {
    Play("W13eAM4/4");
    Play("2qGeGqG3eAq.BqBeBqA2eG3qA");
    Play("eB2hG3q.BqBeCq.DD");
    Play("qCeBqCeDhB");
  }
}

U0 Squirt()
{
  I8 msg_code,p1,p2;
  SettingsPush; //See SettingsPush
  Fs->song_task=Spawn(&SongTask,NULL,"Song",Fs);
  WinMax;
  WinBorder(OFF);
  Preempt(OFF);
  LtfCursor(OFF);

  LBts(&Fs->display_flags,DISPLAYf_NO_DBL_CLICK);
  WordStat(OFF);

  MenuPush(
"File {"
"  Abort(,CH_CTRLQ);"
"  Exit(,CH_ESC);"
"}"
"Play {"
"  Restart(,CH_CR);"
"  Left(,,SC_CURSOR_LEFT);"
"  Right(,,SC_CURSOR_RIGHT);"
"  Up(,,SC_CURSOR_UP);"
"  Down(,,SC_CURSOR_DOWN);"
"}"
  );

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

  try {
    while (TRUE) {
      msg_code=GetMsg(&p1,&p2,1<<MSG_IP_L_DOWN|1<<MSG_IP_R_DOWN|1<<MSG_IP_R_UP|1<<MSG_KEY_DOWN);
      switch (msg_code) {
        case MSG_IP_L_DOWN:
          break;
        case MSG_IP_R_DOWN:
          break;
        case MSG_IP_R_UP:
          break;
        case MSG_KEY_DOWN:
          switch (p1) {
            case 0:
              switch (p2.u1[0]) {
                case SC_CURSOR_LEFT:
                  Spawn(&MoveNozzleTaskX,-1,"Move Nozzle",Fs);
                  break;
                case SC_CURSOR_RIGHT:
                  Spawn(&MoveNozzleTaskX,1,"Move Nozzle",Fs);
                  break;
                case SC_CURSOR_UP:
                  Spawn(&MoveNozzleTaskY,-1,"Move Nozzle",Fs);
                  break;
                case SC_CURSOR_DOWN:
                  Spawn(&MoveNozzleTaskY,1,"Move Nozzle",Fs);
                  break;
              }
              break;
            case CH_CR:
              CleanUp;
              Init;
              break;
            case CH_CTRLQ:
            case CH_ESC:
              goto squirt_done;
          }
          break;
      }
    }
squirt_done: //Don't goto out of try
  } catch
    Fs->catch_except=TRUE;
  GetMsg(NULL,NULL,1<<MSG_KEY_UP);
  SettingsPop;
  CleanUp;
  MenuPop;
}

Squirt;