[#_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.