singe/docs/fr/lessons/04-repeating.adoc

447 lines
18 KiB
Text
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[#_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()`.