Repository: Liquipedia/Lua-Modules
Pull request #5636: feat(match2): Legacy wrappers for cr
Author: login: hjpalpha
Labels: name: c: match2
name: g: clashroyale
description: Clash Royale
Created: 2025-03-13T10:44:57Z | Merged: 2025-03-17T09:30:44Z

## Summary
Legacy wrappers for cr

## How did you test this change?
live for new modules, dev for existing ones

## to do
- [x] write BMS wrapper (convert input into a readable format for the commons processing, mostly due to submatch bullshit ...)
- [x] bot brackets
- [x] matchlist wrapper (version 1)
- [x] bot matchlists (version 1)
- [x] matchlist wrapper (version 2)
- [x] bot matchlists (version 2)

## needs custom mapping
- [x] https://liquipedia.net/clashroyale/Template:32DEBracket

## to do manually
- https://liquipedia.net/clashroyale/Template:4DE2v2Bracket (2v2 ...)
- bad mapping
  - https://liquipedia.net/clashroyale/Template:12DEBracket (2)
  - https://liquipedia.net/clashroyale/Template:64DEBracket (1)
  - https://liquipedia.net/clashroyale/Template:8DE4LTeamBracket (5)
- utter bullshit
  - https://liquipedia.net/clashroyale/Template:32SEBracket (28)
- https://liquipedia.net/clashroyale/Template:24SETeamBracket (1 usage with bad inputs)


Diff:
diff --git a/lua/wikis/clashroyale/MatchGroup/Input/Custom.lua b/lua/wikis/clashroyale/MatchGroup/Input/Custom.lua
index 3982e6d6eab..ba1c8907e34 100644
--- a/lua/wikis/clashroyale/MatchGroup/Input/Custom.lua
+++ b/lua/wikis/clashroyale/MatchGroup/Input/Custom.lua
@@ -92,6 +92,11 @@ end
 function MapFunctions.calculateMapScore(map)
 	local winner = tonumber(map.winner)
 	return function(opponentIndex)
+		-- to be able to legacy convert old brackets/matchlists need to add manual score input per map ...
+		if Logic.isNumeric(map['score' .. opponentIndex]) then
+			return tonumber(map['score' .. opponentIndex])
+		end
+
 		-- TODO Better to check if map has started, rather than finished, for a more correct handling
 		if not winner then
 			return
diff --git a/lua/wikis/clashroyale/MatchGroup/Legacy/BMS.lua b/lua/wikis/clashroyale/MatchGroup/Legacy/BMS.lua
new file mode 100644
index 00000000000..3ec0f213cbe
--- /dev/null
+++ b/lua/wikis/clashroyale/MatchGroup/Legacy/BMS.lua
@@ -0,0 +1,63 @@
+---
+-- @Liquipedia
+-- wiki=clashroyale
+-- page=Module:MatchGroup/Legacy/BMS
+--
+-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
+--
+
+local Arguments = require('Module:Arguments')
+local Array = require('Module:Array')
+local FnUtil = require('Module:FnUtil')
+local Json = require('Module:Json')
+local Logic = require('Module:Logic')
+local Table = require('Module:Table')
+
+local NUMBER_OF_OPPONENTS = 2
+local NUMBER_OF_PLAYERS = 5
+
+local LegacyBracketMatchSummary = {}
+
+---@param frame Frame
+---@return string
+function LegacyBracketMatchSummary.run(frame)
+	local args = Arguments.getArgs(frame)
+
+	local maps = Array.mapIndexes(FnUtil.curry(LegacyBracketMatchSummary.handleMap, args))
+
+	local processedArgs = Table.copy(args)
+	Array.forEach(maps, function(map, mapIndex)
+		processedArgs['map' .. mapIndex] = map
+	end)
+
+	return Json.stringify(processedArgs)
+end
+
+---@param args table
+---@param mapIndex integer
+---@return table?
+function LegacyBracketMatchSummary.handleMap(args, mapIndex)
+	local prefix = 'g' .. mapIndex
+
+	local scores = Array.parseCommaSeparatedString(args[prefix .. 'score'], '-')
+
+	local map = {
+		winner = args[prefix .. 'win'],
+		score1 = scores[1],
+		score2 = scores[2],
+		vod = args['vodgame' .. mapIndex],
+	}
+
+	Array.forEach(Array.range(1, NUMBER_OF_OPPONENTS), function(opponentIndex)
+		Array.forEach(Array.range(1, NUMBER_OF_PLAYERS), function(playerIndex)
+			local playerPrefix = 't' .. opponentIndex .. 'p' .. playerIndex
+			map[playerPrefix] = args[prefix .. playerPrefix]
+			map[playerPrefix .. 'link'] = args[prefix .. playerPrefix .. 'link']
+			map[playerPrefix .. 'flag'] = args[prefix .. playerPrefix .. 'flag']
+		end)
+	end)
+
+	return Logic.nilIfEmpty(map)
+end
+
+return LegacyBracketMatchSummary
diff --git a/lua/wikis/clashroyale/MatchGroup/Legacy/Default.lua b/lua/wikis/clashroyale/MatchGroup/Legacy/Default.lua
new file mode 100644
index 00000000000..f73ed0b4b59
--- /dev/null
+++ b/lua/wikis/clashroyale/MatchGroup/Legacy/Default.lua
@@ -0,0 +1,31 @@
+---
+-- @Liquipedia
+-- wiki=clashroyale
+-- page=Module:MatchGroup/Legacy/Default
+--
+-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
+--
+
+local Class = require('Module:Class')
+local Lua = require('Module:Lua')
+
+local MatchGroupLegacy = Lua.import('Module:MatchGroup/Legacy')
+
+---@class ClashroyaleMatchGroupLegacyDefault: MatchGroupLegacy
+local MatchGroupLegacyDefault = Class.new(MatchGroupLegacy)
+
+---@return table
+function MatchGroupLegacyDefault:getMap()
+	return {
+		['$notEmpty$'] = 'map$1$',
+		['$parse$'] = 'map$1$',
+	}
+end
+
+---@param frame Frame
+---@return string
+function MatchGroupLegacyDefault.run(frame)
+	return MatchGroupLegacyDefault(frame):build()
+end
+
+return MatchGroupLegacyDefault
diff --git a/lua/wikis/clashroyale/MatchGroup/Legacy/MatchList.lua b/lua/wikis/clashroyale/MatchGroup/Legacy/MatchList.lua
new file mode 100644
index 00000000000..e194f3f21a2
--- /dev/null
+++ b/lua/wikis/clashroyale/MatchGroup/Legacy/MatchList.lua
@@ -0,0 +1,192 @@
+---
+-- @Liquipedia
+-- wiki=clashroyale
+-- page=Module:MatchGroup/Legacy/MatchList
+--
+-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
+--
+
+local Arguments = require('Module:Arguments')
+local Array = require('Module:Array')
+local Json = require('Module:Json')
+local Logic = require('Module:Logic')
+local Match = require('Module:Match')
+local MatchGroup = require('Module:MatchGroup')
+local PageVariableNamespace = require('Module:PageVariableNamespace')
+local Table = require('Module:Table')
+local Template = require('Module:Template')
+
+local OpponentLibraries = require('Module:OpponentLibraries')
+local Opponent = OpponentLibraries.Opponent
+
+local globalVars = PageVariableNamespace()
+local matchlistVars = PageVariableNamespace('LegacyMatchlist')
+
+local LegacyMatchList = {}
+
+local NUMBER_OF_OPPONENTS = 2
+
+-- invoked by Template:Legacy Match list start
+---@param frame Frame
+function LegacyMatchList.init(frame)
+	local args = Arguments.getArgs(frame)
+	local store = Logic.nilOr(
+		Logic.readBoolOrNil(args.store),
+		not Logic.readBool(globalVars:get('disable_LPDB_storage'))
+	)
+
+	matchlistVars:set('store', tostring(store))
+	matchlistVars:set('bracketid', args.id)
+	matchlistVars:set('matchListTitle', args.title or args[1] or 'Match List')
+	matchlistVars:set('width', args.width)
+	matchlistVars:set('hide', args.hide or 'true')
+end
+
+-- invoked by Template:MatchMaps
+---@param frame Frame
+---@return Html|string
+function LegacyMatchList.matchMaps(frame)
+	local args = Arguments.getArgs(frame)
+
+	local processedArgs = Table.copy(args)
+	LegacyMatchList._handleOpponents(processedArgs)
+
+	if processedArgs.date then
+		processedArgs.dateheader = true
+	end
+
+	local details = Json.parseIfTable(args.details) or {}
+	processedArgs.date = Logic.emptyOr(details.date, processedArgs.date)
+	processedArgs.finished = Logic.emptyOr(details.finished, processedArgs.finished)
+
+	Table.deepMergeInto(processedArgs, LegacyMatchList.handleMaps(processedArgs), details)
+
+	processedArgs.details = nil
+
+	-- case matchlist version 1
+	if Logic.isNotEmpty(matchlistVars:get('bracketid')) then
+		Template.stashReturnValue(processedArgs, 'LegacyMatchlist')
+		return mw.html.create('div')
+	else -- case matchlist version 2
+		return Json.stringify(processedArgs)
+	end
+end
+
+---@param processedArgs table
+---@return table
+function LegacyMatchList.handleMaps(processedArgs)
+	local maps = Array.mapIndexes(function(mapIndex)
+		return Logic.nilIfEmpty{winner = Table.extract(processedArgs, 'map' .. mapIndex .. 'win')}
+	end)
+
+	return Table.map(maps, function(mapIndex, map)
+		return 'map' .. mapIndex, map
+	end)
+end
+
+---@param processedArgs table
+function LegacyMatchList._handleOpponents(processedArgs)
+	for opponentIndex = 1, NUMBER_OF_OPPONENTS do
+		if processedArgs['player' .. opponentIndex] and processedArgs['player' .. opponentIndex]:lower() == 'bye' then
+			processedArgs['opponent' .. opponentIndex] = {
+				['type'] = Opponent.literal,
+				name = 'BYE',
+			}
+		else
+			processedArgs['opponent' .. opponentIndex] = {
+				['type'] = Opponent.solo,
+				name = processedArgs['player' .. opponentIndex],
+				flag = processedArgs['player' .. opponentIndex .. 'flag'],
+			}
+			if processedArgs['player' .. opponentIndex] == '' then
+				processedArgs['opponent' .. opponentIndex]['type'] = 'literal'
+			end
+		end
+
+		processedArgs['player' .. opponentIndex] = nil
+	end
+end
+
+-- invoked by Template:Match list end
+---@return string
+function LegacyMatchList.close()
+	local matches = Template.retrieveReturnValues('LegacyMatchlist') --[[@as table]]
+
+	for matchIndex, match in ipairs(matches) do
+		matches['M' .. matchIndex] = Match.makeEncodedJson(match)
+		matches[matchIndex] = nil
+	end
+
+	matches.id = matchlistVars:get('bracketid')
+	matches.isLegacy = true
+	matches.title = matchlistVars:get('matchListTitle')
+	matches.width = matchlistVars:get('width')
+	if matchlistVars:get('hide') == 'true' then
+		matches.collapsed = true
+		matches.attached = true
+	else
+		matches.collapsed = false
+	end
+	if Logic.readBool(matchlistVars:get('store')) then
+		matches.store = true
+	else
+		matches.noDuplicateCheck = true
+		matches.store = false
+	end
+
+	-- generate Display
+	-- this also stores the MatchData
+	local matchHtml = MatchGroup.MatchList(matches)
+
+	LegacyMatchList._resetVars()
+
+	return matchHtml
+end
+
+function LegacyMatchList._resetVars()
+	globalVars:set('match2bracketindex', (globalVars:get('match2bracketindex') or 0) + 1)
+	globalVars:set('match_number', 0)
+	globalVars:delete('matchsection')
+	matchlistVars:delete('store')
+	matchlistVars:delete('bracketid')
+	matchlistVars:delete('matchListTitle')
+	matchlistVars:delete('hide')
+	matchlistVars:delete('width')
+end
+
+-- handle the second version of matchlists ...
+-- invoked by Template:LegacyMatchList
+function LegacyMatchList.run(frame)
+	local args = Arguments.getArgs(frame)
+	local store = Logic.nilOr(
+		Logic.readBoolOrNil(args.store),
+		not Logic.readBool(globalVars:get('disable_LPDB_storage'))
+	)
+
+	local matches = Array.mapIndexes(function(matchIndex)
+		return args['match' .. matchIndex]
+	end)
+
+	local matchListArgs = Table.copy(matches)
+	matchListArgs.id = args.id
+	matchListArgs.isLegacy = true
+	matchListArgs.title = args.title or args[1] or 'Match List'
+	matchListArgs.width = args.width
+
+	if Logic.nilOr(Logic.readBoolOrNil(args.hide), true) then
+		matchListArgs.collapsed = true
+		matchListArgs.attached = true
+	else
+		matchListArgs.collapsed = false
+	end
+	if store then
+		matchListArgs.store = true
+	else
+		matchListArgs.noDuplicateCheck = true
+		matchListArgs.store = false
+	end
+
+	return MatchGroup.MatchList(matchListArgs)
+end
+
+return LegacyMatchList
diff --git a/lua/wikis/clashroyale/MatchSummary.lua b/lua/wikis/clashroyale/MatchSummary.lua
index ffc04d826bb..cd880a25987 100644
--- a/lua/wikis/clashroyale/MatchSummary.lua
+++ b/lua/wikis/clashroyale/MatchSummary.lua
@@ -71,6 +71,7 @@ function CustomMatchSummary._createGame(game, gameIndex, date)
 			local playerCards = player.cards or {}
 			local cards = Array.map(Array.range(1, NUM_CARDS_PER_PLAYER), function(idx)
 				return playerCards[idx] or DEFAULT_CARD end)
+			---@cast cards table
 			cards.tower = playerCards.tower
 			return cards
 		end)
@@ -126,12 +127,21 @@ function CustomMatchSummary._getSubMatchOpponentsAndPlayers(match, subMatch, sub
 	subMatch.opponents = Array.map(Array.range(1, #subMatch.players), function(opponentIndex)
 		local score, status = MatchGroupInputUtil.computeOpponentScore(
 			{opponentIndex = opponentIndex},
-			FnUtil.curry(MatchGroupInputUtil.computeMatchScoreFromMapWinners, subMatch.games)
+			FnUtil.curry(CustomMatchSummary.computeSubMatchScore, subMatch.games)
 		)
 		return {score = score, status = status}
 	end)
 end
 
+---@param games {winner: integer?, opponents: {score: integer?}[]}[]
+---@param opponentIndex integer
+---@return integer
+function CustomMatchSummary.computeSubMatchScore(games, opponentIndex)
+	return Array.reduce(Array.map(games, function(game)
+		return (game.opponents[opponentIndex] or {}).score or (game.winner == opponentIndex and 1 or 0)
+	end), Operator.add, 0)
+end
+
 ---@param subMatch table
 function CustomMatchSummary._calculateSubMatchWinner(subMatch)
 	subMatch.scores = Array.map(subMatch.opponents, Operator.property('score'))
diff --git a/lua/wikis/commons/Array.lua b/lua/wikis/commons/Array.lua
index a2566ad871f..dadfc4a4559 100644
--- a/lua/wikis/commons/Array.lua
+++ b/lua/wikis/commons/Array.lua
@@ -530,7 +530,7 @@ Array.reduce({2, 3, 5}, pow)
 ---@generic T, V
 ---@param array T[]
 ---@param operator fun(aggregate: V, arrayValue: T): V
----@param initialValue V
+---@param initialValue V?
 ---@return V
 ---@nodiscard
 function Array.reduce(array, operator, initialValue)


Reviews:
user: login: Rathoz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T11:51:16Z
user: login: Rathoz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T11:53:00Z
user: login: hjpalpha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T13:08:18Z
user: login: Rathoz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T13:11:05Z
user: login: Rathoz
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T13:11:33Z
user: login: hjpalpha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T13:57:42Z
user: login: hjpalpha
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-14T11:13:41Z
user: login: Rathoz
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-17T09:30:34Z

Review comments:
diff_hunk: @@ -126,12 +127,21 @@ function CustomMatchSummary._getSubMatchOpponentsAndPlayers(match, subMatch, sub
 	subMatch.opponents = Array.map(Array.range(1, #subMatch.players), function(opponentIndex)
 		local score, status = MatchGroupInputUtil.computeOpponentScore(
 			{opponentIndex = opponentIndex},
-			FnUtil.curry(MatchGroupInputUtil.computeMatchScoreFromMapWinners, subMatch.games)
+			FnUtil.curry(CustomMatchSummary.computeSubMatchScore, subMatch.games)
 		)
 		return {score = score, status = status}
 	end)
 end
 
+---@param games {winner: integer?, opponents: {score: integer?}[]}[]
+---@param opponentIndex integer
+---@return integer
+function CustomMatchSummary.computeSubMatchScore(games, opponentIndex)
+	return Array.reduce(Array.map(games, function(game)
+		return (game.opponents[opponentIndex] or {}).score or (game.winner == opponentIndex and 1 or 0)
+	end), Operator.add)
path: lua/wikis/clashroyale/MatchSummary.lua
user: login: Rathoz
body: This can return nil (if games list is empty). Consider adding a default `0` if `return integer` is correct
created_at: 2025-03-13T11:51:16Z
author_association: COLLABORATOR
diff_hunk: @@ -71,6 +71,7 @@ function CustomMatchSummary._createGame(game, gameIndex, date)
 			local playerCards = player.cards or {}
 			local cards = Array.map(Array.range(1, NUM_CARDS_PER_PLAYER), function(idx)
 				return playerCards[idx] or DEFAULT_CARD end)
+			---@cast cards table
path: lua/wikis/clashroyale/MatchSummary.lua
user: login: Rathoz
body: ```suggestion
			---@cast cards string[]
```
Slightly more accurate I think?
created_at: 2025-03-13T11:52:59Z
author_association: COLLABORATOR
diff_hunk: @@ -71,6 +71,7 @@ function CustomMatchSummary._createGame(game, gameIndex, date)
 			local playerCards = player.cards or {}
 			local cards = Array.map(Array.range(1, NUM_CARDS_PER_PLAYER), function(idx)
 				return playerCards[idx] or DEFAULT_CARD end)
+			---@cast cards table
path: lua/wikis/clashroyale/MatchSummary.lua
user: login: hjpalpha
body: it is string[] at that point but need it as a table the line after so can insert the tower bullshit
created_at: 2025-03-13T13:08:18Z
author_association: COLLABORATOR
diff_hunk: @@ -126,12 +127,21 @@ function CustomMatchSummary._getSubMatchOpponentsAndPlayers(match, subMatch, sub
 	subMatch.opponents = Array.map(Array.range(1, #subMatch.players), function(opponentIndex)
 		local score, status = MatchGroupInputUtil.computeOpponentScore(
 			{opponentIndex = opponentIndex},
-			FnUtil.curry(MatchGroupInputUtil.computeMatchScoreFromMapWinners, subMatch.games)
+			FnUtil.curry(CustomMatchSummary.computeSubMatchScore, subMatch.games)
 		)
 		return {score = score, status = status}
 	end)
 end
 
+---@param games {winner: integer?, opponents: {score: integer?}[]}[]
+---@param opponentIndex integer
+---@return integer
+function CustomMatchSummary.computeSubMatchScore(games, opponentIndex)
+	return Array.reduce(Array.map(games, function(game)
+		return (game.opponents[opponentIndex] or {}).score or (game.winner == opponentIndex and 1 or 0)
+	end), Operator.add) or 0
path: lua/wikis/clashroyale/MatchSummary.lua
user: login: Rathoz
body: ```suggestion
	end), Operator.add, 0)
```
created_at: 2025-03-13T13:11:05Z
author_association: COLLABORATOR
diff_hunk: @@ -530,7 +530,7 @@ Array.reduce({2, 3, 5}, pow)
 ---@generic T, V
 ---@param array T[]
 ---@param operator fun(aggregate: V, arrayValue: T): V
----@param initialValue V
+---@param initialValue V?
path: lua/wikis/commons/Array.lua
user: login: Rathoz
body: I'm not sure if this actually is needed, I think the nil possiblity gets accounted into the general V?
created_at: 2025-03-13T13:11:33Z
author_association: COLLABORATOR
diff_hunk: @@ -530,7 +530,7 @@ Array.reduce({2, 3, 5}, pow)
 ---@generic T, V
 ---@param array T[]
 ---@param operator fun(aggregate: V, arrayValue: T): V
----@param initialValue V
+---@param initialValue V?
path: lua/wikis/commons/Array.lua
user: login: hjpalpha
body: vscode yelled at me in several modules, adding this ? shut it up
created_at: 2025-03-13T13:57:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,31 @@
+---
+-- @Liquipedia
+-- wiki=clashroyale
+-- page=Module:MatchGroup/Legacy/Default
+--
+-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
+--
+
+local Class = require('Module:Class')
+local Lua = require('Module:Lua')
+
+local MatchGroupLegacy = Lua.import('Module:MatchGroup/Legacy')
+
+---@class ClashroyaleMatchGroupLegacyDefault: MatchGroupLegacy
+local MatchGroupLegacyDefault = Class.new(MatchGroupLegacy)
+
+---@return table
+function MatchGroupLegacyDefault:getMap()
+	return {
+		['$notEmpty$'] = 'map$1$',
+		['$parse$'] = 'match$1$',
path: lua/wikis/clashroyale/MatchGroup/Legacy/Default.lua
user: login: hjpalpha
body: ```suggestion
		['$parse$'] = 'map$1$',
```
created_at: 2025-03-14T11:13:41Z
author_association: COLLABORATOR

PR comments:
user: login: hjpalpha
created_at: 2025-03-13T11:38:12Z
body: requesting review before starting bot jobs ...
@Rathoz 
~~(please do not merge yet until bracket bot jobs done and i hence listed all the brackets that need manual updating)~~
author_association: COLLABORATOR
user: login: hjpalpha
created_at: 2025-03-13T16:59:05Z
body: rfm
author_association: COLLABORATOR