local Z=setmetatable local I=ipairs local S=pairs local C={directions={north={x=0,y=-1};east={x=1;y=0};south={x=0,y=1};west={x=-1;y=0}}}function C.new(L,I,S,C,f)f=f or{}Z(f,L)L.__index=L for Z=1,S,1 do f[Z]={}for I=1,I,1 do f[Z][I]={east=f:CreateDoor(C),south=f:CreateDoor(C)}if I~=1 then f[Z][I].west=f[Z][I-1].east else f[Z][I].west=f:CreateDoor(C)end if Z~=1 then f[Z][I].north=f[Z-1][I].south else f[Z][I].north=L:CreateDoor(C)end end end return f end function C.width(Z)return#Z[1]end function C.height(Z)return#Z end function C.DirectionsFrom(f,Z,I,C)local L={}C=C or function()return true end for S,n in S(f.directions)do local y,i=Z+n.x,I+n.y if f[i]and(f[i][y]and C(f[i][y],y,i))then L[#L+1]={name=S,x=y,y=i}end end return L end function C.ResetDoors(C,Z,S)for f=1,#C,1 do for I,C in I(C[f])do C.north:SetClosed(Z or f==1 and not S)C.west:SetClosed(Z)end C[f][1].west:SetClosed(Z or not S)C[f][#C[1]].east:SetClosed(Z or not S)end for I,C in I(C[#C])do C.south:SetClosed(Z or not S)end end function C.ResetVisited(Z)for I=1,#Z,1 do for S=1,#Z[1],1 do Z[I][S].visited=nil end end end function C.tostring(Z,S,C)S=S or"\035"C=C or" "local f=""local L=""for I=1,#Z[1],1 do L=L..(S..(Z[1][I].north:IsClosed()and S or C))end L=L..S f=f..(L.."\n")for Z,L in I(Z)do local n=L[1].west:IsClosed()and S or C local y=S for Z,I in I(L)do n=n..(" "..(I.east:IsClosed()and S or C))y=y..((I.south:IsClosed()and S or C)..S)end f=f..(n..("\n"..(y.."\n")))end return f end C.__tostring=C.tostring function C.CreateDoor(I,Z)local S={}S.closed=Z and true or false function S.IsClosed(Z)return Z.closed end function S.IsOpened(Z)return not Z.closed end function S.Close(Z)Z.closed=true end function S.Open(Z)Z.closed=false end function S.SetOpened(I,Z)if Z then I:Open()else I:Close()end end function S.SetClosed(I,Z)I:SetOpened(not Z)end return S end return C
local setmetatable = setmetatable
local ipairs = ipairs
local pairs = pairs

local Maze = {
	directions = {
		north = {
			x = 0,
			y = -1,
		},
		east = {
			x = 1,
			y = 0,
		},
		south = {
			x = 0,
			y = 1,
		},
		west = {
			x = -1,
			y = 0,
		},
	},
}

function Maze:new(width, height, closed, obj)
	obj = obj or {}
	setmetatable(obj, self)
	self.__index = self

	-- Actual maze setup
	for y = 1, height do
		obj[y] = {}
		for x = 1, width do
			obj[y][x] = {
				east = obj:CreateDoor(closed),
				south = obj:CreateDoor(closed),
			}

			-- Doors are shared beetween the cells to avoid out of sync conditions and data dublication
			if x ~= 1 then
				obj[y][x].west = obj[y][x - 1].east
			else
				obj[y][x].west = obj:CreateDoor(closed)
			end

			if y ~= 1 then
				obj[y][x].north = obj[y - 1][x].south
			else
				obj[y][x].north = self:CreateDoor(closed)
			end
		end
	end

	return obj
end

function Maze:width()
	return #self[1]
end

function Maze:height()
	return #self
end

function Maze:DirectionsFrom(x, y, validator)
	local directions = {}
	validator = validator or function()
		return true
	end

	for name, shift in pairs(self.directions) do
		local x, y = x + shift.x, y + shift.y

		if self[y] and self[y][x] and validator(self[y][x], x, y) then
			directions[#directions + 1] = {
				name = name,
				x = x,
				y = y,
			}
		end
	end

	return directions
end

function Maze:ResetDoors(close, borders)
	for y = 1, #self do
		for i, cell in ipairs(self[y]) do
			cell.north:SetClosed(close or y == 1 and not borders)
			cell.west:SetClosed(close)
		end

		self[y][1].west:SetClosed(close or not borders)
		self[y][#self[1]].east:SetClosed(close or not borders)
	end

	for i, cell in ipairs(self[#self]) do
		cell.south:SetClosed(close or not borders)
	end
end

function Maze:ResetVisited()
	for y = 1, #self do
		for x = 1, #self[1] do
			self[y][x].visited = nil
		end
	end
end

function Maze.tostring(maze, wall, passage)
	wall = wall or '#'
	passage = passage or ' '

	local result = ''

	local verticalBorder = ''
	for i = 1, #maze[1] do
		verticalBorder = verticalBorder .. wall .. (maze[1][i].north:IsClosed() and wall or passage)
	end
	verticalBorder = verticalBorder .. wall
	result = result .. verticalBorder .. '\n'

	for y, row in ipairs(maze) do
		local line = row[1].west:IsClosed() and wall or passage
		local underline = wall
		for x, cell in ipairs(row) do
			line = line .. ' ' .. (cell.east:IsClosed() and wall or passage)
			underline = underline .. (cell.south:IsClosed() and wall or passage) .. wall
		end
		result = result .. line .. '\n' .. underline .. '\n'
	end

	return result
end

Maze.__tostring = Maze.tostring

function Maze:CreateDoor(closed)
	local door = {}
	door.closed = closed and true or false

	function door:IsClosed()
		return self.closed
	end

	function door:IsOpened()
		return not self.closed
	end

	function door:Close()
		self.closed = true
	end

	function door:Open()
		self.closed = false
	end

	function door:SetOpened(opened)
		if opened then
			self:Open()
		else
			self:Close()
		end
	end

	function door:SetClosed(closed)
		self:SetOpened(not closed)
	end

	return door
end

return Maze