Tutorial: Masters of the (Lua) Universe

Posted by

This tutorial is written by Jayant Varma of OZ Apps and is cross-posted from OZ’s Teach Me How To… blog. If you have a tutorial you’d like us to cross-post, let me know and we’ll get that ball rolling. :-)


Lua, a simple scripting language can be inspiring as it did for me. The best part of Lua is that if used properly, it can be a game changer, it can do things that one would have never thought of. It can be functional to an extent. Here’s a lovely example I found written by randrews.

I hope he does not mind me taking the code and talking about it here, after all it is available on Gist here . Well those that were here in for the ride can (or already have) clicked on the link and out of here.

For the rest that want to read on, well, here’s why I found this amazing and interesting and what can we learn from this.

The code makes lua like Prolog. For those that do not know what PROLOG is, it is an older programming language that worked on relationship and predicates. It coul dbe used and the principles can still be used to create connections and AI. So how does it make the code look like Prolog? Think if we had code like this, would that work in Lua?

father(Vader, Luke)
father(Vader, Leia)

friend(Vader, Emperor)
friend(Emperor, Vader)
friend(Han, Luke)
friend(Luke, Han)

brother(Luke, Leia)
sister(Leia, Luke)

assert(is_father(Vader, Luke))
assert(is_sister(Leia, Luke))
assert(is_friend(Han, Luke))
assert(is_friend(Luke, Han))

assert(not is_friend(Vader, Luke))
assert(not is_friend(Han, Jabba))

Not only that, you can add any more of these that you want, so for example

company(Jayant,OZApps)
 language(Corona, Lua)
 language(Cocos2D, ObjectiveC)

 print(is_language(Corona, ObjectiveC))
 print(is_company(Carlos, OZApps))

Obviously we do not have the functions defined for

father, friend, brother, sister and is_sister, is_father

, etc. This will therefore cause an error, but wouldn’t it be fun (it is not very useful really) to have the functionality to have these functions created automatically? Impossible you say? That’s what PROLOG was about and that is what we shall do in this example. Now do you see how cool and powerful LUA is or can be? How do we do that?

The way it works is it hijacks the setmetatable function, or rather in other words it uses that for purposes other than just defining an OO class prototype. Generally, the

__index

method is what is used to check for the existence of a variable, so what we do is we use this method and create the relevant variables and connections.

Let’s see how this works. Start a new project and just type the following code and run it.

 setmetatable(_G, {__index =
     function(globals, name)
       print(name)
     end })

 what(was, this)

You will see that you see some output in the console before it gives you a Runtime Error about Global what not being present, etc.

So you see that setmetatable looks for everything that is passed, you can extend the language (in some ways) but that’s for another day….

Now, we need to eliminate the Runtime Errors. So all we do is we use rawset as

setmetatable(_G, {__index =
     function(globals, name)
       print(name)
       rawset(globals, name, { })
     end })

 what(was, this)

we find that still we get a Runtime error as it does not find what, it is supposed to be a function. so what we do is we segregate if we have any word with an uppercase it is a variable and anything with a lowercase a function. So we can update our code as

setmetatable(_G, {__index =
     function(globals, name)
       print(name)
       if name:match("^[A-Z]") then -- entity
            rawset(globals, name, { })
       else
            rawset(globals,name,function() end)
       end

      return rawget(globals,name)
     end })

 what(Was, This)

This time when we run it, we get no errors, it all works just fine. However it is not very functional is it? This will still work with the combination of local variable, etc. So, to see it in action, let’s try

setmetatable(_G, {__index =
     function(globals, name)
       print(name)
       if name:match("^[A-Z]") then -- entity
            rawset(globals, name, { })
       else
            rawset(globals,name,function() end)
       end

      return rawget(globals,name)
     end })

 what(Was, This)

 local Name="OZApps"
 Url = "http://www.oz-apps.com"

 new(Company,Name)

You will see that in the console the variables, name and url are not displayed as they already exist in the context and need not be set for _G.

Now back to our example, we need to set relationships using the functions and query it using the

is_

prefix. Out setmetatable code changes a bit now to

setmetatable(_G,{ __index =
                  function(globals, name)

                  print(globals,name)

                     if name:match("^[A-Z]") then -- entity
                        rawset(globals, name, { })
                     else -- rule
                        local rule = make_rule(name)
                        rawset(globals, name, rule)
                     end
                     return rawget(globals, name)
                  end })

                 function make_rule(name)
                    return function(a, b)
                              a[name .. "_of"] = b
                              b[name] = a
                    end
                end

 what(Was, This)

 local Name="OZApps"
 Url = "http://www.oz-apps.com"

 new(Company,Name)

You will see that there is an error, as Name is local not available Globally in _G, so this will now work with non-local variables only here on (specifically for what we are attempting). Let us also add the

is_

prefix that will return the relationship.

setmetatable(_G,{ __index =
                  function(globals, name)

                  print(globals,name)

                     if name:match("^[A-Z]") then -- entity
                        rawset(globals, name, { })
                     elseif name:match("^is_") then -- predicate
                        local pred = make_predicate(name)
                        rawset(globals, name, pred)
                     else -- rule
                        local rule = make_rule(name)
                        rawset(globals, name, rule)
                     end
                     return rawget(globals, name)
                  end })

function make_rule(name)
         return function(a, b)
               a[name .. "_of"] = b
               b[name] = a
         end
end

function make_predicate(name)
--print("predicate")
   local rule = name:match("^is_(.*)")
   return
          function(a, b)
             return b[rule] == a
          end
end

 father(Vader,Luke)
 father(Vader,Leia)
 brother(Luke,Leia)
 sister(Luke,Leia)

 friend(Han, Luke)
 friend(Luke,Han)

 print(is_father(Vader,Luke))
 print(is_brother(Luke,Leia))
 print(is_sister(Luke,Leia))

 print(is_friend(Han, Luke))
 print(is_friend(Luke,Han))

If you see that relationships are one sided, so Luke is the Brother of Leia, but Leia is *NOT* the brother of Luke. So we have to set the two way friendship between Han and Luke. So you can see complex stuff like Betty -> likes Archie -> likes Veronica and Reggie -> likes Veronica. in the way we set up the setmetatable, all we need to do is

-- The metatable code and the two functions here

 likes(Betty, Archie)
 likes(Archie, Veronica)
 likes(Reggie, Veronica)

 print(is_likes(Archie, Betty))
 print(is_likes(Betty, Archie))
 print(is_likes(Reggie, Veronica))
 print(is_likes(Veronica, Archie))

Fun, eh??? And to think that it is just one simple setmetatable function that can help you achieve all of that. Lua can do some really cool stuff, if we know how to tame it.

Thanks to randrews for this code.

Ready to get started?

Create amazing games and apps for iOS & Android

6 Comments

Thomas Vanden AbeeleSeptember 28th, 2011 at 12:17 pm

I’m going to have to take some serious time off this weekend to make sense of this, but knowing Jayant’s material, it’ll be worth it!

ThySeptember 28th, 2011 at 9:16 pm

Whoa. Head exploding…

LergSeptember 29th, 2011 at 12:06 am

It would be useful if solving engine would also be implemented. Like if we set up
friend(‘Betty’, ‘Megan’)
friend(‘Megan’, ‘Polly’)

Then is_friend(‘Betty’, ‘Polly’) would also return true. But prolog stuff is really hard to understand and it’s just a convenient way of solving linear equations.
Need to think differently when designing such AI.

ThomasSeptember 29th, 2011 at 3:59 am

No comments about Princess Lua so far ;-)

OZAppsSeptember 29th, 2011 at 7:05 pm

@Lerg, This can be expanded if required to have a solving engine. But logically, if there is a common bond between two separate objects, does not necessarily imply that they have the same relationship.

I was thinking of this engine more in terms of Ontologies and solvers, where

isA(Animal,Dog)
isA(Animal,Cat)
isA(Feline,Cat)
is_feline(Dog)
isA(Dog,Alsatian)
isA(Dog,BorderCollie)
isA(Cat,Siamese)
is_cat(Siamese)
is_dog(Siamese)

to give a very basic example, then as Thy said, “Head exploding…” this is not for *basic* game making processes and borders on computer science more than IT.

Given that it can make things so much easier to work with if implemented properly.

cheers,

?:)

Nicholas GSeptember 30th, 2011 at 2:05 pm

Ahhhhhrrrrrrgh my head is about to go boom boom!

ng

Leave a comment

Your comment