Module:Build bracket/Paths
Appearance
-- Module:Build Bracket/Paths
local Paths = {}
-- Public:
-- Paths.build(state, config, Helpers, StateChecks)
-- ====== Parsing ======
local function parsePaths(state, config, Helpers, j)
local split, notempty = Helpers.split, Helpers.notempty
local fargs = config._fargs or {}
local str = fargs['col'..j..'-col'..(j+1)..'-paths'] or ''
local result = {}
for val in str
:gsub("%s+","")
:gsub(",",", ")
:gsub("%S+","\0%0\0")
:gsub("%b()", function(s) return s:gsub("%z","") end)
:gmatch("%z(.-)%z")
do
local array = split(val:gsub("%s+",""):gsub("%)",""):gsub("%(",""), {"-"})
for k,_ in pairs(array) do
array[k] = split(array[k], {","})
end
if notempty(array[2]) then
array[3] = array[3] or {}
for m=1,#array[2] do
array[2][m] = split(array[2][m], {":"})
array[3][m] = array[2][m][2]
array[2][m] = array[2][m][1]
end
for n=1,#array[1] do
for m=1,#array[2] do
table.insert(result, { tonumber(array[1][n]), tonumber(array[2][m]), color = array[3][m] })
end
end
end
end
return result
end
-- Show/hide logic for a path between j and j+1, start->stop (header rows)
local function isPathHidden(state, config, Helpers, StateChecks, j, start, stop)
local function truthy(s)
s = (s or ''):lower()
return s=='y' or s=='yes' or s=='1' or s=='true'
end
local bargs = Helpers.bargs
local showBye = truthy(Helpers.bargs('show-bye-paths'))
local function sideHidden(col, headerRow, neighborRow)
local colEntries = state.entries[col]
local e = colEntries and colEntries[headerRow]
if not e then return false end
local hidx = e.headerindex
local byes = state.byes[col] and state.byes[col][hidx]
local hid = state.hide[col] and state.hide[col][hidx]
if byes and StateChecks.isBlankEntry(state, col, headerRow) and StateChecks.isBlankEntry(state, col, neighborRow) then
if not showBye then return true end
end
if hid then return true end
return false
end
if sideHidden(j, start - 1, start + 1) then return true end
if sideHidden(j + 1, stop - 1, stop + 1) then return true end
local rdflag = (bargs('RD'..j..'-RD'..(j+1)..'-path') or ''):lower()
if rdflag == 'n' or rdflag == 'no' or rdflag == '0' then
local srcHdr = state.entries[j] and state.entries[j][start - 1]
if srcHdr and srcHdr.headerindex == 1 then
return true
end
end
return false
end
-- ====== Cell scaffolding ======
local function ensurePathSlot(state, j, row, colIndex)
state.pathCell[j] = state.pathCell[j] or {}
state.pathCell[j][row] = state.pathCell[j][row] or {}
state.pathCell[j][row][colIndex] = state.pathCell[j][row][colIndex] or {}
local cell = state.pathCell[j][row][colIndex]
cell[1] = cell[1] or { [1]=0, [2]=0, [3]=0, [4]=0 }
return cell[1], cell
end
local function setPathCell(state, j, row, colIndex, borderIndex, value, color)
local borders, cell = ensurePathSlot(state, j, row, colIndex)
if borderIndex >= 1 and borderIndex <= 4 then
borders[borderIndex] = value
end
cell.color = color
end
local function getBorder(state, j, row, k, edge)
state.pathCell[j] = state.pathCell[j] or {}
state.pathCell[j][row] = state.pathCell[j][row] or {}
state.pathCell[j][row][k] = state.pathCell[j][row][k] or { {0,0,0,0}, color = (config and config.COLORS and config.COLORS.path_line_color) or 'gray' }
local borders = state.pathCell[j][row][k][1]
return borders[edge] or 0
end
local function getCellColor(state, config, j, row, k)
local col = state.pathCell[j]
local cell = col and col[row] and col[row][k]
return (cell and cell.color) or ((config and config.COLORS and config.COLORS.path_line_color) or 'gray')
end
local function setMultipleCells(state, j, row, colIndexes, borderIndex, value, color)
for _, colIndex in ipairs(colIndexes) do
setPathCell(state, j, row, colIndex, borderIndex, value, color)
end
end
-- ====== Writers ======
local function handleStraightPath(state, config, j, start, color, straightpaths)
table.insert(straightpaths, { start, color })
end
local function handleDownwardPath(state, config, j, start, stop, cross, color)
if stop > config.r then return end
setPathCell(state, j, start + 1, 1, 1, 1, color)
if (state.hascross[j]) then
if cross == 0 then
setPathCell(state, j, start + 1, 2, 1, 1, color)
for i = start + 1, stop do
setPathCell(state, j, i, 2, 2, 1, color)
end
else
state.crossCell[j][cross].left = { 1, color }
for i = start + 1, cross - 1 do setPathCell(state, j, i, 1, 2, 1, color) end
for i = cross + 2, stop do setPathCell(state, j, i, 2, 2, 1, color) end
end
else
if cross == 0 then
for i = start + 1, stop do setPathCell(state, j, i, 1, 2, 1, color) end
else
-- (with no cross slots, treat like straight vertical)
for i = start + 1, stop do setPathCell(state, j, i, 1, 2, 1, color) end
end
end
setPathCell(state, j, stop, 3, 3, 1, color)
end
local function handleUpwardPath(state, config, j, start, stop, cross, color)
if start > config.r then return end
setPathCell(state, j, stop + 1, 3, 1, 1, color)
if (state.hascross[j]) then
if cross == 0 then
for i = stop + 1, start do setPathCell(state, j, i, 2, 2, 1, color) end
setPathCell(state, j, start, 2, 3, 1, color)
else
state.crossCell[j][cross].right = { 1, color }
for i = stop + 1, cross - 1 do setPathCell(state, j, i, 2, 2, 1, color) end
for i = cross + 2, start do setPathCell(state, j, i, 1, 2, 1, color) end
end
else
if cross == 0 then
for i = stop + 1, start do setPathCell(state, j, i, 1, 2, 1, color) end
else
for i = stop + 1, start do setPathCell(state, j, i, 1, 2, 1, color) end
end
end
setPathCell(state, j, start, 1, 3, 1, color)
end
-- ====== Thickness/joins ======
local function thickenStraightPaths(state, config, j, straightpaths)
for _, sp in ipairs(straightpaths) do
local i, color = sp[1], sp[2]
if i > config.r then break end
if state.pathCell[j][i][1][1][3] == 0 then
setMultipleCells(state, j, i, {1,2,3}, 3, 1, color)
if state.pathCell[j][i+1][1][1][1] == 0 then
setMultipleCells(state, j, i+1, {1,2,3}, 1, 0.5, color)
end
elseif state.pathCell[j][i+1][1][1][1] == 0 then
setMultipleCells(state, j, i+1, {1,3}, 1, 1, color)
if state.hascross[j] then setMultipleCells(state, j, i+1, {2}, 1, 1, color) end
end
end
end
local function adjustOutpaths(state, config, j, outpaths)
for _, op in ipairs(outpaths) do
local i = op[1]
if i < config.r then
local top1, top2 = state.pathCell[j][i+1][1], state.pathCell[j][i+1][2]
local bottom1, bottom2 = state.pathCell[j][i][1], state.pathCell[j][i][2]
if bottom1[1][3] == 1 and top1[1][1] == 0 then
top1[1][1] = 0.5 * bottom1[1][3]
top2[1][1] = 0.5 * bottom2[1][3]
top1.color = bottom1.color
top2.color = bottom2.color
elseif bottom1[1][3] == 0 and top1[1][1] == 1 then
bottom1[1][3] = top1[1][1]
bottom2[1][3] = top2[1][1]
top1[1][1] = 0.5 * bottom1[1][3]
top2[1][1] = 0.5 * bottom2[1][3]
bottom1.color = top1.color
bottom2.color = top2.color
end
end
end
end
local function thinDoubleInPaths(state, config, j, inpaths)
for _, ip in ipairs(inpaths) do
local i = ip[1]
if i < config.r then
local curr3 = state.pathCell[j][i][3]
local next3 = state.pathCell[j][i+1][3]
if curr3[1][3] == 1 and next3[1][1] == 1 and curr3.color == next3.color then
next3[1][1] = 0.5 * curr3[1][3]
end
end
end
end
-- ====== Cross-col bridging ======
local function connectStraightPaths(state, config)
for j = config.minc, config.c - 1 do
for i = 1, config.r - 1 do
local straightpath = false
local topEntry = state.entries[j+1] and state.entries[j+1][i-1]
local isTopBlankOrBye = (topEntry == nil or (state.byes[j+1][topEntry and topEntry.headerindex] and (true)))
if isTopBlankOrBye then
state.pathCell[j] = state.pathCell[j] or {}
state.pathCell[j+1] = state.pathCell[j+1] or {}
state.entries[j+1] = state.entries[j+1] or {}
if state.pathCell[j] and state.pathCell[j+1] and state.entries[j] and state.entries[j+1] then
local curr_i3_b = state.pathCell[j][i][3][1][3] or 0
local next_i1_b = state.pathCell[j+1][i][1][1][3] or 0
local curr_ip1_t= state.pathCell[j][i+1][3][1][1] or 0
local next_ip1_t= state.pathCell[j+1][i+1][1][1][1] or 0
local cond1 = (curr_i3_b ~= 0 and next_i1_b ~= 0)
local cond2 = (curr_ip1_t ~= 0 and next_ip1_t ~= 0)
if cond1 or cond2 then
local next_i3_b = state.pathCell[j+1][i][3][1][3] or 0
local next_ip1_1= state.pathCell[j+1][i+1][3][1][1] or 0
if next_i1_b == next_i3_b and next_ip1_t == next_ip1_1 then
straightpath = true
end
local color_i = getCellColor(state, config, j, i, 3)
local color_ip1 = getCellColor(state, config, j, i+1, 3)
setPathCell(state, j+1, i, 1, 3, curr_i3_b, color_i)
setPathCell(state, j+1, i+1, 1, 1, curr_ip1_t, color_ip1)
setPathCell(state, j+1, i, 2, 3, curr_i3_b, color_i)
setPathCell(state, j+1, i+1, 2, 1, curr_ip1_t, color_ip1)
state.entries[j+1][i-1] = { ctype = 'line', border = 'bottom' }
state.entries[j+1][i] = { ctype = 'blank' }
if state.entries[j+1][i+1] ~= nil then
state.entries[j+1][i+1].ctype = 'line'
state.entries[j+1][i+1].border = 'top'
else
state.entries[j+1][i+1] = { ctype = 'line', border = 'top' }
end
state.entries[j+1][i+2] = { ctype = 'blank' }
if straightpath then
setPathCell(state, j+1, i, 3, 3, state.pathCell[j+1][i][1][1][3], getCellColor(state, config, j+1, i, 1))
setPathCell(state, j+1, i+1, 3, 1, state.pathCell[j+1][i+1][1][1][1], getCellColor(state, config, j+1, i+1, 1))
end
end
end
end
end
end
end
-- ====== Driver ======
function Paths.build(state, config, Helpers, StateChecks)
state.pathCell = state.pathCell or {}
state.crossCell= state.crossCell or {}
state.skipPath = state.skipPath or {}
state.hascross = state.hascross or {}
-- 1) which columns have cross paths?
for j = config.minc, config.c - 1 do
local fargs = config._fargs or {}
state.hascross[j] = Helpers.notempty(fargs['col' .. j .. '-col' .. (j + 1) .. '-cross'])
end
-- 2) process each column
for j = config.minc, config.c - 1 do
local paths = parsePaths(state, config, Helpers, j)
state.pathCell[j], state.crossCell[j], state.skipPath[j] = {}, {}, {}
for i = 1, config.r do
state.pathCell[j][i] = {
{ {0,0,0,0}, color = (config.COLORS and config.COLORS.path_line_color) or 'gray' },
{ {0,0,0,0}, color = (config.COLORS and config.COLORS.path_line_color) or 'gray' },
{ {0,0,0,0}, color = (config.COLORS and config.COLORS.path_line_color) or 'gray' },
}
state.crossCell[j][i] = { left = {0, (config.COLORS and config.COLORS.path_line_color) or 'gray'},
right= {0, (config.COLORS and config.COLORS.path_line_color) or 'gray'} }
state.skipPath[j][i] = false
end
-- cross locations list (optional gradient bg handling)
local crossstr = (config._fargs or {})['col' .. j .. '-col' .. (j + 1) .. '-cross'] or ''
local crossloc = {}
if crossstr ~= '' then
for n in crossstr:gmatch("%d+") do crossloc[#crossloc+1] = tonumber(n) end
end
local inpaths, outpaths, straightpaths = {}, {}, {}
for _, v in ipairs(paths) do
local start, stop = v[1], v[2]
local color = v.color or ((config.COLORS and config.COLORS.path_line_color) or 'gray')
if isPathHidden(state, config, Helpers, StateChecks, j, start, stop) then
-- skip drawing
else
local cross = 0
for _, c in ipairs(crossloc) do
if (start < c and stop >= c) or (start > c and stop <= c) then
cross = c; break
end
end
if start == stop then
handleStraightPath(state, config, j, start, color, straightpaths)
elseif start < stop then
handleDownwardPath(state, config, j, start, stop, cross, color)
table.insert(outpaths, { stop })
else
handleUpwardPath(state, config, j, start, stop, cross, color)
table.insert(inpaths, { stop })
end
end
end
thickenStraightPaths(state, config, j, straightpaths)
adjustOutpaths(state, config, j, outpaths)
thinDoubleInPaths(state, config, j, inpaths)
end
-- 3) connect straight paths between adjacent columns
connectStraightPaths(state, config)
end
return Paths