Jump To …

Map.lua

module(..., package.seeall)
local utils = require(_PACKAGE .. 'utils')
local Point = require(_PACKAGE .. 'Point')
local List = require(_PACKAGE .. 'List')

local Map = utils.public_class('Map')

function Map:initialize(width, height, fill)
    fill = fill or 0
    self.width = width
    self.height = height

    local c = {}
    for i=1, width*height do c[i]=fill end
    self.cells = List(c)
end

function Map.static.new_from_strings(strs)
   assert(type(strs) == 'table' and type(strs[1])=='string')
   local l = Map(#(strs[1]), #strs)
   for p in l:each() do
      local s = strs[p.y+1]:sub(p.x+1, p.x+1)
      l:at(p, s)
   end
   return l
end

function Map.static.new_from_string(str)
   assert(type(str) == 'string')
   local strs = {}
   local line = ''
   for n = 1, #str do
      local c = str:sub(n,n)
      if c == "\n" then
         table.insert(strs, line)
         line = ''
      else
         line = line .. c
      end
   end

   return Map.new_from_strings(strs)
end

function Map:at(pt, val)
   if self:inside(pt) then
      if val~=nil then self.cells[pt.x+pt.y*self.width] = val end
      return self.cells[pt.x+pt.y*self.width]
   else
      return nil
   end
end

Map.__call = Map.at

function Map:clamp(pt)
   pt = pt:copy()
   if pt.x < 0 then pt.x = 0 end
   if pt.x > self.width-1 then pt.x = self.width-1 end
   if pt.y < 0 then pt.y = 0 end
   if pt.y > self.height-1 then pt.y = self.height-1 end
   return pt
end

function Map:inside(pt)
   return pt >= Point(0, 0) and pt < Point(self.width, self.height)
end

function Map:clear(value)
   for p in self:each() do
      self:at(p, value)
   end
end

function Map:each(start, w, h)
   local maxx, maxy

   if w then maxx = start.x + w-1 else maxx = self.width-1 end
   if h then maxy = start.y + h-1 else maxy = self.height-1 end

   start = start or Point(0, 0)
   local p = start

   return function()
             local r = p -- return this one...

Decide what the next one will be:

             p = p + Point(1, 0)
             if p.x > maxx then p = Point(start.x, p.y+1) end

             if r.y > maxy then return nil
             else return r, self:at(r) end
          end
end

function Map:__tostring()
   local s = ''

   for y = 0, self.height-1 do
      for x = 0, self.width-1 do
         s = s .. tostring(self:at(Point(x,y))) .. ' '
      end
      s = s .. "\n"
   end

   return s
end

function Map:find(fn)
   assert(type(fn) == 'function')
   local fit = List{}
   for pt in self:each() do
      if fn(self, pt) then fit:push(pt) end
   end
   return fit
end

function Map:find_value(value)
   local fn = function(map, pt) return map(pt) == value end
   return self:find(fn)
end

function Map:random(fn)
   fn = fn or function() return true end
   local fit = self:find(fn)
   if fit:empty() then return nil
   else return fit:random() end
end

----------------------------------------

function Map:empty(pt)
   return self:at(pt) == ''
end

function Map:full(pt)
   return not self:empty(pt)
end

function Map:neighbors(pt, fn, diag)
   local all = {pt + Point(-1, 0),
                pt + Point(1, 0),
                pt + Point(0, -1),
                pt + Point(0, 1)}

   if diag then
       table.insert(all, pt+Point.southwest)
       table.insert(all, pt+Point.northwest)
       table.insert(all, pt+Point.northeast)
       table.insert(all, pt+Point.southeast)
   end

   if fn and type(fn) ~= 'function' then
       local val = fn
       fn = function(_, p) return self:at(p) == val end
   end

   local fit = List{}
   for _, p in ipairs(all) do
       if self:inside(p) and (not fn or fn(self, p)) then fit:push(p) end
   end
   return fit
end

function Map:connected(start, fn, diag)

Turn a point into a number so we can use it a a table key

    local function num(pt) return pt.x + pt.y * self.width end
    local points = {[num(start)]=start} -- maps from x+y*width to Point
    local closed = {}
    local open = List{start}

    while not open:empty() do
        local p = open:shift()
        local n = self:neighbors(p, fn, diag)

        n:each(function(pt)
                   if not closed[num(pt)] then
                       open:push(pt)
                       points[num(pt)] = pt
                   end
               end)

        closed[num(p)] = p
    end

    local points_list = List()
    for _, p in pairs(points) do points_list:push(p) end
    return points_list
end

function Map:connected_value(start, value, diag)
    return self:connected(start,
                          function(map, p) return map:at(p) == value end,
                          diag)
end

----------------------------------------

function Map.static.test()

Constructor

   local m = Map(10, 10)
   assert(m.width == 10)
   assert(m:inside(Point(3,3)))
   assert(not m:inside(Point(10,10)))

clear / set

   m:clear(0)
   m:at(Point(3,2),1)

at

   assert(m:at(Point(1,1)) == 0)
   assert(m:at(Point(3,2)) == 1)
   assert(m:at(Point(10,10)) == nil)

each

   local n = 0
   for p in m:each() do n = n + 1 end
   assert(n == 100)

fit

   m:clear('')
   m:at(Point(1,0),1)
   assert(m:neighbors(Point(5,5)):length() == 4)
   assert(m:neighbors(Point(0,1)):length() == 3)
   assert(m:neighbors(Point(0,0)):length() == 2)
   assert(m:neighbors(Point(0,0), m.empty):length() == 1)

   local n2 = 0
   for p in m:each(Point(2, 2), 4, 4) do n2 = n2 + 1 end
   assert(n2 == 16)
end

return Map