== Lesson 3: Making Decisions image::learn/03-decisions.png[The finished lesson, 480] Everything you have written so far does the same thing every frame. The word in lesson two moved whether you wanted it to or not, and the only reason it ever turned round was four blocks of code you were told to take on trust. This lesson is those four blocks, properly. By the end of it your program will ask questions, act on the answers, and -- because the most interesting question a game can ask is "what is the player doing?" -- you will be steering a box around the window with the arrow keys. Keep that box. It is the player of the game you build in lesson seven. === Start a New Folder A folder called `decisions`, a file in it called `decisions.singe`, and `Singe -R decisions` running in a terminal. Same as last time. === What an `if` Really Says Here is the block from lesson two on its own. [source,lua] ---- if wordX > lastColumn then wordX = lastColumn end ---- Four parts. The word `if`. A question. The word `then`. And a body of lines that run only when the answer to the question is yes, closed off by `end`. The question is `wordX > lastColumn`, and the `>` is doing the real work. It compares the two numbers either side of it and produces an answer that is either *true* or *false*. Those two words are not text and are not numbers; they are values in their own right, called *booleans*, and a boolean is the only thing an `if` is interested in. The indentation is the same idea as in lesson one: the lines inside are indented so you can see at a glance what belongs to the `if`, and the `end` lines up with the `if` that opened it. Lua would run it without the spaces. You would not be able to read it. There are six of these comparisons and you will use all of them. [cols="1,4"] |=== | `a < b` | a is less than b | `a > b` | a is greater than b | `a <= b` | a is less than or equal to b | `a >= b` | a is greater than or equal to b | `a == b` | a is the same as b | `a ~= b` | a is not the same as b |=== The last two are the ones to be careful with, for different reasons. `~=` means "is not equal to". Most languages write that `!=`, and if you have seen a program before, your fingers will type `!=` and Lua will refuse it. The squiggle is on your keyboard next to the `1` or under the `#`, depending on where you live. `==` is two equals signs, and it is not a typo for one. This is the single most common mistake in programming, and it is worth stopping on for a moment. In lesson two you learned that one equals sign means *put the right-hand side into the name on the left*. That is a thing you do. It changes something. Two equals signs mean *are these the same?* That is a question you ask. It changes nothing, and it hands back true or false. [source,lua] ---- lives = 3 if lives == 3 then ---- The first line sets `lives` to three. The second asks whether it is three. Different jobs, different symbols, one character apart. Lua will catch you if you get it the wrong way round, and you will meet the error it uses at the end of this lesson. === When the Answer Is No An `if` on its own does nothing at all when the answer is false. Very often you want the other thing to happen instead, and that is `else`. [source,lua] ---- if lives > 0 then message = "Ready" else message = "Game Over" end ---- Exactly one of those two bodies runs, never both, never neither, so `message` always ends up holding one of the two strings and never nothing. When there are more than two possibilities, `elseif` chains them. [source,lua] ---- if score >= 10000 then rank = "ace" elseif score >= 5000 then rank = "pilot" elseif score >= 1000 then rank = "rookie" else rank = "passenger" end ---- Lua tries the questions from the top and stops at the first one that is true. A score of 6000 is not greater than 10000, so it tries the next, which is true, so `rank` becomes `pilot` and the other two questions are never asked. That last point matters: the `elseif` for 1000 is also true for a score of 6000, and it is never reached, which is why the order is from biggest to smallest. The whole chain needs exactly one `end`, no matter how many `elseif` parts are in it. It is one decision, not four. === True, False, and Nothing at All You do not have to compare two things to get a boolean. You can keep one. [source,lua] ---- paused = false ---- That is a variable like any other, and the value in it is the boolean `false` itself, written without quotes because it is not the word "false", it is the idea. The same goes for `true`. A variable holding one of them can go straight into an `if` with no comparison at all: [source,lua] ---- if paused then ---- Read it as "if paused". That is better English than `if paused == true`, which does the same thing the long way round, and experienced programmers will look at you oddly for writing it. Three words join and flip these answers. [source,lua] ---- if alive and not shielded then if coins > 0 or freePlay then ---- `and` is true when both sides are. `or` is true when either side is, or both. `not` turns true into false and false into true. They read like English and, unusually for programming, they mean what English means. There is one more value to know about, and it is the one that produces the most confusing errors of your first year. [source,lua] ---- overlayPrint(0, 0, "lives " .. tostring(lives)) ---- If you have never put anything into `lives`, that prints `lives nil`. Lua does not complain about a name it has never seen; it hands you `nil`, which is its word for *nothing here*. It is not zero and it is not an empty string. Zero is a number you meant to store. `nil` is the absence of a value. You have met `nil` twice already without being told. `frames = frames + 1` without the `frames = 0` line above it fails because you asked Lua to add one to nothing. And a callback you spell wrong is `nil` too, which is why a misspelled `onOverlayUpdate` gives you a black window and no error at all: Singe looks for the name, finds nothing there, and quietly gets on with its day. `tostring` is in that line because `..` will not join a boolean or a `nil` to anything. Numbers it converts for you; these it will not. That was the promise at the end of lesson two, and now you have a use for it. Finally, the rule that ties the whole section together, because `if` will accept anything you give it, not only true and false: [quote] Everything in Lua is true except `false` and `nil`. Zero is true. An empty string is true. Only those two are not, and once you know it, `if movingLeft then` reads perfectly whether `movingLeft` holds `true`, `false`, or has never been set at all. === Reading the Controls Time to ask the player something. That takes a new callback. [source,lua] ---- function onInputPressed(what) if what == SWITCH_RIGHT then boxX = boxX + 10 end end ---- `onInputPressed` is the second callback you have written, and it works exactly the way lesson one described: you write it, Singe calls it. `onOverlayUpdate` is called on a timer. This one is called when something happens -- when the player presses anything the engine recognizes. The difference from `onOverlayUpdate` is the word inside the parentheses. Singe has to tell you *which* control was pressed, so it hands the function a value, and `what` is the name you have chosen to receive it under. Lesson one called the things you pass into a function arguments; from the inside, the name that catches one is a *parameter*. The name is yours: call it `what`, call it `button`, call it `k`. Singe fills it in either way. `SWITCH_RIGHT` is a name the engine has already given to a value, like `OVERLAY_UPDATED` in lesson one. You do not have to load anything to use it; Singe defines all of them before your script runs. There is one for everything a cabinet has: `SWITCH_LEFT`, `SWITCH_RIGHT`, `SWITCH_UP`, `SWITCH_DOWN`, `SWITCH_BUTTON1` through `SWITCH_BUTTON4`, `SWITCH_START1`, `SWITCH_COIN1`, and more. The manual lists them all. They are deliberately not called "the right arrow key", because the same `SWITCH_RIGHT` arrives whether the player pushed an arrow key, a joystick, or a control pad's thumbstick. You write the game once and it works on all three. Add that callback, give `boxX` a starting value outside the functions, and draw something at `boxX`. Press the right arrow. The box jumps ten pixels each time you press it. === It Happened, and It Is Happening Hold the arrow key down. Nothing more happens. `onInputPressed` is called once, on the way down, and not again until you let go and press it again. There is no key repeat. That is exactly right for some things -- firing a shot, inserting a coin, choosing a menu item -- and exactly wrong for walking, which needs to keep happening for as long as the key is held. The two are genuinely different questions. *It happened* is an event, and you hear about it once. *It is happening* is a state, and it lasts. Singe tells you about events, and the way you turn events into state is to remember them yourself. Two callbacks, a variable between them. [source,lua] ---- movingRight = false function onInputPressed(what) if what == SWITCH_RIGHT then movingRight = true end end function onInputReleased(what) if what == SWITCH_RIGHT then movingRight = false end end ---- `onInputReleased` is the third callback, and it is the mirror of the second: Singe calls it on the way back up, with the same `SWITCH_` value. Between them they keep `movingRight` telling the truth about what the player's finger is doing right now. Nothing has moved yet, and that is the point. The pressing and the moving are now two separate jobs. The moving goes where all the per-frame work goes. [source,lua] ---- function onOverlayUpdate() if movingRight then boxX = boxX + BOX_SPEED end ... ---- The box now slides smoothly for as long as the key is down, at a speed you control, and the arithmetic is back in the one function that runs sixty times a second where it belongs. This shape -- events set flags, the frame acts on flags -- is how nearly every game handles its controls, and you will use it again in every lesson that has a player in it. === Drawing a Box `overlayBox` takes two corners: the left and top of the rectangle, then the right and bottom. [source,lua] ---- colorForeground(255, 220, 0) overlayBox(boxX, boxY, boxX + BOX_SIZE, boxY + BOX_SIZE) ---- It draws an *outline*, four lines one pixel thick, and not a filled rectangle. There is no filled rectangle in the overlay drawing calls at all. When you want a solid block later there are ways to get one, and lesson nine has the usual one. Its numbers are pixels, not character cells. This catches people out, because `overlayPrint` in lesson two counted in cells of six pixels by thirteen and everything else in Singe counts in pixels. `overlayPrint` is the odd one; the rest of the drawing calls -- `overlayBox`, `overlayLine`, `overlayCircle`, `overlayEllipse`, and `overlayPlot` -- all work in the same pixels that `overlayGetWidth` reports. `colorForeground` sets the colour, as three numbers from 0 to 255: how much red, how much green, how much blue. All three at 255 is white, all at 0 is black, and `(255, 220, 0)` is a warm yellow. Notice that it is not an argument to `overlayBox`. It is a setting: you change it, and everything you draw after that comes out in the new colour until you change it again. `overlayPrint` is the exception once more -- the console font arrives with its own colours baked in and ignores you. === The Whole Thing [source,lua] ---- BOX_SIZE = 20 BOX_SPEED = 2 width = overlayGetWidth() height = overlayGetHeight() maxX = width - BOX_SIZE - 1 maxY = height - BOX_SIZE - 1 boxX = width // 2 boxY = height // 2 movingLeft = false movingRight = false movingUp = false movingDown = false function onInputPressed(what) if what == SWITCH_LEFT then movingLeft = true elseif what == SWITCH_RIGHT then movingRight = true elseif what == SWITCH_UP then movingUp = true elseif what == SWITCH_DOWN then movingDown = true end end function onInputReleased(what) if what == SWITCH_LEFT then movingLeft = false elseif what == SWITCH_RIGHT then movingRight = false elseif what == SWITCH_UP then movingUp = false elseif what == SWITCH_DOWN then movingDown = false end end function onOverlayUpdate() if movingLeft and not movingRight then boxX = boxX - BOX_SPEED elseif movingRight and not movingLeft then boxX = boxX + BOX_SPEED end if movingUp and not movingDown then boxY = boxY - BOX_SPEED elseif movingDown and not movingUp then boxY = boxY + BOX_SPEED end if boxX < 0 then boxX = 0 elseif boxX > maxX then boxX = maxX end if boxY < 0 then boxY = 0 elseif boxY > maxY then boxY = maxY end overlayClear() if movingLeft or movingRight or movingUp or movingDown then colorForeground(255, 220, 0) else colorForeground(80, 160, 255) end overlayBox(boxX, boxY, boxX + BOX_SIZE, boxY + BOX_SIZE) overlayPrint(0, 0, "x " .. boxX .. " y " .. boxY) return OVERLAY_UPDATED end ---- Four things in there are worth a second look. `movingLeft and not movingRight` is there because a player can hold both arrows at once. Without the `not`, the two would fight and the box would jitter or drift; with it, holding both means neither wins and the box stands still, which is what a player expects. `maxX` is worked out once at the top, using `overlayGetWidth` the way lesson two did, and the `- 1` is because the surface's last pixel is one less than its width. Take the `- 1` out and the box's right-hand edge falls off the surface and vanishes, because Singe throws away anything you draw outside the overlay without complaining. The clamping blocks use `elseif` rather than two separate `if` blocks. The box cannot be off the left edge and off the right edge at the same time, so asking the second question when the first was true is wasted work. And the colour decision is an `if` whose only job is to pick a value. Yellow while the player is doing something, blue while they are not, which is a free way of seeing that your input handling actually works. === Try It . *Change the speed.* `BOX_SPEED = 1` and `BOX_SPEED = 8`. At eight, watch what the clamping does when you shove the box into a corner. . *Show your flags.* Add a line to `onOverlayUpdate`: `overlayPrint(0, 1, "left " .. tostring(movingLeft))`. Hold and release the left arrow and watch it flip. Then take the `tostring` off and read the error Lua gives you. . *Make it faster when a button is down.* Add a `running` flag driven by `SWITCH_BUTTON1` in the two input callbacks, and use an `if` in `onOverlayUpdate` to move by twice `BOX_SPEED` while it is true. . *Mark the middle.* Draw a second, small box in the centre of the window that changes colour when the player's box overlaps it. You will need `and` to join four comparisons, and it is harder than it sounds. Getting it slightly wrong is normal; lesson eleven does it properly. . *Take the releases out.* Delete the whole `onInputReleased` function and run it. The box takes one step and then never stops. Work out which line is now never reached. === Break It on Purpose Find the clamping block and take one of the equals signs out of a comparison, so it reads: [source,lua] ---- if boxX = 0 then ---- Save it. The window sits there empty and the terminal says: ---- Error running script: decisions.singe:57: 'then' expected near '=' ---- This is the mistake the whole `==` section was warning about, and the message is a good example of an error that is completely accurate and completely unhelpful if you take it literally. Lua is not asking you to add the word `then`. Lua got as far as `if boxX`, expected a comparison or a `then` next, and found `=`, which cannot be either. Whenever you see `'then' expected` after an `if`, look for a single `=` that should be a double one. It is nearly always that. Note where this one appeared: Singe never opened the window, the same as lesson one's unfinished string, because Lua could not read the file. The frame counter error in lesson two happened while running. Errors in the shape of your code stop you before the game starts. Errors in what your code does wait until it runs. === What You Learned * `if question then ... end` runs a body only when the answer is true. * `else` gives the other case, and `elseif` chains more; the first true question wins and the rest are never asked. * The comparisons are `<`, `>`, `<=`, `>=`, `==`, and `~=`. * `=` puts a value into a name. `==` asks whether two things are the same. * `true` and `false` are values, and you can keep them in variables. * `and`, `or`, and `not` join and flip those answers. * `nil` is what you get from a name you never set, and it is not zero. * Everything is true except `false` and `nil`. * `onInputPressed` and `onInputReleased` are callbacks Singe calls with a `SWITCH_` value when the player presses or lets go of a control. * A press is an event and happens once; hold the state in a variable and act on it every frame. * `overlayBox` draws an outline in pixels, and `colorForeground` is a setting, not an argument. === Next Time One box is easy. A hundred stars are not, at least not the way you have written things so far, because typing a hundred `overlayPlot` lines is nobody's idea of a good evening. Lesson four is the loop: telling the computer to do the same thing many times, with something different each time round, which is what makes a screen full of anything possible.