singe/docs/fr/lessons/05-functions.adoc

722 lines
25 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_5_your_own_functions]
== Leçon 5 : Vos propres fonctions
image::learn/05-functions.png[La leçon terminée, 480]
La leçon quatre s'est terminée sur un avertissement à propos de votre propre
programme : `onOverlayUpdate` calcule où vont cent vingt étoiles, décide de la
luminosité de chacune, les dessine et compte la dérive, et c'est une seule
fonction qui fait quatre travaux. On est sur le point de lui en demander un
cinquième, parce qu'un champ d'étoiles sans rien devant n'est pas un jeu.
Dans cette leçon, vous le démontez. Vous allez écrire vos propres fonctions --
des fonctions dont Singe n'a jamais entendu parler et qu'il n'appellera
jamais -- et leur confier le travail. À la fin, tout le champ d'étoiles tient en une
ligne qui dit `drawStars()`, un vaisseau est revenu en bas de l'écran, et le
programme est plus facile à lire que lorsqu'il en faisait moins.
[#_start_a_new_folder_4]
=== Créez un nouveau dossier
Un dossier appelé `functions`, dedans un fichier appelé `functions.singe`, et
`Singe -R functions` qui tourne dans un terminal.
Copiez-y le programme terminé de la leçon quatre. Le voici, inchangé, et ça vaut
la peine de le lancer une fois avant de toucher à quoi que ce soit :
[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
----
Trois couches d'étoiles qui descendent dans la fenêtre à trois vitesses. Rien
dans la première moitié de cette leçon ne change ce que vous voyez. Tout ce
qu'elle contient change la façon dont le programme se lit.
[#_one_job_one_name]
=== Un travail, un nom
Commencez par les deux lignes qui mettent une étoile à l'écran. Au-dessus de
`onOverlayUpdate`, ajoutez ceci :
[source,lua]
----
function drawStar(x, y, shade)
colorForeground(shade, shade, shade)
overlayPlot(x, y)
end
----
Puis, dans la boucle intérieure, supprimez ces deux lignes et appelez-la à la
place :
[source,lua]
----
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
drawStar(starX, starY, shade)
end
----
Enregistrez. Rien ne change à l'écran.
Vous avez déjà écrit des fonctions -- `onOverlayUpdate` en est une, et
`onInputPressed` et `onInputReleased` de la leçon trois aussi -- mais ce sont
des callbacks. Vous les écrivez et Singe les appelle. `drawStar` est différente.
Singe n'en a jamais entendu parler. Elle s'exécute parce que *vous* avez écrit
son nom et des parenthèses, de la même façon que vous appelez `overlayPlot`, et
rien au monde ne l'exécutera si vous ne le faites pas.
Tout le truc est là. Désormais, quand vous vous surprenez à penser « et
maintenant, dessiner une étoile », vous pouvez écrire « dessiner une étoile », et
c'est bien ce que ça voudra dire.
[#_a_parameter_is_a_name_not_a_thing]
=== Un paramètre est un nom, pas une chose
Regardez de nouveau la première ligne de `drawStar`.
[source,lua]
----
function drawStar(x, y, shade)
----
Les trois noms entre les parenthèses sont des *paramètres*. Un paramètre est un
nom que la fonction utilise pour ce que l'appelant lui a passé, quoi que ce
soit. Quand vous écrivez `drawStar(starX, starY, shade)`, Lua met dans `x` ce
qu'il y a dans `starX`, dans `y` ce qu'il y a dans `starY`, dans `shade` ce
qu'il y a dans `shade`, puis exécute la fonction.
Les noms n'ont rien à voir entre eux. Le nombre de l'appelant s'appelle `starX`
là-bas et `x` ici, et ça marcherait exactement pareil si vous l'appeliez
`across` ou `banana`. À l'intérieur de la fonction, seuls les noms entre les
parenthèses existent, et ils contiennent des copies de ce qui est entré.
C'est ici que les parenthèses vides de la leçon un prennent enfin leur sens.
[source,lua]
----
function onOverlayUpdate()
----
Rien entre les parenthèses veut dire aucun paramètre : le moteur n'a rien à dire
à cette fonction, donc il n'y a rien à nommer. Comparez avec le callback que
vous avez écrit à la leçon trois.
[source,lua]
----
function onInputPressed(what)
----
Un paramètre, appelé `what`. Le moteur a quelque chose à dire à celui-ci --
quelle commande a été enfoncée -- donc il y a un nom pour l'attraper. Vous vous
servez d'un paramètre depuis la leçon trois.
Un paramètre est vraiment la copie personnelle de la fonction, et ça vaut la
peine de vous le prouver une fois. Tapez ceci, appelez-la depuis
`onOverlayUpdate`, et retirez-la ensuite :
[source,lua]
----
function half(number)
number = number // 2
return number
end
----
Appelez-la sous la forme `half(drift)`. La fonction divise son `number` par deux
et renvoie la réponse, et `drift`, là-bas dans `onOverlayUpdate`, continue de
compter comme si de rien n'était. Une fonction ne peut pas aller changer les
variables de l'appelant à travers un paramètre. Elle reçoit une copie, et ce
qu'elle fait de la copie ne regarde qu'elle.
[#_handing_an_answer_back]
=== Renvoyer une réponse
`half` a fait quelque chose que les fonctions que vous avez écrites jusqu'ici ne
faisaient pas : elle a donné une réponse. C'est la dernière ligne.
[source,lua]
----
return number
----
`return` renvoie une valeur à celui qui a appelé la fonction, et l'appel se
transforme en cette valeur. Écrivez `smaller = half(drift)` et Lua exécute
`half`, prend ce que `half` a renvoyé et le met dans `smaller`. Vous avez
rencontré `return` à la leçon un, où `onOverlayUpdate` renvoie
`OVERLAY_UPDATED` pour répondre à la question de Singe : est-ce que quelque
chose a changé ? C'est le même mot qui fait le même travail, sauf que
maintenant, c'est vous qui posez la question.
La ligne de la luminosité ne demande qu'à devenir une fonction qui donne une
réponse. Ajoutez celle-ci :
[source,lua]
----
function shadeFor(layer)
return math.random(60, 90) + layer * LAYER_BRIGHT
end
----
Tout le corps est un `return`, ce qui est normal pour une petite fonction.
Maintenant, la boucle intérieure peut passer la réponse directement à
`drawStar`, sans nom entre les deux :
[source,lua]
----
drawStar(starX, starY, shadeFor(layer))
----
Un appel dans un appel. Lua calcule d'abord celui de l'intérieur, reçoit un
nombre de `shadeFor`, et passe ce nombre à `drawStar` comme troisième argument.
Vous avez déjà écrit quelque chose de ce genre sans le remarquer : le
`math.floor(drift * layer * LAYER_SPEED)` de la leçon quatre est un appel dont
la réponse est utilisée sur place, au milieu d'un calcul plus grand.
[#_two_answers_at_once]
=== Deux réponses à la fois
Les deux lignes de position sont plus difficiles, parce que ce sont deux nombres
calculés ensemble et qu'il faudrait en choisir un à renvoyer. Dans la plupart
des langages, une fonction peut renvoyer une valeur, et en obtenir deux est une
corvée. Lua vous laisse en renvoyer autant que vous voulez.
[source,lua]
----
function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
----
Vous attrapez les réponses en mettant deux noms à gauche, séparés par une
virgule :
[source,lua]
----
local x, y = starAt(star, layer)
----
La première valeur dans le premier nom, la deuxième dans le deuxième. Si vous
demandez moins de noms que la fonction ne renvoie de valeurs, les valeurs en
trop sont jetées : `local x = starAt(star, layer)` vous donne le nombre
horizontal et perd le vertical, sans se plaindre. Si vous demandez plus de noms
qu'il n'y a de valeurs, les noms en trop valent `nil`.
Ignorez le mot `local` dans ces deux blocs encore une minute. Il aura bientôt sa
propre partie, et c'est la plus importante.
[#_the_star_field_in_one_line]
=== Le champ d'étoiles en une ligne
La boucle intérieure tient maintenant en trois lignes courtes, qui parlent
toutes d'une couche d'étoiles. C'est un travail qui a un nom.
[source,lua]
----
function drawLayer(layer)
for star = 1, STARS_PER_LAYER do
local x, y = starAt(star, layer)
drawStar(x, y, shadeFor(layer))
end
end
----
Et la boucle extérieure est elle aussi un travail qui a un nom :
[source,lua]
----
function drawStars()
for layer = 1, LAYERS do
drawLayer(layer)
end
end
----
Des fonctions qui appellent des fonctions qui appellent des fonctions, sur trois
niveaux, et chacune fait trois ou quatre lignes que vous pouvez garder en tête
d'un seul coup. `onOverlayUpdate` peut maintenant dire ce qu'**est** une image :
[source,lua]
----
function onOverlayUpdate()
drift = drift + 1
overlayClear()
drawStars()
return OVERLAY_UPDATED
end
----
Voilà la promesse faite à la fin de la leçon quatre, tenue.
[#_put_the_ship_back]
=== Remettez le vaisseau
Maintenant, le cinquième travail, celui qui aurait mis le désordre dans l'ancien
`onOverlayUpdate`. Ramenez la boîte que vous pilotiez à la leçon trois, cette
fois sous la forme d'un vaisseau posé près du bas de l'écran, qui ne bouge que
vers la gauche et la droite.
Il lui faut quelques valeurs à lui, en haut avec les autres, et les deux
callbacks d'entrée de la leçon trois, sans les branches pour le haut et le bas :
[source,lua]
----
PLAYER_WIDTH = 20
PLAYER_HEIGHT = 6
PLAYER_SPEED = 3
PLAYER_MARGIN = 6
playerX = width // 2
playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN
movingLeft = false
movingRight = false
----
[source,lua]
----
function onInputPressed(what)
if what == SWITCH_LEFT then
movingLeft = true
elseif what == SWITCH_RIGHT then
movingRight = true
end
end
function onInputReleased(what)
if what == SWITCH_LEFT then
movingLeft = false
elseif what == SWITCH_RIGHT then
movingRight = false
end
end
----
Le dessiner prend deux lignes, donc c'est une fonction :
[source,lua]
----
function drawPlayer()
colorForeground(80, 255, 120)
overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT)
end
----
C'est en le déplaçant que la leçon porte ses fruits. À la leçon trois, garder la
boîte à l'écran prenait quatre lignes de `if` et de `elseif` pour chaque
direction. Cette idée -- tenir un nombre entre un plancher et un plafond -- a un
nom, et ce nom, c'est *clamp* :
[source,lua]
----
function clamp(value, low, high)
if value < low then
return low
end
if value > high then
return high
end
return value
end
----
Trois sorties, et une seule au plus se produit, parce que `return` ne fait pas
que renvoyer une réponse : il arrête net la fonction. Rien de ce qui suit ne
s'exécute. Si la valeur est sous le plancher, renvoyez le plancher et arrêtez.
Si elle dépasse le plafond, renvoyez le plafond et arrêtez. Sinon, renvoyez ce
qui est entré.
[source,lua]
----
function movePlayer()
if movingLeft then
playerX = playerX - PLAYER_SPEED
end
if movingRight then
playerX = playerX + PLAYER_SPEED
end
playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1)
end
----
Une fonction qui se termine sans `return` ne renvoie rien, c'est-à-dire `nil`.
C'est ce qu'il faut pour `drawPlayer` et `movePlayer`, qu'on appelle pour faire
quelque chose plutôt que pour répondre à quelque chose. C'est un bug dans une
fonction comme `clamp` : oubliez ce dernier `return value` et chaque position au
milieu de la plage revient sous la forme `nil`.
[#_where_names_live]
=== Où vivent les noms
Chaque nom que vous avez créé depuis la leçon deux était un nom *global*. Un nom
global appartient à tout le programme : `drift`, `playerX` et `LAYERS` peuvent
être lus et écrits par n'importe quelle fonction du fichier, à tout moment, et
ils restent là tant que le jeu tourne.
Ça a l'air pratique, et pour six noms, ça l'est. Les ennuis commencent à
soixante. Chaque nom global est un nom que tout le programme s'est engagé à
réserver, donc quand une autre fonction veut un endroit où garder une position
horizontale et l'appelle `starX` elle aussi, les deux sont la même boîte, et
chacune écrit par-dessus le nombre de l'autre. Quand un nom global contient
quelque chose de faux, n'importe quoi dans le fichier a pu l'y mettre, et
trouver quoi veut dire tout relire.
Un nom *local* est le remède. Mettez le mot `local` devant un nom la première
fois que vous l'utilisez, et le nom n'existe qu'à l'intérieur de la `function`,
de la boucle ou du `if` où il a été écrit.
[source,lua]
----
function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
----
Ici, `x` et `y` ne regardent personne d'autre. Une autre fonction peut avoir son
propre `x`, et les deux ne se rencontrent jamais. Quand le `x` de `starAt` est
faux, les trois lignes ci-dessus sont les trois seules qui ont pu le provoquer.
Sans le mot `local`, ces deux lignes créeraient discrètement deux noms globaux
de plus, et c'est exactement ce que `starX` et `starY` étaient depuis le début.
Maintenant, la partie qui surprend tout le monde. Tapez cette fonction, et
mettez dans `onOverlayUpdate` une ligne qui vous montre ce qu'elle renvoie :
[source,lua]
----
function countUp()
local counted = 0
counted = counted + 1
return counted
end
----
[source,lua]
----
overlayPrint(0, 0, "Count: " .. countUp())
----
`Count: 1`, et ça reste `Count: 1` tant que le jeu tourne, alors que la fonction
est appelée soixante fois par seconde et ajoute un à chaque fois. Un nom local
est créé tout neuf quand la fonction commence et disparaît quand elle se
termine, donc `counted` naît à `0`, devient `1` et meurt, soixante fois par
seconde, pour toujours. Un nom local ne se souvient pas. Retirez les deux une
fois que vous avez regardé.
Ce n'est pas un défaut, c'est le but : un nom local ne peut pas être empoisonné
par l'appel précédent, parce qu'il ne reste rien de l'appel précédent. Quand
vous avez vraiment besoin de vous souvenir de quelque chose d'un appel à
l'autre -- `drift` et `playerX` doivent se souvenir -- le nom doit vivre en
dehors de toute fonction. Mettez-le en haut du fichier, avec `local` devant :
[source,lua]
----
local drift = 0
local playerX = width // 2
----
Un `local` écrit en haut du fichier, en dehors de toutes les fonctions, vit
aussi longtemps que le script et peut être vu par toutes les fonctions en
dessous. Ce dernier mot compte : *en dessous*. Un nom local existe à partir de
sa propre ligne et vers le bas, donc une fonction écrite au-dessus de la ligne
qui le crée ne peut pas le voir. C'est vrai aussi des fonctions elles-mêmes, une
fois que vous les marquez `local`, et c'est pourquoi le script terminé est dans
cet ordre : les valeurs d'abord, puis vos propres fonctions, puis les
callbacks, en dernier.
Parcourez maintenant tout le fichier et mettez `local` devant chaque nom que
vous avez créé -- les constantes en majuscules, `width` et `height`, `drift`,
les quatre valeurs du joueur et chacune de vos propres fonctions. Trois noms n'y
ont pas droit : `onInputPressed`, `onInputReleased` et `onOverlayUpdate` doivent
rester globaux, parce que Singe les trouve par leur nom et qu'un nom local est
invisible depuis l'extérieur du fichier.
Soyons honnêtes sur ce que les noms locaux ne règlent pas. Si vous faites une
faute de frappe dans un nom auquel vous affectez une valeur --
`movinLeft = true` au lieu de `movingLeft = true` -- Lua ne se plaint pas. Il
crée un tout nouveau nom global avec votre faute de frappe pour nom, le remplit,
et laisse le vrai tranquille. Rien ne bouge, aucune erreur, et vous fixez
l'écran pendant dix minutes. Les noms locaux ne vous sauveront pas de celle-là.
Relire le nom deux fois, si.
[#_the_whole_thing_4]
=== Le tout
Voici le programme terminé, avec chaque morceau de la leçon dedans.
[source,lua]
----
local LAYERS = 3
local STARS_PER_LAYER = 40
local ACROSS_STEP = 37
local DOWN_STEP = 61
local LAYER_SHIFT = 13
local LAYER_SPEED = 0.5
local LAYER_BRIGHT = 55
local PLAYER_WIDTH = 20
local PLAYER_HEIGHT = 6
local PLAYER_SPEED = 3
local PLAYER_MARGIN = 6
local width = overlayGetWidth()
local height = overlayGetHeight()
local drift = 0
local playerX = width // 2
local playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN
local movingLeft = false
local movingRight = false
local function clamp(value, low, high)
if value < low then
return low
end
if value > high then
return high
end
return value
end
local function shadeFor(layer)
return math.random(60, 90) + layer * LAYER_BRIGHT
end
local function starAt(star, layer)
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
return x, y
end
local function drawStar(x, y, shade)
colorForeground(shade, shade, shade)
overlayPlot(x, y)
end
local function drawLayer(layer)
for star = 1, STARS_PER_LAYER do
local x, y = starAt(star, layer)
drawStar(x, y, shadeFor(layer))
end
end
local function drawStars()
for layer = 1, LAYERS do
drawLayer(layer)
end
end
local function drawPlayer()
colorForeground(80, 255, 120)
overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT)
end
local function movePlayer()
if movingLeft then
playerX = playerX - PLAYER_SPEED
end
if movingRight then
playerX = playerX + PLAYER_SPEED
end
playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1)
end
function onInputPressed(what)
if what == SWITCH_LEFT then
movingLeft = true
elseif what == SWITCH_RIGHT then
movingRight = true
end
end
function onInputReleased(what)
if what == SWITCH_LEFT then
movingLeft = false
elseif what == SWITCH_RIGHT then
movingRight = false
end
end
function onOverlayUpdate()
drift = drift + 1
overlayClear()
movePlayer()
drawStars()
drawPlayer()
return OVERLAY_UPDATED
end
----
Deux choses méritent d'être montrées du doigt avant de passer à la suite.
`local function drawStar(x, y, shade)` marque la fonction elle-même comme
locale. Vos propres fonctions sont des noms comme les autres, et elles
n'appartiennent qu'à ce fichier. Les trois callbacks restent globaux pour que
Singe puisse les trouver.
Et `onOverlayUpdate` fait maintenant six lignes qui disent ce qu'est une image :
compter la dérive, effacer l'écran, déplacer le joueur, dessiner le ciel,
dessiner le joueur, terminé. Il fait cinq travaux en nommant cinq choses qui
font chacune un travail, et c'est la forme de toutes les boucles de jeu que vous
écrirez jamais.
[#_a_function_is_a_name_for_an_idea]
=== Une fonction est le nom d'une idée
`clamp` n'est appelée qu'une fois. La sortir n'a économisé aucune frappe --
quatre lignes en sont sorties, une seule y est entrée, et une fonction de neuf
lignes est apparue ailleurs. Ça valait quand même la peine.
Une fonction n'est pas seulement un moyen d'éviter de vous répéter. C'est un
moyen de donner un nom à une idée, pour que le code qui se sert de l'idée puisse
dire le nom et passer à autre chose. « Garder le joueur entre le bord gauche et
le bord droit » est une pensée que vous avez eue une fois.
`clamp(playerX, 0, width - PLAYER_WIDTH - 1)`, c'est cette pensée, écrite.
Quatre instructions `if`, c'est la même pensée démontée en morceaux que vous
devez remonter dans votre tête chaque fois que vous les lisez.
Quand vous vous demandez si quelque chose mérite d'être une fonction, ne vous
demandez pas combien de fois vous l'appellerez. Demandez-vous si ça a un nom.
[#_try_it_5]
=== Essayez
. *Renommez un paramètre.* Dans `drawStar`, changez `x` et `y` en `across` et
`down`, partout dans la fonction. Ne touchez à rien en dehors. Lancez.
Trouvez pourquoi rien n'a cassé.
. *Dessinez-les dans l'autre ordre.* Échangez `drawStars()` et `drawPlayer()`
dans `onOverlayUpdate` et regardez le vaisseau. Puis remettez-les. Chacune de
ces cinq lignes est un travail, et leur ordre à l'écran est leur ordre dans la
fonction.
. *Utilisez `clamp` sur une nuance.* Changez `shadeFor` en
`return clamp(math.random(60, 90) + layer * 90, 0, 200)` et regardez les trois
couches. Puis retirez le `clamp` et regardez encore : `colorForeground`
retenait discrètement les nombres pour vous depuis le début, et maintenant
vous voyez où.
. *Appelez `drawStar` avec deux arguments.* Écrivez `drawStar(100, 100)`
quelque part dans `drawLayer` et lancez. L'erreur nomme exactement ce qui
manquait. Lisez-la avant de remettre le troisième argument.
. *Déplacez un nom local.* Prenez la ligne `local drift = 0` et déplacez-la à la
fin du fichier, en dessous de tout. Lancez, lisez l'erreur, et pensez aux mots
« en dessous » dans la partie ci-dessus.
[#_break_it_on_purpose_5]
=== Cassez-le exprès
L'ordre d'un fichier qui contient des noms locaux compte, et l'erreur que vous
obtenez quand vous cassez cet ordre n'est pas celle que vous attendez.
Prenez toute la fonction `drawStar` et déplacez-la tout *en bas* du script, sous
`onOverlayUpdate`. Tout est encore là, écrit pareil. Lancez :
----
Error executing function 'onOverlayUpdate': functions/functions.singe:50: attempt to call a nil value (global 'drawStar')
stack traceback:
functions/functions.singe:50: in upvalue 'drawLayer'
functions/functions.singe:57: in upvalue 'drawStars'
functions/functions.singe:101: in function 'onOverlayUpdate'
----
Elle n'a pas la même allure que l'erreur de la leçon un, parce que c'est une
autre sorte d'erreur. L'erreur de la leçon un a été attrapée avant que le script
s'exécute. Celle-ci a été attrapée pendant qu'il s'exécutait, c'est pourquoi
Singe nomme la fonction dans laquelle il était, et pourquoi il y a un
*traceback* dessous : la liste de qui a appelé qui, le plus récent en premier.
La ligne 50 est dans `drawLayer`, qui a été appelée depuis la ligne 57, dans
`drawStars`, qui a été appelée depuis la ligne 101, dans `onOverlayUpdate`. Vos
numéros de ligne seront différents si votre fichier ne correspond pas exactement
à celui ci-dessus, et le moteur peut ajouter une ligne ou deux de son cru sous
celles-ci. Le mot `upvalue` est le nom que Lua donne à un nom local qu'une
fonction a emprunté au fichier qui l'entoure ; passez dessus pour l'instant.
La plainte elle-même vaut la peine d'être apprise par cœur. « Attempt to call a
nil value » veut dire que vous avez mis des parenthèses après un nom qui ne
contenait rien. Le mot `global` devant `drawStar`, c'est la partie qui vend la
mèche : vous avez marqué `drawStar` comme locale, donc quand Lua a lu
`drawLayer` et n'a trouvé aucun nom local qui s'appelle ainsi nulle part
au-dessus, il ne restait plus qu'à essayer un nom global -- et il n'y a pas non plus de nom global appelé
`drawStar`. Remettez la fonction au-dessus du code qui l'appelle, et l'erreur
s'en va avec.
[#_what_you_learned_5]
=== Ce que vous avez appris
* Vous pouvez écrire vos propres fonctions, et rien ne les exécute à part vos
propres appels.
* Les noms entre les parenthèses d'une fonction sont des paramètres, et ce sont
les copies, propres à la fonction, de ce que l'appelant a passé.
* `return` renvoie une valeur et arrête la fonction sur-le-champ, et une
fonction qui se termine sans en avoir un renvoie `nil`.
* Un appel peut être l'argument d'un autre appel, et celui de l'intérieur se
fait en premier.
* Lua peut renvoyer plus d'une valeur, et vous les attrapez avec plusieurs noms
séparés par des virgules.
* `local` crée un nom qui n'existe que là où il a été écrit.
* Un nom local dans une fonction est créé tout neuf à chaque appel et ne se
souvient de rien de l'appel précédent.
* Un nom local ne peut être vu que sous la ligne qui le crée, donc les valeurs
et vos propres fonctions vont au-dessus des callbacks qui s'en servent.
* Les callbacks restent globaux, parce que Singe les trouve par leur nom.
* Une fonction vaut la peine d'être écrite quand ce qu'elle fait a un nom, même
si vous ne l'appelez qu'une fois.
[#_next_time_5]
=== La prochaine fois
Vos cent vingt étoiles sont toujours cent vingt copies de trois idées. Rien dans
le programme n'est *une étoile* : il y a un nombre, et un peu d'arithmétique qui
transforme ce nombre en position. Vous ne pouvez pas dire « celle-ci est plus
proche, donc elle tombe plus vite et brille plus fort », parce qu'il n'y a pas
de « celle-ci » dont le dire, et vous ne pouvez pas laisser une étoile tomber
par le bas et être remplacée par une nouvelle, parce qu'il n'y a rien là à
retirer.
La leçon six vous donne la chose qui contient une étoile, puis un moyen d'en
contenir cent vingt et d'en ajouter une cent vingt et unième pendant que le jeu
tourne. C'est la leçon la plus utile de cette partie du livre.