using System.Collections.Generic; using System.Linq; namespace Luneraria.Core.Tests { /// /// S3: bots that recruit their heroes early and a rotation over all profiles, neutral camps /// (League of Legends buff camps), wards and the fog of war (Dota 2) under the extended /// rules; none of it under the reference rules. /// public sealed class S3RulesSuite : CheckSuite { public override string Name { get { return "s3"; } } protected override void RunChecks() { TestReference(); TestBots(); TestCamps(); TestWards(); TestFog(); } static LuneMatch NewMatch(bool extended, int seed = 61) { var state = new LuneMatch(); state.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = extended, Bunds = new List { "glutpakt", "wurzelbund", "glutpakt", "wurzelbund", "glutpakt", "wurzelbund" } }); return state; } void TestReference() { LuneMatch state = NewMatch(false); state.RoundNumber = 3; state.Players[0].Draft.Gold += 500; Check(!state.Submit(0, Command.Ward("mid")).Ok, "reference: no wards"); state.BeginCombat(); Check(state.Combat.Units.All(u => u.Kind != "camp"), "reference: no camps in round 3"); Check(LuneFog.Hidden(state, state.Combat, 0, false).Count == 0, "reference: no fog"); } void TestBots() { var profiles = new HashSet(); LuneMatch probe = NewMatch(true, 1); for (int slot = 0; slot < 6; slot++) profiles.Add(LuneBot.ProfileFor(probe, slot)); Check(profiles.Count == 6, "extended rotation gives six different profiles (" + string.Join(",", profiles) + ")"); int heroes = 0; int seats = 0; for (int seed = 1; seed <= 6; seed++) { LuneMatch match = new LuneMatch(); match.Setup(new MatchOptions { Seed = seed, HumanSlot = -1, Extended = true }); while (match.Phase != Phase.Over && match.RoundNumber <= 6) { for (int slot = 0; slot < 6; slot++) LuneBot.Plan(match, slot); match.BeginCombat(); match.FinishCombat(); } heroes += match.Players.Count(p => p.Hero != null); seats += 6; } Check(heroes * 2 >= seats, "extended bots have most heroes after round 6 (" + heroes + " of " + seats + ")"); LuneMatch reference = new LuneMatch(); reference.Setup(new MatchOptions { Seed = 1, HumanSlot = -1 }); for (int round = 0; round < 6; round++) { for (int slot = 0; slot < 6; slot++) LuneBot.Plan(reference, slot); reference.BeginCombat(); reference.FinishCombat(); } Check(reference.Players.Count(p => p.Hero != null) == 0, "reference bots keep their late heroes (parity)"); LuneMatch bounty = NewMatch(true); Check(bounty.ChampionBountyFor(100) == 50 && NewMatch(false).ChampionBountyFor(100) == 100, "extended: defenders get half a champion's bounty"); } void TestCamps() { LuneMatch state = NewMatch(true); Check(!state.CampRound(1) && !state.CampRound(2) && state.CampRound(3) && state.CampRound(6), "camps every third round"); state.BeginCombat(); Check(state.Combat.Units.All(u => u.Kind != "camp"), "no camp in round 1"); state.FinishCombat(); state.RoundNumber = 3; state.Players[0].Draft = state.Snapshot(state.Players[0]); state.BeginCombat(); List camps = state.Combat.Units.Where(u => u.Kind == "camp").ToList(); Check(camps.Count == 3 && camps.All(c => c.Team == LuneCombat.Neutral && c.Zone == "lane"), "three neutral guardians, one per lane"); LuneUnit top = camps.First(c => c.Lane == "top"); Vec2 start = top.Position; for (int i = 0; i < 40; i++) state.Combat.Step(); Check(top.Position.DistanceTo(start) < 0.000001, "a guardian never moves"); // Blau's units stand next to the top guardian when it falls. foreach (LuneUnit unit in state.Combat.Units.Where(u => u.Team == 0 && u.IsMobile()).Take(4)) unit.Position = top.Position + new Vec2(1.0, 0.0); top.Hp = 0.0; LuneUnit bot = camps.First(c => c.Lane == "bot"); foreach (LuneUnit unit in state.Combat.Units.Where(u => u.Team == 1 && u.IsMobile()).Take(4)) unit.Position = bot.Position + new Vec2(1.0, 0.0); bot.Hp = 0.0; state.Combat.Step(); CombatEvent topEvent = state.Combat.Events.First(e => e.Kind == "camp" && e.Lane == "top"); Check(topEvent.ClaimTeam == 0, "the team with more units near the guardian takes the camp"); int[] before = state.Players.Select(p => p.Gold).ToArray(); state.FinishCombat(); Check(state.Boss.CampBuff[0] == 2 && state.Boss.CampBuff[3] == 0, "Glutsegen for Blau for two combats"); int redGain = state.Players[4].Gold - before[4] - state.LastSettlement.Gains[4] + 50; Check(state.LastSettlement.Gains[3] >= 50 && state.LastSettlement.Gains[4] >= 50 && state.LastSettlement.Gains[5] >= 50, "Beute: Rot's players get 50 gold each"); state.Players[0].Draft.Gold += 500; state.Submit(0, Command.Buy("g01", "top")); state.BeginCombat(); LuneUnit troop = state.Combat.Units.First(u => u.Kind == "troop" && u.OwnerSlot == 0 && u.Def.Id == "g01"); Check(GdMath.IsEqualApprox(troop.Def.Damage, DataCatalog.GetUnit("g01").Damage * 1.10), "Glutsegen: +10 % damage"); state.FinishCombat(); state.Boss.CampBuff[4] = 1; state.Players[3].Draft.Gold += 500; state.Submit(3, Command.Buy("w01", "top")); state.BeginCombat(); LuneUnit red = state.Combat.Units.First(u => u.Kind == "troop" && u.Team == 1); Check(GdMath.IsEqualApprox(red.Shield, red.MaxHp * 0.10) || red.Shield > red.MaxHp * 0.10, "Mondschild: shield of 10 % at the start"); Check(redGain >= 0, "settlement consistent"); // Combats end without waiting for a lone guardian. LuneMatch lonely = NewMatch(true); lonely.RoundNumber = 3; lonely.BeginCombat(); foreach (LuneUnit unit in lonely.Combat.Units.Where(u => u.Kind != "camp" && u.IsMobile())) unit.Hp = 0.0; lonely.Combat.Step(); lonely.Combat.Step(); Check(lonely.Combat.Finished && lonely.Combat.EndReason == "cleared", "a guardian alone does not hold the combat open"); } void TestWards() { LuneMatch state = NewMatch(true); int gold = state.GoldOf(0); Check(!state.Submit(0, Command.Ward("river")).Ok, "unknown lane rejected"); Check(state.Submit(0, Command.Ward("mid")).Ok && state.GoldOf(0) == gold - 40, "a ward costs 40"); Check(!state.Submit(0, Command.Ward("mid")).Ok, "one ward per lane and planning"); Check(LuneFog.LaneScouted(state, 0, "mid", state.Players[0].Draft) && !LuneFog.LaneScouted(state, 0, "mid"), "the own draft ward scouts at once, the team sees it after the commit"); state.BeginCombat(); Check(state.Boss.Wards[1] == 3 && state.Boss.Wards[4] == 0, "the team ward watches mid for three rounds"); state.FinishCombat(); Check(state.Boss.Wards[1] == 2 && LuneFog.LaneScouted(state, 0, "mid") && !LuneFog.LaneScouted(state, 1, "mid"), "it counts down and scouts only for Blau"); state.BuildingHp["rot_top_outer"] = 0.0; Check(LuneFog.LaneScouted(state, 0, "top"), "a fallen enemy outer tower scouts the lane"); } void TestFog() { LuneMatch state = NewMatch(true); state.RoundNumber = 3; state.BeginCombat(); HashSet hidden = LuneFog.Hidden(state, state.Combat, 0, false); Check(state.Combat.Units.Where(u => u.Team == 1 && u.IsMobile()).All(u => hidden.Contains(u.Id)), "at combat start Blau sees no Rot unit (they stand at their base)"); Check(state.Combat.Units.Where(u => u.Team != 1).All(u => !hidden.Contains(u.Id)), "own and neutral units are always visible"); LuneUnit enemy = state.Combat.Units.First(u => u.Team == 1 && u.IsMobile()); LuneUnit own = state.Combat.Units.First(u => u.Team == 0 && u.IsMobile() && u.Zone == enemy.Zone); enemy.Position = own.Position + new Vec2(5.0, 0.0); Check(!LuneFog.Hidden(state, state.Combat, 0, false).Contains(enemy.Id), "an enemy within 9 m of an own unit is visible"); enemy.Position = LuneFog.WardPoint(state, enemy.Lane == "grotto" ? "mid" : enemy.Lane); bool before = LuneFog.Hidden(state, state.Combat, 0, false).Contains(enemy.Id); state.Boss.Wards[LuneMap.LaneIndex(enemy.Lane)] = 2; Check(before && !LuneFog.Hidden(state, state.Combat, 0, false).Contains(enemy.Id), "a ward reveals the middle of its lane"); state.FinishCombat(); LuneMatch plan = NewMatch(true); plan.Players[3].Draft.Gold += 200; plan.Submit(3, Command.Buy("w01", "top")); plan.Submit(3, Command.Buy("w01", "mid")); plan.BeginCombat(); plan.FinishCombat(); plan.Boss.Wards[0] = 1; List scouted = LuneFog.ScoutedEnemies(plan, 0); Check(scouted.Any(e => e.Lane == "top" && e.Slot == 3) && scouted.All(e => e.Lane == "top"), "planning: the warded lane shows Rot's committed army, the others nothing"); Check(LuneFog.ScoutedEnemies(plan, 1).Count == 0 && LuneFog.ScoutedEnemies(NewMatch(false), 0).Count == 0, "no scouting without a ward or under the reference rules"); } } }