Module:Build bracket/Params
local Params = {}
-- =========================
-- 1) MODULE BINDINGS
-- =========================
-- Upvalues bound per call
local state, config, Helpers, StateChecks
-- Stdlib aliases
local str_format = string.format
local t_insert = table.insert
local t_sort = table.sort
-- Locals filled on bind (with safe fallbacks)
local isempty, notempty, bargs, getPArg, getFArg, toChar, split
local function _toCharFallback(n)
n = tonumber(n or 0) or 0
if n >= 1 and n <= 26 then
return string.char(96 + n) -- 'a'..'z'
end
return tostring(n)
end
local function bind(_state, _config, _Helpers, _StateChecks)
state, config, Helpers, StateChecks = _state, _config, _Helpers, _StateChecks
isempty = Helpers and Helpers.isempty or function(v)
return v == nil or v == ""
end
notempty = Helpers and Helpers.notempty or function(v)
return v ~= nil and v ~= ""
end
bargs = Helpers and Helpers.bargs
getPArg = Helpers and Helpers.getPArg
getFArg = Helpers and Helpers.getFArg
toChar = (Helpers and Helpers.toChar) or _toCharFallback
split = Helpers and Helpers.split
end
-- Try both RDj[a]-X and RDj[A]-X; falls back to "" if not present
local function readPerHeaderArg(j, k, suffix)
local ch = toChar(k) -- e.g. 'a' with Helpers.toChar
local key1 = "RD" .. j .. ch .. suffix
local v = bargs(key1)
if v ~= nil and v ~= "" then
return v
end
local key2 = "RD" .. j .. string.upper(ch) .. suffix
v = bargs(key2)
if v ~= nil and v ~= "" then
return v
end
return ""
end
-- =========================
-- 2) ARG HELPERS
-- =========================
-- Trim + split a comma list frame arg (returns {} on blank)
local function readCsvArg(name)
local raw = (getFArg(name) or ""):gsub("%s+", "")
return split(raw, {","}, true)
end
-- ========================================
-- 3) BUILD SKELETON (formerly getCells)
-- ========================================
local function buildSkeleton()
local DEFAULT_TPM = 2
local maxrow = 1
local colentry = {}
local hasNoHeaders = true
-- ensure containers
state.entries = state.entries or {}
state.shift = state.shift or {}
state.teamsPerMatch = state.teamsPerMatch or {}
state.maxtpm = state.maxtpm or 0
local Cmin, Cmax = config.minc, config.c
-- Phase 1: Determine header presence and teamsPerMatch
for j = Cmin, Cmax do
if notempty(getFArg("col" .. j .. "-headers")) then
hasNoHeaders = false
end
local tpm =
tonumber(getFArg("RD" .. j .. "-teams-per-match")) or tonumber(getFArg("col" .. j .. "-teams-per-match")) or
tonumber(getFArg("teams-per-match")) or
DEFAULT_TPM
state.teamsPerMatch[j] = tpm
if tpm > state.maxtpm then
state.maxtpm = tpm
end
end
-- Phase 2: Build colentry for each column
for j = Cmin, Cmax do
state.entries[j] = {}
state.shift[j] = tonumber(bargs("RD" .. j .. "-shift")) or tonumber(bargs("shift")) or 0
colentry[j] = {
readCsvArg("col" .. j .. "-headers"),
readCsvArg("col" .. j .. "-matches"),
readCsvArg("col" .. j .. "-lines"),
readCsvArg("col" .. j .. "-text"),
readCsvArg("col" .. j .. "-groups") -- reserved for user-specified groups
}
-- inject a default header if none were specified anywhere (unless noheaders=y/yes)
local noheaders = (getFArg("noheaders") or ""):lower()
if hasNoHeaders and (noheaders ~= "y" and noheaders ~= "yes") then
t_insert(colentry[j][1], 1)
end
end
-- Ctype mapping for colentry positions
local CTYPE_MAP = {"header", "team", "line", "text", "group"}
-- Helpers to populate entries (preserve legacy shapes)
local function populateTeam(j, rowIndex, n)
local TPM = state.teamsPerMatch[j]
-- scaffold a text row above when needed (legacy behavior)
if state.entries[j][rowIndex - 1] == nil and state.entries[j][rowIndex - 2] == nil then
state.entries[j][rowIndex - 2] = {ctype = "text", index = n}
state.entries[j][rowIndex - 1] = {ctype = "blank"}
end
-- first team (top)
state.entries[j][rowIndex] = {ctype = "team", index = TPM * n - (TPM - 1), position = "top"}
state.entries[j][rowIndex + 1] = {ctype = "blank"}
-- remaining teams in the match (every 2 rows)
for m = 2, TPM do
local idx = TPM * n - (TPM - m)
local r = rowIndex + 2 * (m - 1)
state.entries[j][r] = {ctype = "team", index = idx}
state.entries[j][r + 1] = {ctype = "blank"}
end
end
local function populateText(j, rowIndex, index)
state.entries[j][rowIndex] = {ctype = "text", index = index}
state.entries[j][rowIndex + 1] = {ctype = "blank"}
end
local function populateLine(j, rowIndex)
-- first segment draws its bottom edge
state.entries[j][rowIndex] = {ctype = "line", border = "bottom"}
state.entries[j][rowIndex + 1] = {ctype = "blank"}
-- second segment draws its top edge
state.entries[j][rowIndex + 2] = {ctype = "line", border = "top"}
state.entries[j][rowIndex + 3] = {ctype = "blank"}
end
local function populateGroup(j, rowIndex, n)
state.entries[j][rowIndex] = {ctype = "group", index = n}
state.entries[j][rowIndex + 1] = {ctype = "blank"}
end
local function populateDefault(j, rowIndex, n)
state.entries[j][rowIndex] = {ctype = "header", index = n, position = "top"}
state.entries[j][rowIndex + 1] = {ctype = "blank"}
end
-- Phase 3: Populate entries for each column
for j = Cmin, Cmax do
local textindex = 0
local TPM = state.teamsPerMatch[j]
local shiftJ = state.shift[j]
for k, positions in ipairs(colentry[j]) do
t_sort(positions)
local ctype = CTYPE_MAP[k]
for n = 1, #positions do
if shiftJ ~= 0 and positions[n] > 1 then
positions[n] = positions[n] + shiftJ
end
local rowIndex = 2 * positions[n] - 1
local lastRow = rowIndex + 2 * TPM - 1
if lastRow > maxrow then
maxrow = lastRow
end
if ctype == "team" then
populateTeam(j, rowIndex, n)
textindex = n
elseif ctype == "text" then
populateText(j, rowIndex, textindex + n)
elseif ctype == "line" then
populateLine(j, rowIndex)
elseif ctype == "group" then
populateGroup(j, rowIndex, n)
else
populateDefault(j, rowIndex, n)
end
end
end
end
if isempty(config.r) then
config.r = maxrow
end
end
-- ========================================
-- 4) NAME RESOLUTION HELPERS
-- ========================================
local function paramNames(cname, j, i, l)
local function getArg(key)
return bargs(key) or ""
end
local function getP(key)
return getPArg(key) or ""
end
local e = state.entries[j][i]
local RD = "RD" .. j
local hidx = e.headerindex
local hchar = toChar(hidx)
local RDh = RD .. hchar
local SYNONYMS = {legs = {"legs", "sets"}}
-- Try a list of names against base+index(+suffix); returns first non-empty
local function tryAny(base, names, idx, suffix)
suffix = suffix or ""
for _, nm in ipairs(names) do
local a = bargs(base .. "-" .. nm .. idx .. suffix) or ""
if isempty(a) then
a = bargs(base .. "-" .. nm .. string.format("%02d", idx) .. suffix) or ""
end
if notempty(a) then
return a
end
end
return ""
end
local function tryBoth(base, name, idx, suffix)
suffix = suffix or ""
local a = getArg(base .. "-" .. name .. idx .. suffix)
if isempty(a) then
a = getArg(base .. "-" .. name .. str_format("%02d", idx) .. suffix)
end
return a
end
-- Round names (prefer altname when present)
local RDlabel = getArg(RD .. "-altname") or RD
local RDhlabel = getArg(RDh .. "-altname") or RDh
local rname = {{RD, RDlabel}, {RDh, RDhlabel}}
local name = {cname, getArg(cname .. "-altname") or cname}
local nameKeys = SYNONYMS[cname] or {name[1]}
local index = {e.index, e.altindex}
local result = {}
if cname == "header" then
if hidx == 1 then
for _, base in ipairs({rname[1], rname[2]}) do
for k = 2, 1, -1 do
result[#result + 1] = getArg(base[k])
end
end
else
for k = 2, 1, -1 do
result[#result + 1] = getArg(rname[2][k])
end
end
elseif cname == "pheader" then
if hidx == 1 then
for _, base in ipairs({rname[1], rname[2]}) do
for k = 2, 1, -1 do
result[#result + 1] = getP(base[k])
end
end
else
for k = 2, 1, -1 do
result[#result + 1] = getP(rname[2][k])
end
end
elseif cname == "score" then
local bases = {rname[2][2], rname[2][1], rname[1][2], rname[1][1]}
local idxs = {index[2], index[2], index[1], index[1]}
for n = 1, 4 do
if l == 1 then
result[#result + 1] = tryAny(bases[n], nameKeys, idxs[n])
end
result[#result + 1] = tryAny(bases[n], nameKeys, idxs[n], "-" .. l)
end
elseif cname == "shade" then
for k = 2, 1, -1 do
local base = (hidx == 1) and rname[1][k] or rname[2][k]
result[#result + 1] = getArg(base .. "-" .. name[1])
end
result[#result + 1] = getArg("RD-shade")
result[#result + 1] = (config.COLORS and config.COLORS.cell_bg_dark) or "#eaecf0"
elseif cname == "text" then
local bases = {rname[2][2], rname[2][1], rname[1][2], rname[1][1]}
local idxs = {index[2], index[2], index[1], index[1]}
local names = {name[2], name[1]}
for ni = 1, 2 do
for n = 1, 4 do
result[#result + 1] = tryBoth(bases[n], names[ni], idxs[n])
end
end
else
local bases = {rname[2][2], rname[2][1], rname[1][2], rname[1][1]}
local idxs = {index[2], index[2], index[1], index[1]}
for n = 1, 4 do
result[#result + 1] = tryAny(bases[n], nameKeys, idxs[n])
end
end
for _, val in ipairs(result) do
if notempty(val) then
return val
end
end
return ""
end
-- ========================================
-- 5) NUMBERED PARAM MODE
-- ========================================
local masterindex = 1
local function numberedParams(j)
local row = state.entries[j]
if not row then
return
end
local function nextArg()
local v = bargs(tostring(masterindex)) or ""
masterindex = masterindex + 1
return v
end
local R = config.r
for i = 1, R do
local e = row[i]
if e then
local ct = e.ctype
if ct == "team" then
local legs = state.rlegs[j]
if config.forceseeds then
e.seed = nextArg()
end
e.team = nextArg()
e.legs = paramNames("legs", j, i)
e.score = {weight = {}}
e.weight = "normal"
if notempty(e.legs) then
legs = tonumber(e.legs) or legs
end
for l = 1, legs do
e.score[l] = nextArg()
e.score.weight[l] = "normal"
end
if config.aggregate and legs > 1 then
e.score.agg = nextArg()
e.score.weight.agg = "normal"
end
elseif ct == "header" then
e.header = paramNames("header", j, i)
e.pheader = paramNames("pheader", j, i)
e.shade = paramNames("shade", j, i)
elseif ct == "text" then
e.text = nextArg()
elseif ct == "group" then
e.group = nextArg()
elseif ct == "line" and e.hastext == true then
e.text = nextArg()
end
end
end
end
-- ========================================
-- 6) NAMED MODE ASSIGNERS (per-ctype)
-- ========================================
local function cellHasMeaningfulContent(e)
if not e then
return false
end
if e.ctype == "team" then
return notempty(e.team)
end
if e.ctype == "text" then
return notempty(e.text)
end
if e.ctype == "group" then
return notempty(e.group)
end
if e.ctype == "line" and e.hastext == true then
return notempty(e.text)
end
return false
end
local function enforceContentUnhide(j)
if not (state.hide and state.hide[j]) then
return
end
local explicit = (state._hideExplicit and state._hideExplicit[j]) or {}
local R = config.r
-- If master hid a header, but we later discover content in that header,
-- flip it visible unless there was an EXPLICIT per-header hide.
for i = 1, R do
local e = state.entries[j][i]
if e and e.headerindex then
local h = e.headerindex
if state.hide[j][h] and cellHasMeaningfulContent(e) then
state.hide[j][h] = false
end
end
end
end
local function assignTeamParams(j, i)
local legs = state.rlegs[j]
local e = state.entries[j][i]
e.seed = paramNames("seed", j, i)
e.team = paramNames("team", j, i)
e.legs = paramNames("legs", j, i)
e.score = {weight = {}}
e.weight = "normal"
if notempty(e.legs) then
legs = tonumber(e.legs) or legs
end
if config.autolegs then
local l = 1
repeat
e.score[l] = paramNames("score", j, i, l)
e.score.weight[l] = "normal"
l = l + 1
until isempty(paramNames("score", j, i, l))
legs = l - 1
else
for l = 1, legs do
e.score[l] = paramNames("score", j, i, l)
e.score.weight[l] = "normal"
end
end
if config.aggregate and legs > 1 then
e.score.agg = paramNames("score", j, i, "agg")
e.score.weight.agg = "normal"
end
end
local function assignHeaderParams(j, i)
local e = state.entries[j][i]
e.header = paramNames("header", j, i)
e.pheader = paramNames("pheader", j, i)
e.shade = paramNames("shade", j, i)
-- Did shade originate from an RD*-shade param?
local hchar = toChar(e.headerindex)
local rdNames = {
"RD" .. j .. "-shade",
"RD" .. j .. hchar .. "-shade",
"RD-shade"
}
e.shade_is_rd = false
for _, pname in ipairs(rdNames) do
local v = bargs(pname)
if notempty(v) and e.shade == v then
e.shade_is_rd = true
break
end
end
end
local function assignTextParams(j, i)
state.entries[j][i].text = paramNames("text", j, i)
end
local function assignGroupParams(j, i)
state.entries[j][i].group = paramNames("group", j, i)
end
local function assignLineTextParams(j, i)
state.entries[j][i].text = paramNames("text", j, i)
end
-- ========================================
-- 7) TABLE-WIDE ASSIGNMENT PASS
-- ========================================
local function getScalarRoundParam(j, bases) -- e.g. {"legs","sets"}
-- prefer per-round keys, then global
for _, base in ipairs(bases) do
local v = bargs("RD" .. j .. "-" .. base)
if notempty(v) then
return v
end
end
for _, base in ipairs(bases) do
local v = bargs(base)
if notempty(v) then
return v
end
end
return ""
end
local function assignParams()
masterindex = 1
local maxcol = 1
local Cmin, Cmax, R = config.minc, config.c, config.r
for j = Cmin, Cmax do
-- prepare per-round containers
state.hide[j] = state.hide[j] or {}
state.byes[j] = state.byes[j] or {}
-- Set legs for this column
local vlegs = getScalarRoundParam(j, {"legs", "sets"})
state.rlegs[j] = tonumber(vlegs) or 1
if notempty(vlegs) then
config.autolegs = false
end
-- assign params
if config.paramstyle == "numbered" then
numberedParams(j)
else
local col = state.entries[j]
for i = 1, R do
local cell = col[i]
if cell ~= nil then
local ct = cell.ctype
if ct == "team" then
assignTeamParams(j, i)
elseif ct == "header" then
assignHeaderParams(j, i)
elseif ct == "text" then
assignTextParams(j, i)
elseif ct == "group" then
assignGroupParams(j, i)
elseif ct == "line" and cell.hastext == true then
assignLineTextParams(j, i)
end
end
if config.autocol and not StateChecks.isBlankEntry(j, i) and j > maxcol then
maxcol = j
end
end
end
enforceContentUnhide(j)
-- parent header text forces visible
for i = 1, R do
local e = state.entries[j][i]
if e and e.ctype == "header" then
local hidx = e.headerindex
if (Helpers.notempty and Helpers.notempty(e.pheader)) then
state.hide[j][hidx] = false
end
end
end
end
if config.autocol then
config.c = maxcol
end
end
-- ========================================
-- 8) STRUCTURE DISCOVERY (hide/byes/indices)
-- ========================================
local function getHide(j)
state.hide[j] = {}
state._hideExplicit = state._hideExplicit or {}
state._hideExplicit[j] = {}
-- master round-level hide flag: RD{j}-hide
local masterRaw = bargs("RD" .. j .. "-hide") or ""
local masterOn = (Helpers and Helpers.yes and Helpers.yes(masterRaw)) or false
for k = 1, state.headerindex[j] do
state.hide[j][k] = masterOn
-- per-header override
local rh = readPerHeaderArg(j, k, "-hide")
if rh ~= "" then
if Helpers and Helpers.yes and Helpers.yes(rh) then
state.hide[j][k] = true
state._hideExplicit[j][k] = true
elseif Helpers and Helpers.no and Helpers.no(rh) then
state.hide[j][k] = false
state._hideExplicit[j][k] = true
end
end
end
end
local function getByes(j)
state.byes[j] = {}
for k = 1, state.headerindex[j] do
-- global byes
local byes = (bargs("byes") or ""):lower()
if (Helpers.yes and Helpers.yes(byes)) then
state.byes[j][k] = true
elseif tonumber(byes) then
state.byes[j][k] = (j <= tonumber(byes))
else
state.byes[j][k] = false
end
-- per-round byes
local r = (bargs("RD" .. j .. "-byes") or ""):lower()
if (Helpers.yes and Helpers.yes(r)) then
state.byes[j][k] = true
elseif r == "no" or r == "n" then
state.byes[j][k] = false
end
-- per-header byes
local rh = (readPerHeaderArg(j, k, "-byes") or ""):lower()
if (Helpers.yes and Helpers.yes(rh)) then
state.byes[j][k] = true
elseif rh == "no" or rh == "n" then
state.byes[j][k] = false
end
end
end
local function getAltIndices()
local Cmin, Cmax, R = config.minc, config.c, config.r
for j = Cmin, Cmax do
state.headerindex[j] = 0
-- per-round counters
local teamindex, textindex, groupindex = 1, 1, 1
local row = state.entries[j]
-- if the very first cell is nil, bump headerindex once (legacy quirk)
if row and row[1] == nil then
state.headerindex[j] = state.headerindex[j] + 1
end
-- walk rows in the round
for i = 1, R do
local e = row and row[i] or nil
if e then
local ct = e.ctype
if ct == "header" then
e.altindex = state.headerindex[j]
teamindex, textindex = 1, 1
state.headerindex[j] = state.headerindex[j] + 1
elseif ct == "team" then
e.altindex = teamindex
teamindex = teamindex + 1
elseif ct == "text" or (ct == "line" and e.hastext == true) then
e.altindex = textindex
textindex = textindex + 1
elseif ct == "group" then
e.altindex = groupindex
groupindex = groupindex + 1
end
e.headerindex = state.headerindex[j]
end
end
getByes(j)
getHide(j)
end
end
-- ========================================
-- 9) PUBLIC API
-- ========================================
function Params.buildSkeleton(_state, _config, _Helpers, _StateChecks)
bind(_state, _config, _Helpers, _StateChecks)
buildSkeleton()
end
function Params.scanStructure(_state, _config, _Helpers, _StateChecks)
bind(_state, _config, _Helpers, _StateChecks)
getAltIndices()
end
function Params.assign(_state, _config, _Helpers, _StateChecks)
bind(_state, _config, _Helpers, _StateChecks)
assignParams()
end
-- ========================================
-- 10) SLICING FOR MIN ROUND (base offset)
-- ========================================
local function shiftCols(tbl, base, c)
if not tbl then
return {}
end
local out = {}
for j = base + 1, c do
out[j - base] = tbl[j]
end
return out
end
function Params.sliceForMinround(_state, _config)
local base = _config.base or 0
if base <= 0 then
return
end
local oldC = _config.c
local newC = oldC - base
if newC < 1 then
newC = 1
end
-- Shift all column-indexed tables
_state.entries = shiftCols(_state.entries, base, oldC)
_state.headerindex = shiftCols(_state.headerindex, base, oldC)
_state.rlegs = shiftCols(_state.rlegs, base, oldC)
_state.maxlegs = {} -- recompute later
_state.hascross = {} -- rebuild later
_state.crossCell = {} -- rebuild later
_state.pathCell = {} -- rebuild later
_state.skipPath = {} -- rebuild later
_state.hide = shiftCols(_state.hide, base, oldC)
_state.byes = shiftCols(_state.byes, base, oldC)
_state.teamsPerMatch = shiftCols(_state.teamsPerMatch, base, oldC)
_state.matchgroup = {} -- recompute
-- Update view range: now we render 1..newC
_config.c = newC
_config.minc = 1
end
return Params
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.