def __init__(self, astronomical_object, initial_distance=1000): ''' Initializes the FreeFallSimulation instance. @precondition: none. @postcondition: The instance is ready to use. You should bind_to_root though before you use the object. @param initial_distance: The initial distance of the falling object in meters. Defaults to 1000m. @param astronomical_object: The astronomical object the falling_object will free fall toward. ''' self._canvas = None self._velocity = None self._velocity_label = None self.falling_object = \ CanvasedFallingObject(FreeFallSimulation.WIDTH, FreeFallSimulation.FREE_FALL_HEIGHT, initial_distance=initial_distance) self.astronomical_object = astronomical_object
class FreeFallSimulation(object): ''' A representation of a free fall Simulation. Represents a free fall simulation for a single falling object and astronomical object. ''' '''The height of the Free Fall part of the canvas.''' FREE_FALL_HEIGHT = 400 '''The height of the planet part of the canvas.''' PLANET_IMG_HEIGHT = 150 '''The width of the entire canvas.''' WIDTH = 150 def __init__(self, astronomical_object, initial_distance=1000): ''' Initializes the FreeFallSimulation instance. @precondition: none. @postcondition: The instance is ready to use. You should bind_to_root though before you use the object. @param initial_distance: The initial distance of the falling object in meters. Defaults to 1000m. @param astronomical_object: The astronomical object the falling_object will free fall toward. ''' self._canvas = None self._velocity = None self._velocity_label = None self.falling_object = \ CanvasedFallingObject(FreeFallSimulation.WIDTH, FreeFallSimulation.FREE_FALL_HEIGHT, initial_distance=initial_distance) self.astronomical_object = astronomical_object def _kaboom(self): ''' Displays the kaboom image if it is not shown. @precondition: none. @postcondition: The kaboom image is displayed over the planet image if it is not already there. ''' if not self._kaboom_image.on_canvas(): height = FreeFallSimulation.FREE_FALL_HEIGHT self._kaboom_image.add_to_canvas(self._canvas, 0, height, anchor=tkinter.NW) def _unkaboom(self): ''' Removes the kaboom image if it is not shown. @precondition: none. @postcondition: The kaboom image is removed over the planet image if it is there. ''' if self._kaboom_image.on_canvas(): self._kaboom_image.remove_from_canvas(self._canvas) def bind_to_root(self, root, row, col): ''' binds this instances canvas and labels to the given root and sets them up. @precondition: none. @postcondition: A canvas object is place under the root widget using a grid and is placed in col, row. @param root: The root tkinter widget that this canvas will attach to. @param col: The column number in the root's grid where this canvas should be place. @param row: The row number in the root's grid where this canvas should be placed. ''' # Create a label with the astronomical object name. self._label = tkinter.Label(root, text=self.astronomical_object.name) self._label.grid(column=col, row=row, sticky=(tkinter.W, tkinter.E)) # Create the canvas and add it to the grid expanding to all # four corners. height = (FreeFallSimulation.FREE_FALL_HEIGHT + FreeFallSimulation.PLANET_IMG_HEIGHT) self._canvas = tkinter.Canvas(root, width=FreeFallSimulation.WIDTH, height=height, bg='black') self._canvas.grid(column=col, row=row + 1, sticky=(tkinter.N, tkinter.W, tkinter.E, tkinter.S)) self.falling_object.add_to_canvas(self._canvas) # Add the planet image at the bottom height = FreeFallSimulation.FREE_FALL_HEIGHT self.astronomical_object.image.add_to_canvas(self._canvas, 0, height, anchor=tkinter.NW) # Add a label for the gravitational force just below the # canvas. We use a StringVar because it will update the label # automatically. self._g = tkinter.StringVar() self._g.set("{0:.3f}".format(self.astronomical_object.gravity)) self._g_label = tkinter.Label(root, textvariable=self._g) self._g_label.grid(column=col, row=row + 2, sticky=(tkinter.N, tkinter.W, tkinter.E, tkinter.S)) # Add a label for the velocity just below the canvas. We use a # StringVar because it will update the label automatically. self._velocity = tkinter.StringVar() self._velocity.set('0.0') self._velocity_label = tkinter.Label(root, textvariable=self._velocity) self._velocity_label.grid(column=col, row=row + 3, sticky=(tkinter.N, tkinter.W, tkinter.E, tkinter.S)) # Add a label for the time to land below the canvas. We use a # StringVar because it will update the label automatically. self._ttl = tkinter.StringVar() self._ttl.set('0.0') self._ttl_label = tkinter.Label(root, textvariable=self._ttl) self._ttl_label.grid(column=col, row=row + 4, sticky=(tkinter.N, tkinter.W, tkinter.E, tkinter.S)) # Add a label for the distance to land below the canvas. We # use a StringVar because it will update the label # automatically. self._d = tkinter.StringVar() self._d.set('0.0') self._d_label = tkinter.Label(root, textvariable=self._d) self._d_label.grid(column=col, row=row + 5, sticky=(tkinter.N, tkinter.W, tkinter.E, tkinter.S)) self._kaboom_image = FreeFallCanvasImage('kaboom', 'kaboom.gif') def move_falling_object(self, time=0): ''' Move the falling object to the position at the specified time. Moves the falling object to its proper position at the given time and updates all the associated calculations. @precondition: none. @postcondition: The falling object is moved on the canvas to its position at the given time and the other calculates are updated to represent its new location. @param time: The time in seconds to where the ball should be moved. @raise FreeFallException: If the canvas is not initialized, this is thrown. Calling 'bind_to_root' first will solve this problem. ''' # We need a canvas to do anything. if self._canvas is None: from freefallsimulator import FreeFallException raise FreeFallException("Tkinter objects not initialized. " + "Call 'bind_to_root' first.") if self.has_collided(time): # If it's collided, set the time to 0. The resolution may not # be sufficient to show an exact 0. self._ttl.set('{0:.3f}'.format(float(0.0))) self._d.set('{0:.3f}'.format(float(0.0))) self._kaboom() self.falling_object.remove_object() else: # Prepare some calculations for display. g = self.astronomical_object.gravity t = time v = self.falling_object.calculate_velocity_at_time(gravity=g, time=t) t0 = self.falling_object.calculate_time_to_surface(gravity=g) d = self.falling_object.calculate_distance_to_planet(time=t, gravity=g) # Move it. self.falling_object.create_object() self.falling_object.move_object(gravity=g, time=t) self._velocity.set('{0:.3f}'.format(v)) self._ttl.set('{0:.3f}'.format(t0)) self._d.set('{0:.3f}'.format(d)) # We aren't at the end, so make sure we aren't displaying # the kaboom. self._unkaboom() def has_collided(self, time=0): ''' Determines if a collision has occurred at the given time. @precondition: none. @postcondition: none. @param time: The time in seconds to where the ball should be checked for a collision. @return: True if a collision has occurred at or before the given time, False otherwise. ''' i = self.falling_object.initial_distance g = self.astronomical_object.gravity tts = self.falling_object.calculate_time_to_surface(distance=i, gravity=g) return tts <= time