Exemplo n.º 1
0
    def new(self):
        # initialize all variables and do all the setup for a new game
        self.all_sprites = pg.sprite.Group()
        self.walls = pg.sprite.Group()
        self.chest = pg.sprite.Group()
        self.items = pg.sprite.Group()
        self.carrots = pg.sprite.Group()
        for tile_object in self.map.tmxdata.objects:
            if tile_object.name == "player":
                print("player location: ", tile_object.x, tile_object.y)
                self.player = AIPlayer(self, tile_object.x, tile_object.y)
            # self.player = Player(self, tile_object.x, tile_object.y)

            if tile_object.name == "wall":
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name == "carrot":
                Carrot(self,
                       tile_object.x,
                       tile_object.y,
                       groups=[self.all_sprites, self.carrots, self.items])
                self.total_carrots += 1
            if tile_object.name == "chest":
                Chest(self,
                      tile_object.x,
                      tile_object.y,
                      groups=[self.all_sprites, self.chest, self.items])

        self.camera = Camera(self.map.width, self.map.height)
Exemplo n.º 2
0
    def new(self):

        # Generic Game variables
        self.ticker = Ticker()
        self.bus = Publisher()
        self.game_state = c.GAME_STATE_PLAYING
        self.player_took_action = False
        self.minimap_enable = False
        self.objects = []
        self.level = 1

        # initializing map structure
        self.map = MapFactory("LordCroket Caves - Level {}".format(self.level),
                              self.all_images).map
        self.minimap = Minimap(self)

        # Field of view
        self.fov = FieldOfView(self)

        # We have 5 sprites groups: two below the player, the player one and two above
        # They are drawn in the order below:
        self.player_min2_sprite_group = pg.sprite.Group()
        self.player_min1_sprite_group = pg.sprite.Group()
        self.player_sprite_group = pg.sprite.Group(
        )  # the default group, also called level 0
        self.player_plus1_sprite_group = pg.sprite.Group()
        self.player_plus2_sprite_group = pg.sprite.Group()
        self.all_groups = [
            self.player_min2_sprite_group, self.player_min1_sprite_group,
            self.player_sprite_group, self.player_plus1_sprite_group,
            self.player_plus2_sprite_group
        ]

        # Camera
        self.camera = Camera(self.map.tile_width * TILESIZE_SCREEN,
                             self.map.tile_height * TILESIZE_SCREEN)

        self.place_doors_stairs_traps(self.level)

        # Place player
        all_pos = self.map.get_all_available_tiles(c.T_FLOOR,
                                                   self.objects,
                                                   without_objects=True)
        self.player = PlayerHelper(self, all_pos.pop())
        self.visible_player_array = self.fov.get_vision_matrix_for(
            self.player, flag_explored=True)

        # place monsters and items
        ItemFactory(self).build_list(20)  # 220
        MonsterFactory(self).build_list(120)

        # And we end with the screens...
        self.screens = {
            c.GAME_STATE_INVENTORY: InventoryScreen(self,
                                                    c.GAME_STATE_PLAYING),
            c.GAME_STATE_MAP: MapScreen(self, c.GAME_STATE_PLAYING),
            c.GAME_STATE_CHARACTER: CharacterScreen(self,
                                                    c.GAME_STATE_PLAYING),
            c.GAME_STATE_PLAYING: PlayingScreen(self, None)
        }
Exemplo n.º 3
0
 def clear_sprites(self):
     game = self.game
     game.camera = Camera(self.map.width, self.map.height)
     game.all_sprites = pg.sprite.LayeredUpdates()
     game.walls = pg.sprite.Group()
     game.mobs = pg.sprite.Group()
     game.items = pg.sprite.Group()
    def new(self):
        """For things that should happen once, at the start of a new game"""
        # Load map from file
        self.map_folder = path.join(self.game_folder, 'data/maps')
        # self.map = Map(path.join(game_folder, 'map.txt'))
        self.map = TiledMap(self, path.join(self.map_folder, MAP_NAME))
        self.map_img = self.map.make_map()
        self.map_rect = self.map_img.get_rect()
        # self.paths = breadth_first_search(self.map, self
        # initialize all variables and do all the setup for a new game
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.walls = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.items = pg.sprite.Group()

        # For Loading map from tmx data
        for tile_object in self.map.tmxdata.objects:
            obj_center = vec(tile_object.x + tile_object.width / 2,
                             tile_object.y + tile_object.height / 2)
            if tile_object.name == 'player':
                self.player = Player(self, obj_center.x, obj_center.y)
            if tile_object.name == 'zombie':
                Mob(self, obj_center.x,
                    obj_center.y)  # For EM all entities must have unique id
            if tile_object.name == 'wall':
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name in ['health', 'shotgun', 'pistol']:
                Item(self, obj_center, tile_object.name)

#        self.EM = AI.EntityManager()
        for mob in self.mobs:
            # self.EM.add_entity(mob.id) #All entities must have a unique id
            mob.SM.current_state = zombie_states.Idle(self, mob)
            mob.SM.global_state = zombie_states.ZombieGlobalState(self, mob)

        assert self.player is not None
        self.camera = Camera(self.map.width,
                             self.map.height)  # Give camera total size of map
        # Flags
        self.draw_debug = False
        self.paused = False
        self.night = False
        self.effects_sounds['level_start'].play()
Exemplo n.º 5
0
    def create_map(self, filename):
        # Basic map background image with data.
        self.map = TiledMap(os.path.join(self.map_folder, filename))
        self.map.make_map()

        # Create the camera with the map dimensions.
        self.camera = Camera(SCREEN_WIDTH, SCREEN_HEIGHT, self.map.width,
                             self.map.height)

        movement = {
            "back": True,
            "parts": {
                1: {
                    "vel": 150,
                    "rot": 90,
                    "distance": 400
                },
                2: {
                    "vel": 50,
                    "rot": 45,
                    "distance": 100
                },
                3: {
                    "vel": 200,
                    "rot": 0,
                    "distance": 600,
                }
            }
        }

        # Map objects.
        for tile_object in self.map.tilemap_data.objects:
            # The center of the tile.
            object_center = Vec(tile_object.x + tile_object.width / 2,
                                tile_object.y + tile_object.height / 2)
            # Obstacles.
            if tile_object.object == "obstacle":
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height, tile_object.type)
            elif tile_object.object == "moving_obstacle":
                MovingObstacle(self, tile_object.x, tile_object.y,
                               tile_object.width, tile_object.height,
                               tile_object.type, movement)
            elif tile_object.object == "item":
                Item(self, object_center, tile_object.type, RANDOM_START_STEP)
Exemplo n.º 6
0
    def new(self):
        """
        Creates a new game
        :return: None
        """
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.walls = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.items = pg.sprite.Group()
        self.swingAreas = pg.sprite.Group()
        self.camera = Camera(self.map.width, self.map.height)
        self.paused = False
        self.running = True
        self.pathfinder = Pathfinder()
        self.game_graph = WeightedGraph()
        mob_positions = []
        wall_positions = []

        for row, tiles in enumerate(self.map.data):
            for col, tile in enumerate(tiles):
                if tile == '1':
                    wall_positions.append((col * TILESIZE, row * TILESIZE))
                if tile == 'P':
                    self.player = Player(self, col * TILESIZE, row * TILESIZE)
                if tile == 'E':
                    mob_positions.append((col * TILESIZE, row * TILESIZE))
                if tile == 'W':
                    WeaponPickup(self, (col * TILESIZE, row * TILESIZE))

        for position in wall_positions:
            Obstacle(self, position[0], position[1])

        for position in mob_positions:
            Mob(self, position[0], position[1])

        self.game_graph.walls = [(int(wall[0] // TILESIZE), int(wall[1] // TILESIZE)) for wall in wall_positions]
        self.mob_idx = 0
        self.last_queue_update = 0
        g.run()
Exemplo n.º 7
0
    def new(self):
        # initialize all variables and do all the setup for a new game
        self.draw_debug = False
        self.paused = False
        self.night = True

        # Sprite groups
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.obstacles = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.items = pg.sprite.Group()

        # Map
        self.map = TiledMap(path.join(self.map_folder, '01.tmx'))
        self.map_img = self.map.make_map()
        self.map_rect = self.map_img.get_rect()

        # Map Objects
        for tile_object in self.map.tmxdata.objects:
            # For spawning in center of tiled object
            obj_center = vec(tile_object.x + tile_object.width / 2,
                             tile_object.y + tile_object.height / 2)
            if tile_object.name == 'player':
                self.player = Player(self, obj_center.x, obj_center.y)
            if tile_object.name == 'zombie':
                Mob(self, obj_center.x, obj_center.y)
            if tile_object.name == 'wall':
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name in ['health', 'shotgun']:
                Item(self, obj_center, tile_object.name)

        # Camera object
        self.camera = Camera(self.map.width, self.map.height)

        # Start a new level sound
        self.effects_sounds['level_start'].play()
Exemplo n.º 8
0
    def load(self, filename="savegame"):
        with open(filename, "rb") as f:
            [self.objects, self.map, player_name,
             self.all_groups] = pick.load(f)
            self.map.game = self

            # Generic Game variables
            self.ticker = Ticker()
            self.game_state = c.GAME_STATE_PLAYING
            self.player_took_action = False
            self.minimap_enable = False

            self.inventory_screen = InventoryScreen(self, c.GAME_STATE_PLAYING)
            self.map_screen = MapScreen(self, c.GAME_STATE_PLAYING)
            self.playing_screen = PlayingScreen(self, None)
            self.character_screen = CharacterScreen(self, c.GAME_STATE_PLAYING)

            # initializing map structure
            self.minimap = Minimap(self)

            # Field of view
            self.fov = FieldOfView(self)

            # We have 5 sprites groups: two below the player, the player one and two above
            # They are drawn in the order below:
            [
                self.player_min2_sprite_group, self.player_min1_sprite_group,
                self.player_sprite_group, self.player_plus1_sprite_group,
                self.player_plus2_sprite_group
            ] = self.all_groups

            # Camera
            self.camera = Camera(self.map.width, self.map.height)

            for entities in self.objects:
                entities.game = self
                entities.init_graphics()
                if entities.name == player_name:
                    self.player = entities
                    self.visible_player_array = self.fov.get_vision_matrix_for(
                        self.player, flag_explored=True)
            for entities in self.player.inventory:
                entities.game = self
                entities.init_graphics(in_inventory=True)
Exemplo n.º 9
0
 def new(self):
     for row, tiles in enumerate(reversed(self.map.data)):
         for col, tile in enumerate(tiles):
             # print(tile)
             if tile == "1":
                 tile = Wall(tilex=col, tiley=row)
                 self.add_widget(tile)
             #     self.add_new_sprite(tile)
             elif tile == "P":
                 print("P", col, row, self.children)
                 self.player.tile = [col, row]
                 # pokemons = [
                 #     Pokemon(name="charmander", level=4),
                 #     Pokemon(name="charmander", level=6)
                 # ]
                 # self.player = tile =\
                 #     Player(tilex=col, tiley=row, pokemons=pokemons)
                 # parent = self.player.parent
                 # parent.remove_widget(self.player)
                 # self.add_new_sprite(self.player, 0)
                 # self.add_widget(self.player)
             elif tile == "G":  # grass
                 print(col, row)
                 tile = Grass(tilex=col, tiley=row)
                 self.add_widget(tile)
             #     self.add_new_sprite(tile)
             elif "D" in tile:  # door
                 print(tile, col, row)
                 _map = tile.split("D_")[1]
                 print("_map", _map)
                 tile = Door(tilex=col, tiley=row, map=_map)
                 self.add_widget(tile)
             #     self.add_new_sprite(tile)
             # if tile == "W":  # water
             #     tile = Water(tilex=col, tiley=row)
             #     self.add_new_sprite(tile)
             # if tile == "D":  # door
             #     tile = Door(tilex=col, tiley=row)
             #     self.add_new_sprite(tile)
     self.camera = Camera(self.map.width, self.map.height)
Exemplo n.º 10
0
    def go_next_level(self):

        # First: cleanup!
        # Warning: we must act on a copy of the list!!!!!
        for entity in self.objects[:]:
            if entity != self.player:
                entity.remove_completely_object()

        self.level += 1

        # initializing map structure
        self.map = MapFactory(
            "Cave of LordCrocket - Level {}".format(self.level),
            self.all_images).map
        self.minimap = Minimap(self)

        # Field of view
        self.fov = FieldOfView(self)

        self.place_doors_stairs_traps(self.level)

        # Place player
        all_pos = self.map.get_all_available_tiles(c.T_FLOOR,
                                                   self.objects,
                                                   without_objects=True)
        new_player_pos = all_pos.pop()
        self.player.x = new_player_pos[0]
        self.player.y = new_player_pos[1]
        self.visible_player_array = self.fov.get_vision_matrix_for(
            self.player, flag_explored=True)
        self.player.invalidate_fog_of_war = True
        self.player_sprite_group.add(self.player)

        # Camera
        self.camera = Camera(self.map.tile_width * TILESIZE_SCREEN,
                             self.map.tile_height * TILESIZE_SCREEN)
        # place monsters
        ItemFactory(self).build_list(50)
        MonsterFactory(self).build_list(130)
Exemplo n.º 11
0
class Game:
    def __init__(self):
        # pg.init()
        pg.display.init()
        pg.font.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        pg.key.set_repeat(250, 50)  # Allows us to hold dowW keys to move, etc.
        self.load_data()
        self.total_carrots = 0
        self.go_screen = False

    def load_data(self):
        game_folder = path.dirname(__file__)
        map_folder = path.join(game_folder, "maps")
        self.map = TiledMap(path.join(map_folder, "AI-map.tmx"))
        self.map_img = self.map.make_map()
        self.map_rect = self.map_img.get_rect()
        #self.map = Map(path.join(game_folder, "map.txt"))

    def new(self):
        # initialize all variables and do all the setup for a new game
        self.all_sprites = pg.sprite.Group()
        self.walls = pg.sprite.Group()
        self.chest = pg.sprite.Group()
        self.items = pg.sprite.Group()
        self.carrots = pg.sprite.Group()
        for tile_object in self.map.tmxdata.objects:
            if tile_object.name == "player":
                print("player location: ", tile_object.x, tile_object.y)
                self.player = AIPlayer(self, tile_object.x, tile_object.y)
            # self.player = Player(self, tile_object.x, tile_object.y)

            if tile_object.name == "wall":
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name == "carrot":
                Carrot(self,
                       tile_object.x,
                       tile_object.y,
                       groups=[self.all_sprites, self.carrots, self.items])
                self.total_carrots += 1
            if tile_object.name == "chest":
                Chest(self,
                      tile_object.x,
                      tile_object.y,
                      groups=[self.all_sprites, self.chest, self.items])

        self.camera = Camera(self.map.width, self.map.height)

    def reset(self):
        # initialize all variables anddo all the setup for a new game

        self.chest = pg.sprite.Group()
        for chest in self.chest:
            self.all_sprites.remove(chest)
            chest.kill()

        for carrot in self.carrots:
            self.all_sprites.remove(carrot)
            carrot.kill()
        self.carrots.empty()
        self.carrots = pg.sprite.Group()

        for item in self.items:
            self.all_sprites.remove(item)
            item.kill()
        self.items = pg.sprite.Group()
        self.total_carrots = 0

        for tile_object in self.map.tmxdata.objects:
            if tile_object.name == "player":
                print("player location: ", tile_object.x, tile_object.y)
                #self.player = AIPlayer(self, tile_object.x, tile_object.y)
                self.player.reset(tile_object.x, tile_object.y)
            if tile_object.name == "carrot":
                Carrot(self,
                       tile_object.x,
                       tile_object.y,
                       groups=[self.all_sprites, self.carrots, self.items])
                self.total_carrots += 1
            if tile_object.name == "chest":
                Chest(self,
                      tile_object.x,
                      tile_object.y,
                      groups=[self.all_sprites, self.chest, self.items])

    def run(self):
        # game loop - set self.playing = False to end the game
        # useful for ending a training session
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            self.update()
            self.draw()

    def quit(self):
        self.playing = False
        pg.quit()
        pg.display.quit()
        sys.exit()

    def update(self):
        # update portion of the game loop
        self.all_sprites.update()
        self.camera.update(self.player)

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))

    def draw(self):
        self.screen.fill(BGCOLOR)
        self.draw_grid()
        self.screen.blit(self.map_img, self.camera.apply_rect(self.map_rect))
        for sprite in self.all_sprites:
            self.screen.blit(sprite.image, self.camera.apply(sprite))

        self.screen.blit(self.score_surface(self.player), (0, 0))

        # Yuck
        try:
            if isinstance(self.player, AIPlayer) and DEBUG == True:
                if not self.player.controller.debug_queue.empty():
                    (self.episode, self.state_values
                     ) = self.player.controller.debug_queue.get()
                if self.episode % DEBUG_BLIT_RATE == 0:
                    for state in self.state_values:
                        val = self.state_values.get(state, 0)
                        val = round(val, 2)
                        x, y = (state[0][0] + TILESIZE / 2,
                                state[0][1] + TILESIZE / 2)
                        if len(state) == 3 and state[2] == True:
                            y = state[0][1] + TILESIZE / 4
                        self.screen.blit(self.state_value_surface(str(val)),
                                         (x, y))
        except (NameError, AttributeError) as e:
            pass
        pg.display.flip()

    def events(self):
        # catch all events here
        for event in pg.event.get():
            if event.type == pg.QUIT:
                if isinstance(self.player, AIPlayer):
                    self.player.controller.quit()
                self.quit()

            self.player.events(event)

            if self.go_screen == True and event.type == pg.KEYDOWN and event.key == pg.K_SPACE:
                self.go_screen = False

    def score_surface(self, player):
        template = "Score: {}, Remaining Moves: {}, Pos: ({}, {})"
        template = template.format(player.score,
                                   MAX_ACTIONS - player.total_actions,
                                   player.rect.x, player.rect.y)

        default_font = pg.font.get_default_font()
        return pg.font.Font(default_font, 18).render(template, True,
                                                     (0, 0, 255))

    def show_start_screen(self):
        pass

    def game_over_surface(self):
        text = "Game Over! Press Space To Play Again"
        default_font = pg.font.get_default_font()
        return pg.font.Font(default_font, 24).render(text, True, (0, 0, 255))

    def state_value_surface(self, V):
        default_font = pg.font.get_default_font()
        return pg.font.Font(default_font, 8).render(V, True, (0, 0, 240))

    def show_go_screen(self):
        self.go_screen = True
        while self.go_screen:
            go_surf = self.game_over_surface()
            self.screen.blit(go_surf, (WIDTH / 2 -
                                       (go_surf.get_width() / 2), HEIGHT / 2))

            pg.display.flip()

            self.events()
            self.update()
Exemplo n.º 12
0
class Game:
    """
    Blueprint for game objects
    """

    def __init__(self):
        # initialize game window, etc
        pg.init()
        pg.mixer.pre_init(44100, -16, 1, 1024)
        pg.mouse.set_visible(False)
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        self.screen.set_alpha(None)
        self.screen_width = WIDTH
        self.screen_height = HEIGHT
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()

        # Resource folders
        self.game_folder = path.dirname(__file__)
        self.img_folder = path.join(self.game_folder, 'img')
        self.snd_folder = path.join(self.game_folder, 'snd')
        self.music_folder = path.join(self.snd_folder, 'Music')
        self.crosshair_folder = path.join(self.img_folder, 'Crosshairs')
        self.item_folder = path.join(self.img_folder, 'Items')
        # Loads game assets
        self.load_data()
        self.running = True
        # Debugging flags
        self.debug = False
        self.hardcore_mode = False

    def load_data(self):
        """
        Loads the necessary assets for the game. 
        :return: None
        """
        # Game map
        self.map = Map(path.join(self.game_folder, 'map3.txt'))

        self.pause_screen_effect = pg.Surface(self.screen.get_size()).convert()
        self.pause_screen_effect.fill((0, 0, 0, 225))

        # Fog of war
        self.fog = pg.Surface(self.screen.get_size()).convert()
        self.fog.fill(NIGHT_COLOR)

        # Light on the player
        self.light_mask = pg.image.load(path.join(self.img_folder, LIGHT_MASK)).convert_alpha()
        self.light_mask = pg.transform.smoothscale(self.light_mask, (LIGHT_RADIUS, LIGHT_RADIUS))
        self.light_rect = self.light_mask.get_rect()

        # HUD Elements
        self.mag_img = pg.transform.smoothscale(pg.image.load(path.join(self.img_folder, CLIP_IMG)),
                                                (40, 40)).convert_alpha()
        # Crosshairs
        self.crosshairs = [
            pg.transform.smoothscale(pg.image.load(path.join(self.crosshair_folder, crosshair)),
                                     (32, 32)).convert_alpha()
            for crosshair in CROSSHAIRS]
        # todo - have a menu decide the player's crosshair
        self.crosshair = choice(self.crosshairs)

        # Item pickups
        self.pickup_items = {}
        for item in ITEM_IMAGES:
            self.pickup_items[item] = []
            self.pickup_items[item] = pg.image.load(path.join(self.item_folder, ITEM_IMAGES[item])).convert_alpha()

        # Fonts
        self.hud_font = path.join(self.img_folder, 'Fonts\Impacted2.0.ttf')

        # Sound loading
        self.music_tracks = {"main menu": MAIN_MENU_MUSIC, 'Game over': GAME_OVER_MUSIC, 'background music': BG_MUSIC}
        pg.mixer.music.load(path.join(self.music_folder, self.music_tracks['background music']))
        pg.mixer.music.set_volume(.5)

        self.swing_noises = {}
        for weapon in PLAYER_SWING_NOISES:
            noise = pg.mixer.Sound(path.join(self.snd_folder, PLAYER_SWING_NOISES[weapon]))
            noise.set_volume(.25)
            self.swing_noises[weapon] = noise

        self.player_hit_sounds = []
        for snd in PLAYER_HIT_SOUNDS:
            self.player_hit_sounds.append(pg.mixer.Sound(path.join(self.snd_folder, snd)))

        self.weapon_sounds = {}
        for weapon in WEAPONS['sound']:
            self.weapon_sounds[weapon] = {}
            sound_list = {}
            for snd in WEAPONS['sound'][weapon]:
                noise = pg.mixer.Sound(path.join(self.snd_folder, WEAPONS['sound'][weapon][snd]))
                noise.set_volume(.9)
                sound_list[snd] = noise
            self.weapon_sounds[weapon] = sound_list

        self.zombie_moan_sounds = []
        for snd in ZOMBIE_MOAN_SOUNDS:
            noise = pg.mixer.Sound(path.join(self.snd_folder, snd))
            noise.set_volume(.25)
            self.zombie_moan_sounds.append(noise)

        self.zombie_hit_sounds = {}
        for type in ENEMY_HIT_SOUNDS:
            self.zombie_hit_sounds[type] = []
            for snd in ENEMY_HIT_SOUNDS[type]:
                snd = pg.mixer.Sound(path.join(self.snd_folder, snd))
                snd.set_volume(.75)
                self.zombie_hit_sounds[type].append(snd)

        self.player_foot_steps = {}
        for terrain in PLAYER_FOOTSTEPS:
            self.player_foot_steps[terrain] = []
            for snd in PLAYER_FOOTSTEPS[terrain]:
                snd = pg.mixer.Sound(path.join(self.snd_folder, snd))
                snd.set_volume(.5)
                self.player_foot_steps[terrain].append(snd)

        # Bullets
        self.bullet_images = {}
        self.bullet_images['lg'] = pg.transform.smoothscale(pg.image.load(path.join(self.img_folder, RIFLE_BULLET_IMG)),
                                                            (10, 3)).convert_alpha()
        self.bullet_images['med'] = pg.transform.smoothscale(
            pg.image.load(path.join(self.img_folder, HANDGUN_BULLET_IMG)), (5, 3)).convert_alpha()
        self.bullet_images['sm'] = pg.transform.smoothscale(pg.image.load(
            path.join(self.img_folder, SHOTGUN_BULLET_IMG)).convert_alpha(), (7, 7))

        # Effects
        self.gun_flashes = [pg.image.load(path.join(self.img_folder, flash)).convert_alpha() for flash in
                            MUZZLE_FLASHES]

        # Load enemy animations
        self.enemy_imgs = [pg.transform.smoothscale(pg.image.load(path.join(self.game_folder, name)),
                                                    (96, 96)).convert_alpha() for name in
                           ENEMY_IMGS]
        # Load player animations
        self.default_player_weapon = 'knife'
        self.default_player_action = 'idle'
        self.player_animations = {'handgun': {}, 'knife': {}, 'rifle': {}, 'shotgun': {}}

        # Create all hand gun animations
        self.player_animations['handgun']['idle'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in HANDGUN_ANIMATIONS['idle']]
        self.player_animations['handgun']['melee'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                      for name in HANDGUN_ANIMATIONS['melee']]
        self.player_animations['handgun']['move'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in HANDGUN_ANIMATIONS['move']]
        self.player_animations['handgun']['reload'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                       for name in HANDGUN_ANIMATIONS['reload']]
        self.player_animations['handgun']['shoot'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                      for name in HANDGUN_ANIMATIONS['shoot']]

        # Create all knife animations
        self.player_animations['knife']['idle'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                   name in KNIFE_ANIMATIONS['idle']]
        self.player_animations['knife']['melee'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                    name in KNIFE_ANIMATIONS['melee']]
        self.player_animations['knife']['move'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                   name in KNIFE_ANIMATIONS['move']]

        # Create all rifle animations
        self.player_animations['rifle']['idle'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                   name in RIFLE_ANIMATIONS['idle']]
        self.player_animations['rifle']['melee'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                    name in RIFLE_ANIMATIONS['melee']]
        self.player_animations['rifle']['move'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                   name in RIFLE_ANIMATIONS['move']]
        self.player_animations['rifle']['reload'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in RIFLE_ANIMATIONS['reload']]
        self.player_animations['rifle']['shoot'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha() for
                                                    name in RIFLE_ANIMATIONS['shoot']]

        # Create all shotgun animations
        self.player_animations['shotgun']['idle'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in SHOTGUN_ANIMATIONS['idle']]
        self.player_animations['shotgun']['melee'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                      for name in SHOTGUN_ANIMATIONS['melee']]
        self.player_animations['shotgun']['move'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                     for name in SHOTGUN_ANIMATIONS['move']]
        self.player_animations['shotgun']['reload'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                       for name in SHOTGUN_ANIMATIONS['reload']]
        self.player_animations['shotgun']['shoot'] = [pg.image.load(path.join(self.game_folder, name)).convert_alpha()
                                                      for name in SHOTGUN_ANIMATIONS['shoot']]

    def new(self):
        """
        Creates a new game
        :return: None
        """
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.walls = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.items = pg.sprite.Group()
        self.swingAreas = pg.sprite.Group()
        self.camera = Camera(self.map.width, self.map.height)
        self.paused = False
        self.running = True
        self.pathfinder = Pathfinder()
        self.game_graph = WeightedGraph()
        mob_positions = []
        wall_positions = []

        for row, tiles in enumerate(self.map.data):
            for col, tile in enumerate(tiles):
                if tile == '1':
                    wall_positions.append((col * TILESIZE, row * TILESIZE))
                if tile == 'P':
                    self.player = Player(self, col * TILESIZE, row * TILESIZE)
                if tile == 'E':
                    mob_positions.append((col * TILESIZE, row * TILESIZE))
                if tile == 'W':
                    WeaponPickup(self, (col * TILESIZE, row * TILESIZE))

        for position in wall_positions:
            Obstacle(self, position[0], position[1])

        for position in mob_positions:
            Mob(self, position[0], position[1])

        self.game_graph.walls = [(int(wall[0] // TILESIZE), int(wall[1] // TILESIZE)) for wall in wall_positions]
        self.mob_idx = 0
        self.last_queue_update = 0
        g.run()

    def find_path(self, predator, prey):
        """
        Finds a path for the predator to reach its prey
        :param predator: The entity who seeks
        :param prey: The unknowning target
        :return: A list of Vector2 objects to guide the predator
        """
        return self.pathfinder.a_star_search(self.game_graph,
                                             vec(predator.pos.x // TILESIZE, predator.pos.y // TILESIZE),
                                             vec(prey.pos.x // TILESIZE, prey.pos.y // TILESIZE))

    def run(self):
        """
        Runs the game
        :return: None
        """
        self.playing = True
        pg.mixer.music.play(loops=-1)
        while self.playing:
            pg.display.set_caption("{:.1f}".format(self.clock.get_fps()))
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            if not self.paused:
                self.update()
            self.draw()

    def update(self):
        """
        Updates game state
        :return: None
        """
        self.impact_positions = []
        for sprite in self.all_sprites:
            if sprite == self.player:
                sprite.update(pg.key.get_pressed())
            else:
                sprite.update()
        self.camera.update(self.player)
        self.swingAreas.update()
        self.update_pathfinding_queue()

        # Player hits mobs
        hit_melee_box = pg.sprite.groupcollide(self.mobs, self.swingAreas, False, True, collide_hit_rect)
        for hit in hit_melee_box:
            choice(self.zombie_hit_sounds['bash']).play()
            hit.health -= hit.health
            self.impact_positions.append(hit.rect.center)

        # Enemy hits player
        hits = pg.sprite.spritecollide(self.player, self.mobs, False, collide_hit_rect)
        for hit in hits:
            if random() < .7:
                choice(self.player_hit_sounds).play()
            if hit.can_attack:
                self.impact_positions.append(self.player.rect.center)
                self.player.health -= hit.damage
                hit.vel.normalize()
                hit.pause()
                if self.player.health <= 0:
                    self.playing = False
        if hits:
            self.player.pos += vec(ENEMY_KNOCKBACK, 0).rotate(-hits[0].rot)

        # Bullet collisions
        hits = pg.sprite.groupcollide(self.mobs, self.bullets, False, False, collide_hit_rect)
        for mob in hits:
            for bullet in hits[mob]:
                self.impact_positions.append(bullet.rect.center)
                mob.health -= bullet.damage
                mob.pos += vec(WEAPONS[self.player.weapon]['damage'] // 10, 0).rotate(-self.player.rot)
            # Drowns out blood gushing noises the further the collision is from the player
            dist = self.player.pos.distance_to(mob.pos)
            ratio = 1
            if dist > 0:
                ratio = round(200 / dist, 2)
                if ratio > 1:
                    ratio = 1
            snd = choice(self.zombie_hit_sounds['bullet'])
            snd.set_volume(ratio)
            if snd.get_num_channels() > 2:
                snd.stop()
            snd.play()

        # Item collisions
        hits = pg.sprite.spritecollide(self.player, self.items, True, collide_hit_rect)
        for hit in hits:
            self.player.pickup_item(hit)
            if isinstance(hit, WeaponPickup):
                snd = self.weapon_sounds[hit.type]['pickup']
                snd.play()
            else:
                pass

    def events(self):
        """
        Game loop event handling
        :return: None
        """
        for event in pg.event.get():
            if event.type == pg.QUIT:
                if self.playing:
                    self.playing = False
                self.running = False
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_b:
                    self.debug = not self.debug
                if event.key == pg.K_p:
                    self.paused = not self.paused
                if event.key == pg.K_h:
                    self.hardcore_mode = not self.hardcore_mode

    def draw_grid(self):
        """
        Used for debugging
        :return: None
        """
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))

    def render_fog(self):
        self.fog.fill(NIGHT_COLOR)
        self.light_rect.center = self.camera.apply_rect(self.player.hit_rect.copy()).center
        self.fog.blit(self.light_mask, self.light_rect)
        self.screen.blit(self.fog, (0, 0), special_flags=pg.BLEND_RGBA_MULT)

    def draw(self):
        """
        Draws the updated game state onto the screen
        :return: None
        """
        self.screen.fill(DARKGREY)
        if self.debug:
            self.draw_grid()
        self.draw_blood_splatters()
        # Draw all sprites to the screen
        for sprite in self.all_sprites:
            # if isinstance(sprite, Mob):
            #     sprite.draw_health()
            self.screen.blit(sprite.image, self.camera.apply(sprite))
            if self.debug:
                pg.draw.rect(self.screen, (0, 255, 255), self.camera.apply_rect(sprite.hit_rect), 1)
        self.render_fog()
        x, y = pg.mouse.get_pos()
        self.screen.blit(self.crosshair, (x - self.crosshair.get_rect().width // 2,
                                          y - self.crosshair.get_rect().height // 2))
        # draw hud information
        if not self.hardcore_mode:
            self.update_hud()
        if self.paused:
            self.screen.blit(self.pause_screen_effect, (0, 0))
            self.draw_text('Paused', self.hud_font, 105, RED, WIDTH / 2, HEIGHT / 2, align='center')
        pg.display.flip()

    def update_hud(self):
        """
        Updates the HUD information for the player to see
        :return: None
        """
        self.draw_player_stats()
        if self.player.weapon != 'knife':
            self.draw_current_clip(self.screen, 10, 70, self.player.arsenal[self.player.weapon]['clip'], 3, 15)

    def draw_player_stats(self):
        """
        Gives the player visual indication of their health & stamina
        :return: None
        """
        self.draw_player_health(self.screen, 10, 10, self.player.health // PLAYER_HEALTH)
        self.draw_text(str(self.player.health) + "%", self.hud_font, 15, (119, 136, 153),
                       BAR_LENGTH // 2 - 5, 10)
        self.draw_player_stamina(self.screen, 10, 40, self.player.stamina / PLAYER_STAMINA)
        self.draw_text("{0:.0f}".format(self.player.stamina) + "%", self.hud_font, 15, (119, 136, 153),
                       BAR_LENGTH // 2 - 5, 40)

    def draw_current_clip(self, surface, x, y, bullets, bullet_length, bullet_height):
        """
        Used to give the player visual indication of how much ammunition they have at 
        the moment in their weapon
        :param surface: The location in which to draw this information
        :param x: The x location to draw
        :param y: The y location to draw
        :param bullets: How many bullets to draw
        :param bullet_length: How wide the bullet figure will be
        :param bullet_height: How tall the bullet figure will be
        :return: None
        """
        if self.player.weapon != 'knife':
            temp = x
            # Draws how many bullets are in the player's magazine in GOLD with the difference between
            # maximum mag capacity - current bullet count is in LIGHTGREY
            for j in range(0, WEAPONS[self.player.weapon]['clip size']):
                bullet_outline = pg.Surface((bullet_length, bullet_height)).convert()
                if j < bullets:
                    bullet_outline.fill(GOLD)
                else:
                    bullet_outline.fill(LIGHTGREY)
                surface.blit(bullet_outline, (temp, y))
                temp += 2 * bullet_length
            surface.blit(self.mag_img, (temp, y - 10))
            self.draw_text('x', self.hud_font, 15, WHITE,
                           temp + 32, y)
            self.draw_text(str(self.player.arsenal[self.player.weapon]['reloads']), self.hud_font, 20, WHITE,
                           temp + 40, y - 5)

    @staticmethod
    def draw_player_health(surface, x, y, pct):
        """
        Draws the player's health bar
        :param surface: The surface on which to draw the health bar
        :param x: x location
        :param y: y location
        :param pct: How much health is left in %
        :return: None
        """
        if pct < 0:
            pct = 0
        fill = pct * BAR_LENGTH
        static = BAR_LENGTH

        outline_rect = pg.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
        filled_rect = pg.Rect(x, y, fill, BAR_HEIGHT)
        static_rect = pg.Rect(x, y, static, BAR_HEIGHT)

        pg.draw.rect(surface, LIMEGREEN, static_rect)
        pg.draw.rect(surface, GREEN, filled_rect)
        pg.draw.rect(surface, WHITE, outline_rect, 2)

    @staticmethod
    def draw_player_stamina(surface, x, y, pct):
        """
        Draws the player's stamina bar
        :param surface: The surface to draw on
        :param x: x location
        :param y: y location
        :param pct: How much stamina is left in %
        :return: None
        """
        if pct < 0:
            pct = 0

        fill = pct * BAR_LENGTH
        static = BAR_LENGTH
        outline_rect = pg.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
        filled_rect = pg.Rect(x, y, fill, BAR_HEIGHT)
        static_rect = pg.Rect(x, y, static, BAR_HEIGHT)

        pg.draw.rect(surface, DODGERBLUE, static_rect)
        pg.draw.rect(surface, DEEPSKYBLUE, filled_rect)
        pg.draw.rect(surface, WHITE, outline_rect, 2)

    def draw_text(self, text, font_name, size, color, x, y, align='nw'):
        """
        Draws informative text.
        :param text: The text to draw.
        :param font_name: The font to use.
        :param size: The size of the font.
        :param color: The colour of the font.
        :param x: The x-coordinate of the location
        :param y: The y-coordinate of the location 
        :param align: Compass location
        :return: None
        """
        font = pg.font.Font(font_name, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect()
        if align == "nw":
            text_rect.topleft = (x, y)
        if align == "ne":
            text_rect.topright = (x, y)
        if align == "sw":
            text_rect.bottomleft = (x, y)
        if align == "se":
            text_rect.bottomright = (x, y)
        if align == "n":
            text_rect.midtop = (x, y)
        if align == "s":
            text_rect.midbottom = (x, y)
        if align == "e":
            text_rect.midright = (x, y)
        if align == "w":
            text_rect.midleft = (x, y)
        if align == "center":
            text_rect.center = (x, y)

        self.screen.blit(text_surface, text_rect)

    def draw_blood_splatters(self):
        if self.impact_positions:
            for pos in self.impact_positions:
                impact = True
                while impact:
                    self.events()
                    for magnitude in range(1, randrange(35, 65)):
                        exploding_bit_x = pos[0] + randrange(-1 * magnitude, magnitude) + self.camera.camera.x
                        exploding_bit_y = pos[1] + randrange(-1 * magnitude, magnitude) + self.camera.camera.y
                        pg.draw.circle(self.screen, choice(BLOOD_SHADES), (exploding_bit_x, exploding_bit_y),
                                       randrange(1, 5))
                    impact = False
            self.impact_positions.clear()

    def update_pathfinding_queue(self):
        """
        Gives each mob on the level the opportunity to find a path
        to its prey (the player). Each mob will be given this
        opportunity in order according to their list position into
        the mobs group.
        :return: None
        """
        now = pg.time.get_ticks()
        if now - self.last_queue_update > 5000:
            self.last_queue_update = now
            if self.mob_idx == len(self.mobs):
                self.mob_idx = 0
            count = 0
            for mob in self.mobs:
                mob.can_find_path = False
                if count == self.mob_idx:
                    mob.can_find_path = True
                count += 1
            self.mob_idx += 1

    def show_start_screen(self):
        """
        Displays the start screen for the game
        :return: None
        """
        pass

    def show_gameover_screen(self):
        """
        Displays the gameover screen for the game
        :return: None
        """
        pass

    def show_pause_screen(self):
        """
        Displays the pause screen for the game
        :return: None
        """
        pass
class Game:
    def __init__(self):
        self.logger = logging.getLogger(f"{__name__}.Game")
        self.logger.debug("Initializing game object")
        pg.mixer.pre_init(
            44100, -16, 1,
            1024)  # increase sound buffer to minise lag when playing sounds
        pg.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        # pg.key.set_repeat(10, 100)  # Lets you hold down a key to keep the move going
        self.game_folder = path.dirname(__file__)
        self.load_images()
        self.load_sounds()
        self.player = None

    def draw_text(self, text, font_name, size, color, x, y, align="nw"):
        font = pg.font.Font(font_name, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect()
        if align == "nw":
            text_rect.topleft = (x, y)
        if align == "ne":
            text_rect.topright = (x, y)
        if align == "sw":
            text_rect.bottomleft = (x, y)
        if align == "se":
            text_rect.bottomright = (x, y)
        if align == "n":
            text_rect.midtop = (x, y)
        if align == "s":
            text_rect.midbottom = (x, y)
        if align == "e":
            text_rect.midright = (x, y)
        if align == "w":
            text_rect.midleft = (x, y)
        if align == "center":
            text_rect.center = (x, y)
        self.screen.blit(text_surface, text_rect)

    def load_images(self):
        self.logger.debug("loading images...")
        # Define game folders
        image_folder = path.join(self.game_folder, 'data/images')
        # Load images from files
        self.title_font = path.join(image_folder, 'ZOMBIE.TTF')
        self.hud_font = path.join(image_folder, 'Impacted2.0.TTF')
        self.dim_screen = pg.Surface(self.screen.get_size()).convert_alpha()
        self.dim_screen.fill((0, 0, 0, 180))
        self.player_imgs = {}
        for img in PLAYER_IMGS:
            self.player_imgs[img] = pg.image.load(
                path.join(image_folder, PLAYER_IMGS[img])).convert_alpha()
        self.wall_img = pg.image.load(path.join(image_folder,
                                                WALL_IMG)).convert_alpha()
        self.wall_img = pg.transform.scale(self.wall_img, (TILESIZE, TILESIZE))
        self.mob_img = pg.image.load(path.join(image_folder,
                                               MOB_IMG)).convert_alpha()
        self.bullet_images = {}
        self.bullet_images['lg'] = pg.image.load(
            path.join(image_folder, BULLET_IMG)).convert_alpha()
        self.bullet_images['lg'] = pg.transform.scale(self.bullet_images['lg'],
                                                      (5, 5))
        self.splat = pg.image.load(path.join(image_folder,
                                             SPLAT)).convert_alpha()
        self.splat = pg.transform.scale(self.splat, (64, 64))
        self.gun_smoke = []
        for img in MUZZLE_SMOKE:
            self.gun_smoke.append(
                pg.image.load(path.join(image_folder, img)).convert_alpha())
        self.item_images = {}
        for item in ITEM_IMAGES:
            self.item_images[item] = pg.image.load(
                path.join(image_folder, ITEM_IMAGES[item])).convert_alpha()
        # Lighting
        self.fog = pg.Surface((WIDTH, HEIGHT))
        self.fog.fill(NIGHT_COLOUR)
        self.light_mask = pg.image.load(path.join(image_folder,
                                                  LIGHT_MASK)).convert_alpha()
        self.light_mask = pg.transform.scale(self.light_mask, LIGHT_RADIUS)
        self.light_rect = self.light_mask.get_rect()

    def load_sounds(self):
        self.logger.debug("loading sounds...")
        sound_folder = path.join(self.game_folder, 'data/sounds')
        self.music_folder = path.join(self.game_folder, 'data/music')
        # Load initial music - define user event to trigger when it ends, picked up in events()
        self.SONG_END = pg.USEREVENT + 1
        self.current_song = random.choice(BG_MUSIC)
        pg.mixer.music.set_endevent(self.SONG_END)
        pg.mixer.music.load(path.join(self.music_folder, self.current_song))
        pg.mixer.music.set_volume(BG_MUSIC_LEVEL)
        # Effects
        self.effects_sounds = {}
        for eff in EFFECTS_SOUNDS:
            self.effects_sounds[eff] = pg.mixer.Sound(
                path.join(sound_folder, EFFECTS_SOUNDS[eff]['file']))
            self.effects_sounds[eff].set_volume(EFFECTS_SOUNDS[eff]['volume'])

        # Weapons
        self.weapon_sounds = {}
        for weapon in WEAPON_SOUNDS:
            self.weapon_sounds[weapon] = []
            for sound in WEAPON_SOUNDS[weapon]:
                s = pg.mixer.Sound(path.join(sound_folder, sound))
                s.set_volume(GUN_SOUND_LEVEL)
                self.weapon_sounds[weapon].append(s)
        #  Zombies
        self.zombie_moan_sounds = []
        for sound in ZOMBIE_MOAN_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(ZOMBIE_MOAN_LEVEL)
            self.zombie_moan_sounds.append(s)
        self.zombie_hit_sounds = []
        for sound in ZOMBIE_HIT_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(ZOMBIE_HIT_LEVEL)
            self.zombie_hit_sounds.append(s)
        # Player
        self.player_hit_sounds = []
        for sound in PLAYER_HIT_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(PLAYER_HIT_LEVEL)
            self.player_hit_sounds.append(s)
        self.player_pain_sounds = []
        for sound in PLAYER_PAIN_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(PLAYER_PAIN_LEVEL)
            self.player_pain_sounds.append(s)
        self.player_panic_sounds = []
        for sound in PLAYER_PANIC_SOUNDS:
            s = pg.mixer.Sound(path.join(sound_folder, sound))
            s.set_volume(PLAYER_PANIC_LEVEL)
            self.player_panic_sounds.append(s)

    def new(self):
        """For things that should happen once, at the start of a new game"""
        # Load map from file
        self.map_folder = path.join(self.game_folder, 'data/maps')
        # self.map = Map(path.join(game_folder, 'map.txt'))
        self.map = TiledMap(self, path.join(self.map_folder, MAP_NAME))
        self.map_img = self.map.make_map()
        self.map_rect = self.map_img.get_rect()
        # self.paths = breadth_first_search(self.map, self
        # initialize all variables and do all the setup for a new game
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.walls = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.items = pg.sprite.Group()

        # For Loading map from tmx data
        for tile_object in self.map.tmxdata.objects:
            obj_center = vec(tile_object.x + tile_object.width / 2,
                             tile_object.y + tile_object.height / 2)
            if tile_object.name == 'player':
                self.player = Player(self, obj_center.x, obj_center.y)
            if tile_object.name == 'zombie':
                Mob(self, obj_center.x,
                    obj_center.y)  # For EM all entities must have unique id
            if tile_object.name == 'wall':
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name in ['health', 'shotgun', 'pistol']:
                Item(self, obj_center, tile_object.name)

#        self.EM = AI.EntityManager()
        for mob in self.mobs:
            # self.EM.add_entity(mob.id) #All entities must have a unique id
            mob.SM.current_state = zombie_states.Idle(self, mob)
            mob.SM.global_state = zombie_states.ZombieGlobalState(self, mob)

        assert self.player is not None
        self.camera = Camera(self.map.width,
                             self.map.height)  # Give camera total size of map
        # Flags
        self.draw_debug = False
        self.paused = False
        self.night = False
        self.effects_sounds['level_start'].play()

    def run(self):
        pg.mixer.music.play()
        # game loop - set self.playing = False to end the game
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            if not self.paused:
                self.update()
            self.draw()

    def quit(self):
        pg.quit()
        sys.exit()

    def update(self):
        """For things that should happen every frame"""
        # update portion of the game loop
        self.all_sprites.update()
        self.camera.update(
            self.player
        )  # Call camera.update - give player as target to follow

        # Game over condition - No more zombies
        #        if len(self.mobs) == 0:
        #            self.playing = False

        # Mobs hit player
        hits = pg.sprite.spritecollide(self.player, self.mobs, False,
                                       collide_hit_rect)
        for hit in hits:
            now = pg.time.get_ticks()
            if now - hit.last_hit > MOB_HIT_TIMEOUT and random.random(
            ) < MOB_HIT_CHANCE:
                hit.last_hit = now
                # self.player.health -= MOB_DAMAGE
                hit.vel = vec(0, 0)
                random.choice(self.player_hit_sounds).play()
                if self.player.health <= 0:
                    self.playing = False
                self.player.got_hit()
                self.player.pos += vec(MOB_KNOCKBACK, 0).rotate(-hits[0].rot)

        # Bullets hit mobs
        hits = pg.sprite.groupcollide(self.mobs, self.bullets, False, True)
        for mob in hits:
            # hit.health -= WEAPONS[self.player.weapon]['damage'] * len(hits[hit])
            # hit.draw_health()
            for bullet in hits[mob]:
                mob.health -= bullet.damage
            mob.vel = vec(0, 0)

        # Player hits items
        hits = pg.sprite.spritecollide(self.player, self.items, False)
        for hit in hits:
            if hit.type == 'health' and self.player.health < PLAYER_HEALTH:
                hit.kill()
                self.effects_sounds['health_up'].play()
                self.player.add_health(HEALTH_PACK_AMOUNT)

            if hit.type in WEAPONS.keys():
                hit.kill()
                self.player.pickup(hit.type)
                # print(hit.type)

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, GREEN, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, GREEN, (0, y), (WIDTH, y))

    def render_fog(self):
        # draw light mask (gradient) onto fog image
        self.fog.fill((NIGHT_COLOUR))
        self.light_rect.center = self.camera.apply(self.player).center
        self.fog.blit(self.light_mask, self.light_rect)
        self.screen.blit(self.fog, (0, 0), special_flags=pg.BLEND_MULT)

    def draw(self):
        pg.display.set_caption("{:.2f}".format(self.clock.get_fps()))
        # self.screen.fill(BGCOLOR)
        self.screen.blit(self.map_img, self.camera.apply_rect(self.map_rect))
        # self.draw_grid()
        # self.all_sprites.draw(self.screen)
        for sprite in self.all_sprites:
            if isinstance(sprite, Mob):
                sprite.draw_health()
            self.screen.blit(
                sprite.image, self.camera.apply(sprite)
            )  # blit all sprites to screen in location of where the camera says the sprite is


#            if self.draw_debug:
#                pg.draw.rect(self.screen, CYAN, self.camera.apply_rect(sprite.hit_rect), 1)

        if self.night:
            self.render_fog()
        # DEBUG HUD
        if self.draw_debug:
            for wall in self.walls:
                pg.draw.rect(self.screen, CYAN,
                             self.camera.apply_rect(wall.rect), 1)
            for sprite in self.all_sprites:
                if isinstance(sprite, Mob):
                    sprite.draw_health()
                    pg.draw.circle(self.screen, CYAN,
                                   (self.camera.apply(sprite).centerx,
                                    self.camera.apply(sprite).centery),
                                   sprite.detect_radius, 1)
                pg.draw.rect(self.screen, CYAN,
                             self.camera.apply_rect(sprite.hit_rect), 1)

        # HUD functions
        draw_player_stats(self.screen, 10, 10,
                          (self.player.health / PLAYER_HEALTH))
        draw_player_stats(self.screen, 10, 40,
                          (self.player.fatigue / PLAYER_FATIGUE), BEIGE)
        self.draw_text("Zombies: {}".format(len(self.mobs)),
                       self.hud_font,
                       30,
                       WHITE,
                       WIDTH - 10,
                       10,
                       align="ne")
        if self.player.weapon is not None:
            self.draw_text("{}:{}".format(self.player.weapon.name,
                                          self.player.weapon.remaining_shots),
                           self.hud_font,
                           30,
                           WHITE,
                           10,
                           HEIGHT - 50,
                           align="nw")

        # Paused
        if self.paused:
            self.screen.blit(self.dim_screen, (0, 0))
            self.draw_text("Paused",
                           self.title_font,
                           105,
                           RED,
                           WIDTH / 2,
                           HEIGHT / 2,
                           align='center')

        pg.display.flip()

    def play_new_song(self):
        next_song = random.choice(BG_MUSIC)
        self.current_song = next_song
        # pg.mixer.music.load(next_song)
        pg.mixer.music.load(path.join(self.music_folder, next_song))
        pg.mixer.music.play()

    def events(self):
        # catch all events here
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()
            if event.type == pg.MOUSEBUTTONDOWN:
                # print(event.button)
                if event.button == 1:
                    self.player.shoot()
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    self.quit()
                if event.key == pg.K_h:
                    self.draw_debug = not self.draw_debug
                if event.key == pg.K_p:
                    self.paused = not self.paused
                if event.key == pg.K_n:
                    self.night = not self.night
                if event.key == pg.K_r:
                    self.player.reload()
                if event.key == pg.K_SPACE:
                    self.player.cycle_weapon()
                if event.key == pg.K_g:
                    self.player.print_inventory()
            if event.type == self.SONG_END:
                self.play_new_song()

    def show_start_screen(self):
        pass

    def show_go_screen(self):
        self.screen.fill(BLACK)
        pg.mixer.fadeout(5000)
        self.draw_text("GAME OVER",
                       self.title_font,
                       180,
                       RED,
                       WIDTH / 2,
                       HEIGHT / 2,
                       align='center')
        self.draw_text("Press any key to start",
                       self.title_font,
                       75,
                       WHITE,
                       WIDTH / 2,
                       HEIGHT * 3 / 4,
                       align='center')
        pg.display.flip()
        self.wait_for_key()

    def wait_for_key(self):
        pg.event.wait()  # Clears event queue
        waiting = True
        while waiting:
            self.clock.tick(FPS)
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    waiting = False
                    self.quit()

                if event.type == pg.KEYUP:
                    waiting = False
Exemplo n.º 14
0
class Game:
    def __init__(self):
        # Initialize pygame.
        pg.mixer.pre_init(44100, -16, 1, 2048)
        pg.init()
        pg.mixer.init()

        # Display
        pg.display.set_caption(TITLE)
        self.screen = pg.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT),
                                          FULLSCREEN)
        self.show_fps = False
        self.debug = False

        # Sprites groups.
        self.all_sprites = pg.sprite.Group()
        self.players = pg.sprite.Group()
        self.visible_sprites = pg.sprite.Group()
        self.walls = pg.sprite.Group()
        self.moving_walls = pg.sprite.Group()
        self.items = pg.sprite.Group()

        # Game loop.
        self.clock = pg.time.Clock()
        self.running = True
        self.playing = True

        self.player_pos = []

        self.camera_update = True

        # Load data from files.
        self.load()

    def load(self):
        # Folders.
        game_folder = os.path.dirname(__file__)
        font_folder = os.path.join(game_folder, "fnt")
        img_folder = os.path.join(game_folder, "img")
        snd_folder = os.path.join(game_folder, "snd")
        self.map_folder = os.path.join(game_folder, "map")

        # App icon.
        self.icon = pg.image.load(os.path.join(img_folder, GAME_IMG))
        pg.display.set_icon(self.icon)

        # Sprite images.
        self.player_imgs = {}
        for filename in PLAYER_IMGS:
            new_img = pg.image.load(os.path.join(img_folder,
                                                 filename)).convert_alpha()
            # Rotate so the sprite moves in the direction it is pointing.
            self.player_imgs[filename] = new_img
            # self.player_imgs[filename] = pg.transform.rotate(new_img, 90)

        # Wall images.
        self.wall_imgs = {}
        for filename in WALL_IMGS:
            new_img = pg.image.load(os.path.join(img_folder,
                                                 filename)).convert_alpha()
            self.wall_imgs[filename] = new_img

        # Item images.
        self.item_imgs = {}
        for filename in ITEM_IMGS:
            new_img = pg.image.load(os.path.join(img_folder,
                                                 filename)).convert_alpha()
            self.item_imgs[filename] = new_img

        # Sounds.
        self.sounds = {}
        for sound_type, filename in SOUNDS.items():
            new_snd = pg.mixer.Sound(os.path.join(snd_folder, filename))
            new_snd.set_volume(0.1)
            self.sounds[sound_type] = new_snd

        # Music.
        self.game_music = os.path.join(snd_folder, GAME_BG_MUSIC)

        # Text font.
        self.theme_font = os.path.join(font_folder, THEME_FONT)

    def create_map(self, filename):
        # Basic map background image with data.
        self.map = TiledMap(os.path.join(self.map_folder, filename))
        self.map.make_map()

        # Create the camera with the map dimensions.
        self.camera = Camera(SCREEN_WIDTH, SCREEN_HEIGHT, self.map.width,
                             self.map.height)

        movement = {
            "back": True,
            "parts": {
                1: {
                    "vel": 150,
                    "rot": 90,
                    "distance": 400
                },
                2: {
                    "vel": 50,
                    "rot": 45,
                    "distance": 100
                },
                3: {
                    "vel": 200,
                    "rot": 0,
                    "distance": 600,
                }
            }
        }

        # Map objects.
        for tile_object in self.map.tilemap_data.objects:
            # The center of the tile.
            object_center = Vec(tile_object.x + tile_object.width / 2,
                                tile_object.y + tile_object.height / 2)
            # Obstacles.
            if tile_object.object == "obstacle":
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height, tile_object.type)
            elif tile_object.object == "moving_obstacle":
                MovingObstacle(self, tile_object.x, tile_object.y,
                               tile_object.width, tile_object.height,
                               tile_object.type, movement)
            elif tile_object.object == "item":
                Item(self, object_center, tile_object.type, RANDOM_START_STEP)

    def new(self):
        # Create the map.
        self.create_map("map1.tmx")

        # Create the player object.
        self.player = Player(self, 100, 1800, "playerimg.png")

        # Start playing the background music.
        pg.mixer.music.load(self.game_music)
        pg.mixer.music.set_volume(0.1)
        pg.mixer.music.play(loops=-1)

        # Start running the game..
        self.run()

    def run(self):
        # Game loop.
        self.playing = True
        while self.playing:
            # Pause.
            self.dt = self.clock.tick(FPS) / 1000.0
            self.events()
            self.update()
            self.draw()

    def events(self):
        # Game events loop.
        for event in pg.event.get():
            # Check for closing window.
            if event.type == QUIT or event.type == KEYDOWN and event.key == \
                    K_ESCAPE:
                self.playing = False
                self.running = False
            if event.type == KEYDOWN:
                if event.key == K_b:
                    # Toggle debug mode.
                    self.debug = not self.debug
                if event.key == K_f:
                    self.show_fps = not self.show_fps
                if event.key == K_SPACE:
                    self.player.try_jump("push")
                if event.key == K_g:
                    # Change the player gravity up/down.
                    self.player.gravity_orientation *= -1
                if event.key == K_c:
                    # Change the player gravity up/down.
                    self.camera_update = not self.camera_update

    def update(self):
        # Game update loop.
        # self.moving_walls.update()
        # self.players.update()
        self.all_sprites.update()
        # Make the camera center on the player sprite.
        if self.camera_update:
            self.camera.update(self.player)
        # Update title with information.
        title = TITLE + f" FPS: {round(self.clock.get_fps(), 2)}"
        pg.display.set_caption(title)

    def draw_grid(self):
        # A grid of lines to represent the tiles of the map. The grid will
        # move along with the player/camera.
        for x in range(self.camera.x, SCREEN_WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGRAY, (x, 0), (x, SCREEN_HEIGHT))
        for y in range(self.camera.y, SCREEN_HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGRAY, (0, y), (SCREEN_WIDTH, y))

    def draw_boundary(self, sprite, sprite_color):
        # Image boundary.
        pg.draw.rect(self.screen, sprite_color,
                     self.camera.apply_sprite(sprite), 1)
        # Hit box.
        pg.draw.rect(self.screen, sprite_color,
                     self.camera.apply_rect(sprite.hit_rect), 1)
        surface = pg.Surface((sprite.hit_rect.width, sprite.hit_rect.height))
        surface.set_alpha(128)
        surface.fill(sprite_color)
        self.screen.blit(surface, self.camera.apply_rect(sprite.hit_rect))

    def draw_debug(self):
        # Grid of tiles.
        self.draw_grid()

        # Draw wall boundaries.
        for sprite in self.all_sprites:
            self.draw_boundary(sprite, sprite.color)

    def draw(self):
        # Game draw loop.
        self.screen.fill(BGCOLOR)
        # Map image.
        self.screen.blit(self.map.image, self.camera.apply_rect(self.map.rect))
        # Draw all sprites.
        for sprite in self.visible_sprites:
            self.screen.blit(sprite.image, self.camera.apply_sprite(sprite))

        if self.show_fps:
            # Draw FPS
            self.draw_text(f"FPS: {round(self.clock.get_fps(), 2)}",
                           OVERLAY_SIZE,
                           TEXT_COLOR,
                           SCREEN_WIDTH / 2,
                           0,
                           align="n",
                           font_name=self.theme_font)
        if self.debug:
            # Draw debug.
            self.draw_debug()

        # # Cool effect.
        # self.player_pos.append(Vec(self.player.pos.x, self.player.pos.y))
        # if len(self.player_pos) > 100:
        #     del self.player_pos[0]
        # width, height = self.player.hit_rect.width, self.player.hit_rect.height
        # for pos in self.player_pos:
        #     surface = pg.Surface(
        #         (width, height))
        #     surface.set_alpha(50)
        #     surface.fill(self.player.color)
        #     self.screen.blit(surface,
        #                      self.camera.apply_rect(pg.Rect(pos.x - width / 2,
        #                                                     pos.y - height / 2,
        #                                                     width, height)))
        # # Make sure it doesn't cover the player, so draw the player on top.
        # for sprite in self.players:
        #     self.screen.blit(sprite.image, self.camera.apply_sprite(sprite))
        # # Cool effect.

        # Flip the display (update the display).
        pg.display.flip()

    def draw_text(self,
                  text,
                  size,
                  fillcolor,
                  x,
                  y,
                  align="n",
                  font_name=None):
        # Create the font.
        font = pg.font.Font(font_name, size)
        # Create a surface to put the text on.
        text_surface = font.render(text, True, fillcolor)
        text_rect = text_surface.get_rect()
        # Align the text.
        if align == "nw":
            text_rect.topleft = (x, y)
        elif align == "ne":
            text_rect.topright = (x, y)
        elif align == "sw":
            text_rect.bottomleft = (x, y)
        elif align == "se":
            text_rect.bottomright = (x, y)
        elif align == "n":
            text_rect.midtop = (x, y)
        elif align == "s":
            text_rect.midbottom = (x, y)
        elif align == "e":
            text_rect.midright = (x, y)
        elif align == "w":
            text_rect.midleft = (x, y)
        elif align == "center":
            text_rect.center = (x, y)
        self.screen.blit(text_surface, text_rect)

        # Return the dimensions of the text rect in case it is needed for
        # positioning multiple text rects so that they fit together nicely.

        return text_rect

    def show_start_screen(self):
        # Game start screen.
        pass

    def show_game_over_screen(self):
        # Game over screen.
        pass
Exemplo n.º 15
0
class Game:
    def __init__(self):
        # Initializes PyGame buffer=256
        pg.mixer.pre_init(frequency=22050, size=-16, channels=2, buffer=256)
        pg.init()

        # Create screen and title
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)

        # Clock speed and key delay
        self.clock = pg.time.Clock()
        pg.key.set_repeat(500, 100)

        # Loads data..
        self.load_data()

    def load_data(self):
        # Asset folders
        game_folder = path.dirname(__file__)
        img_folder = path.join(game_folder, 'assets', 'textures')
        snd_folder = path.join(game_folder, 'assets', 'sounds', 'snd')
        music_folder = path.join(game_folder, 'assets', 'sounds', 'music')
        self.map_folder = path.join(game_folder, 'assets', 'maps')

        # Font
        self.title_font = path.join(img_folder, 'ZOMBIE.TTF')
        self.hud_font = path.join(img_folder, 'Impacted2.0.ttf')

        # Object images
        self.dim_screen = pg.Surface(self.screen.get_size()).convert_alpha()
        self.dim_screen.fill((0, 0, 0, 150))
        self.player_img = pg.image.load(
            path.join(img_folder, 'player', PLAYER_IMG)).convert_alpha()

        self.bullet_images = {}
        self.bullet_images['lg'] = pg.image.load(
            path.join(img_folder, 'bullet', BULLET_IMG)).convert_alpha()
        self.bullet_images['sm'] = pg.transform.scale(self.bullet_images['lg'],
                                                      (10, 10))

        self.mob_img = pg.image.load(path.join(img_folder, 'mob',
                                               MOB_IMG)).convert_alpha()
        self.splat = pg.image.load(path.join(img_folder, 'mob',
                                             SPLAT)).convert_alpha()
        self.splat = pg.transform.scale(self.splat, (64, 64))

        # Particles
        self.gun_flashes = []
        for img in MUZZLE_FLASHES:
            self.gun_flashes.append(
                pg.image.load(path.join(img_folder, 'particles',
                                        img)).convert_alpha())

        # Items
        self.item_images = {}
        for item in ITEM_IMAGES:
            self.item_images[item] = pg.image.load(
                path.join(img_folder, 'items',
                          ITEM_IMAGES[item])).convert_alpha()

        # Lighting effects
        self.fog = pg.Surface((WIDTH, HEIGHT))
        self.fog.fill(NIGHT_COLOR)
        self.light_mask = pg.image.load(path.join(img_folder,
                                                  LIGHT_MASK)).convert_alpha()
        self.light_mask = pg.transform.scale(self.light_mask, LIGHT_RADIUS)
        self.light_rect = self.light_mask.get_rect()

        # Sounds
        pg.mixer.music.load(path.join(music_folder, BG_MUSIC))

        self.effects_sounds = {}
        for type in EFFECTS_SOUNDS:
            s = pg.mixer.Sound(path.join(snd_folder, EFFECTS_SOUNDS[type]))
            s.set_volume(0.5)
            self.effects_sounds[type] = s

        self.weapon_sounds = {}
        for weapon in WEAPON_SOUNDS:
            self.weapon_sounds[weapon] = []
            for snd in WEAPON_SOUNDS[weapon]:
                s = pg.mixer.Sound(path.join(snd_folder, snd))
                if weapon == 'shotgun':
                    s.set_volume(0.2)
                else:
                    s.set_volume(0.5)
                self.weapon_sounds[weapon].append(s)

        self.zombie_moan_sounds = []
        for snd in ZOMBIE_MOAN_SOUNDS:
            s = pg.mixer.Sound(path.join(snd_folder, snd))
            s.set_volume(0.2)
            self.zombie_moan_sounds.append(s)

        self.player_hit_sounds = []
        for snd in PLAYER_HIT_SOUNDS:
            self.player_hit_sounds.append(
                pg.mixer.Sound(path.join(snd_folder, snd)))

        self.zombie_hit_sounds = []
        for snd in ZOMBIE_HIT_SOUNDS:
            s = pg.mixer.Sound(path.join(snd_folder, snd))
            s.set_volume(0.3)
            self.zombie_hit_sounds.append(s)

    def new(self):
        # initialize all variables and do all the setup for a new game
        self.draw_debug = False
        self.paused = False
        self.night = True

        # Sprite groups
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.obstacles = pg.sprite.Group()
        self.mobs = pg.sprite.Group()
        self.bullets = pg.sprite.Group()
        self.items = pg.sprite.Group()

        # Map
        self.map = TiledMap(path.join(self.map_folder, '01.tmx'))
        self.map_img = self.map.make_map()
        self.map_rect = self.map_img.get_rect()

        # Map Objects
        for tile_object in self.map.tmxdata.objects:
            # For spawning in center of tiled object
            obj_center = vec(tile_object.x + tile_object.width / 2,
                             tile_object.y + tile_object.height / 2)
            if tile_object.name == 'player':
                self.player = Player(self, obj_center.x, obj_center.y)
            if tile_object.name == 'zombie':
                Mob(self, obj_center.x, obj_center.y)
            if tile_object.name == 'wall':
                Obstacle(self, tile_object.x, tile_object.y, tile_object.width,
                         tile_object.height)
            if tile_object.name in ['health', 'shotgun']:
                Item(self, obj_center, tile_object.name)

        # Camera object
        self.camera = Camera(self.map.width, self.map.height)

        # Start a new level sound
        self.effects_sounds['level_start'].play()

    def run(self):
        # game loop - set self.playing = False to end the game
        self.playing = True

        # Starts music
        pg.mixer.music.play(loops=-1)

        while self.playing:
            # dt - How much time did the last frame take in ms (For frame-independent movement)
            self.dt = self.clock.tick(FPS) / 1000

            self.events()

            if not self.paused:
                self.update()

            self.draw()

    def events(self):
        # catch all events here
        for event in pg.event.get():
            # Closing window
            if event.type == pg.QUIT:
                self.quit()

            # Keyboard press
            if event.type == pg.KEYDOWN:
                # ESC
                if event.key == pg.K_ESCAPE:
                    self.quit()
                if event.key == pg.K_h:
                    self.draw_debug = not self.draw_debug
                if event.key == pg.K_p:
                    self.paused = not self.paused
                if event.key == pg.K_n:
                    self.night = not self.night

    def update(self):
        # Update portion of the game loop
        self.all_sprites.update()
        self.camera.update(self.player)

        # Game over?
        if len(self.mobs) == 0:
            self.playing = False

        # Player and item
        hits = pg.sprite.spritecollide(self.player, self.items, False)
        for hit in hits:
            if hit.type == 'health' and self.player.health < PLAYER_HEALTH:
                hit.kill()  # Destroy item
                self.effects_sounds['health_up'].play()  # Play sound
                self.player.add_health(HEALTH_PACK_AMOUNT)  # Add health
            if hit.type == 'shotgun':
                hit.kill()
                self.effects_sounds['gun_pickup'].play()
                self.player.weapon = 'shotgun'

        # Player and mobs
        hits = pg.sprite.spritecollide(self.player, self.mobs, False,
                                       collide_hit_rect)
        for hit in hits:
            # Play sound
            if random() < 0.7:
                choice(self.player_hit_sounds).play()

            # Take damage
            self.player.health -= MOB_DAMAGE
            hit.vel = vec(0, 0)
            if self.player.health <= 0:
                self.playing = False
        if hits:
            self.player.hit()
            self.player.pos += vec(MOB_KNOCKBACK, 0).rotate(-hits[0].rot)

        # Bullets and Mobs
        hits = pg.sprite.groupcollide(self.mobs, self.bullets, False, True)
        for mob in hits:
            for bullet in hits[mob]:
                mob.health -= bullet.damage
            mob.vel = vec(0, 0)

    def draw(self):
        # Shows window title and FPS counter
        pg.display.set_caption("{} FPS - {:.2f}".format(
            TITLE, self.clock.get_fps()))

        # Fill background
        # self.screen.fill(BGCOLOR)

        # Draws grid
        # self.draw_grid()

        # Draws map
        self.screen.blit(self.map_img, self.camera.apply_rect(self.map_rect))

        # Draws all sprites ant hit rectangles
        for sprite in self.all_sprites:
            if isinstance(sprite, Mob):
                sprite.draw_health()
            self.screen.blit(sprite.image, self.camera.apply(sprite))
            if self.draw_debug:
                pg.draw.rect(self.screen, CYAN,
                             self.camera.apply_rect(sprite.hit_rect), 1)
        if self.draw_debug:
            for obstacle in self.obstacles:
                pg.draw.rect(self.screen, CYAN,
                             self.camera.apply_rect(obstacle.rect), 1)

        # Lighting effects
        if self.night:
            self.render_fog()

        # Draws HUD
        draw_player_health(self.screen, 10, 10,
                           self.player.health / PLAYER_HEALTH)
        self.draw_text('Zombies: {}'.format(len(self.mobs)),
                       self.hud_font,
                       30,
                       WHITE,
                       WIDTH - 10,
                       10,
                       align="ne")

        # Draws PAUSED
        if self.paused:
            self.screen.blit(self.dim_screen, (0, 0))
            self.draw_text("Paused",
                           self.title_font,
                           105,
                           RED,
                           WIDTH / 2,
                           HEIGHT / 2,
                           align="center")

        # Flips/Updates screen
        pg.display.flip()

    def render_fog(self):
        self.fog.fill(NIGHT_COLOR)
        self.light_rect.center = self.camera.apply(self.player).center
        self.fog.blit(self.light_mask, self.light_rect)
        self.screen.blit(self.fog, (0, 0), special_flags=pg.BLEND_MULT)

    def draw_text(self, text, font_name, size, color, x, y, align="nw"):
        font = pg.font.Font(font_name, size)
        text_surface = font.render(text, True, color)
        text_rect = text_surface.get_rect()
        if align == "nw":
            text_rect.topleft = (x, y)
        if align == "ne":
            text_rect.topright = (x, y)
        if align == "sw":
            text_rect.bottomleft = (x, y)
        if align == "se":
            text_rect.bottomright = (x, y)
        if align == "n":
            text_rect.midtop = (x, y)
        if align == "s":
            text_rect.midbottom = (x, y)
        if align == "e":
            text_rect.midright = (x, y)
        if align == "w":
            text_rect.midleft = (x, y)
        if align == "center":
            text_rect.center = (x, y)
        self.screen.blit(text_surface, text_rect)

    def draw_grid(self):
        for x in range(0, WIDTH, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (x, 0), (x, HEIGHT))
        for y in range(0, HEIGHT, TILESIZE):
            pg.draw.line(self.screen, LIGHTGREY, (0, y), (WIDTH, y))

    def show_start_screen(self):
        pass

    def show_go_screen(self):
        self.screen.fill(BLACK)
        self.draw_text("GAME OVER",
                       self.title_font,
                       100,
                       RED,
                       WIDTH / 2,
                       HEIGHT / 2,
                       align="center")
        self.draw_text("Press a key to start",
                       self.title_font,
                       75,
                       WHITE,
                       WIDTH / 2,
                       HEIGHT * 3 / 4,
                       align="center")
        pg.display.flip()
        self.wait_for_key()

    def wait_for_key(self):
        pg.event.wait()
        waiting = True
        while waiting:
            self.clock.tick(FPS)
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    waiting = False
                    self.quit()
                if event.type == pg.KEYUP:
                    waiting = False

    def quit(self):
        pg.quit()
        sys.exit()