« Jeu de la vie » : différence entre les versions

De Wiki du LAMA (UMR 5127)
Aller à la navigation Aller à la recherche
Aucun résumé des modifications
Aucun résumé des modifications
(32 versions intermédiaires par le même utilisateur non affichées)
Ligne 1 : Ligne 1 :
===Introduction===
===Introduction===


Le jeu de la vie est un automate cellulaire inventé par John Conway en 1970. Il se base sur des règles très simples pour déterminer l'évolution d'un ensemble de cellules sur une grille. Cette simulation permet d'observer l'émergence de motifs complexes à partir d'états initialement simple.
Le jeu de la vie est un automate cellulaire inventé par le mathématicien John Conway en 1970. Il s'agit d'un système fondé sur des règles très simples qui déterminent l'évolution d'un ensemble de cellules sur une grille. Chaque cellule peut être dans l'un des deux états : vivante ou morte, et son état à la génération suivante dépend de celui de ses voisines.

<blockquote>
Le contexte de sa création était celui de la recherche sur les systèmes dynamiques et l'émergence de la complexité à partir de règles élémentaires. Le jeu de la vie a ainsi ouvert la voie à l'étude des automates cellulaires et a inspiré de nombreux travaux en mathématiques, en informatique théorique, mais aussi en art numérique.
</blockquote>


Malgré cette simplicité, le jeu de la vie donne naissance à des comportements étonnamment complexes et variés comme nous le verrons. Dans ce projet, nous allons explorer différentes manières d'implémenter le jeu de la vie, en commençant par une version simple sur une grille finie, puis en passant à une grille infinie optimisée, avant d'aborder l'approche en arbre.


<h1>Théorie et règles</h1>
<h1>Théorie et règles</h1>
Ligne 7 : Ligne 14 :
<h2>Règles de base du jeu de la vie</h2>
<h2>Règles de base du jeu de la vie</h2>


A chaque génération, les règles suivantes s'appliquent simultanément à toutes les cellules :
A chaque génération, l'état de chaque cellule dépend de ses 8 voisins immédiats (représenté en vert):


[[Fichier:voisins_jeu_de_la_vie.png]]
(expliquer le principe de voisins : diagonale, haut, bas, gauche, droite)


- Une cellules vivante meurt si elle a moins de 2 ou plus de 3 voisins vivants (sous-population ou surpopulation)
* Une cellules vivante meurt si elle a moins de 2 ou plus de 3 voisins vivants (sous-population ou surpopulation)


- Une cellule vivante survit si elle a exactement 2 ou 3 voisins.
* Une cellule vivante survit si elle a exactement 2 ou 3 voisins.


- Une cellule morte naît si elle a exactement 3 voisins vivants
* Une cellule morte naît si elle a exactement 3 voisins vivants


Ces règles basiques s'appliquent sur toute la grille, toutes les cellules évoluent simultanément à chaque génération. La simplicité de ces règles est ce qui permet l'émergence de structures extrêmement complexes.
Ces règles simples donnent naissance à une diversité de motifs et d'interactions.


<h2>Exemples de comportements</h2>
<h2>Exemples de comportements</h2>


* <b>Les structures stationnaires</b> : Ce sont des configurations qui restent figées dans le temps, sans changer d'état à travers les générations. Un exemple classique est le <i>Block</i>, un carré de 2x2 cellules vivantes qui reste stable tant qu’aucune cellule vivante ne vient interagir avec lui.
(Mettre des gif pour illustrer)


[[Fichier:jeu_de_la_vie.gif]]
[[Fichier:block.png|220px]]


* <b>Les vaisseaux</b> : Ce sont des structures capables de se déplacer à travers la grille tout en conservant leur forme. Le plus connu est le <i>Spaceship</i>, un petit vaisseau qui se déplace horizontalement. Ces motifs montrent comment un mouvement global peut émerger de règles strictement locales.
- Les oscillateurs

- Les vaisseaux
[[Fichier:vaisseau.gif|220px]]
- Les structures stationnaires

* <b>Les oscillateurs</b> : Il s’agit de structures qui reviennent périodiquement à leur configuration initiale après un certain nombre de générations. Le <i>Pulsar</i> est un oscillateur de période 3 : il alterne entre trois états différents avant de retrouver sa forme de départ.

[[Fichier:pulsar.gif|220px]]

Ces exemples montrent comment le jeu de la vie permet d'observer différents comportements : les structures stationnaires, la mobilité des vaisseaux et le cycle répétitif des oscillateurs.


<h1>Implémentation sur une grille finie</h1>
<h1>Implémentation sur une grille finie</h1>

Cette première version permet d'expérimenter les règles du jeu de la vie sur une grille simple. Elle est idéale pour bien comprendre le fonctionnement de base, même si elle montre vite ses limites sur des grandes tailles.


<h2>Structure de données utilisée</h2>
<h2>Structure de données utilisée</h2>


La version naïve repose sur un tableau à deux dimensions, chaque élément représente l'état de la cellule (0 pour morte, 1 pour vivante).
On utilise un tableau à deux dimensions pour stocker l’état de chaque cellule. Chaque élément vaut 0 (cellule morte) ou 1 (cellule vivante).


<h2>Initialisation et affichage de la grille</h2>
<h2>Initialisation et affichage de la grille</h2>
Ligne 44 : Ligne 59 :
<h2>Calcul des voisins et évolution des cellules</h2>
<h2>Calcul des voisins et évolution des cellules</h2>


L'évolution d'une cellule s'effectue ensuite en appliquant les règles selon le nombre de voisins vivants.
L'évolution d'une cellule s'effectue en comptant le nombre de voisins vivants autour d'elle et en appliquant les règles du jeu de la vie.


<pre> def voisins_vivants(x, y):
<pre> def voisins_vivants(x, y):
Ligne 57 : Ligne 72 :
return nb_vivants
return nb_vivants
</pre>
</pre>

Cette fonction parcourt les 8 positions autour d'une cellule et compte les cellules vivantes en s'assurant de rester dans les limites de la grille.


<h2>Avantages et limites de l'approche naïve</h2>
<h2>Avantages et limites de l'approche naïve</h2>


- Simple à comprendre et à implémenter.
* Simple à comprendre et à implémenter.


- Utiles pour des grilles de petite taille.
* Utiles pour des grilles de petite taille.


- Gaspillage de mémoire pour de grandes tailles.
* Gaspillage de mémoire pour de grandes tailles.


- Inadapté lorsque la majorité des cellules restent mortes.
* Inadapté lorsque la majorité des cellules restent mortes.


<h1>Implémentation sur une grille infinie</h1>
<h1>Implémentation sur une grille infinie</h1>

Pour simuler une grille infinie, on stocke uniquement les cellules vivantes, ce qui permet d'économiser de la mémoire et d'accélérer les calculs.


<h2>Principe de stockage par dictionnaire</h2>
<h2>Principe de stockage par dictionnaire</h2>


Plutôt que d'utiliser un tableau complet, on utilise un dictionnaire où chaque clé représente les coordonnées d'une cellule vivante.
Pour simuler une grille infinie, seule l'information des cellules vivantes est stockée dans un dictionnaire.

Chaque clé représente les coordonnées d'une cellule vivante :


<pre>cases = {(x, y): 1, ...}</pre>
<pre>cases = {(x, y): 1, ...}</pre>
Ligne 80 : Ligne 97 :
<h2>Gestion des cellules</h2>
<h2>Gestion des cellules</h2>


Pour appliquer les règles, on ne parcourt pas l'ensemble de la grille mais seulement les cellules vivantes, leurs voisines potentielles.
Lors du calcul de la génération suivante, seules les cellules vivantes et leurs voisines potentielles sont considérées, ce qui permet d'ignorer rapidement les zones inactives.


<pre>for (x, y) in cases:
<pre>for (x, y) in cases:
Ligne 91 : Ligne 108 :


L'utilisation d'une dictionnaire pour stocker uniquement les cellules vivante est bien plus efficace en mémoire lorsque la densité de vie est faible. Elle est plus adapté aux grilles de grande taille, voire infinies, car elle évite de gérer un tableau complet.
L'utilisation d'une dictionnaire pour stocker uniquement les cellules vivante est bien plus efficace en mémoire lorsque la densité de vie est faible. Elle est plus adapté aux grilles de grande taille, voire infinies, car elle évite de gérer un tableau complet.

<h1>Approche en arbre (quadtrees)</h1>

Un <b>quadtree</b> est une structure d’arbre qui divise récursivement la grille en 4 sous-régions. Cette approche est particulièrement adaptée pour simuler le jeu de la vie sur de très grandes grilles, voire infinies, lorsqu'une grande partie des cellules reste inactive.

<h2>Principe</h2>

<ul>
<li><b>Subdivision de la grille</b> : La grille est divisée en 4 quadrants, chacun pouvant être à son tour subdivisé en 4 sous-quadrants. Cette récursivité permet de représenter efficacement les zones homogènes, par exemple les régions totalement mortes.</li>
<li><b>Compression </b> : Les régions ayant un état homogène (par exemple, totalement mortes) n'ont pas besoin d'être représentées en détail. Le quadtree regroupe ces zones en un seul noeud, réduisant ainsi la mémoire utilisée.</li>
<li><b>Optimisation de l’évolution</b> : Seules les zones actives, donc celles avec des cellules vivantes ou susceptibles de changer, sont simulées en détail. Ceci permet de gagner en performances en ignorant rapidement les grandes portions inactives.</li>
</ul>

<h2>Avantages de l'approche par quadtree</h2>

Les <b>avantages</b> sont nombreux, en voici certains :

<ul>
<li><b>Mémoire</b> : En ne stockant que les régions qu'il faut, l'approche par quadtree évite le gaspillage de mémoire associé aux grilles de grande taille.</li>
<li><b>Performances améliorées</b> : Le calcul se concentre uniquement sur les zones modifiées, ce qui accélère la simulation, notamment pour des configurations où la densité de cellules vivantes est faible.</li>
<li><b>Extension à l’infini</b> : Grâce à la représentation récursive et à la compression des zones inactives, cette méthode permet de simuler pratiquement une grille infinie.</li>
</ul>

<p>Cette approche est souvent combinée avec l'algorithme <b>Hashlife</b>, qui exploite la redondance des configurations en mémorisant et réutilisant les sous-structures déjà calculées, permettant ainsi d'accélérer les simulations sur des périodes très longues.</p>

Algorithmes :

Version du 14 avril 2025 à 11:57

Introduction

Le jeu de la vie est un automate cellulaire inventé par le mathématicien John Conway en 1970. Il s'agit d'un système fondé sur des règles très simples qui déterminent l'évolution d'un ensemble de cellules sur une grille. Chaque cellule peut être dans l'un des deux états : vivante ou morte, et son état à la génération suivante dépend de celui de ses voisines.

Le contexte de sa création était celui de la recherche sur les systèmes dynamiques et l'émergence de la complexité à partir de règles élémentaires. Le jeu de la vie a ainsi ouvert la voie à l'étude des automates cellulaires et a inspiré de nombreux travaux en mathématiques, en informatique théorique, mais aussi en art numérique.


Malgré cette simplicité, le jeu de la vie donne naissance à des comportements étonnamment complexes et variés comme nous le verrons. Dans ce projet, nous allons explorer différentes manières d'implémenter le jeu de la vie, en commençant par une version simple sur une grille finie, puis en passant à une grille infinie optimisée, avant d'aborder l'approche en arbre.

Théorie et règles

Règles de base du jeu de la vie

A chaque génération, l'état de chaque cellule dépend de ses 8 voisins immédiats (représenté en vert):

Voisins jeu de la vie.png

  • Une cellules vivante meurt si elle a moins de 2 ou plus de 3 voisins vivants (sous-population ou surpopulation)
  • Une cellule vivante survit si elle a exactement 2 ou 3 voisins.
  • Une cellule morte naît si elle a exactement 3 voisins vivants

Ces règles basiques s'appliquent sur toute la grille, toutes les cellules évoluent simultanément à chaque génération. La simplicité de ces règles est ce qui permet l'émergence de structures extrêmement complexes.

Exemples de comportements

  • Les structures stationnaires : Ce sont des configurations qui restent figées dans le temps, sans changer d'état à travers les générations. Un exemple classique est le Block, un carré de 2x2 cellules vivantes qui reste stable tant qu’aucune cellule vivante ne vient interagir avec lui.

Block.png

  • Les vaisseaux : Ce sont des structures capables de se déplacer à travers la grille tout en conservant leur forme. Le plus connu est le Spaceship, un petit vaisseau qui se déplace horizontalement. Ces motifs montrent comment un mouvement global peut émerger de règles strictement locales.

Vaisseau.gif

  • Les oscillateurs : Il s’agit de structures qui reviennent périodiquement à leur configuration initiale après un certain nombre de générations. Le Pulsar est un oscillateur de période 3 : il alterne entre trois états différents avant de retrouver sa forme de départ.

Pulsar.gif

Ces exemples montrent comment le jeu de la vie permet d'observer différents comportements : les structures stationnaires, la mobilité des vaisseaux et le cycle répétitif des oscillateurs.

Implémentation sur une grille finie

Cette première version permet d'expérimenter les règles du jeu de la vie sur une grille simple. Elle est idéale pour bien comprendre le fonctionnement de base, même si elle montre vite ses limites sur des grandes tailles.

Structure de données utilisée

On utilise un tableau à deux dimensions pour stocker l’état de chaque cellule. Chaque élément vaut 0 (cellule morte) ou 1 (cellule vivante).

Initialisation et affichage de la grille

L'affichage se fait via Tkinter, en dessinant des rectangles pour chaque cellule selon son état.

# Initialisation d'une grille de dimensions définies par l'utilisateur
cases = [[0 for _ in range(largeur)] for _ in range(hauteur)]

Calcul des voisins et évolution des cellules

L'évolution d'une cellule s'effectue en comptant le nombre de voisins vivants autour d'elle et en appliquant les règles du jeu de la vie.

 def voisins_vivants(x, y):
    voisins = [(-1, -1), (-1, 0), (-1, 1), 
               (0, -1),           (0, 1),
               (1, -1),  (1, 0),  (1, 1)]
    nb_vivants = 0
    for dx, dy in voisins:
        nx, ny = x + dx, y + dy
        if 0 <= nx < hauteur and 0 <= ny < largeur:
            nb_vivants += cases[nx][ny]
    return nb_vivants

Cette fonction parcourt les 8 positions autour d'une cellule et compte les cellules vivantes en s'assurant de rester dans les limites de la grille.

Avantages et limites de l'approche naïve

  • Simple à comprendre et à implémenter.
  • Utiles pour des grilles de petite taille.
  • Gaspillage de mémoire pour de grandes tailles.
  • Inadapté lorsque la majorité des cellules restent mortes.

Implémentation sur une grille infinie

Pour simuler une grille infinie, on stocke uniquement les cellules vivantes, ce qui permet d'économiser de la mémoire et d'accélérer les calculs.

Principe de stockage par dictionnaire

Plutôt que d'utiliser un tableau complet, on utilise un dictionnaire où chaque clé représente les coordonnées d'une cellule vivante.

cases = {(x, y): 1, ...}

Gestion des cellules

Lors du calcul de la génération suivante, seules les cellules vivantes et leurs voisines potentielles sont considérées, ce qui permet d'ignorer rapidement les zones inactives.

for (x, y) in cases:
    candidats.add((x, y))
    for dx, dy in voisins:
        candidats.add((x + dx, y + dy))

Comparaison avec la grille finie

L'utilisation d'une dictionnaire pour stocker uniquement les cellules vivante est bien plus efficace en mémoire lorsque la densité de vie est faible. Elle est plus adapté aux grilles de grande taille, voire infinies, car elle évite de gérer un tableau complet.

Approche en arbre (quadtrees)

Un quadtree est une structure d’arbre qui divise récursivement la grille en 4 sous-régions. Cette approche est particulièrement adaptée pour simuler le jeu de la vie sur de très grandes grilles, voire infinies, lorsqu'une grande partie des cellules reste inactive.

Principe

  • Subdivision de la grille : La grille est divisée en 4 quadrants, chacun pouvant être à son tour subdivisé en 4 sous-quadrants. Cette récursivité permet de représenter efficacement les zones homogènes, par exemple les régions totalement mortes.
  • Compression  : Les régions ayant un état homogène (par exemple, totalement mortes) n'ont pas besoin d'être représentées en détail. Le quadtree regroupe ces zones en un seul noeud, réduisant ainsi la mémoire utilisée.
  • Optimisation de l’évolution : Seules les zones actives, donc celles avec des cellules vivantes ou susceptibles de changer, sont simulées en détail. Ceci permet de gagner en performances en ignorant rapidement les grandes portions inactives.

Avantages de l'approche par quadtree

Les avantages sont nombreux, en voici certains :

  • Mémoire : En ne stockant que les régions qu'il faut, l'approche par quadtree évite le gaspillage de mémoire associé aux grilles de grande taille.
  • Performances améliorées : Le calcul se concentre uniquement sur les zones modifiées, ce qui accélère la simulation, notamment pour des configurations où la densité de cellules vivantes est faible.
  • Extension à l’infini : Grâce à la représentation récursive et à la compression des zones inactives, cette méthode permet de simuler pratiquement une grille infinie.

Cette approche est souvent combinée avec l'algorithme Hashlife, qui exploite la redondance des configurations en mémorisant et réutilisant les sous-structures déjà calculées, permettant ainsi d'accélérer les simulations sur des périodes très longues.

Algorithmes :