J'ai un peu souffert pour mettre en place le système de sauvegarde / restauration du jeu, mais ça y est, ça fonctionne.

Le principe

Sous Python, il existe un module, pickle, qui permet de sauver dans un fichier sous forme de texte les variables utilisées dans le code Python. Ce module, bien que très pratique, présente (en tout cas dans mon cas) deux inconvénients :

  • il n'est pas très rapide, car implémenté en Python pur; heureusement, une implémentation de pickle existe en C, elle s'appelle cPickle, et s'utilise de la même manière;
  • impossible de sauvegarder une surface Pygame avec; peut-être que j'ai raté un truc, mais toutes mes tentatives dans ce sens ont échoué.

Sauvegarder des surfaces Pygame

Ce problème m'a en fait permis de faire le ménage dans mon code, en supprimant des redondances qui n'avaient pas lieu d'être, comme par exemple le fait que je gardait dans les attributs d'un objet la tuile graphique le représentant, plutôt que la référence vers l'indice de la tuile.

Dans d'autres cas je n'ai pas eu le choix, j'ai ainsi du convertir la carte des neuf zones autour du joueur en tableau Numeric avant de le sauver:

La sauvegarde de la carte:

class Sauvegarde:
   def __init__(self, jeu):
   ...
   self.joueur_z33_carte_zone = pygame.surfarray.pixels3d(jeu.partie.joueur.z33_carte_zone)

La restauration:

class Joueur:
   ...
   def Chargement(self, svgde):
      ...
      self.z33_carte_zone = pygame.surfarray.make_surface(svgde.joueur_z33_carte_zone)

A noter que pour sauvegarder le monde, je sauvegarde simplement son nom et le modèle qui s'y rattache (le modèle décrit la fréquence des montagnes, forêts, déserts et zones glacées, la hauteur de la mer et ainsi de suite). Les autres données sauvegardées concernent le joueur (ses caractéristiques et sa connaissance du monde) et la région de 3 zones sur 3 qui l'entoure.

Le code

Le fichier el_persist.py contient le code permettant de sauvegarder et restaurer l'état du jeu.

Il contient également la classe Sauvegarde qui décrit l'ensemble des données à sauvegarder.

class Sauvegarde:
    def __init__(self, jeu):
        """
        Déclaration des attributs à sauvegarder
        """
        # Attributs du monde
        self.monde_nom                  = jeu.partie.monde.nom
        self.monde_nom_modele           = jeu.partie.monde.modele.nom
        self.monde_taille               = jeu.partie.monde.tailletemp
        # Attributs de la partie
        self.partie_messages            = jeu.partie.messages
        # Attributs du joueur
        self.joueur_nom                 = jeu.partie.joueur.nom
        self.joueur_pv                  = jeu.partie.joueur.pv
        ...

La fonction Sauve sauvegarde le jeu en cours. Elle est appelée à la fermeture du programme.

def Sauve(jeu, nom_fichier = None):
    """sauvegarde du jeu"""
    if nom_fichier is None:
        nom = "partie"
    else:
        nom = nom_fichier
    fichier = open(os.path.join('data', 'sauve', nom + '.sav'), 'wb')
    
    # Sauvegarde
    svgde = Sauvegarde(jeu)            
    pickle.dump(svgde, fichier, pickle.HIGHEST_PROTOCOL)
    fichier.close()

La fonction Charge restaure une sauvegarde effectuée précédemment.

def Charge(nom_fichier = None):
    """chargement du jeu"""
    if nom_fichier is None:
        nom = "partie.sav"
    else:
        nom = nom_fichier
        
    # Restauration
    fichier = open(os.path.join('data', 'sauve', nom), 'rb')
    svgde = pickle.load(fichier)
    fichier.close()
    return svgde

A noter

Deux petites choses pour terminer, sur lesquelles je suis tombé et qui peuvent être utiles à celui qui voudrait implémenter pickle dans son code :

  • le paramètre pickle.HIGHEST_PROTOCOL est une nouveauté de Python 2.3 qui permet de sauvegarder les classes de manière optimisées, et de choisir le meilleur format d'écriture entre texte et binaire.
  • open(fichier, 'wb') permet d'écrire le fichier en binaire, et open(fichier, 'rb') de lire un fichier binaire.