I'm using a game-making framework implemented in Lua to keep busy in quarantine. I'm making a simple platformer with ECS/DOP, and I wanted to generate collision geometry derived from a tilemap rather than just checking for collisions with all tiles.
Each tile has a bounding box component that points to a list that contains the basic shapes. Each shape stores the edges of the bounding box as {{x1,y1},{x2,y2}}. The first step in this process is to parse a TileMap table that contains only tilenames, then insert a copy of the corresponding bounding-box translated by the row/column * grid_dimension into a table named BBOX. The next step is to delete all instances of an edge if it is a duplicate illustrated by this image
which is where I'm stuck.
The desired, basic edge-deletion algorithm looks like this:
for i = #BBOX, 1, -1 do
local edge1 = BBOX[i]
for j = i, 1, -1 do
local edge2 = BBOX[j]
same_edge = edge1 == edge2 -- Not the actual comparison, just the outcome of it
if same_edge and i ~= j then
BBOX[i] = nil
BBOX[j] = nil
end
end
end
The issue is of course that this errors when i is equal to a j that was removed earlier in the loop. I've looked around and haven't been able to find a way to remove all instances of duplicate values in lua, only solutions that care about uniqueness. Has anyone found an efficient method of doing this or something similar?
If you use the value unique to each key you can better detect duplicates.
we will call this unique value edge.key.
If an edge will only exist at most twice then:
BBOX[edge.key] = BBOX[edge.key] == nil and edge or nil
will do the trick.
But where an edge may exist any arbitrary number of time we can do something like this:
local duplicateEdges = {}
for i = #BBOX, 1, -1 do
if BBOX[edge_key] then
duplicateEdges[edge_key] = true
else
BBOX[edge_key] = edge
end
for j = i, 1, -1 do
if BBOX[edge_key] then
duplicateEdges[edge_key] = true
else
BBOX[edge_key] = edge
end
end
end
for k in pairs(duplicateEdges) do
BBOX[k] = nil
end
Related
I don't know what the issue is exactly but I believe the heuristic function is doing something funky.
Breadth search works 100% normally.
But when I add in the heuristic function for priority it just suddenly does really weird stuff, resulting in completely strange paths.
Not just that but it doesn't even finish making the pathing grids (came-from, cost-so-far-and-such), stopping just before the ending.
Then when I try to add the A* part and it's still weird.
At first I didn't mind but after fiddling it just doesn't make sense, I've tried moving things around and trying to figure out if it's something to do with when it's inserted to the table or if it's the Manhattan distance heuristic itself but I've done everything as correctly as I can think to.
Here's my update code:
function updategraph(graph, frontier, camefrom, costsofar, goal)
-- continue while there are entries in the frontier
if #frontier > 0 then
local pos = frontier[1]
-- go over all neighbours
for i, p in ipairs(getcons(graph, pos)) do
local oldcost = costsofar[pos[1]] and costsofar[pos[1]][pos[2]] or 0
local newcost = (graph[p[1]][p[2]].cost or 1) + oldcost
local hascsf = (costsofar[p[1]] and costsofar[p[1]][p[2]])
local solid = (graph[p[1]] and graph[p[1]][p[2]] and graph[p[1]][p[2]].solid)
if not solid and
(not hascsf
or (hascsf and newcost < costsofar[pos[1]][pos[2]]))
then
if not camefrom[p[1]] then camefrom[p[1]] = {} end
camefrom[p[1]][p[2]] = pos
if not costsofar[p[1]] then costsofar[p[1]] = {} end
costsofar[p[1]][p[2]] = newcost
-- use heuristic to find the priority
local priority = math.max(1, math.min(#frontier, newcost + heuristic(goal, p)))
table.insert(frontier, priority, p)
-- if the current position is the goal
if goal[1] == p[1] and goal[2] == p[2] then
return false
end
end
end
-- remove from the frontier
table.remove(frontier, 1)
end
end
The heuristic:
function heuristic(goal, nxt)
-- manhattan distance on a square grid
return math.abs(goal[1] - nxt[1]) + math.abs(goal[2] - nxt[2])
end
Code for getting neighbours/connections:
function getcons(graph, pos)
-- acquire neighbours
local r = {}
-- make sure the grid space exists before adding it to the neighbours list
if graph[pos[1]+1] and graph[pos[1]+1][pos[2]] then
table.insert(r, {pos[1]+1, pos[2]})
end
if graph[pos[1]-1] and graph[pos[1]-1][pos[2]] then
table.insert(r, {pos[1]-1, pos[2]})
end
if graph[pos[1]] and graph[pos[1]][pos[2]+1] then
table.insert(r, {pos[1], pos[2]+1})
end
if graph[pos[1]] and graph[pos[1]][pos[2]-1] then
table.insert(r, {pos[1], pos[2]-1})
end
return r
end
Apologies if I've asked anything incorrectly, tell me and I'll give any more needed information.
I'm trying to work with some matrix in lua for my dungeon generator. Basically I'll have matrix [x][y] and a structure inside there will store the info of each "room". But since it is a generator, I don't know how many rooms I'll have, and the only way I know is to make something like this:
mat = {}
for i = 0, 10 do
mat[i] = {}
for j = 0, 10 do
mat[i][j] = 1
end
end
So the question is, is there a way to create a matrix that dynamic increases the size as I add data to it? because there will be blank spaces since the dungeon is going to be like a tree branch.
From Programming in Lua:
Moreover, tables have no fixed size; you can add as many elements as
you want to a table dynamically.
To handle access to non-existent table members and so avoid error messages for indexing nil values you can use a metatable implementing the __index metamethod.
In the following example Lua will insert an empty table into your table whenever it is not there yet.
Please refer to https://www.lua.org/manual/5.3/manual.html#2.4 for details
local mt_2D = {
__index =
function(t, k)
local inner = {}
rawset(t, k, inner)
return inner
end
}
local array2D = setmetatable({}, mt_2D)
array2D[2][5] = 'it works'
print(array2D[2][5]) --> it works
I'm trying to insert tensors of different dimensions into a lua table. But the insertion is writing the last tensor to all the previous elements in the table.
MWE:
require 'nn';
char = nn.LookupTable(100,10,0,1)
charRep = nn.Sequential():add(char):add(nn.Squeeze())
c = {}
c[1] = torch.IntTensor(5):random(1,100)
c[2] = torch.IntTensor(2):random(1,100)
c[3] = torch.IntTensor(3):random(1,100)
--This works fine
print(c)
charFeatures = {}
for i=1,3 do
charFeatures[i] = charRep:forward(c[i])
--table.insert(charFeatures, charRep:forward(c[i]))
-- No difference when table.insert is used
end
--This fails
print(charFeatures)
Maybe i haven't understood how tables work in Lua. But this code copies the last tensor to all previous charFeatures elements.
The issue is not related with tables, but is very common in Torch though. When you call the forward method on a neural net, its state value output is changed. Now when you save this value into charFeatures[i] you actually create a reference from charFeatures[i] to charRep.output. Then in the next iteration of the loop charRep.output is modified and consequently all the elements of charFeatures are modified too, since they point to the same value which is charRep.output.
Note that this behavior is the same as when you do
a = torch.Tensor(5):zero()
b = a
a[1] = 0
-- then b is also modified
Finally to solve your problem you should clone the output of the network:
charFeatures[i] = charRep:forward(c[i]):clone()
And all will work as expected!
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.
TL;DR Randomly access every tile in a tilemap
I have a way of generating random positions of tiles by just filling an entire layer of them (its only 10x10) then running a forloop like for (int x = 0; x < 13; x++)
{
for (int y = 0; y < 11; y++)}}
Where I randomly delete tiles. I also have a cap to this, which is at about 30. The problem is that when the loop runs over, it uses up the cap to the left (Because it starts at x=0 y=0 then does x0 x1 x2 x3 ...). I tried randomly generating coordinates, but this didn't work because it doesnt go over all the coordinates.
Does anyone know a better practice for scanning over every coordinate in a map in random order?
Number your tiles 0 to n anyway you want. Create a NSMutableIndexSet, and add all to it. Use a random number generator scaled to the number of items still in the index set (actually the range of first to last), grab one, then remove it from the set. If the random number is not in the set generate a new be, etc, until you find one in the set.
I think the best practice to accomplish this is via double hashing. To find out more read this link: Double Hashing. I will try to explain it simply.
You have two auxiliary hash functions which need to be pre-computed. And a main hash function which will run in a for loop. Lets test this out (this will be pseudo code):
key = random_number() //lets get a random number and call it "key"
module = map_size_x // map size for our module
//form of hash function 1 is: h1(key) = key % module, lets compute the hash 1 for our main hash function
aux1 = key % module
//form of hash function 2 is: h2(key) = 1 + (key % module'), where module' is module smaller for a small number (lets use 1), lets compute it:
aux2 = 1 + (key % (module - 1))
//the main hash function which will generate a random permutation is in the form of: h(key, index) = (h1(key) + index*h2(key)) % module. we already have h1 and h2 so lets loop this through:
for (i = 0; i < map_size_x; i++)
{
randomElement = (aux1 + i*aux2) % module //here we have index of the random element
//DO STUFF HERE
}
To get another permutation simply change the value of key. For more info check the link.
Hope this helps. Cheers.