Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lua/wikis/commons/Standings/Parse/Wiki.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 35 additions & 22 deletions lua/wikis/commons/Standings/Parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}[]
Expand Down Expand Up @@ -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),
Expand All @@ -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),
}
Expand All @@ -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
Expand All @@ -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
Expand Down
57 changes: 43 additions & 14 deletions lua/wikis/commons/Standings/Tiebreaker/Factory.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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
4 changes: 3 additions & 1 deletion lua/wikis/commons/Standings/Tiebreaker/Interface.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
7 changes: 4 additions & 3 deletions lua/wikis/commons/Widget/Standings/Swiss.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
Loading