TP5: jeu de la vie (POO)

TP5: jeu de la vie (POO)🔗

On se propose de programmer l'automate cellulaire le plus célèbre, le Jeu de la vie de John Horton Conway (1937–2020), qui simule, à partir de règles simples parfaitement déterministes, l'apparition et l'évolution de systèmes arbitrairement complexes (il est démontré que le Jeu de la Vie est Turing-complet).

Il s'agit donc ici de coder un programme représentant la grille 2D dans laquelle évoluent les cellules (qui naissent, vivent et meurent) et les quelques règles d'évolution d'une configuration à la suivante.

Puisque l'on ne peut pas en pratique travailler sur un monde infini, on prend le parti initial de considérer un monde toroïdal avec des conditions de bord périodiques, p.ex. où la voisine à gauche de la cellule la plus à gauche du monde est la cellule la plus à droite (c'est clair?).

  1. Créer une classe Life, contenant un tableau 2D (liste de listes, ou tableau numpy.ndarray) de h lignes et w colonnes de booléens (True/False pour désigner une cellule vivante/morte) dans un attribut world. La classe est initialisée (__init__) par les entiers h et w, et le tableau world est initialement intégralement False (toutes les cellules sont mortes).

  2. Écrire une méthode init_random(frac=0.5) permettant d'initialiser la grille world aléatoirement à l'aide de la fonction random.random(), de sorte que chaque cellule a une probabilité initiale frac d'être vivante.

  3. Écrire la méthode __str__ affichant l'état courant du monde, en représentant les cellules vivantes par le caractère # et celles mortes par ., p.ex.:

    ...O..O.....OO.......
    .....OOO.............
    O........O...........
    .....O...O...........
    ................OO...
    .....O.O......OO..O..
    ..............OO.OO..
    ..............OO.OO..
    ................O....
    
  4. Écrire une méthode get(i, j) permettant d'obtenir l'état de la cellule à la position (i, j), avec i et j quelconques (on n'a pas nécessairement \(0 \leq i \leq w - 1\) ni \(0 \leq j \leq h - 1\)), en implémentant les conditions de bord périodiques.

  5. En utilisant la méthode précédente, écrire une méthode count_neighbors(i, j) permettant de compter le nombres de cellules vivantes autour de la position (i, j) (il y en a un maximum de 8).

  6. L'état d'une cellule est entièrement déterminé par l'état de ses huit cellules voisines, selon les règles suivantes:

    • une cellule morte possédant exactement trois cellules voisines vivantes devient vivante,

    • une cellule vivante ne possédant pas exactement deux ou trois cellules voisines vivantes meurt.

    Écrire une méthode evolve_cell(i, j) retournant l'état futur de la cellule à la position (i, j).

  7. Écrire une méthode evolve() permettant de faire évoluer le monde entier d'une seule itération.

  8. Écrire une méthode show(nstep=100) permettant de faire évoluer le monde sur nstep itérations, et de l'afficher à chaque itération (nstep=0 devra réaliser une boucle infinie). Pour que l'affichage soit agréable à l'oeil, marquez des pauses entre l'affichage de chaque itération grâce à la fonction time.sleep().

Pour aller plus loin

  1. Écrire deux méthodes dump(filename) et load(filename) permettant de sauvegarder/charger l'état du monde dans le format standardisé plaintext.

  2. Écrire une fonction externe load_pattern(filename) permettant de lire une structure prédéfinie, p.ex. le Gosper glider gun dans une structure ad hoc. Compléter la classe Life par une méthode insert(pattern, i=0, j=0) permettant d'insérer une structure prédéfinie dans le monde à la position (i, j).