444 lines
17 KiB
Text
444 lines
17 KiB
Text
[#_lesson_2_numbers_that_change]
|
||
== Leçon 2 : Des nombres qui changent
|
||
|
||
image::learn/02-numbers.png[La leçon terminée, 480]
|
||
|
||
À la leçon un, vous avez mis un mot à l'écran et il est resté là. À la fin de
|
||
cette leçon, le même mot glissera dans toute la fenêtre, fera demi-tour quand il
|
||
atteindra un bord, et il le fera grâce à quatre nombres que le programme change
|
||
soixante fois par seconde.
|
||
|
||
En chemin, vous rencontrerez l'idée qui fait trébucher plus de débutants que
|
||
toute autre : l'endroit où vous mettez une chose décide de combien de temps elle
|
||
vit.
|
||
|
||
[#_start_a_new_folder]
|
||
=== Créez un nouveau dossier
|
||
|
||
Créez un dossier appelé `numbers` à côté de votre dossier `hello`, et dans
|
||
celui-ci un fichier appelé `numbers.singe`. Tapez-y le programme de la leçon un,
|
||
ou copiez `hello.singe` et renommez-le -- les deux conviennent, et le retaper
|
||
n'est pas du temps perdu.
|
||
|
||
[source,lua]
|
||
----
|
||
function onOverlayUpdate()
|
||
overlayClear()
|
||
overlayPrint(2, 2, "Hello!")
|
||
return OVERLAY_UPDATED
|
||
end
|
||
----
|
||
|
||
Lancez-le avec `Singe -R numbers` et laissez-le tourner. Grâce au `-R` de la fin
|
||
de la leçon un, vous n'aurez plus à le relancer de toute cette leçon ;
|
||
enregistrez le fichier et la fenêtre se met à jour toute seule.
|
||
|
||
[#_count_the_frames]
|
||
=== Comptez les images
|
||
|
||
La leçon un disait que Singe appelle `onOverlayUpdate` une soixantaine de fois
|
||
par seconde. C'est facile à dire et difficile à croire, alors comptez-les.
|
||
|
||
[source,lua]
|
||
----
|
||
function onOverlayUpdate()
|
||
frames = 0
|
||
frames = frames + 1
|
||
overlayClear()
|
||
overlayPrint(2, 2, "Hello!")
|
||
overlayPrint(0, 0, "frame " .. frames)
|
||
return OVERLAY_UPDATED
|
||
end
|
||
----
|
||
|
||
Enregistrez-le. Dans le coin supérieur gauche, vous obtenez `frame 1`, et il
|
||
reste à `frame 1` pour toujours.
|
||
|
||
C'est décevant, et c'est aussi ce qu'il y a de plus utile dans cette leçon,
|
||
alors ne le corrigez pas encore. Regardez ce que vous avez écrit et pensez à
|
||
l'ordre des lignes, dont la leçon un disait que c'était la chose la plus
|
||
importante à savoir sur un programme.
|
||
|
||
Singe appelle `onOverlayUpdate`. La première ligne met `frames` à zéro. La
|
||
deuxième y ajoute un, ce qui fait un. Vous l'affichez, vous renvoyez la réponse,
|
||
et la fonction est terminée. Puis Singe l'appelle de nouveau, et la première
|
||
chose qui se passe, c'est que `frames` est remis à zéro.
|
||
|
||
Vous ne comptez pas les images. Vous comptez jusqu'à un, soixante fois par
|
||
seconde.
|
||
|
||
[#_move_the_line_out]
|
||
=== Sortez la ligne
|
||
|
||
Retirez la ligne `frames = 0` de la fonction et mettez-la au-dessus, toute
|
||
seule, avant le mot `function`.
|
||
|
||
[source,lua]
|
||
----
|
||
frames = 0
|
||
|
||
|
||
function onOverlayUpdate()
|
||
frames = frames + 1
|
||
overlayClear()
|
||
overlayPrint(2, 2, "Hello!")
|
||
overlayPrint(0, 0, "frame " .. frames)
|
||
return OVERLAY_UPDATED
|
||
end
|
||
----
|
||
|
||
Enregistrez-le. Le nombre grimpe, vite, et ne s'arrête pas.
|
||
|
||
Rien d'autre n'a changé. La seule différence, c'est *où* se trouve la ligne, et
|
||
toute l'idée est là. Les lignes à l'intérieur d'une fonction s'exécutent chaque
|
||
fois que la fonction est appelée, sans exception. Les lignes en dehors de toute
|
||
fonction s'exécutent une seule fois, quand Singe lit votre fichier, avant que le
|
||
jeu commence.
|
||
|
||
`frames = 0` devait s'exécuter une fois. Il s'exécutait soixante fois par
|
||
seconde, et il effaçait le travail de la ligne du dessous.
|
||
|
||
Tout ce qui bouge dans ce que vous écrirez aura cette forme. Préparez le nombre
|
||
dehors, changez-le dedans.
|
||
|
||
[#_what_just_happened_2]
|
||
=== Ce qui vient de se passer
|
||
|
||
Il y a là trois nouveaux morceaux de code. Les voici, chacun à part.
|
||
|
||
[source,lua]
|
||
----
|
||
frames = 0
|
||
----
|
||
|
||
Ceci invente un nom, `frames`, et met le nombre zéro derrière. Un nom avec une
|
||
valeur derrière est une *variable*, et elle s'appelle ainsi parce que la valeur
|
||
a le droit de varier -- de changer pendant que le programme tourne.
|
||
|
||
Le `=` n'est pas le signe égal de l'école. Il ne dit pas que `frames` est égal à
|
||
zéro ; il dit *mettez zéro dans `frames`*, et il marche toujours dans ce
|
||
sens-là, de droite à gauche. Les programmeurs appellent ça une *affectation*, et
|
||
ça vaut la peine de dire la ligne à voix haute de cette façon en l'écrivant :
|
||
« frames reçoit zéro ».
|
||
|
||
C'est vous qui choisissez le nom. Ce peut être presque n'importe quoi qui
|
||
commence par une lettre et ne contient pas d'espace, et il devrait dire ce
|
||
qu'est la chose. `frames` est un bon nom. `f` en est un mauvais, et vous ne vous
|
||
en remercierez pas dans une semaine.
|
||
|
||
[source,lua]
|
||
----
|
||
frames = frames + 1
|
||
----
|
||
|
||
C'est la ligne qui fait s'arrêter les gens, les yeux écarquillés, parce qu'en
|
||
tant qu'énoncé d'arithmétique, elle n'a aucun sens : rien n'est égal à lui-même
|
||
plus un.
|
||
|
||
Ce n'est pas un énoncé d'arithmétique. Souvenez-vous que `=` met le côté droit
|
||
dans le nom de gauche, et lisez-la dans cet ordre, la droite d'abord. Calculez
|
||
`frames + 1` avec ce qu'il y a dans `frames` en ce moment. Puis remettez la
|
||
réponse dans `frames`. Si `frames` contenait six, le côté droit donne sept, et
|
||
sept va dans `frames`.
|
||
|
||
Ajouter un à une variable de cette façon, vous l'écrirez des milliers de fois.
|
||
|
||
Le `+` fait ce que vous attendez, et les trois autres aussi : `-` soustrait, `*`
|
||
multiplie et `/` divise. Il n'y a pas de touche `x` pour la multiplication,
|
||
c'est pour ça que l'étoile fait ce travail.
|
||
|
||
[source,lua]
|
||
----
|
||
overlayPrint(0, 0, "frame " .. frames)
|
||
----
|
||
|
||
`overlayPrint` veut du texte, et `frames` est un nombre, pas du texte. Les deux
|
||
points collent les deux ensemble et transmettent le résultat comme une seule
|
||
chaîne : les cinq caractères `frame`, un espace, puis autant de chiffres qu'il
|
||
en faut au nombre.
|
||
|
||
Ces deux points sont l'opérateur de jonction, et son vrai nom vaut la peine
|
||
d'être connu, parce que les messages d'erreur l'emploient : joindre des chaînes
|
||
s'appelle la *concaténation*.
|
||
|
||
Remarquez l'espace entre les guillemets, avant celui qui ferme. Sans lui, vous
|
||
obtenez `frame6`. Lua joint exactement ce que vous lui donnez et n'ajoute rien
|
||
de lui-même.
|
||
|
||
Lua transforme un nombre en texte pour vous quand vous le joignez à une chaîne
|
||
de cette façon, c'est pourquoi vous n'avez rien eu à faire de spécial. Il ne le
|
||
fait pas pour tout. Si un jour vous essayez de joindre quelque chose qui n'est
|
||
ni du texte ni un nombre, Lua s'arrête sur une erreur, et le moyen de la
|
||
contourner, c'est `tostring`, qui transforme absolument n'importe quoi en texte :
|
||
|
||
[source,lua]
|
||
----
|
||
overlayPrint(0, 0, "frame " .. tostring(frames))
|
||
----
|
||
|
||
Cette ligne fait exactement la même chose que celle d'avant. Vous n'avez pas
|
||
besoin de `tostring` pour les nombres. Vous en aurez besoin à la leçon trois.
|
||
|
||
[#_make_it_move]
|
||
=== Faites-le bouger
|
||
|
||
Vous avez maintenant tout ce qu'il faut pour déplacer le mot. Le mot est dessiné
|
||
là où vous le dites, vous avez un moyen de garder un nombre d'une image à
|
||
l'autre, et vous avez un moyen de le changer.
|
||
|
||
Donnez à la colonne sa propre variable en dehors de la fonction, et ajoutez-y
|
||
quelque chose à l'intérieur.
|
||
|
||
[source,lua]
|
||
----
|
||
frames = 0
|
||
wordX = 0
|
||
|
||
|
||
function onOverlayUpdate()
|
||
frames = frames + 1
|
||
wordX = wordX + 1
|
||
overlayClear()
|
||
overlayPrint(wordX, 2, "Hello!")
|
||
overlayPrint(0, 0, "frame " .. frames)
|
||
return OVERLAY_UPDATED
|
||
end
|
||
----
|
||
|
||
Enregistrez-le. Le mot file par le côté droit et ne revient jamais.
|
||
|
||
Il avançait d'une colonne à chaque image, soixante fois par seconde, il a donc
|
||
traversé toute la fenêtre en une seconde environ. Ralentissez-le en ajoutant
|
||
moins d'une colonne entière :
|
||
|
||
[source,lua]
|
||
----
|
||
wordX = wordX + 0.25
|
||
----
|
||
|
||
Maintenant, il lui faut quatre images pour avancer d'une colonne, et le mot se
|
||
traîne. `wordX` est la plupart du temps un nombre comme `12.75`, qui n'est pas
|
||
du tout une colonne, et Singe s'en arrange en jetant tout ce qui suit le point
|
||
quand il dessine : `12.75` se dessine dans la colonne douze. Garder la fraction
|
||
dans la variable, c'est justement ce qui vous permet d'aller moins vite qu'une
|
||
colonne par image. Le nombre est plus fin que l'écran.
|
||
|
||
[#_where_is_the_edge]
|
||
=== Où est le bord ?
|
||
|
||
Le mot s'en va toujours. Pour lui faire faire demi-tour, il faut savoir où est
|
||
le bord droit, et vous pourriez le trouver en comptant : un jeu sans vidéo
|
||
reçoit par défaut un monde de 720 sur 480, l'overlay sur lequel vous dessinez en
|
||
fait la moitié, 360 sur 240, et les lettres de la police de console intégrée
|
||
font six pixels de large, il y a donc soixante colonnes.
|
||
|
||
Ne faites pas ça. Demandez au moteur.
|
||
|
||
[source,lua]
|
||
----
|
||
lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word)
|
||
----
|
||
|
||
`overlayGetWidth` et `overlayGetHeight` sont deux autres fonctions que Singe
|
||
fournit, comme `overlayPrint`. Elles ne prennent rien et renvoient un nombre :
|
||
la largeur et la hauteur de la surface de dessin. Vous avez vu `return` renvoyer
|
||
une valeur à la leçon un, et ceci en est l'autre bout -- une fonction que vous
|
||
appelez peut vous donner quelque chose, et ici vous faites de l'arithmétique
|
||
avec ce qu'elle vous a donné.
|
||
|
||
Les nombres qu'elles vous donnent sont en pixels, et `overlayPrint` compte en
|
||
cellules de caractère, il faut donc diviser. `overlayGetFontWidth` vous dit
|
||
combien de pixels fait une cellule en largeur, et `overlayGetFontHeight` en
|
||
hauteur, si bien que vous n'avez jamais à connaître la réponse vous-même. Les
|
||
deux barres obliques veulent dire *divisez et jetez la fraction*, parce qu'un
|
||
tiers de cellule de caractère n'est pas un endroit où l'on peut afficher quelque
|
||
chose. La barre oblique simple vous donnerait une fraction, et une fraction de
|
||
cellule n'est pas un endroit non plus.
|
||
|
||
`string.len` compte les caractères d'une chaîne, et il est là pour que le mot
|
||
fasse demi-tour quand sa *dernière* lettre atteint le bord, et non sa première.
|
||
Essayez sans, et vous verrez six caractères glisser dans le néant.
|
||
|
||
Les majuscules de `CELL_WIDTH`, qui contient ce que vous donne
|
||
`overlayGetFontWidth`, sont une habitude plutôt qu'une règle. Lua s'en moque,
|
||
mais un nom en majuscules, c'est la façon dont les programmeurs disent « ceci
|
||
est un nombre que je règle une fois en haut et auquel je ne touche plus
|
||
jamais », et ça vaut mieux qu'un `6` tout nu au milieu d'un fichier, que
|
||
personne ne saura expliquer un mois plus tard.
|
||
|
||
[#_turn_it_round]
|
||
=== Faites-lui faire demi-tour
|
||
|
||
Aller dans l'autre sens ne demande aucun effort : au lieu d'ajouter `0.25` à
|
||
chaque image, ajoutez `-0.25`. Gardez donc la quantité dans une variable à elle
|
||
et inversez son signe quand le mot arrive à un bord.
|
||
|
||
[source,lua]
|
||
----
|
||
if wordX > lastColumn then
|
||
wordX = lastColumn
|
||
speedX = -speedX
|
||
end
|
||
----
|
||
|
||
C'est une décision, et les décisions sont tout le sujet de la leçon trois, alors
|
||
croyez-moi sur parole pendant une leçon. Lisez-la à voix haute : *si* `wordX` a
|
||
dépassé `lastColumn`, *alors* exécutez les deux lignes indentées ; sinon,
|
||
sautez-les. Le `end` la ferme, de la même façon que `end` ferme une fonction.
|
||
|
||
La première ligne cale le mot exactement sur le bord, pour qu'un mot rapide ne
|
||
puisse pas filer au-delà. La seconde est l'inversion. `-speedX`, c'est moins ce
|
||
que contient `speedX`, donc s'il contenait `0.25`, il contient maintenant
|
||
`-0.25`, et ajouter `-0.25` à chaque image ramène le mot par où il est venu.
|
||
Quand il atteint le bord gauche, le `if` correspondant l'inverse de nouveau, et
|
||
il repasse à `0.25`.
|
||
|
||
Deux de plus pour les bords du haut et du bas, et le mot rebondit dans toute la
|
||
fenêtre.
|
||
|
||
[#_the_whole_thing]
|
||
=== Le tout
|
||
|
||
[source,lua]
|
||
----
|
||
CELL_WIDTH = overlayGetFontWidth()
|
||
CELL_HEIGHT = overlayGetFontHeight()
|
||
TOP_ROW = 2
|
||
|
||
word = "Hello!"
|
||
wordX = 0
|
||
wordY = TOP_ROW
|
||
speedX = 0.25
|
||
speedY = 0.125
|
||
frames = 0
|
||
|
||
lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word)
|
||
lastRow = overlayGetHeight() // CELL_HEIGHT - 1
|
||
|
||
|
||
function onOverlayUpdate()
|
||
frames = frames + 1
|
||
wordX = wordX + speedX
|
||
wordY = wordY + speedY
|
||
|
||
if wordX < 0 then
|
||
wordX = 0
|
||
speedX = -speedX
|
||
end
|
||
|
||
if wordX > lastColumn then
|
||
wordX = lastColumn
|
||
speedX = -speedX
|
||
end
|
||
|
||
if wordY < TOP_ROW then
|
||
wordY = TOP_ROW
|
||
speedY = -speedY
|
||
end
|
||
|
||
if wordY > lastRow then
|
||
wordY = lastRow
|
||
speedY = -speedY
|
||
end
|
||
|
||
overlayClear()
|
||
overlayPrint(0, 0, "frame " .. frames)
|
||
overlayPrint(wordX, wordY, word)
|
||
return OVERLAY_UPDATED
|
||
end
|
||
----
|
||
|
||
`TOP_ROW` tient le mot à l'écart des deux lignes du haut, pour qu'il ne
|
||
gribouille jamais par-dessus le compteur d'images. `lastRow` contient un `- 1`
|
||
plutôt qu'un `string.len`, parce qu'un mot fait plusieurs caractères de large
|
||
mais toujours une seule ligne de haut.
|
||
|
||
Remarquez que toute la moitié haute du fichier s'exécute une fois et que toute
|
||
la moitié basse s'exécute soixante fois par seconde, et que vous pouvez
|
||
distinguer l'une de l'autre en regardant l'indentation. C'est à ça que servaient
|
||
les espaces.
|
||
|
||
[#_try_it_2]
|
||
=== Essayez
|
||
|
||
. *Changez les vitesses.* Essayez `speedX = 1` et `speedY = 1`. Puis essayez
|
||
`0.05` pour les deux. Puis rendez l'une beaucoup plus grande que l'autre.
|
||
. *Changez le mot.* Mettez un mot beaucoup plus long entre les guillemets --
|
||
assez long pour remplir un tiers de la fenêtre -- et regardez le rebond à
|
||
droite se faire toujours au bon endroit. C'est `string.len` qui fait son
|
||
travail.
|
||
. *Retirez l'effacement.* Supprimez la ligne `overlayClear()`. La leçon un
|
||
promettait que ça compterait un jour, et ce jour est arrivé : le mot laisse
|
||
une traînée à travers la fenêtre et n'en efface jamais rien, parce que
|
||
dessiner a toujours dessiné par-dessus ce qui était là. Remettez-la.
|
||
. *Empêchez-le d'aller de côté.* Mettez `speedX = 0` et lancez. Trouvez pourquoi
|
||
le mot ne quitte plus jamais la colonne zéro, et pourquoi le `if` du bord
|
||
gauche ne se déclenche jamais.
|
||
. *Cassez le compteur d'images exprès.* Remettez la ligne `frames = 0` dans la
|
||
fonction, au-dessus de `frames = frames + 1`. Regardez le compteur rester
|
||
bloqué à un pendant que le mot continue de bouger parfaitement. Puis trouvez
|
||
pourquoi le mot n'est pas touché.
|
||
|
||
[#_break_it_on_purpose_2]
|
||
=== Cassez-le exprès
|
||
|
||
Remplacez les deux points de la ligne du compteur d'images par un signe plus,
|
||
pour qu'elle devienne :
|
||
|
||
[source,lua]
|
||
----
|
||
overlayPrint(0, 0, "frame " + frames)
|
||
----
|
||
|
||
Enregistrez-le. La fenêtre se ferme, Singe s'arrête, et le terminal affiche :
|
||
|
||
----
|
||
Error executing function 'onOverlayUpdate': numbers/numbers.singe:42: attempt to add a 'string' with a 'number'
|
||
----
|
||
|
||
suivi de plusieurs lignes commençant par `stack traceback:`, que vous pouvez
|
||
ignorer pour l'instant.
|
||
|
||
Lisez-le comme la leçon un vous l'a appris : le fichier, la ligne, puis la
|
||
plainte. La ligne 42 est celle que vous avez changée. La plainte dit qu'on a
|
||
demandé à Lua de faire une *addition* entre une chaîne et un nombre, et
|
||
l'addition, c'est pour les nombres. `+` et `..` se ressemblent sur la page et
|
||
font des travaux complètement différents : le plus, c'est de l'arithmétique,
|
||
`..`, c'est de la colle.
|
||
|
||
Celle-ci vaut la peine d'être rencontrée à cause de l'endroit où elle est
|
||
apparue. L'erreur de la leçon un arrêtait Singe avant même que votre jeu
|
||
démarre, parce que Lua ne pouvait pas du tout lire le fichier. Celle-ci est une
|
||
ligne parfaitement lisible qui ne tourne mal que lorsqu'elle s'exécute, donc la
|
||
fenêtre s'est ouverte, le jeu a tourné, et il est mort à la première image. Les
|
||
deux sortes vous donnent le fichier et la ligne. Commencez par là dans les deux
|
||
cas.
|
||
|
||
[#_what_you_learned_2]
|
||
=== Ce que vous avez appris
|
||
|
||
* Une variable est un nom avec une valeur derrière, et la valeur peut changer.
|
||
* `=` veut dire « mettez le côté droit dans le nom de gauche », pas « est égal
|
||
à ».
|
||
* `frames = frames + 1` calcule d'abord le côté droit, puis le range.
|
||
* `+`, `-`, `*` et `/` font l'arithmétique que vous attendez ; `//` divise et
|
||
jette la fraction.
|
||
* `..` colle du texte ensemble, et transforme un nombre en texte au passage.
|
||
* Une ligne à l'intérieur de `onOverlayUpdate` s'exécute à chaque image. Une
|
||
ligne en dehors de toute fonction s'exécute une fois, avant que le jeu
|
||
commence.
|
||
* Tout ce qui doit se souvenir de quelque chose d'une image à l'autre doit vivre
|
||
dehors.
|
||
* `overlayGetWidth` et `overlayGetHeight` vous donnent la taille de la surface
|
||
de dessin, si bien que vous n'avez jamais à la deviner.
|
||
* `overlayClear` compte dès que quelque chose bouge.
|
||
|
||
[#_next_time_2]
|
||
=== La prochaine fois
|
||
|
||
Le mot bouge, mais il bouge tout seul, et rien de ce que vous faites n'y change
|
||
quoi que ce soit. À la leçon trois, vous reprenez les quatre blocs `if` que vous
|
||
avez utilisés sur parole, vous apprenez ce qu'ils disent vraiment, et vous les
|
||
branchez sur les touches fléchées au lieu des bords de l'écran. La boîte que
|
||
vous pilotez dans la fenêtre à la fin de cette leçon est le joueur du jeu que
|
||
vous construirez à la leçon sept.
|