Tutorial: Masters of the (Lua) Universe
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
, 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
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
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
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.
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!
Whoa. Head exploding…
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.
No comments about Princess Lua so far
@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,
?:)
Ahhhhhrrrrrrgh my head is about to go boom boom!
ng