Merry Christmas! This has been one of the most requested features for me to add to the tutorial, so here goes. While it modifies the free tutorial code, the concepts here can also apply to the book.
Saving and loading data from code to a binary or text format is referred to as “serialization” and “deserialization”. When different computer systems need to share data, this is often done with XML or JSON. If that’s not a concern, Python can use the builtin pickle library.
Saving the game
First, we’ll need to create the action for the player. Let’s start by adding the action to the player. First add
import pickle to the top of the file, then add this method:
def save_and_exit(self): pickle.dump(self, open( "saved_player.p", "wb" )) pickle.dump(world._world, open( "saved_world.p", "wb" )) print("Game saved!") exit()
pickle.dump method converts an object to a binary format for Python. The first parameter is the object to save and the second parameter specifies where to save the object. We need to create a save for both the world itself (tiles) and the player.
Next, go to the actions file and create an action for this method:
class SaveAndExit(Action): def __init__(self): super().__init__(method=Player.save_and_exit, name="Save and Exit", hotkey='x')
Finally, we have to make sure this shows up as an option for the player, so add
moves.append(actions.SaveAndExit()) to the end of the list of actions in
def available_actions(self): """Returns all of the available actions in this room.""" moves = self.adjacent_moves() moves.append(actions.ViewInventory()) moves.append(actions.SaveAndExit()) return moves
If you run the game now, you can save the game and you should notice two files appear in the directory of your game code.
Loading the game
To load the game, we basically have to do the same process in reverse. So we’ll check to see if the save files exist, and if they do, transform the data into the game objects.
Since we need to handle new games and saved games, I renamed the
play method to
game_loop and created a new
def play(saved_world=None, saved_player=None): if saved_world and saved_player: world._world = saved_world player = saved_player else: world.load_tiles() player = Player() game_loop(player) def game_loop(player): # same code that used to be in "play"
play method has optional parameters for the saved objects. If they are present, we manually set the world and player to those parameters. Otherwise, we default to creating a new world and player.
Next add these imports:
from pathlib import Path import pickle
Now we’ll create a method that checks to see if the files are present, and if so load them and pass them into our new
play method. Here we’ll use
pickle.load which reads in a file and unpacks it into an object.
def check_for_save(): if Path("saved_player.p").is_file() and Path("saved_world.p").is_file(): saved_world = pickle.load(open("saved_world.p", "rb")) saved_player = pickle.load(open("saved_player.p", "rb")) save_exists = True else: save_exists = False
I wanted to give the player the option of loading the saved game or starting a new one, so I added this code too:
if save_exists: valid_input = False while not valid_input: load = input("Saved game found! Do you want to load the game? Y/N ") if load in ['Y','y']: play(saved_world, saved_player) valid_input = True elif load in ['N','n']: play() valid_input = True else: print("Invalid choice.") else: play()
Notice how we use the two variants of
play, sometimes with parameters and sometimes without.
Last, we have to change the entry point of our code:
if __name__ == "__main__": check_for_save()
Now when you play the game, you should see it pick up your save files and load the game at the spot when you saved it.
As always, the full code is on GitHub!