Module:Bracket

From TwogPedia
Revision as of 22:30, 16 October 2023 by Couchor (talk | contribs)

Documentation for this module may be created at Module:Bracket/doc

local getArgs = require('Module:Arguments').getArgs
local html = require('Module:MatchHTML')
local Bracket = {}

local VariablesLua = mw.ext.VariablesLua

function Bracket.main(frame)
	local args = getArgs(frame)
	if args.teams == nil then return 'Have to enter a "teams" value as a Bracket template paramater' end
	-- if best-of set to bracket, then set VariableLua for it for entire bracket to use
	if args.bestof then VariablesLua.vardefine('bestof', args.bestof) end
    if args.twitch then VariablesLua.vardefine('twitch', args.twitch) end
	if args.youtube then VariablesLua.vardefine('youtube', args.youtube) end
	-- set const values
	local roundW = args.roundW or 180
	-- create all the html parent elements
	local container = mw.html.create('div'):addClass('bracket')
	if args.lteams then container:addClass('de') end
	local wrapper = mw.html.create('div'):addClass('bracket-wrapper')
	local ubheader = mw.html.create('div'):addClass('bracket-header')
	local ubrounds = mw.html.create('div'):addClass('bracket-rounds')
	local lbheader = mw.html.create('div'):addClass('bracket-header')
	local lbrounds = mw.html.create('div'):addClass('bracket-rounds')
	container:node(wrapper)
	-- get max amount of matches for the first round
	local maxUbroundAmount = args.rounds or math.ceil(math.log(args.teams) / math.log(2))
	local ubroundAmount = 0
	local lbroundAmount = 0

	local upper = args.upper ~= nil and mw.text.jsonDecode(args.upper) or {}
	local lower = args.lower ~= nil and mw.text.jsonDecode(args.lower) or {}
	local lteams = tonumber(args.lteams)

	local matchAmount = (2^maxUbroundAmount)
	local upperMatches = args.teams and args.teams / 2 or 0
	local lowerMatches = lteams and lteams / 2 or 0
	local dropping = upperMatches
	local upperRounds = {}
	local lowerRounds = {}

	-- Go through upper bracket to see how many rounds and matches it has
	while tonumber(matchAmount) > 1 do
		ubroundAmount = ubroundAmount + 1
	
		local round = upper['R' .. ubroundAmount] ~= nil and mw.text.jsonDecode(upper['R' .. ubroundAmount]) or nil
		local roundTitle = (round and round.title) or lteams and 'Upper round ' .. ubroundAmount or 'Round ' .. ubroundAmount 
		
		-- if round.matches is lower than previous round divided by 2
		if (round and round.matches) and round.matches * 2 < matchAmount then
			matchAmount = matchAmount / 2
		else 
			matchAmount = (round and round.matches) or matchAmount / 2
		end
		upperRounds[ubroundAmount] = {
			matches = tonumber(matchAmount),
			title = roundTitle
		}
	end
	
	-- If lower bracket exists then populate lowerRounds and adjust upperRounds
	if lteams ~= nil then
		local ubRound = 1
		if lteams > 0 then ubRound = 0 end
		local lbRound = 1
		local lower = args.lower ~= nil and mw.text.jsonDecode(args.lower) or {}
		local upperMatches = upperRounds[1].matches
		local lowerMatches = lteams > 0 and lteams / 2 or upperMatches / 2
		if args.ladvance then lowerMatches = lowerMatches + args.ladvance / 2 end
		-- Start loop
		while #upperRounds >= ubRound and lowerMatches >= 1 do
			upperMatches = (upperRounds[ubRound] and upperRounds[ubRound].matches) or 0
			local nextUpperMatches = upperRounds[ubRound + 1] and upperRounds[ubRound + 1].matches or 0
			local round = lower['R' .. lbRound] ~= nil and mw.text.jsonDecode(lower['R' .. lbRound]) or {}
			local roundTitle = round.title or upperMatches ~= 1 and 'LB Round ' .. lbRound or 'LB Final'
			-- If amount of matches is manually entered, then overwrite
			if round.matches then lowerMatches = round.matches end
			
			-- If lowerMatches is exactly 2 times lower than upperMatches
			if lowerMatches * 2 == upperMatches then 
				table.insert(lowerRounds, Bracket.newRound(lowerMatches, roundTitle))
				ubRound = ubRound + 1
			-- If lowerMatches is same as upperMatches
			elseif lowerMatches == upperMatches then
				table.insert(lowerRounds, Bracket.newRound(lowerMatches, roundTitle))
				if upperMatches ~= 1 then 
					table.insert(upperRounds, ubRound + 1, {matches = 0})
					nextUpperMatches = 0
				end
				ubRound = ubRound + 1
			-- Else if upperMatches is lower
			elseif upperMatches < lowerMatches then
				table.insert(lowerRounds, Bracket.newRound(lowerMatches, roundTitle))
			end
			
			lowerMatches = upperMatches == 0 and lowerMatches or nextUpperMatches / 2 + lowerMatches / 2
			if lbRound == 1 and args.ladvance then lowerMatches = lowerMatches / 2 end
			lbRound = lbRound + 1
			if upperMatches == 0 then ubRound = ubRound + 1 end
		end
	end

	-- If lower and upper bracket both start with same amount of teams, then shift upper bracket one column to the right
	if args.teams == args.lteams then
		table.insert(upperRounds, 1, {matches = 0})
	end
	-- If ladvance is set then add empty round to UB round 2
	if args.ladvance then table.insert(upperRounds, 2, {matches = 0}) end

	-- draw upper bracket
	local upperOffset = 0
	for i = 1, #upperRounds do
		local roundNode = mw.html.create('div'):addClass('bracket-round')
		local round = upperRounds[i]
		local argsRound = upper['R' .. i - upperOffset] ~= nil and mw.text.jsonDecode(upper['R' .. i - upperOffset]) or nil
		local matches = round.matches
		if round.matches == 0 then upperOffset = upperOffset + 1 end

		-- Add round header to container
		ubheader:node(mw.html.create('div'):wikitext(round.title))
		
		for j = 1, matches do
			local match = argsRound and argsRound['M' .. j] or nil
			-- if no existing match html, then create it from scratch
			if match == nil then
				roundNode:node(mw.html.create('div'):addClass('match'):node(html.team('')):node(html.team('')))
				-- roundNode:node(mw.html.create('div'):addClass('match'):node(team1):node(team2):node(html.team('')):node(html.team('')))
			else 
				roundNode:node(match)
			end
		end
		--see if next rounds matchAmount is equal to or greater than current rounds
		local nextRound = upperRounds[i + 1] or {} 
		if tonumber(matches) <= tonumber(nextRound.matches or 0) then
			roundNode:addClass('round-offset')
		else
			if tonumber(matches) ~= 1 or lteams ~= nil then
				roundNode:addClass('conn-r')
			end
		end

		-- If previous round was empty
		if upperRounds[i - 1] and upperRounds[i - 1].matches == 0 then 
			if i ~= 2 then
				roundNode:addClass('long') 
			else 
				roundNode:addClass('first') 
			end
		end
		ubrounds:node(roundNode)
	end
	
	-- draw lower bracket
	if args.lteams then
		for i = 1, #lowerRounds do
			local roundNode = mw.html.create('div'):addClass('bracket-round')
			local round = lowerRounds[i]
			local argsRound = lower['R' .. i] ~= nil and mw.text.jsonDecode(lower['R' .. i]) or nil
			local matches = round.matches
			-- Add round header to container
			lbheader:node(mw.html.create('div'):wikitext(round.title))
			for j = 1, matches do
				local match = argsRound and argsRound['M' .. j] or nil
				-- if no existing match html, then create it from scratch
				if match == nil then
					roundNode:node(mw.html.create('div'):addClass('match'):node(html.team('')):node(html.team('')))
				else 
					roundNode:node(match)
				end
			end
			--see if next rounds matchAmount is equal to or greater than current rounds
			local nextRound = lowerRounds[i + 1] or {} 
			if tonumber(matches) <= tonumber(nextRound.matches or 0) then
				roundNode:addClass('round-offset')
			else
				if tonumber(matches) ~= 1 or lteams ~= nil then
					roundNode:addClass('conn-r')
				end
			end
			
			-- Add adjusting classes for connectors if next round is round offset
			if lowerRounds[i + 1] and lowerRounds[i + 2] and lowerRounds[i + 1].matches == lowerRounds[i + 1].matches then
				roundNode:addClass('conn-adj')
			end
			
			-- If previous round was empty
			if lowerRounds[i - 1] and lowerRounds[i - 1].matches == 0 then roundNode:addClass('long') end
			if i == #lowerRounds then roundNode:addClass('last') end
			lbrounds:node(roundNode)
		end
		-- Add grand final
		local maxMatches = 0
	    for _, obj in ipairs(upperRounds) do
	        if obj.matches and obj.matches > maxMatches then
	            maxMatches = obj.matches
	        end
	    end

		local grandFinal = mw.html.create('div'):addClass('grand-final'):css('min-width', roundW .. 'px'):css('width', roundW .. 'px'):css('height', (maxMatches * 56 + (maxMatches - 1) * 10 + 26 + 10) .. 'px')
		grandFinal:node(mw.html.create('div'):addClass('bracket-header'):node(mw.html.create('div'):wikitext('Grand Final')))
		if args.final == nil then
			grandFinal:node(mw.html.create('div'):addClass('match'):node(html.team('')):node(html.team('')))
		else 
			grandFinal:node(mw.html.create('div'):addClass('grand-final-round'):node(args.final))
		end
		container:node(grandFinal)
	end
	
	local maxRounds = #upperRounds <= #lowerRounds and #lowerRounds or #upperRounds
	ubheader:css('grid-template-columns', 'repeat(' .. maxRounds .. ', ' .. roundW .. 'px)')
	ubrounds:css('grid-template-columns', 'repeat(' .. maxRounds .. ', ' .. roundW .. 'px)')
	lbheader:css('grid-template-columns', 'repeat(' .. maxRounds .. ', ' .. roundW .. 'px)')
	lbrounds:css('grid-template-columns', 'repeat(' .. maxRounds .. ', ' .. roundW .. 'px)')
	
	wrapper:node(ubheader):node(ubrounds)
	if args.lteams then wrapper:node(lbheader):node(lbrounds) end

    if args.bestof then VariablesLua.vardefine('bestof', '') end
    if args.twitch then VariablesLua.vardefine('twitch', '') end
	if args.youtube then VariablesLua.vardefine('youtube', '') end

	return container
end

function Bracket.newRound(matches, title)
	return {
		matches = matches,
		title = title
	}
end

return Bracket