Table des matières

Jeu de la vie

Le jeu de la vie est un jeu inventé en 1970 par 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.

Les règles

On considère l'état de la grille à un instant donné.

Ci-dessous, les cellules encadrée en rouge meurent et celles en vert naissent. Les autres ne changent pas.

Une fois que l'on a décidé quelles cellules allaient naître ou mourir, on réactualise la grille :

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 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 :

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 :

Solution possible en Processing.