So I have a list of defined classes that gets exported on the exit of the program.. and it looks like this:
<__main__.Block object at 0x02416B70>,
<__main__.Block object at 0x02416FF0>,
<__main__.Block object at 0x0241C070>,
<__main__.Block object at 0x0241C0D0>,
<__main__.Block object at 0x0241C130>,
<__main__.Block object at 0x0241C190>,
<__main__.Block object at 0x02416DF0>,
<__main__.Block object at 0x0241C250>,
<__main__.Block object at 0x0241C2B0>,
<__main__.Block object at 0x0241C310>,
<__main__.Block object at 0x0241C370>,
<__main__.Block object at 0x0241C3D0>,
<__main__.Block object at 0x0241C430>,
<__main__.Block object at 0x0241C490>,
<__main__.Block object at 0x0241C4F0>,
<__main__.Block object at 0x0241C550>,
<__main__.Block object at 0x0241C5B0>,
<__main__.Block object at 0x0241C610>
Perfect! Right? Now I should easily be able to convert that to a list..
So I use this:
x=x.split(",")
And it converts it to a list yes, but it turns the classes into strings! Making them un-usable.
Basically what I need is to "suspend" the state of the game within a file when it is closed, and then reload it upon opening it.
So how can I do this without converting the class names to strings?
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:
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).