Le jeu de la vie

../../_images/jeu3D.png

Les objectifs de ce TP sont :

  • Écrire un programme simulant un automate cellulaire simple.

  • Utiliser la classe StdDraw pour représenter les cellules.

État initial : écosystème de 25 x 25 avec une probabilité de 50% de cellules vivantes.

Génération 200

../../_images/init25-50.png ../../_images/gen200-25-50.png

Présentation

Le jeu de la vie est un automate cellulaire imaginé par John H. Conway au début des années 1970. Les règles d’évolution en sont très simples mais permettent aux générations successives de cellules de s’organiser spontanément en motifs extrêmement complexes. Le jeu se déroule sur une grille 2D, l’écosystème, où chaque case représente une cellule, qui peut être soit vivante soit morte, et rien d’autre.

À chaque étape de l’évolution, l’état de chacune des cellules est déterminé par l’état de ses huit voisines à l’étape précédente :

  • Une cellule morte possédant exactement trois voisines vivantes devient vivante.

  • Une cellule vivante possédant deux ou trois voisines vivantes reste vivante, sinon elle meurt.

La classe JeuVie

La classe JeuVie modélise l’écosystème. Elle comporte entre autres attributs :

  • ecosysteme : Un tableau 2D de Cell, type énumération représentant l’état d’une cellule.

  • voisines : Un tableau 2D d’entiers contenant le nombre de voisines vivantes de chaque cellule (à l’étape courante).

Pour la phase d’initialisation, la méthode tireProba() déjà conçue à l’occasion du TP StdDraw, sera ré-employée. La classe StdDraw est aussi nécessaire pour réaliser les tracés.

Une ébauche de code pour la classe JeuVie peut ressembler à ceci :

public class JeuVie {
  static final Scanner input = new Scanner(System.in);
  static final Random rand = new Random();

  enum Cell{DEAD, ALIVE}
  int size ;
  Cell[][] ecosysteme ;
  int[][] voisines ;

  /**
   * Renvoie vrai avec une probabilité p
   * @param p la probabilité de tirage vrai souhaitée
   * @return  le tirage vrai ou faux
   */
  public boolean tireProba(double p){
      if(rand.nextDouble() < p) return true ;
      return false ;
  }

  /**
   * Constructeur d'un jeu de la vie de taille n.
   * Initialisé avec nxn cellules dont p% sont vivantes
   * @param n la taille du coté de l'écosystème
   */
  public JeuVie(int n, double p){
      size = n;
      ...
  }
  ...
}

À faire

  1. JeuVie(), le constructeur de la classe, doit allouer et initialiser les attributs

    • ecosysteme : en remplissant le tableau cellule par cellule selon l’état, DEAD ou ALIVE, tiré au sort.

    • voisines : en calculant pour chaque cellule de l”ecosysteme, le nombre de voisines vivantes qu’elle cotoie. Cette initialisation fera appel à la méthode nbVoisines() qui calcule le nombre de voisines vivantes d’un cellule désignée par sa position (i,j) dans l’ecosystème.

  2. nbVoisines(), prend en paramètres :
    • l’indice de ligne i de la cellule

    • l’indice de colonne j de la cellule

    et retourne le nombre de voisines vivantes de la cellule en (i,j).

    Avertissement

    • Même les cellules situées sur les bords de l’écosystème possèdent huit voisines ; l’écosystème est circulaire dans les deux dimensions.

  3. dispEco(), la méthode qui réalise l’affichage de l’ecosysteme en représentant par exemple les cellules vivantes par un rond de couleur. Les cellules mortes ne seront pas représentées, la case du tableau sera laissée vide.

  4. Modifier dispEco() de sorte à ce qu’au dessous du certaine taille d’ecosystème (30 par exemple), les valeurs du tableau voisines soient affichées en gris pâle dans les cases. Cela facilitera la mise au point du programme.

  5. nextGen(), la méthode qui effectue une itération et calcule l’état de cellule pour la génération suivante. Elle ne prend pas de paramètres mais retourne le nombre de changements d’état qui sont intervenus.

  6. main() qui demandera à l’utilisateur d’entrer la taille de l’écosystème, puis calculera l’état de l’écosystème de génération en génération, en demandant à l’utilisateur après chaque affichage, combien de générations simuler avant le prochain affichage. Pour aider la mise au point, il peut-être utile d’afficher en console les valeurs retournées par la méthode nextGen(). Une squelette de main() peut avoir l’allure suivante :

    public static void main(String[] args) {
      int n ; // taille de l'ecosystème
      int nb; // nombre de générations à sauter entre deux affichages
      int gen =0; // numéro de la génération courante
      int cpt; // compteur de générations à sauter entre deux affichages
      int ch;  // nombre de changements d'une génération à l'autre
      final double PROBA = 0.5 ; // pour l'initialisation
      System.out.println("Taille de l'ecosystème : ");
      n = input.nextInt();
      StdDraw.setScale(0, n); // mise à l'échelle de la fenêtre
    
      JeuVie jeu = new JeuVie(n,PROBA) ; // création du jeu
      jeu.dispEco();                      // affichage initial
    
      do{
          ... à vous de jouer ...
      } while (...);
    }
    

Pour les plus chevronnés :

  1. Permettre une initialisation libre, c’est à dire où l’utilisateur choisit les cellules vivantes en cliquant dans les cases correspondantes par exemple, où en entrant les coordonées (c’est plus facile).

  2. Étendre le jeu à la troisième dimension, comme sur la figure d’illustration. Attention, il y a du boulot, là !…

Correction