Programme girafe_affichage.py


# -*- coding: cp1252 -*-
import pygame
import random
import copy
import time

class Bord :
    """définition d'un bord ou côté d'une pièce, il possède :
       - partie : une partie de la girafe (haut ou bas)
       - une couleur : la couleur de cette partie, (orange, violet, bleu clair, bleu foncé)
    """
    
    def __init__ (self, definition) :
        """definition est une chaîne de 2 caractères qui définit un bord, exemples :
            HO pour haut orange
            BB pour bas bleu
            BV pour bas violet
            HM pour haut mauve
        """
        
        if definition [0] == "H" : self.partie = "haut" 
        elif definition [0] == "B" : self.partie = "bas"
        else : self.partie = definition + "###"
        if definition [1] == "O" : self.couleur = "orange"
        elif definition [1] == "B" : self.couleur = "bleu clair"
        elif definition [1] == "M" : self.couleur = "violet"
        elif definition [1] == "V" : self.couleur = "bleu fonce"
        else : self.couleur = definition + "##"
        
    def __str__ (self) :
        """cette méthode est appelée lorsqu'on exécute l'instruction print
        avec un objet de type Bord"""
        s = self.partie + " " + self.couleur
        return s
        
    def compatible (self, bord) :
        """dit si deux bords sont compatibles, c'est a dire
        de la meme couleur et de partie differente"""
        return self.couleur == bord.couleur and self.partie != bord.partie

        
class Piece :
    """définition d'une pièce du puzzle, celle-ci inclut :
      - bord : cette liste contient quatre objet de type Bord, cette liste ne changera plus
      - position : c'est la position de la pièce dans le puzzle, ce qui nous intéresse,
                      c'est la position finale de la pièce dans le puzzle, cette information 
                      va donc bouger au fur et à mesure que nous allons essayer de 
                      résoudre le puzzle
      - orientation : de même que pour la position, une pièce peut être tournée sans changer de
                        position, c'est le résultat final qui nous intéresse
                        
    pour l'affichage, on ajoute deux informations :
      - name : le nom de l'image de la pièce
      - image : c'est la représentation de l'image dans la mémoire de l'ordinateur pour le module pygame
    """
    
    def __init__ (self, name, definition, position, numero) :
        """ on définit la pièce
           - name : nom de l'image représentant la pièce
           - definition : chaîne de 8 caractères, c'est une suite de 4 x 2 caractères définissant chaque bord,
                             voir la classe bord pour leur signification
           - position : c'est la position initiale de la pièce, on suppose que l'orientation est nulle pour commencer
        
        à partir de ces informations, on construit :
          - image : c'est la représentation en mémoire de l'image de la pièce
          - bord : c'est une liste qui définit les 4 bords de la pièce
          - orientation : c'est l'orientation de la pièce, au début de la résolution, elle est nulle
        """
        self.name = name 
        image = pygame.image.load (name)
        self.image = pygame.transform.scale (image, (250,250))
        s = self.image.get_size ()
        self.image_petite = pygame.transform.scale (self.image,  (int (s [0] * 0.7), int (s [1] * 0.7)) )
        self.bord = []
        for i in range (0,4) :
            self.bord.append ( Bord (definition [i*2:i*2+2] ) )
        self.orientation = 0
        self.position = position
        self.numero = numero
        
    def __str__ (self) :
        """définition ce qu'on doit afficher lorsqu'on exécute l'instruction print avec un objet de type Piece"""
        s = str (self.position) + " : "
        for b in self.bord :
            s += str (b) + " - "
        s += " orientation " + str (self.orientation)
        s += " numero " + str (self.numero)
        return s
        
    def affiche (self, screen, position) :
        """affiche la pièce à l'écran en tenant compte de sa position et de son orientation"""
        if "darker" in self.__dict__ and self.darker :
            position = ( position [0] + 20, position [1] + 20 )
            image = pygame.transform.rotate (self.image_petite, self.orientation)
            screen.blit (image, position)
        else :
            image = pygame.transform.rotate (self.image, self.orientation)
            screen.blit (image, position)
        
    def bord_angle (self, angle, orientation = None) :
        """retourne le bord connaissant l'orientation de la pièce,
        le bord demandé est celui correspondant à :
           - 0  bord droit
           - 90 bord haut
           - 180 bord gauche
           - 270 bord bas"""
        if orientation == None : return self.bord_angle (angle, self.orientation)
        else : 
            dif = (angle - orientation + 360) % 360 / 90
            return self.bord [dif]
        
    def voisin_possible (self, p, a) :
        """détermine si la pièce self peut être voisine avec la pièce p tournée de l'angle a"""
        d = p.position - self.position
        if abs (d) == 1 :
            # voisin en x
            if d == 1 :
                a1 = 0
                a2 = a1 + 180
            else :
                a1 = 180
                a2 = 0
        elif abs (d) == 3 :
            # voisin en y
            if d == 1 :
                a1 = 90
                a2 = 270
            else :
                a1 = 270
                a2 = 90
        else :
            # pas voisin
            return False
        
        b1 = self.bord_angle (a1)
        b2 = p.bord_angle (a2, a)
        return b1.compatible (b2)
        
        
class Puzzle :
    """définition d'une classe puzzle, elle contient simplement une liste de 9 pièces dont les positions sont :
    1 2 3
    4 5 6
    7 8 9
    et les orientations choisies dans l'ensemble { 0,90,180,270 }
    """
    
    
    def __init__ (self, dir) :
        """on définit le puzzle à partir des informations contenus dans le répertoire dir qui doit contenir :
        - 9 images appelées piece0.png, ..., piece1.png
        - un fichier definition.txt contenant la définition de chacun des 4 bords de chacune des 9 pièces
                            HOBBBVHM
                            HOBBBVHM
                            HBBMBVHO
                            HMBBBVHB
                            BMBOHBHV
                            HVBMBOHM
                            BMBVHBHO
                            HVHMBBBO
                            BMHOHVBB        
        """
        
        # on lit définition.txt
        f = open (dir + "/definition.txt")
        bo = f.readlines ()
        f.close ()
        
        # on définit chaque pièce
        self.piece = []
        for i in range (1,10) :
            name = dir + "/piece" + str (i) + ".png"
            d = bo [i-1]
            p = Piece (name, d, 0, i)
            self.piece.append ( p )
            
    def __str__ (self) :
        """ce qu'on doit afficher lorsqu'on exécute l'instruction print avec un objet de type Puzzle"""
        s = """1 2 3
4 5 6
7 8 9
"""
        for p in self.piece :
            s += str (p) + "\n"
        return s
            
    def pixel (self, position) :
        """retourne en fonction de la position (1 à 9) de la pièce sa position sur l'écran,
        soit deux coordonnées"""
        p       = position - 1
        ligne   = p / 3
        colonne = p % 3
        return (colonne * 250, ligne * 250)
        
    def affiche (self, screen, petite = False) :
        """affiche les pièces sur l'écran, 
        en plus petit pour celles qui ne sont pas encore placées"""
        screen.fill ((0,0,0))    
        free = [0 for i in self.piece]
        for p in self.piece :
            if p.position > 0 :
                p.darker = False
                p.affiche (screen, self.pixel (p.position))
                free [p.position-1] = 1
                
        if petite :
            em = []
            for i in range (0, len (free)) :
                if free [i] == 0 : em.append (i+1)
            i = 0
            for p in self.piece :
                if p.position == 0 :
                    p.darker = True
                    p.affiche (screen, self.pixel (em [i]))
                    i += 1
                    
        pygame.display.flip ()
            
    def meilleure_piece (self, free, pos) :
        """retourne la prochaine pièce à placer sur le puzzle,
        dans un premier temps, on peut prend la première qui vient,
        ensuite, on peut essayer un choix plus judicieux"""
        if len (free) == 0 : return None
        else : return free [0]
        
    def piece_position (self, pi) :
        """recherche la piece associée à la position pi"""
        for p in self.piece :
            if p.position == pi :
                return p
        return None
            
    def ensemble_voisin (self, i) :
        """retourne les positions voisins de la position i"""
        i -= 1
        res = []
        for x in [-1,0,1] :
            for y in [-1,0,1] :
                if abs (x) == abs (y) : continue
                if x == -1 and i % 3 == 0 : continue
                if x == 1  and i % 3 == 2 : continue
                if y == -1 and i / 3 == 0 : continue
                if y == 1  and i / 3 == 2 : continue
                j = i + x + y * 3
                if j in range (0,9) :
                    res.append (j)
        return [ j+1 for j in res ]
        
    def nb_place (self) :
        """retourne le nombre de places vides"""
        i = 0
        for p in self.piece :
            if p.position == 0 : i += 1
        return i
        
    def angle_possible (self, p, display = False) :
        """retourne l'ensemble des angles possibles pour une pièce donnée"""
        voisin = self.ensemble_voisin (p.position)
        if display :
            print "voisin = ", voisin
        res = []
        for a in [0,90,180,270] :
            r = True
            for v in voisin :
                piece = self.piece_position (v)
                if piece != None :
                    r = r and piece.voisin_possible (p, a)
            if r :
                res.append (a)
        return res
        
    def solution (self, pos = 1, screen = None) : 
        if pos == 1 :
            for p in self.piece :
                p.position = 0
            self.nb_position = 0
            self.essai = 0
            
        self.essai += 1
            
        if self.nb_position == len (self.piece) :
            time.sleep (0.2)
            return

        free = []
        for p in self.piece :
            if p.position == 0 :
                free.append (p)
               
        if screen != None :
            self.affiche (screen, True)
            pygame.display.flip ()
            
        p = self.meilleure_piece (free, pos)
        while p != None :
            
            p.position     = pos
            angle          = self.angle_possible (p)
            
            for a in angle :
                if pos == 1 :
                    print "piece ", p.numero, " angle ", a, " essai ", self.essai, self.nb_place ()
               
                p.orientation = a
                
                time.sleep (0.2)
                
                if self.nb_place () == 0 : 
                    return True
                else : 
                    r = self.solution (pos+1, screen = screen)
                    if r : return True
                    
            p.position     = 0
            free2 = free
            free = []
            for f in free2 :
                if f.numero != p.numero : free.append (f)
            if len (free) != len (free2) - 1 :
                stop
                    
            p = self.meilleure_piece (free, pos)
            
            
        

def attendre_clic ():
    """attend la pression d'un clic de souris"""
    reste = True
    while reste:
        for event in pygame.event.get():
            if event.type == pygame.MOUSEBUTTONUP :
                reste = False
                break

if __name__ == "__main__" :
    
    # taille de l'écran
    size = (750,750)
    
    # initialisation du module pygame
    pygame.init ()
    screen  = pygame.display.set_mode (size)
    
    # on définit le puzzle
    #p = Puzzle ("puzzle")
    p = Puzzle (".")
    
    # on affiche le puzzle avec print (format texte)
    print p
    
    # on affiche le puzzle à l'écran
    p.affiche (screen, petite = True)
    attendre_clic ()
    
    # on rafraîchit l'écran pour que le puzzle apparaissent
    pygame.display.flip ()
    
    # on trouve la solution
    r = p.solution (screen = screen)
    print "résolution ", r
    
    # on attend la pression d'un clic de souris avant de terminer le programme
    p.affiche (screen, petite = True)
    attendre_clic ()
        
        
        

créé avec py2html version:0.62