Example #1
0
class Zombie(BaseClass):
    """Create a Zombie
    Params:
    x: x coordinate of zombie
    y: y coordinate of zombie"""
    instances = set()  # set of all zombies
    with open(Options.mappath) as file:
        spawn_tiles = [
            i for i, x in enumerate(file.read().replace("\n", "")) if x == "Z"
        ]
    imgs = tuple(
        scale(pygame.image.load("assets/Images/Zombies/zombie{}.png".format(
            i))) for i in range(1, 5))
    speed_tuple = _get_vel_list()
    logging.debug("zombie speeds: %s", speed_tuple)
    health_func_tuple = (lambda h: h, lambda h: h / 2, lambda h: h * 1.2,
                         lambda h: h * 4)
    new_round_song = pygame.mixer.Sound(
        "assets/Audio/Other/new_round_short.ogg")
    new_round_song.set_volume(Options.volume)
    new_round_song_length = new_round_song.get_length()
    base_health = 100
    attack_range = Tile.length * 1.3  # Max distance from zombie for an attack, in pixels
    level = 1
    init_round = 0 if Options.no_zombies else 10  # How many zombies spawn on round 1
    left_round = init_round
    PickUp.zombie_init_round = init_round
    cool_down, play_song = False, True
    spawn_interval = Options.fps * 2  # Frames between spawns, decreses exponentially
    AStarThread = None

    def __init__(self, x, y):
        self.direction = math.pi
        type_ = randint(0, 3)
        self.type = type_
        self.org_img = Zombie.imgs[type_]
        self.img = self.org_img
        self.speed = Zombie.speed_tuple[type_]
        self.health_func = Zombie.health_func_tuple[type_]
        self.health = self.health_func(Zombie.base_health)
        self.org_health = self.health
        self.angle_to_vel = {
            0: (self.speed, 0),
            math.pi / 2: (0, -self.speed),
            math.pi: (-self.speed, 0),
            math.pi * 3 / 2: (0, self.speed)
        }
        self.vel = Vector(0, 0)
        super().__init__(x, y)
        Zombie.instances.add(self)
        self.path = []
        self.last_angle = 0.
        self.path_colour = Colours.random(s=(0.5, 1))
        logging.debug("speed: %s type. %s", self.speed, self.type)

    def set_target(self, next_tile):
        self.to = next_tile.pos
        angle = angle_between(*self.pos, *next_tile.pos)
        assert angle % (math.pi /
                        2) == 0., "angle: %s to: %s pos: %s mod %s" % (
                            angle, self.to, self.pos, angle % math.pi / 2)
        self.vel = Vector(*self.angle_to_vel[angle])

    @classmethod
    def update(cls, screen, survivor):
        del_zmbs = set()
        for zmb in cls.instances:
            if zmb.health <= 0.:
                Drop.spawn(zmb.pos)
                del_zmbs.add(zmb)
                stats["Zombies Killed"] += 1
                continue

            screen.blit(zmb.img, zmb.pos)
            zmb.health_bar(surface=screen)  # Health bar with rounded edges
            if Options.debug:
                for tile in zmb.path:
                    pygame.draw.circle(screen, zmb.path_colour,
                                       tile.get_centre(), Tile.length // 3)

            zmb_to_survivor_dist = (survivor.pos - zmb.pos).magnitude()

            if zmb_to_survivor_dist <= cls.attack_range:
                survivor.health -= 0.4

            if zmb.to is not None:  # if the zombie is not next to the player
                angle = angle_between(*zmb.pos, *(zmb.to + zmb.vel))
                if zmb.pos == zmb.to:  # If the zombie is directly on a tile
                    zmb.to = None  # Trigger A-Star, not run between tiles for performance
                else:
                    if "freeze" not in Drop.actives:
                        zmb.pos += zmb.vel
                if zmb.direction != angle:  # New direction, frame after a turn
                    zmb.rotate(angle)

            if zmb.to is None and zmb_to_survivor_dist > Tile.length:
                AStar(zmb, survivor).solve()
        cls.instances -= del_zmbs

    @classmethod
    def spawn(cls, screen, totalframes, survivor):
        """Spawning and rounds"""
        if Options.no_zombies:
            return

        if cls.left_round and not cls.cool_down:
            if totalframes % cls.spawn_interval == 0:
                cls.left_round -= 1
                if cls.play_song:
                    # filenanes are numbered in hexadecimal
                    file_nr = format(randint(0, 19), "x")
                    sound = pygame.mixer.Sound(
                        "assets/Audio/Spawn/zmb_spawn{}.wav".format(file_nr))
                    sound.set_volume(Options.volume)
                    sound.play()
                cls.play_song = not cls.play_song  # Loop True and False, only play every other spawn
                further_than_ = partial(further_than,
                                        survivor=survivor,
                                        min_dist=150)
                valid_tiles = list(filter(further_than_, cls.spawn_tiles))
                if not valid_tiles:
                    valid_tiles.extend(cls.spawn_tiles)
                spawn_idx = choice(valid_tiles)
                spawn_node = Tile.instances[spawn_idx]
                cls(*spawn_node.pos)
                logging.debug(
                    "spawn_idx: %s, spawn_node: %s, valid: %s, survivor: %s",
                    spawn_idx, spawn_node.pos, valid_tiles, survivor.pos)
        elif not (cls.instances
                  or cls.cool_down):  # Round is over, start cooldown
            cls.cool_down = int(totalframes +
                                Options.fps * cls.new_round_song_length)
            cls.new_round_song.play()
            pygame.mixer.music.pause()
            cooldown_time = cls.cool_down - totalframes
            cls.cooldown_counter = NextRoundCountdown(
                cls.new_round_song_length * Options.fps)
            logging.debug(
                "Round over, start cooldown; cooldown: %s frames, %s sec",
                cooldown_time, cooldown_time // Options.fps)

        elif totalframes == cls.cool_down:  # Cooldown is over, start round
            pygame.mixer.music.unpause()
            cls.cool_down = False
            cls.base_health *= 1.16
            cls.init_round = 10 + cls.level * 3
            cls.left_round = cls.init_round
            cls.level += 1
            cls.spawn_interval //= 1.14
            PickUp.zombie_init_round = cls.init_round
            PickUp.init_round = cls.level // 2 + 3
            PickUp.left_round = PickUp.init_round
            logging.debug(
                "level: %s, base health: %s, Zombies: %s, Pick-Ups: %s, Interval: %s",
                cls.level, cls.base_health, cls.init_round, PickUp.init_round,
                cls.spawn_interval)

        else:
            if cls.cool_down:
                cls.cooldown_counter.update(screen)

    def rotate(self, new_dir):
        """Rotate self.img, set self.direction to new_dir
        :param new_dir: The angle to rotate self clockwise from the x-axis in radians"""
        self.img = rotated(self.org_img, new_dir)
        self.direction = new_dir

    def health_bar(self, surface):
        """Draw a health bar with rounded egdes above the zombie"""
        colour = 170, 0, 0
        rect = pygame.Rect(*(self.pos - (0, 12)),
                           self.width * self.health / self.org_health,
                           self.height / 6)
        zeroed_rect = rect.copy()
        zeroed_rect.topleft = 0, 0
        image = pygame.Surface(rect.size).convert_alpha()
        image.fill((0, 0, 0, 0))

        corners = zeroed_rect.inflate(-6, -6)
        for attribute in ("topleft", "topright", "bottomleft", "bottomright"):
            pygame.draw.circle(image, colour, getattr(corners, attribute), 3)
        image.fill(colour, zeroed_rect.inflate(-6, 0))
        image.fill(colour, zeroed_rect.inflate(0, -6))

        surface.blit(image, rect)
Example #2
0
class Survivor(BaseClass):
    """Create the survivor
    Params:
    x: the x coordinate of the survivor
    y: the y coordinate of the survivor"""
    guns = (scale(pygame.image.load("assets/Images/Weapons/pistol2.png"),
                  Tile.size.scale(1 / 2, 1 / 4)),
            scale(pygame.image.load("assets/Images/Weapons/shotgun.png"),
                  Tile.size.scale(1 / 2, 1 / 4)),
            scale(pygame.image.load("assets/Images/Weapons/automatic2.png"),
                  Tile.size.scale(1 / 2, 1 / 4)),
            scale(pygame.image.load("assets/Images/Weapons/sniper.png"),
                  Tile.size.scale(1 / 2, 1 / 4)))
    imgs = {
        d: scale(
            pygame.image.load(
                "assets/Images/Players/player_{0}_{1}.png".format(
                    Options.gender, d)))
        for d in "nsew"
    }

    def __init__(self, x, y):
        self.current_gun = 0
        self.direction = pi * 3 / 1 / 2
        self.img = Survivor.imgs[dir2chr[self.direction]]
        super().__init__(x, y)
        self.health = 100 << 10 * Options.debug  # 100 if not debug, 10*2**10 if debug
        self.vel = Vector(0, 0)
        self.init_ammo_count = 100, 50, 150, 50
        self.ammo_count = list(self.init_ammo_count)

    def movement(self):
        """If survivor is between two tiles, or self.to hasn"t been updated:
              if survivor is on tile: Set self.to to None
              if survivor is between tile: Add self.vel to self.pos (Move)"""
        if self.to is not None:
            if self.pos == self.to:
                self.to = None
            else:
                self.pos += self.vel

    def draw(self, screen):
        """Draw survivor and survivor"s gun"""
        screen.blit(self.img, self.pos.as_ints())
        w = self.width
        h, q = w >> 1, w >> 2  # fractions of width for placing gun_img
        gun_pos = {
            pi: (-q, h),
            0: (h + q, h),
            pi * 3 / 2: (h, w),
            pi / 2: (h, -h)
        }
        gun_img = Survivor.guns[self.current_gun]
        gun_img_rotated = rotated(gun_img, self.direction)
        screen.blit(gun_img_rotated, self.pos + gun_pos[self.direction])

    def rotate(self, new_dir):
        """If new_dir isn"t self.direction, update self.img to new_dir
        :param new_dir: direction of player in radians"""
        if self.direction != new_dir:
            self.img = Survivor.imgs[dir2chr[new_dir]]
            self.direction = new_dir

    @property
    def ammo(self):
        """Returns the ammo of the survivor"s current gun
        >>> a = Survivor(0, 0)
        >>> a.current_gun = 0
        >>> a.ammo
        100
        >>> a.current_gun = 2
        >>> a.ammo
        150"""
        return self.ammo_count[self.current_gun]

    @ammo.setter
    def ammo(self, value):
        """Set the ammo of survivors current_gun to value"""
        self.ammo_count[self.current_gun] = value

    def set_target(self, next_tile):
        """Set the tile to which the survivor will be moving to next_tile"""
        self.to = next_tile.pos
        logging.debug("self.pos: %s, self.to: %s", self.to, self.pos)
Example #3
0
class PickUp(BaseClass):
    """Creates a PickUp
    Params:
    x: x coordinate of the PickUp
    y: y coordinate of the PickUp
    spawn_tile: The index of the tile on which the PickUp is
    type_: A float between 0 and 1. If it is over 2/3 the PickUp is ammo else health
    Example:
    >>> tile = Tile.instances[PickUp.spawn_tiles[0]]
    >>> a = PickUp(*tile.pos, PickUp.spawn_tiles[0], type_=0.9)
    >>> a.type
    "health"

    TODO: Add more pick ups"""

    with open(Options.mappath) as file:
        spawn_tiles = [
            i for i, x in enumerate(file.read().replace("\n", "")) if x == "P"
        ]

    init_round, left_round = 4, 4
    zombie_init_round = None

    images = {
        "ammo": scale(pygame.image.load("assets/Images/PickUps/ammo.png")),
        "health": scale(pygame.image.load("assets/Images/PickUps/health.png"))
    }
    sounds = {
        "ammo": pygame.mixer.Sound("assets/Audio/PickUp/ammo_short.ogg"),
        "health": pygame.mixer.Sound("assets/Audio/PickUp/health.ogg")
    }
    sounds["ammo"].set_volume(Options.volume)
    sounds["health"].set_volume(Options.volume)
    instances = set()

    def __init__(self, x, y, spawn_tile, type_):
        super().__init__(x, y)
        PickUp.instances.add(self)
        self.incr = randint(20, 35)
        self.spawn_tile = spawn_tile
        self.type = "ammo" if type_ < 2 / 3 else "health"
        PickUp.spawn_tiles.remove(spawn_tile)

    @classmethod
    def spawn(cls, survivor):
        _further_than = partial(further_than, survivor=survivor, min_dist=150)
        pos_spawn_tiles = list(filter(_further_than, cls.spawn_tiles))
        if not pos_spawn_tiles:  # If no pick-up spawn is far enough away
            if not cls.spawn_tiles:  # If all pick-up spawns are occupied, don"t spawn
                return
            pos_spawn_tiles.extend(cls.spawn_tiles)
        cls.left_round -= 1
        type_ = random()
        spawn_tile = choice(pos_spawn_tiles)
        spawn_node = Tile.instances[spawn_tile]
        cls(*spawn_node.pos, spawn_tile, type_)

    @classmethod
    def update(cls, screen, survivor, total_frames):
        if cls.left_round:
            try:
                if total_frames % ((Options.fps * cls.zombie_init_round * 2) //
                                   cls.init_round) == 0:
                    cls.spawn(survivor)
            except ZeroDivisionError:
                if total_frames % Options.fps * 10 == 0:
                    cls.spawn(survivor)
        del_pick_up = set()
        for pick_up in cls.instances:
            screen.blit(cls.images[pick_up.type], pick_up.pos.as_ints())
            if collide(*pick_up.pos, *pick_up._size, *survivor.pos,
                       *survivor._size):
                setattr(survivor, pick_up.type,
                        getattr(survivor, pick_up.type) + pick_up.incr)
                cls.sounds[pick_up.type].play()
                cls.spawn_tiles.append(pick_up.spawn_tile)
                del_pick_up.add(pick_up)
                del pick_up

        cls.instances -= del_pick_up
Example #4
0
class Bullet(BaseClass):
    """Creates a Bullet
    Params:
    pos: A Vector with a x and y attribute
    vel: The constant velocity of the bullet. A Vector with a x and y velocity
    type_: The type of bullet. An int between 0 and 3"""

    width, height = 7, 9
    instances = set()
    images = (scale(pygame.image.load("assets/Images/Bullets/pistol_b.png"),
                    Tile.size.scale(1 / 3, 1 / 5)),
              scale(pygame.image.load("assets/Images/Bullets/shotgun_b2.png"),
                    Tile.size.scale(1 / 3, 1 / 5)),
              scale(pygame.image.load("assets/Images/Bullets/automatic_b.png"),
                    Tile.size.scale(1 / 3, 1 / 5)),
              scale(pygame.image.load("assets/Images/Bullets/sniper_b2.png"),
                    Tile.size.scale(1 / 3, 1 / 5)))

    sounds = (pygame.mixer.Sound("assets/Audio/Gunshots/pistol.wav"),
              pygame.mixer.Sound("assets/Audio/Gunshots/shotgun2.wav"),
              pygame.mixer.Sound("assets/Audio/Gunshots/automatic.wav"),
              pygame.mixer.Sound("assets/Audio/Gunshots/sniper.wav"))
    for sound in sounds:
        sound.set_volume(Options.volume / 2)

    dmg_func = (lambda d: max(
        (-0.00442 * d**2 - 1.4273 * d + 1433.3) / Tile.length, 12), lambda d: (
            (432000 * math.e**((-1) / 20 * d) + 720) / (100 * math.e**(
                (-1) / 20 * d) + 1)) / Tile.length, lambda d: max(
                    (-2 * d + 1080) / Tile.length, 10),
                lambda d: 36 / Tile.length *
                (40 * math.log(d + 40) - 100) / 1.1)

    min_bullet_dist = (Tile.length * 2, Tile.length * 3, Tile.length,
                       Tile.length * 8)
    last_bullet = None

    new_keys = (0, -1), (0, 1), (-1, 0), (1, 0)
    vel2img = dict(zip(new_keys, new_dir_func.values()))

    # Exchange the keys of new_dir_func with new_keys

    def __init__(self, pos, vel, type_, survivor):
        if survivor.ammo_count[type_] <= 0:
            return  # Don"t create bullet if there is no ammo
        try:
            dist = (Bullet.last_bullet[1] - Bullet.last_bullet[0]).magnitude()
            if Bullet.min_bullet_dist[type_] >= dist:
                return  # Don"t create bullet if last_bullet is too close
        except TypeError:  # If Bulle.last_bullet hasn"t been updated yet. 1st bullet
            dist = None

        Bullet.sounds[type_].play()
        survivor.ammo_count[type_] -= 1
        self.type = type_
        self.orgpos = pos
        self.vel = vel
        self.dmg_drop = 1
        self.hits = set()
        Bullet.instances.add(self)
        stats["Bullets Fired"] += 1
        self.vel_as_signs = vel.signs()  # Eg. (-3, 0) -> (-1, 0)
        self.img = Bullet.vel2img[self.vel_as_signs](Bullet.images[type_])
        super().__init__(*pos, Bullet.width, Bullet.height)
        Bullet.last_bullet = pos, pos.copy(), vel
        logging.debug(
            "pos: %s, vel: %s, type: %s, dist: %s, last_bullet: %s, sign: %s" +
            "survivor pos: %s", pos, vel, type_, dist, Bullet.last_bullet,
            self.vel_as_signs, survivor.pos)

    def offscreen(self):
        return (self.pos.x < 0 or self.pos.y < 0
                or self.pos.x + self.width > Options.width
                or self.pos.y + self.height > Options.height)

    def calc_dmg(self):
        dist = (self.orgpos - self.pos).magnitude()
        dmg = Bullet.dmg_func[self.type](dist)
        dmg *= self.dmg_drop
        dmg *= 4 if "quad" in Drop.actives else 1
        return dmg

    @classmethod
    def update(cls, screen):
        try:
            cls.last_bullet[1] += cls.last_bullet[2]
        except TypeError:  # If no bullets has been fired last_bullet is None
            pass
        del_bullets = set()

        for bullet in cls.instances:
            bullet.pos += bullet.vel
            screen.blit(bullet.img, bullet.pos)

            if get_number(bullet.pos + bullet.vel) in Tile.solid_nums and "trans" not in Drop.actives \
                    or bullet.offscreen():
                del_bullets.add(bullet)
                del bullet
                continue
            for zombie in Zombie.instances - bullet.hits:
                if collide(*bullet.pos, *bullet._size, *zombie.pos,
                           *zombie._size):
                    dmg = bullet.calc_dmg()
                    assert dmg > 0
                    zombie.health -= dmg
                    logging.debug(
                        "type %s, dmg %s, dmg_drop %s, zh %s, 4xd: %s",
                        bullet.type, dmg, bullet.dmg_drop, zombie.health,
                        "quad" in Drop.actives)
                    bullet.dmg_drop /= 1.1
                    if not bullet.hits:
                        stats["Bullets Hit"] += 1
                    bullet.hits.add(zombie)

        cls.instances -= del_bullets
Example #5
0
class Drop(BaseClass):
    """Drop power ups similar to power ups in call of duty
    Current power ups are double damage for 5 seconds and max ammo and
    walking through walls

    The order of the drops in all lists is:
    0th max_ammo, 1st quad damage, 2nd freeze, 3rd through_walls

    params:
    ---------------
    pos: The tile on which the power is
    type_: An integer indicating the type of the drop as its index in Drop.effects

    TODO: Add more types of power ups"""

    instances = set()

    load_img = lambda s: scale(
        pygame.image.load("assets/Images/Drops/%s.png" % s))
    imgs = (load_img("max_ammo"), load_img("quad_damage"), load_img("freeze"),
            load_img("through_walls"))

    load_sound = lambda s: pygame.mixer.Sound("assets/Audio/Drop/%s.ogg" % s)
    sounds = (load_sound("max_ammo"), load_sound("quad_damage"),
              load_sound("freeze"), load_sound("through_walls"))
    for sound in sounds:
        sound.set_volume(Options.volume)

    effects = full_ammo, quad_dmg, freeze, through_walls

    actives = {}

    def __init__(self, pos, type_):
        self.type_ = type_
        self.countdown = Options.fps * 5
        Drop.instances.add(self)
        super().__init__(*pos)

    @classmethod
    def spawn(cls, pos):
        if random.random() < 2 / 3 if Options.debug else 14 / 15:
            return
        type_ = random.randint(0, len(cls.effects) - 1)
        cls(pos, type_)

    @classmethod
    def update(cls, screen, survivor):
        del_drops = set()
        for drop in cls.instances:
            drop.countdown -= 1
            if drop.countdown == 0:
                del_drops.add(drop)
                continue
            screen.blit(cls.imgs[drop.type_], drop.pos)
            if collide(*drop.pos, *drop._size, *survivor.pos, *survivor._size):
                cls.effects[drop.type_](survivor)
                cls.sounds[drop.type_].play()
                del_drops.add(drop)
                continue
        cls.instances -= del_drops
        for power_up, value in tuple(cls.actives.items()):
            cls.actives[power_up] -= 1
            if value == 0:
                if power_up == "trans":
                    new_tile = survivor.get_tile().closest_open_tile()
                    survivor.pos = new_tile.pos.copy()
                    survivor.to = None
                del cls.actives[power_up]