singe/docs/fr/lessons/06-tables.adoc

789 lines
28 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_6_lists_of_things]
== Leçon 6 : Des listes de choses
image::learn/06-tables.png[La leçon terminée, 480]
Vous avez cent vingt étoiles et pas une seule n'existe. Il n'y a d'étoile nulle
part dans votre programme : il y a une boucle qui tourne cent vingt fois, et à
chaque tour elle calcule deux nombres à partir du numéro de l'étoile et de sa
couche, allume un pixel, et oublie tout. Les trois couches sont là pour simuler
la seule chose que cet arrangement ne sait pas faire, c'est-à-dire laisser une
étoile être différente de sa voisine.
Cette leçon vous donne ce qui manque. Vous allez apprendre à garder une liste
d'éléments, à fabriquer un élément qui contient plusieurs valeurs nommées à la
fois, puis à réunir les deux en une liste d'éléments qui se souviennent chacun
de leur position, de leur vitesse et de leur luminosité. C'est la plus longue
leçon de cette partie du livre, et celle sur laquelle s'appuie tout ce qui suit.
Prenez votre temps.
[#_start_a_new_folder_5]
=== Créez un nouveau dossier
Un dossier appelé `tables`, dedans un fichier appelé `tables.singe`, et
`Singe -R tables` qui tourne dans un terminal. Copiez-y le programme terminé de
la leçon cinq : le champ d'étoiles, le vaisseau, et tout le reste.
[#_a_list]
=== Une liste
Ajoutez cette ligne en haut de `tables.singe`, sous les autres valeurs, et les
trois appels à `overlayPrint` dans `onOverlayUpdate`, au-dessus du `return`.
[source,lua]
----
local names = { "Ada", "Grace", "Alan" }
----
[source,lua]
----
overlayPrint(2, 2, names[1])
overlayPrint(2, 3, names[3])
overlayPrint(2, 4, "There are " .. #names .. " names.")
----
Lancez-le. `Ada` sur une ligne, `Alan` sur la suivante, et `There are 3 names.`
en dessous.
Les accolades de la première ligne fabriquent une *table*. Une table est le seul
conteneur qu'ait Lua, et tout ce dont vous voudrez un jour garder plus d'un
exemplaire va dans une table. Ici, elle contient trois chaînes à la suite, ce
qui est la forme qu'on appelle une *liste*.
Les crochets des lignes suivantes en sortent un élément. `names[1]` est le
premier élément, `names[3]` le troisième, et le nombre entre les crochets
s'appelle l'*indice*.
Ce nombre, c'est là que Lua se sépare de presque tous les autres langages que
vous rencontrerez. *Lua compte à partir de un.* C, Java, Python, JavaScript et
la plupart des autres comptent à partir de zéro, si bien que leur premier
élément est l'élément zéro et que leur troisième élément est à l'indice deux. Le
premier élément de Lua est l'élément un, et c'est la seule chose qui fait
trébucher les programmeurs venus d'ailleurs. Vous ne venez de nulle part, alors
votre tâche est plus facile : le premier est le numéro un.
`names[0]` n'est pas une erreur, au passage. C'est `nil` -- la valeur « rien »
de la leçon trois -- parce que rien n'y est rangé. Lire un indice qui n'a jamais
été rempli donne toujours `nil`, ce qui est bien aimable jusqu'au moment où vous
essayez de vous en servir.
Le `#` devant le nom d'une table est la dernière ponctuation nouvelle avant un
bon moment. Il vous donne le nombre d'éléments de la liste, donc `#names` vaut
`3`. Comme le nombre d'éléments et le dernier indice sont le même nombre,
`names[#names]` est toujours le dernier élément, quelle que soit la longueur de
la liste.
[#_adding_and_taking_away]
=== Ajouter et retirer
Une liste qu'il faut écrire en entier ne sert pas à grand-chose dans un jeu. Ces
deux appels sont la manière dont une liste change pendant que le programme
tourne.
[source,lua]
----
table.insert(names, "Katherine")
----
Ça met `Katherine` au bout, et `#names` vaut maintenant `4`. `table.insert` est
une fonction comme une autre, et le point au milieu de son nom veut dire qu'elle
vit dans une table appelée `table` que Lua fournit -- une table de fonctions
pour travailler sur les tables. Vous n'avez pas à la créer ni à la charger. Elle
est là.
Donnez trois arguments à `table.insert` et celui du milieu dit où mettre le
nouvel élément.
[source,lua]
----
table.insert(names, 1, "Edsger")
----
`Edsger` entre en tête, et tout ce qui était déjà là se décale pour lui faire de
la place : `Ada` était l'élément un et devient l'élément deux. Rien n'est écrasé
et rien n'est perdu.
Retirer un élément, c'est l'image inverse.
[source,lua]
----
table.remove(names, 2)
----
L'élément deux s'en va, et tout ce qui le suivait recule d'un cran pour combler
le vide, si bien que la liste n'a jamais de trou au milieu. `table.remove` vous
rend aussi l'élément qu'il a retiré, donc `local gone = table.remove(names, 2)`
s'en débarrasse et vous dit ce que c'était en une seule ligne. Ne mettez pas de
nombre du tout -- `table.remove(names)` -- et il prend le dernier élément, ce
qui est le moyen le plus rapide de se servir d'une liste comme d'une pile.
Mettez ces trois appels dans votre script, un à la fois, lancez-le après chacun,
et regardez changer le nombre sur la troisième ligne.
[#_walking_a_list]
=== Parcourir une liste
Vous connaissez déjà une manière de visiter chaque élément, parce que vous vous
en servez depuis la leçon quatre :
[source,lua]
----
for i = 1, #names do
overlayPrint(2, i + 1, names[i])
end
----
Ça marche. Commencer à un, s'arrêter au nombre d'éléments, se servir du compteur
comme indice. Il y a une meilleure manière, et c'est celle que vous écrirez
désormais :
[source,lua]
----
for i, name in ipairs(names) do
overlayPrint(2, i + 1, name)
end
----
`ipairs` parcourt une liste depuis l'élément un jusqu'à ce qu'il n'y en ait
plus, et donne deux choses à votre boucle à chaque tour : l'indice et l'élément
lui-même. C'est le deuxième qui compte. Avec le `for` numérique, vous obtenez un
nombre et vous devez retourner à la table avec `names[i]` pour savoir ce que
c'est ; avec `ipairs`, l'élément est déjà dans votre main, sous le nom que vous
avez mis en second.
C'est aussi plus difficile de se tromper. `for i = 1, #names` est une belle
occasion de se tromper d'une unité -- commencez à zéro et vous affichez un
`nil`, arrêtez-vous à `#names - 1` et vous perdez le dernier -- et `ipairs` ne
contient aucun nombre sur lequel se tromper.
La plupart du temps, vous ne voulez pas l'indice, seulement l'élément. Écrivez
l'indice sous la forme d'un simple tiret bas, et n'en parlons plus :
[source,lua]
----
for _, name in ipairs(names) do
----
`_` est un nom parfaitement ordinaire, et rien dans Lua ne le traite à part.
C'est une habitude que partagent les programmeurs pour dire « quelque chose
arrive ici et je ne vais pas m'en servir ». Quiconque lit votre boucle sait au
premier coup d'œil que l'indice n'y compte pas.
Supprimez maintenant vos essais avec `names`, et les lignes `overlayPrint` avec.
Vous savez ce qu'est une liste. Il est temps d'y ranger quelque chose de mieux
qu'une chaîne.
[#_a_table_can_also_be_a_record]
=== Une table peut aussi être un enregistrement
Une étoile doit se souvenir de quatre choses : où elle se trouve de gauche à
droite, où elle se trouve de haut en bas, à quelle vitesse elle tombe, et à quel
point elle brille. Quatre listes séparées, toutes tenues à jour ensemble à la
main, ce serait l'enfer. Fabriquez plutôt une table et mettez-y des noms.
[source,lua]
----
local star = {}
star.x = 40
star.y = 0
star.speed = 2
star.shade = 180
----
Les accolades vides fabriquent une table vide. Les quatre lignes suivantes y
mettent des valeurs sous des noms au lieu de numéros, et le point est la manière
de dire quel nom vous voulez. `star.x` se lit le `x` de `star`, et ça se
comporte exactement comme n'importe quelle autre variable : on peut le lire, lui
donner une valeur, lui ajouter quelque chose.
[source,lua]
----
star.y = star.y + star.speed
----
Une table utilisée de cette manière -- un ensemble fixe de valeurs nommées qui
décrivent une seule chose -- est un *enregistrement*, et les noms qu'elle
contient sont ses *champs*. C'est le même genre de table que la liste
ci-dessus. La seule différence, c'est qu'on trouve les éléments d'une liste par
numéro et les champs d'un enregistrement par nom, et qu'une même table peut
faire les deux à la fois si un jour vous le voulez.
Le point est un raccourci. `star.x` et `star["x"]` veulent dire exactement la
même chose, et ça vaut la peine de le voir une fois : les crochets acceptent un
nom aussi volontiers qu'un nombre, donc une liste et un enregistrement sont
vraiment une seule idée sous deux casquettes. Utilisez le point. Ça se lit
mieux.
Un champ que vous n'avez jamais rempli vaut `nil`, comme un indice jamais
rempli. Il n'existe nulle part de liste des noms de champ autorisés, et rien ne
vérifie votre orthographe, donc `star.sped` n'est pas une erreur -- c'est `nil`,
et vous verrez ce qui arrive ensuite à la fin de cette leçon.
Écrire quatre lignes pour remplir quatre champs, on s'en lasse. Vous pouvez
mettre les champs à l'intérieur des accolades quand vous fabriquez la table :
[source,lua]
----
local star = {
x = 40,
y = 0,
speed = 2,
shade = 180
}
----
La même table, en une seule instruction. Les virgules séparent les champs, le
dernier n'a pas besoin de virgule après lui, et les lignes ne sont étalées que
pour se lire facilement.
[#_a_list_of_records]
=== Une liste d'enregistrements
Voici toute l'idée de cette leçon en deux lignes :
[source,lua]
----
local stars = {}
table.insert(stars, star)
----
Une liste peut contenir n'importe quoi, y compris une table. Une liste
d'enregistrements est donc une liste de choses qui se souviennent chacune de
tout ce qui les concerne. `stars[1]` est une étoile entière, et `stars[1].y` est
la distance de cette étoile depuis le haut de l'écran. Presque tous les jeux que
vous écrirez sont une poignée de listes comme celle-ci : les ennemis, les
balles, les bonus et les blocs qui tombent.
Il y a une chose à savoir sur les tables avant d'écrire le code, et elle piège
tout le monde une fois. Donner une table à une fonction ne la copie pas. Un
nombre est copié -- c'est pour ça que `half` pouvait découper son `number` à la
leçon cinq sans déranger `drift` -- mais une table est partagée. Les deux noms
désignent la même table, et un changement fait par l'un se voit par l'autre.
[source,lua]
----
local star = stars[3]
star.y = star.y + 1
----
Ça déplace la troisième étoile de la liste. Pas une copie d'elle : elle. C'est
exactement ce que vous voulez ici, et c'est ce qui fait marcher la section
suivante, mais souvenez-vous-en le jour où une fonction que vous avez écrite
change quelque chose que vous ne vous attendiez pas à la voir changer.
[#_rebuild_the_star_field]
=== Reconstruisez le champ d'étoiles
Dehors, l'arithmétique. Une étoile va devenir une chose.
Supprimez `starAt`, `shadeFor`, `drawLayer` et l'ancien `drawStar`, et avec eux
les constantes qu'ils utilisaient : `LAYERS`, `STARS_PER_LAYER`, `ACROSS_STEP`,
`DOWN_STEP`, `LAYER_SHIFT` et `LAYER_SPEED`. La variable `drift` s'en va
aussi, tout comme la ligne de `drift` en haut de `onOverlayUpdate`.
`LAYER_BRIGHT` reste, sous un nom qui ne parle plus de couches, et deux valeurs
le rejoignent :
[source,lua]
----
local STAR_COUNT = 120
local STAR_SHADE = 55
local BURST_COUNT = 10
----
[source,lua]
----
local stars = {}
----
Cent vingt, c'est ce que vous aviez : trois couches de quarante. La liste
commence vide, et voici ce qui la remplit :
[source,lua]
----
local function newStar(y)
local speed = math.random(1, 3)
local star = {
x = math.random(0, width - 1),
y = y,
speed = speed,
shade = math.random(60, 90) + speed * STAR_SHADE
}
return star
end
----
`newStar` fabrique une étoile et vous la rend. Regardez le dernier champ et
comparez-le avec `shadeFor` à la leçon cinq : le même calcul, avec la vitesse de
l'étoile là où se trouvait le numéro de couche. Les couches n'ont pas tant
disparu que fondu. Chaque étoile choisit maintenant sa propre vitesse, un, deux
ou trois pixels par image, et une étoile qui tombe plus vite est plus brillante,
ce qui fait toute l'illusion de profondeur et coûte une ligne.
Le `y` de départ est un paramètre parce que les deux appelants veulent des
choses différentes. Les étoiles fabriquées au démarrage du jeu doivent être
éparpillées sur tout l'écran ; celles fabriquées plus tard arrivent par le haut.
[source,lua]
----
local function makeStars()
for number = 1, STAR_COUNT do
table.insert(stars, newStar(math.random(0, height - 1)))
end
end
----
Rien n'appelle encore `makeStars`. Ajoutez une ligne tout en bas du fichier,
après tout le reste et en dehors de toute fonction :
[source,lua]
----
makeStars()
----
Il y a plus à dire sur cette ligne, et ça attendra la fin de la leçon. Passons
au dessin, qui raccourcit chaque fois que vous y touchez :
[source,lua]
----
local function drawStar(star)
colorForeground(star.shade, star.shade, star.shade)
overlayPlot(star.x, star.y)
end
local function drawStars()
for _, star in ipairs(stars) do
drawStar(star)
end
end
----
`drawStar` prenait trois nombres à la leçon cinq et prend maintenant une étoile.
Tout ce qu'elle a besoin de savoir est dans l'étoile, et c'est toute la raison
d'être des enregistrements : une seule chose à faire passer au lieu de quatre,
et aucun risque de les donner dans le mauvais ordre. Et `drawStars` a perdu sa
boucle sur les couches, ainsi que tout intérêt pour le nombre d'étoiles ou leur
provenance.
[#_stars_that_come_and_go]
=== Des étoiles qui vont et viennent
Maintenant, la partie que l'ancien champ d'étoiles ne savait pas faire du tout.
Les étoiles tombent chacune à sa vitesse, sortent par le bas, et sont remplacées
en haut.
[source,lua]
----
local function moveStars()
for i = #stars, 1, -1 do
local star = stars[i]
star.y = star.y + star.speed
if star.y >= height then
table.remove(stars, i)
end
end
while #stars < STAR_COUNT do
table.insert(stars, newStar(0))
end
end
----
Appelez-la depuis `onOverlayUpdate`, sur la ligne où se trouvait
`drift = drift + 1`.
Chaque étoile descend de sa propre `speed`, et c'est la ligne que la leçon cinq
ne pouvait pas écrire. Le `while` du bas, c'est le réapprovisionnement : tant
qu'il y a moins de cent vingt étoiles, en fabriquer une autre en haut de
l'écran. Il tourne autant de fois qu'il le faut puis s'arrête, et c'est le bon
outil quand vous ne savez pas combien il en manque.
La ligne `for` du haut compte à rebours, avec le pas que vous avez rencontré à
la leçon quatre. `for i = #stars, 1, -1` visite le dernier élément en premier et
remonte jusqu'à l'élément un. Ce n'est pas une fioriture. C'est la seule manière
sûre de faire ce que fait cette boucle, et la section suivante explique
pourquoi.
[#_never_remove_going_forwards]
=== Ne retirez jamais en avançant
Écrivez la même boucle dans l'autre sens et elle est cassée :
[source,lua]
----
for i = 1, #stars do
local star = stars[i]
star.y = star.y + star.speed
if star.y >= height then
table.remove(stars, i)
end
end
----
Suivez-la à la main avec quatre étoiles, et supposez que les étoiles deux et
trois aient toutes les deux atteint le bas.
* `i` vaut `1`. L'étoile un va bien.
* `i` vaut `2`. L'étoile deux est sortie par le bas, alors elle s'en va. Tout
recule d'un cran : l'étoile qui était la trois est maintenant la deux, et
celle qui était la quatre est maintenant la trois.
* `i` vaut `3`. C'est l'étoile qui était la quatre. *L'étoile trois n'a jamais
été examinée.* Retirer l'élément deux l'a fait reculer dans la case deux, et
la boucle était déjà passée par la case deux.
* `i` vaut `4`. Il n'y a plus d'élément quatre. `stars[4]` vaut `nil`.
Deux défauts distincts pour une seule petite erreur. Des éléments sont sautés,
si bien qu'une étoile qui avait atteint le bas n'est même pas examinée à ce
tour-ci. Une étoile qui survit une image de plus ne fait pas de mal, mais la
même boucle sur une liste d'ennemis, où c'est l'examen qui les tue, est un bug
que vous chercheriez pendant une heure. Le second défaut fait plus de bruit. La
liste est maintenant plus courte que le nombre avec lequel la boucle a commencé,
parce que `#stars` a été calculé une seule fois, avant tout retrait, alors les
derniers tours de la boucle vont au-delà de la fin de la liste :
----
Error executing function 'onOverlayUpdate': tables/tables.singe:60: attempt to index a nil value (local 'star')
stack traceback:
tables/tables.singe:60: in upvalue 'moveStars'
tables/tables.singe:124: in function 'onOverlayUpdate'
----
« Attempt to index a nil value » veut dire que vous avez mis un point après
quelque chose qui ne contenait rien. Le `(local 'star')` à la fin le nomme :
`star` valait `nil`, donc `star.y` n'avait nulle part où chercher.
Aller à rebours corrige les deux défauts à la fois, et ça vaut la peine de voir
pourquoi plutôt que de le croire sur parole. Quand vous retirez l'élément `i`,
tout ce qui suit `i` recule d'une case -- et en allant à rebours, tout ce qui
suit `i`, c'est tout ce que vous avez déjà visité. Ce qui reste à venir, les
éléments `i - 1` jusqu'à `1`, n'a pas bougé du tout. La liste qui rétrécit
derrière vous ne peut pas vous faire de mal non plus, parce que vous marchez
vers l'élément un et que l'élément un est toujours là.
La règle, qui vous servira toute votre vie : *quand une boucle risque de retirer
des éléments de la liste qu'elle parcourt, parcourez-la à rebours.*
`ipairs` n'est pas une exception. Retirer des éléments au milieu d'une boucle
`ipairs` tourne mal de la même façon. `ipairs` sert à regarder, et le `for` à
rebours sert à changer.
[#_add_some_yourself]
=== Ajoutez-en vous-même
Une branche de plus dans `onInputPressed` et vous pouvez regarder la liste
grandir pendant que le jeu tourne. Elle va avec les deux autres :
[source,lua]
----
elseif what == SWITCH_BUTTON1 then
addBurst()
----
[source,lua]
----
local function addBurst()
for number = 1, BURST_COUNT do
table.insert(stars, newStar(0))
end
end
----
`addBurst` va au-dessus des callbacks, avec vos autres fonctions. Et pour que
vous puissiez voir changer la longueur de la liste, affichez le nombre d'étoiles
dans `onOverlayUpdate` :
[source,lua]
----
overlayPrint(0, 0, "Stars: " .. #stars)
----
Appuyez sur le bouton -- la barre d'espace, sauf si vous avez changé ce qui
produit `SWITCH_BUTTON1` -- une douzaine de fois. Le nombre monte de dix à
chaque appui et le ciel s'épaissit, et rien ne se répète tant que vous le gardez
enfoncé : `onInputPressed` n'est prévenu qu'une fois, quand le bouton s'enfonce.
Puis regardez le nombre redescendre tout seul à cent vingt à mesure que les
étoiles en trop atteignent le bas et sont retirées, parce que le
réapprovisionnement ne fait jamais que compléter la liste jusqu'à `STAR_COUNT`
et ne la raccourcit jamais.
Rien dans le dessin, le déplacement ou le retrait n'a eu besoin d'être prévenu
que le nombre d'étoiles avait changé. Tout fonctionne à partir de la liste.
[#_the_whole_thing_5]
=== Le tout
[source,lua]
----
local STAR_COUNT = 120
local STAR_SHADE = 55
local BURST_COUNT = 10
local PLAYER_WIDTH = 20
local PLAYER_HEIGHT = 6
local PLAYER_SPEED = 3
local PLAYER_MARGIN = 6
local width = overlayGetWidth()
local height = overlayGetHeight()
local stars = {}
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 newStar(y)
local speed = math.random(1, 3)
local star = {
x = math.random(0, width - 1),
y = y,
speed = speed,
shade = math.random(60, 90) + speed * STAR_SHADE
}
return star
end
local function makeStars()
for number = 1, STAR_COUNT do
table.insert(stars, newStar(math.random(0, height - 1)))
end
end
local function addBurst()
for number = 1, BURST_COUNT do
table.insert(stars, newStar(0))
end
end
local function moveStars()
for i = #stars, 1, -1 do
local star = stars[i]
star.y = star.y + star.speed
if star.y >= height then
table.remove(stars, i)
end
end
while #stars < STAR_COUNT do
table.insert(stars, newStar(0))
end
end
local function drawStar(star)
colorForeground(star.shade, star.shade, star.shade)
overlayPlot(star.x, star.y)
end
local function drawStars()
for _, star in ipairs(stars) do
drawStar(star)
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
elseif what == SWITCH_BUTTON1 then
addBurst()
end
end
function onInputReleased(what)
if what == SWITCH_LEFT then
movingLeft = false
elseif what == SWITCH_RIGHT then
movingRight = false
end
end
function onOverlayUpdate()
overlayClear()
movePlayer()
moveStars()
drawStars()
drawPlayer()
overlayPrint(0, 0, "Stars: " .. #stars)
return OVERLAY_UPDATED
end
makeStars()
----
[#_what_just_happened_3]
=== Ce qui vient de se passer
Le programme est plus court que celui de la leçon cinq, et il fait quelque chose
que la leçon cinq ne pouvait même pas commencer à faire. Trois morceaux méritent
un second regard.
[source,lua]
----
makeStars()
----
Cette unique ligne tout en bas du fichier n'est à l'intérieur d'aucune fonction,
et on la rate facilement. Lua lit votre fichier de haut en bas quand Singe le
charge, et une ligne au niveau extérieur du fichier s'exécute sur-le-champ, une
fois, avant que le moteur ait appelé quoi que ce soit. Tout ce qui est au-dessus
n'est que définitions -- voici une valeur, voici une fonction -- et c'est la
seule instruction qui s'exécute au chargement. Elle doit être en bas, parce que
`makeStars` n'existe pas tant que la ligne qui la crée n'a pas été lue.
[source,lua]
----
local star = stars[i]
star.y = star.y + star.speed
----
`star` est une variable locale créée à neuf à chaque tour de la boucle, et elle
contient la même table que celle qui se trouve dans la liste -- pas une copie.
Changer `star.y` déplace l'étoile qui est dans la liste. Si les tables étaient
copiées, cette boucle déplacerait cent vingt copies par image et rien ne
changerait jamais à l'écran.
[source,lua]
----
for _, star in ipairs(stars) do
drawStar(star)
end
----
Comparez avec la leçon quatre : deux boucles imbriquées, deux calculs par
étoile, un reste pour garder chacune à l'écran, et trois couches pour simuler la
variété que vous obtenez maintenant gratuitement. Le dessin ne sait plus et ne
se soucie plus de savoir comment les étoiles sont arrivées là où elles sont,
combien il y en a, ni ce qui va leur arriver. Chaque partie du programme fait un
seul travail, et la liste est ce sur quoi elles sont toutes d'accord.
[#_try_it_6]
=== Essayez
. *Rendez-le plus profond.* Changez `math.random(1, 3)` en `math.random(1, 6)`
et `STAR_SHADE` en `25`. Plus de vitesses, plus de nuances, un ciel plus
profond.
. *Moins d'étoiles, plus grosse salve.* Mettez `STAR_COUNT` à `10` et
`BURST_COUNT` à `100`, puis appuyez trois ou quatre fois sur le bouton.
Regardez le nombre monter à l'écran, et chronométrez le temps qu'il met à
redescendre.
. *Donnez un autre champ à une étoile.* Ajoutez `age = 0` dans `newStar`,
ajoutez un à `star.age` dans `moveStars`, et affichez `stars[1].age` à côté du
nombre d'étoiles. Rien d'autre dans le programme n'a besoin de changer pour
porter un fait nouveau sur chaque étoile.
. *Arrêtez de les retirer.* Supprimez les trois lignes du bloc
`if star.y >= height` dans `moveStars` et lancez. Attendez dix secondes, puis
lisez le nombre à l'écran et regardez le ciel. Trouvez comment les deux
peuvent être vrais à la fois.
. *Faites-le dans l'autre sens.* Changez `for i = #stars, 1, -1` en
`for i = 1, #stars` et lancez jusqu'à ce que ça plante. Puis relisez la
section ci-dessus avec la vraie erreur sous les yeux.
[#_break_it_on_purpose_6]
=== Cassez-le exprès
Faites une faute dans un nom de champ. Dans `moveStars`, changez `star.speed`
en `star.sped` :
[source,lua]
----
star.y = star.y + star.sped
----
Lancez-le :
----
Error executing function 'onOverlayUpdate': tables/tables.singe:60: attempt to perform arithmetic on a nil value (field 'sped')
stack traceback:
tables/tables.singe:60: in upvalue 'moveStars'
tables/tables.singe:124: in function 'onOverlayUpdate'
----
Vos numéros de ligne ne seront pas exactement ceux-là, sauf si votre fichier
correspond ligne pour ligne à celui d'au-dessus. Le nom du fichier, le nombre
qui le suit et la plainte, voilà ce que vous lisez.
Lisez d'abord la fin de la première ligne. `field 'sped'`, c'est Lua qui vous
dit quel nom est revenu vide, et il vous dit que c'était un champ -- quelque
chose après un point -- plutôt qu'une variable à vous. « Attempt to perform
arithmetic on a nil value », c'est ce qu'il était en train de faire quand il
s'en est aperçu : ajouter `nil` à un nombre, ce qui est impossible.
Il n'y a eu aucune plainte quand la table a été fabriquée sans champ `sped`, et
aucune au moment où vous l'avez demandé. Une table donne `nil` pour tout nom
qu'elle n'a pas, sans commentaire. L'erreur vient plus tard, à la première
ligne qui essaie d'*utiliser* le rien qu'on vous a donné, et qui peut être très
loin de la faute de frappe. Quand vous voyez `nil value (field 'something')`,
cherchez l'endroit où ce nom a été écrit autrement.
[#_what_you_learned_6]
=== Ce que vous avez appris
* Les accolades fabriquent une table, le seul conteneur qu'ait Lua. Une table
utilisée comme liste contient des éléments à la suite, et des crochets avec un
indice en sortent un.
* Lua compte à partir de un. La plupart des autres langages comptent à partir
de zéro.
* `#list` est le nombre d'éléments qu'elle contient, et `list[#list]` est le
dernier.
* `table.insert` ajoute un élément, au bout ou à la position que vous indiquez ;
`table.remove` en retire un et comble le vide.
* Un indice ou un champ jamais rempli vaut `nil`, et rien ne vous prévient tant
que vous ne vous en servez pas.
* `ipairs` parcourt une liste et vous donne l'indice et l'élément ; `_` est
l'habitude pour un indice dont vous ne voulez pas.
* Une table utilisée comme enregistrement contient des champs nommés, qu'on
atteint avec un point.
* Une liste d'enregistrements, c'est la manière dont un jeu garde ses ennemis,
ses balles, et tout ce dont il a plus d'un exemplaire.
* Les tables sont partagées, pas copiées, quand vous les faites passer.
* Quand une boucle risque de retirer des éléments de la liste qu'elle parcourt,
parcourez-la à rebours.
[#_next_time_6]
=== La prochaine fois
Vous avez maintenant toutes les pièces d'un jeu : des choses qui bougent, un
vaisseau qui répond aux commandes, des décisions, des boucles, des fonctions, et
une liste que des choses peuvent rejoindre et quitter pendant que le jeu tourne.
La leçon sept les assemble en un jeu que vous pouvez perdre. Vous vous dirigez
le long du bas de l'écran, des blocs tombent du haut, et vous restez hors de
leur chemin -- et les blocs qui tombent sont une liste d'enregistrements,
fabriqués, déplacés et retirés exactement comme vos étoiles.