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<string> _commands;
private static List<Bomb> _bombStates;
private static int _factoryCount;
private static List<Factory> _wantedFactories;
private static Factory _friendlyHq;
private static Factory _enemyHq;
private static List<Factory> _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<Link> map = new List<Link>();
_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<string>();
_bombStates = new List<Bomb>();
while (true) // game loop
{
UpdateGame(map);
}
}
private static void UpdateGame(List<Link> map)
{
_commands.Clear(); // Reset commands each turn.
List<Entity> entities = new List<Entity>();
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<Factory> friendlyFactories = _allFactories.Where(e => e.IsFriendly).ToList();
List<Factory> nonFriendlyFactories = _allFactories.Where(e => !e.IsFriendly).ToList();
List<Factory> 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<Troop> enemyTroops = entities.Where(e => e.IsHostile && e.GetType() == typeof(Troop))
.Select(t => (Troop)t).ToList();
List<Bomb> 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<Link> map, List<Factory> friendlyFactories, List<Factory> nonFriendlyFactories, List<Troop> enemyTroops, List<Bomb> 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<Link> 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<Link> map, List<Entity> entities, List<Factory> friendlyFactories, List<Factory> nonFriendlyFactories)
{
Factory enemyHq = (Factory)entities.First(e => e.IsHostile && e.GetType() == typeof(Factory));
List<int> 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<Factory> friendlyFactories, List<Link> map, List<Factory> nonFriendlyFactories)
{
Factory hq = friendlyFactories.First();
var requiredExpansions = (_factoryCount / 2);
List<int> 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<Factory> 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<Bomb> 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<string> Evacuate(List<Factory> friendlyFactories, Factory factory, List<Link> map)
{
IEnumerable<Factory> 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<string> { $"MSG Evacuating {factory.Id}", $"MOVE {factory.Id} {evacuationTarget} {factory.Defense}" };
}
private static bool ShouldEvacuateFactory(Factory factory, List<Factory> factories, List<Link> 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<Factory> friendlyFactories, List<Troop> enemyTroops, List<Link> 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<Troop> 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<Troop> enemyTroops)
{
List<Troop> 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<Link> 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<Link> 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<Factory> candidates = new List<Factory>();
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<Factory> targets, List<Bomb> friendlyBombs, List<Link> 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<Bomb> 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<Link> 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<int> GetClosestXLinkedFactories(Factory source, List<Link> map, int take)
{
IEnumerable<Link> 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<Entity> 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; }
}
}
Comments