Programming Games

Class 1 - Hello World of Games

Play homework

Colossal Cave Adventure

Mabel Addis - First video game writer

Topics

Description

This course teaches programming from the ground up in the context of Lua and LÖVE. It teaches basic computer science and software building skills along the way, but more importantly, teaches you how to teach yourself and find out how to go about solving a problem or building a solution. Tools come and go, so the goal is to teach things of value with less focus on the programming language and other tools used to build the software.

This first session will help ground the course and get you situated into the learning environment and get your feet wet while writing your first code.

The goal of this week is to teach the most necessary building blocks of programming. By the end you will be be able to build basic programs which we will apply with exercises in the following weeks.

Class notes

Interactive coding

What’s a REPL?

Programming doesn’t take much effort beyond loading up a REPL and just typing. What is a REPL? It’s an interactive window you can type code into and it spits out the results on screen when you hit enter. It stands for Read-Evaluate-Print-Loop. These are the 4 things the REPL does:

  1. Read the code that was just typed
  2. Evaluate, or process the code down into a result
  3. Print, or spit out the result
  4. Loop… do everything again and again until the programmer is done

It’s actually simpler than it sounds. Let’s go to a website with a REPL and try it out: https://repl.it/languages/Lua

You will see two window panes on the website: a light side on the left and dark side on the right. The right-side is the REPL and is what we’re interested in for now. It has a lot of information that isn’t necessarily useful to us at the moment. Something similar to this:

Lua 5.1  Copyright (C) 1994-2006 Lua.org, PUC-Rio
[GCC 4.2.1 (LLVM, Emscripten 1.5)] on linux2

This is just telling you what programming language this REPL is loading, in this case, Lua. If you click inside the window pane and start typing you will see your text appear.

Let’s try typing some code for the REPL to Read. You already know some code if you know arithmetic. Type:

2 + 2

Then hit ENTER and immediately the REPL will Print out:

=> 4

A lot happened very quickly. After hitting ENTER, the REPL, Read the line 2 + 2, it Evaluated the value of that statement to be 4, it Printed 4 on the screen for you, then Looped back to a new line to await your next command. Try out some more arithmetic. Multiplication:

2 * 3

Subtraction:

2 + 2 - 4

Division:

6 / 2

You can use parenthesis to tell it which order to do the operations:

(2 + 2) * (3 + 1)

Which gives different results than:

2 + 2 * 3 + 1

If you give the REPL a single number:

12

It will give you back 12, because this can’t be simplified down any further.

You can also do exponents using the ^ (caret) symbol:

2^4

Numbers are a type of data, and +, -, /, *, ^, % are operators. Statements such as 2 - 2 and 23 * 19 are all operations.

One last arithmetic operation we’ll cover is modulo, which is done with the modulus operator. The modulus operator is represented in most languages as a % (percent) symbol:

8 % 3

Modulus operations are quite common in software and computer sciences. The way it works is you take the 2nd number and subtract it from the bigger number as many times as possible until the 2nd number is bigger than the 1st. The result is what’s left of the 1st number. With 8 % 3, if you keep subtracting 3 from 8 then you end up with 2 left.

A real world example is time elapsing on an analog clock. Imagine the face of a clock with the hour hand on noon. If 25 hours pass then the hour hand goes all the way around twice and ends on 1. That would be equivalent to writing:

25 % 12
=> 1

The hour hand resets every time it passes 12, so 13 % 12, 25 % 12, and 37 % 12 would all equal 1. Likewise, 10 % 4 results in 2 because 4 goes into 10 twice, and leaves a remainder of 2.

Exercises

Strings

Numbers are one type of data that can be operated on. Let’s explore another data type within the REPL. Take a set of quotes and put some text in it and hit ENTER:

"hello"

The REPL will print hello back to you. This is a string. A string is a set of characters (letters and symbols) stringed together as one single piece of data. This string is made of 9 characters:

"H-E-L-L-O"

Like numbers, there are operators to make strings play with each other. The concatenate operator (..) combines strings together:

"hello" .. "world"

What’s the result? Notice that the resulting string has no space between the two words. If you wanted a space, you would have to put one in the quotes to be part of the operation:

"hello " .. "world"

You could even make a separate string with the space in it:

"hello" .. " " .. "world"

Strings can have any characters in them that you want.

"abc" .. "123"
"Япо́нский" .. "ロシア語!!"

Exercises

Nil and variables

Data, or the lack thereof

Humans have different ways of representing a lack of data. If there are no sheep to count then we have zero sheep. If there are no words on a page then the page is blank. In a computer we may represent the number of sheep as 0 or the missing words on a page as an empty "". These are still data though… a number and a string. In software when you want to represent a lack of data we have:

nil

Sometimes called null or undefined data in other languages. It’s seemingly useless. You can’t use operators on nil.

nil + nil

This will print an error like it did when you tried doing arithmetic on strings. Let’s take a look at variables and we’ll discover the purpose of nil.

Variables

Sometimes you want to write out data, but you want that data to be easy to change. Variables let you give data a name to reference. Here’s an example to try:

name = "Mandy"
"hello my name is " .. name

Since you told it what name is, it knows what value to add to the string "hello my name is ". If you type:

name

…and hit ENTER, it will print out the value that belongs to this variable to remind you. The = (equal) sign tells Lua that you want to assign a value to the given name/variable. You can change the value of a variable and get different results:

name = "Jeff"
"hello my name is " .. name

Assignment isn’t the same as it is in Algebra. You can change the value of a variable multiple times. We can tell name that it equals itself with some additional information concatenated to it:

name = "abc"
name = name .. "def"
name

You can assign any type of data to a variable, including numbers:

name = "Jeff"
age = 16
"hello my name is " .. name .. " and I am " .. age .. "."

You can change numbers after assignment too:

age = 16
age = age * 2
"my age doubled is " .. age

So, what if you type in a made up variable name?

noname

You will see it has nil, or no data yet. If you try to use nil in your string operation you will get an error:

"hello my name is " .. nil
[string "return "hello my name is " .. nil"]:1: attempt to concatenate a nil value
"hello my name is " .. noname
[string "return "hello my name is " .. noname"]:1: attempt to concatenate global 'noname' (a nil value)

Try assigning a value to a variable name:

best_color = "purple"

then assigning that variable data to another:

worst_color = best_color
worst_color

You’ll see that both variables now have the value "purple".

Variables can have names made up of letters, numbers and underscores (_). Variable names cannot begin with a number though, otherwise it will think you’re trying to type in number data. Here’s some examples of valid variables:

my_dog = "Poe"
myDog = "Zia"
DOG3 = "Ember"

Exercises

Using functions

Most programming languages come with some variables already defined for us. Lua has many, so let’s type one in and hit ENTER to see what the value is:

string.reverse
=> function: 0x2381b60

Oh my. So “function” is another data type, but what is 0x2381b60? It’s just telling you where in the computer’s memory that function exists, just in case you wanted to know. Functions work very differently than numbers in strings. Essentially functions are pre-defined instructions that tell the program how to do different things. They take data and return back different data. Let’s see how to give this function data:

string.reverse("hello")
=> olleh

At the end of the function’s variable name, string.reverse, we type a set of parenthesis, string.reverse(), and put inside the parenthesis some data we want changed (string.reverse("hello")). Making the function run is often called invoking the function. Having a function that reverses text in a string for us can be useful, and we can capture the return value (the results) of the function using a variable. Try it out:

greeting = "hello, how are you?"
backwards_greeting = string.reverse(greeting)
backwards_greeting
=> ?uoy era woh ,olleh

It should be obvious from the name what that function’s purpose is. How about this one?

string.upper("hello, how are you?")

Now try capturing that value by assigning it to a variable:

greeting = "hello, how are you?"
shouting_greeting = string.upper(greeting)
crazy_greeting = string.reverse(shouting_greeting)

We can get crazier. How about invoking a function when invoking another function??

string.reverse(string.upper("hey"))

What’s happening here is the string is being uppercased by string.upper but then the value from string.upper is being reversed by string.reverse as soon as it is done. It’s just like in arithmetic when you have nested parenthesis. The inner-most parenthesis are resolved before doing the outer-most parenthesis.

Let’s try one more function. This function has two parameters, meaning it accepts two pieces of data which it requires to work properly.

math.max(7, 10)

When giving more than one piece of data to a function, you need to put a comma (,) between the parameters

These are great functions, but wouldn’t it be great if we could make our own? We’ll give it a shot in just a few pages.

Exercises

Comments in code

Sometimes we might want to write a comment in our code– an explanation to a friend or our future selves on what the purpose of some code is. Perhaps we want to write a note to ourselves to change something later. Comments work very similarly in different languages so they’re pretty easy to read even if you don’t understand the programming language or the code itself. Lua denotes a comment as -- and any text that follows it:

1 + 1
-- This is a code comment
1 + 2
-- This is another line of comments
3 + 4

These comments will be completely ignored by the computer and are meant for the human to read. Comments can also be on the same line as code. The computer will just ignore the rest of the line when it sees a comment starting.

1 + 1 -- This is my comment. This code adds some numbers together in case you didn't know!

You will see comments appear in future example code, so don’t let it surprise you!

Scripting and printing

Looking back at the website, (you bookmarked it, right?) we have been using the REPL window pane on the right, but haven’t talked about the pane on the left. This window is just a text editor. Instead of running the program with each line you type, it allows you to write multiple lines of code before executing it all. Let’s try typing something in it. Once you are done typing all the code you can click the “Run” button.

number = 4
number = number + 1

But when you click run, nothing happens. So let’s provide another statement to our program.

number = 4
number = number + 1
print(number)

Now when you click Run, the text 5 appears in the right-hand pane. When you told it to run, it read and evaluated each line of the code in sequence.

You can print any type of data, not just numbers:

print("hello")

Remember those other functions we used before? You can write those inside of a print statement.

print(string.reverse("hello"))

We can even print functions themselves:

print(string.reverse)

And see a memory location of where that function exists:

function: 0x1795320

This can serve as a unique indentity for that function, which we’ll see more of in a later page.

Lua provides this print function to allow us to poke around while our program is running. We can print as many things as we want.

print("hello")
print("world")

Exercises

Scripting and printing

Looking back at the website, (you bookmarked it, right?) we have been using the REPL window pane on the right, but haven’t talked about the pane on the left. This window is just a text editor. Instead of running the program with each line you type, it allows you to write multiple lines of code before executing it all. Let’s try typing something in it. Once you are done typing all the code you can click the “Run” button.

number = 4
number = number + 1

But when you click run, nothing happens. So let’s provide another statement to our program.

number = 4
number = number + 1
print(number)

Now when you click Run, the text 5 appears in the right-hand pane. When you told it to run, it read and evaluated each line of the code in sequence.

You can print any type of data, not just numbers:

print("hello")

Remember those other functions we used before? You can write those inside of a print statement.

print(string.reverse("hello"))

We can even print functions themselves:

print(string.reverse)

And see a memory location of where that function exists:

function: 0x1795320

This can serve as a unique indentity for that function, which we’ll see more of in a later page.

Lua provides this print function to allow us to poke around while our program is running. We can print as many things as we want.

print("hello")
print("world")

Exercises

Making functions

Functions are the third data type we’ve seen. We’ve accessed some variables where functions were defined for us and had a blast using them (I know I did). Functions are the building blocks of software. You can compose them then snap them together like Danish plastic blocks. It takes time to understand how they work and much longer to master their inner power. So without further ado, let’s see what they actually look like:

function()
  return 4 + 4
end

Type it out in the text editor window and let us break this down line by line and word for word. Whenever we type function() we are beginning a new function. The 2nd line is the body of our function where things happen. The body of the function can be many lines long. The body of the function could also be empty (but that’s not very useful). On the last line of the function body we write return which tells our function to stop running and to “return” data back to the main program. Then on the third line, we’re telling the computer we’re done writing our function. In order to use this example function, we should probably use a variable to give it a name:

add = function()
  return 4 + 4
end

The first bit should be understandable. We declared a variable called add, then we assigned some data to it on the right of the equal sign. In this case, our function. Now it is ready to use.

add = function()
  return 4 + 4
end

result = add()
print(result)
8

We’ve made our very own function with our very own name for it and even invoked it and got back data! If you instead got an error message, double check what you typed that nothing is missing. Error messages give you a line number of where to find the error that crashed the program.

Take a look for a minute at how we invoked our function:

add()

We typed out the variable name that our function is assigned to, followed by some parenthesis. In those parenthesis is the data that we passed into our function… wait a minute the parenthesis are empty. We didn’t pass any data into our function. Whenever we called those other functions we passed in data, like when we passed "hello" into string.reverse("hello"). What if we modify our line where we invoke our function and give it some data?

add = function()
  return 4 + 4
end

result = add(16)
print(result)

It seems it always returns 8 no matter what arguments we try to pass in. We need to rewind to the first line of our function and take a close look at this bit:

add = function()

The () at the end of function() is where we tell our program how many arguments we are accepting. If the parenthesis are empty, then our function is ignoring all arguments and will likely always return the same result. Let’s tweak the function slightly and give it one parameter with the name a. Let’s also tweak the second line while we’re at it:

add = function(a)
  return a + 4
end

result = add(16)
print(result)
20

Now when we pass in different numbers, we get different results:

add = function(a)
  return a + 4
end

print(add(16))
print(add(12))

To complete this function, let’s give it a second parameter of b and modify the return statement in the function body:

add = function(a, b)
  return a + b
end

print(add(16))
print(add(12))

If we try and run the code now, we’ll get another error:

[string "add = function(a, b)..."]:2: attempt to perform arithmetic on local 'b' (a nil value)

Let’s read this error carefully. It is saying inside the square brackets that an error occurred when using the function we defined (add = function(a, b)...). To the right of the square brackets it is saying line 2 (:2) of our text is the particular location of the crash. To the right of the line number is what happened that made it crash. It tried to perform addition with a + b but the value of b was nil. We stated that our function requires two parameters now, a and b, and our program will crash if we try and invoke the function with only one parameter. Let’s modify the lines where we invoke the function to give it two arguments each time we invoke it:

add = function(a, b)
  return a + b
end

print(add(16, 10))
print(add(12, 2))

Great, everything is working again! With the experience of our first, fully-functional function, we can now start treading the waters of this great world.

Exercises

Booleans

Data types are like elements on the periodic table. The more elements you have the more chemicals can create. Luckily there aren’t as many data types as there are elements. In fact we’ve learned almost all of them. There are only two possible booleans:

true

and

false

That’s right. And you can assign them to variables just like numbers, strings, nil, and functions:

myboolean = true
print(myboolean)

The cool thing with numbers and strings is you can use them to create statements that can be evaluated as true or false. Let me give an example by introducing some new operators. Try these out in the REPL:

5 > 3
=> true
5 < 3
=> false

“5 is greater than 3” is a true statement so it returns a true boolean. Naturally, “5 is less than 3” is a false statement and returns false. We can check to see if two numbers are equal in value:

number = 5
number == 5
=> true

By using a double equal (==) we can compare the equality of two numbers. This also works for strings:

"hello" == "hello"
=> true
"hello" == "HELLO"
=> false

For strings, often time you will see single quotes ' ' (apostrophe) used instead of regular quotes (sometimes called double quotes) wrapper around the text. Lua doesn’t care as long as the text inside both strings are identical. We can prove that with an equality check:

'hello' == "hello"
=> true

Anyways, you can also do the inverse of an equality check and check for inequality (if two things are not equal):

5 ~= 3
=> true
"HELLO" ~= string.upper("hello")
=> false

Now let’s dig in a little deeper with two more operators. First is the and operator:

3 < 4 and 4 < 5
=> true

This reads out almost as plain English. 3 is less than 4 and 4 is less than 5. This is a logically sound statement so it evaluates to true. Just to be clear on what’s actually going on here though, let’s break it down. What we said is being grouped into 3 separate operations:

(3 < 4) and (4 < 5)

The two sets of parenthesis are evaluated first and internally the computer breaks these two operations down to:

(true) and (true)

True and true are both true. This sounds silly, but it is indeed logically sound. Let’s try one more just to get the hang of it:

"hello" == "hello" and 6 > 10

Finally, let’s try one more operator to put a bow on things. Sometimes we don’t care that both operations are correct. We only care if one or the other is correct.

4 == 10 or 4 ~= 10
=> true
1 > 100 or 12 == 12 or "hello" == "bananas"
=> true

As long as one of the operations is correct, the entire statement is logically true. With the introduction of true and false we’ve brought in a lot of new operators: “greater than” (>), “less than” (<), “equal” (==), “not equal” (~=), “and” (and), and “or” (or).

Trivia

Booleans get their name from George Boole who invented boolean algebra, which we’ve just seen a little bit of.

Exercises

Flow control

Typically the computer starts at the top of our script and reads each line down in a sequence. We make the programs jump around with functions in the mix Try this out in the text editor:

print("I'm called 1st")

add = function(a, b)
  print("I'm called 5th")
  return a + b
end

subtract = function(a, b)
  print("I'm called 3rd")
  return a - b
end

print("I'm called 2nd")

subtract(16, 10)

print("I'm called 4th")

add(12, 2)

We have a function that is saved to the variable add but it isn’t invoked until further down in the code. So in a sense our program has worked its way down the page then jumped back up to the function and worked its way through the body of the function then picked back up where it was before. In a similar fashion, we can make our program take one path or another depending if the data is true or false.

noise = function(animal)
  if (animal == "dog") then return "woof" end
  return ""
end

print(noise("dog"))
print(noise("rabbit"))

Let’s analyze this function line by line. The function is called noise and takes an animal name (string) as a parameter. On the next line it says if “animal is dog” is true then return something special. We put an end at the end of our statement to make it obvious to the computer. If the statement was false, then "woof" does not get returned. Instead an empty string ("") gets returned. When we invoke the function with the argument “dog” then we get back “woof!”. With “rabbit” we get back silence. Maybe the rabbit doesn’t want the dog to hear where she is. Let’s make our function more versatile by adding more animals:

noise = function(animal)
  if animal == "dog" or animal == "wolf" then return "woof" end
  if animal == "cat" then return "meow" end
  return ""
end

print(noise("dog"))
print(noise("cat"))
print(noise("rabbit"))
print(noise("wolf"))

We have branching paths happening within our function. If we were to map out these branches it may look something like:

 |
 +--> "woof"
 +--> "meow"
 |
 +--> ""

There’s no requirement that a statement has to be all written out on one line. Sometimes when doing multiple things inside an if statement we may want to put it on multiple lines:

if my_age > 17 then
  print("You're an adult!")
  print("Get a job!")
end

Similar to functions having bodies, everything between then and end is considered the body of the if statement. Sometimes it is necessary for our branches to have forks within them. Let’s say our function takes a language as a second, optional parameter:

noise = function(animal, language)
  if animal == "dog" or animal == "wolf" then return "woof" end
  if animal == "cat" then return "meow" end
  if animal == "bird" then
    if language == "spanish" then return "pío" end
    return "tweet"
  end
  return ""
end

print(noise("dog"))
print(noise("rabbit"))
print(noise("bird"))
print(noise("bird", "spanish"))

The if statement for checking if the animal is a bird is 4 lines long. Once we find out that the animal is a bird, while still in the body of the if statement, we stop to check and see if the language is set to Spanish. If it is, we end up inside an if statement within an if statement! Otherwise we’ll return "tweet" if the language isn’t Spanish. Maybe mapping out the paths will clear things up:

 |
 +--> "woof"
 +--> "meow"
 +-----> "pío"
 |   |
 |   +--> "tweet"
 |
 +--> ""

Our code can get unreadable very quickly if we start nesting if statements inside each other. Fortunately doing so isn’t usually necessary.

Let’s talk about another aspect of if statements. Suppose I have two branches of code that are opposite of each other:

if daytime == true then
  thermostat = 71
end
if daytime == false then
  thermostat = 68
end

Rather than writing this out as two if statements and checking the value of daytime twice, I can take advantage of the keyword else:

if daytime == true then
  thermostat = 71
else
  thermostat = 68
end

That way if daytime is not true, it will default to the second branch. You could read this off almost like a sentence: “If daytime is true then set the thermostat to 71, otherwise set the thermostat to 68.” Not having to check things twice when doing computations saves us time and makes our program run more efficiently. Since daytime is a boolean in this case, we don’t need to check if it is true or false. We can just pass it to the if statement to be checked for true/false and make our operation even simpler.

if daytime then
  thermostat = 71
else
  thermostat = 68
end

Better. “If daytime then set thermostat to 71, otherwise set thermostat to 68.” There’s one more feature of if statements we should discuss. If there is another condition you need to check, maybe several more, you can use the elseif keyword. It looks something like this:

color = "green"

if color == "blue" then
  print("That's my favorite color!")
elseif color == "green" then
  print("Very subtle choice. I like it.")
elseif color == "pink" then
  print("Nice, bold choice.")
else
  print("I don't think that color would match your shoes.")
end

Try it out!

Exercises

While

Another way to check conditions is with the while keyword.

while 1 + 1 == 2 do
  print("My math is correct!")
end

While a condition is true, the body (everything between the do and end) will be run repeatedly and not stop. So if you tried to run that bit of code, your screen probably went crazy printing over and over in a never-ending loop. We need to make sure the condition can get changed so we’re not stuck in a never-ending loop. Let’s write a loop we can escape out of.

boolean = true

-- This condition will get checked twice. The first time it
-- is checked it will be true and the body of the while-loop
-- will be run. The second time the condition is checked,
-- our boolean will be false and the while-loop won't be run again!
while boolean do
  print("Switching boolean to false.")
  boolean = false
  print("Boolean has been set to false.")
end

print("We made it out of the loop!")

Understanding that we can change the while condition from inside the body of the loop, we have the power to write programs that end exactly when we want them to. Can you guess what this will do when we run it?

countdown = 10

while countdown > 1 do
  print(countdown .. "...")
  -- This line is critical to make our number shrink.
  countdown = countdown - 1
end

print("Blast off!")

…And remember to use a > and not a <, or your loop may never run.

Exercise

Type checking

Lua doesn’t care what type of data a variable has.

data = 12
data = "hello"
data = true

To this end, we can use the type function to check what kind of data a variable is holding.

type(data)
=> boolean

We can check the type of function:

type(string.reverse)
type(type)

We can also use it to check what type of data a function is returning back to us:

type(string.reverse("hello"))
=> string
type(type(12))
=> string

Converting data types

We’ve already seen data type conversion previously when we took numbers and an operation in, transforming that into a true or false statement.

type(12 > 3)
=> boolean

There are also ways to convert between numbers and strings using tonumber and tostring.

number = tonumber("24")
print(type(number))
string = tostring(number)
print(type(string))
number
string

Interesting but maybe less useful, you can convert other data types to string:

print(tostring("already a string"))
print(tostring(true))
print(tostring(nil))
print(tostring(tostring))

Exercises

First game

Let’s learn about a few new functions and then we’ll be able to write our first game!

Reading input

Not only can our program print out data, but using the function io.read it can take data too. This function doesn’t need any arguments because it will prompt us in the window on the right for us to type in data.

print("Enter your name:")
name = io.read()

print("Your name is " .. name .. ".")

After you click “Run”, the program will pause when it runs io.read(). Type your name and hit ENTER and look, the program prints back out the name you gave it. Notice the last print statement. We combined the name with two other strings to form a sentence. You can prompt the user multiple times if you need to get additional information:

print("Enter your name:")
name = io.read()

print("What's your favorite food?")
food = io.read()

print("Your name is " .. name .. " and your favorite food is " .. food .. ".")

One limitation with doing this is the data will always come in as a string:

print("What's your favorite number?")
data = io.read()

print(type(data))
string

In the last section we talked about converting data between different types. If we wanted to find out whether your favorite number is odd or even, we would need to convert it to an actual number to perform operations on it. Type this in your text editor and run it:

print("What's your favorite number?")
data = io.read()
number = tonumber(data)

-- If the user gave us an answer that isn't a
-- number, then the value of "number" is nil.
if number == nil then
  print("Invalid number.")
elseif number % 2 == 0 then
  print("Your number is even.")
else
  print("Your number is odd.")
end

Random number

Many languages give us access to a random number generator. Randomness is how we generate secure passwords and keys in the real world. To generate a random number in Lua, we use math.random:

math.random(100)
=> 63

This generates a random number between 1 and 100. Except, if you run the program repeatedly you may notice that it spits out the same number. That’s because nothing in the computer world is random. If we fed in random noises through a speaker or white noise from an old television set then our computer could use this to generate random numbers. Since we don’t easily have access to those things, we need to seed Lua with some perceived randomness.

If we run os.time we will get the computer’s current time in integer form:

os.time()
=> 1.529098167e+09

This number is hard enough to guess that it will work as a seed for our program. Let’s take the system time and feed it in using math.randomseed then from there, Lua will be able to generate a “random” number in the range we want (1-100).

seed_number = os.time()
math.randomseed(seed_number)
print(math.random(100))
=> 19

Success! It is generated different numbers each time we run it, with no pattern.

Putting it all together

I should probably explain what this game is. It’s quite simple. We want the computer to make up a number and the user has to guess what the number is. If they’re wrong, then we should give them a hint and make them guess again. We can take advantage of the while loop to make them continue guessing while their guess is incorrect.

-- The computer's secret number
math.randomseed(os.time())
number = math.random(100)

print("Guess my secret number. It is between 1 and 100.")

guess = tonumber(io.read())

-- While the user's guess is not equal to
-- the number, repeat the body of the loop.
while guess ~= number do
  -- Give them some hints
  if guess > number then
    print("Your guess is too high.")
  end
  if guess < number then
    print("Your guess is too low.")
  end

  -- Make them guess again and again until they get it
  print("Guess again:")
  guess = tonumber(io.read())
end

-- Winning message
print("You guessed correctly! The number was " .. number .. ".")

Let’s re-factor one bit of this code to make it easier to read. When we talked about if statements, remember the keyword else?

-- The computer's secret number
math.randomseed(os.time())
number = math.random(100)

print("Guess my secret number. It is between 1 and 100.")

guess = tonumber(io.read())

-- While the user's guess is not equal to
-- the number, repeat the body of the loop.
while guess ~= number do
  -- Give them some hints
  if guess > number then
    print("Your guess is too high.")
  else
    print("Your guess is too low.")
  end

  -- Make them guess again and again until they get it
  print("Guess again:")
  guess = tonumber(io.read())
end

-- Winning message
print("You guessed correctly! The number was " .. number .. ".")

Now that things are cleaner, let’s add one feature to our program. It would be more fun if the game kept track of how many guesses we made so we could give them a special message. Let’s create a variable called guess_count that will start at 1 and increment every time the user makes another guess. We’ll also go ahead and add some messages to the bottom to praise the user if they did it in a reasonable number of guesses.

-- The computer's secret number
math.randomseed(os.time())
number = math.random(100)
-- Our starting number of guesses
guess_counter = 1

print("Guess my secret number. It is between 1 and 100.")

guess = tonumber(io.read())

-- While the user's guess is not equal to
-- the number, repeat the body of the loop.
while guess ~= number do
  -- Increment the guess counter by 1
  guess_counter = guess_counter + 1

  -- Give them some hints
  if guess > number then
    print("Your guess is too high.")
  else
    print("Your guess is too low.")
  end

  -- Make them guess again and again until they get it
  print("Guess again:")
  guess = tonumber(io.read())
end

-- Winning messages
print("You guessed correctly! The number was " .. number .. ".")

if guess_counter <= 5 then
  print("Amazing! It only took you " .. guess_counter .. " tries.")
else
  print("It took you " .. guess_counter .. " tries. Not bad.")
end

Exercises

Code homework

For homework, you will code a text-based adventure game in Lua. Your game must have at least 7 rooms and at least 3 items. Each room should have a text description and describe its exits.

Credits