From 5d0915b82b8f7e421cddcfa3a84fe9b2fd759a18 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 18 Jan 2022 14:37:48 -0500 Subject: [PATCH] Basic save/load functionality --- .gitignore | 3 ++ engine.py | 8 +++ input_handlers.py | 27 ++++++++++ main.py | 59 +++++++--------------- menu_background.png | Bin 0 -> 10619 bytes setup_game.py | 119 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 menu_background.png create mode 100644 setup_game.py diff --git a/.gitignore b/.gitignore index 45c9f53..41b017e 100644 --- a/.gitignore +++ b/.gitignore @@ -264,3 +264,6 @@ dmypy.json cython_debug/ # End of https://www.toptal.com/developers/gitignore/api/python,jetbrains+all,macos + +# Don't save game saves to git! +savegame.sav diff --git a/engine.py b/engine.py index deec79c..9f9127b 100644 --- a/engine.py +++ b/engine.py @@ -1,5 +1,7 @@ from __future__ import annotations +import lzma +import pickle from typing import TYPE_CHECKING from tcod.console import Console @@ -54,3 +56,9 @@ class Engine: ) render_names_at_mouse_location(console, x=21, y=44, engine=self) + + def save_as(self, filename: str) -> None: + """Save this Engine instance as a compressed file.""" + save_data = lzma.compress(pickle.dumps(self)) + with open(filename, "wb") as f: + f.write(save_data) diff --git a/input_handlers.py b/input_handlers.py index 1d17a2b..50814af 100644 --- a/input_handlers.py +++ b/input_handlers.py @@ -89,6 +89,33 @@ class BaseEventHandler(tcod.event.EventDispatch[ActionOrHandler]): raise SystemExit() +class PopupMessage(BaseEventHandler): + """Display a popup text window.""" + + def __init__(self, parent_handler: BaseEventHandler, text: str): + self.parent = parent_handler + self.text = text + + def on_render(self, console: tcod.Console) -> None: + """Render the parent and dim the result, then print the message on top.""" + self.parent.on_render(console) + console.tiles_rgb["fg"] //= 8 + console.tiles_rgb["bg"] //= 8 + + console.print( + console.width // 2, + console.height // 2, + self.text, + fg=color.white, + bg=color.black, + alignment=tcod.CENTER, + ) + + def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[BaseEventHandler]: + """Any key returns to the parent handler.""" + return self.parent + + class EventHandler(BaseEventHandler): def __init__(self, engine: Engine): self.engine = engine diff --git a/main.py b/main.py index 238fe9f..7f61b0b 100755 --- a/main.py +++ b/main.py @@ -1,31 +1,25 @@ #!/usr/bin/env python3 -import copy import traceback import tcod import color -from engine import Engine -import entity_factories import exceptions import input_handlers -from procgen import generate_dungeon +import setup_game + + +def save_game(handler: input_handlers.BaseEventHandler, filename: str) -> None: + """If the current event handler has an active Engine then save it.""" + if isinstance(handler, input_handlers.EventHandler): + handler.engine.save_as(filename) + print("Game saved.") def main() -> None: screen_width = 80 screen_height = 50 - map_width = 80 - map_height = 43 - - room_max_size = 10 - room_min_size = 6 - max_rooms = 30 - - max_monsters_per_room = 2 - max_items_per_room = 2 - tileset = tcod.tileset.load_tilesheet( "dejavu10x10_gs_tc.png", 32, @@ -33,28 +27,7 @@ def main() -> None: tcod.tileset.CHARMAP_TCOD ) - player = copy.deepcopy(entity_factories.player) - - engine = Engine(player) - - engine.game_map = generate_dungeon( - max_rooms, - room_min_size, - room_max_size, - map_width, - map_height, - max_monsters_per_room, - max_items_per_room, - engine, - ) - engine.update_fov() - - engine.message_log.add_message( - "Hello and welcome, adventurer, to yet another dungeon!", - color.welcome_text - ) - - handler: input_handlers.BaseEventHandler = input_handlers.MainGameEventHandler(engine) + handler: input_handlers.BaseEventHandler = setup_game.MainMenu() with tcod.context.new_terminal( screen_width, @@ -67,25 +40,29 @@ def main() -> None: try: while True: root_console.clear() - engine.event_handler.on_render(console=root_console) + handler.on_render(console=root_console) context.present(root_console) try: for event in tcod.event.wait(): context.convert_event(event) - engine.event_handler.handle_events(event) + handler = handler.handle_events(event) except Exception: # Handle exceptions in game. traceback.print_exc() # Print error to stderr. # Then print the error to the message log. - engine.message_log.add_message(traceback.format_exc(), color.error) + if isinstance(handler, input_handlers.EventHandler): + handler.engine.message_log.add_message( + traceback.format_exc(), + color.error, + ) except exceptions.QuitWithoutSaving: raise except SystemExit: # Save and quit. - # TODO: Add the save function here + save_game(handler, "savegame.sav") raise except BaseException: # Save on any other unexpected exception. - # TODO: Add the save function here + save_game(handler, "savegame.sav") raise diff --git a/menu_background.png b/menu_background.png new file mode 100644 index 0000000000000000000000000000000000000000..70b9c2059be86e70c1c88ce14ef9773c1702a41d GIT binary patch literal 10619 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 Engine: + """Return a brand new game session as an Engine instance.""" + map_width = 80 + map_height = 43 + + room_max_size = 10 + room_min_size = 6 + max_rooms = 30 + + max_monsters_per_room = 2 + max_items_per_room = 2 + + player = copy.deepcopy(entity_factories.player) + + engine = Engine(player) + + engine.game_map = generate_dungeon( + max_rooms=max_rooms, + room_min_size=room_min_size, + room_max_size=room_max_size, + map_width=map_width, + map_height=map_height, + max_monsters_per_room=max_monsters_per_room, + max_items_per_room=max_items_per_room, + engine=engine, + ) + engine.update_fov() + + engine.message_log.add_message( + "Hello and welcome, adventurer, to yet another dungeon!", + color.welcome_text + ) + + return engine + + +def load_game(filename: str) -> Engine: + """Load an engine instance from a file.""" + with open(filename, "rb") as f: + engine = pickle.loads(lzma.decompress(f.read())) + assert isinstance(engine, Engine) + + return engine + + +class MainMenu(input_handlers.BaseEventHandler): + """Handle the main menu rendering and input.""" + + def on_render(self, console: tcod.Console) -> None: + """Render the main menu on a background image.""" + console.draw_semigraphics(background_image, 0, 0) + + console.print( + console.width // 2, + console.height // 2 - 4, + "TOMBS OF THE ANCIENT KINGS", + fg=color.menu_title, + alignment=tcod.CENTER, + ) + console.print( + console.width // 2, + console.height - 2, + "By Timothy J. Warren", + fg=color.menu_title, + alignment=tcod.CENTER, + ) + + menu_width = 24 + for i, text in enumerate([ + "[N] Play a new game", + "[C] Continue last game", + "[Q] Quit" + ]): + console.print( + console.width // 2, + console.height // 2 - 2 + i, + text.ljust(menu_width), + fg=color.menu_text, + bg=color.black, + alignment=tcod.CENTER, + bg_blend=tcod.BKGND_ALPHA(64), + ) + + def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[input_handlers.BaseEventHandler]: + if event.sym in (tcod.event.K_q, tcod.event.K_ESCAPE): + raise SystemExit() + elif event.sym == tcod.event.K_c: + try: + return input_handlers.MainGameEventHandler(load_game("savegame.sav")) + except FileNotFoundError: + return input_handlers.PopupMessage(self, "No saved game to load.") + except Exception as exc: + traceback.print_exc() # Print to stderr. + return input_handlers.PopupMessage(self, f"Failed to load save:\n{exc}") + elif event.sym == tcod.event.K_n: + return input_handlers.MainGameEventHandler(new_game()) + + return None