Show off clean code examples

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Show off clean code examples

Post by nice »

Hello boys and girls!

As a hobby programmer and aspring Game Designer I would like to see examples on how clean code should look like in Löve2D/Lua.
Sometimes I'm not very good keeping an eye on what different parts of the code does so I guess it would be beneficial not only for me but for others to see how clean code should look like.

Personally I would like to see how you Löve user code looks like, articles are good and all but I think it's more interesting to see others and it's entirely up to you if you want to show of your code or not.

I guess I start with a example on a not so clean and in my opinion confusing code from a game that I made:

Code: Select all

local isSpacebarPressed
local frameNum
local colorList
local numLives
local maxTrailSize
local isPpressed
local isMpressed
local isRpressed
local SHOW_COLLISION_BOX
local ship
local shiplives
local smallAsteroid
local mediumAsteroid
local largeAsteroid
local menuImage
local gameoverImage
local numAsteroids
local asteroids
local isUpHeld
local isLeftHeld
local isRightHeld
local score
local sfx
local sfx2
local sfx3
local expSound
local trailPos
local state
local maxVelocity
local trailSprite
local highScore = 0

function resetGame()
	print("in function resetGame")
	isSpacebarPressed = false
	frameNum = 1
	colorList = {}
	numLives = 5
	maxTrailSize = 20
	isPpressed = false
	isMpressed = false
	isRpressed = false

	-- debug
	SHOW_COLLISION_BOX = false

	--Ship--
	ship = {}
	ship.xPos = 400
	ship.yPos = 300
	ship.theta = 4.71
	ship.xVelocity = 0
	ship.xAccel = 0.15
	ship.w = 32
	ship.h = 32
	ship.sprite = love.graphics.newImage("ship2.png")
	trailSprite = love.graphics.newImage("ship.png")
	shipIcon = love.graphics.newImage("shiplives.png")
	maxVelocity = 5.25

	--Asteroid images--
	smallAsteroidImage = love.graphics.newImage("asteroid16.png")
	mediumAsteroidImage = love.graphics.newImage("asteroid32.png")
	largeAsteroidImage = love.graphics.newImage("asteroid64.png")
	menuImage = love.graphics.newImage("menu.png")
	gameoverImage = love.graphics.newImage("gameoverMenu.png")
	-- asteroid sfx
	smallAsteroidSound = love.audio.newSource("pickup.wav")
	mediumAsteroidSound = love.audio.newSource("pickup2.wav")
	largeAsteroidSound = love.audio.newSource("pickup3.wav")
	expSound = love.audio.newSource("deathSfx.wav")
	--Asteroids--
	numAsteroids = 20
	asteroids = {}

	--Asteroid spawner--
	for i = 1, numAsteroids do

		local rand = math.random(1,3) 

		asteroids[i] = {}

		if rand == 1 then
			asteroids[i].W = 16
			asteroids[i].H = 16
			asteroids[i].sprite = smallAsteroidImage
			asteroids[i].v = 2.5
			asteroids[i].pointValue = 10
			asteroids[i].sfx = smallAsteroidSound		
		elseif rand == 2 then
			asteroids[i].W = 32
			asteroids[i].H = 32
			asteroids[i].sprite = mediumAsteroidImage
			asteroids[i].v = 1.3
			asteroids[i].pointValue = 50
			asteroids[i].sfx = mediumAsteroidSound
		else
			asteroids[i].W = 64
			asteroids[i].H = 64
			asteroids[i].sprite = largeAsteroidImage
			asteroids[i].v = 1
			asteroids[i].pointValue = 100
			asteroids[i].sfx = largeAsteroidSound
		end



		rand = math.random(1,4)

		if rand == 1 then
			asteroids[i].X = math.random(-400, -64)
			asteroids[i].Y = math.random(0, 600)
			asteroids[i].theta = -0.785 + (math.random() * 1.57 )
		elseif rand == 2 then
			asteroids[i].X = math.random(864, 2000)
			asteroids[i].Y = math.random(0, 600)
			asteroids[i].theta = 2.35 + (math.random() * 1.57 )
		elseif rand == 3 then
			asteroids[i].X = math.random(0, 800)
			asteroids[i].Y = math.random(-400, -64)
			asteroids[i].theta = 0.398 + (math.random() * 1.57 )
		else
			asteroids[i].X = math.random(0, 800)
			asteroids[i].Y = math.random(700, 2000)
			asteroids[i].theta = 4.71 + (math.random() * 1.57 )
		end

	end

	-- controls
	isUpHeld = false
	isLeftHeld = false
	isRightHeld = false

	--other
	score = 0

	-- pick some random colors
	trailPos = {}
	for i = 1, maxTrailSize do
		local c = {}
		c.r = math.random(255)
		c.g = math.random(255)
		c.b = math.random(255)
		colorList[i] = c
	end
	--Check state
	state = "menuState"
end


function love.load()
	print( "in function love.load" )
	resetGame()
end


function love.draw()

	if state == "menuState" then
		drawMenu()
	elseif state == "gameState" then
		drawGame()
	elseif state == "gameoverState" then
		drawGameover()
	end

end

function love.update()
	mainFont = love.graphics.newFont("Mojang-Regular.ttf", 20)
	love.graphics.setFont(mainFont)
	



	if state == "menuState" then
		updateMenu()
	elseif state == "gameState" then
		updateGame()
	elseif state == "gameoverState" then
		updateGameover()
	end
end

function updateMenu()
	if isPpressed then 
		state = "gameState"
	end
end

function updateGameover()
	if isRpressed then
		resetGame()
		state = "gameState"
	elseif isMpressed then
		resetGame()
		state = "menuState"
	end
end


function updateGame()

	-- stick the pos structure into the *front* of our trail list
	if frameNum % 5 == 0 then

		-- create a pos structure that represents where the ship is right now
		local pos = {}
		pos.x = ship.xPos 
		pos.y = ship.yPos
		pos.theta = ship.theta

		table.insert(trailPos, 1, pos)
		if #trailPos > maxTrailSize then
			table.remove(trailPos)
		end

		if not isSpacebarPressed then
			table.remove(trailPos)
		end

	end

	for i = 1, numAsteroids do
		-- update position
		asteroids[i].X = asteroids[i].X + math.cos(asteroids[i].theta) * asteroids[i].v
		asteroids[i].Y = asteroids[i].Y + math.sin(asteroids[i].theta) * asteroids[i].v
		-- check for asteroid going offscreen, if so then respawn
		if asteroids[i].X > 900 or asteroids[i].Y > 700 or asteroids[i].X < -100 or asteroids[i].Y < -100 then
		
			rand = math.random(1,4)

			if rand == 1 then
				asteroids[i].X = math.random(-100, -64)
				asteroids[i].Y = math.random(0, 600)
				asteroids[i].theta = -0.785 + (math.random() * 1.57 )
			elseif rand == 2 then
				asteroids[i].X = math.random(864, 900)
				asteroids[i].Y = math.random(0, 600)
				asteroids[i].theta = 2.35 + (math.random() * 1.57 )
			elseif rand == 3 then
				asteroids[i].X = math.random(0, 800)
				asteroids[i].Y = math.random(-100, -64)
				asteroids[i].theta = 0.398 + (math.random() * 1.57 )
			else
				asteroids[i].X = math.random(0, 800)
				asteroids[i].Y = math.random(700, 900)
				asteroids[i].theta = 4.71 + (math.random() * 1.57 )
			end

		end
	end
	
	for i = 1, numAsteroids do
	---VVV SHIP TO ASTEROID COLLISION VVV
		-- test each asteroid against our main ship...	
		if ship.xPos > asteroids[i].X and ship.xPos < asteroids[i].X + asteroids[i].W and ship.yPos > asteroids[i].Y and ship.yPos < asteroids[i].Y + asteroids[i].H then
		 	asteroids[i].X = math.random(-100, -64)
		 	asteroids[i].Y = math.random(0, 600)
		 	asteroids[i].theta = -0.785 + (math.random() * 1.57 )
		 	numLives = numLives - 1
		 	ship.xPos = 400
		 	ship.yPos = 300
		 	trailPos = {}
		 	love.audio.stop(expSound)
		 	love.audio.play(expSound)
		 	if numLives == 0 then
		 		love.audio.stop(expSound)
		 		love.audio.play(expSound)
		 		if score > highScore then
		 			highScore = score
		 		end
		 		state = 'gameoverState'
		 	end


		 end
		 -- test each asteroid against every ship in the trail...
		for j = 1, #trailPos do
			if trailPos[j].x > asteroids[i].X and trailPos[j].x < asteroids[i].X + asteroids[i].W and trailPos[j].y > asteroids[i].Y and trailPos[j].y < asteroids[i].Y + asteroids[i].H then
			 	asteroids[i].X = math.random(-100, -64)
			 	asteroids[i].Y = math.random(0, 600)
			 	asteroids[i].theta = -0.785 + (math.random() * 1.57 )
			 	score = score + asteroids[i].pointValue
				love.audio.stop(asteroids[i].sfx)
				love.audio.play(asteroids[i].sfx)
			end
		end
	end
	

	-- Asteroid to asteroid collision
	-- dont flip things twice!
	local hasAlreadyFlipped = {}
	for k = 1, numAsteroids do
		hasAlreadyFlipped[k] = false
	end
	for i = 1, numAsteroids do
		local curAsteroid = asteroids[i]
		for j = 1, numAsteroids do
			local isUpperLeftColliding = false
			local isUpperRightColliding = false
			local isLowerLeftColliding = false
			local isLowerRightColliding = false

			isUpperLeftColliding = curAsteroid.X > asteroids[j].X and 
			  					   curAsteroid.X < asteroids[j].X + asteroids[j].W and 
			  					   curAsteroid.Y > asteroids[j].Y and 
			   					   curAsteroid.Y < asteroids[j].Y + asteroids[j].H

			isUpperRightColliding = curAsteroid.X + curAsteroid.W > asteroids[j].X and 
									curAsteroid.X + curAsteroid.W < asteroids[j].X + asteroids[j].W and 
									curAsteroid.Y > asteroids[j].Y and 
									curAsteroid.Y < asteroids[j].Y + asteroids[j].H

			isLowerLeftColliding = curAsteroid.X > asteroids[j].X and
								   curAsteroid.X < asteroids[j].X + asteroids[j].W and
								   curAsteroid.Y + curAsteroid.H > asteroids[j].Y and
								   curAsteroid.Y + curAsteroid.H < asteroids[j].Y + asteroids[j].H

			isLowerRightColliding = curAsteroid.X + curAsteroid.W > asteroids[j].X and
									curAsteroid.X + curAsteroid.W < asteroids[j].X + asteroids[j].W and
									curAsteroid.Y + curAsteroid.H > asteroids[j].Y and
									curAsteroid.Y + curAsteroid.H < asteroids[j].Y + asteroids[j].H

			local isCurOnscreen = curAsteroid.X > 0 and curAsteroid.X < 800 and curAsteroid.Y > 0 and curAsteroid.Y < 600
			local isOtherOnscreen = asteroids[j].X > 0 and asteroids[j].X < 800 and asteroids[j].Y > 0 and asteroids[j].Y < 600
			if isCurOnscreen and isOtherOnscreen then
				if isUpperLeftColliding or isUpperRightColliding or isLowerLeftColliding or isLowerRightColliding then
					if hasAlreadyFlipped[i] == false then
						curAsteroid.theta = curAsteroid.theta + math.pi
						hasAlreadyFlipped[i] = true
					end
					if hasAlreadyFlipped[j] == false then
						asteroids[j].theta = asteroids[j].theta + math.pi
						hasAlreadyFlipped[j] = true
					end
				end
			end
		end
	end

	if isUpHeld == true then
		ship.xVelocity = ship.xVelocity + 0.5
	else
		ship.xVelocity = ship.xVelocity - 0.075
	end

	if isLeftHeld == true then
		ship.theta = ship.theta - 0.1
	end

	if isRightHeld == true then
		ship.theta = ship.theta + 0.1
	end

	if ship.xVelocity < 0 then
		ship.xVelocity = 0
	end

	if ship.xVelocity > maxVelocity then
		ship.xVelocity = maxVelocity
	end

	ship.xPos = ship.xPos + math.cos(ship.theta) * ship.xVelocity
	ship.yPos = ship.yPos + math.sin(ship.theta) * ship.xVelocity

	if ship.xPos > 800 then
  		ship.xPos = 0
	end

	if ship.xPos < 0 then
	  ship.xPos = 800
	end

	if ship.yPos > 600 then
		ship.yPos = 0
	end

	if ship.yPos < 0 then
		ship.yPos = 600
	end

	frameNum = frameNum + 1

end


function drawMenu()
	love.graphics.setColor(255, 255, 255)
	love.graphics.draw(menuImage, 0, 0) 
end

function drawGameover()
	love.graphics.setColor(255, 255, 255)
	love.graphics.draw(gameoverImage, 0, 0)
	love.graphics.setColor(52, 89, 35)
	love.graphics.print(score, 150, 240)
	--love.graphics.print("best:"..highScore, 100, 340)
end

function drawGame()

	love.graphics.setBackgroundColor(115, 138, 40)

	for i = 1, numLives do
		love.graphics.setColor(52, 89, 35)
		love.graphics.draw( shipIcon, 32*i, 64, -math.pi/2 )
	end

	for i = #trailPos, 1, -1 do
		local alpha = 255
		love.graphics.setColor( colorList[i].r, colorList[i].g, colorList[i].b, alpha )
		love.graphics.draw(trailSprite, trailPos[i].x, trailPos[i].y, trailPos[i].theta, 1, 1, 16, 16 )
	end

	love.graphics.setColor(255, 255, 255)
	for i = 1, numAsteroids do
		love.graphics.draw(asteroids[i].sprite, asteroids[i].X, asteroids[i].Y)
		if SHOW_COLLISION_BOX then
			love.graphics.setColor( math.random(255), math.random(255), math.random(255) )
			love.graphics.rectangle( "line", asteroids[i].X, asteroids[i].Y, asteroids[i].W, asteroids[i].H )
			love.graphics.setPointSize( 10 )
			love.graphics.point( asteroids[i].X + asteroids[i].H, asteroids[i].Y + asteroids[i].H )
			love.graphics.print( i, asteroids[i].X, asteroids[i].Y )
			love.graphics.setColor( 255, 255, 255 )
		end
	end

	love.graphics.setColor(52, 89, 35)
	love.graphics.draw(ship.sprite, ship.xPos, ship.yPos, ship.theta, 1, 1, 16, 16)

	if SHOW_COLLISION_BOX then
		love.graphics.setColor( math.random(255), math.random(255), math.random(255) )
		love.graphics.setPointSize( 3 )
		love.graphics.point( ship.xPos, ship.yPos )
		love.graphics.setColor( 255, 255, 255 )
	end

	love.graphics.setColor(52, 89, 35)
	love.graphics.print(score, 45, 10, 0, 1, 1)

end

function love.keypressed(key)
	if key == 'right' then
		ship.theta = ship.theta + 0.1
	end

	if key == 'left' then
		ship.theta = ship.theta - 0.1
	end

	if key == 'up' then
		isUpHeld = true
	end

	if key == 'left' then
		isLeftHeld = true
	end

	if key == 'right' then
		isRightHeld = true
	end

	if key == 'escape' then
		love.event.push( 'quit' )
	end

	if key == 'c' then
		SHOW_COLLISION_BOX = not SHOW_COLLISION_BOX
	end

	if key == ' ' then
		isSpacebarPressed = true
	end

	if key == 'p' then
		isPpressed = true
	end

	if key == 'm' then
		isMpressed = true
	end

	if key == 'r' then
		isRpressed = true
	end
end


function love.keyreleased(key)
	if key == 'up' then
		isUpHeld = false
	end

	if key == 'left' then
		isLeftHeld = false
	end

	if key == 'right' then
		isRightHeld = false
	end

	if key == ' ' then
		isSpacebarPressed = false
	end

	if key == 'p' then
		isPpressed = false
	end

	if key == 'm' then
		isMpressed = false
	end

	if key == 'r' then
		isRpressed = false
	end
end
I also want to mention that I have a better idea to keep check on my code by commenting on how different parts of the code works and as I mention above I would love to see your take on clean code.
:awesome: Have a good day! :ultraglee:
User avatar
Fenrir
Party member
Posts: 222
Joined: Wed Nov 27, 2013 9:44 am
Contact:

Re: Show off clean code examples

Post by Fenrir »

Hi,

About your code, you should probably start to use classes to make object oriented code and get rid of global functions to make it cleaner. Have a look to HUMP for instance.
User avatar
nice
Party member
Posts: 191
Joined: Sun Sep 15, 2013 12:17 am
Location: Sweden

Re: Show off clean code examples

Post by nice »

Fenrir wrote:Hi,

About your code, you should probably start to use classes to make object oriented code and get rid of global functions to make it cleaner. Have a look to HUMP for instance.
The code that I showed is a little bit over a year old right now and I haven't touch it since as I don't really know where I want it to go and I don't wanna get into HUMP right now as I'm not confident in my programming skills right now.
:awesome: Have a good day! :ultraglee:
User avatar
josefnpat
Inner party member
Posts: 955
Joined: Wed Oct 05, 2011 1:36 am
Location: your basement
Contact:

Re: Show off clean code examples

Post by josefnpat »

At one point, I wanted to see how "clean" I could write a module. This lead to the rather boring, but very clean "RadarChart"

While not very interesting, or technically impressive, I feel it is a good example of "clean code":

viewtopic.php?f=5&t=77355&p=163698

If you are looking for ways to make your code "cleaner", I would highly suggest kikito's series on the subject:
Missing Sentinel Software | Twitter

FORCIBLY IGNORED.
<leafo> when in doubt delete all of your code
<bartbes> git rm -r *
<bartbes> git commit -m "Fixed all bugs"
<bartbes> git push
User avatar
Jasoco
Inner party member
Posts: 3725
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: Show off clean code examples

Post by Jasoco »

That first blog post brings up a problem I'd been having too. Trying to decide on a standard naming scheme for my functions. camelCased, under_scored, alloneword. I can never decide on a standard. I prefer camelCased though but find myself slipping into under_scored when I least expect it. alloneword for me just becomes unreadable. At least with camelCased I can tell where words start. Makes it easier to find function calls in my code.
davisdude
Party member
Posts: 1154
Joined: Sun Apr 28, 2013 3:29 am
Location: North Carolina

Re: Show off clean code examples

Post by davisdude »

Yeah, I use prefer CamelCased. I find it even cleaner when I capitalize the first letter. That makes it very easy to see which functions are mine. This is, however, not a common practice, so do whatever works for you.
If you want an example, I can show off my base game template (currently still being developed).
I think it's pretty clean, but you can decide for yourself.
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
ejmr
Party member
Posts: 302
Joined: Fri Jun 01, 2012 7:45 am
Location: South Carolina, U.S.A.
Contact:

Re: Show off clean code examples

Post by ejmr »

I am happy with the cleanliness of this, although some may consider it too verbose, and I couldn't honestly argue against that criticism.
ejmr :: Programming and Game-Dev Blog, GitHub
南無妙法蓮華經
User avatar
Ortimh
Citizen
Posts: 90
Joined: Tue Sep 09, 2014 5:07 am
Location: Indonesia

Re: Show off clean code examples

Post by Ortimh »

Code: Select all

color = {}

local metatable = {}
local metavariable = {}

function metatable:__call(name, red, green, blue)
	metavariable[name] = {red, green, blue}
end

function metatable:__index(index)
	if (metavariable[index]) then
		return metavariable[index]
	end
end

setmetatable(color, metatable)
Actually I want to return unpacked metavariable values at __index but Lua doesn't support multiple return values at metamethods except for __call. I don't know what do you mean by "clean". Is it clean for performance and such or for readability? Well for readability I think my code is clean, followed Lua rules about naming but I don't really support about single line for body like:

Code: Select all

if (func()) then return true end
But only for reading point.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Show off clean code examples

Post by kikito »

If it's at the end of a function, you can replace this:

Code: Select all

if (func()) then return true end
with this:

Code: Select all

return func()
For example, your __index could be:

Code: Select all

function metatable:__index(index)
  return metavariable[index]
end
That said, that usage of __index is so common, that Lua allows setting __index directly to a table. So this also works (and it's usually a bit faster):

Code: Select all

metatable.__index = metavariable
When I write def I mean function.
User avatar
Ortimh
Citizen
Posts: 90
Joined: Tue Sep 09, 2014 5:07 am
Location: Indonesia

Re: Show off clean code examples

Post by Ortimh »

In case I only want to return true value if func function returns true, so I shouldn't replace

Code: Select all

if (func()) then return true end
with

Code: Select all

return func()
because if I return func function returned value then it returns either true or false.
Post Reply

Who is online

Users browsing this forum: No registered users and 227 guests