cours10 - sbalev/processing101 GitHub Wiki

Cours 10

La boucle for

Un certain type de boucle while, où une valeur est incrémentée de manière répétée, se retrouve utilisé dans de nombreux programmes. Elle est même tellement courante qu'il existe une syntaxe spécifique pour cette boucle.

La boucle for ("Pour") est un raccourci pratique pour ces cas là. Voyons quelques exemples et la notation de cette nouvelle boucle :

En français En Processing
Commencer à 0 et compter jusqu'à 9 for(int i=0; i<10; i=i+1)
Commencer à 0 et compter jusqu'à 100 par pas de 10 for(int i=0; i<101; i=i+10)
Commencer à 100 et décompter jusqu'à 0 par pas de 5 for(int i=100; i>=0; i=i-5)

En regardant les exemples précédents, on voit que la boucle for est composée de trois parties séparées par des points-virgules :

  • Initialisation Une variable est initialisée pour être utilisée dans le corps de la boucle. En général il s'agit d'une variable utilisée comme compteur dans le corps de la boucle.
  • Test d'expression booléenne Il s'agit du même type de test booléen que ceux utilisés pour les boucles while. Cela peut être n'importe quelle expression qui s'évalue à true ou false.
  • Expression d'itération Le dernier élément est une instruction que nous voulons exécuter à chaque tour de boucle. Elle est toujours exécutée à la fin d'un tour de boucle.

Dans l'exemple suivant, nous exécutons un bloc de code dix fois. Dit autrement, nous comptons de zéro à neuf :


Exemple 10.1. Compter de zéro à neuf avec for.

for(int i = 0; i < 10; i++) {
  // faire des choses ...
}

Voici l'ordre dans lequel les choses sont faites :

  1. Déclarer une variable i.
  2. Tester si la condition i < 10 est vraie, si oui exécuter le bloc de code, sinon s'arrêter.
  3. Exécuter le bloc de code (si le test vaut vrai).
  4. Exécuter la troisième partie du for, c'est-à-dire i++.
  5. Retourner au test de la boucle (pas 2).

Nous avons dit que la boucle while était généraliste, car elle permet de faire tous les types de boucles, et la boucle for est un raccourci pour une certaine forme de boucle while. Voyons donc comment faire la même chose, mais avec cette dernière :


Exemple 10.2. Compter de zéro à neuf avec while.

int i = 0;
while(i < 10) {
  // faire des choses ...
  i++;
}

Voici notre exemple de mille-pattes du cours précédent avec la boucle for qui est particulièrement appropriée pour cela :


Exemple 10.3. Le mille-pattes avec la boucle for.

int xDebut = 50;
int xFin = 150;
int y = 80;
int ecart = 10;
int longueur = 20;

size(200, 200);
for(int x = xDebut; x <= xFin; x += ecart) {
  line(x, y, x, y + longueur);
}

Exercice 10.1. Réécrivez l'exercice 9.1 avec la boucle for.

cercles diagonales camembert

size(200,200);
background(255);
noStroke();
for (___;___;___20) {
  fill(___);
  ellipse(___,___,___,___);
}
size(200, 200);
background(255);
for (___;___;___ + 10) {
  line(___, ___, ___, ___);
  line(___, ___, ___, ___);
}
// camembert
size(200, 200);
background(255);
noStroke();
colorMode(RGB, TWO_PI);
for(float ___; ___; ___) {
  ___;
  ___;
}

Exercice 10.2. Voici quelques exemples de boucles for. Faites correspondre la capture d'écran avec la structure de boucle. Chacun des exemples utilise les même quatre lignes d'initialisation.

size(300,300);
background(255);
stroke(0);
noFill();

1 1 2 2

3 3 4 4

// n° ___
for (int i = 0; i < 10; i++) {
  rect(i * 20, height / 2, 5, 5);
}
// n° ___
for (int i = 0; i < 10; i++) {
  ellipse(width / 2, height / 2, i * 10, i * 20);
}
// n° ___
for (float i = 1.0; i < width; i *= 1.1) {
  rect(0, i, i , i * 2);
}
// n° ___
int x = 0;
for (int c = 255; c > 0; c -= 15) {
  fill(c);
  rect(x, height / 2, 10, 10);
  x = x + 10;
}

Bill Amend

Par Bill Amend

Les variables locales et globales

Jusqu'à présent, nous avons toujours déclaré les variables au début du programme, avant setup() :

int x = 0;

void setup() {
  // ...
}

Cela nous permettait de lister les paramètres essentiels du programme, et de nous focaliser sur leur déclaration, initialisation et usage.

Cependant, les variables peuvent être déclarées partout dans un programme, et nous allons maintenant voir quel est l'intérêt à déclarer des variables ailleurs qu'au début et à choisir l'endroit idéal pour leur déclaration.

Nous allons distinguer les variables en deux catégories : les variables globales et locales.

Imaginez un instant que c'est un programme informatique qui dirige votre vie ! Dans cette vie, les variables sont des valeurs dont vous devez vous souvenir, marquées sur des post-its. Un post-it peut contenir l'adresse d'un bon restaurant pour le prochain dîner. Vous y inscrivez l'adresse le matin et le soir en rentrant après un délicieux plat de tofu grillé au gingembre là-bas, vous pouvez jeter le post-it. En revanche, un post-it peut contenir des informations cruciales, comme par exemple votre numéro de compte en banque. Celui-là, vous le stockez dans un endroit sûr pour des années.

Cet exemple illustre le concept de portée. Certaines variables existent durant l'intégralité du déroulement d'un programme, et on les appellera des variables globales. D'autres n'existent que temporairement, seulement pour un bref moment où leur valeur est nécessaire pour un calcul ou une instruction, et on les appellera des variables locales.

Avec Processing, les variables globales sont déclarées en haut du programme, avant setup() et draw(). Ces variables peuvent être utilisées partout dans un programme. C'est la façon la plus facile d'utiliser une variable, car vous n'avez pas à considérer les endroits où vous pouvez l'utiliser ou non.

Les variables locales sont déclarées à l'intérieur d'un bloc de code, entre des accolades. Nous avons déjà rencontré un certain nombre de tels blocs, avec setup(), draw(), mousePressed(), mais aussi avec les instructions if, while et for.

La règle est la suivante : une variable déclarée au sein d'un bloc de code n'est disponible qu'à l'intérieur de ce bloc. Si vous tentez d'utiliser une variable locale en dehors de son bloc, tout ce que vous obtiendrez, est une insulte de la part de Processing :

"The variable 'nomDeVotreVariable' does not exist"

C'est exactement le même message d'erreur que lorsque vous utilisez une variable que vous avez oublié de déclarer.

Voici un exemple où une variable locale est utilisée dans draw() afin d'exécuter une boucle while :


Exemple 10.4. Une variable locale.

void setup() {
  size(200, 200);
  // la variable x n'est pas utilisable ici.
}

void draw() {
  background(0);
  // Déclaration de la variable locale x.
  int x = 0;
  // x est utilisable dans ce bloc à partir de là.
  // Elle est utilisable dans le bloc du while car
  // ce dernier est imbriqué dans celui de draw.
  while(x < width) {
    stroke(255);
    line(x, 0, x, height);
    x += 5;
  }
}

void mousePressed() {
  // La variable x n'est pas utilisable ici.
  println("Un bouton de la souris est pressé !");
}

La question que vous vous posez certainement est : "pourquoi s'en soucier" ? Si cela est plus facile, pourquoi ne pas toujours utiliser des variables globales ? Ici x aurait tout aussi bien pu être déclarée globalement au début du programme.

Cependant, c'est plus efficace, et certainement moins déroutant de déclarer les variables là où on en a besoin. C'est plus efficace car la mémoire de la variable n'est utilisée que là où c'est nécessaire.

Les boucles for nous offrent l'opportunité d'utiliser des variables locales dans la partie initialisation :

// Ici i est une variable locale au bloc du for.
for (int i = 0; i < 100, i += 10) {
  stroke(255);
  fill(i);
  rect(i, 0, 10, height);
}
// Ici i n'est plus accessible

Utiliser une variable déclarée dans le for n'est pas requis, elle pourrait être déclarée ailleurs, mais c'est très pratique.

Il est en théorie possible de déclarer une variable locale ayant le même nom qu'une variable globale. Dans ce cas, la règle est que la variable locale cache la variable globale dans son bloc de définition. En d'autres termes Processing utilise la variable locale dans le bloc où elle est déclarée. Dans les autres blocs, il utilise la variable globale. Mais ce n'est jamais une bonne idée d'avoir une variable locale ayant le même nom qu'une variable globale. C'est en général source de confusion.


Exercice 10.3. Prédisez le résultat des deux exemples suivants, le fond sera-t-il noir, gris ou blanc ?

// Exemple 1, compte global
int compte = 0;

void setup() {
  size(200, 200);
}

void draw() {
  compte = compte + 1;
  background(compte);
}
// Exemple 2, compte local
void setup() {
  size(200, 200);
}

void draw() {
  int compte = 0;
  compte = compte + 1;
  background(compte);
}

Des boucles dans la boucle principale

Avant de continuer avec les boucles, arrêtons-nous un instant sur l'un des points générant le plus de confusion avec Processing. Considérez la boucle suivante :

size(200, 200);
background(255);

for (int y = 0; y < height; y += 10) {
  stroke(0);
  line(0, y, width, y);
}

lignes horizontales

Disons que nous voudrions dessiner les lignes ci-dessus, mais progressivement, l'une après l'autre, comme si elles étaient animées du haut vers le bas. Notre première idée pourrait-être de transformer le croquis ci-dessus en croquis dynamique avec setup() et draw() :

void setup() {
  size(200, 200);
}

void draw() {
  background(255);
  for(int y = 0; y < height; y += 10) {
    stroke(0);
    line(0, y, width, y);
  }
}

Si nous lisons le code ci-dessus, il pourrait sembler évident que les lignes vont apparaître une à une, au fur et à mesure que nous les dessinons dans le for. Mais si nous nous souvenons bien de ce que nous avons vu lorsque nous avons découvert setup() et draw(), nous savons que Processing ne met à jour l'affichage qu'une fois que draw() se termine (cours 3, "une instruction invisible").

S'il y a donc une chose essentielle à retenir c'est que les boucles au sein de draw() ne servent qu'à préparer une image de l'animation. Il existe une autre boucle cachée, gérée par Processing qui exécute draw() un certain nombre de fois par seconde.

Ainsi, afficher les lignes une par une peut être réalisé à l'aide d'une variable globale en combinaison avec la fait que draw() est naturellement déjà une boucle :


Exemple 10.5. Des lignes une par une.

int y = 0; // pas besoin de boucle for, mais une variable compteur globale.

void setup() {
  size(200, 200);
  background(255);
  // On ralentit afin de voir les lignes se dessiner progressivement.
  frameRate(5);
}

void draw() {
  // Dessine une ligne.
  stroke(0);
  line(0, y, width, y);
  // Incrémente notre compteur y pour le prochain tour de la boucle exécutant draw().
  y += 10;
}

Exercice 10.4. Il est quand-même possible de faire apparaître les lignes horizontales une par une en utilisant une boucle for. Ceci est très utile si en plus des lignes vous avez autres objets qui bougent et vous devez nettoyer votre fenêtre à chaque passage par draw().

Dessinez les lignes une par une en utilisant for. Une partie du code vous est donnée pour démarrer :

int finY;

void setup() {
  size(200, 200);
  frameRate(5);
  finY = ___;
}

void draw() {
  background(255);
  for (int y = ___; ___; ___) {
    stroke(0);
    line(0, y, width, y);
  }
  ___;
}

Mais l'utilisation d'une boucle au sein de draw() nous offre aussi des opportunités d'interactivité ! L'exemple suivant affiche une série de rectangles (de gauche à droite), chacun étant coloré avec une intensité fonction de sa distance à la souris.


Exemple 10.6. Une boucle while simple avec de l'interactivité.

void setup() {
  size(255, 255);
  background(0);
}

void draw() {
  background(0);
  int i = 0;
  while (i < width) {
    noStroke();
    float distance = abs(mouseX - i);
    fill(distance);
    rect(i, 0, 10, height);
    i += 10;
  }
}

Interactivité


Exemple 10.7. Chip est un Cylon

Nous pouvons tirer parti de ce que nous avons appris avec les boucles for et while au sein de draw() (qui est elle-même une boucle) pour réaliser des dessins plus complexes sans pour autant écrire plus de code. Chip par ce biais est devenu un peu plus inquiétant :

float chipX;
float chipY;
float couleur = 0;
float vitesse = 1;

void setup() {
  size(400, 400);
  frameRate(30);
  chipX = 35;
  chipY = height / 2;
}

void draw() {
  background(255);

  // dessiner Chip
  // le corps
  stroke(0);
  strokeWeight(1);
  fill(191);
  rect(chipX - 35, chipY, 70, 70);

  // chip k2000
  for (float x = chipX - 30; x < chipX + 30; x += 4) {
    fill(couleur, 0, 0);
    rect(x, chipY + 10, 4, 8);
    couleur = (couleur + 15) % 255;
  }
  couleur += 15;

  // les roues
  stroke(0);
  fill(255);
  ellipse(chipX - 20, chipY + 70, 20, 20);
  ellipse(chipX + 20, chipY + 70, 20, 20);

  // la tête
  fill(191);
  arc(chipX, chipY - 5, 70, 70, -PI, 0, CHORD);

  // la caméra
  fill(0);
  ellipse(chipX, chipY - 20, 20, 20);

  // l'antenne
  strokeWeight(3);
  line(chipX, chipY - 40, chipX, chipY - 80);

  // Chip bouge
  chipX += vitesse;
  if (chipX < 35 || chipX > width - 35) {
    vitesse = -vitesse;
  }
}

Exercice 10.5. Créez une grille de carrés chacun colorés aléatoirement en utilisant la boucle for. Indice : Vous aurez besoin de deux boucles for. Réécrivez cet exercice avec la boucle while.

carrés multicolores


Exercice 10.6. Dessinez un damier de taille N x N, où N est une constante symbolique :

final int N = ___;

La case en bas à gauche doit être toujours noire. Votre sketch doit marcher pour différentes valeurs de N en ne changeant que la ligne ci-dessus. Il doit également marcher pour différentes tailles de la fenêtre en ne changeant que les paramètres de size().

N = 8 8x8 N = 11 11x11


Exercice 10.7. (facultatif) Le rugby a un système de scores différent de la plupart des sports. Au lieu d'avancer point par point, le score avance :

  • de 3 points pour un drop ou une pénalité ;
  • de 5 points pour un essai non transformé ;
  • de 7 points pour un essai transformé.

Calculez le nombre de façons dont un score donné peut être réalisé. Par exemple, il y a 6 façons différentes de faire le score 28 :

Drops/Pénalités x3 Essais non transf x5 Essais transf x7
1 0 0 4
2 1 5 0
3 2 3 1
4 3 1 2
5 6 2 0
6 7 0 1

Exercice 10.8. Ajoutez quelque-chose à votre créature en utilisant une boucle for ou while. Y-a-t'il des éléments de votre créature qui pourraient être réécris plus efficacement avec une boucle ?


⚠️ **GitHub.com Fallback** ⚠️