Game: Don’t Steve #
So far we’ve
- Installed the Lua interpeter
- Edited a basic
.lua
file - Run the
.lua
file from the command line
Instead of just throwing out random code examples without context, let’s make a very simple game called Don’t Steve. The point of the game is to enter as many names as possible, but not Steve. Never Steve.
Open up a new file in your text editor of choice, and save it in your source
directory as dont-steve.lua
.
It’s almost never a good idea to use punctuation or spaces in script file names, it will cause all kinds of problems. That’s why I’ve named the filedont-steve.lua
and notdon't steve.lua
How To Begin #
Any time you start programming something, a useful first step is just to write out the steps you think would be involved in words. Then you can tackle each step on it’s own, breaking them down further if necessary, and hopefully tie them all together into your final program.
So let’s design the game, and write out how it’s supposed to work:
- Show an introduction message
- Ask the player to enter a name
- Read the name the player enters
- Check if the name is Steve
- If the name is NOT Steve then the player scores a point
- If the name IS Steve, then the player loses
- If the player hasn’t lost, repeat and keep asking them to enter names
Key Point
Even though the idea here is simple, we’re already looking at a typical main loop of a game from a programming perspective.
- Handle Input from the player, this might come from the keyboard, the mouse, a game controller, or over a network
- Update The Game state (in this case, just the score)
- Display The Results to the player
- Repeat
Now we can tackle each step on one-by-one…
Show an introduction message #
First things first, let’s give some immediate feedback so that the player knows the game is running and what it’s called.
We can do this by calling a function to print out some text on the screen.
print("Welcome to Don't Steve")
print is the name of a standard function provided by Lua. Usually when you see a word in Lua followed by paranthesis () that means it’s a function call. Functions are the verbs of a programming language, they contain their own list of instructions that make something happen, so when you want that stuff to happen, call that function.
Typing a function name followed by () calls a function, causing it to execute the instructions within.
Often a function will take parameters. In the line above, we’re passing one parameter into the print
function when we call it. The parameter is the text we want to display “Welcome to Don’t Steve”. Parameters are passed to a function when you call it by typing them inside the () following the function name.
Type this into dont-steve.lua
, save it, and run it from the command line with lua dont-steve.lua
You should see the output of the program:
What’s up with the “quotes” #
Why didn’t we just write…
print(Welcome to Don't Steve)
It’s almost the same as before, but missing the ""
around the words we want to print out. If you run this version, you’ll get an error like this:
lua53: dont-steve.lua:1: ')' expected near 'to'
Ok… that’s pretty inscrutable. Maybe even misleading.
One of the black magic parts of programming is learning to interpret error messages you get when things go wrong. And they will go wrong, a lot. Eventually you’ll learn enough things that could go wrong that you can start checking to see if it’s one of those.
The problem here is that without the ""
Lua doesn’t know which parts of your program are written in Lua, and which parts are just words written in english. Lua is trying to understand what Welcome to Don't Steve
means, and it can’t, because Lua only understands Lua, not English.
Surrounding text with "" marks that text as a string, which can contain any old words you want, in English or another language. Lua won’t try and understand what’s on the inside of the string, it will just store it inside the program so that you can do things with it like print it on the screen.
Event the print function – which accepts a string as a parameter – doesn’t understand anything about the words inside the string. It just blindly writes each letter onto the screen.
Watch Out
A missing
"
,(
or)
is likely to be a very common source of errors in your programs, they’re easy to forget or mistype, and can be hard to spot while reading over the code.One of the first things you should do when you get a syntax error, is double check that all your
"
,(
, and)
are in the right places, and balanced, so that there are always two, a"
at both the start and end of each string, and a(
at the start and a)
at the end of each function call.
Tell the player to enter a name #
Since a new player might not be a master of Don’t Steve, it’s probably also a good idea to tell them what is expected by prompting them to enter a name. We can just call print again, and provide instructions:
print("Welcome to Don't Steve")
print("Enter a Name:")
print automatically writes a new line after whatever you said to print, so the output will appear on two lines:
Welcome to Don't Steve
Enter a Name:
Read the name the player enters #
Right now the program simply ends immediately after printing out its instructions. We need to call a different function that will let the player type a name, and give us the result. Lua provides io.read for that purpose.
print("Welcome to Don't Steve")
print("Enter a Name:")
io.read()
We don’t need to pass any parameters to io.read right now, so we call it with empty ()
. Even though there are no parameters, we still need the ()
at the end to indicate that we want to call the function.
Now when you run the program, it doesn’t exit immediately, but instead waits for user input. Type your name, or any name, or anything at all really, and hit Enter.
Welcome to Don't Steve
Enter a Name:
Brook
After reading in the type that you typed, the program ends.
So far so good, but how do we know what the name was so that we can make sure it wasn’t Steve?
Enter variables #
Variables store values, like the name somebody just entered into the game. The function io.read returns a value when it has finished running, and we can store that value in a variable so that we can keep it around, print it back to the player for confirmation, and make sure it isn’t Steve.
what_they_entered = io.read()
This line says to set the variable named what_they_entered to the return value from the function io.read. When Lua runs this line of code, the stuff on the right hand side of the = runs first, and reads the name the player entered. When io.read has completed, it returns a string containing what the player entered, and the = sign takes that value, and stores it in the variable on the left hand side.
In Lua you can create new variables just by assigning a value to them, like we have here. If the variable already existed, its old value will be erased and this new value stored in it. If it didn’t previously exist, it will be created and assigned this new value.
Any line of the form:
variable_name = new_value
Will set the variable on the left, to the value on the right, whatever it may be.
How can we check if this worked? Is the value really stored? Print it out again to see:
print("Welcome to Don't Steve")
print("Enter a Name:")
what_they_entered = io.read()
print("You entered:", what_they_entered)
print like many functions, can accept more than one parameter. If you pass print multiple parameters, separated with a comma, it will print out each one on the screen separated by a tab. Whether or not other functions accept multiple parameters or not, and what happens when you pass in multiple parameters to them is determined by each function, and will work differently in each case.
Note that this time when we call print, the first parameter has ""
around it, because it’s just text we want to print out. However the second parameter what_they_entered
doesn’t have ""
around it. Lua knows this is a variable name now, so Lua will instead pass the value contained in that variable to print.
print(what_they_entered)
Will pass the current value of the variable what_they_entered
into the function print. Whereas…
print("what_they_entered")
Will simply print the string what_they_entered every time, since the ""
tells Lua to treat everything inside the ""
just as text to print out, not as Lua code to interpret.
Now when I run the program:
print("Welcome to Don't Steve")
print("Enter a Name:")
what_they_entered = io.read()
print("You entered:", what_they_entered)
And enter my name, I get the following output:
Welcome to Don't Steve
Enter a Name:
Brook
You entered: Brook
Check if the name is Steve #
We’ve read in the name that the player entered, and stored it in a variable name what_they_entered
. Now all we have to do is check and see if it’s Steve or not.
print("Welcome to Don't Steve")
print("Enter a Name:")
what_they_entered = io.read()
print("You entered:", what_they_entered)
if what_they_entered == "Steve" then
print("You Steve'd! YOU LOSE")
end
Up until now, everthing in our program happens one after the other with no variation, but here we want to make a choice. Using an if statement. if, then and end are all keywords in Lua. They are a fundamental component of the Lua language and have special meanings, and you can’t create variables named the same as any keyword because that would be super confusing.
The syntax if <condition> then <body> end
instructs Lua to check if the condition is true and if so, to run the code in the body of the if statement. end as you might imagine, marks the end of the body of the if statement, after which the program continues as normal.
The condition in this case uses the equality operator ==. Previously we used a single equals sign = to assign (or set) a value to a variable, but the double equals sign == checks to see if the two sides are equal. If the value on the left side and the value on the right side of == are both the same it returns true, and if they’re different it returns false.
Since we’re using the == inside our if condition, the if keyword checks the result, and runs the code in it’s body only if the result is true. Otherwise it skips the body and goes directly to end.
Run this program a few times and try entering a few different names, including Steve. If you enter Steve
you should get a message saying you’ve lost, otherwise the program ends without saying anything.
Watch Out
Mixing up
=
with==
is another common source of errors. It’s easy to mis-type, and can be hard to spot if you aren’t specifically looking for it.If the code you’ve just written isn’t working like you expected, double check each
=
and==
. Remember:
=
sets a variable to a new value==
checks to see whether two values match
“STEVE” does not equal “Steve” #
Run the program and enter some variation on Steve, like STEVE
or steve
. You won’t see the message that you entered Steve and lost, why’s that?
When comparing two strings, the Lua equality operator == is case sensitive, and does not consider upper and lower case letters to be “the same”. Entering STEVE, steve, or sTeVe will cause the == operator to return false and the body of the if statement will not run.
Later we can improve the Don’t Steve game to prevent people from cheating by entering different capitalisations of Steve.
Check if the name is NOT Steve #
We already tell the player they’ve lost if the string they entered is “Steve”. Let’s make something happen if they enter something else.
For now we’ll just print a helpful message saying that they didn’t enter “Steve”, and later we can expand on that to increase their score.
print("Welcome to Don't Steve")
print("Enter a Name:")
what_they_entered = io.read()
print("You entered:", what_they_entered)
if what_they_entered == "Steve" then
print("You Steve'd! YOU LOSE")
end
if what_they_entered ~= "Steve" then
print("Good show, you didn't Steve")
end
Note the ~= operator vs. the == operator. Where == checks if two values are equal, ~= checks if two values are not equal. So if what_they_entered
does not equal “Steve”, we print a message saying so.
Otherwise… #
Right now we’re doing two checks which are a bit redundant, one check for if the string is “Steve” and another check for if it is not. As it turns out, once you know the answer to one of these questions, you already know the answer to both. Lua (and most programming languages) provide a simpler way to handle this situation
Roughly what we want to express is:
if A is true then do B otherwise do C
We can use an if … else statement for that, combining our two separate if statements into one:
if what_they_entered == "Steve" then
print("You Steve'd! YOU LOSE")
else
print("Good show, you didn't Steve")
end
else is equivalent to using otherwise in a sentence. If our condition is true, and what_they_entered
really does equal “Steve”, then the first print will run; otherwise (in all other cases) the second print will run.
Since the else is part of the if, there’s only a single end for the whole thing. An if statement might also be referred to as an if else statement, or if then else statement, based on the components that make up a typical usage.
Our program now looks like this:
print("Welcome to Don't Steve")
print("Enter a Name:")
what_they_entered = io.read()
print("You entered:", what_they_entered)
if what_they_entered == "Steve" then
print("You Steve'd! YOU LOSE")
else
print("Good show, you didn't Steve")
end
Run the program and enter a name, you should see the result of whether or not it was Steve.
Looping #
Right now our program asks for only one name and then exits. But we want to allow the player to keep entering names as long as they don’t enter Steve, at which point they lose and the game is over.
Often when writing a program, you’ll want to perform some specific action repeatedly. One of the most fundamental situations in a game is keep playing the game until the player quits. Or breaking that down into steps to be repeated: handle input, update the game, and then display the results, over and over, until the player quits.
Repeating is referred to as looping, and languages provide different types of loops for different kinds of situations.
A simple loop type, suitable for our main loop that runs the whole game, is a while loop, which looks like this:
while what_they_entered ~= "Steve" do
-- the rest of the game goes here
-- btw, these lines that start with -- are called "comments"
-- they are ignored by Lua, so they're useful for writing notes in your code
end
While what_they_entered
is not “Steve”, do the stuff contained in the while loop (between do and end).
Integrating this into our game so far, we get this:
print("Welcome to Don't Steve")
while what_they_entered ~= "Steve" do
print("Enter a Name:")
what_they_entered = io.read()
print("You entered:", what_they_entered)
if what_they_entered == "Steve" then
print("You Steve'd! YOU LOSE")
else
print("Good show, you didn't Steve")
end
end
We only need to print the welcome message once, so it goes first, outside the while loop. The rest of the game is now contained entirely inside the while loop.
Easily Indenting Text
while loops use a condition just like an if statement. If the condition is true, then the contents of the while loop run. If the condition is not true, the program skips to the end and continues.
The critical difference here – what makes a while loop a loop – is that if the condition is true and the while loop does run, it doesn’t just run once. When the program reaches the end of a while loop, it jumps back up to the beginning of the while and checks the condition again. Every time the loop reaches the end, it returns to the beginning and checks the condition, if the condition is true, then the code inside the loop runs again. If the condition is false, the program skips to the end of the while loop.
The condition I’ve used here is what_they_entered ~= "Steve"
if what_they_entered does not equal “Steve”
… then run the loop. If what_they_entered
ever does equal “Steve”, this condition will be false, and the while loop will skip to its end.
Keeping Score #
Since we’re writing a game and not a spellchecker, it makes sense to keep track of the player’s score so that we can display it to them at the end of the game.
For this we can declare a new variable score
.
We’ll start score
off at zero and increment it (increase it by one) each time they enter a name that isn’t Steve.
This line declares a new variable named score
and sets its initial value to 0 (zero):
score = 0
And this line sets score
to the result of score + 1
:
score = score + 1
If score
was 0
before this line, score + 1
will be 1
, and that value will be stored back into the score
variable with =
. If score was 1
to start with, the result would be 2
and so on.
Finally, we should display the score to the player at the end of the game:
print("Score:", score)
The initial variable declaration should only happen once, so it goes at the beginning of the program before the while
loop.
The line which increments the score we want to happen each time the player successfully enters a name that isn’t Steve, so it goes in the same place as the print
which tells the player they did a good job.
Last of all, the score should be printed once when the game ends, so the score line will go at the end of the program, after the end
of the while
loop.
Putting that all together, we get:
print("Welcome to Don't Steve")
score = 0
while what_they_entered ~= "Steve" do
print("Enter a Name:")
what_they_entered = io.read()
print("You entered:", what_they_entered)
if what_they_entered == "Steve" then
print("You Steve'd! YOU LOSE")
else
print("Good show, you didn't Steve")
score = score + 1
end
end
print("Score:", score)
Now when we play the game, we can see our score:
Variations of Steve #
So far we’ve only been checking if the name entered by the user matches Steve
exactly. If they enter STEVE
or steve
or sTeVe
then it won’t match our string, and we will award the player a point incorrectly.
Of course, maybe you want your version of Don’t Steve to be very specific about what not to enter, and you think that different capitalisations should score a point. This is a game design question, and it’s really up to you.
But for the sake of argument, let’s say that we want to prevent the player from entering any capitalization of Steve
.
Using elseif #
There’s one more keyword which can be used as part of an if statement, and that’s elseif. Here’s an example of how it works:
print("Welcome to Don't Steve")
score = 0
while what_they_entered ~= "Steve" do
print("Enter a Name:")
what_they_entered = io.read()
print("You entered:", what_they_entered)
if what_they_entered == "Steve" then
print("You Steve'd! YOU LOSE")
elseif what_they_entered == "STEVE" then
print("You Steve'd! YOU LOSE")
elseif what_they_entered == "steve" then
print("You Steve'd! YOU LOSE")
else
print("Good show, you didn't Steve")
score = score + 1
end
end
print("Score:", score)
If you want to check more than one option in an if statement, you can add elseif statements to check each one. Note like else, elseif can only be used as part of an if, it can’t be used just by itself.
if always comes first, then any number of elseif, and finally a single else. elseif and else are optional and can be left out if you don’t need them.
There’s a problem though… if you run this program and enter steve
or STEVE
, it will say that you’ve lost, but it keeps running. What gives? Remember that our while loop is also checking what_they_entered ~= "Steve"
, and this part of the program doesn’t deal with any other capitalizations.
It’s also a bit redundant, in general you want to avoid writing code that does the same thing in multiple places. Not only is it more typing, but is very likely to cause bugs, like when you make a change and update one place and forget to change the others.
Adding a game over variable #
Since we only want to check the name in one place, we can add a variable to store the result of that check. Then we can look at the variable any time we want to know what the result was, instead of doing the whole test all over again.
We’ll add a variable near the top named game_over
, and if the player enters Steve
or a variation, we’ll set it to true
. true
is a keyword (along with false
) that are used for keeping track of whether things are … well… true or false.
|
|
Several things changed here, so let’s break it down…
On line 4 we’ve added a new variable named game_over
and set it to false
, since the player hasn’t lost yet.
On line 6 we change the while loop which used to check for what_they_entered ~= "Steve"
(remember ~=
means not equal to), and instead now checks for not game_over
. The while loop will continue to run so long as it’s not game over, and if we set game_over
to true
, the while loop will exit.
Lines 12-16 used to print a message immediately, but now they simply set game_over
to true
. This will cause the while loop to stop, and we will also check game_over
in a few lines to decide what to print.
Lines 19-24 are now a second if statement, separate from the original one. This if checks if game_over
is true, and if so, tells the player they’ve lost. Otherwise it tells them they did a good job, and increases their score.
The code is getting better but we’re still only checking 3 different capitalizations of Steve. We could keep adding more, but there are 32 different ways of capitalizing Steve and that’s going to be a lot of typing.
If only there were a way to check for Steve no matter how it was capitalized…
Case insensitive comparisons #
When using ==
or ~=
to compare strings Lua will do a a case sensitve match, meaning the strings will only be considered the same if the capitalization is exactly the same in both. We want to do a case insensitive match, but Lua doesn’t provide one by default.
One way we can deal with this is to convert everything the player typed to lowercase letters, and then compare the result to steve
. This way, no matter what capitalization they enter on the keyboard, we only need to do one comparison to find out if it was some variation of Steve.
|
|
Line 11 adds a new variable called lowered_name
and we store the result of the standard Lua function string.lower()
.
string.lower()
takes the string that we passed in (the variable what_they_entered
), and returns a new string with all of the letters changed to lowercase.
Now that we know all of the letters are lowercase, we only need to do one comparison.
Line 12 compares the new variable lowered_name
to "steve"
(note we’ve changed the s
to lowercase here as well). If it matches, it’s game over.
Wrapping Up #
That’s as far as we’ll take Don’t Steve in this tutorial!
There are still things that could be improved, in particular if the user enters any spaces or punctuation before or after the name Steve then they’ll still score a point. We’ll cover solutions to this kind of problem later on.
It’s likely that certain things might not be making sense yet and that’s ok. I’ve intentionally glossed over various details in order to keep moving forward. Later chapters will continue to fill in details that were skipped here.
However, if you’re stuck with errors that you can’t fix, or any of the explanations or examples in this tutorial aren’t making sense or contain errors, please let me know.
Challenges #
Of course the best (and possibly only) way to really learn anything is to try applying it yourself. These are some changes that you can try making to the Don’t Steve program by yourself without instruction.
Everything you need to know has already been covered, but that doesn’t mean the solutions will be obvious.
- Add one or two more names that will also cause the player to lose:
Karen
,Chad
, etc. - Add a secret bonus name that increases the score by more than one point.
- Add a name that resets the score to
0
, but lets you keep playing instead of losing immediately. Make sure to print a message that lets the player know what happened.
Even if these sound easy and you think you can probably do them no problem, I’d really recommend actually trying to make these changes. You might run into unexpected complications which will need to be sorted out, that’s the fun part!
Next - Infinite Treasure