def shoot(self, force_shot=False):
		#Force shot tells this to shoot even if a target 
		#is not obviously in view. NPC's will not take such wild shots.
		#Capital ship initially has 3 guns. one on top, one in front, and one on bottom.
		for i in range(len(self.weapons)):
			#If weapon is cool and nearest target is in range then fire.
			if not self.targets[i] is None and \
			self.weapons[i].cooldown == 0 and \
			cygeometry.distance(self.targets[i].lead_indicator, self.gunlocs[i]) < \
			self.weapons[i].weapon_range:
				angle = angleFromPosition(self.gunlocs[i], self.targets[i].lead_indicator)
				self.weapons[i].shoot(angle)
	def resetWarps(self):
		self.warps = []
		#Place warp portals
		for c in self.connections:
			node_id, location = c
			#Get the slope of the line from self.loc to this other warp
			angle = angleFromPosition(self.loc, location)
			scaledDistance = cygeometry.distance(self.loc, location) * WARP_PORTAL_SCALING
			#print scaledDistance #TESTING
			x,y = translate(self.loc, angle, scaledDistance)
			temp = objInstances.WarpPortal(x=x, y=y, destinationNode=node_id,
						method=globalvars.scenario_manager.goToInfiniteSpace)
			self.warps.append(temp)
	def bounceOff(self, other):
		'''Other is another physical object that this physical object 
		just struck and should bounce off of.

		two objects, A and B, collide. let theta be the angle of the line from the 
		center of A to the center of B. Let A be the smaller of the two. let 
		theta' be a line perpendicular to theta.
		If A's direction is less than 90 degrees from pointing at B then 
		reflect A's direction over theta'. Reduce both objects' speeds. 
		else move A's direction half way to theta in the direction away from B. 
		Increase A's speed. Decrease B's speed. (This is the case where A 
		is hit from behind despite moving in the same direction as B.) '''
		#If either object uses rectangular, we need to bounce off differently
		if self.useRectangular or other.useRectangular:
			#Determine if the collision is in the x direction or the y direction.
			selfRight = self.rect.topleft[0]+self.collisionwidth+self.collisiontopleft[0]
			otherRight = other.rect.topleft[0]+other.collisionwidth+other.collisiontopleft[0]
			selfLeft = self.rect.topleft[0]+self.collisiontopleft[0]
			otherLeft = other.rect.topleft[0]+other.collisiontopleft[0]
			if selfRight > otherRight or otherLeft > selfLeft:
				#horizontal collision
				if self.theta > 0:
					self.setAngle(180.0 - self.theta)
				else:
					self.setAngle(-180.0 - self.theta)
			else:
				#vertical collision
				self.setAngle(-self.theta)
		#The following is for collisions with objects that are not rectangular.
		else:
			angleToOther = self.getAngleToTarget(target=other)
			if abs(angleToOther) < 90: #Relatively head on collision
				bounce_off_angle = 90 - angleToOther
				if angleToOther < 0:
					self.turnClockwise(bounce_off_angle)
				else:
					self.turnCounterClockwise(bounce_off_angle)
				#Decrease speed by an amount related to the
				#headon-ed-ness of the collision
				self.speed = self.speed / (1.0 + float(bounce_off_angle) / 90.0)
				self.targetSpeed = self.speed
			else: #Rear end collision
				#Increase speed, as when an object is hit from behind.
				#The angle will also change slightly to be more in the
				#direction of the object that struck us.
				#Specifically, change our angle to be halfway between 
				#our angle and the angle of other.
				#First pass for code follows. This is good enough for now.
				amountToTurn = (180 - abs(angleToOther))/2
				if angleToOther < 0:
					self.turnCounterClockwise(amountToTurn)
				else:
					self.turnClockwise(amountToTurn)
				#Increase speed by adding other object's speed
				self.speed = self.speed+other.speed
				#Decrease other object's speed
				other.speed -= self.speed
				other.targetSpeed = other.speed
		#Prevent multiple consecutive collisions with the same object
		#Get the angle to move away from other's center.
		angle_to_move = geometry.angleFromPosition(\
				other.rect.center, self.rect.center)
		while self.inCollision(other):
			self.loc = geometry.translate(self.loc, angle_to_move, 1.0)
			self.rect.center = self.loc
Beispiel #4
0
def run(countdown=-1, track_FPS=False, track_efficiency=False):
    """Runs the game."""
    take_screenshot = False
    # For more efficient animations
    dirty_rects = []  # Places where we are drawing new images
    dirty_covers = []  # Places where we are covering old images with background

    start_time = datetime.datetime.now()
    tick_length = 0

    offsetx = 0
    offsety = 0
    offset = offsetx, offsety

    # key polling:
    # Use this to keep track of which keys are up and which
    # are down at any given point in time.
    keys = []
    for _i in range(322):
        keys.append(False)
        # mouse is [pos, button1, button2, button3,..., button6].
        # new Apple mice think they have 6 buttons.
        # Use this to keep track of which mouse buttons are up and which
        # are down at any given point in time.
        # Also the tuple, mouse[0], is the current location of the mouse.
    mouse = [(0, 0), 0, 0, 0, 0, 0, 0]

    # pygame setup:
    clock = pygame.time.Clock()

    running = True

    # Gather some data on efficiency by measuring the relationship between time burned in the ticks method and the number of objects to update on the screen
    globalvars.time_lapses = [0 for _ in xrange(1900)]
    globalvars.dirty_rect_size = [0 for _ in xrange(1900)]
    efficiency_index = 0

    # The in-round loop (while player is alive):
    while running:
        # Use this for more accurate profiling:
        if countdown != -1:
            countdown -= 1
            if countdown < 0:
                exit()

                # Draw everything on the screen. Do so either using dirty rects or just by
                # redrawing the whole screen. Dirty rects are usually more efficient.
        if update_mechanism == DIRTY:
            pygame.display.update(dirty_rects)
            pygame.display.update(dirty_covers)
        elif update_mechanism == FLIP:
            pygame.display.flip()
        else:
            above_threshold = len(dirty_rects) + len(dirty_covers) > UPDATE_THRESHOLD
            if above_threshold:
                pygame.display.flip()
            else:
                pygame.display.update(dirty_rects)
                pygame.display.update(dirty_covers)

                # Cover previous dirty rectangles and empty them out.
        if update_mechanism != FLIP:
            # Cover the dirty rectangles with background
            coverAll(dirty_rects)
            # Copy dirty rects into dirty covers
            dirty_covers = dirty_rects[:]
            # Empty dirty rects
            dirty_rects = []

        if take_screenshot:
            pygame.image.save(globalvars.screen, "screenshots/" + str(rd.randint(0, 999999999)) + ".jpeg")
            take_screenshot = False

        if track_FPS:
            # Calculate how long we took in the above loop to estimate the number of frames per second.
            # We want time_lapse to be stable.
            time_lapse = datetime.datetime.now() - start_time

            # Used for calculating actual frames per second in
            # order to determine when we are dropping frames
            # so that efficiency improvements can be made.
            start_time = datetime.datetime.now()

            # Alert user if fraps drops below half the desired threshold.
            if float(time_lapse.microseconds) / 1000000.0 > (2.0 / float(globalvars.FPS)):
                print "\nWarning: frames dropping."
                print "Goal frames per second is " + str(globalvars.FPS) + ". Current is " + str(
                    1.0 / (float(time_lapse.microseconds) / 1000000.0)
                )[
                    :2
                ]  # Cut off decimal because I don't care.
                print "Sizes of Sprite Groups follows:"
                print "Tangibles: " + str(len(globalvars.tangibles))
                print "Intangibles_bottom: " + str(len(globalvars.intangibles_bottom))
                print "Intangibles_top: " + str(len(globalvars.intangibles_top))
                print "Whiskerables: " + str(len(globalvars.whiskerables))

        if track_efficiency:
            tick_length = datetime.datetime.now()

            # frame maintainance:
            # aim for globalvars.FPS frames per second.
        clock.tick(globalvars.FPS)

        if track_efficiency:
            # We want this value, tick_length, to be large because that means the processor
            # is taking long rests because everything else is happening so efficiently.
            tick_length = datetime.datetime.now() - tick_length
            # Gather some efficiency data
            globalvars.time_lapses[efficiency_index] = tick_length.microseconds
            globalvars.dirty_rect_size[efficiency_index] = len(dirty_rects) + len(dirty_covers)
            efficiency_index = (efficiency_index + 1) % len(globalvars.time_lapses)

            # Display the panel
        if not globalvars.menu.main_panel is None:
            globalvars.menu.main_panel.draw()
            pygame.display.flip()
            # Check for another key press to remove the panel.
            for event in pygame.event.get():
                # Check for event m key pressed to remove the menu.
                if event.type == pygame.KEYDOWN and event.key == 109:
                    globalvars.menu.main_panel = None
                    redrawWholeBackground()
                    pygame.display.flip()
                    break
                    # Panel event handeling can make the panel itself None so we have
                    # to check if the panel has become None for every event. If the
                    # panel has become None we break and ignore further input events.
                elif globalvars.menu.main_panel is None:
                    redrawWholeBackground()
                    pygame.display.flip()
                    break
                    # Pass all other events to the panel
                else:
                    globalvars.menu.main_panel.handleEvent(event)
                    # Skip all the rest while displaying the menu.
                    # This effectively pauses the game.
            continue

            # event polling:
            # See what buttons may or may not have been pushed.
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mouse[event.button] = 1
                mouse[0] = event.pos
                # Also causes shooting
                globalvars.player.shoot(force_shot=True)
            elif event.type == pygame.MOUSEBUTTONUP:
                mouse[event.button] = 0
                # elif event.type == pygame.MOUSEMOTION:
                # 	mouse[0] = event.pos
            elif event.type == pygame.KEYDOWN:
                keys[event.key % 322] = 1
                # print "TODO TESTING: key press "+str(event.key)

                # Respond to key taps.
                # Keys that we want to respond to holding them down
                # will be dealt with below.
                if event.key == 273 or event.key == 119:  # Pressed up arrow or w key
                    # increase speed by a fraction of max up to max.
                    globalvars.player.targetSpeed = min(
                        globalvars.player.maxSpeed,
                        globalvars.player.targetSpeed + globalvars.player.maxSpeed * globalvars.player.speedIncrements,
                    )
                elif event.key == 274 or event.key == 115:  # Pressed down arrow or s key
                    # decrease speed by a fraction of
                    # max down to zero.
                    globalvars.player.targetSpeed = max(
                        0,
                        globalvars.player.targetSpeed - globalvars.player.maxSpeed * globalvars.player.speedIncrements,
                    )
                elif event.key == 27:  # escape key or red button
                    running = False
                elif event.key == 109:  # m key
                    # If player is dead, access the restart panel, not the testing panel.
                    if globalvars.player.isDead():
                        globalvars.menu.setRestartPanel()
                    elif globalvars.disable_menu:
                        globalvars.menu.setPausePanel()
                    else:
                        globalvars.menu.setShipPanel()
                    continue
                elif event.key == 98:  # b key
                    globalvars.player.parkingBrake()
                elif event.key == 113:  # q key
                    # Obliterate destination.
                    # Change to free flight.
                    globalvars.player.killDestination()
                elif event.key == 116:  # t key
                    # shoot a bunch of hit box testers
                    # in towards the player
                    print "Width: " + str(globalvars.player.image.get_width()) + " vs " + str(
                        globalvars.player.rect.width
                    )
                    print "Height: " + str(globalvars.player.image.get_height()) + " vs " + str(
                        globalvars.player.rect.height
                    )
                    test.hitBoxTest(globalvars.player.rect.center)
                elif event.key == 108:  # l key
                    import profilingObject

                    temp = profilingObject.CollisionAvoidanceTester()
                    globalvars.intangibles_bottom.add(temp)
                elif event.key == 111:  # o key
                    import profilingObject

                    temp = profilingObject.ProfilingObject()
                    globalvars.intangibles_bottom.add(temp)
                    # Begin profiling
                    import cProfile

                    cProfile.runctx("run(track_efficiency=True)", globals(), None, "profiling/game.run.profile")
                    exit()
                elif event.key == 121:  # y key
                    # Profile lots of methods, but not game.run()
                    profileEverything(offset)
                elif event.key == 117:  # u key
                    # Profile game.run()
                    import cProfile

                    print "Profiling game.run(countdown=1800). " + "Press escape to quit early. " + "Profiling will stop automatically after 30 seconds."
                    # Run for 1800 frames (or 30 seconds assuming 60 frames per second.
                    cProfile.runctx("run(countdown=1800)", globals(), None, "profiling/game.run.profile")
                    exit()
                elif event.key == 105:  # i key
                    # Record and display efficiency data.
                    # We want time_lapses to be large.

                    # Write efficiency data to file.
                    # filehandle = open('profiling/efficiency_data.txt', 'w')
                    # for i in xrange(len(globalvars.time_lapses)):
                    # 	filehandle.write(str(globalvars.time_lapses[i])+\
                    # 		', '+str(globalvars.dirty_rect_size[i])+'\n')
                    # filehandle.close()

                    # Plot the data then exit
                    import matplotlib.pyplot as plt

                    plt.plot(globalvars.time_lapses, "ro")
                    plt.plot(globalvars.dirty_rect_size)
                    plt.show()

                    plt.plot(globalvars.dirty_rect_size)
                    plt.show()

                    exit()
                elif event.key == 47:
                    # forward slash (question mark
                    # without shift) key.
                    # Useful for querying one time info.
                    print "Print player destination: " + str(globalvars.player.destx) + "," + str(
                        globalvars.player.desty
                    )
                elif event.key == 104 or event.key == 304:  # "h key" lower or upper case.
                    # Display help menu.
                    globalvars.menu.setHelpPanel()
                elif event.key == 112:  # p key - takes a screenshot
                    take_screenshot = True
                if event.key == 120:  # Pressed x key
                    globalvars.player.shoot(force_shot=True, weapon=globalvars.player.missile)
                if event.key == 122:  # Pressed z key
                    globalvars.player.shoot(force_shot=True, weapon=globalvars.player.mine)

            elif event.type == pygame.KEYUP:
                # Keep track of which keys are no longer
                # being pushed.
                keys[event.key % 322] = 0

                ##This will make the player move towards the mouse
                ##without any clicking involved.
                ##Set player destination to current mouse coordinates.
        if mouse[1]:
            x, y = pygame.mouse.get_pos()
            x += offsetx
            y += offsety
            globalvars.player.setDestination((x, y))

            # Respond to key holds.
            # Keys that we want to respond to tapping them
            # will be dealt with above.
        if keys[276] or keys[97]:  # Pressed left arrow or a key
            globalvars.player.turnCounterClockwise()
        elif keys[275] or keys[100]:  # Pressed right arrow or d key
            globalvars.player.turnClockwise()
            # This is not part of the above else if.
            # You can shoot and turn at the same time.
        if keys[32] or keys[99]:  # Pressed space bar or c key
            # Force shot tells this to shoot even if a target
            # is not obviously in view. NPC's will not take such wild shots.
            globalvars.player.shoot(force_shot=True)

            # Check all collisions
        collisionHandling()

        # If arena is non-zero, then make sure player and all
        # whiskerables are within it
        if globalvars.arena > 0:
            # The inner concentric ring bounces the player back
            # towards center (don't actually bounce, just change
            # angle directly towards center. The outer
            # concentric ring, defined by distance from center
            # plus object radius will bounce asteroids in a
            # semi random direction towards the center-ish area.

            # Make sure player is within arena.
            # If not, change player's heading to be towards 0,0
            if cygeometry.distance(globalvars.player.rect.center, (0.0, 0.0)) > globalvars.arena:
                globalvars.player.theta = geometry.angleFromPosition(globalvars.player.rect.center, (0.0, 0.0))
                globalvars.player.updateImageAngle()
                # Check each whiskerable and if it is more than
                # arena + diameter from center, then change its
                # angle to point somewhere within the arena too.
            for w in globalvars.whiskerables:
                if cygeometry.distance(w.rect.center, (0.0, 0.0)) > globalvars.arena + w.collisionradius:
                    # Whiskerables reflect randomly
                    # off arena boundaries towards a
                    # point somewhere within 3/4 the
                    # center of the arena.
                    limit = 3 * globalvars.arena / 4
                    x = rd.randint(-limit, limit)
                    y = rd.randint(-limit, limit)
                    if not w.direction is None:
                        # Asteroids distinguish between their orientation,
                        # theta, and direction of movement, direction.
                        # Thus we want direction changed, not theta.
                        w.direction = geometry.angleFromPosition(w.rect.center, (x, y))
                    else:
                        w.theta = geometry.angleFromPosition(w.rect.center, (x, y))

                        # update all sprites:

                        # First tell the ships what is closest to them
                        # so that they can avoid collisions
        setClosestSprites()
        # setClosestSpritesAlt() #TODO - This one should be more efficient and superior to the other, but it's not.

        # Update all the sprites
        globalvars.intangibles_bottom.update()
        globalvars.tangibles.update()
        globalvars.intangibles_top.update()

        # Get the offset based on the player location.
        offsetx = globalvars.player.rect.centerx - globalvars.CENTERX
        offsety = globalvars.player.rect.centery - globalvars.CENTERY
        offset = offsetx, offsety

        if update_mechanism == FLIP:
            # Draw the background over the screen.
            redrawWholeBackground()
            # Draw all the things that are on the screen
            for x in globalvars.intangibles_bottom:
                if x.isOnScreen(offset):
                    x.draw(offset)
            for x in globalvars.tangibles:
                if x.isOnScreen(offset):
                    x.draw(offset)
            for x in globalvars.intangibles_top:
                if x.isOnScreen(offset):
                    x.draw(offset)
        else:
            # Put on screen rects in dirty rects
            for x in globalvars.intangibles_bottom:
                if x.isOnScreen(offset):
                    dirty_rects.append(x.getDirtyRect(offset))
                    x.draw(offset)
            for x in globalvars.tangibles:
                if x.isOnScreen(offset):
                    dirty_rects.append(x.getDirtyRect(offset))
                    x.draw(offset)
            for x in globalvars.intangibles_top:
                if x.isOnScreen(offset):
                    dirty_rects.append(x.getDirtyRect(offset))
                    x.draw(offset)

                    # Draw player last so the background isn't drawn overtop of the player.
        globalvars.player.playerUpdate()
        if not globalvars.player.isDead() and globalvars.player.fuel > 0:
            if update_mechanism == DIRTY:
                dirty_rects.append(globalvars.player.getDirtyRect(offset))
            elif update_mechanism == FLIP:
                globalvars.player.drawAt((globalvars.CENTERX, globalvars.CENTERY))
            else:
                dirty_rects.append(globalvars.player.getDirtyRect(offset))
                if above_threshold:
                    globalvars.player.drawAt((globalvars.CENTERX, globalvars.CENTERY))
        else:
            # Make player death kick the player back to a menu where player
            # can choose to restart. Display a death screen then. Reset the
            # scenario and everything else.
            # Countdown before kicking player back to menu
            globalvars.deathcountdown -= 1
            if globalvars.deathcountdown < 0:
                globalvars.menu.setRestartPanel()