532 lines
23 KiB
Text
532 lines
23 KiB
Text
[#_lesson_8_when_it_goes_wrong]
|
||
== Leçon 8 : Quand ça tourne mal
|
||
|
||
image::learn/08-when-it-goes-wrong.png[La leçon terminée, 480]
|
||
|
||
Vous avez écrit un jeu. À partir de maintenant, vous passerez plus de temps à
|
||
réparer des programmes qu'à en écrire, et ce n'est pas le signe que quelque
|
||
chose ne va pas chez vous. C'est le métier. La différence entre quelqu'un qui
|
||
aime programmer et quelqu'un qui abandonne n'est presque jamais le talent ;
|
||
c'est de savoir s'il a appris à lire ce que l'ordinateur lui dit.
|
||
|
||
Cette leçon n'a donc rien de nouveau à construire. Ce sont les cinq messages que
|
||
vous verrez le plus souvent pour le reste de votre vie, ce que chacun veut
|
||
vraiment dire, et quatre manières de traquer l'erreur qui se cache derrière.
|
||
Gardez ouvert `dodge.singe` de la leçon sept, parce que vous allez le casser
|
||
exprès plusieurs fois.
|
||
|
||
[#_two_kinds_of_stop]
|
||
=== Deux sortes d'arrêt
|
||
|
||
Singe s'arrête pour deux raisons différentes, et il le dit de deux manières
|
||
différentes.
|
||
|
||
Quand le script ne veut même pas démarrer, vous obtenez cette forme :
|
||
|
||
----
|
||
Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock'
|
||
----
|
||
|
||
Lua lit votre fichier entier avant d'en exécuter un seul mot, et ceci veut dire
|
||
qu'il n'a pas pu comprendre ce qu'il a lu. Rien ne s'est exécuté du tout.
|
||
|
||
Quand le script a bien démarré mais que quelque chose a mal tourné plus tard,
|
||
vous obtenez plutôt cette forme :
|
||
|
||
----
|
||
Error executing function 'onOverlayUpdate': dodge/dodge.singe:98: attempt to perform arithmetic on a nil value (global 'blockSped')
|
||
stack traceback:
|
||
dodge/dodge.singe:98: in function 'updatePlaying'
|
||
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
||
----
|
||
|
||
Le jeu tournait. Singe a appelé votre `onOverlayUpdate`, comme il le fait
|
||
soixante fois par seconde, et cette fois l'appel n'est pas revenu.
|
||
|
||
Les deux formes contiennent les trois mêmes choses, et ce sont les trois choses
|
||
que vous voulez : un fichier, une ligne et une plainte. Lisez-les dans cet
|
||
ordre, à chaque fois.
|
||
|
||
Les numéros de ligne de cette leçon viennent du script terminé de la leçon sept.
|
||
Si les vôtres sont décalés d'une ligne ou deux, votre fichier l'est aussi, et
|
||
rien dans la lecture d'une erreur ne dépend de ce que le numéro corresponde.
|
||
|
||
[#_the_ones_that_stop_it_before_it_starts]
|
||
=== Celles qui l'arrêtent avant qu'il démarre
|
||
|
||
Ce sont les faciles, et ce sont aussi celles qui font le plus mauvais effet,
|
||
parce que l'écran reste noir et qu'il n'y a rien à regarder.
|
||
|
||
Allez dans `updatePlaying` et supprimez le mot `then` de la ligne
|
||
`if spawnTimer <= 0 then`. Enregistrez :
|
||
|
||
----
|
||
Error running script: dodge/dodge.singe:92: 'then' expected near 'spawnBlock'
|
||
----
|
||
|
||
Lua est arrivé au bout de la ligne du `if`, voulait le mot `then`, et a trouvé à
|
||
la place le début de la ligne suivante -- qui est `spawnBlock()`, et c'est la
|
||
partie `near 'spawnBlock'`. « Near » veut dire « c'est là que je m'en suis
|
||
aperçu », et c'est en général la première chose *après* ce que vous avez oublié.
|
||
|
||
Maintenant, une plus méchante. Remettez `then`, et supprimez plutôt le `end` de
|
||
la dernière ligne de `spawnBlock`. Enregistrez :
|
||
|
||
----
|
||
Error running script: dodge/dodge.singe:148: 'end' expected (to close 'function' at line 58) near <eof>
|
||
----
|
||
|
||
La ligne 148 est la fin du fichier, juste après sa dernière ligne. La ligne 148
|
||
n'a rien qui cloche.
|
||
Lua a lu jusqu'au bout -- `<eof>` est son nom pour la fin d'un fichier -- en
|
||
attendant toujours un `end` qui n'est jamais venu, et à ce moment-là il avait
|
||
avalé toutes les fonctions qui suivent `spawnBlock` comme si elles en faisaient
|
||
partie.
|
||
|
||
La moitié utile de ce message, c'est `(to close 'function' at line 58)`, qui est
|
||
l'endroit où commence `spawnBlock`. Quand un message montre la fin du fichier,
|
||
croyez ce qu'il dit entre parenthèses, pas le numéro de ligne. C'est aussi pour
|
||
ça que l'indentation compte tant : un `end` manquant est presque invisible dans
|
||
une suite de lignes, et saute aux yeux dès que tout ce qui est en dessous est
|
||
décalé d'un cran de trop.
|
||
|
||
Deux autres à savoir reconnaître, parce que vous les rencontrerez toutes les
|
||
deux cette semaine :
|
||
|
||
----
|
||
Error running script: dodge/dodge.singe:41: unfinished string near '"GAME OVER)'
|
||
----
|
||
|
||
Un guillemet ouvert et jamais fermé, que vous avez rencontré à la leçon un.
|
||
|
||
----
|
||
Error running script: dodge/dodge.singe:135: 'then' expected near '='
|
||
----
|
||
|
||
Celle-là, c'est `if state = "playing" then`. Un seul `=` donne une valeur à une
|
||
variable, deux `==` posent une question, et Lua ne vous laisse pas donner une
|
||
valeur à l'intérieur d'un `if`. La plainte a l'air étrange, et elle veut
|
||
toujours dire la même chose.
|
||
|
||
[#_attempt_to_index_a_nil_value]
|
||
=== Attempt to index a nil value
|
||
|
||
C'est celle que vous verrez le plus, et de loin.
|
||
|
||
Allez dans `spawnBlock` et faites une faute dans `block` sur la première ligne
|
||
qui l'utilise, pour qu'elle se lise
|
||
`blocl.x = math.random(0, screenWidth - BLOCK_SIZE)`. Enregistrez, appuyez sur
|
||
Espace :
|
||
|
||
----
|
||
Error executing function 'onOverlayUpdate': dodge/dodge.singe:60: attempt to index a nil value (global 'blocl')
|
||
stack traceback:
|
||
dodge/dodge.singe:60: in function 'spawnBlock'
|
||
dodge/dodge.singe:92: in function 'updatePlaying'
|
||
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
||
----
|
||
|
||
*Indexer*, c'est ce que fait le point. `blocl.x` veut dire « va dans la chose
|
||
appelée `blocl` et trouve le `x` qui s'y trouve ». On ne peut faire ça qu'avec
|
||
une table.
|
||
|
||
*Nil*, c'est ainsi que Lua appelle un nom derrière lequel il n'y a rien. Vous ne
|
||
l'avez pas beaucoup vu parce que vous avez pris soin de donner une valeur à tout,
|
||
mais chaque nom dont vous ne vous êtes jamais servi vaut déjà `nil`, et taper
|
||
un nom de travers en invente un tout neuf.
|
||
|
||
Le message entier dit donc : *vous avez utilisé un point sur quelque chose qui
|
||
n'existe pas*.
|
||
|
||
La partie entre parenthèses est le cadeau. `global 'blocl'` est le nom exact de
|
||
la chose qui était vide. Cherchez-le dans votre fichier. S'il n'apparaît qu'une
|
||
fois, c'est une faute d'orthographe, et vous l'avez trouvée en dix secondes.
|
||
|
||
Le mot avant le nom vous dit où Lua l'a cherché :
|
||
|
||
* `global` -- un nom qui n'a de `local` devant lui nulle part.
|
||
* `local` -- un nom que vous avez déclaré avec `local` dans la fonction où vous
|
||
êtes.
|
||
* `field` -- un nom après un point, comme le `y` de `block.y`.
|
||
* `upvalue` -- un `local` venu de plus loin, que cette fonction peut voir, et
|
||
c'est ce que sont toutes les variables de `dodge.singe`, puisqu'elles sont
|
||
déclarées en haut du fichier et utilisées à l'intérieur des fonctions.
|
||
|
||
`field` est l'autre cause courante, et ce n'est pas du tout une faute
|
||
d'orthographe. Si vous avez écrit `block.hit.when` sans jamais avoir rempli
|
||
`block.hit`, vous obtenez `attempt to index a nil value (field 'hit')`. Le point
|
||
qui échoue, c'est le deuxième. Lua vous dit lequel en le nommant.
|
||
|
||
[#_attempt_to_call_a_nil_value]
|
||
=== Attempt to call a nil value
|
||
|
||
Remettez `blocl` comme il faut. Maintenant, faites une faute dans une fonction du
|
||
moteur : dans `drawGame`, changez `overlayPrint` en `overlayPirnt`.
|
||
Enregistrez :
|
||
|
||
----
|
||
Error executing function 'onOverlayUpdate': dodge/dodge.singe:36: attempt to call a nil value (global 'overlayPirnt')
|
||
stack traceback:
|
||
dodge/dodge.singe:36: in function 'drawGame'
|
||
dodge/dodge.singe:140: in function 'onOverlayUpdate'
|
||
----
|
||
|
||
Même `nil`, autre crime. *Appeler*, c'est ce que font les parenthèses, et on ne
|
||
peut faire ça qu'avec une fonction. Il n'y a pas de fonction appelée
|
||
`overlayPirnt`, donc `overlayPirnt` vaut `nil`, donc les parenthèses n'ont rien
|
||
à appeler.
|
||
|
||
Ça arrive pour quatre raisons, de la plus fréquente à la plus rare :
|
||
|
||
. Vous avez fait une faute dans le nom d'une de vos propres fonctions.
|
||
. Vous avez fait une faute dans le nom d'une de celles de Singe. Rien dans le
|
||
moteur ne vérifie votre orthographe au chargement du fichier, donc un nom du
|
||
moteur mal écrit n'est qu'un nom de plus qui se trouve être vide, et ça ne
|
||
vous coûte rien tant que la ligne ne s'exécute pas.
|
||
. La fonction n'existe pas du tout dans Singe -- c'était une supposition
|
||
raisonnable, et les suppositions raisonnables sont fausses à peu près une fois
|
||
sur deux. La liste des fonctions de la Référence est le seul moyen d'en être
|
||
sûr.
|
||
. Vous l'avez rendue `local` et vous l'avez appelée depuis un endroit situé
|
||
au-dessus de sa déclaration. C'est exactement pour ça que les fonctions de
|
||
`dodge.singe` sont globales.
|
||
|
||
Il existe une version bien pire de cette erreur, et vous l'avez rencontrée à la
|
||
leçon un sans le savoir : écrivez mal le nom d'un *callback* et vous n'obtenez
|
||
aucun message du tout. `onOverlayUpdated` n'est pas une erreur. C'est une
|
||
fonction parfaitement correcte dont Singe n'a jamais entendu parler et qu'il
|
||
n'appellera jamais, alors l'écran reste noir et rien ne se plaint. Quand toute
|
||
une fonctionnalité ne fait absolument rien, soupçonnez le nom.
|
||
|
||
[#_attempt_to_perform_arithmetic_on_a_nil_value]
|
||
=== Attempt to perform arithmetic on a nil value
|
||
|
||
Remettez `overlayPrint`. Dans `updatePlaying`, faites une faute dans
|
||
`blockSpeed` sur la ligne de chute, pour qu'elle se lise
|
||
`block.y = block.y + blockSped` :
|
||
|
||
----
|
||
Error executing function 'onOverlayUpdate': dodge/dodge.singe:98: attempt to perform arithmetic on a nil value (global 'blockSped')
|
||
stack traceback:
|
||
dodge/dodge.singe:98: in function 'updatePlaying'
|
||
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
||
----
|
||
|
||
L'arithmétique, c'est `+`, `-`, `*` et `/`. On ne peut pas ajouter rien à un
|
||
nombre. Là encore, les parenthèses nomment la chose vide, et là encore la cause
|
||
la plus courante est une faute de frappe -- mais celle-ci a une seconde cause
|
||
qu'il vaut la peine de connaître, parce qu'elle est plus difficile à voir.
|
||
|
||
Une variable à laquelle vous n'avez jamais donné de nombre vaut `nil`, pas zéro.
|
||
Si vous ajoutez un meilleur score au jeu et écrivez `local hiScore` en haut sans
|
||
`= 0`, elle contient `nil` jusqu'à ce que quelque chose la remplisse, et le
|
||
premier `hiScore + score` arrête le jeu. Lua ne fait pas partir les nombres de
|
||
zéro pour vous. C'est vous qui les faites partir de zéro.
|
||
|
||
Deux proches parents, avec la même cause et le même remède :
|
||
|
||
----
|
||
dodge/dodge.singe:37: attempt to concatenate a nil value (upvalue 'hiScore')
|
||
----
|
||
|
||
`..` qui joint une chaîne à quelque chose qui n'est pas là. Vous verrez
|
||
celle-là chaque fois que vous afficherez un score que vous avez oublié de
|
||
remplir -- ici, `hiScore` ajouté à la ligne du score dans `drawGame`, qui est la
|
||
ligne 37 maintenant que `local hiScore` se trouve au-dessus.
|
||
|
||
----
|
||
dodge/dodge.singe:105: attempt to compare nil with number
|
||
----
|
||
|
||
Ça, c'est `if score > hiScore then`, placé sous `state = "over"` pour garder le
|
||
meilleur score à la fin d'une partie, sans rien dans `hiScore`. Remarquez que ce
|
||
message
|
||
ne nomme rien entre parenthèses. Quand Lua ne peut pas vous dire quel côté était
|
||
vide, la section après la suivante vous montre comment le découvrir.
|
||
|
||
[#_an_argument_of_the_wrong_kind]
|
||
=== Un argument du mauvais type
|
||
|
||
Tous les messages jusqu'ici venaient de Lua. Celui-ci vient de Singe, et il a
|
||
l'air complètement différent, alors ça vaut la peine de le rencontrer exprès.
|
||
|
||
Supposez que vous vouliez voir ce que contient `blocks`, et que vous essayiez de
|
||
l'afficher. Ajoutez cette ligne dans `drawGame`, sous le score :
|
||
|
||
[source,lua]
|
||
----
|
||
overlayPrint(1, 3, blocks)
|
||
----
|
||
|
||
----
|
||
37:overlayPrint: Argument 3 must be a string.
|
||
----
|
||
|
||
Pas de nom de fichier, pas de trace de pile, aucun `attempt to` quoi que ce
|
||
soit. Une plainte du moteur commence par le numéro de ligne, puis le nom de la
|
||
fonction qui a refusé, puis ce qu'elle voulait. Ici, on a donné à
|
||
`overlayPrint` une table là où il lui faut du texte, et elle s'est arrêtée
|
||
plutôt que de dessiner quelque chose qui n'a pas de sens.
|
||
|
||
Le message cousin compte au lieu de vérifier :
|
||
|
||
----
|
||
34:overlayBox: Expected 4 argument(s), got 3.
|
||
----
|
||
|
||
C'est un `overlayBox` auquel il manque un coin. Le curieux `argument(s)` est
|
||
celui du moteur, pas une faute de frappe de votre part.
|
||
|
||
Quand vous obtenez l'un de ces messages, l'entrée de cette fonction dans la
|
||
Référence est la réponse, et ça vous prendra une minute. Chaque entrée donne les
|
||
arguments dans l'ordre, avec ce que chacun doit être. Singe les vérifie parce
|
||
que l'autre possibilité, c'est un jeu qui tourne dix minutes puis dessine
|
||
n'importe quoi.
|
||
|
||
[#_finding_out_what_is_really_in_there]
|
||
=== Découvrir ce qu'il y a vraiment dedans
|
||
|
||
La moitié du débogage, ce n'est pas du tout lire des erreurs. C'est le cas où
|
||
rien ne plante et où rien ne marche : des blocs qui n'apparaissent jamais, un
|
||
joueur qui refuse de bouger, un score coincé à zéro. Pour ça, il vous faut voir
|
||
à l'intérieur du jeu en marche, et il y a deux manières de le faire.
|
||
|
||
La première, c'est `debugPrint`, qui écrit une ligne dans le terminal depuis
|
||
lequel vous avez lancé Singe. Prenez la ligne à laquelle vous ne comprenez rien
|
||
et mettez-en un au-dessus :
|
||
|
||
[source,lua]
|
||
----
|
||
debugPrint("blocks " .. #blocks .. ", timer " .. spawnTimer)
|
||
----
|
||
|
||
Vous regardez maintenant ce qui est réellement là au lieu de ce que vous êtes
|
||
sûr d'y trouver, et c'est dans cet écart que vivent les bugs. Affichez
|
||
`playerX` de la même façon et vous verrez aussi la vérité à son sujet : il
|
||
s'affiche `160.0`, pas `160`, parce qu'une division avec `/` donne toujours en
|
||
Lua un nombre avec une partie fractionnaire, même quand cette partie vaut zéro.
|
||
|
||
Deux choses vont vous mordre. La première, c'est que `debugPrint` exige du texte
|
||
ou un nombre ; donnez-lui autre chose et il arrête le jeu comme l'a fait
|
||
`overlayPrint`. `debugPrint(blocks)` ne vous montrera pas une liste, et
|
||
`debugPrint("left " .. goingLeft)` tout en haut de la fonction `updatePlaying`
|
||
ne vous montrera pas non plus un `true` ou un `false` :
|
||
|
||
----
|
||
dodge/dodge.singe:78: attempt to concatenate a boolean value (upvalue 'goingLeft')
|
||
----
|
||
|
||
`tostring` est le remède. Il prend absolument n'importe quoi et vous en donne
|
||
du texte, donc `tostring(goingLeft)` vaut `"true"` ou `"false"`, et la ligne
|
||
marche.
|
||
|
||
La seconde, c'est que `onOverlayUpdate` tourne soixante fois par seconde, donc
|
||
un `debugPrint` à l'intérieur affiche soixante lignes par seconde et vous ne
|
||
pouvez en lire aucune. La Référence le dit dans son entrée : l'appel ne coûte pas
|
||
cher, mais rien ne le freine.
|
||
|
||
Avant toute chose, mettez un interrupteur devant chaque ligne de débogage que
|
||
vous ajoutez, pour pouvoir couper le bruit sans avoir à les traquer de nouveau :
|
||
|
||
[source,lua]
|
||
----
|
||
local DEBUG = true
|
||
|
||
|
||
function debugLog(text)
|
||
if DEBUG then
|
||
debugPrint(text)
|
||
end
|
||
end
|
||
----
|
||
|
||
Chaque `debugLog` du jeu obéit à ce seul `true`. Changez-le en `false` et le jeu
|
||
se tait ; remettez-le et tout revient.
|
||
|
||
Ensuite, affichez quelque chose quand il se *passe* quelque chose plutôt qu'à
|
||
chaque image :
|
||
|
||
[source,lua]
|
||
----
|
||
debugLog("hit at y " .. math.floor(block.y) .. ", lives now " .. lives)
|
||
----
|
||
|
||
Ou affichez avec un minuteur, décompté exactement comme l'apparition des
|
||
blocs :
|
||
|
||
[source,lua]
|
||
----
|
||
reportTimer = reportTimer - 1
|
||
if reportTimer <= 0 then
|
||
debugReport()
|
||
reportTimer = REPORT_FRAMES
|
||
end
|
||
----
|
||
|
||
Le dossier `learn` contient le tout sous le nom `08-when-it-goes-wrong.singe` :
|
||
c'est le jeu de la leçon sept avec un rapport environ toutes les deux secondes,
|
||
une ligne quand vous êtes touché, et une ligne quand la partie se termine.
|
||
|
||
La seconde manière de voir à l'intérieur, c'est de le mettre à l'écran, ce qui
|
||
vaut mieux pour tout ce qui change à chaque image, parce que soixante lignes par
|
||
seconde dans un terminal, ça ne sert à rien, alors qu'un nombre qui défile dans
|
||
le coin se surveille facilement :
|
||
|
||
[source,lua]
|
||
----
|
||
if DEBUG then
|
||
overlayPrint(1, 3, "BLOCKS " .. #blocks .. " PLAYER " .. playerX)
|
||
end
|
||
----
|
||
|
||
[#_cutting_the_problem_in_half]
|
||
=== Couper le problème en deux
|
||
|
||
Quand vous n'arrivez pas à voir laquelle de vingt lignes est fausse, arrêtez de
|
||
regarder et commencez à retirer.
|
||
|
||
Mettez deux tirets au début d'une ligne et Lua ignore tout le reste de la ligne.
|
||
C'est un *commentaire*, et on s'en sert d'habitude pour se laisser des notes,
|
||
mais le meilleur usage est celui-ci :
|
||
|
||
[source,lua]
|
||
----
|
||
-- overlayPrint(1, 1, "SCORE " .. score .. " LIVES " .. lives)
|
||
----
|
||
|
||
Mettez en commentaire la moitié de `drawGame` et lancez. Si le problème est
|
||
toujours là, il est dans la moitié que vous avez gardée. S'il a disparu, il est
|
||
dans la moitié que vous avez retirée. Remettez cette moitié et coupez-*la* en
|
||
deux. Vingt lignes demandent cinq tours de ce petit jeu, et chaque tour, c'est
|
||
un enregistrement et un coup d'œil.
|
||
|
||
Ça marche pour autre chose que des lignes. Mettez en commentaire le corps de
|
||
`updatePlaying` et les blocs arrêtent de bouger, ce qui vous dit si ce que vous
|
||
traquez est dans le déplacement ou dans le dessin. Mettez en commentaire la
|
||
branche de `collideRects` et voyez si le jeu arrête de planter. Vous n'essayez
|
||
de rien réparer pendant que vous faites ça. Vous essayez de découvrir où le
|
||
problème n'est pas.
|
||
|
||
[#_going_round_faster]
|
||
=== Faire le tour plus vite
|
||
|
||
Tout ceci repose sur la possibilité d'essayer quelque chose en deux secondes, et
|
||
c'est à ça que sert `-R`. Vous vous en servez depuis la leçon un ; voici ce
|
||
qu'il fait vraiment, et ça vaut la peine de le savoir maintenant que vous allez
|
||
vous appuyer dessus.
|
||
|
||
Singe surveille chaque fichier de script que votre jeu a chargé. Quand vous en
|
||
enregistrez un, le jeu repart du début : tout ce que le script a fabriqué est
|
||
jeté et votre fichier est exécuté à neuf, dans le temps qu'il faut pour lire le
|
||
fichier plutôt que dans celui qu'il faut pour démarrer le moteur. `F5` fait la
|
||
même chose à la demande, sans rien enregistrer.
|
||
|
||
Une touche que vous gardez enfoncée pendant un rechargement est ignorée jusqu'à
|
||
ce que vous la relâchiez, donc le joueur ne partira pas tout seul après un
|
||
rechargement.
|
||
|
||
Les deux sortes d'arrêt se comportent différemment ici, et c'est utile de savoir
|
||
laquelle vous avez sous les yeux sans lire le message. Un script qui ne compile
|
||
pas est *signalé, et Singe y survit* : la fenêtre reste ouverte et vide, le
|
||
fichier reste surveillé, et enregistrer la correction ramène le jeu. Un plantage
|
||
à l'intérieur d'un callback -- tous les messages `attempt to`, et les plaintes du
|
||
moteur sur les arguments -- fait tomber Singe, et vous le relancez à la main.
|
||
|
||
[#_reading_a_traceback]
|
||
=== Lire une trace de pile
|
||
|
||
Les lignes sous `stack traceback:` sont la liste des fonctions qui étaient en
|
||
cours quand tout s'est arrêté, la plus intérieure en premier. Voici de nouveau
|
||
celle de `blocl` :
|
||
|
||
----
|
||
dodge/dodge.singe:60: in function 'spawnBlock'
|
||
dodge/dodge.singe:92: in function 'updatePlaying'
|
||
dodge/dodge.singe:136: in function 'onOverlayUpdate'
|
||
----
|
||
|
||
Lisez-la de bas en haut et c'est une phrase. Singe a appelé `onOverlayUpdate`. À
|
||
la ligne 136, `onOverlayUpdate` a appelé `updatePlaying`. À la ligne 92,
|
||
`updatePlaying` a appelé `spawnBlock`. À la ligne 60, `spawnBlock` a abandonné.
|
||
|
||
La ligne du haut, c'est l'endroit où ça a cassé. Les lignes en dessous, c'est le
|
||
chemin pour y arriver, et c'est ce qu'il vous faut quand la ligne du haut a l'air
|
||
innocente. Une fonction qui ajoute un nombre à `block.y` n'a rien de faux ; une
|
||
fonction qui a mis un bloc dans la liste sans `y`, si. Seule la piste relie les
|
||
deux, et la piste est affichée pour vous à chaque fois, sans exception.
|
||
|
||
[#_the_habit]
|
||
=== L'habitude
|
||
|
||
Tout ce qui précède, c'est de la technique. Voici l'habitude, et elle compte
|
||
davantage :
|
||
|
||
*Changez une chose. Lancez. Regardez ce qui s'est passé.*
|
||
|
||
Quand quelque chose refuse de marcher, la tentation est de changer quatre choses
|
||
à la fois, parce que l'une d'elles sera forcément la bonne. Faites ça et ça
|
||
arrête de marcher d'une nouvelle façon, et vous ne savez plus laquelle des quatre
|
||
en est responsable, ni si deux d'entre elles s'annulent. Vous avez agrandi le
|
||
problème et vous l'avez mieux caché.
|
||
|
||
Un changement. Lancez. Regardez. Ça a l'air plus lent. C'est la méthode la plus
|
||
rapide qui existe, et tous les programmeurs expérimentés que vous rencontrerez
|
||
font comme ça, exactement pour la raison que vous êtes sur le point de
|
||
découvrir.
|
||
|
||
[#_try_it_8]
|
||
=== Essayez
|
||
|
||
. *Rencontrez-les toutes.* Faites exprès chacune des cinq erreurs de cette leçon
|
||
dans `dodge.singe`, une à la fois, et lisez le message avant de corriger. Elles
|
||
ne vous feront plus jamais peur.
|
||
. *Surveillez les apparitions.* Mettez dans `spawnBlock` un `debugPrint` qui
|
||
affiche le `x` du nouveau bloc. Jouez trente secondes, puis regardez les
|
||
nombres. Sont-ils répartis sur tout l'écran, ou préfèrent-ils un côté ?
|
||
. *Coupez-le.* Changez `DEBUG` en `false` dans le script de la leçon, et
|
||
vérifiez par vous-même que rien ne s'affiche et que le jeu n'a pas changé.
|
||
. *Un bug sans erreur.* Dans `updatePlaying`, changez `if spawnTimer <= 0 then`
|
||
en `if spawnTimer == 0 then`, qui a l'air de vouloir dire la même chose.
|
||
Lancez et commencez une partie. Il n'y a pas d'erreur, rien ne plante, et pas
|
||
un seul bloc ne tombe, si bien que vous ne pouvez même pas perdre. Mettez un
|
||
`debugLog` dans `spawnBlock`, ajoutez `spawnTimer` à la première ligne
|
||
qu'affiche `debugReport`, et rejouez. Déduisez de ce qu'ils disent, et de ce
|
||
que l'un d'eux ne dit jamais, pourquoi `==` était la mauvaise question à
|
||
poser.
|
||
. *Cassez le nom d'un callback.* Renommez `onInputPressed` en `onInputPress` et
|
||
lancez. Aucune erreur ne s'affiche, rien ne plante, le joueur refuse de bouger, et
|
||
Espace ne lance pas de partie. Puis expliquez-vous pourquoi c'est l'erreur la
|
||
plus dangereuse de cette leçon.
|
||
|
||
[#_what_you_learned_8]
|
||
=== Ce que vous avez appris
|
||
|
||
* Les erreurs ont un fichier, une ligne et une plainte. Lisez-les dans cet
|
||
ordre.
|
||
* `Error running script` veut dire que rien ne s'est exécuté ; la forme avec une
|
||
trace de pile veut dire que ça tournait et que ça s'est arrêté.
|
||
* `nil` est un nom derrière lequel il n'y a rien, et il est derrière la plupart
|
||
des messages d'erreur que vous verrez jamais.
|
||
* Le mot entre parenthèses nomme la chose vide, et dit si c'était une variable
|
||
globale, une variable locale, un champ ou une upvalue.
|
||
* Un `end` manquant est signalé à la fin du fichier, et les parenthèses vous
|
||
disent où est vraiment l'erreur.
|
||
* Les plaintes du moteur ont un autre air : un numéro de ligne, un nom de
|
||
fonction, et ce qu'elle voulait à la place.
|
||
* `debugPrint` vous montre ce que contient vraiment une variable. `tostring`
|
||
rend n'importe quoi affichable, et un interrupteur comme `DEBUG` coupe le
|
||
bruit.
|
||
* Mettez une moitié en commentaire avec `--` pour découvrir dans quelle moitié
|
||
est le problème.
|
||
* Un callback mal nommé ne produit aucune erreur, ce qui le rend pire qu'un
|
||
callback qui en produit une.
|
||
* Changez une chose, lancez, regardez.
|
||
|
||
[#_next_time_8]
|
||
=== La prochaine fois
|
||
|
||
C'est la fin de la première partie. Vous savez écrire un jeu, et maintenant vous
|
||
savez en réparer un, ce qui veut dire que tout ce qui suit est un ajout et non
|
||
une fondation. La deuxième partie commence par remplacer ces boîtes de couleur
|
||
par de vraies images, et à partir de là, le jeu que vous construisez commence à
|
||
ressembler à un jeu auquel quelqu'un d'autre aurait envie de jouer.
|