Trivial Solutions Corp. 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. //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 { U1 *img[2]; F8 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}, }; AcctRegSetDefaultEntry("LT/KeepAway","I8 best_score0=0,best_score1=9999;\r\n"); AcctRegExecuteBranch("LT/KeepAway"); F8 game_t_end,foul_t_end; I8 score0,score1; #define MAX_OBJS 11 I8 num_men; BoolI1 someone_shooting; class Object { I8 team; //-1 is ball F8 x,y,z,DxDt,DyDt,DzDt,ay,radius,stolen_t0; F8 last_t0,next_t0,foul_t0; I8 last_img,next_img; BoolI1 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 values. I also didn't use the Ode feature, just to be different. */ U0 DrawObj(GrBitMap *base,Object *o,F8 tt) { U1 *inter; F8 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) GrElemsPlot(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)); GrElemsPlotRotY(base,o->x,(o->y-o->z)/2,GR_Z_ALL-o->y,inter,o->ay); Free(inter); } } I8 ObjCompare(Object *o1,Object *o2) { return o1->y-o2->y; } U0 UpdateWin(TaskStruct *task) { F8 tt=tP(task),d,d_down,d_up; I8 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/P3I4Norm(&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; } } } QSortU8(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:",ToI8(tt),(tt-ToI8(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) { F8 d,dx,dy,dt,dx2,dy2,t0=tP(parent_task); I8 i,j,team; BoolI1 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)-o->x; dy=(ipy-parent_task->win_pixel_top)*2-o->y; } else { dx=o->DxDt+=RANDOM_MAN_ACCELERATION/sqrt2*RandI2/MAX_I2*dt; dy=o->DyDt+=RANDOM_MAN_ACCELERATION/sqrt2*RandI2/MAX_I2*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() { I8 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() { I8 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); LBts(&Fs->display_flags,DISPLAYf_NO_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; GetMsg(NULL,NULL,1<<MSG_KEY_UP); SettingsPop; AcctRegWriteBranch("LT/KeepAway","I8 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