[HOWTO]Debug code

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
User avatar
Zer0
Citizen
Posts: 59
Joined: Sat Oct 06, 2012 9:55 am
Location: Sweden
Contact:

[HOWTO]Debug code

Post by Zer0 »

We all had bugs, but as you get better the less you will create.
This guide won't help you avoid bugs, but it will tell you how to locate and fix them.

Let's begin by having a look at the things we can use to find the source of bugs.
error traceback - This is a pointer as to where the problem occured. (even if it can point you in the wrong direction)
print - This will print one or more values and/or notes to the console
logic - The most important tool there is

let's create an example with bugs and have a look at how to fix it.

Code: Select all

x, y = 400,300
function love.update(dt)
	if love.keyboard.isDown("w") then
		new_y = y - dt * 40
	elseif love.keyboard.isDown("s") then
		new_y = y + dt * 40
	elseif love.keyboard.isDown("a") then
		new_x = x - dt * 40
	elseif love.keyboard.isDown("d") then
		new_x = x + dt * 40
	end
	x = new_x
	y = new_y
end

function love.draw()
	love.graphics.circle("line",x,y,40)
end
This is one bug-filled code, but may seem perfectly fine to people with less experience.
Let's run it and see the error traceback.

Code: Select all

Error: main.lua:17: bad argument #2 to 'circle' (number expected, got nil)
stack traceback:
        [C]: in function 'circle'
        main.lua:17: in function 'draw'
        [string "boot.lua"]:438: in function <[string "boot.lua"]:399>
        [C]: in function 'xpcall'
The error was on line 17 according to the error traceback, and that is not entirely true.
Let's see what we can figure out if we print it to the console.

First make sure the console is enabled in [wiki]conf.lua[/wiki].
That file have alot of fun things to play around with, the one you should enable for now is the t.console.
Now that we can print values to the console, let's add a print statement right before the error.

Code: Select all

x, y = 400,300
function love.update(dt)
	if love.keyboard.isDown("w") then
		new_y = y - dt * 40
	elseif love.keyboard.isDown("s") then
		new_y = y + dt * 40
	elseif love.keyboard.isDown("a") then
		new_x = x - dt * 40
	elseif love.keyboard.isDown("d") then
		new_x = x + dt * 40
	end
	x = new_x
	y = new_y
end

function love.draw()
	print(x, y) -- New line
	love.graphics.circle("line", x, y, 40)
end
Let's look at the console output again

Code: Select all

nil     nil
Error: main.lua:19: bad argument #2 to 'circle' (number expected, got nil)
stack traceback:
        [C]: in function 'circle'
        main.lua:19: in function 'draw'
        [string "boot.lua"]:438: in function <[string "boot.lua"]:399>
        [C]: in function 'xpcall'
We now know that both values are nil and we should look at places where we set the values.
So let's add some more print statements

Code: Select all

x, y = 400,300
function love.update(dt)
	if love.keyboard.isDown("w") then
		new_y = y - dt * 40
	elseif love.keyboard.isDown("s") then
		new_y = y + dt * 40
	elseif love.keyboard.isDown("a") then
		new_x = x - dt * 40
	elseif love.keyboard.isDown("d") then
		new_x = x + dt * 40
	end
	print("update", x, y, new_x, new_y) -- We print all 4 values and "update" so we know it was the print in love.update
	x = new_x
	y = new_y
end

function love.draw()
	print("draw", x, y) -- We add the line "draw" before the values to ensure that this is the print in love.draw
	love.graphics.circle("line", x, y, 40)
end
Let's look at the new console output.

Code: Select all

update  400     300     nil     nil
draw    nil     nil
Error: main.lua:20: bad argument #2 to 'circle' (number expected, got nil)
stack traceback:
        [C]: in function 'circle'
        main.lua:20: in function 'draw'
        [string "boot.lua"]:438: in function <[string "boot.lua"]:399>
        [C]: in function 'xpcall'
It looks like 'x' and 'y' is not nil in love.update but it looks like 'new_x' and 'new_y' is.
Now that we have located the source of the problem, it should be quite easy to solve.

Code: Select all

x, y = 400,300
function love.update(dt)
	if love.keyboard.isDown("w") then
		new_y = y - dt * 40
	elseif love.keyboard.isDown("s") then
		new_y = y + dt * 40
	elseif love.keyboard.isDown("a") then
		new_x = x - dt * 40
	elseif love.keyboard.isDown("d") then
		new_x = x + dt * 40
	end
	print("update", x, y, new_x, new_y) -- We print all 4 values and "update" so we know it was the print in love.update
	x = new_x or x -- With the 'or' statement we ensure that x is unchanged in the cases that new_x is nil
	y = new_y or y
end

function love.draw()
	print("draw", x, y) -- We add the line "draw" before the values to ensure that this is the print in love.draw
	love.graphics.circle("line", x, y, 40)
end
This would make sure 'x' and 'y' is never set to nil in the update function.
Let's run the example again just to be sure.

Looks like you have a circle moving around with the WASD keys now, a nice sign that it's working.
If you look at the console now you will see it filled with information that is no longer useful, just comment the print statements out if you think you may need them later or just remove them.

Code: Select all

x, y = 400,300
function love.update(dt)
	if love.keyboard.isDown("w") then
		new_y = y - dt * 40
	elseif love.keyboard.isDown("s") then
		new_y = y + dt * 40
	elseif love.keyboard.isDown("a") then
		new_x = x - dt * 40
	elseif love.keyboard.isDown("d") then
		new_x = x + dt * 40
	end
	--print("update", x, y, new_x, new_y) -- We print all 4 values and "update" so we know it was the print in love.update
	x = new_x or x -- With the 'or' statement we ensure that x is unchanged in the cases that new_x is nil
	y = new_y or y
end

function love.draw()
	--print("draw", x, y) -- We add the line "draw" before the values to ensure that this is the print in love.draw
	love.graphics.circle("line", x, y, 40)
end
But let's have a look at another type of bug now, one that does not give you an error message.
Start the example again and see if you can move up and left at the same time... That's just as I suspected, you can't.
Now we use the final tool, logic, we place the print statements where we think the source of the bug is.
Now let's place some new print statements for this bug.

Code: Select all

x, y = 400,300
function love.update(dt)
	if love.keyboard.isDown("w") then
		print("up") -- New
		new_y = y - dt * 40
	elseif love.keyboard.isDown("s") then
		print("down") -- New
		new_y = y + dt * 40
	elseif love.keyboard.isDown("a") then
		print("left") -- New
		new_x = x - dt * 40
	elseif love.keyboard.isDown("d") then
		print("right") -- New
		new_x = x + dt * 40
	end
	--print("update", x, y, new_x, new_y)
	x = new_x or x
	y = new_y or y
end

function love.draw()
	--print("draw", x, y)
	love.graphics.circle("line", x, y, 40)
end
If you run this example you may notice some things, and if you have some idea of how programming works you should be a step ahead and see what the problem is, but it may not always be this easy.
Let's pretend we press both W and A but not S and D, and suddenly THIS,

Code: Select all

	if love.keyboard.isDown("w") then
		print("up")
		new_y = y - dt * 40
	elseif love.keyboard.isDown("s") then
		print("down")
		new_y = y + dt * 40
	elseif love.keyboard.isDown("a") then
		print("left")
		new_x = x - dt * 40
	elseif love.keyboard.isDown("d") then
		print("right")
		new_x = x + dt * 40
	end
Turn into THIS,

Code: Select all

	if true then
		print("up")
		new_y = y - dt * 40
	elseif false then
		print("down")
		new_y = y + dt * 40
	elseif true then
		print("left")
		new_x = x - dt * 40
	elseif false then
		print("right")
		new_x = x + dt * 40
	end
You should know how if and elseif statements work, if not look at some tutorials on lua and re-read this guide once you have a better understanding of the language in general.
Now we see that it stops at the if and the elseif statement is never even checked.
Let's rewrite it a little bit so everything can happen at once.

Code: Select all

	if love.keyboard.isDown("w") then
		--print("up")
		new_y = y - dt * 40
	end
	if love.keyboard.isDown("s") then
		--print("down")
		new_y = y + dt * 40
	end
	if love.keyboard.isDown("a") then
		--print("left")
		new_x = x - dt * 40
	end
	if love.keyboard.isDown("d") then
		--print("right")
		new_x = x + dt * 40
	end
We can now move it freely both up and to the left at the same time, elseif has alot of uses but this was a case where end followed by a new if was a better solution.
Now we have another bug, they just keep coming don't they?
Try holding both W and S at the same time, it moves down, it really feels like it should stand still though, but how do we do this?
If we move at different speeds up and down it may be better to add a not statement checking to make sure it does not try to walk in the opposite direction at the same time.
But since we move at the same speed there is another solutions that I will use this time.

This is by modifying new_x and new_y by new_x and new_y instead of x and y, we do this by declaring new_x and new_y prior to checking for keyboard input.

Code: Select all

x, y = 400,300
function love.update(dt)
	local new_x, new_y = x, y -- Declaring new_x, new_y in a local scope, read about scopes if you don't know what I am talking about it's probably the reason for most bugs.
	if love.keyboard.isDown("w") then
		--print("up")
		new_y = new_y - dt * 40 -- Replaced the y with the now already declared new_y
	end
	if love.keyboard.isDown("s") then
		--print("down")
		new_y = new_y + dt * 40
	end
	if love.keyboard.isDown("a") then
		--print("left")
		new_x = new_x - dt * 40
	end
	if love.keyboard.isDown("d") then
		--print("right")
		new_x = new_x + dt * 40
	end
	--print("update", x, y, new_x, new_y) -- We print all 4 values and "update" so we know it was the print in love.update
	x = new_x -- We no longer need the nil check because new_x and new_y is always declared this time.
	y = new_y
end

function love.draw()
	--print("draw", x, y) -- We add the line "draw" before the values to ensure that this is the print in love.draw
	love.graphics.circle("line", x, y, 40)
end
Now if you try to run this it should work just as expected, and you are done with debugging it.
If you managed to follow this wihtout any problems, you should be able to apply the same concept to your own project and most likely be able to find and eleminate the source of your bugs.

If you find any gramatical errors or if it's something you're wondering about just post a reply.
Attachments
LoveTest.love
The example I used
(532 Bytes) Downloaded 136 times
If you can't fix it, Kill it with fire. ( Preferably before it lays eggs. )
User avatar
Ranguna259
Party member
Posts: 911
Joined: Tue Jun 18, 2013 10:58 pm
Location: I'm right next to you

Re: [HOWTO]Debug code

Post by Ranguna259 »

Nice tutorial, I always use these kind of things to debug code, prints everywhere :joker:
LoveDebug- A library that will help you debug your game with an on-screen fully interactive lua console, you can even do code hotswapping :D

Check out my twitter.
DarthGrover
Prole
Posts: 16
Joined: Wed Feb 05, 2014 11:31 pm
Location: Ohio USA

Re: [HOWTO]Debug code

Post by DarthGrover »

this is avery usefull tutorial. Can you tell me about other uses for the console. Ormore about how to use it?
davisdude
Party member
Posts: 1154
Joined: Sun Apr 28, 2013 3:29 am
Location: North Carolina

Re: [HOWTO]Debug code

Post by davisdude »

Well, you can type things into the console using io.read().
Other than debugging, the console doesn't really do much.
Also, note: when you close the console (instead of the window), love.quit is not called. This caused me a lot of trouble when I was messing with saving files and things of that nature.
GitHub | MLib - Math and shape intersections library | Walt - Animation library | Brady - Camera library with parallax scrolling | Vim-love-docs - Help files and syntax coloring for Vim
User avatar
Zer0
Citizen
Posts: 59
Joined: Sat Oct 06, 2012 9:55 am
Location: Sweden
Contact:

Re: [HOWTO]Debug code

Post by Zer0 »

DarthGrover wrote:Can you tell me about other uses for the console.
print shows nice formated messages, io.write does by default write to the console but can be set to write to a file instead, calling io.read from the main thread pauses the game until you write something into the console and can be good if you want the game to be paused right before a crash.

Code: Select all

local oldError = error
error = function(...)
	io.read()
	oldError(...)
end
added at the start of main.lua would pause the game and wait for input into the console unless you messed with default input/output of the io library.

You could turn off the window in the love config file and use it as game-server that could be useful if you're doing server-side physics and stuff.

In general just look at lua tutorials, all of them that are not about libraries use the console so you should get some experience with it then.
If you can't fix it, Kill it with fire. ( Preferably before it lays eggs. )
Post Reply

Who is online

Users browsing this forum: No registered users and 78 guests