709 lines
24 KiB
Text
709 lines
24 KiB
Text
[#_lesson_5_your_own_functions]
|
|
== Lezione 5: Funzioni tutte tue
|
|
|
|
image::learn/05-functions.png[La lezione finita, 480]
|
|
|
|
La quarta lezione è finita con un avvertimento sul tuo stesso programma:
|
|
`onOverlayUpdate` calcola dove vanno centoventi stelle, decide quanto è luminosa
|
|
ognuna, le disegna e conta la discesa, ed è una sola funzione che fa quattro
|
|
lavori. Le stanno per chiedere di farne un quinto, perché un campo di stelle
|
|
senza niente davanti non è un gioco.
|
|
|
|
In questa lezione la smonti. Scriverai funzioni tutte tue -- funzioni di cui
|
|
Singe non ha mai sentito parlare e che non chiamerà mai -- e passerai a loro il
|
|
lavoro. Alla fine tutto il campo di stelle è una riga che dice `drawStars()`, in
|
|
fondo allo schermo è tornata un'astronave, e il programma è più facile da
|
|
leggere di quando faceva meno.
|
|
|
|
[#_start_a_new_folder_4]
|
|
=== Crea una nuova cartella
|
|
|
|
Una cartella chiamata `functions`, dentro un file chiamato `functions.singe`, e
|
|
`Singe -R functions` in esecuzione in un terminale.
|
|
|
|
Copiaci dentro il programma finito della quarta lezione. Eccolo, invariato, e
|
|
vale la pena eseguirlo una volta prima di toccare qualsiasi cosa:
|
|
|
|
[source,lua]
|
|
----
|
|
LAYERS = 3
|
|
STARS_PER_LAYER = 40
|
|
ACROSS_STEP = 37
|
|
DOWN_STEP = 61
|
|
LAYER_SHIFT = 13
|
|
LAYER_SPEED = 0.5
|
|
LAYER_BRIGHT = 55
|
|
|
|
width = overlayGetWidth()
|
|
height = overlayGetHeight()
|
|
|
|
drift = 0
|
|
|
|
|
|
function onOverlayUpdate()
|
|
drift = drift + 1
|
|
overlayClear()
|
|
|
|
for layer = 1, LAYERS do
|
|
for star = 1, STARS_PER_LAYER do
|
|
starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
|
|
starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
|
|
shade = math.random(60, 90) + layer * LAYER_BRIGHT
|
|
colorForeground(shade, shade, shade)
|
|
overlayPlot(starX, starY)
|
|
end
|
|
end
|
|
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Tre strati di stelle che scendono lungo la finestra a tre velocità. Niente nella
|
|
prima metà di questa lezione cambia quello che vedi. Tutto quello che c'è cambia
|
|
il modo in cui si legge il programma.
|
|
|
|
[#_one_job_one_name]
|
|
=== Un lavoro, un nome
|
|
|
|
Comincia dalle due righe che mettono una stella sullo schermo. Sopra
|
|
`onOverlayUpdate`, aggiungi questo:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawStar(x, y, shade)
|
|
colorForeground(shade, shade, shade)
|
|
overlayPlot(x, y)
|
|
end
|
|
----
|
|
|
|
Poi, nel ciclo interno, cancella quelle due righe e al loro posto chiama la
|
|
funzione:
|
|
|
|
[source,lua]
|
|
----
|
|
for star = 1, STARS_PER_LAYER do
|
|
starX = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
|
|
starY = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
|
|
shade = math.random(60, 90) + layer * LAYER_BRIGHT
|
|
drawStar(starX, starY, shade)
|
|
end
|
|
----
|
|
|
|
Salva. L'immagine è la stessa.
|
|
|
|
Hai già scritto delle funzioni -- `onOverlayUpdate` è una di queste, e lo sono
|
|
anche `onInputPressed` e `onInputReleased` della terza lezione -- ma quelle sono
|
|
callback. Tu le scrivi e Singe le chiama. `drawStar` è diversa. Singe non ne ha
|
|
mai sentito parlare. Viene eseguita perché *tu* hai scritto il suo nome e le
|
|
parentesi, nello stesso modo in cui chiami `overlayPlot`, e niente al mondo la
|
|
eseguirà se non lo fai tu.
|
|
|
|
Il trucco è tutto qui. D'ora in poi, quando ti trovi a pensare «e adesso
|
|
disegna una stella», puoi scrivere «disegna una stella» e intenderlo davvero.
|
|
|
|
[#_a_parameter_is_a_name_not_a_thing]
|
|
=== Un parametro è un nome, non una cosa
|
|
|
|
Guarda di nuovo la prima riga di `drawStar`.
|
|
|
|
[source,lua]
|
|
----
|
|
function drawStar(x, y, shade)
|
|
----
|
|
|
|
I tre nomi tra le parentesi sono *parametri*. Un parametro è un nome che la
|
|
funzione usa per qualunque cosa le abbia passato chi la chiama. Quando scrivi
|
|
`drawStar(starX, starY, shade)`, Lua mette in `x` quello che c'è in `starX`, in
|
|
`y` quello che c'è in `starY`, in `shade` quello che c'è in `shade`, e poi
|
|
esegue la funzione.
|
|
|
|
I nomi non hanno niente a che fare tra loro. Il numero di chi chiama là fuori si
|
|
chiama `starX` e qui dentro `x`, e funzionerebbe esattamente allo stesso modo se
|
|
lo chiamassi `across` o `banana`. Dentro la funzione esistono solo i nomi tra le
|
|
parentesi, e contengono copie di quello che è entrato.
|
|
|
|
È qui che le parentesi vuote della prima lezione finalmente hanno senso.
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
----
|
|
|
|
Niente tra le parentesi vuol dire nessun parametro: il motore non ha niente da
|
|
dire a questa funzione, quindi non c'è niente a cui dare un nome. Confrontala
|
|
con la callback che hai scritto nella terza lezione.
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
----
|
|
|
|
Un parametro, chiamato `what`. A questa il motore ha qualcosa da dire -- quale
|
|
comando è stato premuto -- quindi c'è un nome in cui riceverlo. Usi un parametro
|
|
dalla terza lezione.
|
|
|
|
Un parametro è davvero la copia personale della funzione, e vale la pena
|
|
dimostrartelo una volta. Scrivi questa, chiamala da `onOverlayUpdate` e poi
|
|
toglila di nuovo:
|
|
|
|
[source,lua]
|
|
----
|
|
function half(number)
|
|
number = number // 2
|
|
return number
|
|
end
|
|
----
|
|
|
|
Chiamala come `half(drift)`. La funzione dimezza il suo `number` e restituisce
|
|
la risposta, e `drift`, là fuori in `onOverlayUpdate`, continua a contare come
|
|
se niente fosse. Una funzione non può allungare la mano e cambiare le variabili
|
|
di chi la chiama attraverso un parametro. Riceve una copia, e quello che fa alla
|
|
copia sono affari suoi.
|
|
|
|
[#_handing_an_answer_back]
|
|
=== Restituire una risposta
|
|
|
|
`half` ha fatto una cosa che le funzioni scritte da te finora non facevano: ha
|
|
dato una risposta. È l'ultima riga.
|
|
|
|
[source,lua]
|
|
----
|
|
return number
|
|
----
|
|
|
|
`return` restituisce un valore a chi ha chiamato la funzione, e la chiamata si
|
|
trasforma in quel valore. Scrivi `smaller = half(drift)` e Lua esegue `half`,
|
|
prende quello che `half` ha restituito e lo mette in `smaller`. Hai incontrato
|
|
`return` nella prima lezione, dove `onOverlayUpdate` restituisce
|
|
`OVERLAY_UPDATED` per rispondere alla domanda di Singe se fosse cambiato
|
|
qualcosa. Qui è la stessa parola che fa lo stesso lavoro, solo che adesso la
|
|
domanda la fai tu.
|
|
|
|
La riga della luminosità implora di diventare una funzione con una risposta.
|
|
Aggiungi questa:
|
|
|
|
[source,lua]
|
|
----
|
|
function shadeFor(layer)
|
|
return math.random(60, 90) + layer * LAYER_BRIGHT
|
|
end
|
|
----
|
|
|
|
Tutto il corpo è un `return`, cosa normale per una funzione piccola. Ora il
|
|
ciclo interno può passare la risposta direttamente a `drawStar`, senza un nome
|
|
in mezzo:
|
|
|
|
[source,lua]
|
|
----
|
|
drawStar(starX, starY, shadeFor(layer))
|
|
----
|
|
|
|
Una chiamata dentro una chiamata. Lua calcola prima quella interna, riceve un
|
|
numero da `shadeFor` e passa quel numero a `drawStar` come terzo argomento. Hai
|
|
già scritto qualcosa di simile senza accorgertene: il
|
|
`math.floor(drift * layer * LAYER_SPEED)` della quarta lezione è una chiamata la
|
|
cui risposta viene usata sul momento, in mezzo a un calcolo più grande.
|
|
|
|
[#_two_answers_at_once]
|
|
=== Due risposte insieme
|
|
|
|
Le due righe della posizione sono più difficili, perché sono due numeri
|
|
calcolati insieme e dovresti sceglierne uno da restituire. Nella maggior parte
|
|
dei linguaggi una funzione può restituire un solo valore, e tirarne fuori due è
|
|
una seccatura. Lua ti lascia restituirne quanti ne vuoi.
|
|
|
|
[source,lua]
|
|
----
|
|
function starAt(star, layer)
|
|
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
|
|
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
|
|
return x, y
|
|
end
|
|
----
|
|
|
|
Raccogli le risposte mettendo due nomi a sinistra, separati da una virgola:
|
|
|
|
[source,lua]
|
|
----
|
|
local x, y = starAt(star, layer)
|
|
----
|
|
|
|
Il primo valore nel primo nome, il secondo nel secondo. Se chiedi meno nomi di
|
|
quanti valori restituisce la funzione, i valori in più vengono buttati via:
|
|
`local x = starAt(star, layer)` ti dà il numero orizzontale e perde quello
|
|
verticale, senza lamentarsi. Se chiedi più nomi di quanti sono i valori, quelli
|
|
in più sono `nil`.
|
|
|
|
Ignora la parola `local` in quei due blocchi ancora per un minuto. Sta arrivando
|
|
una sezione tutta sua, ed è quella importante.
|
|
|
|
[#_the_star_field_in_one_line]
|
|
=== Il campo di stelle in una riga
|
|
|
|
Il ciclo interno ora è tre righe brevi, tutte su uno strato di stelle. Questo è
|
|
un lavoro con un nome.
|
|
|
|
[source,lua]
|
|
----
|
|
function drawLayer(layer)
|
|
for star = 1, STARS_PER_LAYER do
|
|
local x, y = starAt(star, layer)
|
|
drawStar(x, y, shadeFor(layer))
|
|
end
|
|
end
|
|
----
|
|
|
|
E anche il ciclo esterno è un lavoro con un nome:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawStars()
|
|
for layer = 1, LAYERS do
|
|
drawLayer(layer)
|
|
end
|
|
end
|
|
----
|
|
|
|
Funzioni che chiamano funzioni che chiamano funzioni, tre livelli di
|
|
profondità, e ognuna è tre o quattro righe che riesci a tenere in testa tutte
|
|
insieme. Ora `onOverlayUpdate` può dire che cosa *è* un fotogramma:
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
drift = drift + 1
|
|
overlayClear()
|
|
drawStars()
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Ecco la promessa fatta alla fine della quarta lezione, mantenuta.
|
|
|
|
[#_put_the_ship_back]
|
|
=== Rimetti l'astronave
|
|
|
|
Ora il quinto lavoro, quello che avrebbe fatto un disastro della vecchia
|
|
`onOverlayUpdate`. Riporta il riquadro che guidavi nella terza lezione, stavolta
|
|
come un'astronave ferma vicino al fondo dello schermo che si muove solo a destra
|
|
e a sinistra.
|
|
|
|
Le servono alcuni valori suoi, su in cima insieme agli altri, e le due callback
|
|
dei comandi della terza lezione senza i rami per su e giù:
|
|
|
|
[source,lua]
|
|
----
|
|
PLAYER_WIDTH = 20
|
|
PLAYER_HEIGHT = 6
|
|
PLAYER_SPEED = 3
|
|
PLAYER_MARGIN = 6
|
|
|
|
playerX = width // 2
|
|
playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN
|
|
movingLeft = false
|
|
movingRight = false
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = true
|
|
end
|
|
end
|
|
|
|
|
|
function onInputReleased(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = false
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = false
|
|
end
|
|
end
|
|
----
|
|
|
|
Disegnarla sono due righe, quindi è una funzione:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawPlayer()
|
|
colorForeground(80, 255, 120)
|
|
overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT)
|
|
end
|
|
----
|
|
|
|
Muoverla è il punto in cui la lezione dà i suoi frutti. Nella terza lezione,
|
|
tenere il riquadro sullo schermo richiedeva quattro righe di `if` ed `elseif`
|
|
per ogni direzione. Quell'idea -- tenere un numero tra un minimo e un massimo --
|
|
ha un nome, e il nome è *clamp*:
|
|
|
|
[source,lua]
|
|
----
|
|
function clamp(value, low, high)
|
|
if value < low then
|
|
return low
|
|
end
|
|
if value > high then
|
|
return high
|
|
end
|
|
return value
|
|
end
|
|
----
|
|
|
|
Tre return, e al massimo uno di loro avviene, perché `return` non si limita a
|
|
restituire una risposta: ferma la funzione di colpo. Niente di quello che viene
|
|
dopo viene eseguito. Se il valore è sotto il minimo, restituisci il minimo e
|
|
fermati. Se è sopra il massimo, restituisci il massimo e fermati. Altrimenti
|
|
restituisci quello che è entrato.
|
|
|
|
[source,lua]
|
|
----
|
|
function movePlayer()
|
|
if movingLeft then
|
|
playerX = playerX - PLAYER_SPEED
|
|
end
|
|
if movingRight then
|
|
playerX = playerX + PLAYER_SPEED
|
|
end
|
|
playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1)
|
|
end
|
|
----
|
|
|
|
Una funzione che finisce senza un `return` restituisce niente, cioè `nil`. Va
|
|
bene per `drawPlayer` e `movePlayer`, che vengono chiamate per fare qualcosa e
|
|
non per rispondere a qualcosa. È un bug in una funzione come `clamp`: togli
|
|
quell'ultimo `return value` e ogni posizione in mezzo all'intervallo torna
|
|
indietro come `nil`.
|
|
|
|
[#_where_names_live]
|
|
=== Dove vivono i nomi
|
|
|
|
Ogni nome che hai creato dalla seconda lezione in poi è stato una *variabile
|
|
globale*. Una globale appartiene a tutto il programma: `drift`, `playerX` e
|
|
`LAYERS` possono essere letti e scritti da qualsiasi funzione del file, in
|
|
qualsiasi momento, e restano lì finché il gioco gira.
|
|
|
|
Sembra comodo, e con sei nomi lo è. I guai cominciano a sessanta. Ogni globale è
|
|
un nome che tutto il programma ha accettato di riservare, quindi quando un'altra
|
|
funzione vuole un posto dove tenere una posizione orizzontale e lo chiama anche
|
|
lei `starX`, i due sono la stessa scatola, e ognuno scrive sopra il numero
|
|
dell'altro. Quando una globale contiene qualcosa di sbagliato, qualsiasi cosa
|
|
nel file potrebbe avercelo messo, e scoprire che cosa vuol dire leggerlo tutto.
|
|
|
|
La cura è una *locale*. Metti la parola `local` davanti a un nome la prima volta
|
|
che lo usi, e il nome esiste solo dentro la `function`, il ciclo o il blocco `if` in cui
|
|
è stato scritto.
|
|
|
|
[source,lua]
|
|
----
|
|
function starAt(star, layer)
|
|
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
|
|
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
|
|
return x, y
|
|
end
|
|
----
|
|
|
|
Qui `x` e `y` non sono affari di nessun altro. Un'altra funzione può avere la
|
|
sua `x` e le due non si incontrano mai. Quando la `x` di `starAt` è sbagliata,
|
|
le tre righe qui sopra sono le uniche tre che possono averlo fatto. Senza la
|
|
parola `local`, quelle due righe creerebbero in silenzio altre due globali, che è
|
|
esattamente ciò che `starX` e `starY` sono sempre stati.
|
|
|
|
Ora la parte che sorprende tutti. Scrivi questa funzione, e metti in
|
|
`onOverlayUpdate` una riga che ti mostri che cosa restituisce:
|
|
|
|
[source,lua]
|
|
----
|
|
function countUp()
|
|
local counted = 0
|
|
counted = counted + 1
|
|
return counted
|
|
end
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(0, 0, "Count: " .. countUp())
|
|
----
|
|
|
|
`Count: 1`, e resta `Count: 1` finché il gioco gira, anche se quella funzione
|
|
viene chiamata sessanta volte al secondo e aggiunge uno ogni volta. Una locale
|
|
viene creata nuova quando la funzione comincia e sparisce quando la funzione
|
|
finisce, quindi `counted` nasce come `0`, diventa `1` e muore, sessanta volte al
|
|
secondo, per sempre. Una locale non ricorda. Quando l'hai visto, togli di nuovo
|
|
le due cose.
|
|
|
|
Non è un difetto, è proprio lo scopo: una locale non può essere avvelenata dalla
|
|
chiamata precedente, perché della chiamata precedente non resta niente. Quando
|
|
invece devi ricordare qualcosa tra una chiamata e l'altra -- `drift` e `playerX`
|
|
devono ricordare -- il nome deve vivere fuori da tutte le funzioni. Mettilo in
|
|
cima al file con `local` davanti:
|
|
|
|
[source,lua]
|
|
----
|
|
local drift = 0
|
|
local playerX = width // 2
|
|
----
|
|
|
|
Un `local` scritto in cima al file, fuori da tutte le funzioni, vive quanto lo
|
|
script e può essere visto da ogni funzione che sta sotto. Quell'ultima parola
|
|
conta: *sotto*. Una locale esiste dalla sua riga in giù, quindi una funzione
|
|
scritta sopra la riga che la crea non può vederla. Lo stesso vale per le
|
|
funzioni stesse una volta che le segni come `local`, ed è per questo che lo
|
|
script finito è nell'ordine in cui è: prima i valori, poi le tue funzioni, poi,
|
|
per ultime, le callback.
|
|
|
|
Adesso passa tutto il file e metti `local` davanti a ogni nome che hai creato --
|
|
le costanti in maiuscolo, `width` e `height`, `drift`, i quattro valori del
|
|
giocatore e ognuna delle tue funzioni. Tre nomi non lo ricevono:
|
|
`onInputPressed`, `onInputReleased` e `onOverlayUpdate` devono restare globali,
|
|
perché Singe le trova per nome e un nome locale è invisibile da fuori del file.
|
|
|
|
Sii onesto su quello che le locali non risolvono. Se sbagli a scrivere un nome a
|
|
cui stai assegnando un valore -- `movinLeft = true` invece di
|
|
`movingLeft = true` -- Lua non si lamenta. Crea una globale nuova di zecca con
|
|
il tuo errore di battitura come nome, la imposta e lascia stare quella vera. Non
|
|
si muove niente, non c'è nessun errore, e tu lo fissi per dieci minuti. Da
|
|
quello le locali non ti salvano. Leggere il nome due volte sì.
|
|
|
|
[#_the_whole_thing_4]
|
|
=== Tutto insieme
|
|
|
|
Ecco il programma finito, con dentro ogni pezzo della lezione.
|
|
|
|
[source,lua]
|
|
----
|
|
local LAYERS = 3
|
|
local STARS_PER_LAYER = 40
|
|
local ACROSS_STEP = 37
|
|
local DOWN_STEP = 61
|
|
local LAYER_SHIFT = 13
|
|
local LAYER_SPEED = 0.5
|
|
local LAYER_BRIGHT = 55
|
|
|
|
local PLAYER_WIDTH = 20
|
|
local PLAYER_HEIGHT = 6
|
|
local PLAYER_SPEED = 3
|
|
local PLAYER_MARGIN = 6
|
|
|
|
local width = overlayGetWidth()
|
|
local height = overlayGetHeight()
|
|
|
|
local drift = 0
|
|
local playerX = width // 2
|
|
local playerY = height - PLAYER_HEIGHT - PLAYER_MARGIN
|
|
local movingLeft = false
|
|
local movingRight = false
|
|
|
|
|
|
local function clamp(value, low, high)
|
|
if value < low then
|
|
return low
|
|
end
|
|
if value > high then
|
|
return high
|
|
end
|
|
return value
|
|
end
|
|
|
|
|
|
local function shadeFor(layer)
|
|
return math.random(60, 90) + layer * LAYER_BRIGHT
|
|
end
|
|
|
|
|
|
local function starAt(star, layer)
|
|
local x = (star * ACROSS_STEP + layer * LAYER_SHIFT) % width
|
|
local y = (star * DOWN_STEP + math.floor(drift * layer * LAYER_SPEED)) % height
|
|
return x, y
|
|
end
|
|
|
|
|
|
local function drawStar(x, y, shade)
|
|
colorForeground(shade, shade, shade)
|
|
overlayPlot(x, y)
|
|
end
|
|
|
|
|
|
local function drawLayer(layer)
|
|
for star = 1, STARS_PER_LAYER do
|
|
local x, y = starAt(star, layer)
|
|
drawStar(x, y, shadeFor(layer))
|
|
end
|
|
end
|
|
|
|
|
|
local function drawStars()
|
|
for layer = 1, LAYERS do
|
|
drawLayer(layer)
|
|
end
|
|
end
|
|
|
|
|
|
local function drawPlayer()
|
|
colorForeground(80, 255, 120)
|
|
overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT)
|
|
end
|
|
|
|
|
|
local function movePlayer()
|
|
if movingLeft then
|
|
playerX = playerX - PLAYER_SPEED
|
|
end
|
|
if movingRight then
|
|
playerX = playerX + PLAYER_SPEED
|
|
end
|
|
playerX = clamp(playerX, 0, width - PLAYER_WIDTH - 1)
|
|
end
|
|
|
|
|
|
function onInputPressed(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = true
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = true
|
|
end
|
|
end
|
|
|
|
|
|
function onInputReleased(what)
|
|
if what == SWITCH_LEFT then
|
|
movingLeft = false
|
|
elseif what == SWITCH_RIGHT then
|
|
movingRight = false
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
drift = drift + 1
|
|
overlayClear()
|
|
movePlayer()
|
|
drawStars()
|
|
drawPlayer()
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Prima di andare avanti vale la pena indicare due cose.
|
|
|
|
`local function drawStar(x, y, shade)` segna come locale la funzione stessa. Le
|
|
tue funzioni sono nomi come tutti gli altri, e appartengono solo a questo file.
|
|
Le tre callback restano globali così Singe può trovarle.
|
|
|
|
E ora `onOverlayUpdate` è sei righe che dicono che cos'è un fotogramma: conta la
|
|
discesa, pulisci lo schermo, muovi il giocatore, disegna il cielo, disegna il
|
|
giocatore, fatto. Fa cinque lavori nominando cinque cose che fanno un lavoro
|
|
ciascuna, che è la forma di ogni ciclo di gioco che scriverai.
|
|
|
|
[#_a_function_is_a_name_for_an_idea]
|
|
=== Una funzione è un nome per un'idea
|
|
|
|
`clamp` viene chiamata una volta sola. Tirarla fuori non ha risparmiato nessuna
|
|
battitura -- quattro righe sono uscite, una è entrata, e da un'altra parte è
|
|
comparsa una funzione di nove righe. Valeva comunque la pena farlo.
|
|
|
|
Una funzione non è solo un modo per non ripeterti. È un modo per dare un nome a
|
|
un'idea, così il codice che usa l'idea può dire il nome e andare avanti.
|
|
«Tieni il giocatore tra il bordo sinistro e il bordo destro» è un pensiero che
|
|
hai avuto una volta. `clamp(playerX, 0, width - PLAYER_WIDTH - 1)` è quel
|
|
pensiero, messo per iscritto. Quattro istruzioni `if` sono lo stesso pensiero
|
|
smontato in pezzi che devi rimettere insieme in testa ogni volta che li leggi.
|
|
|
|
Quando stai decidendo se una cosa merita di essere una funzione, non chiederti
|
|
quante volte la chiamerai. Chiediti se ha un nome.
|
|
|
|
[#_try_it_5]
|
|
=== Prova tu
|
|
|
|
. *Rinomina un parametro.* In `drawStar`, cambia `x` e `y` in `across` e
|
|
`down`, ovunque dentro la funzione. Non toccare niente fuori. Eseguilo. Scopri
|
|
perché non si è rotto niente.
|
|
. *Disegnali al contrario.* Scambia `drawStars()` e `drawPlayer()` in
|
|
`onOverlayUpdate` e guarda l'astronave. Poi rimettili a posto. Ognuna di
|
|
quelle cinque righe è un lavoro, e il loro ordine sullo schermo è il loro
|
|
ordine nella funzione.
|
|
. *Usa `clamp` su una sfumatura.* Cambia `shadeFor` in
|
|
`return clamp(math.random(60, 90) + layer * 90, 0, 200)` e guarda i tre
|
|
strati. Poi togli il `clamp` e guarda di nuovo: `colorForeground` ti teneva in
|
|
silenzio i numeri sotto il limite da sempre, e ora puoi vedere dove.
|
|
. *Chiama `drawStar` con due argomenti.* Scrivi `drawStar(100, 100)` da qualche
|
|
parte in `drawLayer` ed eseguilo. L'errore nomina esattamente la cosa che è
|
|
mancata. Leggilo prima di rimettere il terzo argomento.
|
|
. *Sposta una locale.* Prendi la riga `local drift = 0` e spostala in fondo al
|
|
file, sotto tutto il resto. Eseguilo, leggi l'errore e pensa alla parola
|
|
«sotto» nella sezione precedente.
|
|
|
|
[#_break_it_on_purpose_5]
|
|
=== Rompilo apposta
|
|
|
|
L'ordine di un file con dentro delle locali conta, e l'errore che ottieni quando
|
|
rompi quell'ordine non è quello che ti aspetti.
|
|
|
|
Prendi tutta la funzione `drawStar` e spostala in *fondo* allo script, sotto
|
|
`onOverlayUpdate`. C'è ancora tutto, scritto allo stesso modo. Eseguilo:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': functions/functions.singe:50: attempt to call a nil value (global 'drawStar')
|
|
stack traceback:
|
|
functions/functions.singe:50: in upvalue 'drawLayer'
|
|
functions/functions.singe:57: in upvalue 'drawStars'
|
|
functions/functions.singe:101: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
Ha un aspetto diverso dall'errore della prima lezione perché è un tipo di errore
|
|
diverso. L'errore della prima lezione è stato colto prima che lo script
|
|
partisse. Questo è stato colto mentre era in esecuzione, ed è per questo che
|
|
Singe nomina la funzione in cui si trovava, e per questo sotto c'è un
|
|
*traceback*: l'elenco di chi ha chiamato chi, dal più recente. La riga 50 è
|
|
dentro `drawLayer`, che è stata chiamata dalla riga 57 dentro `drawStars`, che è
|
|
stata chiamata dalla riga 101 dentro `onOverlayUpdate`. I tuoi numeri di riga
|
|
saranno diversi se il tuo file non corrisponde esattamente a quello qui sopra, e
|
|
il motore potrebbe aggiungere sotto queste una o due righe sue. La parola
|
|
`upvalue` è il nome che Lua dà a una locale che una funzione ha preso in
|
|
prestito dal file che la circonda; per ora passaci sopra.
|
|
|
|
La lamentela in sé vale la pena di impararla a memoria. «Attempt to call a nil
|
|
value» vuol dire che hai messo delle parentesi dopo un nome che non conteneva
|
|
niente. La parola `global` davanti a `drawStar` è la parte che svela tutto: hai segnato `drawStar`
|
|
come locale, quindi quando Lua ha letto `drawLayer` e non ha trovato da nessuna
|
|
parte sopra una locale con quel nome, l'unica cosa che restava da provare era
|
|
una globale -- e non c'è nemmeno una globale chiamata `drawStar`. Rimetti la
|
|
funzione sopra il codice che la chiama e l'errore se ne va con lei.
|
|
|
|
[#_what_you_learned_5]
|
|
=== Cosa hai imparato
|
|
|
|
* Puoi scrivere funzioni tutte tue, e niente le esegue tranne le tue chiamate.
|
|
* I nomi tra le parentesi di una funzione sono parametri, e sono le copie
|
|
personali della funzione di quello che ha passato chi la chiama.
|
|
* `return` restituisce un valore e ferma la funzione lì dov'è, e una funzione
|
|
che finisce senza di esso restituisce `nil`.
|
|
* Una chiamata può essere un argomento di un'altra chiamata, e quella interna
|
|
avviene per prima.
|
|
* Lua può restituire più di un valore, e li raccogli con più nomi separati da
|
|
virgole.
|
|
* `local` crea un nome che esiste solo dove è stato scritto.
|
|
* Una locale dentro una funzione viene creata nuova a ogni chiamata e non
|
|
ricorda niente di quella precedente.
|
|
* Una locale si vede solo sotto la riga che la crea, quindi i valori e le tue
|
|
funzioni vanno sopra le callback che li usano.
|
|
* Le callback restano globali, perché Singe le trova per nome.
|
|
* Vale la pena scrivere una funzione quando la cosa che fa ha un nome, anche se
|
|
la chiami una volta sola.
|
|
|
|
[#_next_time_5]
|
|
=== La prossima volta
|
|
|
|
Le tue centoventi stelle sono ancora centoventi copie di tre idee. Niente nel
|
|
programma è *una stella*: c'è un numero, e un po' di aritmetica che trasforma il
|
|
numero in una posizione. Non puoi dire «questa è più vicina, quindi cade più in
|
|
fretta e brilla di più», perché non c'è nessun «questa» di cui dirlo, e non puoi
|
|
lasciare che una stella cada fuori dal fondo e venga sostituita da una nuova,
|
|
perché lì non c'è niente da togliere.
|
|
|
|
La sesta lezione ti dà la cosa che contiene una stella, e poi un modo per
|
|
contenerne centoventi e aggiungerne una centoventunesima mentre il gioco gira. È
|
|
la lezione più utile di questa parte del libro.
|