text: """
  pyPhysicsSandbox is a simple wrapper around Pymunk that makes it easy to write code to explore physics simulations.
  It's intended for use in introductory programming classrooms.
  
  Caution! The simulation does not behave well if you start out with shapes overlapping each other, especially if
  overlapping shapes are connected with joints.  To have overlapping shapes connected by joints, set the group on
  each shape to the same number to disable collision detection between those shape.
  
  Shapes far enough outside the simulation window (generally, above or below by the height of the window, or to
  either side by the width of the window) are automatically removed from the simulation and their active property
  set to False.  The distance can be modified, but be wary of making it too large...this keeps shapes that are not
  visible in the simulation and can slow the simulation down if the number of shapes grows too large.
  
  """
  
  import pygame
  import pymunk
  import math
  
  from pygame import Color
  from pygame import constants
  
  __docformat__ = "reStructuredText"
  
  
  __all__ = ['window', 'add_observer', 'gravity', 'resistance', 'mouse_clicked',
             'static_ball', 'ball', 'static_box', 'box', 'static_rounded_box',
             'rounded_box', 'static_polygon', 'polygon', 'static_triangle',
             'triangle', 'static_text', 'text', 'static_text_with_font',
             'text_with_font', 'static_line', 'line', 'pivot', 'gear',
             'motor', 'pin', 'rotary_spring', 'run', 'draw', 'Color',
             'cosmetic_text', 'cosmetic_text_with_font', 'num_shapes',
             'constants', 'deactivate', 'reactivate', 'mouse_point',
             'add_collision', 'slip_motor', 'set_margins', 'cosmetic_box',
             'cosmetic_rounded_box', 'cosmetic_ball', 'cosmetic_line',
             'cosmetic_polygon', 'cosmetic_triangle', 'spring',
             'color'
             ]
  
  
  pygame.init()
  
  space = pymunk.Space()
  space.gravity = (0.0, 500.0)
  space.damping = 0.95
  
  win_title = "Untitled"
  win_width = 500
  win_height = 500
  x_margin = win_width
  y_margin = win_height
  observers = []
  clicked = False
  default_color = Color('black')
  
  shapes = {}
  
  
  def window(title, width, height):
      """Sets the caption, width, and height of the window that will
      appear when run () is executed.
  
      :param title: the caption of the window
      :type title: string
      :param width: the width of the window in pixels
      :type width: int
      :param height: the height of the window in pixels
      :type height: int
  
      """
      global win_title
      global win_width
      global win_height
      global x_margin
      global y_margin
  
      win_title = title
      win_width = width
      win_height = height
      x_margin = win_width
      y_margin = win_height
  
  
  def add_observer(hook):
      """Adds an observer function to the simulation.  Every observer
      function is called once per time step of the simulation (roughly
      50 times a second).  The function should be defined like this:
  
          def function_name(keys):
              # do something each time step
  
      To pass a function in use the name of the function without the
      parenthesis after it.
  
      The observer function must take a single parameter which is a
      list of keys pressed this step.  To see if a particular key has
      been pressed, use something like this:
  
              if constants.K_UP in keys:
                  # do something based on the up arrow being pressed
  
      :param hook: the observer function
      :type hook: function
  
      """
      global observers
  
      observers.append(hook)
  
  
  def set_margins(x, y):
      """Sets the distance outside the simulation that shapes can be and remain active.
      This defaults to the window width and height.  You can change it to either remove
      shapes more quickly when they're out of view, or to allow creating shapes farther
      outside the visible window.
  
      :param x: horizontal margin
      :param y: vertical margin
      """
      global x_margin
      global y_margin
  
      x_margin = x
      y_margin = y
  
  
  def gravity(x, y):
      """Sets the direction and amount of gravity used by the simulation.
      Positive x is to the right, positive y is downward.  This value can
      be changed during the run of the simulation.
  
      :param x: The horizontal gravity
      :type x: int
      :param y: The vertical gravity
      :type y: int
  
      """
      space.gravity = (x, y)
  
  
  def color(c):
      """Sets the default color to use for shapes created after this
      call.  The function may be called at any point to change the
      color for new shapes.
  
      To see available color names go to
      https://sites.google.com/site/meticulosslacker/pygame-thecolors
      and hover the mouse pointer over a color of interest.
  
      :param c: the color name as a string
      :type c: str
      """
      global default_color
  
      default_color = Color(c)
  
  
  def resistance(v):
      """Sets the amount of velocity that all objects lose each second.
      This can be used to simulate air resistance.  Resistance value
      defaults to 1.0.  Values less than 1.0 cause objects to lose
      velocity over time, values greater than 1.0 cause objects to
      gain velocity over time.
  
      For example a value of .9 means the body will lose 10% of its
      velocity each second (.9 = 90% velocity retained each second).
  
      This value can be changed during the run of the simulation.
  
      :param v: The resistance value
      :type v: float
  
      """
      space.damping = v
  
  
  def mouse_clicked():
      """Returns True if the mouse has been clicked this time step. Usable only in an observer function.
  
      :rtype: bool
  
      """
      return clicked
  
  
  def mouse_point():
      """Returns the current location of the mouse pointer.
  
      If the mouse is out of the simulation window, this will return the last location of the mouse
      that was in the simulation window.
  
      :rtype: (x, y)
      """
      return pygame.mouse.get_pos()
  
  
  def static_ball(p, radius):
      """Creates a ball that remains fixed in place.
  
      :param p: The center point of the ball
      :type p: (int, int)
      :param radius: The radius of the ball
      :type radius: int
      :rtype: shape
  
      """
      return _ball(p, radius, pymunk.inf, True)
  
  
  def ball(p, radius, mass=-1):
      """Creates a ball that reacts to gravity.
  
      :param p: The center point of the ball
      :type p: (int, int)
      :param radius: The radius of the ball
      :type radius: int
      :param mass: The mass of the shape (defaults to 1)
      :type mass: int
      :rtype: shape
  
      """
      return _ball(p, radius, mass, False)
  
  
  def cosmetic_ball(p, radius):
      """Creates a ball that does not interact with the simulation in any way.
  
      :param p: The center point of the ball
      :type p: (int, int)
      :param radius: The radius of the ball
      :type radius: int
      :rtype: shape
  
      """
      return _ball(p, radius, 0, False, True)
  
  
  def _ball(p, radius, mass, static=False, cosmetic=False):
      from .ball_shape import Ball
  
      if mass == -1:
          mass = math.pi*radius*radius
  
      result = Ball(space, p[0], p[1], radius, mass, static, cosmetic)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def static_box(p, width, height):
      """Creates a box that remains fixed in place.
  
      :param p: The upper left corner of the box
      :type p: (int, int)
      :param width: The width of the box
      :type width: int
      :param height: The height of the box
      :type height: int
      :rtype: shape
  
      """
      return _box(p, width, height, pymunk.inf, True)
  
  
  def box(p, width, height, mass=-1):
      """Creates a box that reacts to gravity.
  
      :param p: The upper left corner of the box
      :type p: (int, int)
      :param width: The width of the box
      :type width: int
      :param height: The height of the box
      :type height: int
      :param mass: The mass of the shape (defaults to 1)
      :type mass: int
      :rtype: shape
  
      """
      return _box(p, width, height, mass, False)
  
  
  def cosmetic_box(p, width, height):
      """Creates a box that does not react with the simulation in any way.
  
      :param p: The upper left corner of the box
      :type p: (int, int)
      :param width: The width of the box
      :type width: int
      :param height: The height of the box
      :type height: int
      :rtype: shape
  
      """
      return _box(p, width, height, 0, False, 0, True)
  
  
  def _box(p, width, height, mass, static, radius=0, cosmetic=False):
      from .box_shape import Box
  
      if mass == -1:
          mass = width * height
  
      # Polygons expect x,y to be the center point
      x = p[0] + width / 2
      y = p[1] + height / 2
  
      result = Box(space, x, y, width, height, radius, mass, static, cosmetic)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def static_rounded_box(p, width, height, radius):
      """Creates a box with rounded corners that remains fixed in place.
  
      :param p: The upper left corner of the box
      :type p: (int, int)
      :param width: The width of the box
      :type width: int
      :param height: The height of the box
      :type height: int
      :param radius: The radius of the rounded corners
      :type radius: int
      :rtype: shape
  
      """
      return _box(p, width, height, pymunk.inf, True, radius)
  
  
  def rounded_box(p, width, height, radius, mass=-1):
      """Creates a box with rounded corners that reacts to gravity.
  
      :param p: The upper left corner of the box
      :type p: (int, int)
      :param width: The width of the box
      :type width: int
      :param height: The height of the box
      :type height: int
      :param radius: The radius of the rounded corners
      :type radius: int
      :param mass: The mass of the shape (defaults to 1)
      :type mass: int
      :rtype: shape
  
      """
      return _box(p, width, height, mass, False, radius)
  
  
  def cosmetic_rounded_box(p, width, height, radius):
      """Creates a box with rounded corners that does not interact with the simulation in any way.
  
      :param p: The upper left corner of the box
      :type p: (int, int)
      :param width: The width of the box
      :type width: int
      :param height: The height of the box
      :type height: int
      :param radius: The radius of the rounded corners
      :type radius: int
      :rtype: shape
  
      """
      return _box(p, width, height, 0, False, radius, True)
  
  
  def static_polygon(vertices):
      """Creates a polygon that remains fixed in place.
  
      :param vertices: A tuple of points on the polygon
      :type vertices: ((int, int), (int, int), ...)
      :rtype: shape
  
      """
      return _polygon(vertices, pymunk.inf, True)
  
  
  def polygon(vertices, mass=-1):
      """Creates a polygon that reacts to gravity.
  
      :param vertices: A tuple of points on the polygon
      :type vertices: ((int, int), (int, int), ...)
      :param mass: The mass of the shape (defaults to 1)
      :type mass: int
      :rtype: shape
  
      """
      return _polygon(vertices, mass, False)
  
  
  def cosmetic_polygon(vertices):
      """Creates a polygon that does not interact with the simulation in any way.
  
      :param vertices: A tuple of points on the polygon
      :type vertices: ((int, int), (int, int), ...)
      :rtype: shape
  
      """
      return _polygon(vertices, 0, False, True)
  
  
  def _polygon(vertices, mass, static, cosmetic=False):
      from .poly_shape import Poly
      from .util import poly_centroid
      from .util import poly_area
  
      x, y = poly_centroid(vertices)
  
      if mass == -1:
          mass = poly_area(vertices)
  
      vertices = [(v[0] - x, v[1] - y) for v in vertices]
      result = Poly(space, x, y, vertices, 0, mass, static, cosmetic)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def static_triangle(p1, p2, p3):
      """Creates a triangle that remains fixed in place.
  
      :param p1: The first point of the triangle
      :type p1: (int, int)
      :param p2: The second point of the triangle
      :type p2: (int, int)
      :param p3: The third point of the triangle
      :type p3: (int, int)
      :rtype: shape
  
      """
      return _triangle(p1, p2, p3, pymunk.inf, True)
  
  
  def triangle(p1, p2, p3, mass=-1):
      """Creates a triangle that reacts to gravity.
  
      :param p1: The first point of the triangle
      :type p1: (int, int)
      :param p2: The second point of the triangle
      :type p2: (int, int)
      :param p3: The third point of the triangle
      :type p3: (int, int)
      :param mass: The mass of the shape (defaults to 1)
      :type mass: int
      :rtype: shape
  
      """
      return _triangle(p1, p2, p3, mass, False)
  
  
  def cosmetic_triangle(p1, p2, p3):
      """Creates a triangle that does not interact with the simulation in any way.
  
      :param p1: The first point of the triangle
      :type p1: (int, int)
      :param p2: The second point of the triangle
      :type p2: (int, int)
      :param p3: The third point of the triangle
      :type p3: (int, int)
      :rtype: shape
  
      """
      return _triangle(p1, p2, p3, 0, False, True)
  
  
  def _triangle(p1, p2, p3, mass, static, cosmetic=False):
      from .poly_shape import Poly
      from .util import poly_area
  
      x1, y1 = p1
      x2, y2 = p2
      x3, y3 = p3
  
      x = (x1 + x2 + x3) / 3
      y = (y1 + y2 + y3) / 3
      vertices = ((x1 - x, y1 - y), (x2 - x, y2 - y), (x3 - x, y3 - y))
  
      if mass == -1:
          mass = poly_area(vertices)
  
      result = Poly(space, x, y, vertices, 0, mass, static, cosmetic)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def static_text(p, caption):
      """Creates a text rectangle that remains fixed in place, using
      Arial 12 point font.
  
      :param p: The upper left corner of the text rectangle
      :type p: (int, int)
      :param caption: The text to display
      :type caption: string
      :rtype: shape
  
      """
      return _text(p, caption, pymunk.inf, True)
  
  
  def text(p, caption, mass=-1):
      """Creates a text rectangle that reacts to gravity, using
      Arial 12 point font.
  
      :param p: The upper left corner of the text rectangle
      :type p: (int, int)
      :param caption: The text to display
      :type caption: string
      :param mass: The mass of the shape (defaults to 1)
      :type mass: int
      :rtype: shape
  
      """
      return _text(p, caption, mass, False)
  
  
  def cosmetic_text(p, caption):
      """Creates text that displays on the screen but does not interact
      with other objects in any way.
  
      :param p: The upper left corner of the text
      :type p: (int, int)
      :param caption: The text to display
      :type caption: string
      :rtype: shape
  
      """
      return _text(p, caption, 0, False, True)
  
  
  def _text(p, caption, mass, static, cosmetic=False):
      from .text_shape import Text
  
      if mass == -1:
          mass = 10 * len(caption)
  
      result = Text(space, p[0], p[1], caption, "Arial", 12, mass, static, cosmetic)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def static_text_with_font(p, caption, font, size):
      """Creates a text rectangle that remains fixed in place.
  
      :param p: The upper left corner of the text rectangle
      :type p: (int, int)
      :param caption: The text to display
      :type caption: string
      :param font: The font family to use
      :type font: string
      :param size: The point size of the font
      :type size: int
      :rtype: shape
  
      """
      return _text_with_font(p, caption, font, size, pymunk.inf, True)
  
  
  def text_with_font(p, caption, font, size, mass=-1):
      """Creates a text rectangle that reacts to gravity.
  
      :param p: The upper left corner of the text rectangle
      :type p: (int, int)
      :param caption: The text to display
      :type caption: string
      :param font: The font family to use
      :type font: string
      :param size: The point size of the font
      :type size: int
      :param mass: The mass of the shape (defaults to 1)
      :type mass: int
      :rtype: shape
  
      """
      return _text_with_font(p, caption, font, size, mass, False)
  
  
  def cosmetic_text_with_font(p, caption, font, size):
      """Creates text that displays on the screen but does not interact
      with other objects in any way.
  
      :param p: The upper left corner of the text
      :type p: (int, int)
      :param caption: The text to display
      :type caption: string
      :param font: The font family to use
      :type font: string
      :param size: The point size of the font
      :type size: int
      :rtype: shape
  
      """
      return _text_with_font(p, caption, font, size, 0, False, True)
  
  
  def _text_with_font(p, caption, font, size, mass, static, cosmetic=False):
      from .text_shape import Text
  
      if mass == -1:
          mass = 10 * len(caption)
  
      result = Text(space, p[0], p[1], caption, font, size, mass, static, cosmetic)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def static_line(p1, p2, thickness):
      """Creates a line segment that remains fixed in place.
  
      :param p1: The starting point of the line segement
      :type p1: (int, int)
      :param p2: The ending point of the line segement
      :type p2: (int, int)
      :param thickness: The thickness of the line segement
      :type thickness: int
      :rtype: shape
  
      """
      return _line(p1, p2, thickness, pymunk.inf, True)
  
  
  def line(p1, p2, thickness, mass=-1):
      """Creates a line segment that will react to gravity.
  
      :param p1: The starting point of the line segement
      :type p1: (int, int)
      :param p2: The ending point of the line segement
      :type p2: (int, int)
      :param thickness: The thickness of the line segement
      :type thickness: int
      :param mass: The mass of the shape (defaults to 1)
      :type mass: int
      :rtype: shape
  
      """
      return _line(p1, p2, thickness, mass, False)
  
  
  def cosmetic_line(p1, p2, thickness):
      """Creates a line segment that does not interact with the simulation in any way.
  
      :param p1: The starting point of the line segement
      :type p1: (int, int)
      :param p2: The ending point of the line segement
      :type p2: (int, int)
      :param thickness: The thickness of the line segement
      :type thickness: int
      :rtype: shape
  
      """
      return _line(p1, p2, thickness, 0, False, True)
  
  
  def _line(p1, p2, thickness, mass, static, cosmetic=False):
      from .line_segment import Line
  
      if mass == -1:
          mass = math.sqrt(math.pow(p1[0]-p2[0], 2)+math.pow(p1[1]-p2[1], 2))*thickness
  
      result = Line(space, p1, p2, thickness, mass, static, cosmetic)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def pivot(p):
      """Creates a pivot joint around which shapes can freely rotate.
      Shapes must be connected to the pivot using the connect method
      on the returned shape.  The pivot joint remains fixed in place.
  
      :param p: The point at which to place the pivot
      :type p: (int, int)
      :rtype: shape
  
      """
      from .pivot_joint import Pivot
  
      result = Pivot(space, p[0], p[1])
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def gear(shape1, shape2):
      """Connects two objects such that their rotations become the same.
      Can be used in conjunction with a motor on one shape to ensure the
      second shape rotates at the same speed as the first.
  
      :param shape1: The first shape to connect
      :type shape1: shape
      :param shape2: The second shape to connect
      :type shape2: shape
      :rtype: shape
  
      """
      from .gear_joint import Gear
  
      result = Gear(space, shape1, shape2)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def motor(shape1, speed=5):
      """Creates a constant rotation of the given shape around its
      center point.  The direction of rotation is controlled by the
      sign of the speed.  Positive speed is clockwise, negative speed
      is counter-clockwise.
  
      :param shape1: The shape to connect to the motor
      :type shape1: shape
      :param speed: The speed at which to rotate the shape
      :type speed: int
      :rtype: shape
  
      """
      from .motor_joint import Motor
  
      result = Motor(space, shape1, speed)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def pin(p1, shape1, p2, shape2):
      """Creates a connection between the shapes at the given positions.
      Those points on the shapes will remain that same distance apart,
      regardless of movement or rotation.
  
      :param p1: The point on the first shape
      :type p1: (int, int)
      :param shape1: The first shape to connect via the pin
      :type shape1: shape
      :param p2: The point on the second shape
      :type p2: (int, int)
      :param shape2: The second shape to connect via the pin
      :type shape2: shape
      :rtype: shape
  
      """
      from .pin_joint import Pin
  
      result = Pin(space, p1, shape1, p2, shape2)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def spring(p1, shape1, p2, shape2, length, stiffness, damping):
      """Creates a connection between the shapes at the given positions.
      Those points on the shapes will remain that same distance apart,
      regardless of movement or rotation.
  
      :param p1: The point on the first shape
      :type p1: (int, int)
      :param shape1: The first shape to connect via the pin
      :type shape1: shape
      :param p2: The point on the second shape
      :type p2: (int, int)
      :param shape2: The second shape to connect via the pin
      :type shape2: shape
      :param length: The length the spring wants to be
      :type length: float
      :param stiffness: The spring constant (Young’s modulus)
      :type stiffness: float
      :param damping: How soft to make the damping of the spring
      :type damping: float
      :rtype: shape
  
      """
      from .spring_joint import Spring
  
      p1 = (p1[0]-shape1.position[0], p1[1]-shape1.position[1])
      p2 = (p2[0]-shape2.position[0], p2[1]-shape2.position[1])
  
      result = Spring(space, p1, shape1, p2, shape2, length, stiffness, damping)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def slip_motor(shape1, shape2, rest_angle, stiffness, damping, slip_angle, speed):
      """Creates a combination spring and motor.  The motor will rotate shape1
      around shape2 at the given speed.  When shape1 reaches the slip angle it
      will spring back to the rest_angle.  Then the motor will start to rotate
      the object again.
  
      :param shape1: The first shape to connect via the spring
      :type shape1: shape
      :param shape2: The second shape to connect via the spring
      :type shape2: shape
      :param rest_angle: The desired angle between the two objects
      :type rest_angle: float
      :param stiffness: the spring constant (Young's modulus)
      :type stiffness: float
      :param damping: the softness of the spring damping
      :type damping: float
      :param slip_angle: The angle at which to release the object
      :type slip_angle: float
      :param speed: The speed at which to rotate the shape
      :type speed: int
      :rtype: shape
  
      """
      from .slip_motor import SlipMotor
  
      result = SlipMotor(space, shape1, shape2, rest_angle, stiffness, damping, slip_angle, speed)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def rotary_spring(shape1, shape2, angle, stiffness, damping):
      """Creates a spring that constrains the rotations of the given shapes.
      The angle between the two shapes prefers to be at the given angle, but
      may be varied by forces on the objects.  The spring will bring the objects
      back to the desired angle.  The initial positioning of the shapes is considered
      to be at an angle of 0.
  
      :param shape1: The first shape to connect via the spring
      :type shape1: shape
      :param shape2: The second shape to connect via the spring
      :type shape2: shape
      :param angle: The desired angle between the two objects
      :type angle: float
      :param stiffness: the spring constant (Young's modulus)
      :type stiffness: float
      :param damping: the softness of the spring damping
      :type damping: float
      :rtype: shape
  
      """
      from .rotary_spring import RotarySpring
  
      result = RotarySpring(space, shape1, shape2, angle, stiffness, damping)
      result.color = default_color
      shapes[result.collision_type] = result
  
      return result
  
  
  def num_shapes():
      """Returns the number of active shapes in the simulation.
  
      :rtype int
      """
      return len(shapes)
  
  
  def deactivate(shape):
      """Removes the given shape from the simulation.  The shape will no longer
      display or interact with other objects
  
      :param shape: the shape to deactivate
      """
      if not shape.active:
          return
  
      shape.deactivate()
      del shapes[shape.collision_type]
  
  
  def reactivate(shape):
      """The given shape will be reactivated.  Its position and velocity will remain the same
      as it was when it was deactivated.
  
      :param shape: the shape to activate
      """
      if shape.active:
          return
  
      shape.reactivate()
      shapes[shape.collision_type] = shape
  
  
  def add_collision(shape1, shape2, handler):
      """Tells the sandbox to call a function when the two given shapes collide.
      The handler function is called once per collision, at the very start of the
      collision.
  
      The handler function is passed three parameters.  The first two are the
      colliding shapes, the third is the point of the collision, e.g.:
  
          handler(shape1, shape2, p)
  
      :param shape1: the first shape in the collision
      :param shape2: the other shape in the collision
      :param handler: the function to call
      :return:
      """
      temp = space.add_collision_handler(shape1.collision_type, shape2.collision_type)
      temp.data['handler'] = handler
      temp.begin = handle_collision
  
  
  def handle_collision(arbiter, space, data):
      shape1 = shapes[arbiter.shapes[0].collision_type]
      shape2 = shapes[arbiter.shapes[1].collision_type]
      p = arbiter.contact_point_set.points[0].point_a
  
      return data['handler'](shape1, shape2, p)
  
  def _calc_margins():
      global x_margin
      global y_margin
  
      for shape in shapes:
          x, y = shapes[shape].position
          x = abs(x)
          y = abs(y)
  
          if x_margin < x:
              x_margin = x
  
          if y_margin < y:
              y_margin = y
  
  
  def run(do_physics=True):
      """Call this after you have created all your shapes to actually run the simulation.
      This function returns only when the user has closed the simulation window.
  
      Pass False to this method to do the drawing but not activate physics.
      Useful for getting the scene right before running the simulation.
  
      :param do_physics: Should physics be activated or not
      :type do_physics: bool
      """
      global clicked
  
      _calc_margins()
  
      screen = pygame.display.set_mode((win_width, win_height))
      pygame.display.set_caption(win_title)
      clock = pygame.time.Clock()
      running = True
  
      while running:
          keys = []
          clicked = False
  
          for event in pygame.event.get():
              if event.type == pygame.QUIT:
                  running = False
  
              if event.type == pygame.KEYDOWN:
                  keys.append(event.key)
  
              if event.type == pygame.MOUSEBUTTONDOWN:
                  clicked = True
  
          for observer in observers:
              observer(keys)
  
          screen.fill((255, 255, 255))
  
          # Should automatically remove any shapes that are
          # far enough below the bottom edge of the window
          # that they won't be involved in anything visible
          shapes_to_remove = []
          for collision_type, shape in shapes.items():
              if shape.position.x > win_width + x_margin:
                  shapes_to_remove.append(shape)
  
              if shape.position.x < -x_margin:
                  shapes_to_remove.append(shape)
  
              if shape.position.y > win_height + y_margin:
                  shapes_to_remove.append(shape)
  
              if shape.position.y < -y_margin:
                  shapes_to_remove.append(shape)
  
          for shape in shapes_to_remove:
              deactivate(shape)
  
          # Also adjust positions for any shapes that are supposed
          # to wrap and have gone off an edge of the screen.
          for collision_type, shape in shapes.items():
              if shape.wrap_x:
                  if shape.position.x < 0:
                      shape.position = (win_width - 1, shape.position.y)
  
                  if shape.position.x >= win_width:
                      shape.position = (0, shape.position.y)
  
              if shape.wrap_y:
                  if shape.position.y < 0:
                      shape.position = (shape.position.x, win_height - 1)
  
                  if shape.position.y >= win_height:
                      shape.position = (shape.position.x, 0)
  
          # Now draw the shapes that are left
          for collision_type, shape in shapes.items():
              shape.draw(screen)
  
          if do_physics:
              space.step(1 / 50.0)
  
          pygame.display.flip()
          clock.tick(50)
  
      pygame.quit()
  
  
  def draw():
      """Call this after you have created all your shapes to actually draw them.
      This function only returns after you close the window.
  
      This is an alias for run(False).
      """
      run(False)
  