Module:Build bracket/Logic
Appearance
From Wikipedia, the free encyclopedia
You might want to create a documentation page for this Scribunto module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Add categories to the /doc subpage. Subpages of this module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Add categories to the /doc subpage. Subpages of this module.
localLogic={} -- ======================= -- 1) STDLIB ALIASES -- ======================= localm_max=math.max localm_ceil=math.ceil locals_match=string.match locals_find=string.find -- ======================= -- 2) MODULE UPVALUES -- ======================= localstate,config,Helpers,StateChecks localisempty,notempty,teamLegs localfunctionbind(_state,_config,_Helpers,_StateChecks) state,config,Helpers,StateChecks=_state,_config,_Helpers,_StateChecks isempty=_Helpersand_Helpers.isemptyorfunction(v) returnv==nilorv=="" end notempty=_Helpersand_Helpers.notemptyorfunction(v) returnv~=nilandv~="" end teamLegs=_StateChecksand_StateChecks.teamLegs end -- ======================= -- 3) SMALL UTILITIES -- ======================= -- Map common Unicode fraction characters to decimal localfraction_map={ ["1⁄2"]=".5", ["1⁄3"]=".333", ["2⁄3"]=".667", ["1⁄4"]=".25", ["3⁄4"]=".75", ["1⁄5"]=".2", ["2⁄5"]=".4", ["3⁄5"]=".6", ["4⁄5"]=".8", ["1⁄6"]=".167", ["5⁄6"]=".833", ["1⁄8"]=".125", ["3⁄8"]=".375", ["5⁄8"]=".625", ["7⁄8"]=".875" } -- Normalize fractions like "11⁄2" to "1.5" localfunctionnormalizeFractions(s) -- Replace integer + fraction (e.g. "11⁄2" → "1.5") s= s:gsub( "(%d+)%s*([%z1円-127円194円-244円][128円-191円]*)", function(d,frac) localrepl=fraction_map[frac] ifreplthen returnd..repl else returnd..frac end end ) -- Replace standalone fraction (e.g. "1⁄2" → "0.5") s= s:gsub( "([%z1円-127円194円-244円][128円-191円]*)", function(frac) localrepl=fraction_map[frac] ifreplthen return"0"..repl else returnfrac end end ) returns end -- Leading integer from a value; supports string or number; nil if none. localfunctionnumlead(v) ifv==nilthen returnnil end iftype(v)=="number"then returnv end locals=tostring(v) ifs==""then returnnil end -- 1) strip leading spaces and wiki bold/italic quotes ('' or ''') s=s:match("^%s*'*%s*(.*)")ors -- 2) strip *leading* simple wrappers (tags/templates/links), repeatedly, but only if they occur before the first digit. localadvanced=true-- set false if you want the minimal version ifadvancedthen localchanged=true whilechangeddo changed=false locals2=s:gsub("^%s*<[^>]->%s*","",1)-- leading HTML tag ifs2~=sthen s,changed=s2,true end s2=s:gsub("^%s*{{.-}}%s*","",1)-- leading template ifs2~=sthen s,changed=s2,true end s2=s:gsub("^%s*%[%[[^%]]-%]%]%s*","",1)-- leading wikilink ifs2~=sthen s,changed=s2,true end end end -- 3) capture leading digits only (stops at first non-digit) s=normalizeFractions(s) localn=s:match("^(%d+%.?%d*)") returnnandtonumber(n)ornil end -- =========================== -- 4) MATCH GROUPING (PER RD) -- =========================== localfunction_matchGroups() state.matchgroup=state.matchgroupor{} localMINC,C,R=config.minc,config.c,config.r forj=MINC,Cdo localmgj={} state.matchgroup[j]=mgj localtpm=tonumber(state.teamsPerMatch[j])or2 iftpm<1then tpm=2 end localcol=state.entries[j] ifcolthen fori=1,Rdo locale=col[i] ifeande.ctype=="team"then localidx=tonumber(e.index)ortonumber(e.altindex)ori localg=m_ceil(idx/tpm) mgj[i]=g e.group=g end end end end end -- Build ordered lists once per round: gid -> {row indices} localfunctionbuildGroupsForRound(j,R) localgroups={} localmg=(state.matchgroupandstate.matchgroup[j])or{} localcol=state.entries[j] ifnotcolthen returngroups end fori=1,Rdo locale=col[i] ifeande.ctype=="team"then localgid=mg[i] ifgid~=nilthen localt=groups[gid] ifnottthen t={} groups[gid]=t end t[#t+1]=i end end end returngroups end -- Pre-parse leg numbers for each team (per round), only when agg & >1 legs. -- Returns: i -> { [l] = number|nil } localfunctionpreparseLegs(j,R) locallegNums={} localcol=state.entries[j] ifnotcolthen returnlegNums end fori=1,Rdo locale=col[i] ifeande.ctype=="team"then localL=teamLegs(j,i) ifconfig.aggregateandL>1ande.scorethen localt={} forl=1,Ldo t[l]=numlead(e.score[l]) end legNums[i]=t end end end returnlegNums end -- ========================================== -- 5) COMPUTE AGGREGATES (score/legs/sets) -- ========================================== localfunction_computeAggregate() ifconfig.aggregate_mode=="off"orconfig.aggregate_mode=="manual"then return end localMINC,C,R=config.minc,config.c,config.r localmodeLow=(config.boldwinner_mode=="low") forj=MINC,Cdo localgroups=buildGroupsForRound(j,R) locallegNums=preparseLegs(j,R) ifconfig.aggregate_mode=="score"then -- Sum per-leg scores; operate directly on parsed legNums. fori,numsinpairs(legNums)do locale=state.entries[j][i] ifeande.ctype=="team"andconfig.aggregateandteamLegs(j,i)>1then localsc=e.score ifscandisempty(sc.agg)then localsum=0 for_,vinipairs(nums)do ifvthen sum=sum+v end end sc.agg=tostring(sum) end end end else -- 'sets'/'legs': count wins per-leg using high/low rule; ties yield no win. for_,membersinpairs(groups)do localwins={}-- row index -> wins -- Comparable leg count across the group = min(#numeric arrays) localcommonLegs=math.huge for_,iinipairs(members)do localnums=legNums[i] localL=(numsand#nums)or0 ifL==0then commonLegs=0 break end ifL<commonLegsthen commonLegs=L end end forl=1,commonLegsdo localallNumeric=true for_,iinipairs(members)do localv=legNums[i]andlegNums[i][l] ifv==nilthen allNumeric=false break end end ifallNumericthen localbest,bestIdx,tie for_,iinipairs(members)do localv=legNums[i][l] ifbest==nilthen best,bestIdx,tie=v,i,false else if(modeLowandv<best)or(notmodeLowandv>best)then best,bestIdx,tie=v,i,false elseifv==bestthen tie=true end end end ifnottieandbestIdxthen wins[bestIdx]=(wins[bestIdx]or0)+1 end end end -- Write aggregates if still empty for_,iinipairs(members)do locale=state.entries[j][i] ifeande.ctype=="team"andconfig.aggregateandteamLegs(j,i)>1then localsc=e.score ifscandisempty(sc.agg)then sc.agg=tostring(wins[i]or0) end end end end end end end -- ========================================== -- 6) BOLD WINNERS (per cells & whole rows) -- ========================================== localfunction_boldWinner() localfunctionisWin(mine,theirs) returnmodeLowand(mine<theirs)or(notmodeLowandmine>theirs) end localfunctionisAggWin(mine,theirs,colKey) -- For aggregate counts (legs/sets won), higher is always better ifcolKey=="agg"and(config.aggregate_mode~="score")then returnmine>theirs end returnisWin(mine,theirs) end ifnotconfigorconfig.boldwinner_mode=="off"then -- Normalize all weights (defensive; avoids leftovers across calls) forj=config.minc,config.cdo fori=1,config.rdo locale=state.entries[j]andstate.entries[j][i] ifeande.ctype=="team"then e.weight="normal" ife.scoreande.score.weightthen fork,_inpairs(e.score.weight)do e.score.weight[k]="normal" end end end end end return end localMINC,C,R=config.minc,config.c,config.r localmodeLow=(config.boldwinner_mode=="low") localaggOnly=config.boldwinner_aggonly localfunctionisWin(mine,theirs) returnmodeLowand(mine<theirs)or(notmodeLowandmine>theirs) end localfunctionisAggWin(mine,theirs,colKey) ifcolKey=="agg"andconfig.aggregate_mode=="sets"then -- Sets/legs won: larger count wins regardless of low/high sport returnmine>theirs end returnisWin(mine,theirs) end localfunctionhasAllScores(e,legs) localsc=e.score forl=1,legsdo localsv=scandsc[l] ifisempty(sv)ors_find(svor"","nbsp")then-- use alias returnfalse end end returntrue end forj=MINC,Cdo localgroups=buildGroupsForRound(j,R) -- Reset counters & ensure score/weight tables exist fori=1,Rdo locale=state.entries[j]andstate.entries[j][i] ifeande.ctype=="team"then e.wins,e.aggwins=0,0 e.score=e.scoreor{} e.score.weight=e.score.weightor{} end end for_,membersinpairs(groups)do -- Parse per-leg and aggregate numbers ONCE for this group (works for 1+ legs) localperLegNum={}-- row -> { l -> number|nil } localaggNum={}-- row -> number|nil for_,iinipairs(members)do locale=state.entries[j][i] locallegs=teamLegs(j,i) localarr={} forl=1,legsdo arr[l]=numlead(e.scoreande.score[l]) end perLegNum[i]=arr aggNum[i]=numlead(e.scoreande.score.agg) end -- Per-score bolding (skip entirely if agg-only) ifnotaggOnlythen -- iterate to the max leg count in the group localmaxL=0 for_,iinipairs(members)do localL=teamLegs(j,i) ifL>maxLthen maxL=L end end forl=1,maxLdo localallNumeric=true localbest,bestIdx,tie for_,iinipairs(members)do localv=perLegNum[i]andperLegNum[i][l] ifv==nilthen allNumeric=false break end ifbest==nilthen best,bestIdx,tie=v,i,false else if(modeLowandv<best)or(notmodeLowandv>best)then best,bestIdx,tie=v,i,false elseifv==bestthen tie=true end end end ifallNumericandnottieandbestIdxthen -- set the winner bold + increment wins locale=state.entries[j][bestIdx] e.score.weight[l]="bold" e.wins=(e.winsor0)+1 -- normalize others on this leg for_,iinipairs(members)do ifi~=bestIdxthen state.entries[j][i].score.weight[l]="normal" end end else -- no unique winner: normalize everyone for this leg for_,iinipairs(members)do state.entries[j][i].score.weight[l]="normal" end end end end -- Aggregate column (if configured & multi-leg) do localneedAgg=false for_,iinipairs(members)do ifconfig.aggregateandteamLegs(j,i)>1then needAgg=true break end end ifneedAggthen localallNumeric=true localbest,bestIdx,tie -- comparator: for legs/sets counts, higher always wins localfunctionbetterAgg(a,b) ifconfig.aggregate_mode~="score"then returna>b else return(modeLowanda<b)or(notmodeLowanda>b) end end for_,iinipairs(members)do localv=aggNum[i] ifv==nilthen allNumeric=false break end ifbest==nilthen best,bestIdx,tie=v,i,false else ifbetterAgg(v,best)then best,bestIdx,tie=v,i,false elseifv==bestthen tie=true end end end ifallNumericandnottieandbestIdxthen locale=state.entries[j][bestIdx] e.score.weight.agg="bold" e.aggwins=1 for_,iinipairs(members)do ifi~=bestIdxthen localo=state.entries[j][i] o.score.weight.agg="normal" o.aggwins=0 end end else for_,iinipairs(members)do locale=state.entries[j][i] ife.scorethen e.score.weight.agg="normal" end e.aggwins=0 end end end end -- Whole-team bolding (skip if agg-only so only agg cell bolds) ifnotaggOnlythen for_,iinipairs(members)do locale=state.entries[j][i] locallegs=teamLegs(j,i) localuseAggregate=config.aggregateandlegs>1 localwinsKey=useAggregateand"aggwins"or"wins" ifnotuseAggregatethen if(e[winsKey]or0)>legs/2then e.weight="bold" else e.weight=hasAllScores(e,legs)and"bold"or"normal" end end -- Must strictly beat any opponent on winsKey for_,oiinipairs(members)do ifoi~=ithen localopp=state.entries[j][oi] if(e[winsKey]or0)<=tonumber(opp[winsKey]or0)then e.weight="normal" break end end end ifuseAggregatethen -- when using aggregate, team weight follows aggwins comparison e.weight=((e[winsKey]or0)>0)and"bold"or"normal" for_,oiinipairs(members)do ifoi~=ithen localopp=state.entries[j][oi] if(e[winsKey]or0)<=tonumber(opp[winsKey]or0)then e.weight="normal" break end end end end end end end end end -- ============================== -- 7) UPDATE PER-ROUND MAX LEGS -- ============================== localfunction_updateMaxLegs() localMINC,C,R=config.minc,config.c,config.r forj=MINC,Cdo localcol=state.entries[j] localrj=state.rlegs[j] localmj=rj ifcolthen fori=1,Rdo locale=col[i] ifethen ifnotempty(e.legs)then mj=m_max(rj,e.legs) end ifconfig.autolegsande.scorethen locall=1 whilee.score[l]andnotisempty(e.score[l])do l=l+1 end mj=m_max(mj,l-1) end end end end state.maxlegs[j]=mj end end -- ============== -- 8) PUBLIC API -- ============== functionLogic.matchGroups(_state,_config) bind(_state,_config) _matchGroups() end functionLogic.computeAggregate(_state,_config,_Helpers,_StateChecks) bind(_state,_config,_Helpers,_StateChecks) _computeAggregate() end functionLogic.boldWinner(_state,_config,_Helpers,_StateChecks) bind(_state,_config,_Helpers,_StateChecks) _boldWinner() end functionLogic.updateMaxLegs(_state,_config,_Helpers) bind(_state,_config,_Helpers) _updateMaxLegs() end returnLogic