mirror of
https://git.davidovski.xyz/tkinter-space-game.git
synced 2024-08-15 00:43:41 +00:00
341 lines
10 KiB
Python
341 lines
10 KiB
Python
import math
|
|
from random import choice, randint, random
|
|
|
|
from boss import CircleBossFormation, SnakeBossFormation
|
|
from enemy import EnemyAttributes
|
|
from formation import (
|
|
CircleFormation,
|
|
CircleFormationAttributes,
|
|
EnemyFormation,
|
|
FormationAttributes,
|
|
LemniscateFormation,
|
|
RectangleFormation,
|
|
RectangleFormationAttributes,
|
|
TriangleFormation,
|
|
TriangleFormationAttributes,
|
|
)
|
|
|
|
|
|
def wobble_pattern(formation):
|
|
"""A sinusoidal movement pattern
|
|
|
|
:param formation: Formation to move
|
|
"""
|
|
x = (1+math.sin(formation.alpha/80)) * formation.game.w/2
|
|
y = formation.y + (1 if formation.alpha % 4 == 0 else 0)
|
|
formation.set_pos((x, y))
|
|
|
|
|
|
def speed_pattern(formation):
|
|
"""Quickly move the formation downwards
|
|
|
|
:param formation: Formation to move
|
|
"""
|
|
x = formation.x
|
|
y = formation.y + 2
|
|
formation.set_pos((x, y))
|
|
|
|
|
|
def slow_pattern(formation):
|
|
"""Slowly move the formation downwards
|
|
|
|
:param formation: Formation to move
|
|
"""
|
|
x = formation.x
|
|
y = formation.y + (1 if formation.alpha % 8 == 0 else 0)
|
|
formation.set_pos((x, y))
|
|
|
|
|
|
def slide_in_pattern(formation):
|
|
"""Slowly move into the center of the screen and then remain there
|
|
|
|
:param formation: Formation to move
|
|
"""
|
|
cy = formation.game.h//3
|
|
|
|
if formation.alpha < 400:
|
|
x = formation.x
|
|
y = (formation.alpha/400) * (cy*1.5) - (cy*0.5)
|
|
else:
|
|
x = formation.x
|
|
y = formation.y
|
|
|
|
formation.set_pos((int(x), int(y)))
|
|
|
|
|
|
def no_pattern(formation):
|
|
"""No movement, stay in the center
|
|
|
|
:param formation: Formation to move
|
|
"""
|
|
formation.set_pos((
|
|
formation.game.w // 2,
|
|
formation.game.h // 3
|
|
))
|
|
|
|
|
|
def figure_of_eight_pattern(formation):
|
|
"""Move the formation in a figure of eight
|
|
|
|
:param formation: Formation to move
|
|
"""
|
|
period = 600
|
|
edge = 8
|
|
radius = formation.game.h//3 - edge
|
|
cx, cy = formation.game.w//2, formation.game.h//3
|
|
|
|
if formation.alpha < 200:
|
|
x = formation.x
|
|
y = (formation.alpha/200) * (cy*1.5) - (cy*0.5)
|
|
else:
|
|
a = formation.alpha - 200
|
|
t = (a/period)*2*math.pi - math.pi/2
|
|
|
|
y = cy + (radius * math.cos(t)) / (1 + math.sin(t)**2)
|
|
x = cx + (radius * math.sin(t) * math.cos(t)) / (1 + math.sin(t)**2)
|
|
|
|
formation.set_pos((int(x), int(y)))
|
|
|
|
|
|
class FormationSpawner():
|
|
"""Object to manage spawning of enemies and phases"""
|
|
|
|
def __init__(self, game):
|
|
"""Initialise the formation spawner
|
|
|
|
:param game: The game which this belongs to
|
|
"""
|
|
self.game = game
|
|
self.formations = []
|
|
self.difficulty_multiplier = 0.5
|
|
|
|
self.next_formation = 0
|
|
|
|
self.phase = -1
|
|
self.phases = [
|
|
Phase("Phase:1", [
|
|
self.spawn_fleet,
|
|
self.spawn_loop,
|
|
self.spawn_orbital], 10),
|
|
Phase("Boss:1", [self.spawn_circle_boss], 1),
|
|
Phase("Phase:2", [
|
|
self.spawn_fleet,
|
|
self.spawn_loop,
|
|
self.spawn_orbital], 10, max_wave=3),
|
|
Phase("Boss:2", [self.spawn_snake_boss], 1),
|
|
]
|
|
|
|
self.to_spawn = 0
|
|
self.current_reward = 1
|
|
|
|
def tick(self):
|
|
"""Update all formations"""
|
|
for formation, update in self.formations:
|
|
formation.tick(self.game.player)
|
|
update(formation)
|
|
if formation.y > self.game.h:
|
|
formation.destroy()
|
|
self.formations = list(
|
|
filter(lambda s: not s[0].destroyed, self.formations))
|
|
|
|
self.spawn_next()
|
|
|
|
def spawn_random(self):
|
|
"""Spawn a random formation"""
|
|
options = [
|
|
self.spawn_fleet,
|
|
self.spawn_loop,
|
|
self.spawn_orbital,
|
|
self.spawn_rectangle
|
|
]
|
|
choice(options)()
|
|
|
|
def spawn_formation(self, formation: EnemyFormation, update):
|
|
"""Add a formation to the list of formations
|
|
|
|
:param formation: Formation to add
|
|
:type formation: EnemyFormation
|
|
:param update: movement function to use for this formation
|
|
"""
|
|
update(formation)
|
|
formation.show()
|
|
self.formations.append((formation, update))
|
|
|
|
def spawn_circle_boss(self):
|
|
"""Spawn the circle boss"""
|
|
attributes = EnemyAttributes(
|
|
hp=int(15*self.difficulty_multiplier),
|
|
reward=self.current_reward,
|
|
cooldown=50
|
|
)
|
|
formation = CircleBossFormation(self.game, attributes)
|
|
formation.set_pos((self.game.w//2, 0))
|
|
update = figure_of_eight_pattern
|
|
self.spawn_formation(formation, update)
|
|
|
|
def spawn_snake_boss(self):
|
|
"""Spawn the snake boss"""
|
|
attributes = FormationAttributes(
|
|
hp=int(10*self.difficulty_multiplier),
|
|
reward=self.current_reward,
|
|
cooldown=160
|
|
)
|
|
|
|
formation = SnakeBossFormation(self.game, attributes)
|
|
formation.set_pos((self.game.w//2, 0))
|
|
update = slide_in_pattern
|
|
self.spawn_formation(formation, update)
|
|
|
|
def spawn_fleet(self):
|
|
"""Spawn the fleet formation"""
|
|
sprite = randint(6, 7)
|
|
|
|
position = (random()*self.game.w, -32)
|
|
attributes = TriangleFormationAttributes(
|
|
hp=int(self.difficulty_multiplier),
|
|
cooldown=-1,
|
|
reward=self.current_reward,
|
|
count=randint(1, 3)*2 + 1,
|
|
spacing=8
|
|
)
|
|
formation = TriangleFormation(
|
|
self.game, f"smallenemy{sprite}", attributes)
|
|
formation.set_pos(position)
|
|
|
|
update = speed_pattern
|
|
self.spawn_formation(formation, update)
|
|
|
|
def spawn_orbital(self):
|
|
"""Spawn the orbital formation"""
|
|
position = (random()*self.game.w, -32)
|
|
sprite = choice((1, 3))
|
|
|
|
attributes = CircleFormationAttributes(
|
|
hp=int(self.difficulty_multiplier * 2),
|
|
count=randint(3, 4)*2,
|
|
radius=randint(10, 20),
|
|
period=randint(100//int(self.difficulty_multiplier), 400),
|
|
cooldown=80,
|
|
reward=self.current_reward
|
|
|
|
)
|
|
|
|
formation = CircleFormation(
|
|
self.game, f"smallenemy{sprite}", attributes)
|
|
formation.set_pos(position)
|
|
|
|
update = wobble_pattern
|
|
formation.alpha = randint(1, 1000)
|
|
self.spawn_formation(formation, update)
|
|
|
|
def spawn_rectangle(self):
|
|
"""Spawn the rectangle formation"""
|
|
sprite = choice((0, 2))
|
|
position = (random() * self.game.w, -32)
|
|
|
|
attributes = RectangleFormationAttributes(
|
|
hp=int(self.difficulty_multiplier * 2),
|
|
width=randint(4, 6),
|
|
height=randint(2, 3),
|
|
cooldown=80,
|
|
reward=self.current_reward,
|
|
)
|
|
|
|
formation = RectangleFormation(
|
|
self.game, f"smallenemy{sprite}", attributes
|
|
)
|
|
formation.set_pos(position)
|
|
|
|
update = wobble_pattern
|
|
formation.alpha = randint(1, 1000)
|
|
self.spawn_formation(formation, update)
|
|
|
|
def spawn_loop(self):
|
|
"""Spawn the loop formation"""
|
|
sprite = choice((4, 5))
|
|
position = (random()*self.game.w, -32)
|
|
attributes = CircleFormationAttributes(
|
|
count=randint(4, 8),
|
|
radius=randint(self.game.w//2, self.game.w),
|
|
period=randint(200, 300),
|
|
hp=int(self.difficulty_multiplier),
|
|
reward=self.current_reward,
|
|
cooldown=160,
|
|
)
|
|
|
|
formation = LemniscateFormation(
|
|
self.game, f"smallenemy{sprite}", attributes)
|
|
formation.set_pos(position)
|
|
|
|
update = slow_pattern
|
|
self.spawn_formation(formation, update)
|
|
|
|
def spawn_next(self):
|
|
"""Spawn the next formation to be spawned"""
|
|
if self.to_spawn > 0:
|
|
if len(self.formations) < self.current_phase().max_wave:
|
|
if self.game.alpha > self.next_formation \
|
|
and self.next_formation != -1:
|
|
self.next_formation = self.game.alpha \
|
|
+ 100 / self.difficulty_multiplier
|
|
|
|
self.current_phase().get_spawn_function()()
|
|
self.to_spawn -= 1
|
|
else:
|
|
if len(self.formations) == 0:
|
|
self.next_phase()
|
|
|
|
def next_phase(self):
|
|
"""Increment the phase by 1 and start the next phase"""
|
|
self.phase += 1
|
|
self.game.save_game()
|
|
self.start_phase()
|
|
|
|
def start_phase(self):
|
|
"""Start the next phase"""
|
|
self.to_spawn = self.current_phase().duration
|
|
|
|
self.difficulty_multiplier = (self.phase+2) * 0.5
|
|
self.current_reward = int(2**self.difficulty_multiplier)
|
|
|
|
self.next_formation = self.game.alpha + 100
|
|
if self.current_phase().name:
|
|
self.game.effect_player.splash_text(self.current_phase().name)
|
|
|
|
def current_phase(self):
|
|
"""Return the current phase"""
|
|
if self.phase < len(self.phases):
|
|
return self.phases[self.phase]
|
|
|
|
return Phase(f"Phase:{self.phase-1}", [
|
|
self.spawn_random
|
|
], 10 * self.difficulty_multiplier,
|
|
max_wave=int(self.difficulty_multiplier)
|
|
)
|
|
|
|
def clear_all(self):
|
|
"""Remove all formation objects"""
|
|
for f, _ in self.formations:
|
|
f.destroy()
|
|
|
|
|
|
class Phase:
|
|
"""Rules for which formation will be spawned"""
|
|
|
|
def __init__(self, name, spawn_functions, duration, max_wave=2):
|
|
"""__init__.
|
|
|
|
:param name: The name of the phase
|
|
:param spawn_functions: A list of functions to use to spawn enemies
|
|
:param duration: The number of formations to spawn
|
|
before the phase is over
|
|
:param max_wave: The maximum number of formations to spawn at a time
|
|
"""
|
|
self.spawn_functions = spawn_functions
|
|
self.duration = duration
|
|
self.name = name
|
|
self.max_wave = max_wave
|
|
|
|
def get_spawn_function(self):
|
|
"""Return a random spawn function"""
|
|
return choice(self.spawn_functions)
|