====== Jeu de la vie ====== Le [[https://fr.wikipedia.org/wiki/Jeu_de_la_vie|jeu de la vie]] est un jeu inventé en 1970 par [[https://fr.wikipedia.org/wiki/John_Horton_Conway|John Conway]] (1937-2020 //Grand mathématicien britannique//). ===== Principe général ===== Une grille est constituée de cases carrées. Chaque case est une cellule. {{ vie_1.png?direct&400 |}} * Certaines cellules sont vivantes (//ici les bleues//), * la grille évolue : certaines cellules meurent (deviennent blanches), et certaines naissent (deviennent bleues), * le jeu défini des règles précises déterminant quelles cellules naissent et quelles cellules meurent. ===== Les règles ===== On considère l'état de la grille à un instant donné. * On commence par compter le nombre de voisines vivantes de chaque cellules. On compte parmi les 8 voisines immédiates (on compte les voisines en diagonale) * Quand une cellule morte a 3 voisines vivantes, elle naît. * Quand une cellule vivante a autre chose que 2 ou 3 voisines vivantes,elle meurt. Ci-dessous, les cellules encadrée en rouge meurent et celles en vert naissent. Les autres ne changent pas. {{ vie_2.png?direct&400 |}} Une fois que l'on a décidé quelles cellules allaient naître ou mourir, on réactualise la grille : {{ vie_3.png?direct&400 |}} ===== Les fichiers de départ ===== # module life.py WIDTH = 80 # La grille de jeu a 80 cellules de large HEIGHT = 60 # et 60 de haut CELL_SIZE = 10 # chaque cellule fait 10 pixels de côté LIFE_COLOR = 'blue' # cellule vivante est bleue DEAD_COLOR = '#fff' # cellule morte est blanche def create_new_cell(canvas, line:int, col:int, coul:str): """ Crée une nouvelle cellule dans la zone de dessin, à la ligne line et colonne col, de couleur coul la fonction renvoie un identifiant de l'objet graphique créé """ x = col * CELL_SIZE y = line * CELL_SIZE return canvas.create_rectangle(x, y, x + CELL_SIZE, y + CELL_SIZE, fill=coul) def change_cell_coul(canvas, cell_id:int, new_coul:str): """ pour une cellule dont l'identifiant est fourni, on impose une nouvelle couleur """ canvas.itemconfig(cell_id, fill=new_coul) def make_grids(canvas): """ crée et renvoie deux grilles une grille cells contenant les identifiants des cellules graphiques une grille vivants contenant l'état de vie de chaque cellule (True ou False) """ # à faire def vivant(vivants, line, col) -> bool: """ renvoie True si la cellule à la position demandée est vivante si position hors cadre, renvoie False """ # à faire def voisins_vivants(vivants, line, col) -> int: """ renvoie le nombre de voisins vivants pour la position demandée """ # à faire def has_to_die(vivants, line, col) -> bool: """ renvoie True si la cellule à la position demandée est vivante et doit mourir """ # à faire def has_to_born(vivants, line, col) -> bool: """ renvoie True si la cellule à la position demandée est morte et doit naître """ # à faire def set_alive(canvas, vivants, cells, line, col): """ met à l'état vivant la cellule demandée c'est à dire : modifie vivants et l'état de la cellule graphique correspondante """ # à faire def set_dead(canvas, vivants, cells, line, col): """ met à l'état mort la cellule demandée c'est à dire : modifie vivants et l'état de la cellule graphique correspondante """ # à faire def cycle(canvas, vivants, cells): """ exécute un cycle de jeu de la vie """ # à faire # main.py from tkinter import * # bibliothèque graphique from life import make_grids, cycle # init fenêtre tkinter root = Tk() # création de la fenêtre Tkinter root.title("Life") # on attribue un titre à la fenêtre # création de la zone de dessin - canvas canvas = Canvas(root, width = WIDTH*CELL_SIZE, height = HEIGHT*CELL_SIZE, bd=0, bg="white") canvas.pack() # insertion de la zone de dessin dans la fenêtre def reactualisation(): cycle(canvas, vivants, cells) # pour que la réactualisation se relance 1s plus tard, la fonction se termine par : root.after(1000, reactualisation) cells, vivants = make_grid(canvas) # et en fin de script, on amorce en programmant une première # réactualisation root.after(1000, reactualisation) # et on lance la boucle qui maintient l'affichage de la fenêtre root.mainloop() ===== L'interface graphique ===== On peut utiliser [[nsi:modules:tkinter:start|Tkinter]]. Le fenêtre elle-même est désignée par ''root''. À l'intérieur de cette fenêtre, on crée une zone de dessin qui s'appelle ''canvas''. C'est surtout avec elle que l'on va travailler. On souhaite créer des carrés qui changent de couleurs. Ces carrés sont dessinés grâce aux fonctions de ''canvas''. Vous trouvez cela dans la fonction ''%%create_new_cell%%''. Cette fonction se contente de renvoyer un identifiant, simple nombre entier, qui permettra ensuite de retrouver le carré et de changer sa couleur. Quand on veut changer la couleur, on utilise ''%%change_cell_coul%%''. Il est important dans ce programme de traiter à part l'information disant si une cellule est vivante ou morte et l'objet graphique représentant cette cellule. En effet, on pourrait très bien se contenter de travailler sur les objets graphiques. On pourrait utiliser ''canvas'' pour savoir si tel carré est bleu ou blanc, ce qui nous permettrait de savoir si la cellule correspondante est vivante ou morte. Mais cela serait moins lisible et prendrait plus de temps d'exécution. Pour que le jeu de la vie fonctionne bien, il faut être plus économe en temps d'exécution. Tout au long du programme, nous aurons donc deux grilles : * une grille ''vivants'' qui ne contient que des ''True'' et des ''False''. Son contenu change en fonction de l'évolution du jeu. * une grille ''cells'' contenant les identifiants des objets graphiques. Cette grille ne change pas car les objets graphiques sont toujours les mêmes. Ils se contentent de changer de couleur. ===== grilles ===== ==== création des grilles ==== Bien que le jeu soit à deux dimensions, je propose d'utiliser des grilles à une seule dimension car c'est plus facile à utiliser. vivants = [ False, False, False, ..., False, # prévoir 80 colonnes comme indiqué plus haut False, True, False, ..., True , ... False, False, True, ..., True ], # et prévoir 60 lignes # Les cellules sont données tout à la suite ce qui fait qu'on a un tableau de HEIGHT*WIDTH items # pour atteindre la cellule de ligne 13 et colonne 5 vivantes[13*WIDTH + 5] # la cellule en haut à gauche est à la ligne 0 et colonne 0 La fonction ''%%make_grids%%'' doit créer simultanément la grille ''cells'' et la grille ''vivants''. Vous pouvez créer une grille aléatoirement en mettant à vivant ou à mort au hasard. Chaque fois que vous ajoutez un élément dans ''vivants'' il faut ajouter l'élément correspondant dans ''cells''. Je vous rappelle que la création d'une cellule passe par ''%%create_new_cell%%''. ==== lecture de la grille ==== On va vouloir savoir si la cellule à une certaine ligne et une certaine colonne est vivante. Pour cela on doit définir : def vivant(vivants, line, col) -> bool: """ renvoie True si la cellule à la position demandée est vivante si position hors cadre, renvoie False """ # à faire Comme ''vivants'' est un tableau où toutes les valeurs sont mises à la suite les unes des autres, il faut déterminer l'indice de l'élément recherché. J'ai déjà dit plus haut que si je demande la ligne 13 et la colonne 5, il faut demander ''%%vivants[ligne*WIDTH + colonne]%%''. À vous de généraliser. Remarquez qu'il sera plus commode de pouvoir donner des positions hors grille (vous verrez un peu plus loin pourquoi). Donc dans le cas où la position demandée ne correspond pas à une ligne valide ou une colonne valide, il faut renvoyer ''False''. ==== voisins vivants ==== Nous parlons ici de la fonction ''%%voisins_vivants%%'' qui pour une position donnée doit renvoyer le nombre de voisins vivants. Pour cela il suffit de faire la somme des valeurs voisines de la position considérée. Par exemple, si je demande ligne 13, colonne 5, je vais faire la somme des positions (12,4), (12,5), (12,6), (13,4), (13,6), (14,4), (14,5), (14,6). Ces positions contiennent des booléens mais si on demande une somme, les ''True'' seront convertis en ''1'' et la somme reviendra à compter le nombre de vivants. Ce qui vient d'être dit peut poser problème : on va se voir obliger de faire beaucoup de tests quand on est au bord. Par exemple, si j'interroge la position (2,0) *c'est à dire ligne 2 colonne 0*, il n'y a que 5 voisins au lieu de 8 : (1,0), (1,1), (2,1), (3,0), (3,1). En effet, la position (2,-1) n'existe pas. Mais si je décide que les positions hors cadre sont mortes alors rien ne m'empêche d'interroger la position (2,-1) qui sera considérée comme morte. Voilà pourquoi on a fait ce choix pour la fonction ''vivant''. Cela nous permet de ne pas trop nous préoccuper des bords. ==== Changement d'état ==== Deux fonctions permettent de changer l'état des grilles : ''%%set_alive%%'' et ''%%set_dead%%''. Ces fonctions doivent modifier l'état de ''vivants''. Il s'agit de modifier la grille pour mettre ''True'' ou ''False'' à l'endroit désiré. Elles doivent aussi modifier l'affichage. Il faut lire l'identifiant désiré dans ''cells'' afin de changer la couleur de la cellule avec ''%%change_cell_coul%%''. ===== Évolution ===== Je vous propose une approche qui n'est pas trop gourmande en temps de façon à obtenir une animation satisfaisante. La fonction ''cycle'' décide de l'évolution. Il faudra : * préparer une liste ''%%to_die%%'' des cellules devant mourir et une liste ''%%to_born%%'' des cellules devant naître. Ces listes sont vides initialement. * parcourir chaque cellule. Grâce aux fonctions ''%%has_to_born%%'' et ''%%has_to_die%%'' décider du destin de cette cellule. * Quand toutes les cellules ont été lues, passer à la modification des cellules : * mettre à l'état mort chaque cellule de ''%%to_die%%'' * mettre à l'état vivant chaque cellule de ''%%to_born%%'' Solution possible en [[nsi:langages:processing:solutions:jeu_de_la_vie|Processing]].