Skip to content

Instantly share code, notes, and snippets.

@PhiBabin
Created December 29, 2025 22:14
Show Gist options
  • Select an option

  • Save PhiBabin/947485050b3036fd6dbdd7bcb1d5d8cd to your computer and use it in GitHub Desktop.

Select an option

Save PhiBabin/947485050b3036fd6dbdd7bcb1d5d8cd to your computer and use it in GitHub Desktop.
Attempt at improving the automatic color generation of Beyond All Reason
local gadget = gadget ---@type Gadget
function gadget:GetInfo()
return {
name = "AutoColorPicker",
desc = "Automatically assigns colors to teams",
author = "Damgam, Born2Crawl (color palette), kafka42",
date = "2021",
license = "GNU GPL, v2 or later",
layer = -100,
enabled = true,
}
end
local math_pow = math.pow
local anonymousMode = Spring.GetModOptions().teamcolors_anonymous_mode
local gaiaTeamID = Spring.GetGaiaTeamID()
local teamList = Spring.GetTeamList()
local allyTeamList = Spring.GetAllyTeamList()
local allyTeamCount = #allyTeamList - 1
local isSurvival = Spring.Utilities.Gametype.IsPvE()
local survivalColorNum = 1 -- Starting from color #1
local survivalColorVariation = 0 -- Current color variation
local allyTeamNum = 0
local teamSizes = {}
local myAllyTeamID, myTeamID
if not gadgetHandler:IsSyncedCode() then
myAllyTeamID = Spring.GetMyAllyTeamID()
myTeamID = Spring.GetMyTeamID()
end
-- Special colors
local armBlueColor = "#004DFF" -- Armada Blue
local corRedColor = "#FF1005" -- Cortex Red
local scavPurpColor = "#6809A1" -- Scav Purple
local raptorOrangeColor = "#CC8914" -- Raptor Orange
local gaiaGrayColor = "#7F7F7F" -- Gaia Grey
local legGreenColor = "#0CE818" -- Legion Green
-- NEW IceXuick Colors V6
local ffaColors = {
"#004DFF", -- 1
"#FF1005", -- 2
"#0CE908", -- 3
"#FFD200", -- 4
"#F80889", -- 5
"#09F5F5", -- 6
"#FF6107", -- 7
"#F190B3", -- 8
"#097E1C", -- 9
"#C88B2F", -- 10
"#7CA1FF", -- 11
"#9F0D05", -- 12
"#3EFFA2", -- 13
"#F5A200", -- 14
"#C4A9FF", -- 15
"#0B849B", -- 16
"#B4FF39", -- 17
"#FF68EA", -- 18
"#D8EEFF", -- 19
"#689E3D", -- 20
"#B04523", -- 21
"#FFBB7C", -- 22
"#3475FF", -- 23
"#DD783F", -- 24
"#FFAAF3", -- 25
"#4A4376", -- 26
"#773A01", -- 27
"#B7EA63", -- 28
"#764A4A", -- 29
"#7EB900", -- 30
}
-- delete excess so a table shuffe wont use the colors added on the bottom
if #ffaColors > #teamList-1 then
for i = #teamList, #ffaColors do
ffaColors[i] = nil
end
end
-- Tailwind v4 color palette (Mostly brightness 200 to 800)
-- Note: the official color palette used P3 colors (which are meant for HDR displays), this is the fallback RGB colors.
local gradients = {
blue = {{190, 219, 255}, {142, 197, 255}, {81, 162, 255}, {43, 127, 255}, {21, 93, 252}, {20, 71, 230}, {25, 60, 184}},
cyan = {{162, 244, 253}, {83, 234, 253}, {0, 211, 242}, {0, 184, 219}, {0, 146, 184}, {0, 117, 149}, {0, 95, 120}},
violet = {{221, 214, 255}, {196, 180, 255}, {166, 132, 255}, {142, 81, 255}, {127, 34, 254}, {112, 8, 231}, {93, 14, 192}, {77, 23, 154}},
fusia = {{246, 207, 255}, {244, 168, 255}, {237, 106, 255}, {225, 42, 251}, {200, 0, 222}, {168, 0, 183}, {138, 1, 148}},
pink = {{253, 165, 213}, {251, 100, 182}, {246, 51, 154}, {230, 0, 118}, {198, 0, 92}, {163, 0, 76}, {134, 16, 67}},
red = {{255, 201, 201}, {255, 162, 162}, {255, 100, 103}, {251, 44, 54}, {231, 0, 11}, {193, 0, 7}, {159, 7, 18}},
orange = {{255, 214, 167}, {255, 184, 106}, {255, 137, 4}, {255, 105, 0}, {245, 73, 0}, {202, 53, 0}},
yellow = {{255, 240, 133}, {255, 223, 32}, {253, 199, 0}, {240, 177, 0}, {208, 135, 0}, {166, 95, 0}, {137, 75, 0}},
green = {{185, 248, 207}, {123, 241, 168}, {5, 223, 114}, {0, 201, 80}, {0, 166, 62}, {0, 130, 54}, {1, 102, 48}},
lime = {{236, 252, 202}, {216, 249, 153}, {187, 244, 81}, {154, 230, 0}, {124, 207, 0}, {94, 165, 0}, {73, 125, 0}, {60, 99, 0}, {53, 83, 14}},
teal = {{150, 247, 228}, {70, 236, 213}, {0, 213, 190}, {0, 187, 167}, {0, 150, 137}, {0, 120, 111}, {0, 95, 90}},
sky = {{184, 230, 254}, {116, 212, 255}, {0, 188, 255}, {0, 166, 244}, {0, 132, 209}, {0, 105, 168}, {0, 89, 138}},
amber = {{254, 230, 133}, {255, 210, 48}, {255, 185, 0}, {254, 154, 0}, {225, 113, 0}, {187, 77, 0}, {151, 60, 0}}, -- very similar to orange, so only use it in the 4 teams case
}
local allGrandientNames = {"blue", "red", "green", "yellow", "fusia", "teal", "orange", "pink", "lime", "violet", "cyan", "sky"}
local gradientGroupPerNumTeams = {
-- One team
{
{"blue", "sky", "violet", "green", "lime"}, -- cold
},
-- 2 teams
{
{"blue", "sky", "violet", "green", "lime"}, -- cold
{"red", "pink", "fusia", "orange", "yellow"}, -- warm
},
-- 3 teams
{
{"blue", "sky", "violet"}, -- blue
{"red", "orange", "yellow"}, -- red
{"green", "lime", "teal"}, -- green
},
-- 4 teams
{
{"blue", "sky", "violet"}, -- blue
{"red", "pink", "fusia"}, -- red pink
{"green", "lime", "teal"}, -- green
{"orange", "yellow"}, -- yellow orange
},
-- 5 teams
{
{"blue", "sky"}, -- blue
{"red", "pink"}, -- red pink
{"green", "lime"}, -- green
{"orange", "yellow"},
{"violet", "fusia"},
},
-- 6 teams
{
{"blue", "sky"}, -- blue
{"red", "pink"}, -- red pink
{"green", "lime"}, -- green
{"orange", "yellow"},
{"violet", "fusia"},
{"teal", "cyan"},
},
}
local survivalColors = {
"#0B3EF3", -- 1
"#FF1005", -- 2
"#0CE908", -- 3
"#ffab8c", -- 4
"#09F5F5", -- 5
"#FCEEA4", -- 6
"#097E1C", -- 7
"#F190B3", -- 8
"#F80889", -- 9
"#3EFFA2", -- 10
"#911806", -- 11
"#7CA1FF", -- 12
"#3c7a74", -- 13
"#B04523", -- 14
"#B4FF39", -- 15
"#773A01", -- 16
"#D8EEFF", -- 17
"#689E3D", -- 18
"#0B849B", -- 19
"#FFD200", -- 20
"#971C48", -- 21
"#4A4376", -- 22
"#764A4A", -- 23
"#4F2684", -- 24
}
local teamColors = {
{ -- One Team (not possible)
{ -- First Team
"#004DFF", -- Armada Blue
},
},
{ -- Two Teams (40 colors)
{ -- First Team (Cool)
"#0B3EF3", --1
"#0CE908", --2
"#00f5e5", --3
"#6941f2", --4
"#8fff94", --5
"#1b702f", --6
"#7cc2ff", --7
"#a294ff", --8
"#0B849B", --9
"#689E3D", --10
"#4F2684", --11
"#2C32AC", --12
"#6968A0", --13
"#D8EEFF", --14
"#3475FF", --15
"#7EB900", --16
"#4A4376", --17
"#B7EA63", --18
"#C4A9FF", --19
"#37713A", --20
},
{ -- Second Team (Warm)
"#FF1005", --1
"#FFD200", --2
"#FF6107", --3
"#F80889", --4
"#FCEEA4", --5
"#8a2828", --6
"#F190B3", --7
"#C88B2F", --8
"#B04523", --9
"#FFBB7C", --10
"#A35274", --11
"#773A01", --12
"#F5A200", --13
"#BBA28B", --14
"#971C48", --15
"#FF68EA", --16
"#DD783F", --17
"#FFAAF3", --18
"#764A4A", --19
"#9F0D05", --20
},
},
{ -- Three Teams (24 colors)
{ -- First Team (Blue)
"#004DFF", -- 1
"#09F5F5", -- 2
"#7CA1FF", -- 3
"#2C32AC", -- 4
"#D8EEFF", -- 5
"#0B849B", -- 6
"#3C7AFF", -- 7
"#5F6492", -- 8
},
{ -- Second Team (Red)
"#FF1005", -- 1
"#FF6107", -- 2
"#FFD200", -- 3
"#FF6058", -- 4
"#FFBB7C", -- 5
"#C88B2F", -- 6
"#F5A200", -- 7
"#9F0D05", -- 8
},
{ -- Third Team (Green)
"#0CE818", -- 1
"#B4FF39", -- 2
"#097E1C", -- 3
"#3EFFA2", -- 4
"#689E3D", -- 5
"#7EB900", -- 6
"#B7EA63", -- 7
"#37713A", -- 8
},
},
{ -- Four Teams (24 colors)
{ -- First Team (Blue)
"#004DFF", -- 1
"#7CA1FF", -- 2
"#D8EEFF", -- 3
"#09F5F5", -- 4
"#3475FF", -- 5
"#0B849B", -- 6
},
{ -- Second Team (Red)
"#FF1005", -- 1
"#FF6107", -- 2
"#FF6058", -- 3
"#B04523", -- 4
"#F80889", -- 5
"#971C48", -- 6
},
{ -- Third Team (Green)
"#0CE818", -- 1
"#B4FF39", -- 2
"#097E1C", -- 3
"#3EFFA2", -- 4
"#689E3D", -- 5
"#7EB900", -- 6
},
{ -- Fourth Team (Yellow)
"#FFD200", -- 1
"#F5A200", -- 2
"#FCEEA4", -- 3
"#FFBB7C", -- 4
"#BBA28B", -- 5
"#C88B2F", -- 6
},
},
{ -- Five Teams (25 colors)
{ -- First Team (Blue)
"#004DFF", -- 1
"#7CA1FF", -- 2
"#D8EEFF", -- 3
"#09F5F5", -- 4
"#3475FF", -- 5
},
{ -- Second Team (Red)
"#FF1005", -- 1
"#FF6107", -- 2
"#FF6058", -- 3
"#B04523", -- 4
"#9F0D05", -- 5
},
{ -- Third Team (Green)
"#0CE818", -- 1
"#B4FF39", -- 2
"#097E1C", -- 3
"#3EFFA2", -- 4
"#689E3D", -- 5
},
{ -- Fourth Team (Yellow)
"#FFD200", -- 1
"#F5A200", -- 2
"#FCEEA4", -- 3
"#FFBB7C", -- 4
"#C88B2F", -- 5
},
{ -- Fifth Team (Fuchsia)
"#F80889", -- 1
"#FF68EA", -- 2
"#FFAAF3", -- 3
"#AA0092", -- 4
"#701162", -- 5
},
},
{ -- Six Teams (24 colors)
{ -- First Team (Blue)
"#004DFF", -- 1
"#7CA1FF", -- 2
"#D8EEFF", -- 3
"#2C32AC", -- 4
},
{ -- Second Team (Red)
"#FF1005", -- 1
"#FF6058", -- 2
"#B04523", -- 3
"#9F0D05", -- 4
},
{ -- Third Team (Green)
"#0CE818", -- 1
"#B4FF39", -- 2
"#097E1C", -- 3
"#3EFFA2", -- 4
},
{ -- Fourth Team (Yellow)
"#FFD200", -- 1
"#F5A200", -- 2
"#FCEEA4", -- 3
"#9B6408", -- 4
},
{ -- Fifth Team (Fuchsia)
"#F80889", -- 1
"#FF68EA", -- 2
"#FFAAF3", -- 3
"#971C48", -- 4
},
{ -- Sixth Team (Orange)
"#FF6107", -- 1
"#FFBB7C", -- 2
"#DD783F", -- 3
"#773A01", -- 4
},
},
{ -- Seven Teams (21 colors)
{ -- First Team (Blue)
"#004DFF", -- 1
"#7CA1FF", -- 2
"#2C32AC", -- 3
},
{ -- Second Team (Red)
"#FF1005", -- 1
"#FF6058", -- 2
"#9F0D05", -- 3
},
{ -- Third Team (Green)
"#0CE818", -- 1
"#B4FF39", -- 2
"#097E1C", -- 3
},
{ -- Fourth Team (Yellow)
"#FFD200", -- 1
"#F5A200", -- 2
"#FCEEA4", -- 3
},
{ -- Fifth Team (Fuchsia)
"#F80889", -- 1
"#FF68EA", -- 2
"#FFAAF3", -- 3
},
{ -- Sixth Team (Orange)
"#FF6107", -- 1
"#FFBB7C", -- 2
"#DD783F", -- 3
},
{ -- Seventh Team (Cyan)
"#09F5F5", -- 1
"#0B849B", -- 2
"#D8EEFF", -- 3
},
},
{ -- Eight Teams (24 colors)
{ -- First Team (Blue)
"#004DFF", -- 1
"#7CA1FF", -- 2
"#2C32AC", -- 3
},
{ -- Second Team (Red)
"#FF1005", -- 1
"#FF6058", -- 2
"#9F0D05", -- 3
},
{ -- Third Team (Green)
"#0CE818", -- 1
"#B4FF39", -- 2
"#097E1C", -- 3
},
{ -- Fourth Team (Yellow)
"#FFD200", -- 1
"#F5A200", -- 2
"#FCEEA4", -- 3
},
{ -- Fifth Team (Fuchsia)
"#F80889", -- 1
"#FF68EA", -- 2
"#971C48", -- 3
},
{ -- Sixth Team (Orange)
"#FF6107", -- 1
"#FFBB7C", -- 2
"#DD783F", -- 3
},
{ -- Seventh Team (Cyan)
"#09F5F5", -- 1
"#0B849B", -- 2
"#D8EEFF", -- 3
},
{ -- Eigth Team (Purple)
"#872DFA", -- 1
"#6809A1", -- 2
"#C4A9FF", -- 3
},
},
}
local r = math.random(1,100000)
math.randomseed(1) -- make sure the next sequence of randoms can be reproduced
local teamRandoms = {}
for i = 1, #teamList do
teamRandoms[teamList[i]] = { math.random(), math.random(), math.random() }
end
math.randomseed(r)
local iconDevModeColors = {
armblue = armBlueColor,
corred = corRedColor,
scavpurp = scavPurpColor,
raptororange = raptorOrangeColor,
gaiagray = gaiaGrayColor,
leggren = legGreenColor,
}
local iconDevMode = Spring.GetModOptions().teamcolors_icon_dev_mode
local iconDevModeColor = iconDevModeColors[iconDevMode]
local function interpolateGradient(gradient, percentage)
if percentage >= 1.0 then
return gradient[#gradient]
end
local colorPercentage = percentage * (#gradient - 1)
local colorA = gradient[math.floor(colorPercentage) + 1]
local colorB = gradient[math.floor(colorPercentage) + 2]
local lerpRatio = colorPercentage - math.floor(colorPercentage)
return {
math.clamp(math.floor((colorB[1] - colorA[1]) * lerpRatio + colorA[1]), 0, 255),
math.clamp(math.floor((colorB[2] - colorA[2]) * lerpRatio + colorA[2]), 0, 255),
math.clamp(math.floor((colorB[3] - colorA[3]) * lerpRatio + colorA[3]), 0, 255)}
end
local function shuffleTable(Table)
local originalTable = {}
table.append(originalTable, Table)
local shuffledTable = {}
if #originalTable > 0 then
repeat
local r = math.random(#originalTable)
table.insert(shuffledTable, originalTable[r])
table.remove(originalTable, r)
until #originalTable == 0
else
shuffledTable = originalTable
end
return shuffledTable
end
local function shuffleAllColors()
ffaColors = shuffleTable(ffaColors)
survivalColors = shuffleTable(survivalColors)
for i = 1, #teamColors do
for j = 1, #teamColors[i] do
teamColors[i][j] = shuffleTable(teamColors[i][j])
end
end
end
local function hex2RGB(hex)
hex = hex:gsub("#", "")
return { tonumber("0x" .. hex:sub(1, 2)), tonumber("0x" .. hex:sub(3, 4)), tonumber("0x" .. hex:sub(5, 6)) }
end
-- we don't want to use FFA colors for TeamFFA, because we want each team to have its own color theme
local useFFAColors = Spring.Utilities.Gametype.IsFFA() and not Spring.Utilities.Gametype.IsTeams()
if not useFFAColors and not teamColors[allyTeamCount] and not isSurvival then -- Edge case for TeamFFA with more than supported number of teams
useFFAColors = true
end
local teamColorsTable = {}
local trueTeamColorsTable = {} -- run first as if we were specs so when we become specs, we can restore the true intended team colors
local trueFfaColors = table.copy(ffaColors) -- run first as if we were specs so when we become specs, we can restore the true intended ffa colors
local trueSurvivalColors = table.copy(survivalColors) -- run first as if we were specs so when we become specs, we can restore the true intended survival colors
local function setupTeamColor(teamID, allyTeamID, isAI, localRun)
if iconDevModeColor then
teamColorsTable[teamID] = {
r = hex2RGB(iconDevModeColor)[1],
g = hex2RGB(iconDevModeColor)[2],
b = hex2RGB(iconDevModeColor)[3],
}
-- Simple Team Colors
elseif localRun and
(Spring.GetConfigInt("SimpleTeamColors", 0) == 1 or (anonymousMode == "allred" and not mySpecState))
then
Spring.Echo("Simple team color")
local brightnessVariation = 0
local maxColorVariation = 0
if Spring.GetConfigInt("SimpleTeamColorsUseGradient", 0) == 1 then
local totalEnemyDimmingCount = 0
for allyTeamID, count in pairs(dimmingCount) do
if allyTeamID ~= myAllyTeamID then
totalEnemyDimmingCount = totalEnemyDimmingCount + count
end
end
brightnessVariation = (0.7 - ((1 / #Spring.GetTeamList(allyTeamID)) * dimmingCount[allyTeamID])) * 255
brightnessVariation = brightnessVariation * math.min((#Spring.GetTeamList(allyTeamID) * 0.8)-1, 1) -- dont change brightness too much in tiny teams
maxColorVariation = 60
end
local color = hex2RGB(ffaColors[allyTeamID+1] or '#333333')
if teamID == gaiaTeamID then
brightnessVariation = 0
maxColorVariation = 0
color = hex2RGB(gaiaGrayColor)
elseif teamID == myTeamID then
brightnessVariation = 0
maxColorVariation = 0
color = {Spring.GetConfigInt("SimpleTeamColorsPlayerR", 0), Spring.GetConfigInt("SimpleTeamColorsPlayerG", 77), Spring.GetConfigInt("SimpleTeamColorsPlayerB", 255)}
elseif allyTeamID == myAllyTeamID then
color = {Spring.GetConfigInt("SimpleTeamColorsAllyR", 0), Spring.GetConfigInt("SimpleTeamColorsAllyG", 255), Spring.GetConfigInt("SimpleTeamColorsAllyB", 0)}
elseif allyTeamID ~= myAllyTeamID then
color = {Spring.GetConfigInt("SimpleTeamColorsEnemyR", 255), Spring.GetConfigInt("SimpleTeamColorsEnemyG", 16), Spring.GetConfigInt("SimpleTeamColorsEnemyB", 5)}
end
color[1] = math.min(color[1] + brightnessVariation, 255) + ((teamRandoms[teamID][1] * (maxColorVariation * 2)) - maxColorVariation)
color[2] = math.min(color[2] + brightnessVariation, 255) + ((teamRandoms[teamID][2] * (maxColorVariation * 2)) - maxColorVariation)
color[3] = math.min(color[3] + brightnessVariation, 255) + ((teamRandoms[teamID][3] * (maxColorVariation * 2)) - maxColorVariation)
teamColorsTable[teamID] = {
r = color[1],
g = color[2],
b = color[3],
}
elseif isAI and string.find(isAI, "Scavenger") then
print("Scavenger team color")
teamColorsTable[teamID] = {
r = hex2RGB(scavPurpColor)[1],
g = hex2RGB(scavPurpColor)[2],
b = hex2RGB(scavPurpColor)[3],
}
elseif isAI and string.find(isAI, "Raptor") then
print("Raptor team color")
teamColorsTable[teamID] = {
r = hex2RGB(raptorOrangeColor)[1],
g = hex2RGB(raptorOrangeColor)[2],
b = hex2RGB(raptorOrangeColor)[3],
}
elseif teamID == gaiaTeamID then
Spring.Echo("gaia team color")
teamColorsTable[teamID] = {
r = hex2RGB(gaiaGrayColor)[1],
g = hex2RGB(gaiaGrayColor)[2],
b = hex2RGB(gaiaGrayColor)[3],
}
elseif isSurvival and survivalColors[#Spring.GetTeamList()-2] then
print("survivor team color")
teamColorsTable[teamID] = {
r = hex2RGB(survivalColors[survivalColorNum])[1]
+ math.random(-survivalColorVariation, survivalColorVariation),
g = hex2RGB(survivalColors[survivalColorNum])[2]
+ math.random(-survivalColorVariation, survivalColorVariation),
b = hex2RGB(survivalColors[survivalColorNum])[3]
+ math.random(-survivalColorVariation, survivalColorVariation),
}
survivalColorNum = survivalColorNum + 1 -- Will start from the next color next time
-- Use procedural colors
elseif
-- Number of player in last non-gaia team is larger than one and there is not a color palette for it
(#Spring.GetTeamList(allyTeamCount-1) > 1 and (not teamColors[allyTeamCount] or not teamColors[allyTeamCount][1][#Spring.GetTeamList(allyTeamCount-1)]))
-- or There is more than 29 players (ignores gaia)
or #Spring.GetTeamList() -1 > #ffaColors
then
local totalNumAllyTeams = allyTeamCount
local overallNumPlayerPerTeam = #Spring.GetTeamList(allyTeamCount-1)
local numPlayerInTeam = #Spring.GetTeamList(allyTeamID)
local nthPlayerInTeam = dimmingCount[allyTeamID] - 1
local useGradientGroup = not (not gradientGroupPerNumTeams[totalNumAllyTeams])
local color = {125, 125, 125}
-- If gradient groups are used, each ally team is assigned N gradients
if useGradientGroup then
local teamGradientGroup = gradientGroupPerNumTeams[totalNumAllyTeams][allyTeamID + 1]
local colorWithinGradient = color
-- (unlikely edge case) If there are more gradient than player, just take the middle color of the gradient
if numPlayerInTeam <= #teamGradientGroup then
local gradient = gradients[teamGradientGroup[nthPlayerInTeam + 1]]
local percentageWithinGradient = 0.5
colorWithinGradient = interpolateGradient(gradient, percentageWithinGradient)
else -- Regular case, where we sample thru the group of gradient
local playerGradientPercentage = nthPlayerInTeam / numPlayerInTeam
local playerGradientId = math.floor(playerGradientPercentage * #teamGradientGroup)
local playerGradientName = teamGradientGroup[playerGradientId+1]
local gradient = gradients[playerGradientName]
local startGradientPercentage = playerGradientId / #teamGradientGroup
local endGradientPercentage = (playerGradientId + 1) / #teamGradientGroup
local percentageWithinGradient = (playerGradientPercentage - startGradientPercentage) / (endGradientPercentage - startGradientPercentage)
colorWithinGradient = interpolateGradient(gradient, percentageWithinGradient)
end
-- local colorWithinGradient = gradient[math.floor(percentageWithinGradient * #gradient) + 1]
color[1] = colorWithinGradient[1]
color[2] = colorWithinGradient[2]
color[3] = colorWithinGradient[3]
else -- Each ally team use one gradient, this gradient might be share with other ally team(s) (e.g one team is bright green, another is dark green)
local gradientId = math.floor(allyTeamID % #allGrandientNames)
local gradName = allGrandientNames[gradientId + 1]
local gradient = gradients[gradName]
-- How many ally team share the same gradient?
local numAllyTeamWithGradient = math.floor(totalNumAllyTeams / #allGrandientNames)
-- Not all gradients will share the same number of ally team
if gradientId < totalNumAllyTeams % #allGrandientNames then
numAllyTeamWithGradient = numAllyTeamWithGradient + 1
end
local nthAllyTeamWithGradient = math.floor(allyTeamID / #allGrandientNames)
-- If there is only a single player in a team, take the color at the center of the gradient
local playerGradientPercentage = 0.0
if numPlayerInTeam <= 1 then
playerGradientPercentage = 0.5
-- Otherwise, we distribute the players amount the gradient
else
playerGradientPercentage = nthPlayerInTeam / (numPlayerInTeam-1)
local percentagePerPlayer = 1.0 / (numPlayerInTeam-1)
-- When there are fewer than 5 players in an ally team, the brighness change between player is large, so instead of sampling the entire gradient
-- we only sample near the middle of the gradient.
local maxPerPlayerPercentage = 0.25
if percentagePerPlayer > maxPerPlayerPercentage then
-- Basically, space every player by 25% of the gradient, but the range is centered on the middle of the gradient
playerGradientPercentage = maxPerPlayerPercentage * nthPlayerInTeam + (1.0 - maxPerPlayerPercentage * numPlayerInTeam) / 2.0
end
end
-- If multiple ally team share the same gradient, create a gap between the end of one team's and the start of the next one
if numAllyTeamWithGradient > 1 and nthAllyTeamWithGradient + 1 ~= numAllyTeamWithGradient then
playerGradientPercentage = 0.9 * playerGradientPercentage
end
local gradientPercentage = playerGradientPercentage / numAllyTeamWithGradient + nthAllyTeamWithGradient / numAllyTeamWithGradient
local colorWithinGradient = interpolateGradient(gradient, gradientPercentage)
color[1] = colorWithinGradient[1]
color[2] = colorWithinGradient[2]
color[3] = colorWithinGradient[3]
end
teamColorsTable[teamID] = {
r = color[1],
g = color[2],
b = color[3],
}
-- auto ffa gradient colored for huge player games
elseif useFFAColors or
-- or Number of player in last non-gaia team is larger than one and there is not a color palette for it
(#Spring.GetTeamList(allyTeamCount-1) > 1 and (not teamColors[allyTeamCount] or not teamColors[allyTeamCount][1][#Spring.GetTeamList(allyTeamCount-1)]))
-- or There is more than 29 players (ignores gaia)
or #Spring.GetTeamList() > 30
-- or the number of players in the last non-gaia team is one and there is more than team than ffaColor
or (#Spring.GetTeamList(allyTeamCount-1) == 1 and not ffaColors[allyTeamCount])
then
Spring.Echo("Random team color useFFAColors =", useFFAColors, " #Spring.GetTeamList() =", #Spring.GetTeamList(), " #Spring.GetTeamList(allyTeamCount-1)=", #Spring.GetTeamList(allyTeamCount-1))
local color = hex2RGB(ffaColors[allyTeamID+1] or '#333333')
-- maxIteration = floor((num_ally_team-1) / 30)
local maxIterations = math.floor((allyTeamID+1)/(#ffaColors))
-- local maxIterations = math.floor((allyTeamID+1) / #ffaColors)
-- dimmingcount here is you're the Nth player of this team to process
-- So if you're the Nth player of a team with M players:
-- brightnessVariation = (0.6 - N / M) * 255 , so the range is in -0.4*255 to 0.6*255 or -102 to 153
local brightnessVariation = (0.6 - ((1 / #Spring.GetTeamList(allyTeamID)) * dimmingCount[allyTeamID])) * 255
-- brightnessVariation *= math.min(M*0.7 - 1, 1)
-- For M == 1 -> -0.3
-- For M == 2 -> 0.4
-- For M >= 3 -> 1
brightnessVariation = brightnessVariation * math.min((#Spring.GetTeamList(allyTeamID) * 0.7)-1, 1) -- dont change brightness too much in tiny teams
-- Basically maxColorVariation gets lower and lower as the number of team increase
-- maxColorVariation = 120 / max(1, num_team)
-- So for #P team 1 2 3 4 5 6 7 8
-- maxColorVariation = 120, 60, 40, 30, 24, 20, 17, 15...
local maxColorVariation = (120 / math.max(1, allyTeamCount-1))
if #Spring.GetTeamList(allyTeamID) == 1 then
brightnessVariation = 0
maxColorVariation = 0
end
-- Basically if there are more player than the table of ffacolors
-- This is make no sense, because this is trying to add a per team variation, instead of a per player variation
if maxIterations > 1 then
-- iteration = 1 + math.floor((allyTeamID+1) / 30 )
-- iteration = 1 + (allyTeamID+1) // 30
local iteration = 1 + math.floor((allyTeamID+1)/(#ffaColors))
-- ffacolor = (allyTeamID+1) - (#ffaColors*(iteration-1)) + 1
-- = (allyTeamID+1) - (30*((allyTeamID+1) // 30)) + 1
-- = (allyTeamID+1) % 30
local ffaColor = (allyTeamID+1) - (#ffaColors*(iteration-1)) + 1
if iteration ~= 1 then
color = hex2RGB(ffaColors[ffaColor])
end
if iteration == 1 then
color[1] = color[1] + 40
color[2] = color[2] + 40
color[3] = color[3] + 40
elseif iteration == 2 then
color[1] = color[1] - 70
color[2] = color[2] - 70
color[3] = color[3] - 70
elseif iteration == 3 then
color[1] = color[1] + 130
color[2] = color[2] + 130
color[3] = color[3] + 130
end
end
if teamID == gaiaTeamID then
brightnessVariation = 0
maxColorVariation = 0
color = hex2RGB(gaiaGrayColor)
end
-- clamp(floor( r + brightnessVariation + 2 * random * maxColorVariation - maxColorVariation)
-- ), 0, 255)
-- so:
-- clamp(floor( r + brightnessVariation + (2 * random - 1.0) * maxColorVariation)
-- ), 0, 255)
-- So:
-- clamp(floor( r + brightnessVariation + random(-1., 1.) * maxColorVariation)
-- ), 0, 255)
-- brightnessVariation is between -0.4*255 to 0.6*255 for large team
color[1] = math.clamp(math.floor(color[1] + brightnessVariation + ((teamRandoms[teamID][1] * (maxColorVariation * 2)) - maxColorVariation)), 0, 255)
color[2] = math.clamp(math.floor(color[2] + brightnessVariation + ((teamRandoms[teamID][2] * (maxColorVariation * 2)) - maxColorVariation)), 0, 255)
color[3] = math.clamp(math.floor(color[3] + brightnessVariation + ((teamRandoms[teamID][3] * (maxColorVariation * 2)) - maxColorVariation)), 0, 255)
teamColorsTable[teamID] = {
r = color[1],
g = color[2],
b = color[3],
}
else
Spring.Echo("Palette team color")
if not teamSizes[allyTeamID] then
allyTeamNum = allyTeamNum + 1
teamSizes[allyTeamID] = { allyTeamNum, 1, 0 } -- Team number, Starting color number, Color variation
end
if teamColors[allyTeamCount] -- If we have the color set for this number of teams
and teamColors[allyTeamCount][teamSizes[allyTeamID][1]]
then -- And this team number exists in the color set
if not teamColors[allyTeamCount][teamSizes[allyTeamID][1]][teamSizes[allyTeamID][2]] then -- If we have no color for this player anymore
teamSizes[allyTeamID][2] = 1 -- Starting from the first color again..
end
-- Assigning R,G,B values with specified color variations
teamColorsTable[teamID] = {
r = hex2RGB(teamColors[allyTeamCount][teamSizes[allyTeamID][1]][teamSizes[allyTeamID][2]])[1]
+ math.random(-teamSizes[allyTeamID][3], teamSizes[allyTeamID][3]),
g = hex2RGB(teamColors[allyTeamCount][teamSizes[allyTeamID][1]][teamSizes[allyTeamID][2]])[2]
+ math.random(-teamSizes[allyTeamID][3], teamSizes[allyTeamID][3]),
b = hex2RGB(teamColors[allyTeamCount][teamSizes[allyTeamID][1]][teamSizes[allyTeamID][2]])[3]
+ math.random(-teamSizes[allyTeamID][3], teamSizes[allyTeamID][3]),
}
teamSizes[allyTeamID][2] = teamSizes[allyTeamID][2] + 1 -- Will start from the next color next time
else
Spring.Echo("[AUTOCOLORS] Error: Team Colors Table is broken or missing for this allyteam set")
teamColorsTable[teamID] = {
r = 255,
g = 255,
b = 255,
}
end
end
end
local function setupAllTeamColors(localRun)
survivalColorNum = 1 -- Starting from color #1
survivalColorVariation = 0 -- Current color variation
allyTeamNum = 0
teamSizes = {}
dimmingCount = {}
for _, allyTeamID in ipairs(Spring.GetAllyTeamList()) do
dimmingCount[allyTeamID] = 0
end
for i = 1, #teamList do
local teamID = teamList[i]
local allyTeamID = select(6, Spring.GetTeamInfo(teamID))
dimmingCount[allyTeamID] = dimmingCount[allyTeamID] + 1
local isAI = Spring.GetTeamLuaAI(teamID)
setupTeamColor(teamID, allyTeamID, isAI, localRun)
end
end
setupAllTeamColors(false)
trueTeamColorsTable = table.copy(teamColorsTable) -- store the true team colors so we can restore them when we become a spec
if gadgetHandler:IsSyncedCode() then --- NOTE: STUFF DONE IN SYNCED IS FOR REPLAY WEBSITE
local AutoColors = {}
for i = 1, #teamList do
local teamID = teamList[i]
AutoColors[i] = {
teamID = teamID,
r = trueTeamColorsTable[teamID].r,
g = trueTeamColorsTable[teamID].g,
b = trueTeamColorsTable[teamID].b,
}
end
Spring.SendLuaRulesMsg("AutoColors" .. Json.encode(AutoColors))
else -- UNSYNCED
local myPlayerID = Spring.GetLocalPlayerID()
local mySpecState = Spring.GetSpectatingState()
if anonymousMode == "local" then
shuffleAllColors()
end
if anonymousMode == "local" or Spring.GetConfigInt("SimpleTeamColors", 0) == 1 then
setupAllTeamColors(true)
end
local function isDiscoEnabled()
return anonymousMode == "disco" and not mySpecState
end
-- shuffle colors for all teams except ourselves
local function discoShuffle(myTeamID)
-- store own color and do regular shuffle
local myColor = teamColorsTable[myTeamID]
shuffleAllColors()
setupAllTeamColors(true)
-- swap color with any team that might have been assigned own color
local teamIDToSwapWith = nil
for teamID, color in pairs(teamColorsTable) do
if myColor.r == color.r and myColor.g == color.g and myColor.b == color.b then
teamIDToSwapWith = teamID
break
end
end
if teamIDToSwapWith ~= nil then
teamColorsTable[teamIDToSwapWith] = teamColorsTable[myTeamID]
end
-- restore own color
teamColorsTable[myTeamID] = myColor
end
local function updateTeamColors()
if isDiscoEnabled() then
discoShuffle(Spring.GetMyTeamID())
end
for teamID, color in pairs(teamColorsTable) do
Spring.SetTeamColor(teamID, color.r / 255, color.g / 255, color.b / 255)
end
end
updateTeamColors()
local discoTimer = 0
local discoTimerThreshold = 2 * 60 -- shuffle every 2 minutes with disco mode enabled
function gadget:Update()
if isDiscoEnabled() then
discoTimer = discoTimer + Spring.GetLastUpdateSeconds()
if discoTimer > discoTimerThreshold then
discoTimer = 0
updateTeamColors()
end
elseif Spring.GetConfigInt("UpdateTeamColors", 0) == 1 then
setupAllTeamColors(true)
updateTeamColors()
Spring.SetConfigInt("UpdateTeamColors", 0)
Spring.SetConfigInt("SimpleTeamColors_Reset", 0)
end
end
function gadget:PlayerChanged(playerID)
if playerID ~= myPlayerID then
return
end
myAllyTeamID = Spring.GetMyAllyTeamID()
local prevMyTeamID = myTeamID
myTeamID = Spring.GetMyTeamID()
if mySpecState and prevMyTeamID ~= myTeamID and Spring.GetConfigInt("SimpleTeamColors", 0) == 1 then
Spring.SetConfigInt("UpdateTeamColors", 1)
end
if mySpecState ~= Spring.GetSpectatingState() then
mySpecState = Spring.GetSpectatingState()
teamColorsTable = table.copy(trueTeamColorsTable)
ffaColors = table.copy(trueFfaColors)
survivalColors = table.copy(trueSurvivalColors)
Spring.SetConfigInt("UpdateTeamColors", 1)
end
end
end
# pip install matplotlib lupa colormath Pillow
# Lua runtime
from lupa.lua54 import LuaRuntime, LuaSyntaxError, LuaError
# Figure visualization
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
import pandas as pd
from PIL import Image
# Only used for computing the color divergence
from colormath.color_objects import sRGBColor, LabColor
from colormath.color_conversions import convert_color
from colormath.color_diff import delta_e_cie2000
# Fix bug in asscalar of numpy
def patch_asscalar(a):
return a.item()
setattr(np, "asscalar", patch_asscalar)
def get_player_colors(number_teams, number_players):
"""Runs the game_autocolors.lua with the input number of team and number of players and returns the color of each player/team."""
lua = LuaRuntime(unpack_returned_tuples=True)
player_ids = list(range(0, number_players))
team_id_to_players = {}
number_player_per_team = number_players // number_teams
current_team = []
teams = {}
player_to_team_id = {}
for player_id in player_ids:
current_team.append(player_id)
allies_team_id = len(teams)
player_to_team_id[player_id] = allies_team_id
if len(current_team) >= number_player_per_team:
teams[allies_team_id] = current_team
current_team = []
if len(current_team) > 0:
teams[allies_team_id] = current_team
# Gaia is always the last player and its part of its own team
gaia_id = number_players
teams[allies_team_id+1] = [gaia_id]
player_to_team_id[gaia_id] = allies_team_id+1
player_ids.append(gaia_id)
# print(teams)
# print(list(teams.keys()))
# Add input variables to the script
allTeams = "{" + ','.join([str(id) for id in player_ids]) + "}"
alliesTeamsToTeam = str(list(teams.values())).replace(':', '=').replace('[', '{').replace(']', '}')
allAlliesTeamsId = str(list(teams.keys())).replace('[', '{').replace(']', '}')
teamToAliesTeamId = str(list(player_to_team_id.values())).replace('[', '{').replace(']', '}')
input_player_table = f"""
gaia_id = {gaia_id}
allTeams = {allTeams}
alliesTeamsToTeam = {alliesTeamsToTeam}
allAlliesTeamsId = {allAlliesTeamsId}
teamToAliesTeamId = {teamToAliesTeamId}
"""
# print(input_player_table)
# This is the minimum amount of mock to get game_autocolors.lua to run
lua.require('math')
preampule = '''
colorByTeamIdOut = {}
local Gametype = {}
function Gametype:new ()
return {}
end
function Gametype:IsPvE ()
return false
end
function Gametype:IsFFA ()
return false
end
function Gametype:IsTeams ()
return true
end
local Spring = {Utilities = {Gametype = Gametype}}
function Spring:new ()
return {}
end
function Spring.Echo(...)
print("spring>", ...)
end
function Spring:GetModOptions()
return {
teamcolors_icon_dev_mode = 'disabled',
teamcolors_anonymous_mode = 'disabled',
}
end
function Spring:GetGaiaTeamID()
return gaia_id
end
function Spring:GetMyTeamID()
return allTeams[1]
end
function Spring:GetLocalPlayerID()
return allTeams[1]
end
function Spring:GetMyAllyTeamID()
return teamToAliesTeamId[allTeams[1]]
end
-- List of players
function Spring.GetTeamList(teamId)
local teamList = {}
local filterTeamId = -1
if teamId ~= nil then
filterTeamId = teamId
end
for i, teamID in ipairs(allTeams) do
if filterTeamId < 0 or teamToAliesTeamId[i] == filterTeamId then
table.insert(teamList, teamID)
end
end
return teamList
end
-- List of teams
function Spring:GetAllyTeamList()
return allAlliesTeamsId -- {0, 1}
end
function Spring.GetTeamLuaAI(teamID)
return nil
end
function Spring:GetSpectatingState()
return false, false, false
end
function IsPvE()
return false
end
function Spring.GetTeamInfo(teamID, getTeamKeys)
local numberleader = 0
local numberisDead = 0
local numberhasAI = 0
local stringside = "FooBar"
local numberallyTeam = teamToAliesTeamId[teamID+1]
local numberincomeMultiplier = 1.0
local customTeamKeys = {}
return teamID, numberleader, numberisDead, numberhasAI, stringside, numberallyTeam, numberincomeMultiplier, customTeamKeys
end
function Spring:GetConfigInt(field, default)
local options = {
UpdateTeamColors = 0,
SimpleTeamColors_Reset = 0,
SimpleTeamColors = 0,
}
if options[field] ~= nil then
return options[field]
end
return default
end
function Spring.SetTeamColor(teamID, r, g, b)
-- todo
-- print("f", teamID, r, g, b)
color = { r, g, b }
colorByTeamIdOut[teamID] = color
end
local gadgetHandler = {}
function gadgetHandler:IsSyncedCode()
return False
end
local gadget = {}
function math.pow(a, b)
return a ^ b
end
function table:copy(foo)
return foo
end
function math.clamp(low, n, high) return math.min(math.max(n, low), high) end
'''
# Execute game_autocolors.lua
filepath = 'luarules/gadgets/game_autocolors.lua'
# filepath = 'luarules/gadgets/game_autocolors_improved.lua'
with open(filepath) as file:
full_code = input_player_table + preampule + file.read()
try:
lua.execute(full_code)
# When lua fails, print the line which cause the error
except (LuaSyntaxError, LuaError) as err:
err_str = str(err)
if '[string "<python>"]:' in err_str:
line_num = int(err_str.partition('[string "<python>"]:')[2].partition(':')[0])
lines = full_code.split('\n')
for i in range(line_num-5, line_num+5):
if i +1 == line_num:
print(f"{i+1}>\t{lines[i]}")
else:
print(f"{i+1}:\t{lines[i]}")
raise err
# Extract output color of each player
player_to_color = {}
for team_id, rgb_table in dict(lua.globals().colorByTeamIdOut).items():
# print(f"team {team_id} : ", list(dict(rgb_table).values()))
player_to_color[team_id] = (*list(dict(rgb_table).values()),)
team_to_colors = {}
for allies_team_id, players in teams.items():
colors = [player_to_color[id] for id in players]
team_to_colors[allies_team_id] = colors
return team_to_colors
#num_players = 80
# num_player_per_team = 8
# num_team = 5
# Plot some num player / team + number of team permutation as their own figure
for num_player_per_team, num_team in [(10, 3), (8, 8), (30, 2)]:
num_players = num_player_per_team*num_team
team_to_colors = get_player_colors(num_team, num_players)
# Remove the last team, since this is Gaia
team_to_colors = {k:v for k, v in team_to_colors.items() if k != len(team_to_colors)- 1}
# print(team_to_colors)
fig, axs = plt.subplots(nrows=len(team_to_colors))
fig.subplots_adjust(top=0.90, bottom=0.1, left=0.1, right=0.99,
wspace=0.05)
fig.suptitle(f'Autocolor for {num_player_per_team} players / team with {num_team} ally teams ({num_player_per_team * num_team} players in total)', fontsize=12)
for ax, (team_id, player_colors) in zip(axs, team_to_colors.items()):
# Convert the ordered list of colors to 1 x N x 3 array
color_img = np.array([player_colors])
# Draw color map
ax.imshow(color_img, aspect='auto', interpolation="none")
# Add side text
pos = list(ax.get_position().bounds)
x_text = pos[0] - 0.01
y_text = pos[1] + pos[3]/2.
fig.text(x_text, y_text, f"Team {team_id}", va='center', ha='right', fontfamily="Monospace", fontsize=10)
# Turn off *all* ticks & spines, not just the ones with color maps.
for ax in axs.flat:
ax.set_axis_off()
plt.savefig(f"autocolor_{num_player_per_team}pt_{num_team}t.png")
# Compute all color for all team number and number of team permutation
num_team_options = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 20, 24, 30, 50, 60, 120]
num_player_per_team_options = [1, 2, 3, 4, 5, 8, 10, 12, 20, 30, 50, 60]
# num_team_options = [7]
# num_player_per_team_options = [8]
big_color_table = []
for num_team in num_team_options:
grid_row = []
for num_player_per_team in num_player_per_team_options:
num_players = num_player_per_team * num_team
if num_players > 255:
grid_row.append([])
continue
team_to_colors = get_player_colors(num_team, num_players)
grid_row.append(team_to_colors)
big_color_table.append(grid_row)
# Compute LABCIE delta E color difference metric (very slow)
enable_diff_metrics = False
if enable_diff_metrics:
color_diff_table = {"num_player_per_team": [], "num_team": [], "min_same_team_divergence": [], "min_enemy_team_divergence": []}
for y, num_team in enumerate(num_team_options):
for x, num_player_per_team in enumerate(num_player_per_team_options):
num_players = num_player_per_team * num_team
if num_players > 255:
continue
# Skip everything because this has N^4 complexity
# if (not (num_player_per_team == 30 and num_team == 4)) and (not (num_player_per_team == 10 and num_team == 9)):
# continue
print(f"{num_player_per_team=}, {num_team=}")
team_to_colors = big_color_table[y][x]
color_diff_table["num_player_per_team"].append(num_player_per_team)
color_diff_table["num_team"].append(num_team)
# For each color in each team we compute the color distance to every other color in the same team
min_divergence = None
min_color = None
for team_id, player_colors in team_to_colors.items():
for i, color in enumerate(player_colors):
srgb_color = sRGBColor(*color)
lab_color = convert_color(srgb_color, LabColor)
for j in range(i+1, len(player_colors)):
other_color = player_colors[j]
srgb_other_color = sRGBColor(*other_color)
lab_other_color = convert_color(srgb_other_color, LabColor)
delta_e = delta_e_cie2000(lab_color, lab_other_color)
if min_divergence is None or min_divergence > delta_e:
min_divergence = delta_e
min_color = (color, other_color)
print("min_divergence same team", min_divergence, min_color)
color_diff_table["min_same_team_divergence"].append(min_divergence)
min_divergence = None
for team_id, player_colors in team_to_colors.items():
for i, color in enumerate(player_colors):
srgb_color = sRGBColor(*color)
lab_color = convert_color(srgb_color, LabColor)
for other_team_id, other_player_colors in team_to_colors.items():
if other_team_id == team_id:
continue
for i, other_color in enumerate(other_player_colors):
srgb_other_color = sRGBColor(*other_color)
lab_other_color = convert_color(srgb_other_color, LabColor)
delta_e = delta_e_cie2000(lab_color, lab_other_color)
if min_divergence is None or min_divergence > delta_e:
min_divergence = delta_e
min_color = (color, other_color)
print("min_divergence different team", min_divergence, min_color)
color_diff_table["min_enemy_team_divergence"].append(min_divergence)
df = pd.DataFrame(color_diff_table)
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 150)
print("Same team divergence:")
print(df.pivot_table(index="num_team", columns="num_player_per_team", values='min_same_team_divergence'))
print("Enemy team divergence:")
print(df.pivot_table(index="num_team", columns="num_player_per_team", values='min_enemy_team_divergence'))
# Plot the huge figure of all possible team and number of player team variation
plt.style.use('classic')
fig = plt.figure(figsize=(4*len(num_player_per_team_options), 10*len(num_team_options)))
outer = gridspec.GridSpec(nrows=len(num_team_options), ncols=len(num_player_per_team_options), figure=fig)
# fig, axes = plt.subplots(nrows=len(num_team_options), ncols=len(num_player_per_team_options), figsize=(100, 100))
for y, num_team in enumerate(num_team_options):
for x, num_player_per_team in enumerate(num_player_per_team_options):
num_players = num_player_per_team * num_team
if num_players > 255:
continue
raw_team_to_colors = big_color_table[y][x]
# team_to_colors = get_player_colors(num_team, num_players)
# Remove the last team, since this is Gaia
team_to_colors = {k:v for k, v in raw_team_to_colors.items() if k != len(raw_team_to_colors)- 1}
print(f"{num_team=} {num_player_per_team=}")
inner = gridspec.GridSpecFromSubplotSpec(nrows=len(team_to_colors), ncols=1,
subplot_spec=outer[y, x], wspace=0.1, hspace=0.1)
for j, (team_id, player_colors) in enumerate(team_to_colors.items()):
ax = fig.add_subplot(inner[j])
# ax = plt.Subplot(fig, inner[j])
# First team colorbar
if j == 0:
# First row of the figure
if y == 0:
ax.text(0.7*x, -0.55, f"{num_player_per_team}", fontsize=50, horizontalalignment='center', verticalalignment='bottom')
ax.set_title(f"{num_player_per_team} players / team with {num_team} ally teams ({num_player_per_team * num_team} players in total)", fontsize=6, color="gray")
# Middle colorbar of the first column of figures
if x == 0 and j == len(team_to_colors) // 2:
ax.text(-0.8, 0.0, f"{num_team}", fontsize=50, horizontalalignment='right', verticalalignment='center')
# Convert the ordered list of player colors to 1 x N x 3 array
color_img = np.array([player_colors])
# Draw color map
ax.imshow(color_img, aspect='auto', interpolation="none")
# ax.set_axis_off()
ax.spines[['left', 'right','bottom', 'top']].set_visible(False)
ax.get_xaxis().set_ticks([])
ax.get_yaxis().set_ticks([])
if len(team_to_colors) >= 20:
if len(team_to_colors) < 100 or team_id % 4 == 0:
ax.set_ylabel(f"{team_id}", fontfamily="Monospace", fontsize=5 if len(team_to_colors) > 24 else 8)
else:
fontsize = 10
if num_team >= 10:
fontsize = 8
ax.set_ylabel(f"Team {team_id}", fontfamily="Monospace", fontsize=fontsize)
fig.supxlabel("Number of Player in each team", y=0.91, fontsize=100, va="bottom")
fig.supylabel("Number of Team", fontsize=100)
img_path = "huge_team_img.png"
plt.savefig(img_path)
# Create thumbnail
with Image.open(img_path) as im:
im.thumbnail((512,-1), Image.Resampling.LANCZOS)
# Save the new thumbnail image
im.save(img_path.replace(".png", "_thumb.png"), "PNG")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment