690 lines
22 KiB
Text
690 lines
22 KiB
Text
== Lesson 5: Your Own Functions
|
|
|
|
image::learn/05-functions.png[The finished lesson, 480]
|
|
|
|
Lesson four ended with a warning about your own program: `onOverlayUpdate`
|
|
works out where a hundred and twenty stars go, decides how bright each one is,
|
|
draws them, and counts the drift, and it is one function doing four jobs. It
|
|
is about to be asked to do a fifth, because a star field with nothing in front
|
|
of it is not a game.
|
|
|
|
In this lesson you take it apart. You will write functions of your own --
|
|
functions Singe has never heard of and will never call -- and hand the work to
|
|
them. By the end, the whole star field is one line that says `drawStars()`,
|
|
there is a ship back on the bottom of the screen, and the program is easier to
|
|
read than it was when it did less.
|
|
|
|
=== Start a New Folder
|
|
|
|
A folder called `functions`, a file in it called `functions.singe`, and
|
|
`Singe -R functions` running in a terminal.
|
|
|
|
Copy lesson four's finished program into it. This is it, unchanged, and it is
|
|
worth running once before you touch anything:
|
|
|
|
[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
|
|
----
|
|
|
|
Three layers of stars drifting down the window at three speeds. Nothing in the
|
|
first half of this lesson changes what you see. Everything in it changes how
|
|
the program reads.
|
|
|
|
=== One Job, One Name
|
|
|
|
Start with the two lines that put a star on the screen. Above
|
|
`onOverlayUpdate`, add this:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawStar(x, y, shade)
|
|
colorForeground(shade, shade, shade)
|
|
overlayPlot(x, y)
|
|
end
|
|
----
|
|
|
|
Then, in the inner loop, delete those two lines and call it instead:
|
|
|
|
[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
|
|
----
|
|
|
|
Save. The picture is the same.
|
|
|
|
You have written functions before -- `onOverlayUpdate` is one, and so are
|
|
`onInputPressed` and `onInputReleased` from lesson three -- but those are
|
|
callbacks. You write them and Singe calls them. `drawStar` is different. Singe
|
|
has never heard of it. It runs because *you* wrote its name and parentheses,
|
|
the same way you call `overlayPlot`, and nothing in the world will run it
|
|
unless you do.
|
|
|
|
That is the whole trick. From here on, when you find yourself thinking "and
|
|
now draw a star", you can write "draw a star" and mean it.
|
|
|
|
=== A Parameter Is a Name, Not a Thing
|
|
|
|
Look at the first line of `drawStar` again.
|
|
|
|
[source,lua]
|
|
----
|
|
function drawStar(x, y, shade)
|
|
----
|
|
|
|
The three names in the parentheses are *parameters*. A parameter is a name the
|
|
function uses for whatever the caller handed it. When you write
|
|
`drawStar(starX, starY, shade)`, Lua sets `x` to whatever is in `starX`, `y`
|
|
to whatever is in `starY`, `shade` to whatever is in `shade`, and then runs
|
|
the function.
|
|
|
|
The names have nothing to do with each other. The caller's number is called
|
|
`starX` out there and `x` in here, and it would work exactly the same if you
|
|
called it `across` or `banana`. Inside the function, only the names in the
|
|
parentheses exist, and they hold copies of what came in.
|
|
|
|
This is where the empty parentheses from lesson one finally make sense.
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
----
|
|
|
|
Nothing in the parentheses means no parameters: the engine has nothing to tell
|
|
this function, so there is nothing to name. Compare it with the callback you
|
|
wrote in lesson three.
|
|
|
|
[source,lua]
|
|
----
|
|
function onInputPressed(what)
|
|
----
|
|
|
|
One parameter, called `what`. The engine has something to tell this one --
|
|
which control was pressed -- so there is a name to catch it in. You have been
|
|
using a parameter since lesson three without knowing what it was called.
|
|
|
|
A parameter really is the function's own copy, and it is worth proving that to
|
|
yourself once. Type this in, call it from `onOverlayUpdate`, and take it out
|
|
again afterwards:
|
|
|
|
[source,lua]
|
|
----
|
|
function half(number)
|
|
number = number // 2
|
|
return number
|
|
end
|
|
----
|
|
|
|
Call it as `half(drift)`. The function halves its `number` and hands the
|
|
answer back, and `drift` out in `onOverlayUpdate` carries on counting as
|
|
though nothing had happened. A function cannot reach out and change the
|
|
caller's variables through a parameter. It gets a copy, and what it does to
|
|
the copy is its own business.
|
|
|
|
=== Handing an Answer Back
|
|
|
|
`half` did something the functions you have written so far did not: it gave an
|
|
answer. That is the last line.
|
|
|
|
[source,lua]
|
|
----
|
|
return number
|
|
----
|
|
|
|
`return` hands a value back to whoever called the function, and the call turns
|
|
into that value. Write `smaller = half(drift)` and Lua runs `half`, takes what
|
|
`half` returned, and puts it in `smaller`. You met `return` in lesson one,
|
|
where `onOverlayUpdate` returns `OVERLAY_UPDATED` to answer Singe's question
|
|
about whether anything changed. This is the same word doing the same job,
|
|
except that now you are the one asking the question.
|
|
|
|
The brightness line is begging to be a function with an answer. Add this one:
|
|
|
|
[source,lua]
|
|
----
|
|
function shadeFor(layer)
|
|
return math.random(60, 90) + layer * LAYER_BRIGHT
|
|
end
|
|
----
|
|
|
|
The whole body is a `return`, which is normal for a small function. Now the
|
|
inner loop can hand the answer straight to `drawStar` without a name in the
|
|
middle:
|
|
|
|
[source,lua]
|
|
----
|
|
drawStar(starX, starY, shadeFor(layer))
|
|
----
|
|
|
|
A call inside a call. Lua works the inside one out first, gets a number back
|
|
from `shadeFor`, and passes that number to `drawStar` as its third argument.
|
|
You have written something like it before without noticing: the
|
|
`math.floor(drift * layer * LAYER_SPEED)` in lesson four is a call whose
|
|
answer is used on the spot, in the middle of a bigger sum.
|
|
|
|
=== Two Answers at Once
|
|
|
|
The two position lines are harder, because they are two numbers worked out
|
|
together and you would have to pick one to hand back. In most languages a
|
|
function can return one value and getting two out of it is a nuisance. Lua
|
|
lets you return as many as you like.
|
|
|
|
[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
|
|
----
|
|
|
|
You catch the answers by putting two names on the left, separated by a comma:
|
|
|
|
[source,lua]
|
|
----
|
|
local x, y = starAt(star, layer)
|
|
----
|
|
|
|
First value into the first name, second into the second. If you ask for fewer
|
|
names than the function returns, the extra values are thrown away:
|
|
`local x = starAt(star, layer)` gives you the across number and loses the down
|
|
one, with no complaint. If you ask for more names than there are values, the
|
|
extras are `nil`.
|
|
|
|
Ignore the word `local` in those two blocks for one more minute. It has a
|
|
section of its own coming, and it is the important one.
|
|
|
|
=== The Star Field in One Line
|
|
|
|
The inner loop is now three short lines, all of them about one layer of stars.
|
|
That is a job with a name.
|
|
|
|
[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
|
|
----
|
|
|
|
And the outer loop is a job with a name too:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawStars()
|
|
for layer = 1, LAYERS do
|
|
drawLayer(layer)
|
|
end
|
|
end
|
|
----
|
|
|
|
Functions calling functions calling functions, three deep, and each one is
|
|
three or four lines you can hold in your head at once. `onOverlayUpdate` can
|
|
now say what a frame *is*:
|
|
|
|
[source,lua]
|
|
----
|
|
function onOverlayUpdate()
|
|
drift = drift + 1
|
|
overlayClear()
|
|
drawStars()
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
That is the promise lesson four made at the end, kept.
|
|
|
|
=== Put the Ship Back
|
|
|
|
Now the fifth job, the one that would have made a mess of the old
|
|
`onOverlayUpdate`. Bring back the box you steered in lesson three, this time
|
|
as a ship sitting near the bottom of the screen that only moves left and
|
|
right.
|
|
|
|
It needs some values of its own, up at the top with the others, and lesson
|
|
three's two input callbacks with the up and down branches taken out:
|
|
|
|
[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
|
|
----
|
|
|
|
Drawing it is two lines, so it is a function:
|
|
|
|
[source,lua]
|
|
----
|
|
function drawPlayer()
|
|
colorForeground(80, 255, 120)
|
|
overlayBox(playerX, playerY, playerX + PLAYER_WIDTH, playerY + PLAYER_HEIGHT)
|
|
end
|
|
----
|
|
|
|
Moving it is where the lesson pays off. In lesson three, keeping the box on
|
|
the screen took four lines of `if` and `elseif` for each direction. That idea
|
|
-- hold a number between a bottom and a top -- has a name, and the name is
|
|
*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
|
|
----
|
|
|
|
Three returns, and at most one of them ever happens, because `return` does not
|
|
only hand back an answer: it stops the function dead. Nothing after it runs.
|
|
If the value is under the bottom, hand back the bottom and stop. If it is over
|
|
the top, hand back the top and stop. Otherwise hand back what came in.
|
|
|
|
[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
|
|
----
|
|
|
|
A function that ends without a `return` hands back nothing, which is `nil`.
|
|
That is right for `drawPlayer` and `movePlayer`, which are called to do
|
|
something rather than to answer something. It is a bug in a function like
|
|
`clamp`: leave off that last `return value` and every position in the middle
|
|
of the range comes back as `nil`.
|
|
|
|
=== Where Names Live
|
|
|
|
Every name you have made since lesson two has been a *global*. A global
|
|
belongs to the whole program: `drift`, `playerX`, and `LAYERS` can be read and
|
|
written by any function in the file, at any moment, and they stay there for as
|
|
long as the game runs.
|
|
|
|
That sounds convenient, and for six names it is. The trouble starts at sixty.
|
|
Every global is a name the whole program has agreed to reserve, so when some
|
|
other function wants somewhere to keep a position across the screen and calls
|
|
it `starX` as well, the two of them are the same box, and each writes over the
|
|
other's number. When a global holds something wrong, anything in the file
|
|
could have put it there, and finding out which means reading all of it.
|
|
|
|
A *local* is the cure. Put the word `local` in front of a name the first time
|
|
you use it, and the name exists only inside the `function`, the loop, or the
|
|
`if` it was written in.
|
|
|
|
[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
|
|
----
|
|
|
|
`x` and `y` here are nobody else's business. Another function can have its own
|
|
`x` and the two never meet. When `starAt`'s `x` is wrong, the three lines
|
|
above are the only three lines that could have done it. Without the word
|
|
`local`, those two lines would be quietly creating two more globals, which is
|
|
exactly what `starX` and `starY` have been all along.
|
|
|
|
Now the part that surprises everyone. Type this function in, and put a line in
|
|
`onOverlayUpdate` that shows you what it hands back:
|
|
|
|
[source,lua]
|
|
----
|
|
function countUp()
|
|
local counted = 0
|
|
counted = counted + 1
|
|
return counted
|
|
end
|
|
----
|
|
|
|
[source,lua]
|
|
----
|
|
overlayPrint(0, 0, "Count: " .. countUp())
|
|
----
|
|
|
|
`Count: 1`, and it stays `Count: 1` while the game runs, though that function
|
|
is called sixty times a second and adds one every time. A local is made fresh
|
|
when the function starts and is gone when the function ends, so `counted` is
|
|
born as `0`, becomes `1`, and dies, sixty times a second, forever. A local
|
|
does not remember. Take the two of them out again when you have looked at it.
|
|
|
|
That is not a flaw, it is the point: a local cannot be poisoned by the last
|
|
call, because there is nothing left of the last call. When you do need to
|
|
remember something between calls -- `drift` and `playerX` have to remember --
|
|
the name must live outside every function. Put it at the top of the file with
|
|
`local` in front of it:
|
|
|
|
[source,lua]
|
|
----
|
|
local drift = 0
|
|
local playerX = width // 2
|
|
----
|
|
|
|
A `local` written at the top of the file, outside all the functions, lives as
|
|
long as the script does and can be seen by every function below it. That last
|
|
word matters: *below*. A local exists from its own line downwards, so a
|
|
function written above the line that creates it cannot see it. The same is
|
|
true of the functions themselves once you mark them `local`, which is why the
|
|
finished script is in the order it is in: the values first, then your own
|
|
functions, then the callbacks, last.
|
|
|
|
Go through the whole file now and put `local` in front of every name you made
|
|
-- the constants in capitals, `width` and `height`, `drift`, the player's four
|
|
values, and every one of your own functions. Three names do not get it:
|
|
`onInputPressed`, `onInputReleased`, and `onOverlayUpdate` have to stay
|
|
global, because Singe finds them by name and a local name is invisible from
|
|
outside the file.
|
|
|
|
Be honest about what locals do not fix. If you mistype a name you are
|
|
assigning to -- `movinLeft = true` instead of `movingLeft = true` -- Lua does
|
|
not complain. It makes a brand new global with your typo for a name, sets it,
|
|
and leaves the real one alone. Nothing moves, nothing errors, and you stare at
|
|
it for ten minutes. Locals will not save you from that one. Reading the name
|
|
twice will.
|
|
|
|
=== The Whole Thing
|
|
|
|
Here is the finished program, with every piece of the lesson in it.
|
|
|
|
[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
|
|
----
|
|
|
|
Two things are worth pointing at before you move on.
|
|
|
|
`local function drawStar(x, y, shade)` marks the function itself as local.
|
|
Your own functions are names like any other, and they belong to this file
|
|
alone. The three callbacks stay global so that Singe can find them.
|
|
|
|
And `onOverlayUpdate` is now six lines that say what a frame is: count the
|
|
drift, clear the screen, move the player, draw the sky, draw the player, done.
|
|
It does five jobs by naming five things that do one job each, which is the
|
|
shape of every game loop you will ever write.
|
|
|
|
=== A Function Is a Name for an Idea
|
|
|
|
`clamp` is called once. Pulling it out saved no typing at all -- four lines
|
|
went in, one came out, and a nine line function appeared elsewhere. It was
|
|
still worth doing.
|
|
|
|
A function is not only a way to avoid repeating yourself. It is a way to give
|
|
a name to an idea, so that the code using the idea can say the name and move
|
|
on. "Keep the player between the left edge and the right edge" is a thought
|
|
you had once. `clamp(playerX, 0, width - PLAYER_WIDTH - 1)` is that thought,
|
|
written down. Four `if` statements are the same thought taken apart into
|
|
pieces you have to put back together in your head every time you read them.
|
|
|
|
When you are deciding whether something deserves to be a function, do not ask
|
|
how many times you will call it. Ask whether it has a name.
|
|
|
|
=== Try It
|
|
|
|
. *Rename a parameter.* In `drawStar`, change `x` and `y` to `across` and
|
|
`down`, everywhere inside the function. Do not touch anything outside it.
|
|
Run it. Work out why nothing broke.
|
|
. *Draw them the other way round.* Swap `drawStars()` and `drawPlayer()` in
|
|
`onOverlayUpdate` and look at the ship. Then put them back. Each of those
|
|
five lines is one job, and their order on the screen is their order in the
|
|
function.
|
|
. *Use `clamp` on a shade.* Change `shadeFor` to
|
|
`return clamp(math.random(60, 90) + layer * 90, 0, 200)` and look at the
|
|
three layers. Then take the `clamp` out and look again: `colorForeground`
|
|
was quietly holding the numbers down for you all along, and now you can see
|
|
where.
|
|
. *Call `drawStar` with two arguments.* Write `drawStar(100, 100)` somewhere
|
|
in `drawLayer` and run it. The error names the exact thing that went
|
|
missing. Read it before you put the third argument back.
|
|
. *Move a local.* Take the line `local drift = 0` and move it to the bottom of
|
|
the file, below everything. Run it, read the error, and think about the word
|
|
"below" in the section above.
|
|
|
|
=== Break It on Purpose
|
|
|
|
The order of a file with locals in it matters, and the error you get when you
|
|
break that order is not the one you expect.
|
|
|
|
Take the whole `drawStar` function and move it to the *bottom* of the script,
|
|
below `onOverlayUpdate`. Everything is still there, spelled the same. Run it:
|
|
|
|
----
|
|
Error executing function 'onOverlayUpdate': functions.singe:50: attempt to call a nil value (global 'drawStar')
|
|
stack traceback:
|
|
functions.singe:50: in upvalue 'drawLayer'
|
|
functions.singe:57: in upvalue 'drawStars'
|
|
functions.singe:101: in function 'onOverlayUpdate'
|
|
----
|
|
|
|
This looks different from the error in lesson one because it is a different
|
|
kind of error. Lesson one's mistake was caught before the script ran. This one
|
|
was caught while it was running, which is why Singe names the function it was
|
|
in the middle of, and why there is a *traceback* underneath: the list of who
|
|
called whom, newest first. Line 50 is inside `drawLayer`, which was called
|
|
from line 57 inside `drawStars`, which was called from line 101 inside
|
|
`onOverlayUpdate`. Your line numbers will differ if your file does not match
|
|
the one above exactly, and the engine may add a line or two of its own below
|
|
these. The word `upvalue` is Lua's name for a local that a function borrowed
|
|
from the file around it; read past it for now.
|
|
|
|
The complaint itself is worth learning by heart. "Attempt to call a nil value"
|
|
means you put parentheses after a name that held nothing. "Global `drawStar`"
|
|
is the part that gives the game away: you marked `drawStar` as local, so when
|
|
Lua read `drawLayer` and found no local of that name anywhere above it, the
|
|
only thing left to try was a global -- and there is no global called
|
|
`drawStar` either. Put the function back above the code that calls it and the
|
|
error goes with it.
|
|
|
|
=== What You Learned
|
|
|
|
* You can write functions of your own, and nothing runs them but your own
|
|
calls.
|
|
* The names inside a function's parentheses are parameters, and they are the
|
|
function's own copies of what the caller passed.
|
|
* `return` hands a value back and stops the function where it stands, and a
|
|
function that ends without one hands back `nil`.
|
|
* A call can be an argument to another call, and the inside one happens first.
|
|
* Lua can return more than one value, and you catch them with several names
|
|
separated by commas.
|
|
* `local` makes a name that exists only where it was written.
|
|
* A local inside a function is made fresh on every call and remembers nothing
|
|
from the last one.
|
|
* A local can only be seen below the line that creates it, so values and your
|
|
own functions go above the callbacks that use them.
|
|
* Callbacks stay global, because Singe finds them by name.
|
|
* A function is worth writing when the thing it does has a name, even if you
|
|
call it once.
|
|
|
|
=== Next Time
|
|
|
|
Your hundred and twenty stars are still a hundred and twenty copies of three
|
|
ideas. Nothing in the program is *a star*: there is a number, and some
|
|
arithmetic that turns the number into a position. You cannot say "this one is
|
|
nearer, so it falls faster and shines brighter", because there is no "this
|
|
one" to say it about, and you cannot let a star fall off the bottom and be
|
|
replaced by a new one, because there is nothing there to remove.
|
|
|
|
Lesson six gives you the thing that holds a star, and then a way to hold a
|
|
hundred and twenty of them and add a hundred and twenty-first while the game
|
|
is running. It is the most useful lesson in this part of the book.
|