447 lines
18 KiB
Text
447 lines
18 KiB
Text
[#_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()`.
|