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

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

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

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Points : 40
    Points
    40
    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, 156 affichages)
    • Type de fichier : txt 2.txt (212 octets, 139 affichages)
    • Type de fichier : txt 3.txt (212 octets, 131 affichages)

  2. #2
    Membre émérite

    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
    Points : 2 328
    Points
    2 328
    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 sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    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 287
    Points : 36 776
    Points
    36 776
    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 du Club
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Mai 2018
    Messages
    47
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Points : 40
    Points
    40
    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 émérite

    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
    Points : 2 328
    Points
    2 328
    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 du Club
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Mai 2018
    Messages
    47
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Points : 40
    Points
    40
    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, 105 affichages)
    • Type de fichier : txt 1.txt (212 octets, 88 affichages)
    • Type de fichier : txt 2.txt (212 octets, 91 affichages)
    • Type de fichier : txt 3.txt (212 octets, 104 affichages)
    • Type de fichier : txt 4.txt (212 octets, 87 affichages)
    • Type de fichier : txt 5.txt (212 octets, 94 affichages)
    • Type de fichier : txt 6.txt (212 octets, 96 affichages)

  7. #7
    Membre confirmé

    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
    Points : 503
    Points
    503
    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.
    Le temps ronge l'amour comme l'acide.

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

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

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Points : 40
    Points
    40
    Par défaut
    Bonjour,

    merci pour le retour et les conseils.

    L'idée était de dissocier la GUI du mouvement en lui même, et le pacman a besoin des event du clavier pour le mouvement. J'ai donc fait la séparation ainsi. Sans la classe GUI, je peux bien bouger mon pacman et mon fantome en entrant sur la console les commandes de mouvement une part une. J'ai du mal à visualiser autrement, il faudrait que je fasse un diagramme de classe pour y voir plus claire peut etre.

    La classe GUI est effectivement fourre tout, j'aurais peut etre mieux fait de faire des sous classes genre Pacman_GUI.

    Pour le mouvement unifié du fantome et du pacman, c'est vrai que cela rend difficile l'implantation de nouvelles fonctionnalités. C'est juste que au départ je voulais mutualiser au plus. Pour l'héritage, j'avais pris exemple sur une classe véhicule; un bateau et une voiture héritant chacun de cette classe. J'ai voulu reproduire l'idée, le pacman et les ghost sont tous les deux des unités mouvantes et en héritent les caractéristiques communes.

    Pour le jeu, effectivement, les ghost avaient vu las vegas parano juste avant et les couleurs font pleurer les yeux.

  9. #9
    Membre confirmé

    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
    Points : 503
    Points
    503
    Billets dans le blog
    1
    Par défaut
    Salut.

    Citation Envoyé par Kazvert Voir le message
    L'idée était de dissocier la GUI du mouvement en lui même, et le pacman a besoin des event du clavier pour le mouvement. J'ai donc fait la séparation ainsi. Sans la classe GUI, je peux bien bouger mon pacman et mon fantome en entrant sur la console les commandes de mouvement une part une. J'ai du mal à visualiser autrement, il faudrait que je fasse un diagramme de classe pour y voir plus claire peut etre.
    Pourquoi vouloir dissocier les deux ? Dissocier cela dans des méthodes de classes, là, c'est logique, mais morceler tout dans des classes différentes et n'ayant aucun rapport, cela va rendre ton code difficilement maintenable, les déplacements sont des méthodes à part entière des éléments du jeu et donc propre à chaque objet, déplacements d'éléments et éléments graphiques sont bien plus intimement liés qu'une classe GUI généraliste faisant tout le boulot des objets qu'elle manipule.

    Citation Envoyé par Kazvert Voir le message
    La classe GUI est effectivement fourre tout, j'aurais peut etre mieux fait de faire des sous classes genre Pacman_GUI.
    Ce n'est pas à ta classe GUI de manipuler les éléments de ton jeu, une classe PacMan dérivée de Pacman_GUI, pourquoi pas, mais l'intérêt, si tant soit peu qu'il y en a un, est infime, sans compter les complications inutiles.

    Citation Envoyé par Kazvert Voir le message
    Pour le mouvement unifié du fantome et du pacman, c'est vrai que cela rend difficile l'implantation de nouvelles fonctionnalités. C'est juste que au départ je voulais mutualiser au plus. Pour l'héritage, j'avais pris exemple sur une classe véhicule; un bateau et une voiture héritant chacun de cette classe. J'ai voulu reproduire l'idée, le pacman et les ghost sont tous les deux des unités mouvantes et en héritent les caractéristiques communes.
    Ce n'est pas correct d'utiliser de l'héritage ici, comme tu dis, ce sont des caractéristiques, et donc, on n'hérite pas de caractéristiques, rapporté à cela dans l'exemple des véhicules, ce serait comme si une voiture héritait d'une vitesse, d'une carrosserie, de ses pneus, cela n'a aucun sens, ces caractéristiques sont ce qui compose le véhicule, ou définit ses performances, etc. C'est souvent une erreur (dans laquelle je suis tombé plusieurs fois), lorsqu'on débute en POO d'utiliser de l'héritage à mauvais escient.

    Tu devrais t'inspirer de codes existants pas trop complexes pour visualiser comment les autres procèdent
    Le temps ronge l'amour comme l'acide.

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

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

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Points : 40
    Points
    40
    Par défaut
    Ok merci d'avoir apporté ces précisions. Je vais regarder d'autres codes effectivement.

  11. #11
    Membre émérite

    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
    Points : 2 328
    Points
    2 328
    Par défaut
    Séparer le noyau (modèle) de la visualisation/interface est un paradigme qui a fait ses preuves, et je pense que pour un débutant qui mélange un peu les choses, ca peut être bien de l'emmener dans cette direction.

    Ceci dit effectivement là la classe GUI est un peu fourre tout. Il y aurait du découpage à faire là, quite à refaire autant de classe GUI que de classe non GUI.

    Sans parler GUI on a encore des problèmes de conception. Par exemple là :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class Map():
     
        def __init__(self,game_object):
            self.game_object = game_object  
            self.game_object.map_object = self  ####  <--- 
            self.load_map()
    Sur la ligne mise en évidence, plusieurs souci :
    - Si game a besoin de map_object alors c'est ici une facon bien maladroite de le lui fournir.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    class Game():
         def __init__(self, map_object):
    serait beaucoup mieux.
    - Second problème que créée cette ligne : une dépendance cyclique. Game a besoin de Map, et Map a besoin de Game. Ca peut se faire, mais c'est toujours très risqué. Mais pour ma part je pense qu'une carte peut vivre sans être nécéssairement attaché à un jeu. Par le jeu lui a besoin de la carte. Il faut savoir qui a besoin de quoi, et la réponse n'est pas tous le monde a besoin de tous le monde. Un diagramme de classe pourrait en effet vous aider à y voir plus clair.

    Vous avez un sérieux problème sur l'utilisation des paramètres dans les fonctions (ou devrais-je dire, la non-utilisation). Si vous avez besoin de Game dans Map, c'est parce que vous écrivez ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
        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()
    alors que vous devriez écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
        def load_map(self, level=0):
            """Charge la carte à partir d'un fichier txt"""
            self.map_game = loadtxt(str(level) +'.txt')
            Walking_unit.map_game = self.get_map() 
            self.__reset_map_table()
    et ici encore après avoir corriger on voit l'avant dernière ligne qui n'a rien a faire ici, et qui est là car vous avez mal spécifié Walking_unit. Walking_unit connait Game, qui lui même connait Map, donc vous avez tous ce qui vous faut. Pas besoin que Walking_unit est un attribut map_game. Dans Walking_unit, ecrire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.game_object.map_object
    sera bien suffisant pour accéder à la map. Ceci dit pas sûr que le walking_unit ait besoin de game tout entier, lui passer seulement Map suffiriat surement.
    Donc la hierarchie du code devrait être la suivante :
    -Map est une classe autonome
    -Walking_unit a besoin de connaitre Map
    -Game doit connaitre la liste des Walking_unit qui doivent tous partager la même Map. C'est lui qui gère le score et les interractions entre vos objets (quand pacman mange un fruit, quand pacman rencontre un ghost, etc ...)


    Autre souci : l'héritage avec Walking_unit. C'est une bonne idée, mais la factorisation dans Walking_unit n'est pas assez poussée. Pacman a beosin d'un position initiale. Ghost aussi. Alors pourquoi pas Walking_unit ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    class Walking_unit(): 
        def __init__(self,game_object, init_pos):
            self.game_object = game_object        
            self.ball_pos = init_pos
    et vous pouvez aussi d'ailleurs garder init_pos en mémoire à ce niveau là comme vous l'avez fait dans Ghost finalement.

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

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

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Points : 40
    Points
    40
    Par défaut
    Merci pour ce retour.

    Le but de nourrir map de game ainsi était d'éviter effectivement la dépendance cyclique, qui au final n'est pas utile d'après vos exemples. Je vais essayer dans mes prochains programmes de ne pas avoir : tout le monde communique avec tout le monde

  13. #13
    Membre émérite

    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
    Points : 2 328
    Points
    2 328
    Par défaut
    Citation Envoyé par Kazvert Voir le message
    Merci pour ce retour.

    Le but de nourrir map de game ainsi était d'éviter effectivement la dépendance cyclique, qui au final n'est pas utile d'après vos exemples pas utiles. Je vais essayer dans mes prochains programmes de ne pas avoir : tout le monde communique avec tout le monde
    Exactement, car le principe d'une classe c'est l'encapsulation (même si en python elle est beaucoup moins forte que dans d'autre langages). Chaque classe à ses variables qui lui sont propres. Il faut imaginer une classe comme une boite noire interrogeable. La boite noire est créée avec une liste de paramètre connu de l'utilisateur (ce sont ceux du constructeur). Et l'ensemble des questions et ordres que l'on peut donner à la boite, c'est l'ensemble de ces méthodes. Ca peut permettre juste d'obtenir une information sur la boite, ou bien d'en modifier l'état. Et le deal c'est que chaque boite est prévue pour faire un certains nombre de choses, et vous n'êtes pas censé utiliser ces boites autrement que pour ce qu'elles ont été prévues.

    Admettons que j'ai une classe Roue, qui possède une méthode Tourner(). Et maintenant j'ai aussi une classe Voiture qui possède une méthode Avancer(). Dans le constructeur de ma classe Voiture, je vais définir une variable de classe qui contient les 4 roues. Ainsi la méthode Avancer de la Voiture, va faire appel 4 fois à la méthode Tourner (pour chacune des roues). Maintenant avec cet exemple, 2 points :
    1) Si je suis une Voiture, et que je demande à une roue de Tourner et qu'elle tourne, ai-je besoin d'en savoir plus ? La méthode Roue.Tourner peut faire intervenir des choses plus compliquée (la jante, le pneu, leurs taille, etc ...), mais si je suis une voiture tant que mes roues répondent, je me contrefiche de comment elles font pour me répondre !
    2) De même, si je suis utilisateur de la classe Voiture, et que je lui demande d'Avancer, je me contrefiche de savoir ce que vont faire les roues. La classe Voiture sait ce qu'elle à à faire, quitte à délégué elle même certaines taches aux Roues.
    Maintenant, toujours avec ce même exemple, les 4 roues de la voiture sont une liste, qui est une variable de classe que l'on a pas envie que l'utilisateur manipule. Imaginez si l'utilisateur fait qqch comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    une_voiture.roues[0]='toto'
    alors que normalement tous les éléments de la liste une_voiture.roues doivent être des Roues ...
    On peut admettre que sur une Voiture, on puisse changer une Roue. Mais dans ce cas, on va offrir une méthode à l'utilisateur pour cela, et ainsi dans cette méthode on pourra garantir l'intégrité de la classe Voiture, dans le sens où l'attribut roues de Voiture sera assurer d'être une Roue, et pas n'importe quoi que l'utilisateur aurait pu entrer.

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

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

    Informations forums :
    Inscription : Mai 2018
    Messages : 47
    Points : 40
    Points
    40
    Par défaut
    Ok, je vais essayer d'avoir des classes plus encapsulées

+ 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