Module:Bracket

From TwogPedia
Revision as of 23:09, 5 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
	-- 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 'Lower round ' .. lbRound or 'Lower Bracket 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
	for i = 1, #upperRounds do
		local roundNode = mw.html.create('div'):addClass('bracket-round')
		local round = upperRounds[i]
		local argsRound = upper['R' .. i] ~= nil and mw.text.jsonDecode(upper['R' .. i]) or nil
		local matches = round.matches
		-- 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('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(match))
		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):node(lbheader):node(lbrounds)
	
	return container
end

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

return Bracket