[SOLVED] Calling a variable function.

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
Tjakka5
Party member
Posts: 243
Joined: Thu Dec 26, 2013 12:17 pm

[SOLVED] Calling a variable function.

Post by Tjakka5 »

Not really clear title, so time for some backstory.

I've been making some "test programs" to try out things that will be neccesary for a big game I want to make at some point.
The game will be a sandbox game, with lots of building blocks and items, thus I want the functions updating and drawing those to be very modular.

Now, in one of my test programs I made a block called a "hopper", this block had 1 inventory slot. It would suck items from a container above it, and put it in a container in the way it was facing (left, right or down).

The thing is, the hopper code worked something along the lines of this:

Code: Select all

--Not "real" code, just want to show the idea.
function hopper.suckItem()
   container = map.getName(hopperPosition, hopperPosition-1)
   if container then
      containerId = map.getId(hopperPosition, hopperPosition-1)
      --So here it found out about the type and id of the container above it...

      if container == "chest" then
         if not chest.getContent(containerId, 1) then --It finds out if there's nothing in the container
           chest.setContent(containerId, 1, hopperContent[1]) --So it sets slot 1 of the chest to whatever was in the hopper's slot.
         end
      elseif container == "furnace" then
         if not furnace.getContent(containerId, 1) then
            furnace.setContent(containerId, 1, hopperContent[1])
         end
      elseif container == "hopper" then
         if not hopper.getContent(containerId, 1) then
            hopper.setContent(containerId, 1, hopperContent[1])
         end
      end  --And it would set its own content to nothing and all, but I didnt include that.
   end
end
So I hope you see my problem here; the code isnt modular at all. Adding a new container would require 4 extra lines of code, which I really dont like.
So, I was wondering if there's a way to make it do something like this:

Code: Select all

container = map.getName(hopperPosition, hopperPosition-1)
if container then
  containerId = map.getId(hopperPosition, hopperPosition-1)
  
  if not container.getContent(containerId, 1) then
     container.setContent(containerId, 1, hopperContent[1])
  end
end
Meaning that, for example, the container is a chest, the container variable will get set to chest, and then the container.getContent would turn into chest.getContent.

I have a suspission this might be possible with self, but I'm really not sure how to.
Any help would be greatly appriciated.
Last edited by Tjakka5 on Wed Jul 16, 2014 7:53 pm, edited 1 time in total.
User avatar
Tjakka5
Party member
Posts: 243
Joined: Thu Dec 26, 2013 12:17 pm

Re: Calling a variable function.

Post by Tjakka5 »

I feel like I should bump this for more attention.
Sooo...

Bump?
User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: Calling a variable function.

Post by micha »

You are right in that you can save some lines of code here. Whenever you feel like you have to manually copy-paste a lot of code, then you are usually doing something not optimal.

If I understand correctly, then you have a string, called "container" and depending on its content you want to call a different function.
You can solve this by simply putting all the getContent functions into a table.

Code: Select all

objects = {}
objects.furnace = {}
objects.chest = {}
-- and so on
And lets assume each object has it own getContent function. Then you can do something like this:

Code: Select all

container = 'chest'
objects.[container].getContent() -- same as objects.chest.getContent()
container = 'furnace'
objects.[container].getContent() -- same as objects.furnace.getContent()
In other words, with the ".[]" you can translate a string into a a sort of variable name.

The other solution is going OOP. Instead of saving the type of object as a string, save the object isself.
Then you can simply write:

Code: Select all

container = map.getContainerAt(x,y)
container:getContent(...)
And the getContent-function could have a different meaning depending on the container.
User avatar
Tjakka5
Party member
Posts: 243
Joined: Thu Dec 26, 2013 12:17 pm

Re: Calling a variable function.

Post by Tjakka5 »

Thats a really good solution. But, before I implement it though.

I wanted to go with the way as you presented with the OOP, but how would you actually do that?
Would I be able to implement it with the following code, or would it require a way different approach?

Code: Select all

chest = {}
amountChest = 0

function chest.load()
	chest.img = love.graphics.newImage("sprites/chest.png")
end

function chest.spawn(x, y, con1, con2, con3)
	table.insert(chest, {xPos = x, yPos = y, con1 = con1, con2 = con2, con3 = con3, num1 = 0, num2 = 0, num3 = 0})
	amountChest = amountChest+1
	map.set("chest", amountChest, x, y)
end

function chest.returnContent(name)
	return chest[name].con1
end

function chest.returnFull(name)
	if (chest[name].con1 ~= "def") and (chest[name].con2 ~= "def") and (chest[name].con3 ~= "def") then
		return true
	end
end

function chest.returnEmpty(name)
	if (chest[name].con1 == "def") and (chest[name].con2 == "def") and (chest[name].con3 == "def") then
		return true
	end
end

function chest.sort(name)
	if chest[name].con2 == "def" then
		if chest[name].con3 ~= "def" then
			chest[name].con2 = chest[name].con3
			chest[name].con3 = "def"
		end
	end
	if chest[name].con1 == "def" then
		if chest[name].con2 ~= "def" then
			chest[name].con1 = chest[name].con2
			chest[name].con2 = "def"
		end
	end
end

function chest.setContent(name, slot, data)
	if slot == 1 then
		chest[name].con1 = data
	elseif slot == 2 then
		chest[name].con2 = data
	elseif slot == 3 then
		chest[name].con3 = data
	end
end

function chest.gui(name)
	gui.drawCon(chest[name].con1, chest[name].con2, chest[name].con3)
end

--PARENT FUNCTIONS
function chest.update(dt)
	for i,v in ipairs(chest) do
		chest.sort(i)
	end
end

function chest.draw()
	for i,v in ipairs(chest) do
		love.graphics.draw(chest.img, (v.xPos*32)-32, (v.yPos*32)-32)
	end
end
EDIT: Also, container codes come from the same basic code, which has some basic functions like spawning, getting content, setting content, etc.
After I copied it I change all the functions to what kind of container it is, and if neccesary I add new functions that will most likely be called by the update function.
In this case that is chest.sort()

Also, In a newer version I plan on adding a flag for each container object, which will be set to true whenever that object changed (like a thing was added to its contents) so that another function will only update the objects that need updating.
User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: Calling a variable function.

Post by micha »

For now, your code would work like this. But you have to slightly change how the map works. This is what you have now:

Code: Select all

container = map.getName(hopperPosition, hopperPosition-1) -- this returns a string
The function returns a string. But in the future you want to get the actual container. Not a string with the same name.
As already written above, I suggest you rename the function and change the way, data is stored in the map:

Code: Select all

container = map.getContainerAt(hopperPosition, hopperPosition-1) -- this returns a container (which is a table)
Remember, that tables are copied by reference. That means that if you have two cells on the map both with a chest, then the actual chest container is not copied, but both cells refer to the same object(If this is not what you want, I suggest reading a bit about OOP, for example in PiL).
Then you can simply do this:

Code: Select all

container.getContent()
which will call the chest.getContent().
User avatar
Tjakka5
Party member
Posts: 243
Joined: Thu Dec 26, 2013 12:17 pm

Re: Calling a variable function.

Post by Tjakka5 »

So just to make sure, how I am doing this will be the best way for modularity?:

Main.lua

Code: Select all

require "map"
require "chest"

function love.load()
  map.load()
  chest.spawn(1, 3)
end

function love.update(dt) --TO TEST IF ITS ACTUALLY WORKING
  obj = map.get(1, 3) 
  num = obj.update()
end

function love.draw()
  love.graphics.print(num, 10, 10)
end
Map.lua

Code: Select all

map = {}

function map.set(x, y, obj)
  map[x][y] = obj
end

function map.get(x, y)
  return map[x][y]
end

function map.generate() --Not caring about infinte worlds atm
  for i = 1, 20 do
     map[i] = {}
  end
end
Chest.lua

Code: Select all

chest = {}
chestAmount = 0

function chest.spawn(x, y)
  if not map.get(x, y) then --To test if its a valid spawning spot
    map.set(x, y, chest)
  end
  table.insert(chest, {xPos = x, yPos = y}) --This is to give all the chests ID's (table index number) which has values like position and contents
  chestAmount = chestAmount+1 --To know how many chests there are. Can I achieve this also by #chest ?
end

function chest.update()
  return 42
end
Sorry it was typed out like this, my programming computer currently has no way to connect to other computers at all...
User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: Calling a variable function.

Post by micha »

That already looks good. I mean, look at how tidy the main file is.

Two this you can improve:
1) The map setter and getter should be bulletproof. What happens if you call this:

Code: Select all

map.set(10000, 10000, chest)
You program will crash, because the corresponding table inside is not big enough (map[10000] does not exist, so you will get an error, because you try to index a nil value).
Better do it like that:

Code: Select all

function map.set(x, y, obj)
  if not map[x] then
    map[x] = {}
  end
  map[x][y] = obj
end
Similarly, the getter will not work, if x is out of range.

2) The chest.spawn function does not really create a new chest. What it does it adds an entry to the map that points to the table chest (which is you chest-class type of thing, not an actual chest) and then you add a new position to the table chest. What you should do instead is create a new object, which is a chest and then insert it into the map-table. You do not necessarily need to have a list of all chests. If all chests are inside the map, then you still have them.
User avatar
Tjakka5
Party member
Posts: 243
Joined: Thu Dec 26, 2013 12:17 pm

Re: Calling a variable function.

Post by Tjakka5 »

So I made a way to add a limit to the height (which will probably be 128), however, it appears to give me an error saying that the value is nil.
I feel very stupid that I'm unable to figure it out...

Code: Select all

map = {}
mapStats = {
                  limit = 0,
}

function map.load(height)
  for i = 1, height do
    map[i] = {}
  end
  mapStats[limit] = height --Table index is nil
end
Again, I feel really stupid for not being able to figure this out :l
User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: Calling a variable function.

Post by micha »

In fact, you are not forced to preallocate anything in the map-table. Lua allows you to have a table with gaps in it. For example this is totally possible:

Code: Select all

someVariable = {}
someVariable[1] = 5
someVariable[3] = 6
someVariable[4] = 7
There is no entry for "2", but that is no problem. That also means that you can have the map totally empty in the beginning and only if you add something, then you create the entry. Read the code that I wrote in the post above once more. It does exactly this: Whenever you set a value in the map, the code first checks if the outer table exists. If not, then it is created. Then the actual object is placed into the table.

And the reason, why your code does not work is, because this line:

Code: Select all

mapStats[limit] = height
should replaced by one of these two:

Code: Select all

mapStats["limit"] = height
mapStats.limit = height
Either add quotation marks or use the dot-notation. In your original code, you called the table with a key limit but, the was no variable called limit, so that was the same as calling mapStats[nil] and that does not work.
User avatar
Tjakka5
Party member
Posts: 243
Joined: Thu Dec 26, 2013 12:17 pm

Re: Calling a variable function.

Post by Tjakka5 »

Aaaah, that makes so much sense.
In fact, that explains a lot of similair problems I had with other tests.

Marking this thread as solved; Thanks so much!
Post Reply

Who is online

Users browsing this forum: Bing [Bot], Google [Bot] and 199 guests