Example #1
0
class GUI():
    def __init__(self, screen_rect, bg_img):

        # Setup Screen and background image
        self.screen = pygame.display.set_mode((screen_rect.w, screen_rect.h))
        self.screen_rect = screen_rect
        self.bg_img = bg_img.convert()

        # Init starfield (layer above background, slowly scrolls on loop)
        self.star_field = \
            objects.object_types["starField"](
            speed = -0.3,
            surface = self.screen)

        # Init player ship, add it to shipGroup
        self.player_ship = objects.object_types["ship"](position=(50, 50),
                                                        activate=True)
        ship.Ship.shipGroup.add(self.player_ship)

        # Init the alien infested asteroid, add to infestedGroup
        self.infested_asteroid = objects.object_types["infested"](
            position=(self.screen_rect.w - 150, 200))
        infested.Infested.infestedGroup.add(self.infested_asteroid)

        # Init space station, add to spaceStation group
        self.space_station = objects.object_types["spaceStation"](
            position=(75, self.screen_rect.h / 2))
        spaceStation.SpaceStation.spaceStationGroup.add(self.space_station)

        # Init scorekeeping, add to score group
        self.score = objects.object_types["score"](
            position=(self.screen_rect.w, 0),
            time=time.time(),
            spaceStation=self.space_station)
        score.Score.scoreGroup.add(self.score)

        # For the graph section
        self.paths = None
        self.graph_timer = Timer(150 / 1000, self.graph_update)

        # TODO: remove this demo feature
        self.OPTIONS = 0

    def p1_button(self, e, down):
        '''
        Handles user input, allowing player_ship to be piloted

        Args:
        event e, a pygame event
        down, a boolean representing key event type (down==True)
        '''
        if e == K_RIGHT:             \
                            self.player_ship.k_right = down*self.player_ship.turn_speed
        elif e == K_LEFT:             \
                            self.player_ship.k_left = down*self.player_ship.turn_speed
        elif e == K_UP:             \
                            self.player_ship.k_up = down*self.player_ship.acceleration
        elif e == K_DOWN:             \
                            self.player_ship.k_down = down*self.player_ship.acceleration
        elif e == K_SPACE and down:
            self.player_ship.fire_missile()
        elif e == K_TAB and down:
            self.OPTIONS = (self.OPTIONS + 1) % 4
            print(self.OPTIONS)

    def asteroid_spawn(self):
        '''
        Generates an asteroid offscreen right which will travel towards
        spacestation.

        Consecutive asteroids will have semi-uniform motion, defined by
        the speed and direction. Asteroid spawn positions will be 
        divided into rough 'sections' to help create a uniform asteroid field.        
        '''
        # Divide the screen into rough sections (1.25 asteroid height)
        spacing_height = asteroid.Asteroid.sprite.get_rect().h * 1.25
        # Determine how many integer sections fit on screen
        spacing_number = int(self.screen_rect.h / spacing_height)

        # The second term positions the asteroid offscreen by some amount
        x = self.screen_rect.w + random.randrange(10, 50)
        # First term offsets the spacing so asteroids do not spawn
        # offscreen in the vertical axis. Second term determines
        # which 'section' to spawn asteroid in.
        y = 0.5 * spacing_height + \
            spacing_height * random.randint(0,spacing_number)

        # Field movement properties
        speed = random.triangular(0.5, 3, 2)
        direction = random.uniform(170, 190)

        # Init asteroid object, add it to asteroid group
        new_asteroid = objects.object_types["asteroid"](position=(x, y),
                                                        speed=speed,
                                                        direction=direction)
        asteroid.Asteroid.asteroidGroup.add(new_asteroid)

    def alien_spawn(self, QTY):
        '''
        Adds an integer QTY potential aliens to the infested
        asteroid 'aliens' attribute. This quantity will dictate
        the amount of aliens which can spawn from the alien_hop
        method. (I call it 'spawn pool')
        '''
        self.infested_asteroid.aliens += QTY

    def alien_hop(self):
        '''
        Handles creation of new aliens, movement of existing aliens,
        and drawing of teleport beams for successful hops.        
        '''
        # Only create aliens if there is a path
        if not self.paths is None:
            # Send an alien along each path
            for p in self.paths:
                if self.infested_asteroid.aliens != 0:
                    new_alien = objects.object_types["alien"](
                        asteroid=self.infested_asteroid,  # Start at source
                        path=p[1:])  #No need to include source in path
                    alien.Alien.alienGroup.add(new_alien)
                    self.infested_asteroid.aliens -= 1  # Decrement 'spawn pool'
            self.paths = None

        # Only move if the spacestation is alive
        if self.space_station.alive():
            target = self.space_station
            for al in alien.Alien.alienGroup:
                hop = None
                # Alien movement
                hop = al.path_hop(target)

                # Optional alien path drawing
                if self.OPTIONS == 1 and not al.path is None:
                    self.alien_path_beams(al)

                # If hop successful, two nodes were returned, draw the beam
                if not hop is None:
                    beam.Beam.beamGroup.add(\
                        objects.object_types["beam"](\
                            obj1 = hop[0],
                            obj2 = hop[1],
                            color = (128,255,0),
                            surface = self.screen))

    def offscreen(self):
        '''
        Deactivates objects which go offscreen
        '''
        # Since we don't want asteroids deleting before offscreen, add padding
        padding = 50
        # Asteroids
        for o in asteroid.Asteroid.asteroidGroup:
            if o.position[0] < -padding or o.position[0] \
                    > self.screen_rect.w + padding or \
                    o.position[1] < -10 or o.position[1] \
                    > self.screen_rect.h+10:
                o.deactivate()
        # Missiles
        for o in missile.Missile.missileGroup:
            if o.position[0] < -padding or o.position[0] \
                    > self.screen_rect.w + padding or \
                    o.position[1] < -padding or o.position[1] > \
                    self.screen_rect.h+padding:
                o.deactivate()
        # Aliens
        for o in alien.Alien.alienGroup:
            if o.position[0] < -padding or o.position[0] \
                    > self.screen_rect.w + padding or \
                    o.position[1] < -padding or o.position[1] > \
                    self.screen_rect.h+padding:
                o.deactivate()

    def update(self):
        '''
        Called by game.py, calls the update methods of various groups/objects.
        Also handles object collisions.
        '''

        self.star_field.update()
        self.score.update(time.time())

        collision.ast_mis(self.screen)
        collision.ast_ast()

        # Optional ship collision with asteroids
        if self.OPTIONS == 2:
            collision.ship_ast(self.screen)

        asteroid.Asteroid.asteroidGroup.update()
        ship.Ship.shipGroup.update()
        missile.Missile.missileGroup.update()

        self.graph_timer.update()

        alien.Alien.alienGroup.update()

    def draw(self):
        '''
        Draws the background elements and game objects.
        '''
        # Re-draw entire background
        self.screen.blit(self.bg_img, (0, 0))
        self.star_field.draw(self.screen)
        self.score.draw(self.screen)

        # Draw the objects
        infested.Infested.infestedGroup.draw(self.screen)
        asteroid.Asteroid.asteroidGroup.draw(self.screen)
        ship.Ship.shipGroup.draw(self.screen)
        missile.Missile.missileGroup.draw(self.screen)
        spaceStation.SpaceStation.spaceStationGroup.draw(self.screen)
        alien.Alien.alienGroup.draw(self.screen)

        # Draw special effects
        # Particles draw simple circles on update
        particle.Particle.particleGroup.update()

        # Beams draw simple lines on update
        beam.Beam.beamGroup.update()

        # Update the screen
        pygame.display.flip()

    def graph_update(self):
        '''
        Updates the graph (flow network), modifies the GUI paths attribute
        to include any paths returned from the max flow algorithm.
        
        Everytime this function is called, the graph is created from scratch,
        and max flow called on this new flow network.        
        '''
        # Reset paths
        self.paths = None

        # Since the graph creates new source/sink nodes, store these along
        # with the graph object
        g, s, t = graph.gen_flow_network(
            asteroid.Asteroid.asteroidGroup.sprites(),  # Nodes
            self.infested_asteroid,  # Source s
            self.space_station,  # Sink t
            alien.Alien.radius)  # The max teleport radius

        # Optional graph edge drawing
        if self.OPTIONS == 3:
            self.graph_beams(g)

        # Run max flow on the newly created graph object
        flow = graph.max_flow(g, s, t)
        if flow > 0:  # There is at least 1 valid flow path
            # Reconstruct flows to yield a list of path lists.
            self.paths = graph.reconstruct_flows(g, s, t)

    def graph_beams(self, g):
        '''
        Simply for demoing, draws a line for all edges in the graph object
        created in graph_update using the beam class.
        '''
        for e in g.edges():
            beam.Beam.beamGroup.add(\
                objects.object_types["beam"](\
                    health = 1,
                    obj1 = e[0],
                    obj2 = e[1],
                    color = (255,255,0),
                    surface = self.screen))

    def alien_path_beams(self, alien):
        '''
        Simply for demoing, draws a line for all edges in the alien's path.
        Green means still viable, red means out of range.
        '''
        curr = alien.asteroid

        for succ in alien.path[1:]:
            if euclidD(succ.position, curr.position) > alien.radius:
                color = (255, 153, 153)
            else:
                color = (102, 155, 102)
            beam.Beam.beamGroup.add(\
                objects.object_types["beam"](\
                    health = 40,
                    obj1 = curr,
                    obj2 = succ,
                    color = color,
                    surface = self.screen))
            curr = succ
Example #2
0
 def test_timer(self):
     timer = Timer(0.05)
     self.assertFalse(timer.update())
     time.sleep(0.05)
     self.assertTrue(timer.update())
Example #3
0
class Tank(entities.Entity, entities.ProjectileCollider, entities.Blocking):
    def __init__(self, location, graphics, heading=Vector(1, 0)):
        super().__init__()
        self.graphics = graphics

        self.heading = Vector(0, -1)
        self.direction = Direction.NORTH
        self.moving = False
        self.type = type

        self.maxHitPoints = 10
        self.hitpoints = self.maxHitPoints
        self.movementSpeed = 1
        self.scorePoints = 0

        self.shielded = False
        self.shieldEndTime = 0

        self.weapon = Weapon(self, level=1)

        self.controller = None
        self.controllerTimer = Timer(50)

        tileBlockedFunction = lambda tile: not tile is None and tile.blocksMovement
        self.movementHandler = MovementHandler(self, tileBlockedFunction)

        self.setLocation(location)
        self.setHeading(heading)
        self.lastHitTime = None
        self.lastHitVector = Vector(0, 0)
        self.destroyCallback = None
        self.destroyed = False

    def setScorePoints(self, points):
        self.scorePoints = points

    def getScorePoints(self):
        return self.scorePoints

    def setMaxHitpoints(self, hitpoints):
        self.maxHitPoints = hitpoints
        self.repair()

    def repair(self):
        self.hitpoints = self.maxHitPoints

    def setController(self, controller):
        self.controller = controller
        self.playerControlled = isinstance(controller,
                                           tankcontroller.PlayerTankController)

    def getController(self):
        return self.controller

    def isPlayerControlled(self):
        return self.playerControlled

    def update(self, time, timePassed):
        if self.controllerTimer.update(time):
            self.controller.update(time, timePassed)

        if self.moving:
            movementVector = self.heading.multiplyScalar(
                self.movementSpeed * timePassed * 0.05).round()
            self.movementHandler.moveEntity(movementVector)

        self.checkIfShieldIsDone(time)

        self.moving = False
        pass

    def render(self, screen, offset, time):
        extraOffset = Vector(0, 0)

        if not self.lastHitTime == None and time - self.lastHitTime > 50:
            extraOffset = self.lastHitVector.multiplyScalar(-1).toUnit()

        drawOffset = Vector(offset[0], offset[1])
        self.graphics.render(screen,
                             drawOffset.add(self.location).add(extraOffset),
                             self.direction)
        self.controller.render(screen)

    def moveSingleStep(self, direction):
        self.setHeading(direction)
        self.movementHandler.moveEntity(direction.toUnit())

    def moveInDirection(self, direction):
        self.setHeading(direction)
        self.moving = True

    def canMoveInDirection(self, direction):
        return self.movementHandler.canMove(direction)

    def fire(self, time):
        if self.weapon.canFire(time):
            location = self.getProjectileFireLocation()
            self.weapon.fire(location, self.heading, time)

    def getProjectileFireLocation(self):
        halfProjectileSize = Vector(2, 2)
        location = self.getCenterLocation()

        return location.subtract(halfProjectileSize)
        # halfProjectileSize = Vector(2, 2)
        # location = self.getCenterLocation()
        # location = location.subtract(halfProjectileSize)
        # location = location.add(self.heading.toUnit().multiplyScalar(self.size.y / 2))
        # return location

    def hitByProjectile(self, projectile, time):
        if self.shielded:
            return

        self.lastHitTime = time
        self.lastHitVector = projectile.directionVector

        self.hitpoints -= projectile.power
        if self.hitpoints <= 0:
            self.destroy()

    def setImage(self, image):
        self.image = image
        self.setSize(Vector(self.image.get_width(), self.image.get_height()))

    def getHeading(self):
        return self.heading

    def setHeading(self, newHeading):
        self.heading = newHeading
        self.direction = self.getDirectionFromVector(self.heading)
        self.setGraphics(self.direction)

    def setGraphics(self, direction):
        self.setImage(self.graphics.baseImages[direction])
        self.turretImage = self.graphics.turretImages[direction]
        self.turretOffset = self.graphics.turretOffsets[direction]

    def getWeapon(self):
        return self.weapon

    def setWeapon(self, newWeapon):
        self.weapon = newWeapon

    def enableShield(self, duration):
        self.shielded = True
        self.shieldEndTime = pygame.time.get_ticks() + duration
        print(f'Shield enabled for {duration} seconds')

    def checkIfShieldIsDone(self, time):
        if self.shielded and time >= self.shieldEndTime:
            self.shielded = False
            print('Shield has ran out')

    def setDestroyCallback(self, callback):
        self.destroyCallback = callback

    def fireDestroyCallback(self):
        if self.destroyCallback != None:
            self.destroyCallback(self)

    def getHitpoints(self):
        return self.hitpoints

    def destroy(self):
        self.destroyed = True
        self.createExplosion()
        self.fireDestroyCallback()
        self.markDisposable()

    def createExplosion(self):
        image = images.get('explosion')
        entities.manager.add(
            entities.effect.Effect(image, self.getCenterLocation(), 300))

    def isDestroyed(self):
        return self.destroyed
Example #4
0
class AiTankController(TankController):
    def __init__(self, entity):
        self.entity = entity
        self.fireTimer = Timer(500)
        self.lastMovementTime = pygame.time.get_ticks()
        self.pendingPathSearch = None
        self.plannedPath = None
        self.pathPlanTime = 0
        self.searchGridFunction = SearchGridGenerator.getSearchSpaceCellValueForTile
        self.stepLength = 50
    
    def update(self, time, timePassed):
        if self.fireTimer.update(time):
            self.fire(time)

        if self.isPathPlanningPending() and self.isPathPlanningCompleted():
            if self.pendingPathSearch.pathFound():
                self.plannedPath = PlannedPath(self.pendingPathSearch.getPath())
                self.resetLastMovementTime(pygame.time.get_ticks())
            self.pendingPathSearch = None

    def render(self, screen):
        pass
        #self.renderPlannedPath(screen)

    def renderPlannedPath(self, screen):
        if self.plannedPath != None:
            image = images.get('projectile')
            for step in self.plannedPath.path:
                screen.blit(image, ((step[0] * 8) + 8, step[1] * 8))

    def pathRecalculationNeeded(self, time):
        return (not self.hasPath() and not self.isPathPlanningPending()) or self.isPlannedPathExpired(time)

    def isPlannedPathExpired(self, time):
        return time - self.pathPlanTime > 5000

    def canMoveAlongPath(self):
        return self.hasPath() and not self.plannedPath.targetReached()

    def moveAlongPath(self, time):
        movementSteps = int((time - self.lastMovementTime ) / self.stepLength)
        if movementSteps > 0:
            for _ in range(movementSteps):
                self.stepTowardsTarget()
                if self.plannedPath.targetReached():
                    break
            self.resetLastMovementTime(time)

    def resetLastMovementTime(self, time):
        self.lastMovementTime = time

    def stepTowardsTarget(self):
        targetStep = self.toWorldSpaceTuple(self.plannedPath.getTargetStep())
        self.moveTowardsLocation(targetStep)
        self.plannedPath.moveToNextStepIfCurrentStepIsReached(self.entity.getLocation().toIntTuple())

    def fire(self, time):
        self.entity.fire(time)
        self.pickRandomFireTime()

    def pickRandomFireTime(self):
        self.fireTimer.setInterval(random.randint(400, 600))

    def plotPathToLocation(self, targetLocation):
        searchGrid = SearchGridGenerator.generateSearchGridFromPlayfield(self.searchGridFunction)
        start = self.toSearchSpaceCoordinateTuple(self.entity.getLocation())
        end = self.toSearchSpaceCoordinateTuple(targetLocation)
        
        self.pendingPathSearch = PathFindingTask(searchGrid, start, end)
        PathfinderWorker.queueTask(self.pendingPathSearch)

        self.pathPlanTime = pygame.time.get_ticks()

    def isPathPlanningPending(self):
        return self.pendingPathSearch != None

    def isPathPlanningCompleted(self):
        return self.pendingPathSearch != None and self.pendingPathSearch.isCompleted()

    def hasPath(self):
        return self.plannedPath != None

    def moveTowardsLocation(self, targetLocation):
        location = self.entity.location
        if location.x < targetLocation[0]:
            self.entity.moveSingleStep(utilities.vectorRight)
        elif location.x > targetLocation[0]:
            self.entity.moveSingleStep(utilities.vectorLeft)
        elif location.y < targetLocation[1]:
            self.entity.moveSingleStep(utilities.vectorDown)
        elif location.y > targetLocation[1]:
            self.entity.moveSingleStep(utilities.vectorUp)

    def toSearchSpaceCoordinateTuple(self, coordinates):
        return (int(coordinates.x / 8), int(coordinates.y / 8))

    def toWorldSpaceTuple(self, coordinates):
        return (int(coordinates[0] * 8), int(coordinates[1] * 8))