tkinter-space-game/formation.py
2023-01-05 11:35:28 +00:00

296 lines
9 KiB
Python

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))