diff options
Diffstat (limited to 'game.py')
-rw-r--r-- | game.py | 310 |
1 files changed, 310 insertions, 0 deletions
@@ -0,0 +1,310 @@ +from random import randint, random +from tkinter import Canvas, PhotoImage, Tk +from typing import List + +from config import Config +from font import Font +from frame_counter import FrameCounter +from inputs import InputController +from sprite import Sprite +from textures import TextureFactory + + +class Game: + """A generic game object""" + + def __init__(self) -> None: + """Initialise the game + """ + self.win = Tk() + game_width, game_height = ( + Config.WIDTH*Config.SCALE, Config.HEIGHT*Config.SCALE + ) + self.w, self.h = Config.WIDTH, Config.HEIGHT + + self.win.geometry(f"{game_width}x{game_height}") + self.canvas = Canvas(self.win, width=game_width, + height=game_height, bg="#000") + self.canvas.pack() + + self.texture_factory = TextureFactory(scale=Config.SCALE) + self.effect_player = EffectPlayer(self) + self.frame_counter = FrameCounter(self.canvas, Config.FPS) + + self.inputs = InputController(self) + self.sprites = [] + + self.score = 0 + + self.alpha = 0 + + def start(self): + """Start the game""" + self.loop() + self.win.mainloop() + + def tick(self): + """Update the game's sprites""" + for sprite in self.sprites: + sprite.tick() + self.effect_player.tick() + + def loop(self): + """Loop the game at a set framerate""" + self.alpha += 1 + self.tick() + self.frame_counter.next_frame(self.loop) + + def clear_all(self): + """Remove all game sprites""" + for sprite in self.sprites: + sprite.destroy() + self.sprites = [] + + +class GameSprite(Sprite): + """A sprite which belongs to a game""" + + def __init__(self, game: Game, image: PhotoImage): + """Initialise the sprite + + :param game: The game which this belongs to + :type game: Game + :param image: The image to use for the sprite + :type image: PhotoImage + """ + self.game = game + super().__init__(game.canvas, image, (0, 0)) + + def move(self, x, y): + """Move the sprite by a certain amount + + :param x: Amount of pixels to move right + :param y: Amount of pixels to move down + """ + # if the object needs to move less than a pixel + # only move it every few frames to create this effect + if abs(x) >= 1: + self.x += x + elif x != 0: + if self.game.alpha % (1/x) == 0: + self.x += 1 + + if abs(y) >= 1: + self.y += y + elif y != 0: + if self.game.alpha % (1/y) == 0: + self.y += 1 + + self.update_position() + + +class GameEffect(GameSprite): + """An effect that can be played within game""" + + def __init__(self, game: Game, image: PhotoImage, + duration=10, momentum=(0, 0)): + """Initialise the game effect + + :param game: The game which this belongs to + :type game: Game + :param image: The image to use for this + :type image: PhotoImage + :param duration: How long this effect should last for + :param momentum: Which direction to move this effect + """ + self.start_time = game.alpha + self.duration = duration + self.velocity_x, self.velocity_y = momentum + super().__init__(game, image) + + def tick(self): + """Move the effect by its momentum and remove it if its over""" + super().tick() + self.move(self.velocity_x, self.velocity_y) + + alpha = self.game.alpha - self.start_time + if self.duration != -1 and alpha > self.duration: + self.destroy() + + +class AnimatedEffect(GameEffect): + """An effect which involves animating an image""" + + def __init__(self, game: Game, images: List[PhotoImage], + frame_time=1, momentum=(0, 0)): + """Initialise the effect + + :param game: The game which this belongs to + :type game: Game + :param images: The images to use for this animation + :type images: List[PhotoImage] + :param frame_time: Length of each frame of the animation + :param momentum: Direction to move this effect + """ + self.start_time = game.alpha + self.frame_time = frame_time + self.images = images + self.frame_start = game.alpha + super().__init__(game, images[0], duration=len( + images)*frame_time, momentum=momentum) + + def tick(self): + """Animate the effect""" + super().tick() + + alpha = self.game.alpha - self.start_time + + i = int(alpha // self.frame_time) + if i < len(self.images): + self.set_image(self.images[i]) + else: + self.destroy() + + +class EffectPlayer: + """An object which concerns itself with managing the effects""" + + def __init__(self, game: Game) -> None: + """Initialise the + + :param game: The game which this belongs to + :type game: Game + """ + self.sprites = [] + self.game = game + self.explosion_frames = [] + self.star_image: PhotoImage + + def load_textures(self): + """Load effect textures""" + + self.explosion_frames = [ + self.game.texture_factory.get_image(f"explosion{i+1}") + for i in range(3) + ] + self.star_image = self.game.texture_factory.get_image("star") + + def tick(self): + """Update all effects""" + for sprite in self.sprites: + sprite.tick() + + self.sprites = Sprite.remove_destroyed(self.sprites) + + def create_stars(self): + """Initialise the stars in the background""" + for _ in range(100): + self.create_star(True) + + def create_star(self, new=False): + """Add a star to the background + + :param new: Whether this star should be added at + the top of the screen or anywhere + """ + x = randint(0, self.game.w) + if new: + y = randint(0, self.game.h) + else: + y = -1 + + speed = randint(1, 4) * 0.1 + duration = 2*self.game.h / speed + + star = GameEffect( + self.game, + self.star_image, + duration=int(duration), + momentum=(0, speed) + ) + star.set_pos((x, y)) + + star.send_to_back() + star.show() + + self.sprites.append(star) + + def create_explosion(self, position=(0, 0)): + """Create an explosion effect + + :param position: location of the explosion + """ + for _ in range(randint(1, 3)): + m = ((random()*2)-1, (random()*2)-1) + explosion_sprite = AnimatedEffect( + self.game, self.explosion_frames, frame_time=5, momentum=m) + explosion_sprite.set_pos(position) + explosion_sprite.show() + + self.sprites.append(explosion_sprite) + + def splash_text(self, text, duration=50): + """splash_text. + + :param text: + :param duration: + """ + text_img = Font.load_text(self.game.texture_factory, text) + position = ( + (self.game.w-Font.FONT_WIDTH*len(text)) // 2, + (self.game.h-Font.FONT_SIZE) // 3 + ) + + text_sprite = GameEffect( + self.game, text_img, duration=duration) + text_sprite.set_pos(position) + text_sprite.show() + self.sprites.append(text_sprite) + + +class DamageableSprite(GameSprite): + """Sprite with health points """ + + def __init__(self, game: Game, image_name: str, hp=3): + """Initialise the sprite + + :param game: The game which this belongs to + :type game: Game + :param image_name: The name of the image to use for this sprite + :type image_name: str + :param hp: The number of hit points this sprite spawns with + """ + self.image = game.texture_factory.get_image(image_name) + self.white_image = game.texture_factory.get_image( + f"{image_name}:white") + + self.hp = hp + self.animation_frame = 0 + + super().__init__(game, self.image) + + def damage(self, amount=1): + """Decrease number of hit points by an amount + + :param amount: + """ + if not self.destroyed: + self.hp -= amount + self.animation_frame = 5 + if self.hp <= 0: + self.hp = 0 + self.destroy() + self.game.effect_player.create_explosion(self.get_pos()) + + def tick(self): + """Update the sprite""" + super().tick() + if self.animation_frame > 0: + self.animation_frame -= 1 + if self.animation_frame % 2 == 0: + self.set_image(self.image) + else: + self.set_image(self.white_image) + + +if __name__ == "__main__": + print("!!!") + print("This is not the main file!") + print("Pleae run\n\tpython main.py\ninstead!") + print("!!!") |