Jump to content

Module:Build bracket/Paths

Permanently protected module
From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Pbrks (talk | contribs) at 02:21, 13 August 2025 (Created page with '-- 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+...'). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff)
-- 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