Jump to content

Module:User:Cscott/Advent Of Code 2023/Day 16

From Wikipedia, the free encyclopedia
return (function()
local builders = {}
local function register(name, f)
  builders[name] = f
end
register('llpeg', function() return require [[Module:User:Cscott/llpeg]] end)

register('day16', function(myrequire)
--[[ DAY 16 ]]--
local l = myrequire('llpeg')

--[[ PARSING ]]--
local Spot = {}
Spot.__index = Spot
function Spot:new(args)
   return setmetatable(args, self)
end
function Spot:is_mirror() return self.char=='/' or self.char=='\\' end
function Spot:is_splitter() return self.char=='-' or self.char=='|' end
function Spot:is_empty() return self.char=='.' end
function Spot:__tostring()
   return self.char
end

local nl = l.P"\n"

function make_spot(s)
   return Spot:new{char=s}
end

local patt = l.P{
   "Graph",
   Graph = l.Ct( l.V"Row" * (nl^1 * l.V"Row")^0 * nl^0) * -1,
   Row = l.Ct( l.V"Spot"^1 ),
   Spot = l.S".\\/-|" / make_spot,
}

local Graph = {}
Graph.__index = Graph

function parse(source)
   --print(inspect(source))
   local ast, errlabel, pos = patt:match(source)
   if not ast then
      error(string.format("Error at pos %s label '%s'", pos, errlabel))
   end
   --print('Parsed with success!')
   --print(inspect(ast))
   return Graph:new(ast)
end

--[[ Part 1 ]]--

function Graph:new(data)
   return setmetatable({ data=data }, self)
end

function Graph:at(row,col,default)
   return (self.data[row] or {})[col] or default
end

function Graph:rowN()
   return #(self.data)
end

function Graph:colN()
   return #(self.data[1])
end

function Graph:print()
   for r,row in ipairs(self.data) do
      for c,val in ipairs(row) do
         if val == nil then
            val = " "
         elseif val.energized then
            val = "#"
         end
         io.write(tostring(val))
      end
      io.write("\n")
   end
end

function Graph:link()
   for r=1,self:rowN() do
      for c=1,self:colN() do
         local sp = self:at(r,c)
         sp.r, sp.c = r,c
         if r > 1 then sp.n = self:at(r-1,c) end
         if c > 1 then sp.w = self:at(r,c-1) end
         if r < self:rowN() then sp.s = self:at(r+1,c) end
         if c < self:colN() then sp.e = self:at(r,c+1) end
      end
   end
end

function Graph:clearAndScore()
   local sum = 0
   for r=1,self:rowN() do
      for c=1,self:colN() do
         local sp = self:at(r,c)
         if sp.energized then
            sum = sum + 1
            sp.energized = nil
            sp.seen_n = nil
            sp.seen_e = nil
            sp.seen_w = nil
            sp.seen_s = nil
         end
      end
   end
   return sum
end

local mirror_effect = {
   -- east becomes north, north -> east, south-west, west-south
   ['/'] = { e='n', n='e', s='w', w='s' },
   -- east becomes south, south->east, north->west, west->north
   ['\\'] = { e='s', s='e', n='w', w='n' },
   ['|'] = { e='ns', w='ns', n='n', s='s' },
   ['-'] = { e='e', w='w', n='ew', s='ew' },
   ['.'] = { n='n', e='e', s='s', w='w' },
}

function ray_cast(sp, dir)
   if sp['seen_'..dir] ~= nil then return end
   sp.energized = true
   sp['seen_'..dir] = true
   local ndir = mirror_effect[sp.char][dir]
   if #ndir == 1 then
      local nsp = sp[ndir]
      if nsp ~= nil then
         return ray_cast(nsp, ndir) -- tail call
      end
   else
      for i=1,#ndir do
         local nsp = sp[ndir:sub(i,i)]
         if nsp ~= nil then
            ray_cast(nsp, ndir:sub(i,i))
         end
      end
   end
end

function part1(source)
   local graph = parse(source)
   graph:link()
   --graph:print()
   --print()
   ray_cast(graph:at(1,1),"e")
   --graph:print()
   return graph:clearAndScore()
end

function part2(source)
   local graph = parse(source)
   graph:link()
   local max = 0
   local function check(r,c,dir)
      ray_cast(graph:at(r,c),dir)
      local score = graph:clearAndScore()
      if score > max then max = score end
   end
   for r=1,graph:rowN() do
      check(r,1,"e")
      check(r,graph:colN(),"w")
   end
   for c=1,graph:colN() do
      check(1,c,"s")
      check(graph:rowN(),c,"n")
   end
   return max
end

--[[ CLI ] ]--
local source = io.input("day16.input"):read("a")
print('Sum:', part1(source))
print('Sum:', part2(source))
--[ [ END CLI ]]--

return {
   part1 = function(frame)
      local s = mw.title.new(frame.args[1]):getContent()
      return part1(s)
   end,
   part2 = function(frame)
      local s = mw.title.new(frame.args[1]):getContent()
      return part2(s, tonumber(frame.args[2]))
   end,
}

end)

local modules = {}
modules['table'] = require('table')
modules['string'] = require('string')
modules['strict'] = {}
local function myrequire(name)
  if modules[name] == nil then
    modules[name] = true
    modules[name] = (builders[name])(myrequire)
  end
  return modules[name]
end
return myrequire('day16')
end)()