#!/usr/bin/env python3

# Filename: A16c_2D_B2D_serverN.py

import math

from pygame.color import THECOLORS

from A09_vec2d import Vec2D
from A15_air_table_objects import Wall, Puck, Spring
from A15_game_loop import GameLoop
from A15_pool_shots import pool_trick_shot, pool_line_of_balls, burst_of_pucks
import A15_globals as g

#===========================================================
# Functions
#===========================================================
        
def make_some_pucks(demo):
    g.game_window.set_caption("PyBox2D Air-Table Server A16c     Demo #" + str(demo))
    g.env.timestep_fixed = False

    # This removes all references to pucks and walls and effectively deletes them. 
    for eachpuck in g.air_table.pucks[:]:
        eachpuck.delete()
        
    for eachWall in g.air_table.walls[:]:
        if not eachWall.fence:
            eachWall.delete()

    g.air_table.resetFence()
    g.air_table.buildFence() # a complete perimeter fence

    # Most of the demos don't need the tangle checker.
    g.air_table.jello_tangle_checking_enabled = False
    
    # Make sure the throwing thread is not still running.
    g.air_table.delayed_throw = None

    # Now just black out the screen.
    g.game_window.clear()

    g.env.fr_avg.reset()
    g.env.tickCount = 0

    for client_name in g.env.clients:
        client = g.env.clients[client_name]
        if client.drone:
            client.active = False
            client.drone = False

    # Each demo will have a single variation unless specified.
    g.env.demo_variations[demo]['count'] = 1

    if demo == 1:
        g.env.set_gravity("off")
        #    position       , r_m , density
        Puck(Vec2D(2.5, 7.5), 0.25, 0.3, color=THECOLORS["orange"])
        Puck(Vec2D(6.0, 2.5), 0.45, 0.3)
        Puck(Vec2D(7.5, 2.5), 0.65, 0.3) 
        Puck(Vec2D(2.5, 5.5), 1.65, 0.3)
        Puck(Vec2D(7.5, 7.5), 0.95, 0.3)
    
    elif demo == 2:
        g.env.set_gravity("off")
        initial_states = [
            {"p1": {"rps":  4.0, "color": THECOLORS["brown"]},
             "p2": {"rps":  2.0, "color": THECOLORS["tan"]}},

            {"p1": {"rps":  2.0, "color": THECOLORS["tan"]},
             "p2": {"rps": -2.0, "color": THECOLORS["brown"]}},

            {"p1": {"rps":  3.1, "color": THECOLORS["tan"]},
             "p2": {"rps":  3.1, "color": THECOLORS["tan"]}},

            {"p1": {"rps":  0.0, "color": THECOLORS["white"]},
             "p2": {"rps":  2.0, "color": THECOLORS["tan"]}},

            {"p1": {"rps":  3.0, "color": THECOLORS["white"]},
             "p2": {"rps":  6.0, "color": THECOLORS["tan"]}},

            {"p1": {"rps": -3.1, "color": THECOLORS["tan"]},
             "p2": {"rps": -3.1, "color": THECOLORS["tan"]}},

            {"p1": {"rps":  0.0, "color": THECOLORS["tan"]},
             "p2": {"rps":  0.0, "color": THECOLORS["tan"]}}
        ]
        g.env.demo_variations[2]['count'] = len(initial_states)
        state = initial_states[g.env.demo_variations[2]['index']]
        print("Variation", g.env.demo_variations[2]['index'] + 1, 
              "   p1_rps =", state["p1"]["rps"], 
              "   p2_rps =", state["p2"]["rps"])

        p1 = Puck( Vec2D(2.0, 2.0), 1.7, 1.0, 
            border_px=10, color=state["p1"]["color"],
            angularVelocity_rps=state["p1"]["rps"],
            coef_rest=0.0, CR_fixed=True, 
            friction=2.0, friction_fixed=True
        )
        p2 = Puck( Vec2D(8.0, 6.75), 1.7, 1.0, 
            border_px=10, color=state["p2"]["color"],
            angularVelocity_rps=state["p2"]["rps"],
            coef_rest=0.0, CR_fixed=True,
            friction=2.0, friction_fixed=True
        )

        spring_strength_Npm2 = 20.0
        spring_length_m = 1.0
        Spring(p1, p2, spring_length_m, spring_strength_Npm2, width_m=0.15, c_damp=50.0, color=THECOLORS["yellow"])
    
        g.game_window.set_caption( g.game_window.caption + 
            f"     Variation {g.env.demo_variations[2]['index'] + 1}" +
            f"     rps = ({state['p1']['rps']:.1f}, {state['p2']['rps']:.1f})"
        )

    elif demo == 3:
        g.air_table.buildFence(onoff={'L':True,'R':True,'T':False,'B':True})
        g.env.set_gravity("off")
        initial_states = [
            {"p1": {"rps":   4.0, "color": THECOLORS["white"]},
             "p2": {"rps": -34.0, "color": THECOLORS["darkred"]},
             "p3": {"rps":  30.0, "color": THECOLORS["blue"]}},

            {"p1": {"rps":   0.0, "color": THECOLORS["white"]},
             "p2": {"rps":   4.0, "color": THECOLORS["darkred"]},
             "p3": {"rps": -15.0, "color": THECOLORS["blue"]}},

            {"p1": {"rps":  11.0, "color": THECOLORS["white"]},
             "p2": {"rps":   0.0, "color": THECOLORS["blue"]},
             "p3": {"rps":   0.0, "color": THECOLORS["blue"]}},

            {"p1": {"rps":  30.0, "color": THECOLORS["white"]},
             "p2": {"rps":   0.0, "color": THECOLORS["blue"]},
             "p3": {"rps": -30.0, "color": THECOLORS["white"]}},

            {"p1": {"rps":   4.0, "color": THECOLORS["darkred"]},
             "p2": {"rps":   4.0, "color": THECOLORS["darkred"]},
             "p3": {"rps":   4.0, "color": THECOLORS["darkred"]}}
        ]
        g.env.demo_variations[3]['count'] = len(initial_states)
        state = initial_states[g.env.demo_variations[3]['index']]
        print("Variation", g.env.demo_variations[3]['index'] + 1, 
              "   p1_rps =", state["p1"]["rps"], 
              "   p2_rps =", state["p2"]["rps"],
              "   p3_rps =", state["p3"]["rps"])
        
        p1p3_y_m = 2.3
        p1 = Puck( Vec2D(2.0, p1p3_y_m), 1.2, 1.0, 
            angularVelocity_rps=state["p1"]["rps"],
            coef_rest=0.0, CR_fixed=True, 
            friction=2.0, friction_fixed=True,
            border_px=10, color=state["p1"]["color"]
        )
        p3 = Puck( Vec2D(8.0, p1p3_y_m), 1.2, 1.0, 
            angularVelocity_rps=state["p3"]["rps"],
            coef_rest=0.0, CR_fixed=True, 
            friction=2.0, friction_fixed=True,
            border_px=10, color=state["p3"]["color"]
        )
        # Equilateral triangle:  h = (1/2) * √3 * a
        y_m = p1.pos_2d_m.y + (p3.pos_2d_m.x - p1.pos_2d_m.x) * 3**0.5 / 2.0
        x_m = p1.pos_2d_m.x + (p3.pos_2d_m.x - p1.pos_2d_m.x)/2.0
        p2 = Puck( Vec2D(x_m, y_m), 1.2, 1.0, 
            angularVelocity_rps=state["p2"]["rps"],
            coef_rest=0.0, CR_fixed=True, 
            friction=2.0, friction_fixed=True,
            border_px=10, color=state["p2"]["color"]
        )

        spring_strength_Npm2 = 15.0
        spring_length_m = 1.0
        spring_width_m = 0.10
        Spring(p1, p2, spring_length_m, spring_strength_Npm2, width_m=spring_width_m, c_damp=50.0, color=THECOLORS["yellow"])
        Spring(p1, p3, spring_length_m, spring_strength_Npm2, width_m=spring_width_m, c_damp=50.0, color=THECOLORS["yellow"])
        Spring(p2, p3, spring_length_m, spring_strength_Npm2, width_m=spring_width_m, c_damp=50.0, color=THECOLORS["yellow"])

        g.game_window.set_caption( g.game_window.caption + 
            f"     Variation {g.env.demo_variations[3]['index'] + 1}" +
            f"     rps = ({state['p1']['rps']:.1f}, {state['p2']['rps']:.1f}, {state['p3']['rps']:.1f})"
        )

    elif demo == 4:
        g.env.set_gravity("on")
        initial_states = [
            {'w1':{'angle_d':-0.17},'w2':{'angle_d':+4.00}},
            {'w1':{'angle_d':-5.00},'w2':{'angle_d':+2.00}},
            {'w1':{'angle_d':-9.00},'w2':{'angle_d':+1.00}},
            {'w1':{'angle_d': 0.00},'w2':{'angle_d':+9.00}},
            {'funnel_angle_d': 5},
            {'funnel_angle_d':15},
            {'funnel_angle_d':25},
            {'funnel_angle_d':35},
            {'funnel_angle_d':45}
        ]
        g.env.demo_variations[4]['count'] = len(initial_states)
        state = initial_states[g.env.demo_variations[4]['index']]

        # For the first group of variations, add a grid of pucks.
        if 'w1' in state:
            spacing_factor = 1.0
            grid_size = 10,5
            for j in range(grid_size[0]):
                for k in range(grid_size[1]):
                    if (k >= 1 and k <= 3):
                        puck_color_value = THECOLORS['orange']
                    else:
                        puck_color_value = THECOLORS['grey']

                    offset_2d_m = Vec2D(0.0, 4.5)
                    spacing_factor = 0.7
                    position_2d_m = Vec2D(spacing_factor*(j+1), spacing_factor*(k+1)) + offset_2d_m

                    Puck(position_2d_m, radius_m=0.25, density_kgpm2=1.0,
                            color=puck_color_value,
                            CR_fixed=True, coef_rest=0.8, friction=0.05, friction_fixed=True)

            Wall(Vec2D(4.0, 4.5), half_width_m=4.0, half_height_m=0.04, border_px=0,
                angle_radians=state['w1']['angle_d']*(math.pi/180)
            )
            Wall(Vec2D(7.0, 2.5), half_width_m=3.0, half_height_m=0.04, border_px=0,
                angle_radians=state['w2']['angle_d']*(math.pi/180)
            )
            details_desc = f"w1 angle = {state['w1']['angle_d']}   w2 angle = {state['w2']['angle_d']}"
        
        # Two walls symmetrically angled to produce a funnel at the bottom of the window, two pucks.
        elif 'funnel_angle_d' in state:
            # No side walls:
            g.air_table.buildFence(onoff={'L':False,'R':False,'T':True,'B':True})
            
            c_f = 1.0
            y_m = 7.0
            x_m = 0.0
            Puck(Vec2D(x_m,y_m), radius_m=1.25, density_kgpm2=1.0, CR_fixed=True, coef_rest=0.7, friction=c_f, friction_fixed=True)
            x_m = g.air_table.walls_dic['R_m']
            Puck(Vec2D(x_m,y_m), radius_m=1.25, density_kgpm2=1.0, CR_fixed=True, coef_rest=0.7, friction=c_f, friction_fixed=True)

            angle_d = state['funnel_angle_d']
            hw_m = 100.0
            hh_m = 0.10
            y_m = math.sin(angle_d * (math.pi/180)) * hw_m # Position walls so their center end is at the bottom of the window.
            cf_touch = math.cos(angle_d * (math.pi/180))   # Position walls so their center ends touch.
            x_m = (0.5 * g.air_table.walls_dic['R_m']) - cf_touch * hw_m
            Wall(Vec2D( x_m, y_m), half_width_m=hw_m, half_height_m=hh_m, border_px=0, angle_radians=-angle_d*(math.pi/180))
            x_m = (0.5 * g.air_table.walls_dic['R_m']) + cf_touch * hw_m
            Wall(Vec2D( x_m, y_m), half_width_m=hw_m, half_height_m=hh_m, border_px=0, angle_radians=+angle_d*(math.pi/180))

            details_desc = f"funnel angle = {state['funnel_angle_d']}"

        g.game_window.set_caption( g.game_window.caption + 
            f"     Variation {g.env.demo_variations[4]['index'] + 1}" +
            f"     {details_desc}"
        )

    elif demo == 5:
        g.env.set_gravity("off")
        """
        Spring-pinned pucks are placed in a regular-polygons arrangement. Pucks are placed
        at at polygon radius, R = r_puck / sin(π/n). The spring pins are positioned closer
        to the center providing tension to hold the pucks in contact.
        """
        initial_states = [
            {'n_pucks':2},
            {'n_pucks':3},
            {'n_pucks':4},
            {'n_pucks':5},
            {'n_pucks':6},
            {'n_pucks':7},
            {'n_pucks':8},
            {'n_pucks':9},
            {'n_pucks':1}
        ]
        g.env.demo_variations[5]['count'] = len(initial_states)
        state = initial_states[g.env.demo_variations[5]['index']]
        
        # no fence:
        g.air_table.buildFence(onoff={'L':False,'R':False,'T':False,'B':False})

        n_pucks = state['n_pucks']

        if n_pucks == 1:
            g.air_table.pinnedPuck(g.game_window.center_2d_m, radius_m=1.8)
        else:
            radius_m = 1.5
            polygon_radius_m = radius_m / math.sin(math.pi/n_pucks)
            center_to_puck_2d_m = Vec2D(0.0, polygon_radius_m)
            pin_offset_m = 0.4
            center_to_pin_2d_m = Vec2D(0.0, polygon_radius_m - pin_offset_m)
            for i in range(0, n_pucks):
                angle = (360 / n_pucks) * i

                rotated_c_to_puck_2d_m = center_to_puck_2d_m.rotated(angle)
                puck_position_2d_m = g.game_window.center_2d_m + rotated_c_to_puck_2d_m

                rotated_c_to_pin_2d_m = center_to_pin_2d_m.rotated(angle)
                pin_position_2d_m = g.game_window.center_2d_m + rotated_c_to_pin_2d_m

                g.air_table.pinnedPuck(puck_position_2d_m, radius_m=radius_m, 
                                       pin_position_2d_m=pin_position_2d_m)

        g.game_window.set_caption( g.game_window.caption + 
            f"     Variation {g.env.demo_variations[5]['index'] + 1}" +
            f"     pinned pucks = {n_pucks}"
        )

    elif demo == 6:
        g.env.set_gravity("off")
        initial_states = [
            {'type':'three-pucks'},
            {'type':'two-pucks'}
        ]
        g.env.demo_variations[6]['count'] = len(initial_states)
        state = initial_states[g.env.demo_variations[6]['index']]
        
        if state['type'] == 'three-pucks':
            density = 1.5
            radius = 0.7
            coef_rest_puck = 0.3

            p1 = Puck(Vec2D(2.00, 3.00), radius, density, coef_rest=coef_rest_puck, CR_fixed=True)
            p2 = Puck(Vec2D(3.50, 4.50), radius, density, coef_rest=coef_rest_puck, CR_fixed=True)
            p3 = Puck(Vec2D(5.00, 3.00), radius, density, coef_rest=coef_rest_puck, CR_fixed=True)
            
            # No springs on this one.
            Puck(Vec2D(3.50, 7.00), 0.95, density, coef_rest=coef_rest_puck, CR_fixed=True)

            spring_strength_Npm2 = 400.0
            spring_length_m = 2.5
            spring_width_m = 0.07
            spring_drag = 0.0
            spring_damper = 5.0

            Spring(p1, p2, spring_length_m, spring_strength_Npm2, width_m=spring_width_m, 
                c_drag=spring_drag, c_damp=spring_damper, color=THECOLORS["red"])
            Spring(p2, p3, spring_length_m, spring_strength_Npm2, width_m=spring_width_m, 
                c_drag=spring_drag, c_damp=spring_damper, color=THECOLORS["tan"])
            Spring(p3, p1, spring_length_m, spring_strength_Npm2, width_m=spring_width_m, 
                c_drag=spring_drag, c_damp=spring_damper, color=THECOLORS["gold"])

        elif state['type'] == 'two-pucks':
            p1 = Puck(Vec2D(2.00, 3.00),  0.4, 0.3)
            p2 = Puck(Vec2D(3.50, 4.50),  0.4, 0.3)
            
            spring_strength_Npm2 = 20.0 #18.0
            spring_length_m = 1.5
            Spring(p1, p2, spring_length_m, spring_strength_Npm2, width_m=0.2)

        g.game_window.set_caption( g.game_window.caption + 
            f"     Variation {g.env.demo_variations[6]['index'] + 1}"
        )

    elif demo == 7:
        def two_drone_special__rectangular():
            density = 0.7

            # a large pinned circular puck with a visible spoke
            #                              , r_m , density
            tempPuck = Puck(Vec2D(4.0, 1.0), 0.55, density, 
                angularVelocity_rps=0.5, color=THECOLORS["orange"], show_health=True, hit_limit=10
            )
            Spring(tempPuck, Vec2D(4.0, 1.0), strength_Npm=300.0, 
                pin_radius_m=0.03, width_m=0.02, c_drag = 1.5)
            
            # two pinned rectangles
            puck_position = Vec2D(0.0, g.game_window.UR_2d_m.y) + Vec2D(2.0, -2.0) # starting from upper left
            tempPuck = Puck(puck_position, 1.4, density, 
                angularVelocity_rps=0.5, rect_fixture=True, hw_ratio=0.1, show_health=True
            )
            Spring(tempPuck, puck_position, strength_Npm=300.0, 
                pin_radius_m=0.03, width_m=0.02, c_drag=10.0)
            
            puck_position = Vec2D(8.5, 4.0)
            tempPuck = Puck(puck_position, 1.4, density, 
                angularVelocity_rps=0.5, rect_fixture=True, hw_ratio=0.1, show_health=True
            )
            Spring(tempPuck, puck_position, strength_Npm=300.0, 
                pin_radius_m=0.03, width_m=0.02, c_drag=10.0)

            # row of small pinned pucks without spokes
            for m in range(0, 6):
                pinPoint_2d_m = Vec2D(2.0 + (m * 0.65), 4.0)
                tempPuck = Puck(pinPoint_2d_m, 0.25, density, color=THECOLORS["orange"], 
                                show_health=True, hit_limit=15, showSpoke=False)
                Spring(tempPuck, pinPoint_2d_m, strength_Npm=300.0, 
                    width_m=0.02, c_drag=1.5, pin_radius_m=0.03)
            
            # Make user/client controllable pucks for all the clients.
            y_puck_position_m = 1.0
            for client_name in g.env.clients:
                client = g.env.clients[client_name]
                if client.active and not client.drone:
                    # Box2D drag modeling is slightly different than that in the circular
                    # engines. So, c_drag set higher than the default value, 0.7.
                    g.air_table.buildControlledPuck( x_m=6.4, y_m=y_puck_position_m, r_m=0.45, client_name=client_name, sf_abs=False, c_drag=1.5)
                    y_puck_position_m += 1.2
                            
            # drone pucks
            g.air_table.buildControlledPuck( x_m=1.0, y_m=1.0, r_m=0.55, client_name="C5", sf_abs=False, drone=True, bullet_age_limit_s=3.0)
            g.air_table.buildControlledPuck( x_m=8.5, y_m=7.0, r_m=0.55, client_name="C6", sf_abs=False, drone=True, bullet_age_limit_s=3.0)
        
        def rectangle_in_middle():
            g.air_table.buildFence(onoff={'L':True,'R':False,'T':True,'B':True})

            density = 0.7
            puck_position_2d_m = g.game_window.center_2d_m
            tempPuck = Puck(puck_position_2d_m, 3.0, density, 
                angularVelocity_rps=0.5, rect_fixture=True, hw_ratio=0.05, show_health=True
            )
            Spring(tempPuck, puck_position_2d_m, strength_Npm=300.0, 
                pin_radius_m=0.03, width_m=0.02, c_drag=10.0)
            
            radius_m = 0.45
            set_off_m = radius_m + 0.5
            init_2d_m = Vec2D(set_off_m, set_off_m)
            g.air_table.buildControlledPuck(x_m=init_2d_m.x, y_m=init_2d_m.y, r_m=radius_m, client_name='local', sf_abs=False, c_drag=1.5)

        g.air_table.puckPopper_variations(demo, two_drone_special__rectangular, custom_1=rectangle_in_middle)
        
    elif demo == 8:
        g.env.set_gravity("on")
        g.air_table.throwJello_variations(demo)

    elif demo == 9:
        g.env.set_gravity("off")
        g.air_table.targetJello_variations(demo)
    
    elif demo == 0:
        # Demo 0 has four variations showing different puck arrangements and collisions:
        # a: Dominoes - Creates a row of increasingly larger rectangular pucks,
        #    then throws a small round puck to start a chain reaction
        # b: Pyramid stack - Builds a pyramid of tall rectangular pucks on the ground,
        #    then launches a heavy bowling ball at it
        # c: Billiards trick shot - Arranges circular pucks in a partial ring formation,
        #    with a cue ball that's shot to scatter them
        # d: Line of balls - Creates a line of identical circular pucks and a cue ball 
        #    with adjustable vertical offset (0%, 10%, or 50%) to demonstrate different 
        #    collision patterns

        initial_states = [
            {'variation':'a'},
            {'variation':'b'},
            {'variation':'c'},
            
            {'variation':'d', 'offset_percent':0},
            {'variation':'d', 'offset_percent':10},
            {'variation':'d', 'offset_percent':50},

            {'variation':'e', 'n_pucks':8},
            {'variation':'e', 'n_pucks':32},
            {'variation':'e', 'n_pucks':64},
            {'variation':'e', 'n_pucks':128},

            {'variation':'f'}
        ]
        g.env.demo_variations[demo]['count'] = len(initial_states)
        state = initial_states[g.env.demo_variations[demo]['index']]
        
        extra_note = ""

        if state['variation'] == 'a':
            g.air_table.buildFence(onoff={'L':True,'R':True,'T':False,'B':True})
            g.env.set_gravity("on")
            density = 0.7
            half_width_m = 0.01
            hw_ratio = 9.0
            x_position_m = 0.3
            for j in range(0, 9):
                y_puck_position_m = (half_width_m * hw_ratio) + 0.01
                Puck(Vec2D(x_position_m, y_puck_position_m), half_width_m, density, 
                    coef_rest=0.82,
                    color=THECOLORS['darkkhaki'], border_px=0,
                    rect_fixture=True, hw_ratio=hw_ratio, angle_r=0)
                half_width_m *= 1.5
                x_position_m *= 1.5

            # Throw a puck to get the chain reaction started.
            p1 = Puck(Vec2D(0.15, 0.06), 0.06, density, rect_fixture=False, angularVelocity_rps=0, 
                      bullet=True, color=THECOLORS['royalblue'], border_px=0)

            g.air_table.throw_puck(p1, Vec2D(1, 2) * 0.5, delay_s=2.0)

        elif state['variation'] == 'b':
            g.air_table.buildFence(onoff={'L':True,'R':True,'T':False,'B':True})
            g.env.set_gravity("on")

            # Pyramid on the ground
            puck_half_width_m = 0.1
            hw_ratio = 3.0 # height/width
            puck_half_height_m = puck_half_width_m * hw_ratio
            density = 1.0
            n_rows = 11

            x_gap_fraction = 0.70
            deltaX_m = 2*puck_half_width_m + (2*puck_half_width_m * x_gap_fraction)
            y_gap_fraction = 0.03
            deltaY_m = 2*puck_half_height_m + (2*puck_half_height_m * y_gap_fraction)

            x_position_start_m = 1.0
            x_position_m = x_position_start_m
            y_position_m = puck_half_height_m + (2*puck_half_height_m * y_gap_fraction)

            for i in range(n_rows):
                for j in range(i, n_rows):
                    Puck(Vec2D(x_position_m, y_position_m), puck_half_width_m, density, 
                        color=THECOLORS['darkkhaki'], border_px=0,
                        rect_fixture=True, hw_ratio=hw_ratio, angle_r=0, awake=False,
                        coef_rest=0.8, CR_fixed=True)
                    x_position_m += deltaX_m
                y_position_m += deltaY_m
                x_position_m = x_position_start_m + ((i+1) * deltaX_m/2.0)

            # This puck will be flung or bowled by the user at the target stack
            bowlingBall_density = 3.0
            bowlingBall_r_m = 0.3
            p1 = Puck(Vec2D(9.0, bowlingBall_r_m), bowlingBall_r_m, bowlingBall_density,
                        coef_rest=0.7, CR_fixed=True,
                        bullet=True, angularVelocity_rps=0, color=THECOLORS['royalblue'], border_px=0)
            
            g.air_table.throw_puck(p1, Vec2D(-10, 1.0) * 10, delay_s=2.0)

        elif state['variation'] == 'c':
            g.env.set_gravity("off")
            pool_trick_shot()

        elif state['variation'] == 'd':
            g.air_table.buildFence(onoff={'L':False,'R':False,'T':False,'B':False})
            g.env.set_gravity("off")
            pool_line_of_balls(state['offset_percent'])
            extra_note = f"offset_percent = {state['offset_percent']}"

        elif state['variation'] == 'e':
            g.env.set_gravity("off")
            g.air_table.makeSquareFence()
            g.air_table.buildFence(onoff={'L':True,'R':True,'T':True,'B':True})

            burst_of_pucks(state['n_pucks'], speed_mps=3, radius_m=0.1)
            extra_note = f"n_pucks = {state['n_pucks']}"

        elif state['variation'] == 'f':
            g.env.set_gravity("off")
            g.air_table.makeSquareFence()
            g.air_table.buildFence(onoff={'L':True,'R':True,'T':True,'B':True})

            radius_m = 0.4

            x_m = g.game_window.center_2d_m.x
            y_m = g.game_window.UR_2d_m.y - radius_m
            cue_pos_2d_m = Vec2D(x_m, y_m)

            # Now adjust the cue ball position so that it is not touching the wall.
            cue_pos_2d_m -= Vec2D(0.1, 0.1)

            p1 = Puck(cue_pos_2d_m, radius_m, 0.3, coef_rest=1.0, friction=0.0, color=THECOLORS["orange"], showSpoke=False)
            g.air_table.throw_puck(p1, Vec2D(-1, -1) * 2.0, delay_s=1.0)

            # Group of target pucks
            target_pos_2d_m = cue_pos_2d_m - Vec2D(2.00, 2.00)

            for i in range(2):
                Puck(target_pos_2d_m, radius_m, 0.3, coef_rest=1.0, friction=0.0, showSpoke=False)
                target_pos_2d_m -= Vec2D(1.00, 1.00)

        g.game_window.set_caption( g.game_window.caption + 
            f"     Variation {g.env.demo_variations[demo]['index'] + 1}    {extra_note}"
        )
            
    else:
        print("Nothing set up for this key.")

#============================================================
# main procedural script
#============================================================

def main():
    game_loop = GameLoop(engine_type="box2d", window_width_px=900, make_some_pucks=make_some_pucks)
    g.game_loop = game_loop
    game_loop.start(demo_index=7)

#============================================================
# Start everything.
#============================================================

if __name__ == '__main__':
    main()