//The ball and men were generated //with ::/Apps/GrModels/Run.ZC. //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}, }; RegDefault("ZealOS/KeepAway", "I64 best_score0=0,best_score1=9999;\n"); RegExe("ZealOS/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, Terry didn't use the built-in DCF_TRANSFORMATION flag in this game. Instead, he chose a 45 degree angle between Y and Z as the view point. If he had used the transform, he would have to make all the men taller. This is a little simpler, and faster, but adds lots of factor 2 vals. Terry 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. Terry's instinct told him 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; } } } QuickSortI64(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; Sound(86); Sleep(100); Sound; Sleep(100); Sound(86); Sleep(100); Sound; 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 = (mouse.pos.x - parent_task->pix_left - parent_task->scroll_x) - o->x; dy = (mouse.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 message_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) { message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_DOWN | 1 << MESSAGE_MS_R_DOWN | 1 << MESSAGE_KEY_DOWN); switch (message_code) { case MESSAGE_MS_L_DOWN: ka_shoot: Shoot(human); break; case MESSAGE_MS_R_DOWN: ka_jump: human->DzDt = JUMP_VELOCITY; break; case MESSAGE_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 MessageGet(,, 1 << MESSAGE_KEY_UP); } catch PutExcept; SettingsPop; MenuPop; RegWrite("ZealOS/KeepAway", "I64 best_score0=%d,best_score1=%d;\n", best_score0, best_score1); }