Basic save/load functionality
This commit is contained in:
parent
e82d2e5b49
commit
5d0915b82b
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
59
main.py
59
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
|
||||
|
||||
|
||||
|
BIN
menu_background.png
Normal file
BIN
menu_background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
119
setup_game.py
Normal file
119
setup_game.py
Normal file
@ -0,0 +1,119 @@
|
||||
"""Handle the loading and initialization of game sessions."""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import lzma
|
||||
import pickle
|
||||
import traceback
|
||||
from typing import Optional
|
||||
|
||||
import tcod
|
||||
|
||||
import color
|
||||
from engine import Engine
|
||||
import entity_factories
|
||||
import input_handlers
|
||||
from procgen import generate_dungeon
|
||||
|
||||
# Load the background image and remove the alpha channel.
|
||||
background_image = tcod.image.load("menu_background.png")[:, :, :3]
|
||||
|
||||
|
||||
def new_game() -> 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
|
Loading…
Reference in New Issue
Block a user