From 43affc5a6bac4dff55fcafe54f7fd478a65929cf Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:20:30 +0900 Subject: [PATCH 1/8] allow passing extra args --- .../commons/Standings/Tiebreaker/Factory.lua | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua index 182751640e3..3ef6060580b 100644 --- a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua +++ b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua @@ -7,10 +7,17 @@ local Lua = require('Module:Lua') +local Logic = Lua.import('Module:Logic') local String = Lua.import('Module:StringUtils') +local Table = Lua.import('Module:Table') local TiebreakerFactory = {} +---@class ParsedTiebreaker +---@field name string +---@field context string +---@field config table? + local NAME_TO_CLASS = { buchholz = 'Buchholz', manual = 'Manual', @@ -27,23 +34,42 @@ local NAME_TO_CLASS = { gamewinrate = 'Game/WinRate', } ---- Validates and normalizes the name of a tiebreaker input. ----@param input string ----@return string -function TiebreakerFactory.validateAndNormalizeInput(input) +---@param input string|table +---@return ParsedTiebreaker +function TiebreakerFactory._parseTiebreakerInput(input) + if type(input) == 'table' then + local tiebreakerName = Table.extract(input, 'name') + local tiebreakerContext = Table.extract(input, 'context') + return { + name = tiebreakerName, + context = Logic.emptyOr(tiebreakerContext, 'full'), + config = input + } + end local context, name = unpack(String.split(input, '%.')) if name == nil then name = context context = 'full' end + return { + name = name, + context = context + } +end + +--- Validates and normalizes the name of a tiebreaker input. +---@param input string|table +---@return ParsedTiebreaker +function TiebreakerFactory.validateAndNormalizeInput(input) + local parsedInput = TiebreakerFactory._parseTiebreakerInput(input) assert( - context == 'full' or context == 'ml' or context == 'h2h', - 'Invalid tie breaker context: ' .. context + parsedInput.context == 'full' or parsedInput.context == 'ml' or parsedInput.context == 'h2h', + 'Invalid tie breaker context: ' .. parsedInput.context ) - local tiebreakerClassName = NAME_TO_CLASS[name] + local tiebreakerClassName = NAME_TO_CLASS[parsedInput.name] assert(tiebreakerClassName, "Invalid tiebreaker type: " .. tostring(input)) - return table.concat({context, name}, '.') + return parsedInput end ---@param tiebreakerId string From f7a349d2d7b4664a953c521010d88a7a416f86f3 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:21:49 +0900 Subject: [PATCH 2/8] update wiki parsing --- lua/wikis/commons/Standings/Parse/Wiki.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/Standings/Parse/Wiki.lua b/lua/wikis/commons/Standings/Parse/Wiki.lua index 396fd51877f..e95a08a146e 100644 --- a/lua/wikis/commons/Standings/Parse/Wiki.lua +++ b/lua/wikis/commons/Standings/Parse/Wiki.lua @@ -194,9 +194,9 @@ end ---@param args table ---@param tableType StandingsTableTypes ----@return StandingsTiebreaker[] +---@return ParsedTiebreaker[] function StandingsParseWiki.parseTiebreakers(args, tableType) - local tiebreakerInput = Json.parseIfString(args.tiebreakers) or {} + local tiebreakerInput = Json.parseStringified(args.tiebreakers) or {} local tiebreakers = Array.map(tiebreakerInput, TiebreakerFactory.validateAndNormalizeInput) if #tiebreakers == 0 then if tableType == 'ffa' then From 8010497c698709d5e53a9b6f86882b1294890ebe Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:36:59 +0900 Subject: [PATCH 3/8] nilIfEmpty --- lua/wikis/commons/Standings/Tiebreaker/Factory.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua index 3ef6060580b..5669e58ae35 100644 --- a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua +++ b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua @@ -43,7 +43,7 @@ function TiebreakerFactory._parseTiebreakerInput(input) return { name = tiebreakerName, context = Logic.emptyOr(tiebreakerContext, 'full'), - config = input + config = Logic.nilIfEmpty(input) } end local context, name = unpack(String.split(input, '%.')) From 9a40ce9c94ddfd371be94bebb5f917ac77682706 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:47:33 +0900 Subject: [PATCH 4/8] update parsing logic --- lua/wikis/commons/Standings/Parser.lua | 57 ++++++++++++------- .../commons/Standings/Tiebreaker/Factory.lua | 22 ++++++- .../Standings/Tiebreaker/Interface.lua | 4 +- 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/lua/wikis/commons/Standings/Parser.lua b/lua/wikis/commons/Standings/Parser.lua index da58111abc0..8065a0c63ca 100644 --- a/lua/wikis/commons/Standings/Parser.lua +++ b/lua/wikis/commons/Standings/Parser.lua @@ -20,9 +20,9 @@ local StandingsParser = {} ---@param title string? ---@param matches string[] ---@param standingsType StandingsTableTypes ----@param tiebreakerIds string[] +---@param parsedTiebreakers ParsedTiebreaker[] ---@return StandingsTableStorage -function StandingsParser.parse(rounds, opponents, bgs, title, matches, standingsType, tiebreakerIds) +function StandingsParser.parse(rounds, opponents, bgs, title, matches, standingsType, parsedTiebreakers) -- TODO: When all legacy (of all standing type) have been converted, the wiki variable should be updated -- to follow the namespace format. Eg new name could be `standings_standingsindex` local lastStandingsIndex = tonumber(Variables.varDefault('standingsindex')) or -1 @@ -76,16 +76,18 @@ function StandingsParser.parse(rounds, opponents, bgs, title, matches, standings end) end) + local tiebreakers = Array.map(parsedTiebreakers, TiebreakerFactory.getTiebreaker) + Array.forEach(rounds, function(round) StandingsParser.calculateTiebreakerValues(Array.filter(entries, function(opponentRound) return opponentRound.roundindex == round.roundNumber - end), tiebreakerIds) + end), parsedTiebreakers, tiebreakers) end) Array.forEach(rounds, function(round) StandingsParser.determinePlacements(Array.filter(entries, function(opponentRound) return opponentRound.roundindex == round.roundNumber - end), tiebreakerIds) + end), parsedTiebreakers, tiebreakers) end) ---@cast entries {opponent: standardOpponent, standingindex: integer, roundindex: integer, ---points: number, placement: integer?, slotindex: integer}[] @@ -118,14 +120,24 @@ function StandingsParser.parse(rounds, opponents, bgs, title, matches, standings finished = isFinished, extradata = { rounds = rounds, - tiebreakers = Array.map(tiebreakerIds, function(tiebreakerId) - local tiebreaker = TiebreakerFactory.tiebreakerFromId(tiebreakerId) - local tiebreakerContextType = tiebreaker:getContextType() + tiebreakers = Array.map(parsedTiebreakers, function(parsedTiebreaker, tiebreakerIndex) + local tiebreakerName = parsedTiebreaker.name + local tiebreakerContextType = parsedTiebreaker.context + local tiebreakerId = tiebreakerContextType .. tiebreakerName + local tiebreaker = tiebreakers[tiebreakerIndex] if tiebreakerContextType ~= 'full' then - return {id = tiebreakerId} + return { + id = tiebreakerId, + name = tiebreakerName, + context = tiebreakerContextType, + config = parsedTiebreaker.config, + } end return { id = tiebreakerId, + name = tiebreakerName, + context = tiebreakerContextType, + config = parsedTiebreaker.config, title = tiebreaker:headerTitle(), } end), @@ -137,16 +149,16 @@ end ---Does not calculate H2H or ML, only "full" tiebreaker types. ---H2H and ML and resolved in resolveTieForGroup() called by determinePlacements() ---@param opponentsInRound TiebreakerOpponent[] ----@param tiebreakerIds string[] -function StandingsParser.calculateTiebreakerValues(opponentsInRound, tiebreakerIds) - Array.forEach(tiebreakerIds, function(tiebreakerId) - local tiebreaker = TiebreakerFactory.tiebreakerFromId(tiebreakerId) +---@param parsedTiebreakers ParsedTiebreaker[] +---@param tiebreakers StandingsTiebreaker[] +function StandingsParser.calculateTiebreakerValues(opponentsInRound, parsedTiebreakers, tiebreakers) + Array.forEach(tiebreakers, function(tiebreaker, tiebreakerIndex) local tiebreakerContextType = tiebreaker:getContextType() if tiebreakerContextType == 'h2h' or tiebreakerContextType == 'ml' then return end Array.forEach(opponentsInRound, function(opponent) - opponent.extradata.tiebreakerValues[tiebreakerId] = { + opponent.extradata.tiebreakerValues[parsedTiebreakers[tiebreakerIndex].id] = { value = tiebreaker:valueOf(opponentsInRound, opponent), display = tiebreaker:display(opponentsInRound, opponent), } @@ -156,15 +168,16 @@ end ---@param allOpponents TiebreakerOpponent[] ---@param tiedOpponents TiebreakerOpponent[] ----@param tiebreakerIds string[] +---@param parsedTiebreakers ParsedTiebreaker[] +---@param tiebreakers StandingsTiebreaker[] ---@param tiebreakerIndex integer ---@return TiebreakerOpponent[][] -local function resolveTieForGroup(allOpponents, tiedOpponents, tiebreakerIds, tiebreakerIndex) - local tiebreakerId = tiebreakerIds[tiebreakerIndex] - if not tiebreakerId then +local function resolveTieForGroup(allOpponents, tiedOpponents, parsedTiebreakers, tiebreakers, tiebreakerIndex) + local tiebreaker = tiebreakers[tiebreakerIndex] + if not tiebreaker then return { tiedOpponents } end - local tiebreaker = TiebreakerFactory.tiebreakerFromId(tiebreakerId) + local tiebreakerId = parsedTiebreakers[tiebreakerIndex].id local _, groupedOpponents = Array.groupBy(tiedOpponents, function(opponent) if not opponent.extradata.tiebreakerValues[tiebreakerId] then @@ -181,15 +194,15 @@ local function resolveTieForGroup(allOpponents, tiedOpponents, tiebreakerIds, ti if #group == 1 then return { group } end - return resolveTieForGroup(allOpponents, group, tiebreakerIds, tiebreakerIndex + 1) + return resolveTieForGroup(allOpponents, group, parsedTiebreakers, tiebreakers, tiebreakerIndex + 1) end) end ---@param opponentsInRound {opponent: standardOpponent, standingindex: integer, roundindex: integer, points: number, ---placement: integer?, slotindex: integer?, extradata: table}[] ----@param tiebreakerIds string[] -function StandingsParser.determinePlacements(opponentsInRound, tiebreakerIds) - local opponentsAfterTie = resolveTieForGroup(opponentsInRound, opponentsInRound, tiebreakerIds, 1) +---@param parsedTiebreakers ParsedTiebreaker[] +function StandingsParser.determinePlacements(opponentsInRound, parsedTiebreakers, tiebreakers) + local opponentsAfterTie = resolveTieForGroup(opponentsInRound, opponentsInRound, parsedTiebreakers, tiebreakers, 1) local slotIndex = 1 Array.forEach(opponentsAfterTie, function(opponentGroup) local rank = slotIndex diff --git a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua index 5669e58ae35..361ebcd0239 100644 --- a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua +++ b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua @@ -16,6 +16,7 @@ local TiebreakerFactory = {} ---@class ParsedTiebreaker ---@field name string ---@field context string +---@field id string ---@field config table? local NAME_TO_CLASS = { @@ -39,10 +40,11 @@ local NAME_TO_CLASS = { function TiebreakerFactory._parseTiebreakerInput(input) if type(input) == 'table' then local tiebreakerName = Table.extract(input, 'name') - local tiebreakerContext = Table.extract(input, 'context') + local tiebreakerContext = Logic.emptyOr(Table.extract(input, 'context'), 'full') return { name = tiebreakerName, - context = Logic.emptyOr(tiebreakerContext, 'full'), + context = tiebreakerContext, + id = tiebreakerContext .. tiebreakerName, config = Logic.nilIfEmpty(input) } end @@ -53,7 +55,8 @@ function TiebreakerFactory._parseTiebreakerInput(input) end return { name = name, - context = context + context = context, + id = context .. name, } end @@ -72,6 +75,7 @@ function TiebreakerFactory.validateAndNormalizeInput(input) return parsedInput end +---@deprecated ---@param tiebreakerId string ---@return StandingsTiebreaker function TiebreakerFactory.tiebreakerFromId(tiebreakerId) @@ -84,4 +88,16 @@ function TiebreakerFactory.tiebreakerFromId(tiebreakerId) return TiebreakerClass(context) end +---@param parsedTiebreaker ParsedTiebreaker +---@return StandingsTiebreaker +function TiebreakerFactory.getTiebreaker(parsedTiebreaker) + local tiebreakerClassName = NAME_TO_CLASS[parsedTiebreaker.name] + assert(tiebreakerClassName, "Invalid tiebreaker type: " .. tostring(parsedTiebreaker.name)) + ---@type StandingsTiebreaker + local TiebreakerClass = Lua.import('Module:Standings/Tiebreaker/' .. tiebreakerClassName) + + return TiebreakerClass(parsedTiebreaker.context, parsedTiebreaker.config) +end + + return TiebreakerFactory diff --git a/lua/wikis/commons/Standings/Tiebreaker/Interface.lua b/lua/wikis/commons/Standings/Tiebreaker/Interface.lua index ab2ceabb6e1..ec0c784dc4a 100644 --- a/lua/wikis/commons/Standings/Tiebreaker/Interface.lua +++ b/lua/wikis/commons/Standings/Tiebreaker/Interface.lua @@ -14,11 +14,13 @@ local Class = Lua.import('Module:Class') ---@class StandingsTiebreaker ---@field context 'full'|'ml'|'h2h' +---@field config table? ---@field valueOf fun(self: StandingsTiebreaker, state:TiebreakerOpponent[], opponent: TiebreakerOpponent): integer ---@field display fun(self: StandingsTiebreaker, state:TiebreakerOpponent[], opponent: TiebreakerOpponent): string ---@field headerTitle fun(self: StandingsTiebreaker): string -local StandingsTiebreaker = Class.new(function (self, context) +local StandingsTiebreaker = Class.new(function (self, context, config) self.context = context + self.config = config end) ---@param state TiebreakerOpponent[] From 8450dc2d45da66b5788e0895b9e36d341a64876a Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:53:07 +0900 Subject: [PATCH 5/8] fix id format --- lua/wikis/commons/Standings/Tiebreaker/Factory.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua index 361ebcd0239..4ff529d1ec3 100644 --- a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua +++ b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua @@ -44,7 +44,7 @@ function TiebreakerFactory._parseTiebreakerInput(input) return { name = tiebreakerName, context = tiebreakerContext, - id = tiebreakerContext .. tiebreakerName, + id = tiebreakerContext .. '.' .. tiebreakerName, config = Logic.nilIfEmpty(input) } end @@ -56,7 +56,7 @@ function TiebreakerFactory._parseTiebreakerInput(input) return { name = name, context = context, - id = context .. name, + id = context .. '.' .. name, } end From bcb0efa05daea6112cb153c9061d39fa6fb5ae12 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 19 Mar 2026 16:57:41 +0900 Subject: [PATCH 6/8] type annotation --- lua/wikis/commons/Widget/Standings/Swiss.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/wikis/commons/Widget/Standings/Swiss.lua b/lua/wikis/commons/Widget/Standings/Swiss.lua index 583610bcc9a..55374349836 100644 --- a/lua/wikis/commons/Widget/Standings/Swiss.lua +++ b/lua/wikis/commons/Widget/Standings/Swiss.lua @@ -22,16 +22,17 @@ local OpponentDisplay = Lua.import('Module:OpponentDisplay/Custom') ---@class StandingsSwissWidget: Widget ---@operator call(table): StandingsSwissWidget +---@field props {standings: StandingsModel?} local StandingsSwissWidget = Class.new(Widget) ---@return Widget? function StandingsSwissWidget:render() - if not self.props.standings then + local standings = self.props.standings + + if not standings then return end - ---@type StandingsModel - local standings = self.props.standings local lastRound = standings.rounds[#standings.rounds] return DataTable{ From 34b327b9885712e440db0e9350a80b5483e9a999 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 19 Mar 2026 17:03:36 +0900 Subject: [PATCH 7/8] fix incorrect storage --- lua/wikis/commons/Standings/Parser.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/Standings/Parser.lua b/lua/wikis/commons/Standings/Parser.lua index 8065a0c63ca..62e2b66633d 100644 --- a/lua/wikis/commons/Standings/Parser.lua +++ b/lua/wikis/commons/Standings/Parser.lua @@ -123,7 +123,7 @@ function StandingsParser.parse(rounds, opponents, bgs, title, matches, standings tiebreakers = Array.map(parsedTiebreakers, function(parsedTiebreaker, tiebreakerIndex) local tiebreakerName = parsedTiebreaker.name local tiebreakerContextType = parsedTiebreaker.context - local tiebreakerId = tiebreakerContextType .. tiebreakerName + local tiebreakerId = parsedTiebreaker.id local tiebreaker = tiebreakers[tiebreakerIndex] if tiebreakerContextType ~= 'full' then return { From d73b1fbe1108ec59d20745f2e33d691129bbdd6d Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 19 Mar 2026 17:05:12 +0900 Subject: [PATCH 8/8] remove unused function --- lua/wikis/commons/Standings/Tiebreaker/Factory.lua | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua index 4ff529d1ec3..2fa317c0294 100644 --- a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua +++ b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua @@ -75,19 +75,6 @@ function TiebreakerFactory.validateAndNormalizeInput(input) return parsedInput end ----@deprecated ----@param tiebreakerId string ----@return StandingsTiebreaker -function TiebreakerFactory.tiebreakerFromId(tiebreakerId) - local context, name = unpack(String.split(tiebreakerId, '%.')) - local tiebreakerClassName = NAME_TO_CLASS[name] - assert(tiebreakerClassName, "Invalid tiebreaker type: " .. tostring(tiebreakerId)) - ---@type StandingsTiebreaker - local TiebreakerClass = Lua.import('Module:Standings/Tiebreaker/' .. tiebreakerClassName) - - return TiebreakerClass(context) -end - ---@param parsedTiebreaker ParsedTiebreaker ---@return StandingsTiebreaker function TiebreakerFactory.getTiebreaker(parsedTiebreaker)