Odhran Roche
Odhran Roche

Reputation: 1111

Looking out of bounds in a 2D Lua table

I sometimes make small games in Lua, and often have to implement a 2D array as a grid or a board. When I want to check the cells surrounding a particular cell I usually give the 2D array a metatable so that when grid[outOfBoundsNum] is indexed it returns an empty table instead of an error:

setmetatable(grid, {
    __index = 
    function(t, key)
        if not table[key] then
            return {}
        else
            return table[key]
        end
    end})

So when grid[outOfBoundsNum][anything] is called, it returns nil. Then, to check surrounding cells I do something like this :

for k, v in ipairs(neighbours) do
    local cell = grid[v[1][v[2]]
    if cell then -- check if this is actually within the 2D array
        if cell == 1 then 
            -- do something
        elseif cell == 2 then
            -- do something else
        ...
    end
end

This works, but it seems awkward to me. Is there a nicer or better way of doing it?

Upvotes: 3

Views: 1350

Answers (2)

Alex
Alex

Reputation: 96

You don't need the metatable.

for k, v in ipairs(neighbours) do
   local cell = grid[v[1]] and grid[v[1]][v[2]]

   if cell == 1 then 
      -- do something
   elseif cell == 2 then
      -- do something else
   ... 
   end 
end 

Should do the job. It is a relatively common lua idiom to use logical and and or in expressions to act like the ternary operator in C. So this line is equivalent to:

local cell = nil
if grid[v[1]]~=nil then
    cell = grid[v[1]][v[2]]
end

Upvotes: 3

Oberon
Oberon

Reputation: 3249

You could write a forEachNeighbor() function which would take the grid, a position and a function and then call it with each existing neighborfield, i.e. encapsule the loop and the outer if in your second snippet in a function, you would use like:

forEachNeighbor(grid, position, function(cell)
    if cell == 1 then
        -- do something
    elseif cell == 2 then
        -- do something else
    ...
end)

Also, you could provide an at() function which would take a grid position as one parameter and return the corresponding field or nil, so that grid[v[1]][v[2]] becomes at(grid, v). This could also be implemented in addition to or instead of the __index metamethod.

For the __index metamethod itself: First, you probably meant t instead of table and rawget(t, key) instead of t[key] (which would cause infinite recursion). But as lhf pointed out, the check is altogether unnecessary, because __index is only called when the key is not present in t. So you could just write:

__index = function(t, key)
    return {}
end

One last remark:

I sometimes make small games in Lua, and often have to implement a 2D array

Why don't you implement it once and reuse it in other games? That's what modules are for!

Upvotes: 2

Related Questions