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