def update(self):
        # Call the appropriate method for each phase
        if ml.time() - self.phase_change_time > self.phase_change_delay:
            self.phase_method_list[self.phase - 1]()

        super().update()
    def phase4(self):
        # Move boss in circle around center of window
        window_center = ml.window_width / 2, ml.window_height / 2
        radius = 200
        self.circle_angle += 0.5
        circle_x = ml.window_width / 2 + \
                   radius * math.cos(math.radians(self.circle_angle))
        circle_y = ml.window_height / 2 + \
                   radius * math.sin(math.radians(self.circle_angle))
        target_point = circle_x, circle_y
        self.x, self.y, _ = ml.move_point(self, target_point, 999, 0, 360)
        # Laser
        self.laser.change_image(image=ml.get_laser_image(
            ml.angle_to_point(self.rect.center, window_center)))
        self.laser.move(window_center)
        if ml.time() - self.phase_change_time > self.phase_change_delay + 2:
            self.collide_parts()

        # Minions
        self.minion1part.animate(angle_change=15)
        self.minion2part.animate(angle_change=15)
        self.minion3part.animate(angle_change=15)
        # minion3
        self.minion3.x, self.minion3.y, _ = ml.move_point(
            self.minion3, ml.player.rect.center, 1, 0, 360)
        self.phase1fd.angle = 0
        self.phase1timer1 = self.shoot(self.phase1timer1, self.phase1bd,
                                       self.phase1fd)
        self.phase1fd.angle = 180
        self.phase1timer2 = self.shoot(self.phase1timer2, self.phase1bd,
                                       self.phase1fd)
        self.phase1fd.angle = 90
        self.phase1timer3 = self.shoot(self.phase1timer3, self.phase1bd,
                                       self.phase1fd)
        self.phase1fd.angle = 270
        self.phase1timer4 = self.shoot(self.phase1timer4, self.phase1bd,
                                       self.phase1fd)
        # minion1
        self.minion1.x, self.minion1.y, self.minion1.current_angle = \
            ml.move_point(self.minion1, ml.player.rect.center, 5,
                          self.minion1.current_angle, 0.5)
        self.phase2minion_timer = self.shoot(self.phase2minion_timer,
                                             self.phase2minionbd,
                                             self.phase2minionfd)
        # Bounce off walls
        if self.minion1.rect.left < 0:
            self.minion1.rect.left = 0
            self.minion1.x = self.minion1.rect.centerx
            self.minion1.current_angle = 180 - self.minion1.current_angle
        elif self.minion1.rect.right > ml.window_width:
            self.minion1.rect.right = ml.window_width
            self.minion1.x = self.minion1.rect.centerx
            self.minion1.current_angle = 180 - self.minion1.current_angle
        elif self.minion1.rect.top < 0:
            self.minion1.rect.top = 0
            self.minion1.y = self.minion1.rect.centery
            self.minion1.current_angle = 360 - self.minion1.current_angle
        elif self.minion1.rect.bottom > ml.window_height:
            self.minion1.rect.bottom = ml.window_height
            self.minion1.y = self.minion1.rect.centery
            self.minion1.current_angle = 360 - self.minion1.current_angle
        self.minion1.current_angle = ml.normalize_angle(
            self.minion1.current_angle)
        # minion2
        self.minion_angle = ml.normalize_angle(self.minion_angle + 1)
        radius = 250
        circle_x = ml.player.rect.centerx + radius * math.cos(
            math.radians(self.minion_angle))
        circle_y = ml.player.rect.centery - radius * math.sin(
            math.radians(self.minion_angle))
        minion2_point = circle_x, circle_y
        self.minion2.x, self.minion2.y, _ = ml.move_point(
            self.minion2, minion2_point, 20, 0, 360)
        self.minion2part.move((self.minion2.x, self.minion2.y))
        self.phase3miniontimer1 = self.shoot(self.phase3miniontimer1,
                                             self.phase3minionbd1,
                                             self.phase3minionfd)
        # Move parts
        self.minion1part.move((self.minion1.x, self.minion1.y))
        self.minion2part.move((self.minion2.x, self.minion2.y))
        self.minion3part.move((self.minion3.x, self.minion3.y))
    def __init__(self) -> pygame.sprite.Sprite:
        # General data
        self.image_name = 'boss1.png'
        self.invisible_image_path = os.path.join('graphics',
                                                 'boss_invisible.png')
        self.boss_invisible_image = \
            pygame.image.load(self.invisible_image_path).convert_alpha()
        self.health = starburst_health
        self.collision_damage = 2
        self.move_speed = ml.normalize_target_fps(0)
        self.max_speed = ml.normalize_target_fps(3.5)
        self.turning_rate = ml.normalize_target_fps(0)
        self.max_turning_rate = ml.normalize_target_fps(1.4)

        self.name = 'Star Burst'

        super().__init__(ml.window_width / 2,
                         self.move_speed,
                         self.health,
                         self.collision_damage,
                         self.image_name,
                         turning_rate=self.turning_rate,
                         y=350,
                         boss=True,
                         name=self.name)

        # Animation data
        self.rotation_angle = 0
        self.rotation_rate = ml.normalize_target_fps(0.5)

        # Phase data
        self.phase = 1
        self.phase_change_time = ml.time() - 2
        self.phase_change_delay = 5
        # These methods are called depending on the boss's current phase
        self.phase_method_list = [
            self.phase1, self.phase2, self.phase3, self.phase4
        ]
        self.num_phases = len(self.phase_method_list)

        # Shooting data
        self.circle_timer = ml.time()
        self.circle_bd = BulletData(self, speed=2, duration=5.2)
        self.circle_fd = FiringData(aim=True, multi=36)
        self.ring_bd = BulletData(self, speed=4)
        self.ring_firing_speed = 30
        self.ring_fd = FiringData(firing_speed=self.ring_firing_speed,
                                  interval=180,
                                  multi=2,
                                  angle=0)
        self.ring_timer = ml.time()
        self.spiral_bd = BulletData(self,
                                    spiral=True,
                                    radial_growth=2.5,
                                    turning_rate=0.5)
        self.spiral_fd = FiringData(multi=18, interval=20)
        self.spiral_timer = ml.time()
        self.explode_shot_timer = ml.time()
        self.explode_bd = [(BulletData(self, speed=3), FiringData(multi=36))]
        self.explode_shot_bd = BulletData(self,
                                          damage=2,
                                          speed=2,
                                          random_duration=(1.5, 3),
                                          exploding=self.explode_bd)
        self.explode_shot_fd = FiringData(firing_speed=0.5)
        self.circle_random_bd = BulletData(self,
                                           random_speed=(1, 5),
                                           duration=10)
        self.circle_random_fd = FiringData(firing_speed=15)
        self.circle_random_timer = ml.time()
    def __init__(self) -> pygame.sprite.Sprite:
        # General data
        self.image_name = 'ring.png'
        self.invisible_image_path = os.path.join('graphics',
                                                 'ring_invisible.png')
        self.boss_invisible_image = \
            pygame.image.load(self.invisible_image_path).convert_alpha()
        self.health = ring_health
        self.collision_damage = 1
        self.move_speed = ml.normalize_target_fps(0)
        self.turning_rate = ml.normalize_target_fps(2)
        self.base_turning_rate = ml.normalize_target_fps(2)

        self.name = 'Ring'

        super().__init__(ml.window_width / 2,
                         self.move_speed,
                         self.health,
                         self.collision_damage,
                         self.image_name,
                         turning_rate=self.turning_rate,
                         y=300,
                         boss=True,
                         name=self.name)

        # Spawn parts (in draw order)
        self.ring1 = BossPart('ring1_red.png')
        self.ring2 = BossPart('ring2_red.png')
        self.part_color = 'red'

        # Phase data
        self.phase_change_time = ml.time() - 2
        self.phase_change_delay = 5
        self.phase1_delay = self.phase_change_delay - 2
        self.circle_angle = 90
        # self.shield_radius = (self.shield.get_mask_rect().width / 2) - 6

        # Phase 1
        phase1_firing_speed = 1
        self.phase1bd = BulletData(self, speed=4)
        # angle will be modified in phase1()
        self.phase1fd = FiringData(firing_speed=phase1_firing_speed, multi=9)
        self.phase1timer1 = ml.time() + self.phase1_delay
        self.phase1timer2 = ml.time() + self.phase1_delay
        self.phase1timer3 = ml.time() + self.phase1_delay + (
            1 / (2 * phase1_firing_speed))
        self.phase1timer4 = ml.time() + self.phase1_delay + (
            1 / (2 * phase1_firing_speed))
        # Phase 2
        self.phase2exbd = BulletData(self,
                                     speed=5,
                                     random_duration=(1.2, 1.5),
                                     infinite_explosion=True)
        self.phase2exfd = FiringData(aim=True)
        self.phase2ex = [(self.phase2exbd, self.phase2exfd)]
        self.phase2bd = BulletData(self,
                                   speed=3,
                                   duration=2,
                                   exploding=self.phase2ex,
                                   infinite_explosion=True)
        self.phase2fd = FiringData(firing_speed=0.2, aim=True)
        self.phase2timer = ml.time()
        self.shot_alive = False
        # Fake exploding data to make the bullets orange
        self.fakebd = BulletData(self, duration=0)
        self.fakefd = FiringData()
        self.fakeex = [(self.fakebd, self.fakefd)]
        self.phase2minionbd = BulletData(self,
                                         duration=0.5,
                                         turning_rate=6,
                                         spiral=True,
                                         radial_growth=3,
                                         exploding=self.fakeex)
        self.phase2minionfd = FiringData(firing_speed=5,
                                         interval=45,
                                         multi=8,
                                         aim=True)
        self.phase2minion_timer = ml.time()
        # Phase 3
        self.phase3exbd = BulletData(self,
                                     speed=8,
                                     homing=True,
                                     turning_rate=0.6)
        self.phase3exfd = FiringData(aim=True)
        self.phase3ex = [(self.phase3exbd, self.phase3exfd)]
        self.phase3bd = BulletData(self,
                                   speed=6,
                                   duration=0.4,
                                   exploding=self.phase3ex)
        self.phase3fd = FiringData(firing_speed=0.6, multi=5, interval=35)
        self.phase3timer = ml.time()
        self.phase3minionbd1 = BulletData(self,
                                          speed=5,
                                          homing=True,
                                          turning_rate=0.4)
        self.phase3minionbd2 = BulletData(self,
                                          speed=5,
                                          homing=True,
                                          turning_rate=0.4)
        self.phase3minionfd = FiringData(firing_speed=0.5, aim=True)
        self.phase3miniontimer1 = ml.time()
        self.phase3miniontimer2 = ml.time()
        # Phase 4
        self.laser_damage = 2

        # These methods are called depending on the boss's current phase
        self.phase_method_list = [
            self.phase1, self.phase2, self.phase3, self.phase4
        ]
        self.num_phases = len(self.phase_method_list)

        self.test_timer = ml.time()
    def __init__(self) -> pygame.sprite.Sprite:
        # General data
        self.image_name = 'enemy_clone.png'
        self.invisible_image_path = os.path.join('graphics',
                                                 'enemy_clone_invisible.png')
        self.boss_invisible_image = \
            pygame.image.load(self.invisible_image_path).convert_alpha()
        self.health = doppelganger_health
        self.collision_damage = 1
        self.move_speed = ml.normalize_target_fps(0)
        self.turning_rate = ml.normalize_target_fps(2)
        self.base_turning_rate = ml.normalize_target_fps(2)

        self.name = 'Doppelganger'

        super().__init__(ml.window_width / 2,
                         self.move_speed,
                         self.health,
                         self.collision_damage,
                         self.image_name,
                         turning_rate=self.turning_rate,
                         y=300,
                         boss=True,
                         name=self.name)

        # Phase data
        self.phase_change_time = ml.time() - 2
        self.phase_change_delay = 5
        # Phase 4
        self.moving_x = True
        self.moving_y = False
        self.circle_bd = BulletData(self, speed=2.5, duration=8)
        self.circle_fd = FiringData(aim=True, multi=36)
        # Phase 3
        self.move_speed_scale = ml.normalize_target_fps(800)
        self.min_move_speed = ml.normalize_target_fps(3)
        self.max_move_speed = ml.normalize_target_fps(13)
        self.movement_angle = self.current_angle
        # Phase 2
        self.phase2_bd = None
        self.phase2_fd = FiringData(firing_speed=1.2, aim=True, multi=3)
        self.phase2_timer = ml.time()
        self.phase2spread_bd = BulletData(self, speed=4)
        self.phase2spread_fd = FiringData(firing_speed=0.7,
                                          interval=8,
                                          aim=True,
                                          multi=9)
        self.phase2spread_timer = ml.time()
        self.phase2arc_bd = BulletData(self, speed=3)
        self.phase2arc_fd = FiringData(firing_speed=0.7,
                                       angle=None,
                                       burst=9,
                                       burst_delay=0.1)
        self.phase2arc_timer = ml.time()
        self.phase2arc_counter = 0
        self.phase2arc2_timer = ml.time()
        self.phase2arc2_counter = 0
        # Phase 1
        self.phase1homing_bd = BulletData(self,
                                          speed=5,
                                          duration=7,
                                          homing=True,
                                          turning_rate=1.7)
        self.phase1homing_fd = FiringData(firing_speed=0.15,
                                          interval=180,
                                          angle=0,
                                          multi=2)
        self.phase1homing_timer = ml.time()
        self.phase1_timer = ml.time()
        self.phase1explode_bd = BulletData(self, speed=10)
        self.phase1explode_fd = FiringData(interval=180, angle=0, multi=2)
        self.phase1explode = [(self.phase1explode_bd, self.phase1explode_fd)]
        self.phase1_bd = None
        self.phase1_fd = None

        # These methods are called depending on the boss's current phase
        self.phase_method_list = [
            self.phase1, self.phase2, self.phase3, self.phase4
        ]
        self.num_phases = len(self.phase_method_list)

        # Shooting data
        self.mine_timer = ml.time()
        self.mine_explosion_bd = BulletData(self, speed=5)
        self.mine_explosion_fd = FiringData(interval=36,
                                            angle=45,
                                            multi=10,
                                            aim=True)
        self.mine_explosion_data = (self.mine_explosion_bd,
                                    self.mine_explosion_fd)
        self.mine_bd = BulletData(self,
                                  duration=2,
                                  damage=2,
                                  exploding=[self.mine_explosion_data])
        self.mine_fd = FiringData()

        self.test_timer = ml.time()