` =========================================== ` R I C O C H E T ` =========================================== ` Newtonian Collision Simulator ` --- and --- ` Calculated Frame Rate Demo ` ` Version 1.1, 14th May 2005 ` ------------------------------------------- ` http://www.fraxinus.net/ricochet ` ------------------------------------------- ` Copyright © 2005 - David Greenwood ` This program may be freely distributed for ` all non-commercial purposes. ` ------------------------------------------- #constant initial_frames_per_second = 40 #constant temp_bitmap = 1 #constant white = rgb(255,255,255) #constant back_colour = rgb(100,100,255) #constant mask_colour = rgb(255,255,0) #constant scancode_F1 = 59 #constant scancode_F2 = 60 #constant scancode_F3 = 61 #constant scancode_F4 = 62 #constant scancode_F5 = 63 #constant scancode_F6 = 64 #constant scancode_F7 = 65 #constant scancode_F8 = 66 #constant scancode_F9 = 67 #constant scancode_F10 = 68 #constant scancode_F11 = 87 #constant scancode_F12 = 88 #constant scancode_c = 46 #constant scancode_f = 33 #constant scancode_g = 34 #constant scancode_i = 23 #constant scancode_n = 49 #constant scancode_q = 16 #constant scancode_s = 31 #constant scancode_spacebar = 57 #constant start_delay = 1 #constant end_delay = 2 ` Since we have not defined the units we are working in, ` simply use a "magic number" to represent starting energy, ` and set it so that the display speed looks reasonable. #constant kinetic_energy = 2000000 type Tball id radius mass as float x as float y as float velocity_x as float velocity_y as float min_x max_x min_y max_y colour endtype type Tcollision item1 item2 time as float x1 as float y1 as float x2 as float y2 as float endtype type Trectangle left top right bottom endtype type Tkey scan_code current_state old_state endtype type Tkeyid slow normal fast info graphics menu quit large1 large2 large3 little_big1 little_big2 pattern1 pattern2 pattern3 pattern4 mixed1 mixed2 mixed3 colours endtype type Tcolour bright dark endtype `--------------------------------------- ` variables used to calculate frame rate global frame_start_time global seconds_per_frame as float `--------------------------------------- global print_timer global frames_per_second$ global ms_per_frame$ global speed as float global start_timer global end_timer global initial_velocity_x as float global initial_velocity_y as float global auto_restart global show_info global show_graphics global show_colours global last_size global last_colour_id global last_ID global ball_0 global background_image global menu_image global frame as Trectangle global keyid as Tkeyid dim colours(-1) as Tcolour dim ball(-1) as Tball dim collisions(-1) as Tcollision dim key(-1) as Tkey `=========== start of program ========= set display mode 800,600,16 `set display mode 1024,768,16 set image colorkey 255,255,0 sync on sync rate 0 hide mouse `disable escapekey initialise_global_variables() setup_keys() create_menu() create_background() setup_colours() create_images() set current bitmap 0 `======= menu loop ======= do repeat paste image menu_image, 0, 0 user_choice$ = check_keyboard_menu() sync until user_choice$ <> "" repeat setup_balls(user_choice$) initialise_sprites() initialise_sync_rate() `======= display loop ======= repeat calculate_sync_rate() paste image background_image, 0, 0 check_start_timer() return_to_menu = check_keyboard_running() move_balls() find_collisions() calculate_collision_times() do_collisions() draw_balls() print_info() restart = check_end_timer() sync until restart or return_to_menu `====== end of display loop ====== tidy_up() until return_to_menu loop `====== end of menu loop ====== function initialise_global_variables() seconds_per_frame = 0 show_colours = 1 last_ID = 0 show_info = 0 show_graphics = 1 speed = 1 frame.left = 10 frame.top = 10 frame.right = screen width() - 10 frame.bottom = screen height() - 10 endfunction function setup_keys() ` generate key mappings keyid.slow = add_key(scancode_s) keyid.normal = add_key(scancode_n) keyid.fast = add_key(scancode_f) keyid.graphics = add_key(scancode_g) keyid.info = add_key(scancode_i) keyid.colours = add_key(scancode_c) keyid.quit = add_key(scancode_q) keyid.menu = add_key(scancode_spacebar) keyid.large1 = add_key(scancode_F1) keyid.large2 = add_key(scancode_F2) keyid.large3 = add_key(scancode_F3) keyid.little_big1 = add_key(scancode_F4) keyid.little_big2 = add_key(scancode_F5) keyid.mixed1 = add_key(scancode_F6) keyid.mixed2 = add_key(scancode_F7) keyid.mixed3 = add_key(scancode_F8) keyid.pattern1 = add_key(scancode_F9) keyid.pattern2 = add_key(scancode_F10) keyid.pattern3 = add_key(scancode_F11) keyid.pattern4 = add_key(scancode_F12) endfunction function create_menu() ` Pre-draw image for menu page width = screen width() height = screen height() create bitmap temp_bitmap, width, height ink white, white set text font "Arial" set text size 40 set text to bold center text width / 2, 10, "R I C O C H E T - 1.1" set text size 20 x = 300 y = 20 text x, y*3, "RUN SIMULATION" text x, y*4, "F1 - 10 large" text x, y*5, "F2 - 25 large" text x, y*6, "F3 - 50 large" text x, y*7, "F4 - 5 large + 25 small" text x, y*8, "F5 - 25 large + 5 small" text x, y*9, "F6 - 20 mixed" text x, y*10, "F7 - 100 mixed" text x, y*11, "F8 - 200 mixed" text x, y*12, "F9 - All in a Row" text x, y*13, "F10 - Circles" text x, y*14, "F11 - Billiards" text x, y*15, "F12 - The Wall" text x, y*16, "Q - Quit" text x, y*18, "INTERACTIVE KEYS" text x, y*19, "S - Slow" text x, y*20, "N - Normal" text x, y*21, "F - Fast" text x, y*22, "I - Info" text x, y*23, "G - Graphics" text x, y*24, "C - Colours" text x, y*25, "Q - Quit" center text width/2, y*27, "Press SPACE BAR to return to this menu" center text width / 2, height - 40, "www.fraxinus.net/ricochet" set text size 15 center text width / 2, height - 20, "Copyright © 2005 David Greenwood" menu_image = get_ID() get image menu_image, 0, 0, width, height delete bitmap temp_bitmap endfunction function create_background() ` Pre-draw image used to clear screen at beginning of each loop width as integer height as integer width = screen width() height = screen height() create bitmap temp_bitmap, width, height ink white, white box 0, 0, width,height ink back_colour, back_colour box frame.left, frame.top, frame.right, frame.bottom background_image = get_ID() get image background_image, 0, 0, width,height delete bitmap temp_bitmap endfunction function setup_colours() add_colour(rgb(128,128,255), rgb(0,0,64)) : `blue add_colour(rgb(128,255,128), rgb(0,64,0)) : `green add_colour(rgb(255,128,128), rgb(64,0,0)) : `red add_colour(rgb(128,128,128), rgb(0,0,0)) : `black add_colour(rgb(255,255,255), rgb(96,96,96)) : `white add_colour(rgb(255,255,128), rgb(64,64,0)) : `yellow add_colour(rgb(128,255,255), rgb(0,64,64)) : `cyan add_colour(rgb(255,128,255), rgb(64,0,64)) : `magenta endfunction function add_colour(bright, dark) array insert at bottom colours(0) colours().bright = bright colours().dark = dark endfunction function create_images() ` create images of spheres with radii 1 to 15, and in sets of colours ` use dummy ID as base pointer ` e.g. ball with radius 9 has image_ID = ball_0 + (colour * 15) + 9 ball_radius as float ball_light_range as float ball_light_radius as float bitmap_size = 32 ball_0 = get_ID() create bitmap temp_bitmap, bitmap_size, bitmap_size for colour_id = 0 to array count(colours(0)) for radius = 1 to 15 x0 = radius y0 = radius x1 = int(radius/2) y1 = int(radius/2) ball_light_range = radius + sqrt(((x1-x0)*(x1-x0))+((y1-y0)*(y1-y0))) + 1 for x=0 to bitmap_size - 1 for y=0 to bitmap_size - 1 ball_radius = sqrt(((x0-x)*(x0-x))+((y0-y)*(y0-y))) if ball_radius >= radius + 1 dotcolour = mask_colour else ball_light_radius = sqrt(((x1-x)*(x1-x))+((y1-y)*(y1-y))) ball_bright = colours(colour_id).bright ball_dark = colours(colour_id).dark colour = shade(ball_bright, ball_dark, ball_light_radius/ball_light_range) if ball_radius > radius colour = shade(colour, back_colour, ball_radius - radius) endif dotcolour = colour endif dot x, y, dotcolour next y next x get image get_ID(), 0, 0, bitmap_size, bitmap_size next radius next colour_id delete bitmap temp_bitmap endfunction function setup_balls(choice$ as string) auto_restart = 0 select choice$ case "large1" : generate_balls(10, 15) : endcase case "large2" : generate_balls(25, 15) : endcase case "large3" : generate_balls(50, 15) : endcase case "mixed1" : generate_balls(20, 0) : endcase case "mixed2" : generate_balls(100, 0) : endcase case "mixed3" : generate_balls(200, 0) : endcase case "little_big1" generate_balls(25, 8) generate_balls(5, 15) endcase case "little_big2" generate_balls(5, 8) generate_balls(25, 15) endcase case "pattern1" pattern_line() auto_restart = 1 endcase case "pattern2" pattern_circle() auto_restart = 1 endcase case "pattern3" pattern_billiards() auto_restart = 1 endcase case "pattern4" pattern_wall() auto_restart = 1 endcase endselect endfunction function generate_balls(count, size) randomize timer() for b = 1 to count if size = 0 radius = get_size() else radius = size endif direction = rnd(360) ` the calculation of velocity should be multiplied by 2/PI, but since ` we are using a "magic number" for kinetic energy this is unnecessary velocity_x = sqrt((kinetic_energy)/(radius * radius)) * cos(direction) velocity_y = sqrt((kinetic_energy)/(radius * radius)) * sin(direction) new_ball = add_ball(100, 100, radius, velocity_x, velocity_y) repeat ball().x = frame.left + radius + rnd( frame.right - frame.left - (radius * 2)) ball().y = frame.top + radius + rnd( frame.bottom - frame.top - (radius * 2)) until collision()= 0 next b endfunction function add_ball(x, y, radius, velocity_x, velocity_y) array insert at bottom ball(0) new_ID = get_ID() ball().ID = new_ID ball().x = x ball().y = y ball().radius = radius ball().mass = 3.14159265 * radius * radius ball().velocity_x = velocity_x ball().velocity_y = velocity_y ball().min_x = frame.left + radius ball().max_x = frame.right - radius ball().min_y = frame.top + radius ball().max_y = frame.bottom - radius ball().colour = get_colour() endfunction new_ID function initialise_sync_rate() seconds_per_frame = 1.0 / initial_frames_per_second frame_start_time = timer() - (seconds_per_frame * 1000.0) endfunction function calculate_sync_rate() seconds_per_frame = (0.8 * seconds_per_frame) + (0.2 * ((timer() - frame_start_time)/1000.0)) frame_start_time = timer() endfunction function print_info() if show_info = 1 if timer() >= print_timer + 250 frames_per_second$ = str$(1 / seconds_per_frame) ms_per_frame$ = str$(seconds_per_frame * 1000) print_timer = timer() endif ink black, black set text font "courier new" set text size 16 set text to normal text 10, 10, "speed = " + str$(speed) text 10, 23, "frames per sec = " + frames_per_second$ text 10, 36, "ms per frame = " + ms_per_frame$ endif endfunction function draw_balls() ink black, black array index to top ball(0) while array index valid (ball(0)) if show_graphics = 1 ball_image = ball_0 + ball().radius + (ball().colour * show_colours * 15) sprite ball().ID, ball().x, ball().y, ball_image else circle ball().x, ball().y, ball().radius endif next array index ball(0) endwhile endfunction function move_balls() array index to bottom ball(0) while array index valid (ball(0)) ball().x = ball().x + ball().velocity_x * seconds_per_frame * speed ball().y = ball().y + ball().velocity_y * seconds_per_frame * speed if ball().x < ball().min_x ball().velocity_x = - ball().velocity_x ball().x = ball().x + ((ball().min_x - ball().x) * 2) endif if ball().x > ball().max_x ball().velocity_x = - ball().velocity_x ball().x = ball().x - ((ball().x - ball().max_x) * 2) endif if ball().y < ball().min_y ball().velocity_y = - ball().velocity_y ball().y = ball().y + ((ball().min_y - ball().y) * 2) endif if ball().y > ball().max_y ball().velocity_y = - ball().velocity_y ball().y = ball().y - ((ball().y - ball().max_y) * 2) endif previous array index ball(0) endwhile endfunction function shade(from_colour, to_colour, percent as float) ` calculate shade of colour proportionely between two colours R = int((rgbr(from_colour) * (1 - percent)) + (rgbr(to_colour) * percent)) G = int((rgbg(from_colour) * (1 - percent)) + (rgbg(to_colour) * percent)) B = int((rgbb(from_colour) * (1 - percent)) + (rgbb(to_colour) * percent)) colour = rgb(R,G,B) endfunction colour function get_ID() ` simply return next valid id inc last_ID endfunction last_ID function get_colour() last_colour_id = (last_colour_id + 1) mod (array count(colours(0)) + 1) endfunction last_colour_id function get_size() inc last_size last_size = (last_size + 1) mod 16 if last_size < 8 then last_size = 8 endfunction last_size function add_key(scan_code) ` add key to list and return position as reference array insert at bottom Key(0) Key().scan_code = scan_code Key().Current_State = 0 Key().Old_State = 0 position = array count(Key(0)) endfunction position function check_key(key_index) ` check if the specified key has been pressed old_state as integer current_state as integer key_status as integer old_state = key(key_index).Current_State current_state = keystate(key(key_index).Scan_Code) if old_state = 0 and current_state = 1 key_status = 1 else key_status = 0 endif key(key_index).old_state = old_state key(key_index).current_state = current_state endfunction key_status function check_keyboard_menu() ` check keyboard for user input while the menu is displayed choice as string if check_key(keyid.large1) then choice = "large1" if check_key(keyid.large2) then choice = "large2" if check_key(keyid.large3) then choice = "large3" if check_key(keyid.little_big1) then choice = "little_big1" if check_key(keyid.little_big2) then choice = "little_big2" if check_key(keyid.pattern1) then choice = "pattern1" if check_key(keyid.pattern2) then choice = "pattern2" if check_key(keyid.pattern3) then choice = "pattern3" if check_key(keyid.pattern4) then choice = "pattern4" if check_key(keyid.mixed1) then choice = "mixed1" if check_key(keyid.mixed2) then choice = "mixed2" if check_key(keyid.mixed3) then choice = "mixed3" if check_key(keyid.quit) = 1 then end endfunction choice function check_keyboard_running() ` check keyboard while the simulation is running menu_key as integer if check_key(keyid.info) = 1 ` toggle info display show_info = (show_info + 1) mod 2 endif if check_key(keyid.graphics) = 1 ` toggle graphics - sprites/circles if show_graphics = 1 show_graphics = 0 hide all sprites else show_graphics = 1 show all sprites endif endif if check_key(keyid.colours) = 1 ` toggle graphics - sprites/circles show_colours = (show_colours + 1) mod 2 endif if check_key(keyid.slow) = 1 then speed = 0.25 if check_key(keyid.normal) = 1 then speed = 1.0 if check_key(keyid.fast) = 1 then speed = 4.0 if check_key(keyid.quit) = 1 then end if check_key(keyid.menu) = 1 then menu_key = 1 endfunction menu_key function collision() `check if current ball has collided with another ball id = ball().id x = ball().x y = ball().y radius = ball().radius collision_id = 0 for i = 0 to array count(ball(0)) - 1 if ball(i).id <> id xx = ball(i).x - x yy = ball(i).y -y rr = ball(i).radius + radius if (xx * xx) + (yy * yy) < rr * rr collision_id = ball(i).id i = array count(ball(0)) endif endif next i endfunction collision_id function initialise_sprites() array index to top ball(0) while array index valid (ball(0)) sprite ball().ID, ball().x, ball().y, ball_0 + ball().radius set sprite ball().ID, 0, 1 offset sprite ball().ID, ball().radius, ball().radius next array index ball(0) endwhile if show_graphics show all sprites else hide all sprites endif endfunction function find_collisions() empty array collisions(0) for ball1 = 0 to array count(ball(0)) - 1 for ball2 = ball1 + 1 to array count(ball(0)) if is_collision(ball1, ball2) array insert at bottom collisions(0) collisions().item1 = ball1 collisions().item2 = ball2 endif next ball2 next ball1 endfunction function is_collision(item1, item2) xx = ball(item1).x - ball(item2).x yy = ball(item1).y - ball(item2).y rr = ball(item1).radius + ball(item2).radius if (xx * xx) + (yy * yy) < rr * rr result = 1 endif endfunction result function calculate_collision_times() old_x as float : old_y as float new_x as float : new_y as float t0 as float : t as float : t1 as float x1 as float : y1 as float : x2 as float : y2 as float x1t as float : y1t as float : x2t as float : y2t as float vx1 as float : vy1 as float : vx2 as float : vy2 as float distance_squared as float array index to top collisions(0) while array index valid(collisions(0)) ball1 = collisions().item1 ball2 = collisions().item2 x1 = ball(ball1).x y1 = ball(ball1).y x2 = ball(ball2).x y2 = ball(ball2).y vx1 = ball(ball1).velocity_x vy1 = ball(ball1).velocity_y vx2 = ball(ball2).velocity_x vy2 = ball(ball2).velocity_y radii_squared = ball(ball1).radius + ball(ball2).radius radii_squared = radii_squared * radii_squared t0 = 0.0 t1 = 1.0 for i = 1 to 8 ` repeat binary chop to get collision time to 1/256th of a unit t = (t0 + t1)/2 x1t = x1 - (1 - t) * vx1 * seconds_per_frame * speed y1t = y1 - (1 - t) * vy1 * seconds_per_frame * speed x2t = x2 - (1 - t) * vx2 * seconds_per_frame * speed y2t = y2 - (1 - t) * vy2 * seconds_per_frame * speed distance_squared = ((x2t-x1t)*(x2t-x1t)) + ((y2t-y1t)*(y2t-y1t)) if distance_squared > radii_squared t0 = t else t1 = t endif next collisions().time = t0 collisions().x1 = x1 - (1 - t0) * vx1 * seconds_per_frame * speed collisions().y1 = y1 - (1 - t0) * vy1 * seconds_per_frame * speed collisions().x2 = x2 - (1 - t0) * vx2 * seconds_per_frame * speed collisions().y2 = y2 - (1 - t0) * vy2 * seconds_per_frame * speed next array index collisions(0) endwhile endfunction function do_collisions() a as float : b as float : c as float d as float : e as float : f as float ms as float : mt as float : a2b2 as float ux as float : uy as float : vx as float : vy as float xx as float : yy as float : rr as float : distance as float offset as float : x_offset as float : y_offset as float array index to top collisions(0) while array index valid(collisions(0)) s = collisions().item1 t = collisions().item2 a = collisions().x2 - collisions().x1 b = collisions().y2 - collisions().y1 c = ball(s).velocity_x d = ball(s).velocity_y e = ball(t).velocity_x f = ball(t).velocity_y ms = ball(s).mass / ( ball(s).mass + ball(t).mass ) mt = ball(t).mass / ( ball(s).mass + ball(t).mass ) a2b2 = (a*a)+(b*b) ux = (c*ms)+(((a*a*(2*e-c)+2*a*b*(f-d)+b*b*c)/a2b2)*mt) uy = (d*ms)+(((a*a*d+2*a*b*(e-c)+b*b*(2*f-d))/a2b2)*mt) vx = (e*mt)+(((a*a*(2*c-e)+2*a*b*(d-f)+b*b*e)/a2b2)*ms) vy = (f*mt)+(((a*a*f+2*a*b*(c-e)+b*b*(2*d-f))/a2b2)*ms) ball(s).velocity_x = ux ball(s).velocity_y = uy ball(t).velocity_x = vx ball(t).velocity_y = vy ball(s).x = collisions().x1 + (1-collisions().time) * ux * seconds_per_frame * speed ball(s).y = collisions().y1 + (1-collisions().time) * uy * seconds_per_frame * speed ball(t).x = collisions().x2 + (1-collisions().time) * vx * seconds_per_frame * speed ball(t).y = collisions().y2 + (1-collisions().time) * vy * seconds_per_frame * speed ` make sure the balls don't overlap after the collision! ` this can be a problem when speed if set to fast xx = ball(s).x - ball(t).x yy = ball(s).y - ball(t).y rr = ball(s).radius + ball(t).radius distance = sqrt((xx * xx) + (yy * yy)) if distance < rr offset = rr - distance x_offset = offset * xx / distance y_offset = offset * yy / distance ball(s).x = ball(s).x + (x_offset / 2) ball(t).x = ball(t).x - (x_offset / 2) ball(s).y = ball(s).y + (y_offset / 2) ball(t).y = ball(t).y - (y_offset / 2) endif next array index collisions(0) endwhile endfunction function tidy_up() array index to top ball(0) while array index valid (ball(0)) delete sprite ball().ID next array index ball(0) endwhile empty array ball(0) endfunction function check_start_timer() if start_timer > 0 if timer() > start_timer + (1000 * start_delay) start_timer = 0 ball(0).velocity_x = initial_velocity_x ball(0).velocity_y = initial_velocity_y endif endif endfunction function pattern_line() add_ball(25, 300, 15, 0, 0) add_ball(250, 300, 15, 0, 0) add_ball(300, 300, 15, 0, 0) add_ball(350, 300, 15, 0, 0) add_ball(400, 300, 15, 0, 0) add_ball(450, 300, 15, 0, 0) add_ball(500, 300, 15, 0, 0) add_ball(550, 300, 15, 0, 0) add_ball(600, 300, 15, 0, 0) add_ball(650, 300, 15, 0, 0) add_ball(700, 300, 15, 0, 0) add_ball(735 + rnd(30), 271 + (rnd(29) * 2), 15, 0, 0) start_timer = timer() initial_velocity_x = 500 initial_velocity_y = 0 auto_restart = 1 end_timer = 0 endfunction function pattern_circle() x0 = 400 y0 = 300 add_ball(x0 + 1, y0 + 1, 8, 0, 0) for radius = 140 to 260 step 30 for alpha = 0 to 359 step 15 x = x0 + (radius * cos(alpha)) y = y0 + (radius * sin(alpha)) add_ball(x, y, 8, 0, 0) next next start_timer = timer() alpha = rnd(359) initial_velocity_x = 500 * cos(alpha) initial_velocity_y = 500 * sin(alpha) auto_restart = 1 end_timer = 0 endfunction function pattern_billiards() x as float y as float x0 = 500 y0 = 300 x = 32 * cos(30) y = 32 * sin(30) add_ball(100, 300, 15, 0, 0) add_ball(50, 50, 15, 0, 0) add_ball(screen width() - 50, 50, 15, 0, 0) add_ball(50, screen height() - 50, 15, 0, 0) add_ball(screen width() - 50, screen height() - 50, 15, 0, 0) add_ball(x0, y0, 15, 0, 0) add_ball(x0 + x, y0 - y, 15, 0, 0) add_ball(x0 + x, y0 + y, 15, 0, 0) add_ball(x0 + 2*x, y0 - 2*y, 15, 0, 0) add_ball(x0 + 2*x, y0, 15, 0, 0) add_ball(x0 + 2*x, y0 + 2*y, 15, 0, 0) add_ball(x0 + 3*x, y0 - 3*y, 15, 0, 0) add_ball(x0 + 3*x, y0 - y, 15, 0, 0) add_ball(x0 + 3*x, y0 + y, 15, 0, 0) add_ball(x0 + 3*x, y0 + 3*y, 15, 0, 0) add_ball(x0 + 4*x, y0 - 4*y, 15, 0, 0) add_ball(x0 + 4*x, y0 - 2*y, 15, 0, 0) add_ball(x0 + 4*x, y0, 15, 0, 0) add_ball(x0 + 4*x, y0 + 2*y, 15, 0, 0) add_ball(x0 + 4*x, y0 + 4*y, 15, 0, 0) start_timer = timer() alpha = rnd(90) initial_velocity_x = 500 * cos(alpha-45) initial_velocity_y = 500 * sin(alpha-45) auto_restart = 1 end_timer = 0 endfunction function pattern_wall() x as float y as float x0 = 600 y0 = 300 offset = 20 add_ball(40, 300, 10, 0, 0) add_ball(40, 40, 15, 0, 0) add_ball(40, screen height() - 40, 15, 0, 0) for i = -4 to 4 for j = -14 to 14 add_ball(x0 + offset*i, y0 + offset*j, 6, 0, 0) next j next i start_timer = timer() alpha = rnd(179) initial_velocity_x = 400 * cos(alpha-89) initial_velocity_y = 400 * sin(alpha-89) auto_restart = 1 end_timer = 0 endfunction function check_end_timer() stationary = 0 if auto_restart if end_timer = 0 array index to top ball(0) while array index valid (ball(0)) if ball().velocity_x = 0 and ball().velocity_y = 0 stationary = stationary + 1 endif next array index ball(0) endwhile if stationary = 0 end_timer = timer() endif else if timer() > end_timer + (end_delay * 1000) result = 1 endif endif endif endfunction result