singe/docs/fr/lessons/02-numbers.adoc

444 lines
17 KiB
Text
Raw Permalink 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_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.