using System; using System.Collections.Generic; using System.Linq; // To debug: Console.Error.WriteLine("Debug messages..."); internal class Player { private const int MAX_PRODUCTION = 3; private const int UPGRADE_COST = 10; private static string[] _inputs; private static bool _isBombingAvailable; private static bool _isFirstRound; private static List _commands; private static List _bombStates; private static int _factoryCount; private static List _wantedFactories; private static Factory _friendlyHq; private static Factory _enemyHq; private static List _allFactories; private static void Main(string[] args) { _isFirstRound = true; _factoryCount = int.Parse(Console.ReadLine()); int linkCount = int.Parse(Console.ReadLine()); // the number of links between factories List map = new List(); _isBombingAvailable = true; for (int i = 0; i < linkCount; i++) { _inputs = Console.ReadLine().Split(' '); int factory1 = int.Parse(_inputs[0]); int factory2 = int.Parse(_inputs[1]); int distance = int.Parse(_inputs[2]); map.Add(new Link(factory1, factory2, distance)); } _commands = new List(); _bombStates = new List(); while (true) // game loop { UpdateGame(map); } } private static void UpdateGame(List map) { _commands.Clear(); // Reset commands each turn. List entities = new List(); int entityCount = int.Parse(Console.ReadLine()); // the number of entities (e.g. factories and troops) for (int i = 0; i < entityCount; i++) { _inputs = Console.ReadLine().Split(' '); ParseEntity(_inputs, entities); } _allFactories = entities.Where(e => e.GetType() == typeof(Factory)).Select(x => (Factory)x).ToList(); List friendlyFactories = _allFactories.Where(e => e.IsFriendly).ToList(); List nonFriendlyFactories = _allFactories.Where(e => !e.IsFriendly).ToList(); List hostileFactories = _allFactories.Where(e => e.IsHostile).ToList(); if (friendlyFactories.Any() && hostileFactories.Any()) { int production = friendlyFactories.Sum(f => f.Production); int enemyProduction = hostileFactories.Sum(f => f.Production); if (production == enemyProduction) { _commands.Add($"MSG Production is equal!"); } else { int diffBetweenProduction = production - enemyProduction; if (diffBetweenProduction > 2) { _commands.Add($"MSG Production superior!"); } } } List enemyTroops = entities.Where(e => e.IsHostile && e.GetType() == typeof(Troop)) .Select(t => (Troop)t).ToList(); List bombsPresent = entities.Where(e => e.GetType() == typeof(Bomb)).Select(b => (Bomb)b) .ToList(); UpdateBombStates(bombsPresent); if (_isBombingAvailable) { SendBomb(map, entities, friendlyFactories, nonFriendlyFactories); } if (_isFirstRound) { _wantedFactories = _allFactories; _friendlyHq = friendlyFactories.First(); _enemyHq = hostileFactories.First(); ExecuteFirstRound(friendlyFactories, map, nonFriendlyFactories); } else { ExecuteRound(map, friendlyFactories, nonFriendlyFactories, enemyTroops, bombsPresent); } ExecuteWaitCommandAsFallback(); ExecuteCommands(); // Any valid action, such as "WAIT" or "MOVE source destination cyborgs" } private static void ExecuteCommands() { Console.WriteLine(string.Join(';', _commands)); } private static void ExecuteWaitCommandAsFallback() { if (_commands.Count == 0) { _commands.Add("WAIT"); } } private static void ExecuteRound(List map, List friendlyFactories, List nonFriendlyFactories, List enemyTroops, List bombs) { foreach (Factory factory in friendlyFactories) { if (ShouldEvacuateFactory(factory, _allFactories, map)) { _commands.AddRange(Evacuate(friendlyFactories, factory, map)); continue; } if (factory.Production == 0) { if (factory.Defense >= UPGRADE_COST) { Console.Error.WriteLine($"{factory.Id}: ZERO PROD UPGRADE!"); _commands.Add($"INC {factory.Id}"); } } else { int availableCyborgs = CalculateDefenses(factory, enemyTroops); availableCyborgs = DefendFactories(factory, availableCyborgs, friendlyFactories, enemyTroops, map); if (ShouldIncreaseProduction(factory, friendlyFactories.Count, availableCyborgs, bombs.Any(b => b.IsHostile))) { Console.Error.WriteLine($"{factory.Id}: UPGRADE!"); availableCyborgs -= 10; _commands.Add($"INC {factory.Id}"); } if(friendlyFactories.Count >= _wantedFactories.Count) { Factory upgradeTarget = friendlyFactories.FirstOrDefault(f => f.Production == 0 && f.Id != factory.Id); if (upgradeTarget != null && upgradeTarget.Defense < UPGRADE_COST) { Console.Error.WriteLine($"{factory.Id}: UPGRADE TARGET {upgradeTarget.Id}!"); _commands.Add($"MOVE {factory.Id} {upgradeTarget.Id} {UPGRADE_COST - upgradeTarget.Defense}"); } } if (factory.Production == MAX_PRODUCTION || friendlyFactories.Count < _wantedFactories.Count) { int target = FindTarget(factory, nonFriendlyFactories, bombs, map); if (target != factory.Id) { Console.Error.WriteLine($"{factory.Id}: RELOCATE TO {target}!"); RelocateCyborgs(map, factory, availableCyborgs, target); } } } } } private static void RelocateCyborgs(List map, Factory factory, int availableCyborgs, int target) { int path = FindPath(factory, target, map); if (availableCyborgs > 0) { _commands.Add($"MOVE {factory.Id} {path} {availableCyborgs}"); } } private static void SendBomb(List map, List entities, List friendlyFactories, List nonFriendlyFactories) { Factory enemyHq = (Factory)entities.First(e => e.IsHostile && e.GetType() == typeof(Factory)); List linkedFactories = GetClosestXLinkedFactories(enemyHq, map, 3); int bombTarget = nonFriendlyFactories.Where(t => linkedFactories.Contains(t.Id)) .OrderByDescending(t => t.Production).First().Id; _commands.Add($"BOMB {friendlyFactories.First().Id} {enemyHq.Id}"); _commands.Add($"BOMB {friendlyFactories.First().Id} {bombTarget}"); _isBombingAvailable = false; } private static void ExecuteFirstRound(List friendlyFactories, List map, List nonFriendlyFactories) { Factory hq = friendlyFactories.First(); var requiredExpansions = (_factoryCount / 2); List closestExpansions = GetClosestXLinkedFactories(hq, map, requiredExpansions); _wantedFactories = _wantedFactories.Where(f => closestExpansions.Contains(f.Id)).ToList(); _wantedFactories.Add(hq); Console.Error.WriteLine("Wanted factories:"); foreach(var f in _wantedFactories) { Console.Error.WriteLine(f.Id); } IOrderedEnumerable prioritizedTargets = nonFriendlyFactories.Where(f => closestExpansions.Contains(f.Id)) .OrderByDescending(f => (f.Production * 10) / (f.Defense + 1)); int availableCyborgs = hq.Defense; foreach (Factory target in prioritizedTargets) { if (availableCyborgs < target.Defense) { continue; } int requiredForces = target.Defense + 1; _commands.Add($"MOVE {hq.Id} {target.Id} {requiredForces}"); availableCyborgs -= requiredForces; } _isFirstRound = false; } private static void UpdateBombStates(List bombsPresent) { if (!bombsPresent.Any()) { _bombStates.Clear(); } else { _bombStates.AddRange(bombsPresent.Where(b => !_bombStates.Select(s => s.Id).Contains(b.Id))); _bombStates.RemoveAll(b => b.Age == b.ETA); foreach (Bomb b in _bombStates) { b.Age++; } } } private static List Evacuate(List friendlyFactories, Factory factory, List map) { IEnumerable evacuationCandidates = friendlyFactories.Where(f => f.Id != factory.Id); int evacuationTarget = 0; int closestDistance = int.MaxValue; foreach (Factory candidate in evacuationCandidates) { Link link = GetLinkBetween(factory, candidate, map); if (link.Distance < closestDistance) { closestDistance = link.Distance; evacuationTarget = candidate.Id; } } return new List { $"MSG Evacuating {factory.Id}", $"MOVE {factory.Id} {evacuationTarget} {factory.Defense}" }; } private static bool ShouldEvacuateFactory(Factory factory, List factories, List map) { if (_bombStates.Count == 0 || !factories.Any(f => f.IsHostile)) // check hostiles left due to bomb crashing code if game already won. { return false; } foreach (Bomb bomb in _bombStates) { if (bomb.Source == factory.Id) { // Target will never be source factory continue; } else if (IsHostileBomb(bomb)) { Factory bombSource = factories.First(f => f.Id == bomb.Source); int distance = GetLinkBetween(factory, bombSource, map).Distance; if (distance - bomb.Age == 1) { return true; } } else { if (bomb.Target == factory.Id && bomb.ETA == 1) { return true; } } } return false; } private static bool IsHostileBomb(Bomb bomb) { return bomb.Target == -1; } private static int DefendFactories(Factory source, int availableCyborgs, List friendlyFactories, List enemyTroops, List map) { var defenseCandidates = friendlyFactories.Where(f => f.Id != source.Id).Select(f => GetLinkBetween(source, f, map)).Where(f => f.Distance <= 6).OrderBy(f => f.Distance) .Select(f => new { f.Distance, Id = f.Factory1 == source.Id ? f.Factory2 : f.Factory1 }).Take(3); foreach (var candidate in defenseCandidates) { Factory targetFactory = friendlyFactories.First(f => f.Id == candidate.Id); List attackers = enemyTroops.Where(t => t.Target == targetFactory.Id).ToList(); if (!attackers.Any()) { continue; } int attackingTroops = attackers.Where(t => t.ETA <= candidate.Distance).Sum(t => t.Strength); int defendingTroops = targetFactory.Defense + (targetFactory.Production * candidate.Distance); if (attackingTroops <= defendingTroops) { continue; } int backupRequired = attackingTroops - defendingTroops; int sentBackup = backupRequired > availableCyborgs ? availableCyborgs : backupRequired; _commands.Add($"MOVE {source.Id} {candidate.Id} {sentBackup}"); return availableCyborgs - sentBackup; } return availableCyborgs; } // Calculate required defenses six turns ahead, considering already sent enemy troops. // Does not evaluate enemy troops not sent yet by close enemy factories. private static int CalculateDefenses(Factory source, List enemyTroops) { List attackers = enemyTroops.Where(t => t.Target == source.Id && t.ETA <= 6).ToList(); if (attackers.Count == 0) { return source.Defense; } int incomingAttackers = attackers.Sum(t => t.Strength); if (source.Defense > incomingAttackers) { return source.Defense - incomingAttackers; } return 0; } private static int FindPath(Factory source, int targetFactoryId, List map) { Link directPath = map.First(l => (l.Factory1 == source.Id && l.Factory2 == targetFactoryId) || l.Factory1 == targetFactoryId && l.Factory2 == source.Id); int directDistance = directPath.Distance; IEnumerable alternativePaths = map.Where(l => l.Factory1 == source.Id || l.Factory2 == source.Id).Where(l => !(l.Factory1 == source.Id && l.Factory2 == targetFactoryId) && !(l.Factory1 == targetFactoryId && l.Factory2 == source.Id)); List candidates = new List(); foreach (Link alternativePath in alternativePaths) { int intermediateDistance = alternativePath.Distance; int intermediateFactoryId = alternativePath.Factory1 == source.Id ? alternativePath.Factory2 : alternativePath.Factory1; Link directPathFromIntermediate = map.First(l => (l.Factory1 == intermediateFactoryId && l.Factory2 == targetFactoryId) || l.Factory2 == intermediateFactoryId && l.Factory1 == targetFactoryId); if (intermediateDistance + directPathFromIntermediate.Distance <= directDistance) { Factory intermediateFactory = _allFactories.First(f => f.Id == intermediateFactoryId); intermediateFactory.DistanceTo = intermediateDistance; candidates.Add(intermediateFactory); } } if (candidates.Any()) { return candidates.OrderBy(c => c.DistanceTo).ThenByDescending(c => c.Production).First().Id; } return targetFactoryId; } private static int FindTarget(Factory source, List targets, List friendlyBombs, List map) { if (_wantedFactories.All(f => _allFactories.First(a => a.Id == f.Id).IsFriendly)) { // If we control the required factories, send troops to front line. var candidates = _wantedFactories.Where(f => f.Id != source.Id && !TargetWillBeBombed(friendlyBombs, GetLinkBetween(source, f, map))) .OrderBy(f => GetLinkBetween(f, _enemyHq, map).Distance); var selectedTarget = candidates.FirstOrDefault()?.Id ?? source.Id; Console.Error.WriteLine($"{source.Id} selected target {selectedTarget}"); return selectedTarget; } else { Console.Error.WriteLine($"HQ: {_friendlyHq.Id}"); var candidates = _wantedFactories.Where(f => f.Id != source.Id && f.Id != _friendlyHq.Id && !TargetWillBeBombed(friendlyBombs, GetLinkBetween(source, f, map))) .OrderBy(f => _allFactories.Find(a => a.Id == f.Id).Team).ThenBy(f => GetLinkBetween(f, _friendlyHq, map).Distance); Console.Error.WriteLine(string.Join(" ", candidates.Select(c => c.Id))); Console.Error.WriteLine(string.Join(" ", candidates.Select(c => c.Team))); var selectedTarget = candidates.FirstOrDefault()?.Id ?? source.Id; Console.Error.WriteLine($"{source.Id} selected target {selectedTarget}"); return selectedTarget; } } private static bool TargetWillBeBombed(List friendlyBombs, Link link) { return !friendlyBombs.All(b => b.ETA != link.Distance && b.ETA != link.Distance + 1); } private static Link GetLinkBetween(Factory source, Factory target, List map) { if (source.Id == target.Id) { throw new ArgumentException("Source and Target can not be the same factory"); } return map.First(l => (l.Factory1 == source.Id && l.Factory2 == target.Id) || (l.Factory1 == target.Id && l.Factory2 == source.Id)); } private static List GetClosestXLinkedFactories(Factory source, List map, int take) { IEnumerable links = map.Where(m => m.Factory1 == source.Id || m.Factory2 == source.Id).OrderBy(l => l.Distance) .Take(take); return links.Select(link => link.Factory1 == source.Id ? link.Factory2 : link.Factory1).ToList(); } private static bool ShouldIncreaseProduction(Factory source, int factoryCount, int availableCyborgs, bool bombPresent) { return source.Production != MAX_PRODUCTION && factoryCount >= _wantedFactories.Count && availableCyborgs >= 10 && !bombPresent; } private static void ParseEntity(string[] inputs, List entities) { int entityId = int.Parse(inputs[0]); string entityType = inputs[1]; int entityTeam = int.Parse(inputs[2]); int arg2 = int.Parse(inputs[3]); int arg3 = int.Parse(inputs[4]); int arg4 = int.Parse(inputs[5]); int arg5 = int.Parse(inputs[6]); switch (entityType) { case "BOMB": entities.Add(new Bomb { Id = entityId, Type = entityType, Team = entityTeam, Source = arg2, Target = arg3, ETA = arg4 }); break; case "TROOP": entities.Add(new Troop { Id = entityId, Type = entityType, Team = entityTeam, Source = arg2, Target = arg3, Strength = arg4, ETA = arg5 }); break; default: entities.Add(new Factory { Id = entityId, Type = entityType, Team = entityTeam, Defense = arg2, Production = arg3, Cooldown = arg4 }); break; } } public class Link { public int Factory1 { get; } public int Factory2 { get; } public int Distance { get; } public Link(int factory1, int factory2, int distance) { Factory1 = factory1; Factory2 = factory2; Distance = distance; } } public class Target { public int Id { get; } public int Defense { get; } public int Distance { get; } public int Production { get; } public Target(int id, int distance, int production, int defense) { Id = id; Distance = distance; Production = production; Defense = defense; } } public class Entity { public int Id { get; set; } public string Type { get; set; } public int Team { get; set; } public bool IsFriendly => Team == 1; public bool IsNeutral => Team == 0; public bool IsHostile => Team == -1; } private class Troop : Entity { public int Source { get; set; } public int Target { get; set; } public int Strength { get; set; } public int ETA { get; set; } } public class Factory : Entity { public int Defense { get; set; } public int Production { get; set; } public int Cooldown { get; set; } public int DistanceTo { get; set; } } public class Bomb : Entity { public int Source { get; set; } public int Target { get; set; } public int ETA { get; set; } public int Age { get; set; } } }