== Lesson 2: Numbers That Change image::learn/02-numbers.png[The finished lesson, 480] In lesson one you put a word on the screen and it sat there. By the end of this lesson the same word will be sliding around the window, turning round when it reaches an edge, and it will be doing it because of four numbers that the program changes sixty times a second. Along the way you will meet the single idea that trips up more beginners than any other: where you put something decides how long it lives. === Start a New Folder Make a folder called `numbers` next to your `hello` folder, and inside it a file called `numbers.singe`. Type the lesson one program into it, or copy `hello.singe` over and rename it -- either is fine, and typing it again is not wasted. [source,lua] ---- function onOverlayUpdate() overlayClear() overlayPrint(2, 2, "Hello!") return OVERLAY_UPDATED end ---- Run it with `Singe -R numbers` and leave it running. The `-R` from the end of lesson one means you will not have to start it again for the rest of this lesson; save the file and the window catches up on its own. === Count the Frames Lesson one said that Singe calls `onOverlayUpdate` about sixty times a second. That is easy to say and hard to believe, so count them. [source,lua] ---- function onOverlayUpdate() frames = 0 frames = frames + 1 overlayClear() overlayPrint(2, 2, "Hello!") overlayPrint(0, 0, "frame " .. frames) return OVERLAY_UPDATED end ---- Save it. In the top left corner you get `frame 1`, and it stays at `frame 1` forever. That is disappointing, and it is also the most useful thing in this lesson, so do not fix it yet. Look at what you wrote and think about the order of the lines, which lesson one said was the most important thing about a program. Singe calls `onOverlayUpdate`. The first line sets `frames` to zero. The second adds one to it, making one. You print it, you return, and the function is over. Then Singe calls it again, and the first thing that happens is that `frames` is set back to zero. You are not counting frames. You are counting to one, sixty times a second. === Move the Line Out Take the `frames = 0` line out of the function and put it above, on its own, before the word `function`. [source,lua] ---- frames = 0 function onOverlayUpdate() frames = frames + 1 overlayClear() overlayPrint(2, 2, "Hello!") overlayPrint(0, 0, "frame " .. frames) return OVERLAY_UPDATED end ---- Save it. The number climbs, fast, and does not stop. Nothing else changed. The only difference is *where* the line is, and that is the whole of the idea. Lines inside a function happen every single time the function is called. Lines outside every function happen once, when Singe reads your file, before the game starts. `frames = 0` needed to happen once. It was happening sixty times a second, and it was wiping out the work of the line under it. Every moving thing you ever write has this shape. Set the number up outside, change it inside. === What Just Happened Three new pieces of code are in there. Here they are on their own. [source,lua] ---- frames = 0 ---- This makes up a name, `frames`, and puts the number zero behind it. A name with a value behind it is a *variable*, and it is called that because the value is allowed to vary -- to change while the program runs. The `=` is not the equals sign from school. It does not say that `frames` is equal to zero; it says *put zero into `frames`*, and it always works in that direction, right into left. Programmers call it *assignment*, and it is worth saying the line out loud that way as you write it: "frames gets zero". You choose the name. It can be almost anything that starts with a letter and has no spaces in it, and it should say what the thing is. `frames` is a good name. `f` is a bad one, and you will not thank yourself for it in a week. [source,lua] ---- frames = frames + 1 ---- This is the line that makes people stop and stare, because as a statement about arithmetic it is nonsense: nothing is equal to itself plus one. It is not a statement about arithmetic. Remember that `=` puts the right-hand side into the left-hand name, and read it in that order, right first. Work out `frames + 1` using whatever is in `frames` right now. Then put the answer back into `frames`. If `frames` held six, the right-hand side works out to seven, and seven goes into `frames`. Adding one to a variable like this is something you will write thousands of times. The `+` is what you expect, and so are the other three: `-` subtracts, `*` multiplies, and `/` divides. There is no `x` key for multiply, which is why the star does that job. [source,lua] ---- overlayPrint(0, 0, "frame " .. frames) ---- `overlayPrint` wants text, and `frames` is a number, not text. The two dots join the two together and hand the result over as one string: the four characters `frame`, a space, and then however many digits the number needs. Those two dots are the joining operator, and it is worth knowing its real name because the error messages use it: joining strings is called *concatenation*. Note the space inside the quotes, before the closing one. Without it you get `frame6`. Lua joins exactly what you give it and adds nothing of its own. Lua will turn a number into text for you when you join it to a string this way, which is why you did not have to do anything special. It will not do that for everything. If you ever try to join something that is neither text nor a number, Lua stops with an error, and the way round it is `tostring`, which turns anything at all into text: [source,lua] ---- overlayPrint(0, 0, "frame " .. tostring(frames)) ---- That line does exactly the same as the one before it. You do not need `tostring` for numbers. You will need it in lesson three. === Make It Move You now have everything you need to move the word. The word is drawn wherever you say, you have a way to keep a number between frames, and you have a way to change it. Give the column its own variable outside the function, and add to it inside. [source,lua] ---- frames = 0 wordX = 0 function onOverlayUpdate() frames = frames + 1 wordX = wordX + 1 overlayClear() overlayPrint(wordX, 2, "Hello!") overlayPrint(0, 0, "frame " .. frames) return OVERLAY_UPDATED end ---- Save it. The word shoots off the right-hand side and never comes back. It moved one column every frame, sixty times a second, so it crossed the whole window in about a second. Slow it down by adding less than a whole column: [source,lua] ---- wordX = wordX + 0.25 ---- Now it takes four frames to move one column, and the word creeps. `wordX` is mostly a number like `12.75`, which is not a column at all, and Singe deals with that by throwing away everything after the dot when it draws: `12.75` draws in column twelve. Keeping the fraction in the variable is exactly what lets you move slower than one column a frame. The number is finer than the screen is. === Where Is the Edge? The word still leaves. To turn it round you have to know where the right-hand edge is, and you could find that out by counting: a game with no video gets a world 720 by 480 by default, the overlay you draw on is half that, 360 by 240, and the built-in console font's letters are six pixels wide, so there are sixty columns. Do not do that. Ask the engine. [source,lua] ---- lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word) ---- `overlayGetWidth` and `overlayGetHeight` are two more functions Singe provides, like `overlayPrint`. They take nothing and hand back a number: how wide and how tall the drawing surface is. You saw `return` hand a value back in lesson one, and this is the other end of it -- a function you call can give you something, and here you are doing arithmetic with what it gave you. The numbers they give you are in pixels, and `overlayPrint` counts in character cells, so you have to divide. `overlayGetFontWidth` tells you how many pixels wide one cell is, and `overlayGetFontHeight` how tall, so you never have to know the answer yourself. The two slashes mean *divide and throw the fraction away*, because a third of a character cell is not a place you can print. The ordinary single slash would give you a fraction, and a fraction of a cell is not a place either. `string.len` counts the characters in a string, and it is there so that the word turns round when its *last* letter reaches the edge rather than its first. Try it without and you will see six letters slide off into nothing. Those two capitalized names are a habit rather than a rule. Lua does not care, but a name in capitals is how programmers say "this is a number I set once at the top and never touch again", and it beats having a bare `6` halfway down a file that nobody can explain a month later. === Turn It Round Moving the other way is no work at all: instead of adding `0.25` every frame, add `-0.25`. So keep the amount in a variable of its own and flip its sign when the word arrives at an edge. [source,lua] ---- if wordX > lastColumn then wordX = lastColumn speedX = -speedX end ---- That is a decision, and decisions are the whole of lesson three, so take it on trust for one lesson. Read it out: *if* `wordX` has got past `lastColumn`, *then* do the two indented lines; otherwise skip them. The `end` closes it, the same way `end` closes a function. The first line pins the word exactly on the edge, so that a fast word cannot sail past it. The second is the flip. `-speedX` is minus whatever `speedX` holds, so if it held `0.25` it now holds `-0.25`, and adding `-0.25` every frame walks the word back the way it came. When it reaches the left edge, the matching `if` flips it again, and it flips back to `0.25`. Two more of those for the top and bottom edges and the word bounces around the whole window. === The Whole Thing [source,lua] ---- CELL_WIDTH = overlayGetFontWidth() CELL_HEIGHT = overlayGetFontHeight() TOP_ROW = 2 word = "Hello!" wordX = 0 wordY = TOP_ROW speedX = 0.25 speedY = 0.125 frames = 0 lastColumn = overlayGetWidth() // CELL_WIDTH - string.len(word) lastRow = overlayGetHeight() // CELL_HEIGHT - 1 function onOverlayUpdate() frames = frames + 1 wordX = wordX + speedX wordY = wordY + speedY if wordX < 0 then wordX = 0 speedX = -speedX end if wordX > lastColumn then wordX = lastColumn speedX = -speedX end if wordY < TOP_ROW then wordY = TOP_ROW speedY = -speedY end if wordY > lastRow then wordY = lastRow speedY = -speedY end overlayClear() overlayPrint(0, 0, "frame " .. frames) overlayPrint(wordX, wordY, word) return OVERLAY_UPDATED end ---- `TOP_ROW` keeps the word out of the top two lines so that it never scribbles over the frame counter. `lastRow` has a `- 1` in it rather than a `string.len`, because a word is many characters wide but only ever one line tall. Notice that the whole top half of the file runs once and the whole bottom half runs sixty times a second, and that you can tell which is which by looking at the indentation. That is what the spaces were for. === Try It . *Change the speeds.* Try `speedX = 1` and `speedY = 1`. Then try `0.05` for both. Then make one much bigger than the other. . *Change the word.* Put a much longer word in the quotes -- long enough to fill a third of the window -- and watch the right-hand bounce still happen in the right place. That is `string.len` doing its job. . *Take the clear out.* Delete the `overlayClear()` line. Lesson one promised this would matter one day, and this is the day: the word smears a trail across the window and never rubs any of it out, because drawing has always drawn on top of what was there. Put it back. . *Stop it going sideways.* Set `speedX = 0` and run it. Work out why the word now never leaves column zero, and why the `if` for the left edge never fires. . *Break the frame counter on purpose.* Move the `frames = 0` line back inside the function, above `frames = frames + 1`. Watch the counter stick at one while the word keeps moving perfectly. Then work out why the word is unaffected. === Break It on Purpose Change the two dots on the frame counter line to a plus sign, so that it reads: [source,lua] ---- overlayPrint(0, 0, "frame " + frames) ---- Save it. The window closes, Singe stops, and the terminal says: ---- Error executing function 'onOverlayUpdate': numbers.singe:42: attempt to add a 'string' with a 'number' ---- followed by several lines starting with `stack traceback:`, which you can ignore for now. Read it the way lesson one taught you: the file, the line, then the complaint. Line 42 is the line you changed. The complaint says Lua was asked to *add* a string to a number, and adding is for numbers. `+` and `..` look similar on the page and do entirely different jobs: `+` is arithmetic, `..` is glue. This one is worth meeting because of where it appeared. Lesson one's error stopped Singe before the window ever opened, because Lua could not read the file at all. This one is a perfectly readable line that only goes wrong when it runs, so the window opened, the game ran, and it died on the first frame. Both kinds tell you the file and the line. Start there either way. === What You Learned * A variable is a name with a value behind it, and the value can change. * `=` means "put the right-hand side into the name on the left", not "is equal to". * `frames = frames + 1` works out the right-hand side first, then stores it. * `+`, `-`, `*`, and `/` do the arithmetic you expect; `//` divides and throws the fraction away. * `..` joins text together, and turns a number into text on the way. * A line inside `onOverlayUpdate` runs every frame. A line outside every function runs once, before the game starts. * Anything that has to remember something between frames has to live outside. * `overlayGetWidth` and `overlayGetHeight` tell you the size of the drawing surface, so you never have to guess it. * `overlayClear` matters the moment anything moves. === Next Time The word moves, but it moves on its own and nothing you do makes any difference to it. In lesson three you take the four `if` blocks you used on trust, learn what they really say, and point them at the arrow keys instead of the edges of the screen. The box you steer around the window at the end of it is the player of the game you build in lesson seven.