Learn how to program games with the LÖVE framework

Chapter 20 - Debugging

A bug is something that goes wrong in a program (or in our case game). Debugging is the art of fixing these bugs so that they don't occure anymore. As a programmer it's only natural to encounter lots of bugs, so debugging is a very valuable skill to have.

I wrote a short game.

function love.load()
    circle = {x = 100, y = 100}
    bullets = {}
end

function love.update(dt)
    for i,v in ipairs(bullets) do
        v.x = v.x + 400 * dt
    end
end

function love.draw()
    love.graphics.circle("fill", circle.x, circle.y, 50)

    for i,v in ipairs(bullets) do
        love.graphics.circle("fill", v.x, v.y, 10)
    end
end

function love.keypressed()
    if key == "space" then
        shoot()
    end
end

function shoot()
    table.insert(bullets, {circle.x, circle.y})
end

When you press space it will shoot a bullet. Or at least that's what is supposed to happen, but it doesn't work. I don't see any bullets appearing. Let's try to find out why that is.

Here are some of the reasons I can think of for why it's not working.

To figure out where it goes wrong we can use print. For example, let's use print inside the for-loop in love.update to check the x-position of the circle, and if it even gets there to begin with. Because if the bullets aren't spawning, the print will never be reached.

function love.update(dt)
    for i,v in ipairs(bullets) do
        v.x = v.x + 400 * dt
        print(v.x)
    end
end

Remember that you can add the following code at the top of your main.lua to have the output appear right away, and you don't need to close your game for it.

io.stdout:setvbuf("no")

Run the game and press space a few times to shoot. As you can see there are no prints in your output panel. Looks like we're not filling our bullets table. Let's make sure that we actually call the method shoot by putting a print there.

function shoot()
    table.insert(bullets, {circle.x, circle.y})

    -- Did you know that print takes infinite amount of arguments?
    print("How many bullets?", #bullets)
end

Try to shoot again and you'll see that we still don't see anything printed to your output panel. Weird, because our if-statement says if key == "space", and we're definitely pressing space. But to be sure, let's print the value of key. And who knows, maybe we spelled love.keypressed wrong and it doesn't reach that code at all.

function love.keypressed()
    -- Adding texts like these gives context to your print.
    -- This is especially useful when you have multiple prints.
    print("What is the key?", key)
    if key == "space" then
        shoot()
    end
end

When you try to shoot again you'll see that now there is something being printed. Looks like the value of key is nil. How is that possible, because LÖVE passes the key as first argument. But wait, we don't have a parameter called key. I forgot to add it when making the function. So let's fix that.

function love.keypressed(key)
    print("What is the key?", key)
    if key == "space" then
        shoot()
    end
end

Okay so now when you press space, it still doesn't shoot, but something else happens. You get an error.


Reading and fixing errors

An error occurs when the code is trying to execute something that isn't possible. For example, you can't multiply a number by a string. This will give you an error

print(100 * "test")

Another example is trying to call a function that doesn't exist.

doesNotExist()

In our bullet shooting game we get the following error:

So what exactly does the error tell us? Because a mistake that a lot of beginners make is that they don't realize that the error message tells you exactly why and where the error occurs.

main.lua:10:

This tells us the error occurs on line 10 (this may be a different line for you).

attempt to perform arithmetic on field 'x' (a nil value)

Arithmetic means a calculation, e.g. using +, -, *, etc. It tried to calculate using the field x, but x is a nil value.

So that's weird, because we give the table an x and y value, right? Well no. We add the values to the table, but we don't assign them to a field. Let's fix that.

function shoot()
    table.insert(bullets, {x = circle.x, y = circle.y})
end

And now it finally works. We can shoot bullets!

Let's look some new code where I create an circle class, and draw it a few times (you don't necessarily have to write along).

Object = require "classic"

Circle = Object:extend()

function Circle:new()
    self.x = 100
    self.y = 100
end

function Circle:draw(offset)
    love.graphics.circle("fill", self.x + offset, self.y, 25)
end

function love.load()
    circle = Circle()
end

function love.draw()
    circle:draw(10)
    circle:draw(70)
    circle.draw(140)
    circle:draw(210)
end

Upon running this I get the following error:

"Attempt to index" means it tried to find a property of something. So in this case it tried to find the property x on the variable self. But according to the error, self is a number value, so it can't do that. So how did this happen? We use a colon (:) for our function so that it automatically has self as first parameter, and we call the function with a colon so that it passes self as first argument. But aparently somewhere something went wrong. To know where it went wrong, we can use the Traceback.

The bottom part of the error tells us the "path" it took before reaching the error. It's read from down to top. You can ignore the xpcall line. The next line says main.lua:21: in function 'draw'. Interesting, let's take a look. Ah yes, I see. On line 21 I used a dot instead of a colon (circle.draw(140)). I changed it to a colon and now it works!


Syntax errors

A syntax error occurs when the game can't start to begin with, because something is wrong with the code. It tries to "read" the code but can't understand it. For example, remember how you can't have a variable name starting with a number? When you try to do so it will give you an error:

Take a look at the following code (again, no need to type along)

function love.load()
    timer = 0
    show = true
end

function love.update(dt)
    show = false
    timer = timer + dt

    if timer > 1 then
        if love.keyboard.isDown("space") then
            show = true
        end
    if timer > 2 then
        timer = 0
    end 
end

function love.draw()
    if show then
        love.graphics.rectangle("fill", 100, 100, 100, 100)
    end
end

It gives me the following error:

<eof> means end of file. It expects an end at the end of the file. Is that how we fix it, placing an end at the end of the file? Well no. Somewhere I messed up, and we need to fix it correctly. It says that it expects the end to close the function at line 6, so let's start from there and go down. After opening the function I start an if-statement, and then another if-statement. I close the second if-statement and start another if-statement. I close that if-statement as well and then close the outer if-statement. No, wait, that's not right. I should be closing the function at that point. I forgot to add an end somewhere in the function. Let me fix that.

function love.update(dt)
    show = false
    timer = timer + dt

    if timer > 1 then
        if love.keyboard.isDown("space") then
            show = true
        end
        if timer > 2 then
            timer = 0
        end
    end
end

And now it works. This is why indentation in your code is so important. It helps you see where you made a mistake like this one.

Another common mistake is one like the following:

function love.load()
    t = {}
    table.insert(t, {x = 100, y = 50)
end

Which gives me the follow error:

It's because I didn't close the curly brackets.

function love.load()
    t = {}
    table.insert(t, {x = 100, y = 50})
end

Asking for help

Now maybe you have a bug, and you can't fix it. You tried debugging it but you just can't figure out why it won't work, and you feel like you need help. Well then lucky for you there are plenty of people on the internet that are happy to help you. The best places to ask your question are on forums or in the Discord. But asking a question is not simply asking "Hey guys I got this bug where this happens, what do I do?". You need to provide them information that they can use to help you. For example:

But before you ask for help you may want to search for you question. It just might be that it's a common question and it has been answered multiple times.

And remember, no one is obligated to help you, and the ones that do all do it for free, so be kind :)


Rubber duck debugging

You could also get a rubber duck. There's this thing called rubber duck debugging. The idea is that when you explain what you're doing in your code, you oftend realize what you're doing wrong and fix the bug yourself. So instead of explaining it to someone, you explain it to your rubber duck. I have rubber duck myself, his name is Hendrik!


Summary

We can use print to find the source of our bug. Error message tell us exactly what is going wrong. Syntax errors are errors that occur because it can't "read" the code correctly. Indentation is useful because it prevents us from getting end of line-errors. We can ask for help on the internet but we should provide enough information on what is going on to make it less difficult on the people helping us.

Do you need help or do you see a mistake?
Leave a comment or edit this chapter.