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 diff --git a/lua/wikis/commons/Standings/Parser.lua b/lua/wikis/commons/Standings/Parser.lua index da58111abc0..62e2b66633d 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 = parsedTiebreaker.id + 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 182751640e3..2fa317c0294 100644 --- a/lua/wikis/commons/Standings/Tiebreaker/Factory.lua +++ b/lua/wikis/commons/Standings/Tiebreaker/Factory.lua @@ -7,10 +7,18 @@ 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 id string +---@field config table? + local NAME_TO_CLASS = { buchholz = 'Buchholz', manual = 'Manual', @@ -27,35 +35,56 @@ 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 = Logic.emptyOr(Table.extract(input, 'context'), 'full') + return { + name = tiebreakerName, + context = tiebreakerContext, + id = tiebreakerContext .. '.' .. tiebreakerName, + config = Logic.nilIfEmpty(input) + } + end local context, name = unpack(String.split(input, '%.')) if name == nil then name = context context = 'full' end + return { + name = name, + context = context, + id = context .. '.' .. name, + } +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 +---@param parsedTiebreaker ParsedTiebreaker ---@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)) +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(context) + 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[] 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{