The pickle module implements binary protocols for serializing and de-serializing a Python object structure.
“Pickling” or “serialization” is the process of converting an object to a byte stream that can be stored on a disk or sent over a network. “Unpickling” or “deserialization” is the inverse operation, it is the process of converting a byte stream back into a Python object.
import pickle
class Person: # We are going to create a simple class named Person with three properties: name, age, and sex.
def __init__(self, name="Joe", age=18, sex="man"):
self.name = name
self.age = age
self.sex = sex
def save(self): # It saves our object.
with open('person.pkl', 'wb') as f:
pickle.dump(self.name, f) # The name is pickled or serialized into a binary file (person.pkl).
pickle.dump(self.age, f) # The dump function writes the pickled representation of the name, age, and sex to the file object.
pickle.dump(self.sex, f)
def restore(self): # It restores our object.
with open('person.pkl', 'rb') as f:
self.name = pickle.load(f) # We unpickle or deserialize the name from the same binary file.
self.age = pickle.load(f) # The load function reads the pickled representation of our object's attributes or properties from the file object and returns them.
self.sex = pickle.load(f)
def __str__(self):
return "My name is " + self.name + ". My age is " + str(self.age) + " years old. " + \
"I am a " + self.sex + "."
def getting_old(self):
self.age += 1
if __name__ == '__main__':
Anne = Person("Anne", 23, "woman")
Anne.save()
Anne.getting_old()
print(Anne)
Bew = Person()
print(Bew)
Bew.restore()
print(Bew)
My name is Anne. My age is 24 years old. I am a woman. My name is Joe. My age is 18 years old. I am a man. My name is Anne. My age is 23 years old. I am a woman.
We are going to use Python’s pickle module to allow the user to save and restore his current Connect-4 game session.
import pickle
class Game:
def __init__(self):
[...]
self.movements = [] # We are going to save all the game's movements in a list.
self.F1_KEY = False # We are going to use the key "F1", too. The user will be able to access the main menu at any given moment and save/restore the current game session.
# The method game_loop controls the overall flow for the entire game program. There are three tasks: 1. Check all events. 2. Process these events and take action accordingly. 3. Draw the current state of the game so the player can see what is going on.
def game_loop(self):
while self.playing:
[...]
if self.MOUSEBUTTONDOWN: # The mouse button is released. The player has already decided his or her next move.
pygame.draw.rect(self.screen, BLACK, (0, 0, Board.WIDTH, Board.SQUARESIZE))
if self.player.get_turn():
First, we calculate the column where the user has released the mouse. Second, if the column is a valid movement, we find out which is the row where the piece is to be placed, and finally we save or “drop” the piece into the board and add it to the list of movements (self.movements.append(col)).
col = int(math.floor(self.posx/Board.SQUARESIZE))
if self.board.is_valid_location(col):
row = self.board.get_next_open_row(col)
self.movements.append(col)
self.board.drop_piece(row, col, PLAYER_PIECE)
[...]
# If the turn belongs to the AI player and the game is not over yet. This level of indentation corresponds to "if self.MOUSEBUTTONDOWN:"
if self.ai.get_turn() and not self.game_over:
# We use the board's minimax method to find out the best movement.
col, minimax_score = self.board.minimax(self.depth, -math.inf, math.inf, True)
# If the column is a valid movement, we find out which is the row where the piece is to be placed, save or "drop" the piece into the board, and **add it to the list of movements** (_self.movements.append(col)_).
if self.board.is_valid_location(col):
row = self.board.get_next_open_row(col)
self.movements.append(col)
self.board.drop_piece(row, col, AI_PIECE)
Pickle is used for serializing and de-serializing Python objects. Serialization refers to the process of converting an object to a byte stream that can be stored on a disk or sent over a network. Later on, it can then be retrieved and de-serialized back to a Python object.
class Game:
[...]
def save_game(self): # Save our game current session or, more specifically, our list of movements.
with open('connect.pkl', 'wb') as f: # The list is pickled or serialized into a binary file (connect.pkl).
pickle.dump(self.movements, f) # The dump function writes the pickled representation of the list to the file object.
def restore_game(self): # Restore a saved game session.
with open('connect.pkl', 'rb') as f: # We unpickle or deserialize the list from the same binary file.
self.movements = pickle.load(f) # The load function reads the pickled representation of our list from the file object and returns the "reconstituted" list.
self.board = Board(self.screen, self.debug, self.depth) # We initialize our game.
self.posx = 0
self.player = Player(PLAYER, True)
self.ai = Player(AI, False)
i = 0
for movement in self.movements: # We iterate over our list of movements and place each piece in its place.
if i % 2 == 0:
row = self.board.get_next_open_row(movement)
self.board.drop_piece(row, movement, PLAYER_PIECE)
self.player.set_turn(False)
self.ai.set_turn(True)
else:
row = self.board.get_next_open_row(movement)
self.board.drop_piece(row, movement, AI_PIECE)
self.ai.set_turn(False)
self.player.set_turn(True)
i += 1
self.draw()
def check_events(self): # Pygame handles all its event messaging through an event queue. This method checks all these events.
for event in pygame.event.get(): # It iterates over the events of the event queue.
if event.type == pygame.QUIT:
self.running, self.playing = False, False
self.curr_menu.run_display = False
sys.exit()
if event.type == pygame.MOUSEMOTION:
self.MOUSEMOTION = True
self.posx = event.pos[0]
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RETURN:
self.START_KEY = True
if event.key == pygame.K_BACKSPACE:
self.BACK_KEY = True
if event.key == pygame.K_DOWN:
self.DOWN_KEY = True
if event.key == pygame.K_UP:
self.UP_KEY = True
if event.key == pygame.K_F1:
If the user presses “F1”, we stop playing (self.playing = False), and then, we display the menu.
self.F1_KEY = True
self.running, self.playing = True, False
self.curr_menu.run_display = True
if event.type == pygame.MOUSEBUTTONDOWN:
self.MOUSEBUTTONDOWN = True
self.posx = event.pos[0]
def reset_keys(self): # Reset all the keys
self.UP_KEY, self.DOWN_KEY, self.START_KEY, self.BACK_KEY = False, False, False, False
self.MOUSEBUTTONDOWN, self.F1_KEY = False, False
self.MOUSEMOTION = False
Our menu presents some small changes:
class MainMenu(Menu):
def __init__(self, game):
Menu._init__(self, game)
self.state = "Start" # A property that indicates the menu's state or the user's selected option
self.startx, self.starty = self.mid_w, self.mid_h + 30
self.optionsx, self.optionsy = self.mid_w, self.mid_h + 80
self.savex, self.savey = self.mid_w, self.mid_h + 130
self.restorex, self.restorey = self.mid_w, self.mid_h + 180
self.creditsx, self.creditsy = self.mid_w, self.mid_h + 230
self.cursor_rect.midtop = (self.startx + self.offset, self.starty)
This is the first screen/menu which appears on the game. It is the initial “state” of our game.
def display_menu(self):
while self.run_display: # self.run.display is set to True by the Menu's (the parent class) constructor.
self.game.check_events() # We check all the events.
self.process_events() # And process the events.
self.game.screen.fill((0, 0, 0)) # We draw the Main Menu.
self.game.draw_text('Main Menu', 20, self.game.board.WIDTH / 2, self.game.board.HEIGHT / 3 - 20)
self.game.draw_text("Start Game", 20, self.startx, self.starty)
self.game.draw_text("Difficulty", 20, self.optionsx, self.optionsy)
self.game.draw_text("Save Game", 20, self.savex, self.savey)
self.game.draw_text("Restore Game", 20, self.restorex, self.restorey)
self.game.draw_text("Credits", 20, self.creditsx, self.creditsy)
self.draw_cursor()
self.process_events() # Finally, we update the menu.
def move_cursor(self): # If the user presses the arrows up and/or down, we need to change the "state" property and the cursor's position.
if self.game.DOWN_KEY:
if self.state == 'Start':
self.cursor_rect.midtop = (self.optionsx + self.offset, self.optionsy)
self.state = 'Difficulty'
elif self.state == 'Difficulty':
self.cursor_rect.midtop = (self.savex + self.offset, self.savey)
self.state = 'Save'
elif self.state == 'Save':
self.cursor_rect.midtop = (self.restorex + self.offset, self.restorey)
self.state = 'Restore'
elif self.state == 'Restore':
self.cursor_rect.midtop = (self.creditsx + self.offset, self.creditsy)
self.state = 'Credits'
elif self.state == 'Credits':
self.cursor_rect.midtop = (self.startx + self.offset, self.starty)
self.state = 'Start'
elif self.game.UP_KEY:
if self.state == 'Start':
self.cursor_rect.midtop = (self.creditsx + self.offset, self.creditsy)
self.state = 'Credits'
elif self.state == 'Difficulty':
self.cursor_rect.midtop = (self.startx + self.offset, self.starty)
self.state = 'Start'
elif self.state == 'Save':
self.cursor_rect.midtop = (self.optionsx + self.offset, self.optionsy)
self.state = 'Difficulty'
elif self.state == 'Restore':
self.cursor_rect.midtop = (self.savex + self.offset, self.savey)
self.state = 'Save'
elif self.state == 'Credits':
self.cursor_rect.midtop = (self.restorex + self.offset, self.restorey)
self.state = 'Restore'
def process_events(self):
self.move_cursor()
if self.game.START_KEY: # If the user has pressed pygame.K_RETURN, the enter key...
if self.state == 'Start': # and the "state" is 'Start',
self.game.playing = True # we set the property "playing" of our instance of the class Game (self.game) to True so he can start playing the game.
self.game.MOUSEMOTION = True
self.game.posx = 0
elif self.state == 'Save': # If the user has pressed pygame.K_RETURN and the "state" is 'Save',
self.game.save_game() # we save the game current session.
elif self.state == 'Restore': # If the user has pressed pygame.K_RETURN and the "state" is 'Restore',
self.game.playing = True # we restore the game (self.game.restore_game()) and set the property "playing" of our instance of the class Game (self.game) to True so he can start playing the "restored" game
self.game.MOUSEMOTION = True
self.game.posx = 0
self.game.restore_game()
elif self.state == 'Difficulty':
self.game.curr_menu = self.game.difficulty # We change the "curr_menu" property of our instance of the class Game (self.game) to self.game.difficulty, an instance of the class DifficultyMenu.
elif self.state == 'Credits':
self.game.curr_menu = self.game.credits # We change the "curr_menu" property of our instance of the class Game (self.game) to self.game.credits, an instance of the class CreditsMenu.
self.run_display = False # Finally, we update the display property to False.