I'm writing a lua LÖVE program as a school project.
The task is something about ants, that need to find food, take some to the nest they came from and on the way leaving a trace of pheromons. In addition we've write a program visualizing the process. For 100 ants, 5 food sources and all this in a space of 500x500 squares
I chose lua LÖVE for the visualization and wrote the following code:
function love.load()
p = 500 -- Starting position
xNest, yNest = p, p -- Initializing nest position
xAnt1, yAnt1 = p, p -- Initializing ant position
xAnt2, yAnt2 = p, p
end
-- Changes position every frame.
function love.update(dt)
-- AntI // See what I did there?
xAnt1 = xAnt1 + math.random (-2, 2) -- Change position by a random number between 2 steps forward and 2 steps backward
yAnt1 = yAnt1 + math.random (-2, 2) -- Change position by a random number between 2 steps sideways
xAnt2 = xAnt2 + math.random (-2, 2)
yAnt2 = yAnt2 + math.random (-2, 2)
end
-- Draw ants and nest.
function love.draw()
-- Nest
love.graphics.setColor(0, 255, 255) -- set drawing color green
love.graphics.rectangle("line", xNest, yNest, 2, 2) -- draw a nest at xNest, yNest with a size of 2x2
-- Ant
love.graphics.setColor(255, 255, 255) -- set drawing color white
love.graphics.rectangle("line", xAnt1, yAnt1, 2, 2) -- draw an ant at xAnt(number of ant), yAnt(number of ant) with a size of 2x2
love.graphics.rectangle("line", xAnt2, yAnt2, 2, 2)
end
Since my task is to do what I did in
xAntX, yAntX = p, p 100 times, whereby X I mean the number for the ant, I need some kind of loop that creates xAntX, yAntX = p, p, xAntX = xAntX + math.random (-2, 2) , yAntX = yAntX + math.random (-2, 2) and love.graphics.rectangle("line", xAntX, yAntX, 2, 2 a 100 times.
I tried a for loop, but it always yelled at me for trying to append a variable ´i´ to the initialization xAnt .. i, yAnt .. i and then count i++ with i = i + 1.
Make xAnt and yAnt tables, and access individual entries as xAnt[i] and yAnt[i].
While this question is quite old, if you're new to Lua and ended up here with the same question, I'd like to propose a different solution which is in my opinion more elegant than the accepted answer:
One simple method of storing multiple properties in a structured way is to store them as nested "object"-like tables in an "array"-like table. For example, initialise an empty ants table like so:
local ants = {}
Then, use a numeric for to add the desired amount of ants to the ants table:
for i = 1, 100 do
ants[#ants + 1] = { x: ..., y: ... }
end
Each ant itself is also a table, but instead of numeric keys, it uses string keys. See Lua's documentation on tables for more information. Now, you can loop over all the ants in the list and update each ant using:
for _, ant in ipairs(ants) do
ant.x = ant.x + math.random(-2, 2)
ant.y = ant.y + math.random(-2, 2)
end
Specific ants may be updated directly through their index. To update the second ant, use ants[2].
The benefit of this approach is that you only have one global variable which holds all your ants, instead of having separate global variables for each individual property of each ant. This generally makes the code easier to reason about and aids in debugging as well.
Happy developing fellow Luanatics :D
Related
I am trying to teleport the player, but every time I run the script, it teleports the player to 0,0,0 (using a classic style character):
Character.Torso.CFrame = CFrame.new(-7000, 3467, -2380.982 + (g * -10));
Character:SetPrimaryPartCFrame(CFrame.Angles(math.rad(0), math.rad(-90), math.rad(0)));
(g = 1, and character has already been defined)
#Universal Link, I tried your method (adding more arguments), and this is what I got:
Character.Torso.CFrame = CFrame.new(Character.Torso.Position, Vector3.new(-10000, 30467, -2380.982 + (g * -10)));
Character:SetPrimaryPartCFrame(CFrame.Angles(math.rad(0), math.rad(-90), math.rad(0)));
However, the character is still teleported to 0,0,0. I tried removing the second line of code by commenting it out:
Character.Torso.CFrame = CFrame.new(Character.Torso.Position, Vector3.new(-10000, 30467, -2380.982 + (g * -10)));
--Character:SetPrimaryPartCFrame(CFrame.Angles(math.rad(0), math.rad(-90), math.rad(0)));
But then the character doesn't get teleported anywhere. Also, what do you mean by referencing the CFrame with serviceprovider?
There could be a couple of things going on here...
1:
In your first statement (line 1), you haven't provided CFrame with enough arguments. Take into account this statement from Roblox's Wiki:
game.Workspace.Part.CFrame = CFrame.new(Workspace.Part.Position, Vector3.new(0, 75, 75))
Vector3 allows the Lua engine to know you want to move the object 3 Dimensionally anywhere in the workspace. Otherwise the Lua engine disregards what you have put at the end and just moves the part to the center of the game (as it doesn't know what else to do).
2:
The script might not be directly referencing CFrame. In my experience of animating parts in the workspace, I needed to reference the CFrame. You can do this by calling the function from serviceprovider.
Take a look at the Wiki for more information:
http://wiki.roblox.com/index.php?title=CFrame#Quick_Reference
This is a little library I was making for the LOVE2D engine in Lua, which uses separating axis theorem to solve collisions.
I was so happy when I got my SAT program to work, and started testing it with a multitude of polygons. It works in most cases, and gives a correct minimum translation vector for them too. Oddly enough- if both shapes have acute angles, then those angles cause the program to fail, returning collisions when the shape isn't touching, or even more unusually, it gives a bizarre minimum translation vector. I have checked my function that returns normals- as I felt that this was my first point that could have failed, but it seems to be working fine.
This is the main function that handles my collision.
function findIntersection(shape1, shape2)
--Get axes to test.
--MTV means 'minimum translation vector' ie. the shortest vector of intersection
local axes1 = {}
local axes2 = {}
local overlap = false
local MTV = {direction = 0, magnitude = 99999}
for i, vert in pairs(shape1.hitbox) do
nrm = getNormal(shape1.hitbox, i)
table.insert(axes1, nrm)
end
for i, vert in pairs(shape2.hitbox)do
nrm = getNormal(shape2.hitbox, i)
table.insert(axes2, nrm)
end
--print(#axes1 .. ' ' .. #axes2)
--now that we have the axes, we have to project along each of them
for i, axis in pairs(axes1) do
test1 = hitboxProj(shape1, vectorToCoord(axis.direction, axis.magnitude))
test2 = hitboxProj(shape2, vectorToCoord(axis.direction, axis.magnitude))
if test2.max > test1.min or test1.max > test2.min then
if test2.max - test1.min < MTV.magnitude then
MTV.direction = axes1[i].direction
MTV.magnitude = test2.max - test1.min
end
else
return false
end
end
--now that we have the axes, we have to project along each of them
for i, axis in pairs(axes2) do
test1 = hitboxProj(shape1, vectorToCoord(axis.direction, axis.magnitude))
test2 = hitboxProj(shape2, vectorToCoord(axis.direction, axis.magnitude))
if test2.max > test1.min or test1.max > test2.min then
if test2.max - test1.min < MTV.magnitude then
MTV.direction = axes2[i].direction
MTV.magnitude = test2.max - test1.min
end
else
return false
end
end
return {MTV}
end
My project files are here on github https://github.com/ToffeeGoat/ToffeeCollision
It's a good start and your code is fairly clear. There are some things which can be improved, in particular I see that all of your functions are global. For starters you want to store all of your functions in a "module" to avoid polluting the _G space. You can use locals for everything else.
Note that it's not robust to write things like x == 0 this check will only work for integers and may fail when floating point math is involved. I recommend writing a simple test script for each function in your library.
Also, it's not efficient to write return {x = xCoord, y = yCoord} when you can return multiple values with Lua return xCoord, yCoord. Creating a lot of intermediate tables puts a strain on the garbage collector.
Some of your code needs reworking like the "getDirection" function. I mean there are already well-known techniques for this sort of thing. Check out my tutorial for examples: https://2dengine.com/?p=vectors#Angle_between_two_vectors
There is some silly stuff in there like function sq(x) return x*x end. Did you know you can write x^2 in Lua?
addDeg can be replaced by the modulo operator: newAng = (angle + addition)%360
Also note that there is absolutely no benefit to working with degrees - I recommend using only radians. You are already using math.pi which is in radians. Either way you have to pick either radians or degrees and stick to one or the other. Don't use both units in your code.
I don't want to nitpick too much because your code is not bad, you just need to get used to some of the best practices. Here is another one of my tutorials:
https://2dengine.com/?p=intersections
for i = 1,5 do
local link = {}
for j = 1,20 do
link[j] = display.newImage( "link.png" )
-
List item
link[j].x = 121 + (i*34)
link[j].y = 55 + (j*17)
physics.addBody( link[j], { density=2.0, friction=0, bounce=0 } )
-- Create joints between links
if (j > 1) then
prevLink = link[j-1] -- each link is joined with the one above it
else
prevLink = beam -- top link is joined to overhanging beam
end
myJoints[#myJoints + 1] = physics.newJoint( "pivot", prevLink, link[j], 121 + (i*34), 46 + (j*17) )
end
end
What does this mean?
From the Corona SDK's manual:
A pivot joint, known as a revolute joint in Box2D terms, joins two
bodies at an overlapping point, like two boards joined by a rotating
peg. The initial arguments are bodies A and B to join, followed by the
x and y coordinates for the anchor point, declared in content space
coordinates.
local pivotJoint = physics.newJoint( "pivot", bodyA, bodyB, anchor_x, anchor_y )
So the values you ask about are anchor coordinates (the point where both bodies are linked)
The number values are just values someone picked to make it look like he wanted it to.
Just alter them and see the links move!
That's a general advice. If you don't know what something does, refer to the manual and if you cannot break anything (which is the case pretty much always) try what happens if you change it.
That's the only way you can understand things.
I am working on programming a Markov chain in Lua, and one element of this requires me to uniformly generate random numbers. Here is a simplified example to illustrate my question:
example = function(x)
local r = math.random(1,10)
print(r)
return x[r]
end
exampleArray = {"a","b","c","d","e","f","g","h","i","j"}
print(example(exampleArray))
My issue is that when I re-run this program multiple times (mash F5) the exact same random number is generated resulting in the example function selecting the exact same array element. However, if I include many calls to the example function within the single program by repeating the print line at the end many times I get suitable random results.
This is not my intention as a proper Markov pseudo-random text generator should be able to run the same program with the same inputs multiple times and output different pseudo-random text every time. I have tried resetting the seed using math.randomseed(os.time()) and this makes it so the random number distribution is no longer uniform. My goal is to be able to re-run the above program and receive a randomly selected number every time.
You need to run math.randomseed() once before using math.random(), like this:
math.randomseed(os.time())
From your comment that you saw the first number is still the same. This is caused by the implementation of random generator in some platforms.
The solution is to pop some random numbers before using them for real:
math.randomseed(os.time())
math.random(); math.random(); math.random()
Note that the standard C library random() is usually not so uniformly random, a better solution is to use a better random generator if your platform provides one.
Reference: Lua Math Library
Standard C random numbers generator used in Lua isn't guananteed to be good for simulation. The words "Markov chain" suggest that you may need a better one. Here's a generator widely used for Monte-Carlo calculations:
local A1, A2 = 727595, 798405 -- 5^17=D20*A1+A2
local D20, D40 = 1048576, 1099511627776 -- 2^20, 2^40
local X1, X2 = 0, 1
function rand()
local U = X2*A2
local V = (X1*A2 + X2*A1) % D20
V = (V*D20 + U) % D40
X1 = math.floor(V/D20)
X2 = V - X1*D20
return V/D40
end
It generates a number between 0 and 1, so r = math.floor(rand()*10) + 1 would go into your example.
(That's multiplicative random number generator with period 2^38, multiplier 5^17 and modulo 2^40, original Pascal code by http://osmf.sscc.ru/~smp/)
math.randomseed(os.clock()*100000000000)
for i=1,3 do
math.random(10000, 65000)
end
Always results in new random numbers. Changing the seed value will ensure randomness. Don't follow os.time() because it is the epoch time and changes after one second but os.clock() won't have the same value at any close instance.
There's the Luaossl library solution: (https://github.com/wahern/luaossl)
local rand = require "openssl.rand"
local randominteger
if rand.ready() then -- rand has been properly seeded
-- Returns a cryptographically strong uniform random integer in the interval [0, n−1].
randominteger = rand.uniform(99) + 1 -- randomizes an integer from range 1 to 100
end
http://25thandclement.com/~william/projects/luaossl.pdf
I am making a roguelike in Love2D as a hobby project. My approach is to try and use as much of the native capabilities of Lua and the Love2D (0.10.1) API as possible, without relying on fancy libraries like middleclass or HUMP, so as to learn more about the language.
After reading PiL's chapters on OOP and seeing the power there, I decided to set up a Mob class (using metamethods to emulate class functionality) that encompasses the players, monsters, and other NPCs (anything that can move). So, far, it's working beautifully, I can create all kinds of instances easily that share methods and all that stuff. But there's a lot of things I don't know how to do, yet, and one of them is holding my prototype up from further progress.
Setting up collision with the map itself wasn't too bad. My maps are tables full of tables full of integers, with 0 being the floor. The game draws "." and "#" and "+" and such to denote various inanimate objects, from each table. Player 1 moves using the numpad, and their position is tracked by dividing their raw pixel position by 32 to create a grid of 32x32 "tiles". Then, inside love.keypressed(key), I have lines like:
if key == "kp8" and currentmap[player1.grid_y - 1][player1.grid_x] == 0 then
player1.grid_y = player1.grid_y - 1
and so on, with elseifs for each key the player can press. This prevents them from walking through anything that isn't an open floor tile in the map itself.
But, I'm trying to implement some kind of "collision detection" to prevent MOBs from walking through each other and to use in writing the rules for combat, and this is trickier. I had a method in place to calculate the distance between mobs, but I'm told this might eventually cause rounding errors, plus it had to be written for each combination of mobs I want to test, individually.
What I'd like to know is: Is there a known (preferably elegant) way to get all instances of a particular class to pass some number of values to a table?
What I'd like to do is "ask" every Mob on a given map where they are, and have them "report" self.grid_x and self.grid_y to another layer of map that's just for tracking mobs (1 if self.is_here is true, 0 if not, or similar), that gets updated every turn. Then, I could implement collision rules based on coordinates being equal, or maybe a foo.is_here flag or something.
I have only vague ideas about how to proceed, however. Any help would be appreciated, including (and maybe especially) feedback as to a better way to do what I'm trying to do. Thanks!
A simple idea is to store "who is here" information for every cell of the field and update this information on every move of every object.
function create_game_field()
-- initialize a table for storing "who is here" information
who_is_here = {}
for y = 1,24 do
who_is_here[y] = {}
for x = 1,38 do
who_is_here[y][x] = 0
end
end
end
function Mob:can_move(dx, dy)
local u = currentmap[self.y + dy][self.x + dx]
local v = who_is_here[self.y + dy][self.x + dx]
if u == 0 and v == 0 then
return true
else
end
end
function Mob:move(dx, dy)
-- update "who is here"
who_is_here[self.y][self.x] = 0
self.x, self.y = self.x + dx, self.y + dy
who_is_here[self.y][self.x] = 1
end
function Mob:who_is_there(dx, dy) -- look who is standing on adjacent cell
return who_is_here[self.y + dy][self.x + dx] -- return mob or nil
end
function Mob:roll_call()
who_is_here[self.y][self.x] = 1
end
Usage example:
-- player1 spawns in at (6,9) on the grid coords
player1 = Mob:spawn(6,9)
-- player1 added to who_is_here
player1:roll_call()
Then, in love.keypressed(key):
if key == "kp8" and player1:can_move(0, -1) then
player1:move(0, -1)
end
There are a few ways you could get all your instances data but one of the simpler ones is probably to have them all be added to a table when they are created. Providing you add the entire table for that instance, all the values will update in the main table because it acts like a collection of pointers.
function mob:new( x, y, type )
self.x = 100
self.y = 200
self.type = type
-- any other declarations you need
table.insert(allMobs, self)
return self
end
Here we insert all the mobs into the table 'allMobs'. Once we have that we can simply iterate through and get all our coordinates.
for i, v in ipairs(allMobs) do
local x, y = v.x, v.y
-- Do whatever you need with the coordinates. Add them to another table, compare
-- them to others, etc.
end
Now we have a table with all our mobs in it and a way to access each of their positions. If you have any further inquiries then let me know.