using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Luneraria.Core;
///
/// H1 balance runs. Every hero gets the same probe bot (shares of "ausgewogen", same items,
/// first talent choices, recruited in round 3) on slot 0 or slot 3; the other five slots are
/// fixed reference profiles with Ringborke's probe on the opposite seat. Reports the win rate
/// of the probe seat per hero over many seeds and both sides, plus how often Sylvara leaps and
/// releases her ultimate. Usage: dotnet run -c Release --project tools/dotnet/Luneraria.Balance [seeds]
///
public static class Program
{
static readonly string[] Heroes = { "held_ringborke", "held_quellgeflecht", "held_funkenlanze", "held_sylvara" };
public static int Main(string[] args)
{
if (args.Length > 0 && args[0] == "s1")
return RulesComparison(args.Length > 1 ? int.Parse(args[1], CultureInfo.InvariantCulture) : 60);
int seeds = args.Length > 0 ? int.Parse(args[0], CultureInfo.InvariantCulture) : 40;
BotProfile reference = ContentData.BotProfiles["ausgewogen"];
foreach (string hero in Heroes)
{
ContentData.ExtraBotProfiles["probe_" + hero] = new BotProfile
{
Name = "probe_" + hero, Front = reference.Front, Melee = reference.Melee, Ranged = reference.Ranged, Splash = reference.Splash,
Support = reference.Support, UpgradeFrom = reference.UpgradeFrom, SupportLane = 0.0, Hero = hero, HeroRound = 3,
Items = new[] { "glutklinge", "basaltschild", "funkenfokus" }, TalentChoices = new[] { 0, 0 }, Champion = "",
};
}
Console.WriteLine("hero; seat wins; losses; draws; open; win rate; mean rounds; leaps/combat; ultimates/combat");
foreach (string hero in Heroes)
{
int wins = 0, losses = 0, draws = 0, open = 0, rounds = 0, combats = 0, leaps = 0, ults = 0;
for (int seed = 1; seed <= seeds; seed++)
{
for (int side = 0; side < 2; side++)
{
var profiles = new List { "probe_held_ringborke", "druck", "fernkampf", "probe_held_ringborke", "druck", "fernkampf" };
profiles[side * 3] = "probe_" + hero;
var match = new LuneMatch();
match.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Profiles = profiles });
while (match.Phase != Phase.Over && match.RoundNumber <= 80)
{
for (int slot = 0; slot < 6; slot++)
LuneBot.Plan(match, slot);
match.BeginCombat();
match.FinishCombat();
combats++;
foreach (LuneUnit unit in match.Combat.Units)
{
if (unit.Kind == "hero" && unit.OwnerSlot == side * 3)
{
leaps += unit.LeapSerial;
ults += unit.UltimateSerial;
}
}
}
rounds += match.RoundNumber;
if (match.Phase != Phase.Over) open++;
else if (match.Winner == side) wins++;
else if (match.Winner == LuneMatch.Draw) draws++;
else losses++;
}
}
int games = seeds * 2;
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}; {1}; {2}; {3}; {4}; {5:P0}; {6:F1}; {7:F2}; {8:F2}",
hero, wins, losses, draws, open, (double)wins / Math.Max(1, wins + losses + draws), (double)rounds / games, (double)leaps / combats, (double)ults / combats));
}
return 0;
}
///
/// S1: plain bot matches (seed rotation of profiles) under reference and extended rules:
/// match length, winners, how often each extended mechanic acts, and how often the team that
/// first took a tower also won (snowball).
///
static int RulesComparison(int seeds)
{
Console.WriteLine("rules; matches; blue wins; red wins; draws; open; mean rounds; min; max; first tower wins; champion income/seat; barracks waves; exhausted heroes; buybacks; blessings; sylvara seats; sylvara wins; glyphs fired; Ancient levels; recipe items");
foreach (bool extended in new[] { false, true })
{
int blue = 0, red = 0, draws = 0, open = 0, rounds = 0, min = 999, max = 0, firstTowerWins = 0, firstTowerGames = 0;
long income = 0; int barracks = 0, exhausted = 0, buybacks = 0, blessings = 0, sylvaraSeats = 0, sylvaraWins = 0, glyphs = 0, cores = 0, recipes = 0;
for (int seed = 1; seed <= seeds; seed++)
{
var match = new LuneMatch();
match.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = extended });
int firstTower = -1;
while (match.Phase != Phase.Over && match.RoundNumber <= 80)
{
for (int slot = 0; slot < 6; slot++)
{
bool wasExhausted = match.Players[slot].Hero != null && match.Players[slot].Hero.Exhausted;
LuneBot.Plan(match, slot);
if (wasExhausted && match.Players[slot].Draft.Hero != null && !match.Players[slot].Draft.Hero.Exhausted)
buybacks++;
}
for (int team = 0; team < 2; team++)
foreach (string lane in LuneMap.Lanes)
if (extended && match.BarracksBroken(team, lane)) barracks++;
match.BeginCombat();
match.FinishCombat();
glyphs += (match.Combat.GlyphUsed[0] ? 1 : 0) + (match.Combat.GlyphUsed[1] ? 1 : 0);
foreach (Player player in match.Players)
if (player.Hero != null && player.Hero.Exhausted) exhausted++;
if (firstTower < 0 && match.LastSettlement != null && match.LastSettlement.BuildingsLost.Count > 0)
firstTower = match.LastSettlement.BuildingsLost[0].StartsWith("rot") ? 0 : 1;
}
int length = match.RoundNumber;
rounds += length; min = Math.Min(min, length); max = Math.Max(max, length);
if (match.Phase != Phase.Over) open++;
else if (match.Winner == 0) blue++;
else if (match.Winner == 1) red++;
else draws++;
if (firstTower >= 0 && match.Phase == Phase.Over) { firstTowerGames++; if (match.Winner == firstTower) firstTowerWins++; }
foreach (Player player in match.Players)
{
income += player.IncomeBonus;
if (player.Hero != null && player.Hero.HeroId == "held_sylvara") { sylvaraSeats++; if (match.Winner == player.Team) sylvaraWins++; }
}
blessings += match.Boss.Blessing[0] + match.Boss.Blessing[1];
cores += match.Boss.CoreLevel[0] + match.Boss.CoreLevel[1];
foreach (Player player in match.Players)
if (player.Hero != null) recipes += player.Hero.Items.Count(id => ContentData.Items[id].IsRecipe);
}
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0}; {1}; {2}; {3}; {4}; {5}; {6:F1}; {7}; {8}; {9}/{10}; {11:F1}; {12}; {13}; {14}; {15}; {16}; {17}; {18}; {19}; {20}",
extended ? "erweitert" : "referenz", seeds, blue, red, draws, open, (double)rounds / seeds, min, max, firstTowerWins, firstTowerGames,
(double)income / (seeds * 6), barracks, exhausted, buybacks, blessings, sylvaraSeats, sylvaraWins, glyphs, cores, recipes));
}
return 0;
}
}