diff options
Diffstat (limited to 'formation.py')
-rw-r--r-- | formation.py | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/formation.py b/formation.py new file mode 100644 index 0000000..ae13b75 --- /dev/null +++ b/formation.py @@ -0,0 +1,296 @@ +from dataclasses import dataclass +import math +from typing import List + +from enemy import Enemy, EnemyAttributes +from game import Game +from sprite import Sprite + + +@dataclass +class FormationAttributes(EnemyAttributes): + """FormationAttributes.""" + + count: int = 1 + + +class FormationEnemy(Enemy): + """An enemy that belongs to a formation""" + + def __init__(self, game: Game, image_name, offset, + attributes: EnemyAttributes): + """Initialise the enemy + + :param game: The game which this belongs to + :type game: Game + :param image_name: The name of the image to use for the enemy + :param offset: The offset from the other enemies + :param attributes: The attributes given to this enemy + :type attributes: EnemyAttributes + """ + self.offset_x, self.offset_y, self.offset_a = offset + super().__init__(game, image_name, attributes) + + +class EnemyFormation: + """Cluster of enemies that move in a particular way""" + + def __init__(self, game: Game, image_name: str, + enemy_attributes: FormationAttributes): + """Initialise the formation + + :param game: The game which this belongs to + :type game: Game + :param image_name: The name of the image to use for the enemy + :type image_name: str + :param enemy_attributes: The attributes to use for spawned enemies + :type enemy_attributes: FormationAttributes + """ + self.game = game + self.sprites: List[FormationEnemy] = [] + self.image_name = image_name + + self.alpha = 0 + self.attributes = enemy_attributes + + self.x, self.y = 0, 0 + self.destroyed = False + + self.create_enemies() + self.hidden = True + + def create_enemies(self): + """Spawn enemies""" + pass + + def position_enemy(self, enemy: FormationEnemy): + """Position a single enemy + + :param enemy: The enemy to position + :type enemy: FormationEnemy + """ + enemy.set_pos( + ( + int(self.x + enemy.offset_x), + int(self.y + enemy.offset_y) + ) + ) + + def spawn_enemy(self, offset): + """Spawn a single enemy + + :param offset: The offset which to apply to the enemy + """ + enemy = FormationEnemy(self.game, self.image_name, + offset, self.attributes) + self.sprites.append(enemy) + return enemy + + def tick(self, player): + """Update the positions of all enemies + + :param player: The player to check if the enemies collide with + """ + self.alpha += 1 + for enemy in self.sprites: + enemy.tick(player) + self.position_enemy(enemy) + self.sprites = Sprite.remove_destroyed(self.sprites) + if len(self.sprites) == 0: + self.destroy() + + def destroy(self): + """Delete all enemies in this formation""" + for enemy in self.sprites: + enemy.destroy() + self.sprites = [] + self.destroyed = True + + def set_pos(self, pos): + """Set the position of this formation + + :param pos: position to move to + """ + self.x, self.y = pos + + def show(self): + """Make this formation visible""" + if self.hidden: + for enemy in self.sprites: + enemy.show() + self.hidden = False + + def hide(self): + """Make this formation hidden""" + if not self.hidden: + for enemy in self.sprites: + enemy.hide() + self.hidden = True + + +@dataclass +class CircleFormationAttributes(FormationAttributes): + """Attributes for a circle formation""" + + radius: int = 40 + period: int = 300 + + +class CircleFormation(EnemyFormation): + """A circular formation of enemies, rotating in a ring""" + + def __init__(self, game: Game, image_name, + attributes: CircleFormationAttributes): + """Initialise the formation + + :param game: The game which this belongs to + :type game: Game + :param image_name: The name of the image to use for the enemy + :param attributes: The attributes to use for spawned enemies + :type attributes: CircleFormationAttributes + """ + super().__init__(game, image_name, attributes) + self.attributes: CircleFormationAttributes + + def create_enemies(self): + """Spawn all the enemies""" + for i in range(self.attributes.count): + self.spawn_enemy((0, 0, i)) + + def position_enemy(self, enemy: FormationEnemy): + """Position a single enemy + + :param enemy: + :type enemy: FormationEnemy + """ + a = (enemy.offset_a / self.attributes.count) * \ + self.attributes.period + self.game.alpha + enemy.set_pos( + ( + int( + self.x+math.sin((-a/self.attributes.period) + * 2*math.pi) * self.attributes.radius + ), + int( + self.y+math.cos((-a/self.attributes.period) + * 2*math.pi) * self.attributes.radius + ) + ) + ) + + +class LemniscateFormation(EnemyFormation): + """An 'infinity' shape enemy formation""" + + def __init__(self, game: Game, image_name, + attributes: CircleFormationAttributes): + """Initialise the formation + + :param game: The game which this belongs to + :type game: Game + :param image_name: The name of the image to use for the enemy + :param attributes: The attributes to use for spawned enemies + :type attributes: CircleFormationAttributes + """ + super().__init__(game, image_name, attributes) + self.attributes: CircleFormationAttributes + + def create_enemies(self): + """Spawn all enemies""" + for i in range(self.attributes.count): + self.spawn_enemy((0, 0, (i / self.attributes.count) + * self.attributes.period * 0.25)) + + def position_enemy(self, enemy: FormationEnemy): + """Position an enemy + + :param enemy: + :type enemy: FormationEnemy + """ + a = enemy.offset_a + self.game.alpha + + t = (-a/self.attributes.period)*2*math.pi + x = self.x+(self.attributes.radius * math.cos(t)) / \ + (1 + math.sin(t)**2) + y = self.y+(self.attributes.radius * math.sin(t) * math.cos(t)) / \ + (1 + math.sin(t)**2) + + enemy.set_pos( + ( + int(x), + int(y) + ) + ) + + +@dataclass +class TriangleFormationAttributes(FormationAttributes): + """Attributes for a triangular formation""" + + spacing: int = 16 + + +class TriangleFormation(EnemyFormation): + """A v-shaped formation of enemies""" + + def __init__(self, game: Game, image_name: str, + attributes: TriangleFormationAttributes): + """Initialise the formation + + :param game: The game which this belongs to + :type game: Game + :param image_name: The name of the image to use for the enemy + :type image_name: str + :param attributes: The attributes to use for spawned enemies + :type attributes: TriangleFormationAttributes + """ + super().__init__(game, image_name, attributes) + self.attributes: TriangleFormationAttributes + + def create_enemies(self): + """Spawn all enemies in this formation""" + for i in range(self.attributes.count): + y = -((i+1) // 2)*self.attributes.spacing//2 + + # first part is multiply by 1 or -1 to determine the side + # then just have an offset for how far + x = 2*((i % 2)-0.5) * ((i+1)//2)*self.attributes.spacing + + self.spawn_enemy((x, y, 1)) + + +@dataclass +class RectangleFormationAttributes(TriangleFormationAttributes): + """Attributes for a rectangle formation""" + + width: int = 5 + height: int = 2 + + +class RectangleFormation(EnemyFormation): + """A grid-like formation of enemies""" + + def __init__(self, game: Game, image_name, + attributes: RectangleFormationAttributes): + """Initialise the formation + + :param game: The game which this belongs to + :type game: Game + :param image_name: The name of the image to use for the enemy + :param attributes: The attributes to use for spawned enemies + :type attributes: RectangleFormationAttributes + """ + super().__init__(game, image_name, attributes) + self.attributes: RectangleFormationAttributes + + def create_enemies(self): + """Spawn all enemies""" + full_width = self.attributes.width * self.attributes.spacing + full_height = self.attributes.height * self.attributes.spacing + + for y in range(self.attributes.height): + offset_y = ((y+0.5)*self.attributes.spacing)-(full_height/2) + + for x in range(self.attributes.width): + offset_x = ((x+0.5)*self.attributes.spacing)-(full_width/2) + self.spawn_enemy((offset_x, offset_y, 1)) |