IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Tkinter Python Discussion :

Avis et conseil sur mon code de pseudo pacman [Python 3.X]


Sujet :

Tkinter Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Mai 2018
    Messages
    47
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Industrie

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Par défaut Avis et conseil sur mon code de pseudo pacman
    Bonjour,

    J'ai fait un pacman like pour m'améliorer à la POO. J'ai mis 3 niveaux en PJ.

    J'aimerais avoir votre avis sur mon code (je sais déjà qu'il n'est pas terrible, plus cela avance, plus j'ai du mal à rajouter des fonctionnalités facilement. Par exemple, j'ai du mal à remettre tout le monde à sa position initiale après le passage de niveau, signe de mauvaise augure. Ici les boules continuent simplement sur leur lancée, en espérant qu'elles ne soient pas dans un mur après le passage de niveau).

    Avez vous des exemples de code propre en POO un minimum complexe ? J'ai du mal à en trouver avec du tkinter en plus (en général, beaucoup de programmes ne font qu'une classe).

    Qu'est ce que je peux faire pour progresser ?

    Merci

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    from tkinter import Tk, Canvas, Label, Frame, RIGHT, LEFT, IntVar
    from numpy import ones, ndenumerate, loadtxt 
    from random import choice 
     
    CASE_SIZE = 20
    BALL_RADIUS = 8 
    GAME_SPEED = 100
    COIN_SIZE = 5
     
    MOTIONS = {
                'Up':   (0, -1),
                'Down': (0, 1),
                'Left': (-1, 0),
                'Right':(1, 0)
               }
     
    class Appli():
        """Score + Vie + Level + Victoire"""
     
        score = 0
        life = 3
        current_level = 1
        fruit_counter = None 
        object_map = None
        run_status = True 
     
        def score_up():
            if Appli.run_status:
                Appli.score += 1
                Appli.fruit_counter -= 1
                object_map.score_label_content.set(Appli.score)
                Appli.check_victory()
     
        def check_victory():
            if Appli.fruit_counter == 0:
                Appli.current_level += 1 
                Appli.object_map.next_level() 
     
        def lose_life():
            Appli.life -= 1
            Appli.run_status = False   
            object_map.life_label_content.set(Appli.life)
            if Appli.life == 0:     # Fin du jeu
                Appli.object_map.reset_map_table() #Refait slt tableau, pas le graphe
                Appli.object_map.failure()         
     
    class Map():
        """GUI mur + GUI fruits + GUI Game over + next level"""
     
        def __init__(self,root):
            self.load_map()
            self.root = root
            self.init_game_graphics()
     
        def init_game_graphics(self):
            """Canvas + Wall + Fruit""" 
            lar = self.map_game.shape[0]*CASE_SIZE
            hau = self.map_game.shape[1]*CASE_SIZE  
     
            main_frame = Frame(self.root,width=lar+100,height=hau+100 )
            main_frame.pack()       
     
            self.can_game = Canvas(main_frame,width=lar,height=hau,bg="black")
            self.can_game.pack(padx=10,pady=10)
            self.can_game.focus_set() 
     
            self.life_label_content = IntVar()
            self.life_label_content.set(Appli.life)
            self.life_label = Label(main_frame,textvariable=self.life_label_content)  
            Label(main_frame,text='Life:').pack(side=LEFT)         
            self.life_label.pack(side=LEFT)
     
            self.score_label_content = IntVar()
            self.score_label_content.set(Appli.score)
            self.score_label = Label(main_frame,textvariable=self.score_label_content)  
            self.score_label.pack(side=RIGHT)   
            Label(main_frame,text='Score:').pack(side=RIGHT)         
     
            self.reset_map_grahic()
     
        def reset_map_grahic(self):             
            self.fruit_list = self.map_game.shape[0] * self.map_game.shape[1] * [0]
            self.can_game.delete("construct")
     
            for (x,y), value in ndenumerate(self.map_game):
     
                if self.map_game[x,y] == 1:
                    self.can_game.create_rectangle(y*CASE_SIZE,x*CASE_SIZE,
                                               (y+1)*CASE_SIZE,(x+1)*CASE_SIZE,
                                              outline='blue',tags='construct')  #Tags pour les détruire ensuite au passage de niveau. On aurait pu faire une liste d'objet rectangle)
                elif self.map_game[x,y] == 0 or self.map_game[x,y] == 2:
     
                    self.fruit_list[x*self.map_game.shape[1]+y] = self.cercle(CASE_SIZE*(y+1/2),CASE_SIZE*(x+1/2),
                           COIN_SIZE,'yellow','construct') 
     
        def cercle(self,x, y, r, coul ='black', tag ='None'):
            boule = self.can_game.create_oval(x-r, y-r, x+r, y+r, fill=coul, tags = tag) 
            return boule
     
        def load_map(self):
            self.map_game = loadtxt(str(Appli.current_level) + '.txt')
            self.reset_map_table()
     
        def reset_map_table(self):
            fruit_counter = 0
            for (x,y), value in ndenumerate(self.map_game):
                if self.map_game[x,y] == 0 or self.map_game[x,y] == 2: # or == 2, cas reset partie ?
                    self.map_game[x,y] = 2    
                    fruit_counter += 1 
            Appli.fruit_counter = fruit_counter
     
        def check_fruit(self,pos):  
            if self.map_game[pos[0],pos[1]] == 2:
                self.map_game[pos[0],pos[1]] = 0
                self.update_fruit(pos)
                Appli.score_up()
     
        def update_fruit(self,pos):
            x = pos[0]
            y = pos[1]
            self.can_game.delete(self.fruit_list[x*self.map_game.shape[1]+y])
            self.fruit_list[x*self.map_game.shape[1]+y] = 0 
     
        def failure(self):
            self.can_game.delete('all')
            self.can_game.after(400) 
            self.can_game.create_text(CASE_SIZE*self.map_game.shape[0]/2,CASE_SIZE*self.map_game.shape[1]/2,
                                      fill="red",font="Times 15 italic bold",text="Game Over")
     
        def next_level(self):
            self.load_map()
            self.reset_map_grahic()
            self.reset_map_table()
     
        def get_map(self):
            return self.map_game
     
    class Walking_unity():
        """ Parent de pacman et ghost"""
        """vérif carte + ball dessin + reset_position + update GUI ball""" 
     
        def __init__(self,object_map,color):
            self.root = object_map.root
            self.object_map = object_map   
            self.can_game = object_map.can_game        
            self.graphic_ball = self.ball_drawing(color)
     
        def check_map(self):
            map_game = self.object_map.get_map() 
            row = self.ball_pos[0] + self.vector[1] 
            col = self.ball_pos[1] + self.vector[0]
            if col >= 0 and col < map_game.shape[1] and \
                row >= 0 and row < map_game.shape[0]and \
                map_game[row,col] != 1:
                    return True 
     
        def ball_drawing(self,color):
            row = self.ball_pos[0]
            col = self.ball_pos[1]
            return self.cercle(col*CASE_SIZE+CASE_SIZE/2,row*CASE_SIZE+CASE_SIZE/2, BALL_RADIUS,color) 
     
        def update_graphic_ball(self):
            self.can_game.move(self.graphic_ball,self.vector[0]*CASE_SIZE,self.vector[1]*CASE_SIZE)       
     
        def cercle(self,x, y, r, coul ='black'):
            boule = self.can_game.create_oval(x-r, y-r, x+r, y+r, fill=coul) 
            return boule
     
        def reset_position(self):      
            #Mal fait: ne concerne que les ghosts
            row = self.ball_pos_init[0]
            col = self.ball_pos_init[1]
            self.ball_pos = self.ball_pos_init
            self.can_game.delete(self.graphic_ball)
            del self.graphic_ball
            if Appli.life != 0: # Without this, the ghost is recreated eventhough the game over 
                self.graphic_ball = self.ball_drawing('red')           
                Appli.run_status = True 
     
        def set_pos(self):
            self.ball_pos = [self.ball_pos[0]+self.vector[1],self.ball_pos[1]+self.vector[0]]  
     
        def get_pos(self):
            return self.ball_pos  
     
    class Pacman(Walking_unity):
        """Event binder + Move_ball manuel"""    
     
        def __init__(self,object_map):  
            color = 'yellow'
            self.position_initial()  
            self.vector = None        
     
            Walking_unity.__init__(self,object_map,color)
     
            self.init_binder_key_ball()
            self.move_ball_manual()        
     
        def init_binder_key_ball(self):    
            for key in MOTIONS:
                self.can_game.bind('<%s>' % key, self.keyboard_event)  
     
        def keyboard_event(self,event):
            self.vector = MOTIONS[event.keysym]
     
        def position_initial(self):
            self.ball_pos = [0,0]     
            self.ball_pos_init = self.ball_pos 
     
        def move_ball_manual(self): 
            #Utiliser le polymorphisme sur le movement aurait peut etre été intéressant
                if self.vector and self.check_map():
                    self.set_pos()   
                    self.object_map.check_fruit(self.ball_pos)
                    self.update_graphic_ball()
                self.can_game.after(GAME_SPEED, self.move_ball_manual)   
     
    class Ghost(Walking_unity):
        """Verif position pacman + Move_ball auto"""   
     
        def __init__(self,object_map,ball_pos,object_pacman):
     
            color = 'red'        
            self.object_pacman = object_pacman
            self.ball_pos = ball_pos 
            self.ball_pos_init = ball_pos
            Walking_unity.__init__(self,object_map,color)        
     
            self.counter_previous_position = 0
            self.previous_pacman_position  = self.object_pacman.ball_pos
            self.previous_ghost_position = ball_pos
     
            self.vector = (-1, 0)    
            self.move_ball_auto()        
     
        def check_pacman(self):
            self.counter_previous_position +=1
     
            if self.object_pacman.get_pos() == self.get_pos() or \
            (self.previous_ghost_position == self.object_pacman.get_pos() \
            and self.previous_pacman_position == self.get_pos()):
                Appli.lose_life()
                self.reset_position()
     
            self.previous_ghost_position = self.get_pos()
            self.previous_pacman_position = self.object_pacman.get_pos()             
     
        def move_ball_auto(self):
            #Utiliser le polymorphisme sur le movement aurait peut etre été intéressant
            if Appli.run_status:
                if self.check_map():        
                    self.set_pos()                                     
                    self.update_graphic_ball()  
                    self.check_pacman()
                else:
                    self.vector = choice(list(MOTIONS.values()))
                self.can_game.after(GAME_SPEED, self.move_ball_auto) 
     
    # --- Main ---
     
    root = Tk()
    object_map = Map(root)
    Appli.object_map = object_map
    object_pacman = Pacman(object_map)
            #object_map.object_list.append(object_pacman)
    object_ghost1 = Ghost(object_map,(9,9),object_pacman)     
    object_ghost2 = Ghost(object_map,(7,9),object_pacman)
    #object_ghost3 = Ghost(object_map,(6,9),object_pacman)
    root.mainloop()
    Fichiers attachés Fichiers attachés
    • Type de fichier : txt 1.txt (212 octets, 182 affichages)
    • Type de fichier : txt 2.txt (212 octets, 160 affichages)
    • Type de fichier : txt 3.txt (212 octets, 156 affichages)

  2. #2
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    Tu mélanges pas mal de chose. Idéalement je devrais pouvoir créer un pacman et une carte et le déplacer avec des commandes python, puis voir même afficher tout cela en console.

    Tkinter, c'est joli, mais c'est une surcouche conséquente. C'est une GUI. Elle doit être dans une classe isolée (nommée PacmanGUI par exemple) qui va s'appuyer sur celle(s) construite(s) plus haut (Map et Pacman par exemple). Son boulot sera juste de faire un visuel Tkinter sympa de ces classes, qui elles peuvent très bien vivre sans avoir la GUI Tkinter !

    Donc dans un premier temps je te dirais : oublie Tkinter. Fais juste un petit affichage en console pour pouvoir controler que ca se passe bien. Ca va t'obliger à séparer vraiment la partie calcul (le noyau) de la partie graphique. Tu remetteras Tkinter après.


    Autres commentaires plus ponctuels :
    1) Faire des listes, c'est bien :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
            self.fruit_list = self.map_game.shape[0] * self.map_game.shape[1] * [0]
            ......
            self.fruit_list[x*self.map_game.shape[1]+y] =  .....
    Mais bon quand on a une structure 2D comme celle là, on fait des listes de listes (immitant un tableau donc)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
            self.fruit_list = [ [ 0 for i in range(self.map_game.shape[0]) ]  for j in range(self.map_game.shape[1]) ]  ### imperatif de passer par for et non pas par [0]*nb, car sinon toutes les lignes de ton tableau vont se retrouver connectées (modifier un élément d'une ligne, modifiera ce même élément sur toutes les lignes, ce que l'on ne veut pas)        
            ......
            self.fruit_list[x][y] =  .....  ### Plus simple, plus intuitif
    ou mieux encore vu que tu utilises déjà numpy :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
            self.fruit_list = np.zeros(self.map_game.shape)
            self.fruit_list[x,y] =  .....
    2)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
        def set_pos(self):
            self.ball_pos = [self.ball_pos[0]+self.vector[1],self.ball_pos[1]+self.vector[0]]
    Pourquoi les indices sont inversés entre ball_pos et vector ? Ca ne devrait pas.
    De plus tu devrais definir les positions dans des tuples plutôt. Car avec numpy ca se combine très bien :
    Si self.fruit_list est un array, alors
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    print(self.fruit_list[x,y])
    est équivalent à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    self.ball_pos = (x,y)
    print(self.fruit_list[self.ball_pos])  ### Et hop, ici encore on n'a pas à savoir ni x ni y, on traite avec une position
    3) Autre point d'ailleurs encore sur cette fonction set_pos. La définir comme ca n'est pas du tout une bonne idée. Ce qu'il faut faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        def set_pos(self, pos):
            .... #### Verification éventuelle que pos passer en argument est bien une position
            self.ball_pos = pos
    et on complète avec :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        def move_pos(self, vector_move):
            .... #### Verification éventuelle que vector_move passer en argument est bien sous la forme attendue
            self.ball_pos += vector_move
    4) Un élément pythonique dont tu ne te sers pas :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
            row = self.ball_pos[0]
            col = self.ball_pos[1]
    c'est l'unpacking
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
            row, col = self.ball_pos

    Et certainement encore plein d'autres remarques si je lisais plus en détail ton code. Donc là il faut séparer noyau de la GUI. Egalement choisir si oui ou non tu utilise numpy, et si tu l'utilise, l'utiliser à fond (pas juste à moitié et s'embeter avec des listes à côté).

    Bon courage.

    Lg53

    PS : ton projet m'intéresse, je reviendrais surement voir le topic

  3. #3
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 741
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 741
    Par défaut
    Salut,

    Citation Envoyé par Kazvert Voir le message
    Avez vous des exemples de code propre en POO un minimum complexe ? J'ai du mal à en trouver avec du tkinter en plus (en général, beaucoup de programmes ne font qu'une classe).
    Dans les sources Python vous avez idlelib (le code de l'IDE Idle inclus dans Python).
    Un peu de recherche sur Internet avec les mots clefs "tkinter pacman" vous pointerait aussi pas mal d'exemples de code.

    Citation Envoyé par Kazvert Voir le message
    AQu'est ce que je peux faire pour progresser ?
    Votre code montre que vous utilisez assez mal tkinter et numpy...
    C'est pas une couche de POO par dessus qui va combler votre ignorance: seul le temps que vous vous donnez pour apprendre à utiliser ces bibliothèques.
    Il en est de même pour la POO: ce n'est qu'une façon d'organiser/découper son code avec des "class" et ce découpage est un choix de design parmi d'autres.
    Là aussi vous avez une littérature et des exemples abondants. Il faut prendre le temps de se former et compter plus en mois et années qu'en jours et semaines (et avoir la chance de travailler sur des projets intéressants).

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  4. #4
    Membre averti
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Mai 2018
    Messages
    47
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Industrie

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Par défaut
    Merci pour vos retours et conseils.

    Je vais lâcher dans un premier temps tkinter et sans doute numpy pour me concentrer sur la POO et des listes (c'est pas brillant non plus!).

    J'ai du mal à trouver de la littérature intéressante. La section POO en python sur open classroom est super légère je trouve, ce n'est que que des exemples très simples à chaque fois. Cela m'a pris un temps fou pour commencer à comprendre comment des objets pouvaient communiquer entre eux (avoir une idée assez claire de ce qu'on appelle agrégation, association, composition, dépendance m'a fait perdre des cheveux)

    "Fais juste un petit affichage en console pour pouvoir controler que ca se passe bien"
    => Est ce que le contrôle du Pacman avec les flèches vient après avec le graphique et qu'il s'agit dans un premier temps juste de vérifier avec des print que le tout marche bien au niveau des listes et différents attributs ?





    -
    Pourquoi les indices sont inversés entre ball_pos et vector
    Je vais revoir cela, c'est le seul moyen que j'avais trouvé pour que cela fonctionne mais c'est très douteux

    -
    set_pos(self, pos)
    ok , je ferai ainsi, on est sur que pos sera la position qui sera définie

    -
    self.ball_pos += vector_move
    self.fruit_list[self.ball_pos]
    On n'utilise plus du tout x et y au final, d'accord

    - Je n'avais jamais utilisé de unpacking, j'avais l'impression de perdre en clarté.


    Je re posterai sur le topic quand j'aurai quelque chose de plus propre.

    Bonne journée

  5. #5
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    J'ai appris python avec le tuto de Swinnen. La POO aussi. C'est toujours une référence, je te le recommande.


    => Est ce que le contrôle du Pacman avec les flèches vient après avec le graphique et qu'il s'agit dans un premier temps juste de vérifier avec des print que le tout marche bien au niveau des listes et différents attributs ?
    Dans ta classe pacman tu dois avoir une méthode move(). Ta grille est figée et lorsque tu appelles cette méthode ca change la position du pacman. Dire que l'appui sur une flèche du clavier, c'est appeler cette fonction move avec un paramètre particulier (pour la direction), ca c'est le boulot de la classe qui gèrera la GUI.

  6. #6
    Membre averti
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Mai 2018
    Messages
    47
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Industrie

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Par défaut
    Bonjour;

    Suite à vos réponses, j'ai mis à jour mon code (qui suit). J'ai aussi mis à jour les niveaux en PJ.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    from numpy import ndenumerate, loadtxt 
    from random import shuffle 
    from tkinter import Tk, Canvas, Frame, Label, RIGHT, LEFT, IntVar
     
    # - Bug 1: si pacman fini par sa position initiale, il faut qu'il rebouge de 1 avant que le 
    #           niveau suivant ne soit lancé. Au niveau suivant, il occupera position la et non celle init. 
    # - Bug 2: on utilise du .coord pour bouger les fantomes, cela distorsionne un peu la boule. 
    # - Bug 3: Pas de critère d'arret du jeu une fois le nombre de niveau max atteint  
    # - Bug 4: dans GUI, les .after sont dans des if dépednant de l'attribut next_level_flag qui passe false au changement de niveau,
    #          or les .after semblent continuer meme si on n'a plus la condition requise. On a en effet pas besoin de relancer 
    #          les méthode __move_pacman et __move_ghost dans la classe GUI lorsque l'on passe au niveau suivant dans la méthode __next_level
     
    CASE_SIZE = 20
    STARTING_LEVEL = 0
    BALL_RADIUS = 8 
    GAME_SPEED = 200
    COIN_SIZE = 5
     
    MOTIONS = {
                'Up':   (-1, 0),
                'Down': (1, 0),
                'Left': (0, -1),
                'Right':(0, 1)
               }
     
    class Game():
     
        def __init__(self):
            """Init de variables utiles pour le jeu"""
            self.life = 3
            self.map_object = None
            self.score_level = 0    # Deux scores, le premier permet de passer au niveau suivant quand tous les fruits sont mangés 
            self.score_total = 0        
            self.current_level = STARTING_LEVEL
            self.max_fruit = None 
            self.run_game = True # Utilisé quand le joueur a perdu
            self.next_level_flag = False  # Utilisé dans la classe GUI pour passage de niveau
     
        def score_up(self):
            """Gestion du score, déclenche niveau suivant"""
            self.score_level += 1
            if self.score_level == self.max_fruit:
                self.score_total += self.score_level 
                self.score_level = 0
                self.next_level_flag = True
                self.current_level += 1
                self.map_object.load_map()    
                for elt in Walking_unit.object_list:    # Les unités reprennent leur position initiale (peut etre que cela aurait été mieux dans la classe walkable unit)
                    elt.set_pos(elt.init_pos)
     
        def change_run_game(self):
            """Permet le Game Over"""
            self.run_game = False 
     
        def set_life(self,life):
            self.life = life
     
        def get_life(self):
            return self.life
     
        def get_current_level(self):
            return self.current_level
     
        def set_next_level_flag(self,next_level_flag):
            self.next_level_flag = next_level_flag
     
        def get_next_level_flag(self):
            return self.next_level_flag
     
    class Map():
     
        def __init__(self,game_object):
            self.game_object = game_object  
            self.game_object.map_object = self
            self.load_map()  
     
        def __reset_map_table(self):
            """Remplit de fruits - Nb 2 - le tableau"""
            self.fruit_counter = 0
            for (row,col), value in ndenumerate(self.map_game):
                if self.map_game[row,col] == 0 or self.map_game[row,col] == 2: 
                    self.map_game[row,col] = 2    
                    self.fruit_counter += 1 
            self.game_object.max_fruit = self.fruit_counter  # Permet au jeu de connaitre le nombre de fruit max
     
        def load_map(self):
            """Charge la carte à partir d'un fichier txt"""
            current_level = self.game_object.get_current_level()
            self.map_game = loadtxt(str(current_level) +'.txt')
            Walking_unit.map_game = self.get_map()
            self.__reset_map_table()
     
        def get_map(self):
            return self.map_game 
     
    class Walking_unit():
        """Toutes les unités mouvantes"""
        object_list = []    # Liste des toutes les unités créees. 0 sera tjs le pacman, le reste les fantomes  
        map_game = None     # Plus simples d'avoir attribut de classe
     
        def __init__(self,game_object):
            self.game_object = game_object        
            Walking_unit.object_list.append(self)
     
        def move_ball(self, vector_move):
            """S'occupe de bouger la boule"""        
            if self.check_map(vector_move): 
                row, col = self.get_pos()  
                row = row + vector_move[0]
                col = col + vector_move[1]                     
                self.set_pos([row,col])
                self.__check_unit()
                return True
            else:
                return False #Permet d'adapter comportement si mur 
     
        def check_map(self,vector_move):
            """Vérification d'un mur ou des limites de la carte"""
            row, col = self.get_pos() 
            row += vector_move[0]
            col += vector_move[1]
            if col >= 0 and col < Walking_unit.map_game.shape[1] and \
                row >= 0 and row < Walking_unit.map_game.shape[0] and \
                Walking_unit.map_game[row,col] != 1:
                    return True
     
        def __check_unit(self):
            """Qd Pacman bouge, il vérifie les fantomes. Qd fantome bouge, il vérifie pacman"""
            for index in range(1,len(Walking_unit.object_list)):
                if Walking_unit.object_list[index].get_pos() == Walking_unit.object_list[0].get_pos():        
                    self.game_object.set_life(self.game_object.get_life()-1)
                    Walking_unit.object_list[index].reset_ghost_pos()
                    if self.game_object.get_life() == 0:
                        self.game_object.change_run_game() 
     
        def set_pos(self,pos):
            self.ball_pos = pos
     
        def get_pos(self):
            return self.ball_pos   
     
    class Pacman(Walking_unit):
        """Dédié au Pacman"""
     
        def __init__(self,game_object):     
            self.set_pos([0,0])
            self.init_pos = self.get_pos()
            Walking_unit.__init__(self,game_object)  
     
        def __check_fruit(self,map_game):
            """Verifie fruit et incremente le score si fruit"""
            row, col = self.get_pos()        
            if map_game[row,col] == 2:
                self.map_game[row,col] = 0
                self.game_object.score_up()
     
        def move_ball(self, vector_move):   # polymorphisme pour vérifier fruit    
            if Walking_unit.move_ball(self,vector_move):
                self.__check_fruit(Walking_unit.map_game) 
                return True
            else:
                return False                           
     
    class Ghost(Walking_unit):
        """Dédié aux fantomes"""
     
        def __init__(self,game_object,init_pos):
            self.init_pos = init_pos
            self.set_pos(init_pos)         
            Walking_unit.__init__(self,game_object) 
     
        def reset_ghost_pos(self):        
            self.set_pos(self.init_pos) 
     
    class GUI():
        """Gestion des graphiques"""
     
        def __init__(self,game_object,map_object,object_pacman,list_ghost):
            self.root = Tk() 
            self.pacman_move_flag = True # Permet le changement de niveau
            self.game_object = game_object
            self.map_game = map_object.get_map()
            self.object_pacman = object_pacman
            self.list_ghost = list_ghost    
     
            self.__init_canvas()
            self.__init_frame()
            self.__init_map()
            self.__init_pacman()
            self.__move_pacman()  #Ne fait pas parti de __init_pacman() car pas besoin de cela au __next_level()   
            self.__init_binder_key_ball() #idem
            self.__init_ghost()   #Ne fait pas parti de __init_ghost() car pas besoin de cela au __next_level()   
            self.__move_ghost()  #Ne fait pas parti de __init_ghost() car pas besoin de cela au __next_level()   
     
            self.root.mainloop()
     
        def __init_frame(self): 
            """Label de vie + Lable de score"""
            self.life_label_content = IntVar()
            self.life_label_content.set(self.game_object.life)
            self.life_label = Label(self.main_frame,textvariable=self.life_label_content)  
            Label(self.main_frame,text='Life:').pack(side=LEFT)         
            self.life_label.pack(side=LEFT)
     
            self.score_label_content = IntVar()
            self.score_label_content.set(self.game_object.score_total)
            self.score_label = Label(self.main_frame,textvariable=self.score_label_content)  
            self.score_label.pack(side=RIGHT)   
            Label(self.main_frame,text='Score:').pack(side=RIGHT)           
     
        def __init_canvas(self):
            """Mise en place du Canvas"""
            lar = self.map_game.shape[0]*CASE_SIZE
            hau = self.map_game.shape[1]*CASE_SIZE         
     
            self.main_frame = Frame(self.root,width=lar+100,height=hau+100 )
            self.main_frame.pack()       
     
            self.can_game = Canvas(self.main_frame,width=lar,height=hau,bg="black")
            self.can_game.pack(padx=10,pady=10)
            self.can_game.focus_set() 
     
        def __init_pacman(self):  
            self.vector = None    # Pour le pacman    
            self.pacman_move_flag = True
            self.graphic_ball = self.__ball_drawing('yellow',self.object_pacman.get_pos())        
     
        def __init_ghost(self):
            self.GUI_list_ghost = []
            self.vector_ghost_list = [] 
     
            for index in range (len(self.list_ghost)):      
                self.vector_ghost_list.append((-1,0))   
     
            for elt in self.list_ghost:
                self.GUI_list_ghost.append(self.__ball_drawing('red',elt.get_pos()))
     
        def __init_map(self): 
            """Charge la carte, les murs, les fruits"""
            self.can_game.delete('all')        
            self.gui_fruit_list = [ [ 0 for i in range(self.map_game.shape[0]) ]  for j in range(self.map_game.shape[1]) ] 
     
            for (x,y), value in ndenumerate(self.map_game):
     
                if self.map_game[x,y] == 1:
                    self.can_game.create_rectangle(y*CASE_SIZE,x*CASE_SIZE,
                                               (y+1)*CASE_SIZE,(x+1)*CASE_SIZE,
                                              outline='blue') 
                elif self.map_game[x,y] == 0 or self.map_game[x,y] == 2:       
                    self.gui_fruit_list[x][y] =  self.__cercle(CASE_SIZE*(y+1/2),CASE_SIZE*(x+1/2),COIN_SIZE,'yellow')  # Rq: si array, les zeros empechent de typer des objets, python considere que l'on a des entiers et non des objets  
     
        def __init_binder_key_ball(self):    
            for key in MOTIONS:
                self.can_game.bind('<%s>' % key, self.__keyboard_event) 
     
        def __keyboard_event(self,event):
            self.vector = MOTIONS[event.keysym]     
     
        def __move_ghost(self): 
            vector_list = list(MOTIONS.values()) # Si on met du while tant que check_map n'est pas OK, cela pose pb avec le .after. 
            if self.pacman_move_flag:       # Tant qu'il reste des fruits on continue
                for index,elt in enumerate(self.list_ghost):
                    row, col = elt.get_pos()    
                    if not(elt.check_map(self.vector_ghost_list[index])):   #si le vecteur de la liste de fantome à lindice du dit fantome ne vérifie pas la map, on remélange la liste 
                        shuffle(vector_list)
                        for vector in vector_list: # et on la parcourt, cela évite un while
                            if elt.check_map(vector):
                                self.vector_ghost_list[index] = vector
                                break # On sort des qu'un vecteur qui marche a été trouvé 
                    elt.move_ball(self.vector_ghost_list[index])                
                    self.can_game.coords(self.GUI_list_ghost[index],col*CASE_SIZE+CASE_SIZE/2-BALL_RADIUS,
                                         row*CASE_SIZE+CASE_SIZE-2*BALL_RADIUS,col*CASE_SIZE+CASE_SIZE/2+BALL_RADIUS,row*CASE_SIZE+CASE_SIZE-2)          
                self.can_game.after(GAME_SPEED, self.__move_ghost)     
     
        def __move_pacman(self):
            if not(self.game_object.run_game):  #Vérifie si on n'a pas un game_over
                self.__failure()
            else:
                self.score_label_content.set(self.game_object.score_total+self.game_object.score_level) # on update le score et la vie 
                self.life_label_content.set(self.game_object.life)            
     
            if self.pacman_move_flag:   # OK tant qu'il reste des fruits
                if self.vector != None:     # Permet de rester immobile au début
                    if self.object_pacman.move_ball(self.vector): # Si on arrive à bouger la boule avec le vecteur venant des touches, alors on update le graphique
                        self.can_game.move(self.graphic_ball,self.vector[1]*CASE_SIZE,self.vector[0]*CASE_SIZE)  
                        x,y = self.object_pacman.get_pos()
                        if self.gui_fruit_list[x][y] != 0:  # Update des fruits si nécessaires 
                            self.can_game.delete(self.gui_fruit_list[x][y])
                            self.gui_fruit_list[x][y] = 0 
                        elif self.game_object.get_next_level_flag():  #Si on a fini tous les fruits, on passe au niveau suivant  
                            self.pacman_move_flag = False
                            self.game_object.set_next_level_flag(False)
                            self.__next_level()
                self.can_game.after(GAME_SPEED, self.__move_pacman)
     
        def __next_level(self):
            """on remet tout à zéro"""
            self.map_game = map_object.get_map()    #Pas besoin de rappeller move alors qu'on aurait du etre sorti du .after, bizarre 
            self.__init_map()
            self.__init_pacman()    
            self.__init_ghost()         
     
        def __ball_drawing(self,color,ball_pos):
            row, col = ball_pos
            return self.__cercle(col*CASE_SIZE+CASE_SIZE/2,row*CASE_SIZE+CASE_SIZE/2, BALL_RADIUS,color)   
     
        def __cercle(self,x, y, r, coul ='black', tag ='None'):
            boule = self.can_game.create_oval(x-r, y-r, x+r, y+r, fill=coul, tags = tag) 
            return boule 
     
        def __failure(self):
            self.can_game.delete('all')
            self.can_game.after(400) 
            self.can_game.create_text(CASE_SIZE*self.map_game.shape[0]/2,CASE_SIZE*self.map_game.shape[1]/2,
                                      fill="red",font="Times 15 italic bold",text="Game Over")  
            self.life_label_content.set(0)
     
    # ------ Main ------
    game_object = Game()
    map_object = Map(game_object)
    object_pacman = Pacman(game_object) 
    list_ghost = [Ghost(game_object,[1,9]),Ghost(game_object,[9,9])]    # Il faut au moins un fantome, on pourrait en rajouter ici encore 
    object_GUI = GUI(game_object, map_object, object_pacman, list_ghost)

    Dans un premier temps, je me suis passé de tkinter et j'ai testé sur la console les actions basiques (chargement du niveau, mouvement de pacman, mouvement d'un fantome, de plusieurs fantomes, rencontre d'un pacman avec un fantome, rencontre d'un fantome avec un pacman immobile, passage de niveau)

    Après j'ai rajouté une classe GUI qui automatise ce que je faisais dans la console et l'affiche. C'est bien une surcouche au reste.

    J'ai trouvé cela (séparer le graphique du reste) plus facile à implémenté mais cela m'a quand même prix du temps (quelle idée de mettre du while quand il y a un .after ?). Il est plus facile d'y rajouter des fonctionnalités mais cela reste quand même compliqué.

    Pour le graphique des fruits, je suis passé par des listes. Si j'utilise un array que je remplis de 0 au début, Python considère que l'array est du type int. Lles objets que je rajoute ensuite dans l'array sont du coup aussi considérés comme des int et plus comme des objets. Il faudrait que je précise le type de l'array lors de sa création.

    Si vous avez d'autres conseils ...

    Dans le Swinnen, je mettais arrêté avant les bombardes, je vais reprendre. Je vais aussi regarder les design pattern de plus prés également, cela me donnera des idées.
    J'ai trouvé des exemples concrets en Python : https://python-3-patterns-idioms-tes...rnConcept.html


    Merci

    Bonne soirée
    Fichiers attachés Fichiers attachés
    • Type de fichier : txt 0.txt (212 octets, 120 affichages)
    • Type de fichier : txt 1.txt (212 octets, 109 affichages)
    • Type de fichier : txt 2.txt (212 octets, 115 affichages)
    • Type de fichier : txt 3.txt (212 octets, 127 affichages)
    • Type de fichier : txt 4.txt (212 octets, 109 affichages)
    • Type de fichier : txt 5.txt (212 octets, 119 affichages)
    • Type de fichier : txt 6.txt (212 octets, 114 affichages)

  7. #7
    Membre très actif

    Homme Profil pro
    Bidouilleur
    Inscrit en
    Avril 2016
    Messages
    721
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Bidouilleur

    Informations forums :
    Inscription : Avril 2016
    Messages : 721
    Billets dans le blog
    1
    Par défaut
    Salut.

    Il y a des incohérences dans ton code, ta classe GUI est un peu une classe fourre-tout, par exemple, tu as une classe Ghost, Pacman, mais les méthodes de déplacements se situent dans ta classe GUI, ce n'est pas très logique, pareil pour quasiment toutes les méthodes de cette classe, tu as aussi unifié déplacement des fantômes et du pacman, mais je ne trouve pas ça très cohérent non plus, pour moi le pacman se déplace totalement différemment des fantômes, si plus tard tu souhaites faire différents types de fantômes, vitesse différentes, pathfinding différent, etc, tu devras tout revoir. De plus l'héritage des classes Pacman, Ghost de cette classe n'est pas non plus logique point de vue objet, on est plus ici dans la composition, aggrégation, mais certainement pas dans l'héritage.

    Sinon, concernant le jeu, les couleurs (pacman, fantômes, fruits) sont trop proches, j'ai pour ma part du mal à distinguer ce qu'il se passe, un peu plus de nuances serait mieux
    Les fantômes sont trop rapides, il sont sous ectasy ?
    Je ne comprend pas pourquoi tu fais déplacer le pacman d'un bout de couloir à un autre automatiquement, c'est un peu difficile de bifurquer.
    Lorsque qu'un fantôme heurte le pacman, il faudrait je pense supprimer le fantôme ou rendre invincible le pacman un court laps de temps, car on se retrouve dans des situations inextricables où le fantôme heurte le pacman en boucle rapidement et on meurt illico.

    Bonne continuation.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Votre avis sur mon code
    Par Schopenhauer dans le forum Fortran
    Réponses: 4
    Dernier message: 04/05/2011, 15h12
  2. [PHP 5.0] Avis sur mon code
    Par Arnich dans le forum Langage
    Réponses: 0
    Dernier message: 20/05/2010, 12h51
  3. [Projet en cours] Tetris amateur - vos avis sur mon code ?
    Par NainTernaute dans le forum Projets
    Réponses: 24
    Dernier message: 04/05/2010, 22h44
  4. [XL-2003] Votre avis sur mon code en VBA ?
    Par [ZiP] dans le forum Macros et VBA Excel
    Réponses: 2
    Dernier message: 02/03/2010, 13h56
  5. [FFT] Votre avis sur mon code
    Par deubelte dans le forum C++
    Réponses: 1
    Dernier message: 10/02/2007, 20h14

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo