[#_lesson_4_doing_it_again] == Leçon 4 : Recommencer image::learn/04-repeating.png[La leçon terminée, 480] Vous avez dessiné un mot et une boîte. Un jeu a besoin de cent choses : des étoiles, des briques, des balles, les lignes d'un menu. Taper cent lignes n'est pas la réponse, et si ça l'était, il vous faudrait en taper cent autres le jour où vous en voudriez cent une. Cette leçon est la réponse. À la fin, un champ d'étoiles descendra lentement dans votre fenêtre, chacune dessinée par les cinq mêmes lignes de code. [#_start_a_new_folder_3] === Créez un nouveau dossier Un dossier appelé `repeating`, dedans un fichier appelé `repeating.singe`, et `Singe -R repeating` qui tourne dans un terminal. [#_forty_dots] === Quarante points Tapez ceci et lancez-le. [source,lua] ---- function onOverlayUpdate() overlayClear() colorForeground(255, 255, 255) for star = 1, 40 do overlayPlot(star * 8, 100) end return OVERLAY_UPDATED end ---- Une rangée de quarante points blancs en travers du milieu de la fenêtre. `overlayPlot` est l'appel de dessin le plus simple qui soit : il met un pixel à la couleur de premier plan actuelle, et c'est exactement ce qu'il faut à une étoile. Tout ce qui est intéressant ici, ce sont les trois lignes autour. [source,lua] ---- for star = 1, 40 do ---- Ça dit : faites ce qui suit quarante fois, et à chaque tour, `star` contient un nombre différent -- un la première fois, deux la deuxième, quarante la dernière. Les deux bouts sont compris, donc `1, 40` fait vraiment quarante tours et pas trente-neuf. Cette forme est une *boucle*, et celle-ci en particulier est une *boucle for numérique*. `star` est un nom que vous inventez, comme vous avez inventé `frames` à la leçon deux, et la boucle le remplit pour vous. Vous ne le réglez pas, et vous ne devriez pas le changer dans la boucle. Il n'existe qu'entre le `for` et son `end` ; demandez-le après et vous obtenez `nil`. Le `do` est obligatoire et facile à oublier. Omettez-le et Lua dit `'do' expected near`, suivi de ce qu'il a trouvé à la place, qui sera la première chose de la ligne suivante. [source,lua] ---- overlayPlot(star * 8, 100) ---- Le corps. C'est ce qui s'exécute quarante fois, et c'est la même ligne à chaque fois -- ce qui change, c'est `star`, donc le premier point tombe à un x de 8, le deuxième à 16, et le dernier à 320. Une boucle n'est utile que si le corps fait quelque chose avec le numéro de la boucle, et la première chose que vous chercherez à faire à chaque fois, c'est de l'arithmétique avec. Le corps est indenté d'un cran de plus que le `for`, pour la même raison que tout ce qui a été indenté jusqu'ici. [source,lua] ---- end ---- Ferme la boucle. Vous avez maintenant rencontré trois choses qui ont besoin d'un `end` : `function`, `if` et `for`. Elles marchent toutes de la même façon, elles s'emboîtent les unes dans les autres, et c'est grâce à l'indentation que vous savez à quoi appartient chaque `end`. [#_counting_by_more_than_one] === Compter de plus d'un à la fois La boucle compte de un en un, sauf si vous lui dites autre chose. Ajoutez un troisième nombre et elle avance de ce nombre-là à la place. [source,lua] ---- for x = 0, overlayGetWidth() - 1, 10 do overlayPlot(x, 100) end ---- Lisez-le *de, à, par pas de*. Celle-ci fait 0, 10, 20, et ainsi de suite, et s'arrête au dernier pixel de la surface ou avant, ce qui dessine une ligne pointillée tout du long, quelle que soit la taille de la fenêtre. C'est la même rangée de points qu'avant, décrite par l'endroit où ils vont plutôt que par leur nombre. Le pas peut être négatif, ce qui compte à rebours. [source,lua] ---- for countdown = 10, 1, -1 do ---- Dix, neuf, huit, jusqu'à un. Avec un pas négatif, le deuxième nombre est le plancher plutôt que le plafond, ce qui est bien ce que vous attendez, et se tromper de signe donne une boucle qui ne s'exécute jamais : `for i = 10, 1 do` sans pas compte en montant de dix vers un, a déjà dépassé la fin avant de commencer, et ne fait rien. Pas d'erreur, rien à l'écran. Si une de vos boucles semble ne jamais s'exécuter, vérifiez d'abord le sens. Le pas peut aussi être une fraction, même si vous en aurez besoin moins souvent que vous ne le pensez. [#_one_loop_inside_another] === Une boucle dans une autre Le corps d'une boucle est du code ordinaire, et du code ordinaire peut contenir une boucle. [source,lua] ---- for row = 0, overlayGetHeight() - 1, 20 do for column = 0, overlayGetWidth() - 1, 20 do overlayPlot(column, row) end end ---- Ça dessine une grille de points sur toute la fenêtre. Mettez-la dans votre `onOverlayUpdate` à la place de la rangée et regardez-la, parce que la façon dont elle s'exécute vaut la peine d'être bien comprise maintenant plutôt qu'à la leçon onze, quand quelque chose en dépendra. La boucle extérieure s'exécute une fois avec `row` à 0. À l'intérieur, toute la boucle intérieure s'exécute du début à la fin -- dix-huit colonnes, dix-huit points, une rangée de la grille. Ce n'est qu'une fois la boucle intérieure terminée que la boucle extérieure passe à `row` à 20 et exécute de nouveau toute la boucle intérieure. Le corps de la boucle intérieure s'exécute donc dix-huit fois douze fois, soit deux cent seize fois, pour douze tours de la boucle extérieure. C'est cette multiplication qui fait des boucles imbriquées le moyen de remplir un rectangle de n'importe quoi, et c'est aussi pourquoi vous devriez jeter un œil aux nombres avant d'en imbriquer trois. Remarquez que la position de chaque point a besoin des deux variables de boucle : `column` pour le sens horizontal et `row` pour le sens vertical. C'est ça, le modèle. Si le corps intérieur n'utilise que la variable intérieure, la boucle intérieure dessine la même chose encore et encore au même endroit, et vous avez écrit une façon lente de le faire une seule fois. [#_when_you_do_not_know_how_many] === Quand vous ne savez pas combien Une boucle `for` a besoin de savoir combien de fois avant de commencer. Parfois vous ne le savez pas, et il vous faut alors l'autre sorte. [source,lua] ---- x = 0 while x < overlayGetWidth() do overlayPlot(x, 100) x = x + 10 end ---- Une boucle `while` vérifie une question avant chaque tour, exactement le genre de question que poserait un `if`, et continue tant que la réponse est vraie. Quand la réponse est fausse, elle s'arrête et le programme reprend à la ligne après le `end`. Si la réponse est fausse dès la toute première fois, le corps ne s'exécute jamais. Cet exemple dessine la même ligne pointillée que le `for` avec un pas de dix, et il lui faut trois lignes pour faire ce que `for` faisait en une, alors utilisez `for` pour ça. Voici la différence qui tranche : * Utilisez `for` quand le nombre de tours est connu avant de commencer. Quarante étoiles. Un pixel sur dix en largeur. Chaque rangée d'une grille. * Utilisez `while` quand vous attendez que quelque chose devienne vrai et ne pouvez pas dire à l'avance combien de temps ça prendra. Distribuer des cartes jusqu'à ce que le paquet soit vide. Redemander jusqu'à ce que le joueur tape quelque chose de valable. Parcourir une liste jusqu'à trouver ce que vous cherchiez. Un `while` a trois parties, et vous devez écrire les trois vous-même : préparer la variable avant la boucle, la tester dans le `while`, et la changer dans le corps. La boucle `for` fait les trois pour vous, c'est pourquoi c'est elle qu'il faut prendre quand elle convient. Oubliez la troisième partie d'un `while` et la question ne cesse jamais d'être vraie, ce que vous ferez exprès à la fin de cette leçon. [#_a_star_made_out_of_its_own_number] === Une étoile faite de son propre numéro Maintenant, le champ d'étoiles, et un problème qui façonne le reste de la première partie. Chaque étoile a besoin de son propre x et de son propre y, et vous n'avez aucun moyen de garder cent variables séparées -- écrire `star1X`, `star2X` et ainsi de suite jusqu'à cent est pire que de taper les cent lignes que vous vouliez éviter. Alors ne les rangez pas. Calculez chacune à partir de la seule chose qui distingue une étoile de ses voisines : son numéro. [source,lua] ---- starX = (star * 37) % width ---- Le `%` est le dernier des opérateurs arithmétiques, et c'est le *reste* : ce qui reste après une division. Quinze divisé par quatre fait trois, et il reste trois, donc `15 % 4` vaut 3. Sa propriété utile, c'est que la réponse ne peut jamais être aussi grande que le nombre de droite, donc un reste par `width` est toujours une position quelque part sur l'écran, quelle que soit la taille du côté gauche. L'étoile 1 va à 37, l'étoile 5 à 185, l'étoile 10 à 10 -- 370 a fait le tour au-delà de 360 et est revenu par la gauche. Trente-sept est choisi parce qu'il n'a aucun facteur commun avec 360, et c'est ce qui empêche les étoiles de tomber en bandes bien nettes ; essayez 36 à la place et vous verrez le problème tout de suite. La dérive vers le bas, c'est le même truc avec un nombre qui grandit avec le temps. [source,lua] ---- drift = 0 function onOverlayUpdate() drift = drift + 1 ... starY = (star * 61 + math.floor(drift * layer * 0.5)) % height ---- `drift` vit en dehors de la fonction et grimpe à chaque image, exactement comme `frames` à la leçon deux. L'ajouter au y de chaque étoile fait descendre tout le champ d'un bloc, et le `%` ramène en haut une étoile qui tombe par le bas, pour toujours, gratuitement. `math.floor` jette la fraction d'un nombre et renvoie la partie entière en dessous : `math.floor(7.9)` vaut 7. La leçon deux faisait remarquer que Singe le fait pour vous quand il dessine, et c'est vrai, mais le faire vous-même veut dire que le nombre avec lequel vous travaillez est le nombre qui apparaît à l'écran. Ça comptera à la leçon sept, quand deux choses devront être d'accord pour savoir si elles se touchent. Remarquez que `layer`, là-dedans, est une deuxième variable de boucle. Les étoiles sont dessinées par une boucle dans une boucle, et l'extérieure n'est pas une rangée de grille mais une *distance* : trois couches d'étoiles, chacune dérivant à sa propre vitesse et avec sa propre luminosité, et c'est ce qui donne de la profondeur à un champ de points tout plat. [#_chance] === Le hasard Les étoiles scintillent. Scintiller, c'est avoir une luminosité un peu différente à chaque image, et « un peu différente », c'est `math.random`. [source,lua] ---- shade = math.random(60, 90) + layer * 55 colorForeground(shade, shade, shade) ---- `math.random(60, 90)` renvoie un nombre entier de 60 à 90, les deux compris, choisi de nouveau à chaque appel. Appelez-la sans aucun argument et vous obtenez à la place une fraction entre 0 et 1, et c'est la forme à utiliser quand vous voulez que quelque chose arrive une fois sur cinq. Vous n'avez pas à préparer le générateur ; Lua s'en charge quand Singe démarre. Ajouter `layer * 55` sort les couches proches de la plage sombre, si bien que les étoiles lointaines restent pâles et que les proches sont presque blanches. Autant de rouge, de vert et de bleu, comme la leçon trois l'a expliqué, donne un gris. Comme `shade` est tiré de nouveau à chaque image pour chaque étoile, chaque étoile clignote de son côté, ce qui, à soixante images par seconde, passe pour un scintillement plutôt que pour un défaut. [#_the_whole_thing_3] === Le tout [source,lua] ---- LAYERS = 3 STARS_PER_LAYER = 40 ACROSS_STEP = 37 DOWN_STEP = 61 LAYER_SHIFT = 13 LAYER_SPEED = 0.5 LAYER_BRIGHT = 55 width = overlayGetWidth() height = overlayGetHeight() drift = 0 function onOverlayUpdate() drift = drift + 1 overlayClear() for layer = 1, LAYERS do for star = 1, STARS_PER_LAYER do starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height shade = math.random(60, 90) + layer * LAYER_BRIGHT colorForeground(shade, shade, shade) overlayPlot(starX, starY) end end return OVERLAY_UPDATED end ---- Cent vingt étoiles, dessinées par cinq lignes, et le fichier est plus court que celui de la leçon trois. C'est aussi le bout de ce que cette approche peut faire, et ça vaut la peine d'être honnête sur la raison pendant que vous l'avez sous les yeux. Chaque étoile, là-dedans, est une formule. Rien d'une étoile n'est *mémorisé* : vous ne pouvez pas en rendre une plus brillante que sa voisine et qu'elle le reste, vous ne pouvez pas en faire tomber une du ciel, et vous ne pouvez pas laisser le joueur en ramasser une, parce qu'il n'y a nulle part où noter qu'il lui est arrivé quelque chose. L'étoile dix-sept n'est pas une chose. C'est un nombre qui passe brièvement par une variable appelée `star`. Dès que vous voulez cent choses qui se souviennent chacune de quelque chose qui leur est propre, il vous faut un endroit où ranger cent ensembles de faits. C'est la leçon six, et le champ d'étoiles est la raison pour laquelle elle existe. [#_try_it_4] === Essayez . *Plus et moins.* Essayez `STARS_PER_LAYER = 200`, puis `4`. Puis `LAYERS = 8`, et regardez ce que `layer * LAYER_BRIGHT` fait à `shade` quand le numéro de couche devient grand. Les nombres que prend `colorForeground` s'arrêtent à 255. . *Changez l'espacement.* Mettez `ACROSS_STEP` à 36 et lancez. Les étoiles se rangent en bandes, parce que 36 tient exactement dix fois dans 360. Remettez-le à 37 et essayez 71 et 90. . *Arrêtez le scintillement.* Remplacez la ligne `math.random` par un simple `shade = layer * 70` et comparez. Décidez ce que vous préférez ; il n'y a pas de bonne réponse, et remarquer que vous avez une préférence fait partie du métier. . *Faites-les tomber vers le haut.* Un seul caractère. . *Dessinez aussi la grille.* Remettez la boucle de grille imbriquée de tout à l'heure, après les étoiles, dans une couleur sombre. Deux boucles imbriquées dans une seule fonction, qui dessinent deux choses différentes, et la seconde ne dérange pas la première. [#_break_it_on_purpose_4] === Cassez-le exprès Jusqu'ici, chaque erreur était un message. Celle-ci n'en est pas un, et c'est justement pour ça que vous devriez la rencontrer pendant que vous vous y attendez. Mettez ceci dans votre `onOverlayUpdate`, juste sous `overlayClear()`, lancez-le, et lisez le paragraphe suivant avant de faire quoi que ce soit d'autre. [source,lua] ---- x = 0 while x < width do overlayPlot(x, 100) end ---- La ligne qui fait avancer `x` manque, donc `x` reste à zéro, donc `x < width` ne cesse jamais d'être vrai. La boucle tourne pour toujours, en dessinant le même pixel, et elle est dans `onOverlayUpdate`, donc `onOverlayUpdate` ne se termine jamais. Singe attend votre fonction. Il ne peut pas dessiner, il ne peut pas lire le clavier, et il ne peut pas quitter. Ce que vous voyez, c'est une fenêtre qui ne bouge plus. Échap ne fait rien. Q ne fait rien. Rien n'est écrit non plus. Allez dans le terminal où vous avez lancé Singe et appuyez sur *Ctrl* et *C* en même temps. Singe arrête votre boucle et vous dit où elle en était : ---- Stopped: the script did not return, and Singe was asked to quit. stack traceback: repeating/repeating.singe:20: in function 'onOverlayUpdate' ---- Il peut le dire deux fois, à quelques secondes d'intervalle, avant de quitter. Une fois suffit largement. La ligne 20 est le `end` de votre `while`, c'est là que la boucle tournait quand vous l'avez arrêtée. Retirez la boucle, enregistrez, et relancez Singe. C'est un rapport, pas une erreur. Rien ne s'est plaint avant que vous le demandiez, parce que rien ne s'est mal passé, du point de vue de l'ordinateur. Vous avez demandé une boucle qui tourne tant que `x` est plus petit que la largeur, et c'est précisément ce que vous avez obtenu. C'est la seule sorte d'erreur pour laquelle les messages d'erreur ne peuvent rien pour vous, et le remède est une habitude : chaque fois que vous écrivez un `while`, écrivez la ligne qui change la variable avant d'écrire quoi que ce soit d'autre dans le corps. [#_what_you_learned_4] === Ce que vous avez appris * `for name = first, last do ... end` exécute son corps une fois pour chaque nombre du premier au dernier, les deux compris. * Un troisième nombre est le pas, et il peut être négatif pour compter à rebours. * La variable de boucle n'existe qu'à l'intérieur de la boucle, et le corps devrait s'en servir. * `for` a besoin d'un `do`, et chaque boucle a besoin d'un `end`. * Une boucle dans une boucle exécute toute la boucle intérieure à chaque tour de l'extérieure, et c'est comme ça qu'on remplit un rectangle. * `while question do ... end` recommence tant que la question est vraie, et c'est à vous de changer quelque chose dans le corps. * Utilisez `for` quand vous savez combien de fois, et `while` quand vous attendez que quelque chose devienne vrai. * `%` est le reste, et il garde un nombre qui grandit à l'intérieur d'une plage. * `math.random(a, b)` donne un nombre entier de a à b ; `math.floor` jette la fraction. * Une boucle qui ne finit jamais fige tout le jeu sans message d'erreur, et *Ctrl* et *C* dans le terminal, c'est comme ça qu'on en sort. [#_next_time_4] === La prochaine fois Regardez encore ce `onOverlayUpdate`. Il dessine des étoiles, il calcule des positions, il choisit des couleurs, et il commence à être une fonction qui fait quatre travaux au lieu d'un. À la leçon cinq, vous écrivez vos propres fonctions, vous leur donnez des arguments comme les fonctions de Singe en prennent, et vous en obtenez des réponses -- et le champ d'étoiles devient une seule ligne qui dit `drawStars()`.