C’est parti pour le jeu de la vie version Javascript. Dans ce premier article nous allons créer une génération de cellules au hasard et l’afficher.

J’avais annoncé dans l’article précédent que je commencerais par la version Ruby/Opal. Je l’ai écrite mais je n’en suis pas satisfait. Les performances sont très pauvres et j’ai écrit plus de code que nécessaire. Je prendrais donc le temps de la nettoyer un peu avant de la publier.

Pour les tests, j’utiliserais Jasmine et la structure du projet sera la suivante:

$ tree
.
├── app
│   └── application.js
├── index.html
├── jasmine
│   └── lib
│       └── jasmine-2.0.3
│           ├── boot.js
│           ├── console.js
│           ├── jasmine.css
│           ├── jasmine_favicon.png
│           ├── jasmine-html.js
│           └── jasmine.js
├── spec
│   └── test.js
└── test.html

Dans le fichier test.html on charge les dépendances de Jasmine, puis notre application (app/application.js) et enfin nos tests (spec/test.js).

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Test Game of Life</title>
    <link rel="shortcut icon" type="image/png" href="jasmine/lib/jasmine-2.0.3/jasmine_favicon.png">
    <link rel="stylesheet" type="text/css" href="jasmine/lib/jasmine-2.0.3/jasmine.css">

    <script type="text/javascript" src="jasmine/lib/jasmine-2.0.3/jasmine.js"></script>
    <script type="text/javascript" src="jasmine/lib/jasmine-2.0.3/jasmine-html.js"></script>
    <script type="text/javascript" src="jasmine/lib/jasmine-2.0.3/boot.js"></script>
  </head>
  <body>
    <script type="text/javascript" src="app/application.js"></script>
    <script type="text/javascript" src="spec/test.js"></script>
  </body>
</html>

Créer une génération de cellules au hasard

Nous allons écrire la fonction createGeneration qui devra fabriquer un ensemble de cellules, mortes (0) ou vivantes (1), au hasard. Mon premier test avec Jasmine est de m’assurer que cette fonction renvoie un tableau (Array).

describe("createGeneration", function() {

  it("returns an array", function() {
    var result = createGeneration();
    expect(Array.isArray(result)).toBe(true);
  });

});

Lorsqu’on lance test.html Jasmine nous dit que createGeneration n’existe pas. Le code javascript permettant de faire passer ce test consiste donc à renvoyer un tableau vide.

function createGeneration(x, y) {
  return [];
}

Maintenant il nous faut un tableau à 2 dimensions:

describe("createGeneration", function() {

  ...

  it("creates a 2D array", function() {
    var result = createGeneration(3, 5);
    expect(result.length).toBe(5);
    expect(result[0].length).toBe(3);
  });

});

La fonction suivante est suffisante pour faire passer le test.

function createGeneration(x, y) {
  var generation = new Array(y);

  for(var i = 0; i < y; i++) {
    generation[i] = new Array(x);
  }
  return generation;
}

Et maintenant on rempli notre tableau 2D avec des 1 ou des 0. D’abord un test pour vérifier que chaque cellule du tableau contient 1 ou 0.

describe("createGeneration", function() {

  ...

  it("fills each room with 1 or 0", function() {
    var generation = createGeneration(2, 3);
    for(var y = 0; y < 3; y++) {
      for(var x = 0; x < 2; x++) {
        var cell = generation[y][x];
        var result = cell === 0 || cell === 1;
        expect(result).toBe(true);
      }
    }
  });

});

Puis l’implémentation.

function createGeneration(x, y) {
  var generation = new Array(y);

  for(var i = 0; i < y; i++) {
    generation[i] = [];
    for(var j = 0; j < x; j++) {
      generation[i][j] = Math.floor(Math.random() * 2);
    }
  }
  return generation;
}

Voilà, cette première partie du jeu de la vie en Javascript était simple et rapide à coder. Je vais maintenant mettre les tests de coté pour me concentrer sur l’affichage d’une génération.

Afficher une génération

Voyons pour commencer le contenu du fichier index.html.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Game of Life</title>
  </head>
  <body>
    <canvas width="100" height="100" id="canvas"></canvas>
    <script src="app/application.js"></script>
  </body>
</html>

Nous référençons bien entendu notre application.js et nous créons un canvas de (petite) dimension 100x100. C’est dans ce canvas que nous allons dessiner nos cellules.

Sans plus attendre, voici le contenu de application.js.

function createGeneration(width, height) {
  var generation = new Array(height);

  for(var y = 0; y < height; y++) {
    generation[y] = [];
    for(var x = 0; x < width; x++) {
      generation[y][x] = Math.floor(Math.random() * 2);
    }
  }
  return generation;
}

function draw(context2d, generation) {
  var height = generation.length;
  var width = generation[0].length;

  clearBackground(context2d, width, height);
  drawCells(context2d, generation, width, height);
}

function clearBackground(context2d, width, height) {
  context2d.fillStyle = 'black';
  context2d.fillRect(0, 0, width, height);
}

function drawCells(context2d, generation, width, height) {
  context2d.fillStyle = 'white';
  for(var y = 0; y < height; y++) {
    for(var x = 0; x < width; x++) {
      if(generation[x][y] === 1) {
        context2d.fillRect(x, y, 1, 1);
      }
    }
  }
}

(function() {
  var generation = createGeneration(100, 100);
  var c = document.getElementById('canvas');
  var ctx = c.getContext('2d');

  draw(ctx, generation);
})()

Ces quelques petites fonctions sont bien suffisantes pour afficher un ensemble de cellules.

Dans le prochain article nous calculerons l’état des générations suivantes.