1
0
forked from 0ad/0ad

Update of the template analyzer tool with more parameters and probably a more accurate representation of civs (with the right settings).

This was SVN commit r15522.
This commit is contained in:
wraitii 2014-07-13 10:20:35 +00:00
parent 83d37f7857
commit 0cf5f57a68
2 changed files with 140 additions and 95 deletions

View File

@ -51,9 +51,10 @@ h3
text-align: center;
}
.ComparisonTable .Separator
.Separator
{
min-width:50px;
border: none;
}
.ComparisonTable th
@ -106,10 +107,21 @@ h3
{
border-bottom: 1px #DDD solid;
height:28px;
text-align: right;
}
.AverageComp th
{
font-weight: normal
}
.AverageComp td
{
text-align: center;
min-width: 150px;
}
.AverageComp .UnitTd
{
text-align: center;
width: 150px;
}
border: 1px #DDD solid;
padding: 0px 5px 0px 5px;
}

View File

@ -21,7 +21,8 @@ CivBuildings = ["civil_centre", "barracks","gymnasion", "stables", "elephant_sta
#CivBuildings = ["civil_centre", "barracks"]
# Remote Civ templates with those strings in their name.
FilterOut = ["hero", "mecha", "support"]
FilterOut = ["hero", "mecha", "support", "elephant"]
# Graphic parameters: affects only how the data is shown
ComparativeSortByCav = True
@ -30,13 +31,16 @@ SortTypes = ["Support","Pike","Spear","Sword", "Archer","Javelin","Sling","Eleph
ShowCivLists = False
# Parameters: affects the data
paramFactorSpeed = True;
paramIncludePopCost = False
paramFactorCounters = True; # False means attack bonuses are not counted.
paramDPSImp = 5
paramHPImp = 3 # Decrease to make more important
paramRessImp = { "food" : 1.0, "wood" : 1.0, "stone" : 2.0, "metal" : 1.5 }
paramBuildTimeImp = 0.5 # Sane values are 0-1, though more is OK.
paramSpeedImp = 0.3 # Sane values are 0-1, anything else will be weird.
paramRangedCoverage = 1.0 # Sane values are 0-1, though more is OK.
paramRangedMode = "Average" #Anything but "Average" is "Max"
def htbout(file, balise, value):
file.write("<" + balise + ">" + value + "</" + balise + ">\n" )
def htout(file, value):
@ -168,6 +172,8 @@ def WriteUnit(Name, UnitDict):
rstr += "<td>HP: " + UnitDict["HP"] + "</td>\n"
rstr += "<td>BuildTime: " + UnitDict["BuildTime"] + "</td>\n"
rstr += "<td>Speed: " + UnitDict["WalkSpeed"] + "</td>\n"
rstr += "<td>Costs: " + UnitDict["Cost"]["food"] + "F / " + UnitDict["Cost"]["wood"] + "W / " + UnitDict["Cost"]["stone"] + "S / " + UnitDict["Cost"]["metal"] + "M</td>\n"
@ -229,10 +235,12 @@ def CalcValue(UnitDict):
Worth["Cost"] = TotalCost/400.0;
Worth["Cost"] *= int(UnitDict['BuildTime'])/12.0;
Worth["Cost"] = Worth["Cost"] * int(UnitDict['BuildTime'])/12.0 * paramBuildTimeImp + (1.0-paramBuildTimeImp) * Worth["Cost"];
# Speed makes you way less evasive, and you can chase less.
Worth["Cost"] /= float(UnitDict["WalkSpeed"])/10.0;
# It's not really a cost so just decrease Attack and Defence
Worth["Attack"] = Worth["Attack"] * float(UnitDict["WalkSpeed"])/10.0 * paramSpeedImp + (1.0-paramSpeedImp) * Worth["Attack"];
Worth["Defence"] = Worth["Defence"] * float(UnitDict["WalkSpeed"])/10.0 * paramSpeedImp + (1.0-paramSpeedImp) * Worth["Defence"];
Worth["Total"] = (Worth["Attack"] + Worth["Defence"])/Worth["Cost"]
@ -305,10 +313,8 @@ def Compare2(UnitDictA,UnitDictB):
AWorth += int(UnitDictA['HP']) / (BDPS+1);
BWorth += int(UnitDictB['HP']) / (ADPS+1);
if (paramFactorSpeed):
SpeedRatio = float(UnitDictA["WalkSpeed"]) / float(UnitDictB["WalkSpeed"])
else:
SpeedRatio = 1.0
SpeedRatio = 1.0-paramSpeedImp + paramSpeedImp * float(UnitDictA["WalkSpeed"]) / float(UnitDictB["WalkSpeed"])
return AWorth / BWorth * SpeedRatio
@ -391,6 +397,71 @@ def WriteWorthList(Units):
f.write("</table>")
def CivUnitComparisons(CivA, CivB):
averageEffNoCost = 0
MaxMinEffNoCost = 0
MinMaxEffNoCost = 999999
averageEff = 0
MaxMinEff = 0
MinMaxEff = 999999
MaxMinEffUnit = ""
MinMaxEffUnit = ""
for theirUnit in CivB["Units"]:
unitAverageNoCost = 0
unitMaxNoCost = 0
unitAverage = 0
unitMax = 0
for myUnit in CivA["Units"]:
eff = Compare2(CivA["Units"][myUnit],CivB["Units"][theirUnit])
eff = (eff * CivA["RangedUnitsWorth"] / CivB["RangedUnitsWorth"] * paramRangedCoverage) + eff * (1.0-paramRangedCoverage)
unitAverageNoCost += eff
if (eff > unitMaxNoCost): unitMaxNoCost = eff
eff /= CalcValue(CivA["Units"][myUnit])["Cost"] / CalcValue(CivB["Units"][theirUnit])["Cost"]
unitAverage += eff
if (eff > unitMax): unitMax = eff
unitAverageNoCost /= len(CivB["Units"])
unitAverage /= len(CivB["Units"])
averageEffNoCost += unitAverageNoCost
averageEff += unitAverage
if (unitMax < MinMaxEff):
MinMaxEff = unitMax
MinMaxEffUnit = theirUnit
if (unitMaxNoCost < MinMaxEffNoCost): MinMaxEffNoCost = unitMaxNoCost
for myUnit in CivA["Units"]:
unitMinNoCost = 9999999
unitMin = 9999999
for theirUnit in CivB["Units"]:
eff = Compare2(CivA["Units"][myUnit],CivB["Units"][theirUnit])
eff = (eff * CivA["RangedUnitsWorth"] / CivB["RangedUnitsWorth"] * paramRangedCoverage) + eff * (1.0-paramRangedCoverage)
if (eff < unitMinNoCost): unitMinNoCost = eff
eff /= CalcValue(CivA["Units"][myUnit])["Cost"] / CalcValue(CivB["Units"][theirUnit])["Cost"]
if (eff < unitMin): unitMin = eff
if (unitMin > MaxMinEff):
MaxMinEff = unitMin
MaxMinEffUnit = myUnit
if (unitMinNoCost > MaxMinEffNoCost): MaxMinEffNoCost = unitMinNoCost
f.write("<tr><th>" + oCiv + "</th>")
averageEffNoCost /= len(CivA["Units"])
averageEff /= len(CivA["Units"])
f.write("<td style=\"background-color:rgba(" + str(int(255 - (averageEffNoCost-1) * 90)) + "," + str(int((averageEffNoCost-1) * 90)) + ",0,0.7);\">" + str("%.2f" % averageEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MaxMinEffNoCost-0.3) * 360)) + "," + str(int((MaxMinEffNoCost-0.3) * 360))+ ",0,0.7);\">" + str("%.2f" % MaxMinEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MinMaxEffNoCost-1) * 130)) + "," + str(int((MinMaxEffNoCost-1) * 130)) + ",0,0.7);\">" + str("%.2f" % MinMaxEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (averageEff-0.5) * 110)) + "," + str(int((averageEff-0.5) * 110)) + ",0,0.7);\">" + str("%.2f" % averageEff) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MaxMinEff-0.3) * 360)) + "," + str(int((MaxMinEff-0.3) * 360)) + ",0,0.7);\">" + str("%.2f" % MaxMinEff) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MinMaxEff-1) * 130)) + "," + str(int((MinMaxEff-1) * 130)) + ",0,0.7);\">" + str("%.2f" % MinMaxEff) + "</td>")
if MaxMinEff <= MinMaxEff and abs(MaxMinEff * MinMaxEff - 1.0) < 0.1:
f.write("<td style=\"background-color:rgb(0,255,0);\">Surely</td>")
elif MaxMinEff <= MinMaxEff and abs(MaxMinEff * MinMaxEff - 1.0) < 0.2:
f.write("<td style=\"background-color:rgb(255,160,0);\">Probably</td>")
elif MaxMinEff > MinMaxEff or MaxMinEff * MinMaxEff > 1.0:
f.write("<td style=\"background-color:rgb(255,0,0);\">No, too strong</td>")
else:
f.write("<td style=\"background-color:rgb(255,0,0);\">No, too weak</td>")
f.write("<td class=\"Separator\">" + str(int(CivA["RangedUnitsWorth"] / CivB["RangedUnitsWorth"]*100)) + "%</td><td class=\"UnitTd\">" + MaxMinEffUnit + "</td><td class=\"UnitTd\">" + MinMaxEffUnit + "</td></tr>")
basePath = os.path.realpath(__file__).replace("unitTables.py","") + "../../../binaries/data/mods/public/simulation/templates/"
@ -405,6 +476,7 @@ os.chdir(basePath)
#Load values up.
templates = {}; # Values
############################################################
#Whilst loading I also write them.
htbout(f,"h2", "Units")
f.write("<table class=\"UnitList\">")
@ -434,124 +506,85 @@ f.write("<p class=\"desc\">Costs are estimated as the sum of Food*" + str(paramR
f.write("Population cost is " + ("counted" if paramIncludePopCost else "not counted") + " in the costs.<br/>")
WriteWorthList(templates)
#Load Civ specific templates
############################################################
# Load Civ specific templates
# Will be loaded by loading buildings, and templates will be set the Civ building, the Civ and in general.
# Allows to avoid special units.
CivTemplatesByBuilding = {};
CivTemplatesByCiv = {};
CivData = {};
CivTemplates = {};
for Civ in Civs:
if ShowCivLists:
htbout(f,"h2", Civ)
f.write("<table class=\"UnitList\">")
CivTemplatesByCiv[Civ] = {}
CivTemplatesByBuilding[Civ] = {}
CivData[Civ] = { "Units" : {}, "Buildings" : {}, "RangedUnits" : {} }
for building in CivBuildings:
CivTemplatesByBuilding[Civ][building] = {}
CivData[Civ]["Buildings"][building] = {}
bdTemplate = "./structures/" + Civ + "_" + building + ".xml"
TrainableUnits = []
if (os.path.isfile(bdTemplate)):
Template = ET.parse(bdTemplate)
if (Template.find("./ProductionQueue/Entities") != None):
TrainableUnits = Template.find("./ProductionQueue/Entities").text.replace(" ","").replace("\t","").split("\n")
# We have the templates this building can train.
for UnitFile in TrainableUnits:
breakIt = False
for filter in FilterOut:
if UnitFile.find(filter) != -1: breakIt = True
if breakIt: continue
if (os.path.isfile(UnitFile + ".xml")):
templates[UnitFile + ".xml"] = CalcUnit(UnitFile + ".xml")
CivTemplatesByBuilding[Civ][building][UnitFile + ".xml"] = templates[UnitFile + ".xml"]
if UnitFile + ".xml" not in CivTemplatesByCiv[Civ]:
templates[UnitFile + ".xml"] = CalcUnit(UnitFile + ".xml") # Parse
CivData[Civ]["Buildings"][building][UnitFile + ".xml"] = templates[UnitFile + ".xml"]
if UnitFile + ".xml" not in CivData[Civ]["Units"]:
CivTemplates[UnitFile + ".xml"] = templates[UnitFile + ".xml"]
CivTemplatesByCiv[Civ][UnitFile + ".xml"] = templates[UnitFile + ".xml"]
CivData[Civ]["Units"][UnitFile + ".xml"] = templates[UnitFile + ".xml"]
if ShowCivLists:
f.write(WriteUnit(UnitFile + ".xml", templates[UnitFile + ".xml"]))
if "Ranged" in templates[UnitFile + ".xml"]["Classes"]:
CivData[Civ]["RangedUnits"][UnitFile + ".xml"] = templates[UnitFile + ".xml"]
f.write("</table>")
############################################################
# Writing Civ Specific Comparisons.
f.write("<h2>Civ Unit Comparisons</h2>\n")
f.write("<h2>Civilisation Comparisons</h2>\n")
f.write("<p class=\"desc\">The following graphs attempt to do some analysis of civilizations against each other. ")
f.write("They use the data in the comparison tables below. The averages are not extremely useful but can give some indications.<br/>")
f.write("You can deduce some things based on other columns though.<br/><br/>In particular \"Minimal Maximal Efficiency\" is low if there is a unit this civ cannot counter, and \"Maximal Minimal Efficiency\" is high if there is a unit that cannot be countered. If the (minimal) maximal efficiency and the average maximal efficiency are close, this means there are several units for that civ that counter the enemy civ's unit.<br/>The Balance column analyses those two to give some verdict (consider the average effectiveness too though)<br/><br/>Note that \"Counter\" in this data means \"Any unit that is strong against another unit\", so a unit that's simply way stronger than another counts as \"countering it\".<br/>If a civ's average efficiency and average efficiency disregarding costs are very different, this can mean that its costly units are particularly strong (Mauryans fit the bill with elephants).</p>")
f.write("Different kinds of data: A matrix of units efficiency in a 1v1 fight, a graph of unit worthiness (power/cost), and a statistical analysis of civilizations against each other:<br/>")
f.write("In this last one in particular, \"Minimal Maximal Efficiency\" is low if there is a unit this civ cannot counter, and \"Maximal Minimal Efficiency\" is high if there is a unit that cannot be countered. If the (minimal) maximal efficiency and the average maximal efficiency are close, this means there are several units for that civ that counter the enemy civ's unit.<br/>The Balance column analyses those two to give some verdict (consider the average effectiveness too though)<br/><br/>Note that \"Counter\" in this data means \"Any unit that is strong against another unit\", so a unit that's simply way stronger than another counts as \"countering it\".<br/>If a civ's average efficiency and average efficiency disregarding costs are very different, this can mean that its costly units are particularly strong (Mauryans fit the bill with elephants).</p>")
for Civ in Civs:
CivData[Civ]["RangedUnitsWorth"] = 0
for unit in CivData[Civ]["RangedUnits"]:
value = CalcValue(CivData[Civ]["RangedUnits"][unit])["Total"]
if paramRangedMode == "Average": CivData[Civ]["RangedUnitsWorth"] += value
elif value > CivData[Civ]["RangedUnitsWorth"]: CivData[Civ]["RangedUnitsWorth"] = value
if (paramRangedMode == "Average" and len(CivData[Civ]["RangedUnits"]) > 0):
CivData[Civ]["RangedUnitsWorth"] /= len(CivData[Civ]["RangedUnits"])
for Civ in Civs:
f.write("<h3>" + Civ + "</h3>")
f.write("<table class=\"AverageComp\">")
f.write("<tr><td></td><td>Average Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Maximal Minimal Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Minimal Maximal Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Average Efficiency</td><td>Maximal Minimal Efficiency</td><td>Minimal Maximal Efficiency</td><td>Balance</td></tr>")
for oCiv in Civs:
averageEffNoCost = 0
MaxMinEffNoCost = 0
MinMaxEffNoCost = 999999
averageEff = 0
MaxMinEff = 0
MinMaxEff = 999999
if (oCiv == Civ):
continue
for theirUnit in CivTemplatesByCiv[oCiv]:
unitAverageNoCost = 0
unitMaxNoCost = 0
unitAverage = 0
unitMax = 0
for myUnit in CivTemplatesByCiv[Civ]:
eff = Compare2(CivTemplatesByCiv[Civ][myUnit],CivTemplatesByCiv[oCiv][theirUnit])
unitAverageNoCost += eff
if (eff > unitMaxNoCost): unitMaxNoCost = eff
eff /= CalcValue(CivTemplatesByCiv[Civ][myUnit])["Cost"] / CalcValue(CivTemplatesByCiv[oCiv][theirUnit])["Cost"]
unitAverage += eff
if (eff > unitMax): unitMax = eff
unitAverageNoCost /= len(CivTemplatesByCiv[oCiv])
unitAverage /= len(CivTemplatesByCiv[oCiv])
averageEffNoCost += unitAverageNoCost
averageEff += unitAverage
if (unitMax < MinMaxEff): MinMaxEff = unitMax
if (unitMaxNoCost < MinMaxEffNoCost): MinMaxEffNoCost = unitMaxNoCost
for myUnit in CivTemplatesByCiv[Civ]:
unitMinNoCost = 9999999
unitMin = 9999999
for theirUnit in CivTemplatesByCiv[oCiv]:
eff = Compare2(CivTemplatesByCiv[Civ][myUnit],CivTemplatesByCiv[oCiv][theirUnit])
if (eff < unitMinNoCost): unitMinNoCost = eff
eff /= CalcValue(CivTemplatesByCiv[Civ][myUnit])["Cost"] / CalcValue(CivTemplatesByCiv[oCiv][theirUnit])["Cost"]
if (eff < unitMin): unitMin = eff
if (unitMin > MaxMinEff): MaxMinEff = unitMin
if (unitMinNoCost > MaxMinEffNoCost): MaxMinEffNoCost = unitMinNoCost
f.write("<tr><td>" + oCiv + "</td>")
averageEffNoCost /= len(CivTemplatesByCiv[Civ])
averageEff /= len(CivTemplatesByCiv[Civ])
f.write("<td style=\"background-color:rgba(" + str(int(255 - (averageEffNoCost-1) * 90)) + "," + str(int((averageEffNoCost-1) * 90)) + ",0,0.7);\">" + str("%.2f" % averageEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MaxMinEffNoCost-0.3) * 360)) + "," + str(int((MaxMinEffNoCost-0.3) * 360))+ ",0,0.7);\">" + str("%.2f" % MaxMinEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MinMaxEffNoCost-1) * 130)) + "," + str(int((MinMaxEffNoCost-1) * 130)) + ",0,0.7);\">" + str("%.2f" % MinMaxEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (averageEff-0.5) * 110)) + "," + str(int((averageEff-0.5) * 110)) + ",0,0.7);\">" + str("%.2f" % averageEff) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MaxMinEff-0.3) * 360)) + "," + str(int((MaxMinEff-0.3) * 360)) + ",0,0.7);\">" + str("%.2f" % MaxMinEff) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MinMaxEff-1) * 130)) + "," + str(int((MinMaxEff-1) * 130)) + ",0,0.7);\">" + str("%.2f" % MinMaxEff) + "</td>")
if MaxMinEff <= MinMaxEff and abs(MaxMinEff * MinMaxEff - 1.0) < 0.1:
f.write("<td style=\"background-color:rgb(0,255,0);\">Surely</td></tr>")
elif MaxMinEff <= MinMaxEff and abs(MaxMinEff * MinMaxEff - 1.0) < 0.2:
f.write("<td style=\"background-color:rgb(255,160,0);\">Probably</td></tr>")
elif MaxMinEff >= MinMaxEff or MaxMinEff * MinMaxEff > 1.0:
f.write("<td style=\"background-color:rgb(255,0,0);\">No, too strong</td></tr>")
else:
f.write("<td style=\"background-color:rgb(255,0,0);\">No, too weak</td></tr>")
f.write("</table>")
f.write("<h2>Civ Comparison Tables</h2>\n")
for Civ in Civs:
f.write("<h3>" + Civ + "</h3>")
z = {}
for oCiv in Civs:
if (oCiv != Civ):
z.update(CivTemplatesByCiv[oCiv])
WriteComparisonTable(CivTemplatesByCiv[Civ],z)
z.update(CivData[oCiv]["Units"])
WriteComparisonTable(CivData[Civ]["Units"],z)
f.write("<h2>Civ Units Worthiness</h2>\n")
for Civ in Civs:
f.write("<h3>" + Civ + "</h3>")
WriteWorthList(CivTemplatesByCiv[Civ])
WriteWorthList(CivData[Civ]["Units"])
f.write("<p>This Civilization has " + str(len(CivData[Civ]["RangedUnits"])) + " ranged units for a worth of " + str(CivData[Civ]["RangedUnitsWorth"]) + " (method " + ("Average" if paramRangedMode == "Average" else "Max") + "), individually:<br/>")
for unit in CivData[Civ]["RangedUnits"]:
value = CalcValue(CivData[Civ]["RangedUnits"][unit])["Total"]
f.write(unit + " : " + str(value) + "<br/>")
f.write("<p class=\"desc\"><br/>The following table uses the value in the comparison matrix above, factoring in costs (on the 3 columns on the right), and the average efficiency of its ranged units (per ParamRangedCoverage - " + str(paramRangedCoverage) + " right now).</p>")
f.write("<table class=\"AverageComp\">")
f.write("<tr><td></td><td>Average Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Maximal Minimal Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Minimal Maximal Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Average Efficiency</td><td>Maximal Minimal Efficiency</td><td>Minimal Maximal Efficiency</td><td>Balance</td><td>Ranged Efficiency</td><td>Countered the least</td><td>Counters the least</td></tr>")
for oCiv in Civs:
if (oCiv == Civ):
continue
CivUnitComparisons(CivData[Civ],CivData[oCiv])
f.write("</table>")
f.write("</body>\n</html>")