summaryrefslogtreecommitdiff
path: root/game.py
diff options
context:
space:
mode:
Diffstat (limited to 'game.py')
-rw-r--r--game.py310
1 files changed, 310 insertions, 0 deletions
diff --git a/game.py b/game.py
new file mode 100644
index 0000000..2724503
--- /dev/null
+++ b/game.py
@@ -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("!!!")