== 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.