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

363 lines
10 KiB
Python

from enum import Enum, auto
from os import path, remove
from random import random
from boss_key import BossKey
from cheat_engine import Cheat, CheatEngine, DevModeCheat, InvincibilityCheat
from config import Config
from formation_spawner import FormationSpawner
from game import Game
from hud import GameHud
from leaderboard import Leaderboard
from menu import KeybindsMenu, Menu
from shooter import Shooter, ShooterAttributes
from textures import Textures
class GameState(Enum):
"""Enum of possible game states"""
MAIN_MENU = auto()
GAME = auto()
PAUSED = auto()
END_LEADERBOARD = auto()
LEADERBOARD = auto()
SETTINGS = auto()
class GameSave:
"""Static class for saving and loading game"""
@staticmethod
def save_game(game):
"""Save game state to a file
:param game: Game to save
"""
phase = int.to_bytes(game.formation_spawner.phase, 2, "big")
hp = int.to_bytes(game.player.hp, 1, "big")
score = int.to_bytes(game.score, 8, "big")
with open(Config.SAVE_FILE, "wb") as file:
file.write(phase)
file.write(hp)
file.write(score)
if not game.menu.has_item("Continue"):
game.menu.add_item("Continue", game.restore_game, index=0)
@staticmethod
def load_game(game):
"""load game state from file
:param game: Game to load
"""
with open(Config.SAVE_FILE, "rb") as file:
game.formation_spawner.phase = int.from_bytes(file.read(2), "big")
game.player.hp = int.from_bytes(file.read(1), "big")
game.score = int.from_bytes(file.read(8), "big")
@staticmethod
def remove_save(game):
"""Remove the game save file
:param game:
"""
if path.exists(Config.SAVE_FILE):
remove(Config.SAVE_FILE)
if game.menu.has_item("Continue"):
game.menu.del_item("Continue")
class Player(Shooter):
"""Controllable player object"""
def __init__(self, game: Game):
"""Initialise the player
:param game: The game which this belongs to
:type game: Game
"""
attributes = ShooterAttributes(
cooldown=12,
velocity=-2,
hp=10
)
super().__init__(game, "ship", attributes)
self.set_pos(
((self.game.w - self.w) // 2, (self.game.h - self.h)))
def tick(self):
"""Update this object"""
super().tick()
if self.game.inputs.k_left:
self.move(-1, 0)
if self.game.inputs.k_right:
self.move(1, 0)
if self.game.inputs.k_up:
self.move(0, -1)
if self.game.inputs.k_down:
self.move(0, 1)
# clamp the player to the screen
if self.x < 0:
self.set_pos((0, self.y))
if self.y < 0:
self.set_pos((self.x, 0))
if self.x + self.w > self.game.w:
self.set_pos((self.game.w - self.w, self.y))
if self.y + self.h > self.game.h:
self.set_pos((self.x, self.game.h - self.h))
if self.game.inputs.k_action:
self.shoot()
class ShooterGame(Game):
"""Game with menus and enemies to be shot at """
def __init__(self):
"""Initialise the game"""
super().__init__()
self.state = GameState.MAIN_MENU
self.death_time = -1
self.paused_frame = 0
# load textures
Textures.load_textures(self.texture_factory)
self.effect_player.load_textures()
self.effect_player.create_stars()
self.state = GameState.MAIN_MENU
self.formation_spawner = FormationSpawner(self)
self.player = Player(self)
# make the game hud last to make sure its ontop
self.game_hud = GameHud(self)
# create the leaderboard sprites
self.leaderboard = Leaderboard(self)
self.leaderboard.callback = self.show_menu
# make the settings menu
self.settings_menu = KeybindsMenu(self, "Keybinds")
for name, value in vars(self.inputs.settings).items():
label = self.settings_menu.get_label(name, value)
self.settings_menu.add_item(
label,
self.settings_menu.get_set_keybind(name)
)
self.settings_menu.add_item("Return", self.show_menu)
# make the main menu
self.menu = Menu(self, "Main Menu")
if path.exists(Config.SAVE_FILE):
self.menu.add_item("Continue", self.restore_game)
self.menu.add_item("New Game", self.start_game)
self.menu.add_item("Leaderboard", self.show_leaderboard)
self.menu.add_item("Settings", self.show_settings)
self.menu.show()
# make the pause menu
self.pause_menu = Menu(self, "Game Paused")
self.pause_menu.add_item("Resume", self.resume_game)
self.pause_menu.add_item("Save", lambda: (
self.save_game(),
self.effect_player.splash_text("Game saved"),
self.resume_game())
)
self.pause_menu.add_item("Exit", self.show_menu)
# initialise cheats
self.cheat_engine = CheatEngine(self)
self.cheat_engine.add_cheat(
Cheat(self, list("test"),
(lambda: self.effect_player.splash_text("test ok"))
))
self.cheat_engine.add_cheat(
Cheat(self, [
"Up",
"Up",
"Down",
"Down",
"Left",
"Right",
"Left",
"Right",
"b",
"a",
],
(lambda: [self.formation_spawner.spawn_rectangle()
for _ in range(20)])
))
self.cheat_engine.add_cheat(DevModeCheat(self, [
"Left",
"Right",
"Left",
"Right",
"Escape",
"d",
"Up",
"t",
"b",
"b",
"a",
"b",
"s"
]))
self.cheat_engine.add_cheat(InvincibilityCheat(self, list("xyzzy")))
self.boss_key = BossKey(self, self.pause_game)
def tick(self):
"""Update the game state"""
if self.state != GameState.PAUSED:
super().tick()
if random() > 0.9:
self.effect_player.create_star()
if self.state == GameState.MAIN_MENU:
self.menu.tick()
elif self.state == GameState.SETTINGS:
self.settings_menu.tick()
elif self.state == GameState.GAME:
self.tick_game()
elif self.state == GameState.PAUSED:
self.alpha = self.paused_frame
self.pause_menu.tick()
elif self.state == GameState.END_LEADERBOARD:
self.leaderboard.tick()
elif self.state == GameState.LEADERBOARD:
self.leaderboard.tick()
def tick_game(self):
"""Update the game during game play"""
self.game_hud.tick()
self.formation_spawner.tick()
self.player.tick()
if self.player.destroyed:
if self.death_time == -1:
self.death_time = self.alpha
self.effect_player.splash_text("GAME OVER", 100)
elif self.alpha - self.death_time > 100:
self.show_score()
if self.inputs.k_pause:
self.pause_game()
def pause_game(self):
"""Set the game to paused state"""
if self.state == GameState.GAME:
self.state = GameState.PAUSED
self.paused_frame = self.alpha
self.pause_menu.show()
def resume_game(self):
"""Resume the game from paused state"""
self.state = GameState.GAME
self.pause_menu.hide()
def start_game(self):
"""Start a new game"""
self.state = GameState.GAME
GameSave.remove_save(self)
self.menu.hide()
self.pause_menu.hide()
self.formation_spawner.phase = -1
self.clear_all()
self.score = 0
self.player = Player(self)
self.formation_spawner.next_phase()
self.player.show()
self.game_hud.show()
self.death_time = -1
def show_leaderboard(self):
"""Show the game's leaderboard"""
self.state = GameState.LEADERBOARD
self.menu.hide()
self.pause_menu.hide()
self.leaderboard.editing = False
self.leaderboard.populate_entries()
self.leaderboard.start_animation()
self.leaderboard.show()
def show_score(self):
"""Allow the user to enter their name into the leaderboard"""
self.state = GameState.END_LEADERBOARD
self.clear_all()
self.game_hud.hide()
self.leaderboard.editing = True
self.leaderboard.populate_entries()
self.leaderboard.start_animation()
self.leaderboard.show()
GameSave.remove_save(self)
def show_menu(self):
"""Show the main menu"""
self.state = GameState.MAIN_MENU
self.clear_all()
self.leaderboard.hide()
self.game_hud.hide()
self.player.hide()
self.pause_menu.hide()
self.settings_menu.hide()
self.menu.show()
def clear_all(self):
"""Remove all the associated game objects"""
self.formation_spawner.clear_all()
self.player.destroy()
def restore_game(self):
"""Restore the game's state from file"""
self.state = GameState.GAME
self.menu.hide()
self.pause_menu.hide()
self.clear_all()
self.player = Player(self)
self.death_time = -1
GameSave.load_game(self)
self.formation_spawner.start_phase()
self.game_hud.show()
self.player.show()
def save_game(self):
"""Save the game's state to a file"""
GameSave.save_game(self)
def show_settings(self):
"""Show the keybind setting menu"""
self.state = GameState.SETTINGS
self.menu.hide()
self.settings_menu.show()