cours12 - sbalev/processing101 GitHub Wiki

Cours 12

Passer une copie

Souvenez-vous de notre exemple de jeu de frisbee. Nous n'avons pas complètement décrit ce qui se passe. Ce que nous aurions du dire est : Vous avez le frisbee (le paramètre), mais avant de l'envoyer à votre ami (la fonction), vous en faite une copie (un second frisbee, identique au premier), et envoyez la copie à votre ami (la fonction).

À chaque fois que vous passez une valeur primitive (un entier, un flottant, un caractère, un booléen...) à une fonction, vous ne passez pas en réalité la valeur, mais une copie de cette valeur. Ceci n'a pas réellement d'importance si vous écrivez la valeur littéralement dans le code, mais cela à une grande importance si vous passez la valeur par le biais d'une variable. Dans ce cas, vous copiez le contenu de la variable, plutôt que de passer la variable.

L'exemple suivant, est un "aléatoireur". Il reçoit un argument (une nombre réel) et lui ajoute un nombre aléatoire entre -2 et 2. Voici le pseudo-code :

  • n est le nombre 10.
  • n est affiché : 10.
  • une copie de n est passée dans l'argument nouveauN de la fonction aleatoirer().
  • Dans la fonction aleatoirer() :
    • Un nombre aléatoire est ajouté à nouveauN.
    • nouveauN est affiché : 10.42424
    • n est affiché : 10 ! à nouveau ! une copie avait été passée à aleatoirer(), n n'a pas été changé.

Voici le code :

void setup() {
  float n = 10;
  println("Le nombre est : " + n);
  aleatoirer(n);
  println("Le nombre est : " + n);
}

void aleatoirer(float nouveauN) {
  nouveauN = nouveauN + random(-2, 2);
  println("Le nouveau nombre est : " + nouveauN);
}

Bien que la variable n soit passée dans la variable nouveauN, dont on change par la suite la valeur, la valeur originale de n est restée sans changement. En effet, nous avons copié le contenu de n dans nouveauN.

Cela peut se qualifier de passage de paramètre par copie ou plus communément passage par valeur. Cela est vrai pour tous les types de variable que nous connaissons jusqu'à présent (float, int, boolean...), mais cela ne sera pas vrai quand nous découvrions les objets dans les prochains cours.

Cet exemple nous permet aussi de réviser la façon dont le flux de code est exécuté dans un programme qui appelle des fonctions. Vous avez certainement noté la façon dont les messages s'affichent sur la console. En effet quand une fonction est appelée, l'exécution quitte la ligne courante, saute dans la fonction, exécute les instructions de celle-ci, et revient là où elle était. Voici une description de la séquence d'exécution du programme ci-dessus :

  1. Placer n à 10.
  2. Afficher la valeur de n.
  3. Appeler la fonction aleatoirer()
    • a) Ajouter un nombre aléatoire dans nouveauN.
    • b) Afficher la valeur de nouveauN.
  4. Afficher la valeur de n.

Exercice 12.1. Prédisez les sorties sur la console du programme suivant sans l'exécuter. Vérifiez ensuite vos prédictions.

void setup() {
  println("a");
  fonction1();
  println("b");
}

void draw() {
  println("c");
  fonction2();
  println("d");
  fonction1();
  // La fonction `noLoop()` empêche Processing d'appeler `draw()` en boucle.
  // `draw()` ne sera donc exécutée qu'une fois.
  noLoop();
}

void fonction1() {
  println("e");
  println("f");
}

void fonction2() {
  println("g");
  fonction1();
  println("h");
}

Type de retour

Jusqu'à présent nous avons vu comment les fonctions nous permettent de découper nos croquis en parties et peuvent les rendre plus réutilisables par le biais des arguments. Mais nous n'avons toujours pas dit à quoi correspondait void. Souvenez-vous de la syntaxe d'une fonction :

typeDeRetour nomDeLaFonction(arguments) {
  // code
}

Jusqu'à présent toutes nos fonctions ont eu comme type de retour void. En fait cela signifie qu'elle n'ont pas de type de retour ! Mais que voudrais dire d'en avoir un ?

Souvenez-vous de l'utilisation de la fonction random(). Nous l'appelons pour lui demander un nombre aléatoire entre 0 et une valeur en paramètre, et, gracieusement, elle nous renvoie un nombre aléatoire compris dans cet intervalle. En d'autres termes, la fonction random() retourne une valeur. Quel est le type de cette valeur ? Un nombre flottant. On dira donc dans le cas de random() que le type de retour est float.

Le type de retour est le type des valeurs que la fonction nous renvoie. Bien sûr dans le cas de random() ce n'est pas nous qui avons spécifié le type de retour, ce sont les créateurs de Processing qui on fait cela. C'est décrit, comme toujours, dans la documentation en ligne de Processing, à la page pour random() :

Each time the random() function is called, it returns an unexpected value within the specified range. If one parameter is passed to the function, it will return a float between zero and the value of the parameter. The function call random(5) returns values between 0 and 5. If two parameters are passed, it will return a float with a value between the parameters. The functon call random(-5, 10.2) returns values between -5 and 10.2.

Chaque fois que la fonction random() est appelée, elle retourne une valeur inattendue dans l'intervalle spécifié. Si un paramètre est passé à la fonction, elle retourne un flottant entre zero et la valeur de ce paramètre. L'appel à random(5) retourne des valeurs entre 0 et 5. Si deux paramètres sont passés, elle retourne un flottant ayant une valeur entre ces paramètres. L'appel de fonction random(-5, 10.2) retourne des valeurs entre -5 et 10.2.

Source http://www.processing.org/reference/random_.html

Si nous désirons écrire nos propres fonctions avec une valeur de retour, nous devons spécifier le type de retour dans la définition de la fonction. Essayons avec un exemple simple :

int sum(int a, int b, int c) {
  int total = a + b + c;
  return total;
}

Au lieu d'écrire void, nous avons écrit int. Cela spécifie que cette fonction doit retourner une valeur de type entier. De façon à effectivement retourner la valeur nous utilisons le mot clé return. Celui-ci à la syntaxe suivante :

return valeurARetourner;

On peut utiliser une expression dans return. Ainsi on peut simplifier notre fonction :

int sum(int a, int b, int c) {
  return a + b + c;
}

Dans une fonction avec un type de retour non void, le mot clé return est nécessaire. Si nous l'avions oublié, Processing nous aurait insulté avec un message d'erreur :

This method must return a result of type int

Dès que l'instruction return est exécutée, le programme sort de la fonction et envoie la valeur retournée à l'endroit du code où la fonction à été appelée. Cette valeur peut alors être utilisée dans une affectation de variable par exemple ou partout où une valeur du type de retour est attendue (dans notre exemple partout où on peut mettre un entier).

Voici quelques exemples :

int x = sum(5, 6, 8);
int y = sum(8, 9, 10) * 2;
int z = sum(x, y, 40);
line(100, 100, 100, sum(x, y, z));

Et vous vous en doutez, on va reparler de frisbee ! C'est la dernière fois ! On pourrait décrire les choses ainsi : une fois que votre ami (la fonction) à reçu le frisbee, il écrit une valeur sur un post-it (la valeur de retour), le colle sur le frisbee et vous le renvoie !

Les fonctions qui retournent des valeurs sont traditionnellement utilisées pour réaliser des calculs complexes que l'on pourraient avoir à faire plusieurs fois dans un ou plusieurs programmes. Un exemple commun est le calcul de la distance entre deux points (x1,y1) et (x2,y2). Ce genre de calcul est souvent utilisé dans les applications interactives, et nous l'avons déjà utilisé dans plusieurs exercices. C'est d'ailleurs la raison pour laquelle Processing possède une fonction prédéfinie nommée dist().

float d = dist(100, 100, mouseX, mouseY);

La ligne précédente par exemple calcule la distance entre le point (100,100) et la position de la souris.

Pour le moment imaginons que Processing n'a pas la fonction dist(). Sans elle, nous serions obligé de faire le calcul de la distance en utilisant le théorème de Pythagore :

float dx = mouseX - 100;
float dy = mouseY - 100;
float d = sqrt(dx * dx + dy * dy);

Mais si nous devons calculer plusieurs fois la distance, entre des points différents, une fonction devient essentielle :

float distance(float x1, float y1, float x2, float y2) {
  float dx = x1 - x2;
  float dy = y1 - y2;
  float d = sqrt(dx*dx + dy*dy);
  return d;
}

Voyons l'utilisation de cette fonction, comme si Processing n'avait pas dist() :


Exemple 12.1.

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

void draw(){
  background(0);
  stroke(0);
  int w2 = width / 2;
  int h2 = height / 2;

  // Carré haut-gauche.
  fill(distance(0, 0, mouseX, mouseY));
  rect(0, 0, w2 - 1, h2 - 1);

  // Carré haut droit.
  fill(distance(width, 0, mouseX, mouseY));
  rect(w2, 0, w2 - 1, h2 - 1);

  // Carré bas gauche.
  fill(distance(0, height, mouseX, mouseY));
  rect(0, h2, w2 - 1, h2 - 1);

  // Carré bas droit.
  fill(distance(width, height, mouseX, mouseY));
  rect(w2, h2, w2 - 1, h2 - 1);
}

float distance(float x1, float y1, float x2, float y2) {
  float dx = x1 - x2;
  float dy = y1 - y2;
  float d = sqrt(dx * dx + dy * dy);
  return d;
}

Exercice 12.2. Écrivez une fonction qui prend un argument f en degrés Fahrenheit et calcule sa valeur en degrés Celsius. La formule est la suivante c = (f - 32) * 5.0 / 9.0.

___ fahrenheitVersCelsius(float ___) {
    ___ ___ = ____
    ___
}

Écrivez une autre fonction qui convertit des degrés Celsius en Fahrenheit. Qu'attendez vous de ce code ?

float c = 20;
float c1 = fahrenheitVersCelsius(celsiusVersFahrenheit(c));

Testez.

Felsius

Source xkcd


Exercice 12.3. Complétez le sketch suivant :

int r = 0;
int g = 0;
int b = 0;

void dessinerBouton(int x, int y, int taille, color c) {
  // TODO Dessiner un bouton de taille 'taille' et couleur c
  // dont le coin supérieur gauche est en (x, y)
}

boolean boutonPresse(___, ___, ___) {
  // TODO renvoie 'true' si le bouton est pressé
}

void setup() {
  size(400, 400);
  frameRate(30);
}

void draw() {
  background(r, g, b);
  int w2 = width / 2;
  int h2 = height / 2;

  dessinerBouton(25, h2, 50, color(255, 0, 0));
  dessinerBouton(w2 - 25, h2, 50, color(0, 255, 0));
  dessinerBouton(width - 75, h2, 50, color(0, 0, 255));

  if (boutonPresse(25, h2, 50)) {
    r++;
  } else {
    r--;
  }
  if (boutonPresse(w2 - 25, h2, 50)) {
    g++;
  } else {
    g--;
  }
  if (boutonPresse(width - 75, h2, 50)) {
    b++;
  } else {
    b--;
  }
  r = constrain(r, 0, 255);
  g = constrain(g, 0, 255);
  b = constrain(b, 0, 255);
}

Exercices supplémentaires / préparation d'examen


Exercice 12.4.

  1. Écrivez une fonction dessinerDe() qui ... dessine un dé. La position, la taille et la valeur du dé seront des arguments de cette fonction. Par exemple le code suivant
void setup() {
  size(200, 200);
  dessinerDe(50, 50, 100, int(random(1, 7)));
}

void draw() {
}

doit dessiner quelque chose comme :
dé

  1. Changez setup() pour qu'il dessine une grille de 2 x 3 dés :
    dés

  2. Faites en sorte que lorsque l'utilisateur clique sur l'un des dés de la grille, sa valeur change aléatoirement.


Exercice 12.5. Voici un sketch qui modélise une situation délicate impliquant une voiture autonome et un piéton.

float xPieton, yPieton;
float tPieton = 15;
float vPieton = 0.4;

float xVoiture, yVoiture;
float tVoiture = 50;
float vVoiture = 0;
float aVoiture = 0.05;
float vMax = 5;

void dessinerPaysage() {
  background(#9CBA7F);
  // route
  rectMode(CENTER);
  noStroke();
  fill(0);
  rect(width / 2, height / 2, width, tVoiture);
  // passage pieton
  fill(255);
  for (float y = (height - tVoiture) / 2 + 5; y < (height + tVoiture) / 2; y += 10) {
    rect(width / 2, y, tVoiture, 5);
  }
}

void dessinerPieton() {
  ellipseMode(CENTER);
  noStroke();
  fill(127);
  ellipse(xPieton, yPieton, tPieton, tPieton);
}

void bougerPieton() {
  yPieton += vPieton;
  if (yPieton > height) {
    yPieton = 0;
  }
}

void dessinerVoiture() {
  rectMode(CENTER);
  noStroke();
  fill(#ADD8E6);
  rect(xVoiture, yVoiture, tVoiture, tVoiture / 2);
}

void accelerer() {
  vVoiture = constrain(vVoiture + aVoiture, 0, vMax);
}

void freiner() {
  vVoiture = constrain(vVoiture - aVoiture, 0, vMax);
}  

void bougerVoiture() {
  xVoiture += vVoiture;
  if (xVoiture > width) {
    xVoiture = 0;
  }
  accelerer();
}


void setup() {
  size(600, 200);
  xPieton = width / 2;
  yPieton = 0;
  xVoiture = 0;
  yVoiture = height / 2;
}

void draw() {
  dessinerPaysage();
  dessinerPieton();
  dessinerVoiture();
  bougerPieton();
  bougerVoiture();
}
  1. Changez le code du piéton pour qu'il arrête de bouger et change d'apparence lorsqu'il se fait écraser par la voiture, par exemple :
    Oups !
    Les traces de pneus sont facultatives.
    Indication : Une variable booléenne pourrait s'avérer utile :
boolean pietonVivant = true;
  1. Réparez le pilote automatique de la voiture pour qu'il s'arrête devant le piéton et évite ce genre de tragédies. Attention, une voiture ne peut pas s'arrêter instantanément, mais vous êtes autorisés à utiliser la fonction freiner().
    IA en action

Exercice 12. 6.

  1. Dessinez un échiquier de taille 8 x 8 (voir l'exercice 10.6).

  2. Dessinez un cavalier sur l'une des cases :
    Cavalier moche
    Ne perdez pas trop de temps sur la beauté du cavalier.

  3. Si le pointeur de la souris est sur une case accessible à partir de la position courante du cavalier, entourez cette case en rouge.

  4. Lorsque l'utilisateur clique sur une case accessible, déplacez le cavalier sur celle-ci.


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