Automates Cellulaires : Jeu de la Vie

Transcription

Automates Cellulaires : Jeu de la Vie
Automates Cellulaires : Jeu de la Vie
November 15, 2009
Laurent Orseau <[email protected]>
Antoine Cornuéjols <[email protected]>
1
Introduction
Le grand public se fait souvent une idée bien réductrice des ordinateurs. Il ne s’agit pas
uniquement de machines permettant de jouer, faire ses comptes et écrire des emails.
C’est beaucoup plus que cela. L’informatique est reliée à la physique et aux mathématiques à un niveau fondamental.
La Nature et la Vie étaient elles-mêmes reliées à ces deux sciences à un niveau
fondamental (elles se basent uniquement sur des processsus physiques, qui sont sujets
à des lois physico-mathématiques), les liens existants entre Nature, Vie et Informatique
sont beaucoup plus forts qu’on ne pourrait le penser a priori.
Certains scientifiques estiment même sérieusement que le monde physique n’est
qu’un gigantesque ordinateur. Pour reprendre une métaphore cinématographique, en
quelques sortes, "nous sommes tous dans la matrice", à la différence près qu’il n’y a
pas nécessairement un autre monde en dehors de la matrice.
Il ne s’agirait cependant pas d’un ordinateur strictement identique à ceux que nous
connaissont. Nous allons voir un modèle d’ordinateur particulier, appelé Automate
Cellulaire, qui serait plus proche de ces considérations.
1.1
Automates Cellulaires
Un Automate Cellulaire (AC) est un monde virtuel composé de "cellules", qui possèdent un état (représenté généralement par un seul et unique nombre) et qui ne peuvent
"communiquer" qu’avec leurs cellules directement voisines. Cette communication se
concrétise par un changement d’état : chaque cellule change d’état en fonction de l’état
de ses voisines.
1
Cette modélisation est très similaire au monde physique, où toutes les lois sont
locales, et les actions à grande échelle sont dues à une multitude de modification locales
des états.
John Von Neumann (1903-1957), un mathématicien de génie, a démontré très tôt
que ce type de modèle était capable de supporter la vie. Néanmoins, son modèle est
beaucoup trop complexe pour ce cours, aussi nous allons nous intéresser à des AC plus
simples.
Les AC les plus courant possèdent 2 dimensions spatiales, comme ceux que nous
allons voir, mais ils peuvent en avoir un nombre quelconque (comme 1 ou 3).
Le triangle de Sierpinski est un AC de dimension 1, et son évolution au fil du temps
donne (en 2 dimensions donc) :
Voici des coquillages sur lesquels on voit ce même triangle (déformé par du bruit) :
Il n’y a en fait rien d’étonnant à cela, car le processus de pigmentation de la coquille
est très similaire (voire identique) à un automate cellulaire simple. Le processus de
création de la coquille est lui aussi un CA, mais en 3 dimensions et plus complexe.
2
2
Le Jeu de la Vie
Un type de CA très particulier a été inventé par John H. Conway (1937-), qu’il a nommé
"Le Jeu de la Vie", non pas parce qu’il est capable de simuler des cellules vivantes et
les processus génétiques qui en découlent, mais parce que les interactions cellulaires
qui en découlent deviennent très vite extrêmement complexes malgré la simplicité (la
trivialité !) des règles locales de communication. A tel point que lorsqu’on examine le
système de plus près, on peut se poser légitimement la question de la vie dans un tel
système.
Le jeu se présente ainsi. Chaque cellule a 8 voisins directs, et possède 2 états
possibles : elle est soit vivante soit morte (représentés par 1 et 0).
À chaque instant, l’état de la cellule est modifiée selon les règles suivantes :
• Si une cellule vivante a moins de 2 voisins, elle meurt (de solitude).
• Si une cellule vivante et a plus de 3 voisins, elle meurt (de surpopulation).
• Si une cellule vide (morte) a exactement 3 voisins, elle naît (par reproduction).
• Si une cellule vivante ou vide a exactement 2 voisins, elle reste dans le même
état.
2.1
Affichage graphique
Commençons par créer l’affichage graphique. La procédure est très similaire à celle
des Termites. Il nous faut une fenêtre graphique à laquelle est associée une matrice de
cases.
Ouvrez le fichier "game-of-life-etu.ss". Créez-y un monde en grille :
(define monde (make-board 100 100 4 4 1 1))
Il y a plus de paramètre que la dernière fois. Les deux premiers paramètres sont le
nombre de cases en x et y. Les deux suivants sont la largeur graphique des cases en x et
y. Les deux derniers sont l’espace entre les cases. Vous pouvez bien entendu modifier
ces valeurs à votre convenance.
Créez ensuite une imagette correspondant à une case vivante :
(define vivante-pic (board-make-cell-pic monde "blue"))
Créez la fonction d’affichage de la matrice :
(define (dessiner-case v)
(if (= 1 v) vivante-pic 'none))
Et enfin dites au monde d’utiliser cette fonction pour l’affichage :
(board-cell-pic monde dessiner-monde)
Récupérez la matrice sous-jacente au monde :
(define matrice (board-get-matrix monde))
Mettez-y une cellule vivante :
(matrix-set! matrice 10 30 1)
(Vous pouvez toujours utiliser aussi board-set!.)
Et affichez le tout :
(board-draw monde 0.01)
3
Question 1
Ecrivez une fonction qui initialise la matrice avec des valeurs aléatoires. (Ceci est
très similaire au TP précédent.) Et visualisez le résultat à l’écran.
2.2
Respectons les règles
Lorsqu’on programme un AC, on est confronté à un choix concenrnant les bornes du
monde : soit le monde est un "plan infini" soit il est borné. On choisit souvent la
dernière solution, où le monde est défini torique (comme dans les Termites).
Le problème survient alors de savoir comment gérer la fonction de communication
entre cellules aux bords de la matrice, car il faut faire un traitement particulier pour
éviter d’utiliser des valeurs erronées (hors de la matrice).
Question 2
Ecrivez une fonction matrix-ref-tore (ou board-ref-tore si vous utilisez
board-ref) :
(matrix-ref-tore lig col) → any
lig : number?
col : number?
qui peut prendre en entrée des valeurs de lig et col qui dépassent les tailles de la
matrice. Par exemple, si le nombre de lignes de la matrice est 100, matrix-ref-tore
123 8 est équivalent à matrix-ref 23 8.
Rappel : vous pouvez utiliser matrix-nrows et matrix-ncols pour connaitre le
nombre de lignes et de colonnes de la matrice.
Note : vous pouvez utiliser la fonction modulo qui renvoie la partie entière de la
division euclidienne. Néanmoins, il est possible de s’en passer.
Question 3
Ecrivez une fonction :
(vivante? lig col) → boolean?
lig : number?
col : number?
qui renvoie #t si la cellule à la ligne lig et à la colonne col est vivante, #f sinon.
Question 4
Ecrivez une fonction :
(somme-voisins lig col) → number?
lig : number?
col : number?
qui retourne le nombre de voisins vivants de la cellule à la ligne lig et à la colonne
col.
4
Question 5
Ecrivez une fonction :
(nouvel-etat lig col) → number?
lig : number?
col : number?
qui prend en paramètre un numéro de ligne et de colonne et qui retourne le nouvel état
de la cellule correspondante.
2.3
Double-buffer
Il n’est pas possible de mettre à jour la matrice directement, car on modifierait certaines
valeurs pour les voisins qui n’ont pas encore été mis à jour.
Pour éviter ce problème, on utilise la technique du double-buffer (le buffer est une
zone mémoire "tampon", de sauvegarde) qui consiste à avoir 2 matrices de mêmes
tailles, au lieu d’une seule. Les nouvelles valeurs des états des cellules de la matrice 1
sont mémorisés dans la matrice 2, puis on recopie le contenu de la matrice 2 dans la
matrice 1.
Question 6
Créez une matrice matrice2 possédant les mêmes dimension que matrice en
utilisant la fonction :
(make-matrix nrows ncols) → matrix?
nrows : number?
ncols : number?
Question 7
Ecrivez une fonction :
(matrix-copy mat1 mat2) → void?
mat1 : matrix?
mat2 : matrix?
qui recopie le contenu de la matrice mat1 dans la matrice mat2.
Question 8
Ecrivez une fonction :
(etats-suivants mat1 mat2) → void?
mat1 : matrix?
mat2 : matrix?
qui fait la mise-à-jour des états de toutes les cellules de la matrice mat1 dans la matrice
mat2 passées en argument.
Vous pouvez utiliser des fonctions intermédiaires si nécessaire.
5
Question 9
Ecrivez une fonction :
(maj-matrice) → void?
qui met à jour toutes les cellules de la matrice matrice en utilisant la technique du
double-buffer.
2.4
Boucle d’animation
En vous basant sur le programme des Termites, créez une fonction :
(animation) → void?
qui fasse la boucle d’animation.
Puis exécutez cette fonction pour visualiser le résultat.
2.5
Gliders
Une des formes remarquables de base du Jeu de la Vie est le glider :
Question 10
Initialisez la matrice pour qu’un glider se trouve au milieu du plateau au départ,
puis exécutez la boucle d’animation pour visualiser le résultat.
3
Lecture dans un fichier
Au fil des ans, de nombreux motifs du Jeu de la Vie ont été découverts, possédant de
propriétés remarquables. Certains motifs sont périodiques et stables dans le temps.
Les gliders "glissent" sur la grille en passant par une succession de transformations
cycliques.
Les guns génèrent périodiquement des gliders.
D’autres sont des combinaisons de tout cela. Et il en reste probablement à découvrir.
La fonction :
6
(read-life nom board) → void?
nom : string?
board : board?
permet de remplir la matrice avec un motif contenu dans le fichier nom.lif. Essayez par
exemple :
(read-life "GUN44" board1)
Regardez ensuite dans le sous-répertoire "life-patterns", qui contient plusieurs
motifs intéressants et essayez-en quelques-uns.
4
Optimisation
De nombreux calculs sont effectués à chaque étape. Il est possible d’augmenter substanciellement la vitesse de calcul en faisant quelques optimisations du programme.
Cette partie est optionnelle.
La première optimisation est de ne pas utiliser d’image pour les cellules mortes (ce
comportement est celui par défaut si vous avez suivi les instructions).
Il est aussi possible de n’afficher (afficher et non calculer) la matrice qu’une image
sur n, où n est à choisir adéquatement.
Lors du double-buffer, au lieu de recopier la matrice, on peut faire directement un
échange de matrice du monde en utilisant :
(board-set-matrix board matrice) → void?
board : board?
matrice : matrix?
et :
(board-get-matrix board) → matrix?
board : board?
Dans somme-voisins, il est préférable de faire la somme des 8 voisins directement,
sans passer par une fonction récursive.
On peut aussi utiliser matrix-ref-tore uniquement sur les bords de la matrice
au lieu de le faire même pour les cases intérieures où cela n’est pas utile. Il faut alors
séparer les différents cas.
D’autre optimisations sont possibles mais nécessitent des traitements un peu plus
complexes.
5
Variantes
De très nombreuses variantes d’AC sont possibles (le Jeu de la Vie étant l’une des
plsu intéressantes connues), et les AC sont même utilisés pour modéliser des problèmes physiques complexes comme des phénomènes de diffusion de gaz, de réaction
chimiques ou d’épidémie par exemple.
Le CA HodgePodge permet modéliser de tels phénomènes.
7
Ses règles sont les suivantes :
• Une cellule peut avoir une valeur entre 0 et n.
• Une cellule de valeur 0 est dite saine. Son état suivant sera ent(Ninf/k1) +
ent(Nill/k2).
• Une cellule de valeur n est dite malade. Son état suivant sera 0 (saine).
• Sinon, la cellule est indectée. Son état suivant sera ent(S/(Ninf+1)+g), avec un
maximum à n.
où ent est la partie entière (en scheme floor), S est la somme des états des cellules
voisines, Ninf est le nombre de celulles voisines infectées, Nill est le nombre de cellules voisines malades. Les paramètres k1, k2 et g permettent de réguler la vitesse
de propagation de l’infection. Selon leurs valeurs, cela peut engendrer des réactions
stables, périodiques ou complexes.
Utilisez une valeur de n=256 et avant la boucle d’animation, utilisez la palette
graphique de couleurs suivantes (pour remplacer la palette à 2 couleurs) :
(board-set-palette monde red-palette)
8