[Learning LUA] Lesson 3: Values and Operators is up!

Talk about creating Grimrock 1 levels and mods here. Warning: forum contains spoilers!
User avatar
Xanathar
Posts: 629
Joined: Sun Apr 15, 2012 10:19 am
Location: Torino, Italy
Contact:

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by Xanathar » Thu Apr 04, 2013 4:53 pm

Then I definetely need to study the framework more! :D
Waking Violet (Steam, PS4, PSVita, Switch) : http://www.wakingviolet.com

The Sunset Gate [MOD]: viewtopic.php?f=14&t=5563

My preciousss: http://www.moonsharp.org

User avatar
akroma222
Posts: 1025
Joined: Thu Oct 04, 2012 10:08 am

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by akroma222 » Fri Apr 05, 2013 9:59 am

Such a great idea Spider!
I had actually just set about looking for some basics texts to learn more... but... this thread! :D

User avatar
Diarmuid
Posts: 807
Joined: Thu Nov 22, 2012 6:59 am
Location: Montreal, Canada
Contact:

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by Diarmuid » Mon Apr 08, 2013 2:57 am

Hi all,

A scripting help session with drakkan today on the LoTNR chat gave me an idea to contribute to this thread with a general programming principle, which is called

DRY - Don't Repeat Yourself

This says that you should note copy paste identical code many times around because:
1) You can make copy/paste/modify errors
2) If you want to change your code or fix an error in it, you have to change it everywhere, which is
a) long and tedious
b) very error-prone

Basically, you need to follow the rule of three principle. You can copy something twice. Three times? Make a function for it and call the function instead.

Ok, let's go with a practical example. Drakkan wanted a script that checks three alcoves for a red gem, and opens a door if a gem is in all three of them.

STEP 1
First Draft
Here is a first way of doing this, the simplest one. The function checkThree alcoves is called from the alcoves onActivate trigger, when you insert an item:

Code: Select all

function checkThreeAlcoves()

	-- Make one boolean variable for each alcove
	local gemInLeftAlcove = false
	local gemInCenterAlcove = false
	local gemInRightAlcove = false
	
	-- Check left alcove, and set its variable to true if gem is found
	for i in leftAlcove:containedItems() do
		if i.name == "gem_red" then  
			gemInLeftAlcove = true
		end
	end
	
	-- Check center alcove, and set its variable to true if gem is found
	for i in centerAlcove:containedItems() do
		if i.name == "gem_red" then  
			gemInCenterAlcove = true
		end
	end
	
	-- Check left alcove, and set its variable to true if gem is found
	for i in rightAlcove:containedItems() do
		if i.name == "gem_red" then  
			gemInRightAlcove = true
		end
	end
	
	-- Test if all variables are true and open door
	if gemInLeftAlcove and gemInCenterAlcove and gemInRightAlcove then
		door:open()
	end

end
STEP 2
Making it DRY
As you see, in the code above, we are repeating the for loop iterator code three times. Rule of three says we should make 1 function instead.

In the example above (step 1), the only thing that changes between the three times is the alcove. So let's make a new function which returns true if a "red_gem" is found in a given alcove: as the alcove is the only changing parameter, we will make this the argument of the function:

Code: Select all

function checkOneAlcove(alcove)
	
	-- Check if gem is in alcove and return true
	-- Note: When the script finds one it returns true and exits the 
	-- function right away, not checking further items

	for i in alcove:containedItems() do
		if i.name == "gem_red" then  
			return true
		end
	end
	
	-- If the script managed to get to here, this means a gem was not found. So let's return false:

	return false
	
end
Now, we can rewrite our checkThreeAlcoves function like this:

Code: Select all

function checkThreeAlcoves()

	local gemInLeftAlcove = false
	local gemInCenterAlcove = false
	local gemInRightAlcove = false
	
	if checkOneAlcove(leftAlcove) then
		gemInLeftAlcove = true
	end

	if checkOneAlcove(centerAlcove) then
		gemInCenterAlcove = true
	end
	
	if checkOneAlcove(rightAlcove) then
		gemInRightAlcove = true
	end
	
	if gemInLeftAlcove and gemInCenterAlcove and gemInRightAlcove then
		door:open()
	end

end
STEP 3
Cleaning temporary variables
The script above is fine, but now that our code is more DRY, we realize we don't need the temporary boolean variables. As our checkOneAlcove function returns true or false, we can use it directly in an if statement. Let's clean up the code:

Code: Select all

function checkThreeAlcoves()

	if checkOneAlcove(leftAlcove) 
		and checkOneAlcove(centerAlcove) 
		and checkOneAlcove(rightAlcove) then
		
		door:open()
		
	end

end
Much shorter and cleaner, right?

STEP 4
Making code reusable
Again, the script above is fine, but what if we want to check for three green gems elsewhere in the dungeon? Or check for 5 rocks in another place? We would need to copy/paste/modify our checkAlcoves functions, again making our code non-DRY. (or WET, which goes for Write Everything Twice).

The solution? Making functions more generic so that the same function can be reused multiple times.

First, let's add a second item argument to our checkOneAlcove function, so that is can check for different items. Let's also call it more appropriately:

Code: Select all

-- DRY and Reusable version
function alcoveContainsItem(alcove, item)
	
	for i in alcove:containedItems() do
		if i.name == item then  
			return true
		end
	end
	
	return false
	
end
and now our main function becomes:

Code: Select all

function checkLeftCenterAndRightAlcovesForRedGems()

	if alcoveContainsItem(leftAlcove, "gem_red") 
		and alcoveContainsItem(centerAlcove, "gem_red") 
		and alcoveContainsItem(rightAlcove, "gem_red") then
		
		door:open()
		
	end

end
STEP 5
Making code even more reusable
Ok, now the alcoveContainsItem function is reusable, but not the main function, as the funny name above shows. The following version allows us to give it three different alcoves, and an item name:

Code: Select all

function checkThreeAlcovesForAnItem(alcove1, alcove2, alcove3, item)

	if alcoveContainsItem(alcove1, item) 
		and alcoveContainsItem(alcove2, item) 
		and alcoveContainsItem(alcove3, item) then
		
		door:open()
		
	end

end
STEP 6
Making code even EVEN more reusable
Ok, now the function above can check for any item in any three alcoves. But what if we want two alcoves or four, or 10? We cannot start doing a lot of functions like that:

Code: Select all

function checkTwoAlcovesForAnItem(alcove1, alcove2, item)
function checkThreeAlcovesForAnItem(alcove1, alcove2, alcove3, item)
function checkFourAlcovesForAnItem(alcove1, alcove2, alcove3, alcove4, item)
function checkFiveAlcovesForAnItem(alcove1, alcove2, alcove3, alcove4, alcove5, item)
That'll be definetly not DRY.

So how to handle "any number of alcoves"? The solution is to give the function a table of alcoves as an argument, as tables can have any size:

Code: Select all

function checkAlcovesForAnItem(alcovesTable, item)

	local allAlcovesContainTheItem = true
	
	for _, alcove in ipairs(alcovesTable) do
		if not(alcoveContainsItem(alcove, item))
			allAlcovesContainTheItem = false
		end
	end

	if allAlcovesContainTheItem then
		door:open()
	end
	
end
Here, I start by having a variable that is true if all alcoves have the item, and then check alcoves in the table one by one, and if one fails to contain the item, set the variable to false.

STEP 7
Making it even EVEN EVEN EVEN more reusable, or What the hell is that door doing in there anyway?
One last thing to do, is to get out the door opening code from inside the alcove checking function, as we might eventually want to close a pit, or spawn a monster, or whatever. So, final version of code:

Code: Select all

-- Specific function to call for that puzzle
function openDoorIfAlcovesHaveRedGems()

	if checkAlcovesForAnItem({leftAlcove, centerAlcove, rightAlcove}, "gem_red") then
		door:open()
	end

end

-- Generic function used by the above one
function checkAlcovesForAnItem(alcovesTable, item)

	local allAlcovesContainTheItem = true
	
	for _, alcove in ipairs(alcovesTable) do
		if not(alcoveContainsItem(alcove, item))
			allAlcovesContainTheItem = false
		end
	end

	return allAlcovesContainTheItem
	
end

-- Generic sub-function used by the above one
function alcoveContainsItem(alcove, item)
	
	for i in alcove:containedItems() do
		if i.name == item then  
			return true
		end
	end
	
	return false
	
end

Enjoy!!!! :D

alois
Posts: 103
Joined: Mon Feb 18, 2013 7:29 am

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by alois » Mon Apr 08, 2013 8:20 am

Diarmuid wrote:
Enjoy!!!! :D
I'm sorry sir, I would like to check whether in the left alcove is a red gem, in the middle there is a green one, and in the rightmost a blue one. How can I do that? :)

*I see a forthcoming Step 8* :)

alois :)

User avatar
Dr.Disaster
Posts: 2848
Joined: Wed Aug 15, 2012 11:48 am

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by Dr.Disaster » Mon Apr 08, 2013 11:28 am

alois wrote:I'm sorry sir, I would like to check whether in the left alcove is a red gem, in the middle there is a green one, and in the rightmost a blue one. How can I do that? :)
*I see a forthcoming Step 8* :)
Either go with alcoveContainsItem() of Step 4 and simply change the items looked for in the "if" statement or develop Step 8 by moving from a single item to an item table. Naturally the "for" loop then needs to have two index variables instead of one.

I prefer Step 4 for readability and less time spend for writing code that prolly never gets used again. Why? Beginning with Step 5 code specialization starts so it's reusability is not guaranteed. In example when an alcove needs to be checked for more then one item Step 5 and up are not up for the task without additional code.

User avatar
Diarmuid
Posts: 807
Joined: Thu Nov 22, 2012 6:59 am
Location: Montreal, Canada
Contact:

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by Diarmuid » Mon Apr 08, 2013 3:05 pm

Dr.Disaster wrote:I prefer Step 4 for readability and less time spend for writing code that prolly never gets used again. Why? Beginning with Step 5 code specialization starts so it's reusability is not guaranteed. In example when an alcove needs to be checked for more then one item Step 5 and up are not up for the task without additional code.
This is true, thanks for pointing out the code specialization issue. I would have stopped at step 4 probably too in a normal situation, but here I knew that drakkan had a whole puzzle of alcoves like that of different colors (36 alcoves he said), so I kept on doing a function for that.

User avatar
SpiderFighter
Posts: 789
Joined: Thu Apr 12, 2012 4:15 pm

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by SpiderFighter » Mon Apr 08, 2013 3:15 pm

It's great to see this thread taking off as intended! Thanks to everyone for your contributions. I'm going to hold off on any more newbie tutorials for the next two weeks, becuase I have too many balls in the air, and because I'm heading for outpatient surgery on Friday. I want to take this week to get as much of my mod done as I can, since it's so close to being completed (finally), and finishing it will allow me to really dig in deep with Lua. I'll still be monitoring the thread, and I'll still be around the forums (although probably not next weekend, as I imagine I'll simply be enjoying the drugs :) )

Ryeath_Greystalk
Posts: 366
Joined: Tue Jan 15, 2013 3:26 am
Location: Oregon

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by Ryeath_Greystalk » Tue Apr 09, 2013 5:16 am

That is a very nice lesson diarmuid. If I could request one thing though, can you write a a nice simple lesson on passing variables between functions?

You lesson is valuable but if someone doesn't know how top pass things back and forth (hmmm, i.e. me) then it becomes really confusing quickly.

Thanks for your input either way though.

User avatar
Krazzikk
Posts: 37
Joined: Sat Mar 16, 2013 11:30 pm

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by Krazzikk » Fri Apr 26, 2013 5:19 am

Holy crap this is a lot of information to process. And I'm behind! Thankfully I'm understanding most of it so far.

User avatar
Neikun
Posts: 2457
Joined: Thu Sep 13, 2012 1:06 pm
Location: New Brunswick, Canada
Contact:

Re: [Learning LUA] Lesson 3: Values and Operators is up!

Post by Neikun » Tue May 07, 2013 6:00 am

When's the next lesson, Spider?
"I'm okay with being referred to as a goddess."
Community Model Request Thread
See what I'm working on right now: Neikun's Workshop
Lead Coordinator for Legends of the Northern Realms Project
  • Message me to join in!

Post Reply