//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 /* 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.templeos.org/files/SimStrSetUp.zip 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 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); }