[Solved] Save a list of objects on exit of pygame game [closed]


Perfect! Right?

Sadly, no. What you see here (<__main__.Block object at 0x02416B70>) is a typical string representation of a class instance. It’s just a simple string, and there’s no way to convert this string back to an instance of of Block.

I assume you’re still on this game from your last question.

So how do you actually persist the state of the game? The easiest way is to use the standard python module pickle or shelve.

In the following example, I’ll use shelve, because you don’t use a single class to represent the game state:

A “shelf” is a persistent, dictionary-like object. … the values … in a shelf can be essentially arbitrary Python objects … This includes most class instances, recursive data types, and objects containing lots of shared sub-objects. The keys are ordinary strings.

First of all, when we exit the game, we want to save the player and the blocks, so let’s call a new save function when the game is about to exit:

while True:
    ...
    for event in pygame.event.get():
        if event.type == QUIT: 
            save(player, blocklist)
            exit()

The implementation is quite easy (no error handling for brevity):

def save(player, blocks):
    f = shelve.open("save.bin") 
    f['player'] = player
    f['blocks'] = blocks
    f.close()

As you see, using shelve is as easy as using a dict.

Next step is loading our saved data.

player, blocklist = load() or (None, [])

We call a new function load which will either return a tuple of the saved player object and a list of the saved block objects, or None. In case of None, we don’t create a player yet and use an empty list for our blocks.

The implementation is as simple as the save functon:

def load():
    try:
        f = shelve.open("save.bin") 
        return f['player'], f['blocks']
    except KeyError:
        return None
    finally:
        f.close()

And that’s it.

Here’s the complete code:

import pygame,random
from pygame.locals import *
from collections import namedtuple
import shelve

pygame.init()
clock=pygame.time.Clock()
screen=pygame.display.set_mode((640,480))

max_gravity = 100

class Block(object):
    sprite = pygame.image.load("dirt.png").convert_alpha()
    def __init__(self, x, y):
        self.rect = self.sprite.get_rect(centery=y, centerx=x)

class Player(object):
    sprite = pygame.image.load("dirt.png").convert()
    sprite.fill((0,255,0))
    def __init__(self, x, y):
        self.rect = self.sprite.get_rect(centery=y, centerx=x)
        # indicates that we are standing on the ground
        # and thus are "allowed" to jump
        self.on_ground = True
        self.xvel = 0
        self.yvel = 0
        self.jump_speed = 10
        self.move_speed = 8

    def update(self, move, blocks):

        # check if we can jump 
        if move.up and self.on_ground: 
            self.yvel -= self.jump_speed

        # simple left/right movement
        if move.left: self.xvel = -self.move_speed
        if move.right: self.xvel = self.move_speed

        # if in the air, fall down
        if not self.on_ground:
            self.yvel += 0.3
            # but not too fast
            if self.yvel > max_gravity: self.yvel = max_gravity

        # if no left/right movement, x speed is 0, of course
        if not (move.left or move.right):
            self.xvel = 0

        # move horizontal, and check for horizontal collisions
        self.rect.left += self.xvel
        self.collide(self.xvel, 0, blocks)

        # move vertically, and check for vertical collisions
        self.rect.top += self.yvel
        self.on_ground = False;
        self.collide(0, self.yvel, blocks)

    def collide(self, xvel, yvel, blocks):
        # all blocks that we collide with
        for block in [blocks[i] for i in self.rect.collidelistall(blocks)]:

            # if xvel is > 0, we know our right side bumped 
            # into the left side of a block etc.
            if xvel > 0: self.rect.right = block.rect.left
            if xvel < 0: self.rect.left = block.rect.right

            # if yvel > 0, we are falling, so if a collision happpens 
            # we know we hit the ground (remember, we seperated checking for
            # horizontal and vertical collision, so if yvel != 0, xvel is 0)
            if yvel > 0:
                self.rect.bottom = block.rect.top
                self.on_ground = True
                self.yvel = 0
            # if yvel < 0 and a collision occurs, we bumped our head
            # on a block above us
            if yvel < 0: self.rect.top = block.rect.bottom

colliding = False
Move = namedtuple('Move', ['up', 'left', 'right'])

def load():
    try:
        f = shelve.open("save.bin") 
        return f['player'], f['blocks']
    except KeyError:
        return None
    finally:
        f.close()

def save(player, blocks):
    f = shelve.open("save.bin") 
    f['player'] = player
    f['blocks'] = blocks
    f.close()

player, blocklist = load() or (None, [])

while True:
    screen.fill((25,30,90))
    mse = pygame.mouse.get_pos()
    key = pygame.key.get_pressed()

    for event in pygame.event.get():
        if event.type == QUIT: 
            save(player, blocklist)
            exit()

        if key[K_LSHIFT]:
            if event.type==MOUSEMOTION:
                if not any(block.rect.collidepoint(mse) for block in blocklist):
                    x=(int(mse[0]) / 32)*32
                    y=(int(mse[1]) / 32)*32
                    blocklist.append(Block(x+16,y+16))
        else:
            if event.type == pygame.MOUSEBUTTONUP:
                if event.button == 1:
                    to_remove = [b for b in blocklist if b.rect.collidepoint(mse)]
                    for b in to_remove:
                        blocklist.remove(b)

                    if not to_remove:
                        x=(int(mse[0]) / 32)*32
                        y=(int(mse[1]) / 32)*32
                        blocklist.append(Block(x+16,y+16))

                elif event.button == 3:
                    x=(int(mse[0]) / 32)*32
                    y=(int(mse[1]) / 32)*32
                    player=Player(x+16,y+16)

    move = Move(key[K_UP], key[K_LEFT], key[K_RIGHT])

    for b in blocklist:
        screen.blit(b.sprite, b.rect)

    if player:
        player.update(move, blocklist)
        screen.blit(player.sprite, player.rect)

    clock.tick(60)
    pygame.display.flip()

And here you can see loading and saving in action:

enter image description here

Note that you can’t save (or “pickle”) Surfaces this way. In this code, it works because the Surfaces of Player and Block are class variables, not instance variables, and thus don’t get saved to disk. If you want to “pickle” an object with a Surface instance variable, you would have to remove the Surface first (e.g. setting it to None) and load it again (e.g. in the load function).

2

solved Save a list of objects on exit of pygame game [closed]