local A=setmetatable local p=ipairs local f=pairs local R={directions={north={x=0;y=-1};east={x=1;y=0};south={x=0,y=1},west={x=-1,y=0}}}function R.new(K,p,f,R,u)u=u or{}A(u,K)K.__index=K for A=1,f,1 do u[A]={}for p=1,p,1 do u[A][p]={east=u:CreateDoor(R);south=u:CreateDoor(R)}if p~=1 then u[A][p].west=u[A][p-1].east else u[A][p].west=u:CreateDoor(R)end if A~=1 then u[A][p].north=u[A-1][p].south else u[A][p].north=K:CreateDoor(R)end end end return u end function R.width(A)return#A[1]end function R.height(A)return#A end function R.DirectionsFrom(u,A,p,R)local K={}R=R or function()return true end for f,d in f(u.directions)do local i,g=A+d.x,p+d.y if u[g]and(u[g][i]and R(u[g][i],i,g))then K[#K+1]={name=f;x=i;y=g}end end return K end function R.ResetDoors(R,A,f)for u=1,#R,1 do for p,R in p(R[u])do R.north:SetClosed(A or u==1 and not f)R.west:SetClosed(A)end R[u][1].west:SetClosed(A or not f)R[u][#R[1]].east:SetClosed(A or not f)end for p,R in p(R[#R])do R.south:SetClosed(A or not f)end end function R.ResetVisited(A)for p=1,#A,1 do for f=1,#A[1],1 do A[p][f].visited=nil end end end function R.tostring(A,f,R)f=f or"\035"R=R or" "local u=""local K=""for p=1,#A[1],1 do K=K..(f..(A[1][p].north:IsClosed()and f or R))end K=K..f u=u..(K.."\n")for A,K in p(A)do local d=K[1].west:IsClosed()and f or R local i=f for A,p in p(K)do d=d..(" "..(p.east:IsClosed()and f or R))i=i..((p.south:IsClosed()and f or R)..f)end u=u..(d..("\n"..(i.."\n")))end return u end R.__tostring=R.tostring function R.CreateDoor(p,A)local f={}f.closed=A and true or false function f.IsClosed(A)return A.closed end function f.IsOpened(A)return not A.closed end function f.Close(A)A.closed=true end function f.Open(A)A.closed=false end function f.SetOpened(p,A)if A then p:Open()else p:Close()end end function f.SetClosed(p,A)p:SetOpened(not A)end return f end return R
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