enum Fraction { Wall, Elf, Goblin }
class Entity
{
public Fraction Frac;
public int AttackPower;
public int HitPoints;
public int X, Y;
public bool Alive;
public override string ToString() => $"{Frac}[{AttackPower};{HitPoints}]";
}
int Width = 0;
int Height = 0;
Entity[,] Map = null;
List Units = null;
readonly bool DUMP_REACHABLE = false;
readonly bool DUMP_PATHFINDING = false;
readonly bool DUMP_MAP = false;
void Main()
{
Load(File.ReadAllLines(Path.Combine(Path.GetDirectoryName(Util.CurrentQueryPath), @"15_input.txt")));
for (int gen = 0; ; gen++)
{
if (DUMP_MAP) DumpMap(gen);
//if (gen==60)Util.Break();
foreach (var u in Units.OrderBy(p=>p.Y).ThenBy(p=>p.X).ToList())
{
if (!u.Alive) continue;
var success = Tick(u);
if (!success && (Units.Count(q => q.Frac == Fraction.Elf) == 0 || Units.Count(q => q.Frac == Fraction.Goblin) == 0))
{
if (DUMP_MAP) DumpMap(gen+1);
var winner = Units.Where(q => q.Frac != Fraction.Wall).Select(p => p.Frac).Distinct().Single();
var count = Units.Count(q => q.Frac != Fraction.Wall);
var hpsum = Units.Where(q => q.Frac != Fraction.Wall).Sum(q => q.HitPoints);
$"Finished after {gen} rounds with {count} Units ({winner}) and {hpsum} Total HP. The score is [ {hpsum * gen} ] ".Dump();
return;
}
}
}
}
bool Tick(Entity e)
{
var enemyFraction = e.Frac==Fraction.Elf ? Fraction.Goblin : Fraction.Elf;
// [1] Fast Attack
{
Entity target = null;
if (e.Y > 0 && Map[e.X, e.Y - 1] != null && Map[e.X, e.Y - 1].Frac == enemyFraction && (target == null || Map[e.X, e.Y - 1].HitPoints < target.HitPoints)) target = Map[e.X, e.Y - 1];
if (e.X > 0 && Map[e.X - 1, e.Y] != null && Map[e.X - 1, e.Y].Frac == enemyFraction && (target == null || Map[e.X - 1, e.Y].HitPoints < target.HitPoints)) target = Map[e.X - 1, e.Y];
if (e.X < Width - 1 && Map[e.X + 1, e.Y] != null && Map[e.X + 1, e.Y].Frac == enemyFraction && (target == null || Map[e.X + 1, e.Y].HitPoints < target.HitPoints)) target = Map[e.X + 1, e.Y];
if (e.Y < Height - 1 && Map[e.X, e.Y + 1] != null && Map[e.X, e.Y + 1].Frac == enemyFraction && (target == null || Map[e.X, e.Y + 1].HitPoints < target.HitPoints)) target = Map[e.X, e.Y + 1];
if (target != null) { Attack(e,target); return true; }
}
// [2] Path Finding
{
var targetPos = ListTargets(enemyFraction)
.Select(p => (p, GetDistance( (e.X, e.Y), (p.x, p.y) ) ) )
.Where(p => p.Item2!=null)
.OrderBy(p => p.Item2)
.ThenBy(p=>p.p.y)
.ThenBy(p=>p.p.x)
.Select(p=>p.p)
.FirstOrDefault();
if (targetPos==default) { return false; }
int[,] matrix = DoPathFinding(targetPos.x, targetPos.y);
if (DUMP_PATHFINDING) DumpPathFinding(matrix, e, (targetPos.x, targetPos.y));
Tuple targetStep = null;
if (e.Y > 0 && matrix[e.X, e.Y - 1] >= 0 && matrix[e.X, e.Y - 1] < int.MaxValue && (targetStep == null || targetStep.Item3 > matrix[e.X, e.Y - 1])) targetStep = Tuple.Create(e.X, e.Y - 1, matrix[e.X, e.Y - 1]);
if (e.X > 0 && matrix[e.X - 1, e.Y] >= 0 && matrix[e.X - 1, e.Y] < int.MaxValue && (targetStep == null || targetStep.Item3 > matrix[e.X - 1, e.Y])) targetStep = Tuple.Create(e.X - 1, e.Y, matrix[e.X - 1, e.Y]);
if (e.X < Width - 1 && matrix[e.X + 1, e.Y] >= 0 && matrix[e.X + 1, e.Y] < int.MaxValue && (targetStep == null || targetStep.Item3 > matrix[e.X + 1, e.Y])) targetStep = Tuple.Create(e.X + 1, e.Y, matrix[e.X + 1, e.Y]);
if (e.Y < Height - 1 && matrix[e.X, e.Y + 1] >= 0 && matrix[e.X, e.Y + 1] < int.MaxValue && (targetStep == null || targetStep.Item3 > matrix[e.X, e.Y + 1])) targetStep = Tuple.Create(e.X, e.Y + 1, matrix[e.X, e.Y + 1]);
//if (e.X > 0 && matrix[e.X - 1, e.Y] >= 0 && matrix[e.X - 1, e.Y] < int.MaxValue && (targetStep == null || targetStep.Item3 > matrix[e.X - 1, e.Y])) targetStep = Tuple.Create(e.X - 1, e.Y, matrix[e.X - 1, e.Y]);
//if (e.Y > 0 && matrix[e.X, e.Y - 1] >= 0 && matrix[e.X, e.Y - 1] < int.MaxValue && (targetStep == null || targetStep.Item3 > matrix[e.X, e.Y - 1])) targetStep = Tuple.Create(e.X, e.Y - 1, matrix[e.X, e.Y - 1]);
//if (e.Y < Height - 1 && matrix[e.X, e.Y + 1] >= 0 && matrix[e.X, e.Y + 1] < int.MaxValue && (targetStep == null || targetStep.Item3 > matrix[e.X, e.Y + 1])) targetStep = Tuple.Create(e.X, e.Y + 1, matrix[e.X, e.Y + 1]);
//if (e.X < Width - 1 && matrix[e.X + 1, e.Y] >= 0 && matrix[e.X + 1, e.Y] < int.MaxValue && (targetStep == null || targetStep.Item3 > matrix[e.X + 1, e.Y])) targetStep = Tuple.Create(e.X + 1, e.Y, matrix[e.X + 1, e.Y]);
if (targetStep == null) { return false; }
Move(e, targetStep.Item1, targetStep.Item2);
// [3] Normal Attack
if (targetStep.Item3 == 0)
{
Entity att = null;
if (e.Y > 0 && Map[e.X, e.Y - 1] != null && Map[e.X, e.Y - 1].Frac==enemyFraction && (att == null || att.HitPoints > Map[e.X, e.Y - 1].HitPoints)) att = Map[e.X, e.Y - 1];
if (e.X > 0 && Map[e.X - 1, e.Y] != null && Map[e.X - 1, e.Y].Frac==enemyFraction && (att == null || att.HitPoints > Map[e.X - 1, e.Y].HitPoints)) att = Map[e.X - 1, e.Y];
if (e.X < Width - 1 && Map[e.X + 1, e.Y] != null && Map[e.X + 1, e.Y].Frac == enemyFraction && (att == null || att.HitPoints > Map[e.X + 1, e.Y].HitPoints)) att = Map[e.X + 1, e.Y];
if (e.X < Height - 1 && Map[e.X, e.Y + 1] != null && Map[e.X, e.Y + 1].Frac == enemyFraction && (att == null || att.HitPoints > Map[e.X, e.Y + 1].HitPoints)) att = Map[e.X, e.Y + 1];
Attack(e, att);
return true;
}
return true;
}
}
int? GetDistance((int x, int y) p1, (int x, int y) p2)
{
int[,] dmap = new int[Width, Height];
var workload = new Stack<(int, int)>(); //
workload.Push((p1.x, p1.y));
for (int yy = 0; yy < Height; yy++) for (int xx = 0; xx < Width; xx++) dmap[xx, yy] = (Map[xx, yy] != null) ? -1 : int.MaxValue;
dmap[p1.x, p1.y] = 0;
dmap[p2.x, p2.y] = int.MaxValue;
while (workload.Any())
{
(var x, var y) = workload.Pop();
if (y > 0 && dmap[x, y - 1] - 1 > dmap[x, y]) { dmap[x, y - 1] = dmap[x, y] + 1; workload.Push((x, y - 1)); } // [N]
if (x < Width - 1 && dmap[x + 1, y ] - 1 > dmap[x, y]) { dmap[x + 1, y] = dmap[x, y] + 1; workload.Push((x + 1, y )); } // [E]
if (x > 0 && dmap[x - 1, y ] - 1 > dmap[x, y]) { dmap[x - 1, y] = dmap[x, y] + 1; workload.Push((x - 1, y )); } // [W]
if (y < Height - 1 && dmap[x, y + 1] - 1 > dmap[x, y]) { dmap[x, y + 1] = dmap[x, y] + 1; workload.Push((x, y + 1)); } // [S]
}
if (DUMP_REACHABLE) DumpReachable(dmap, p1, p2);
return dmap[p2.x, p2.y]==int.MaxValue ? (int?)null : dmap[p2.x, p2.y];
}
void Move(Entity e, int x, int y)
{
Map[e.X, e.Y] = null;
e.X = x;
e.Y = y;
Map[e.X, e.Y] = e;
}
IEnumerable<(int x,int y, Entity e)> ListTargets(Fraction destFrac)
{
foreach (var u in Units.Where(q => q.Frac==destFrac))
{
if (u.Y > 0 && Map[u.X, u.Y - 1] == null) yield return (u.X, u.Y - 1, u); // [N]
if (u.X < Width - 1 && Map[u.X + 1, u.Y ] == null) yield return (u.X + 1, u.Y, u); // [E]
if (u.X > 0 && Map[u.X - 1, u.Y ] == null) yield return (u.X - 1, u.Y, u); // [W]
if (u.Y < Height - 1 && Map[u.X, u.Y + 1] == null) yield return (u.X, u.Y + 1, u); // [S]
}
}
int[,] DoPathFinding(int dx, int dy)
{
int[,] dmap = new int[Width, Height];
var workload = new Stack<(int, int)>(); //
workload.Push((dx, dy));
for (int yy = 0; yy < Height; yy++) for (int xx = 0; xx < Width; xx++) dmap[xx,yy] = (Map[xx,yy]!=null) ? -1 : int.MaxValue;
dmap[dx,dy]=0;
while (workload.Any())
{
(var x, var y) = workload.Pop();
if (y > 0 && dmap[x, y - 1] - 1 > dmap[x, y]) { dmap[x, y - 1] = dmap[x, y] + 1; workload.Push((x, y - 1)); } // [N]
if (x < Width - 1 && dmap[x + 1, y] - 1 > dmap[x, y]) { dmap[x + 1, y] = dmap[x, y] + 1; workload.Push((x + 1, y)); } // [E]
if (x > 0 && dmap[x - 1, y] - 1 > dmap[x, y]) { dmap[x - 1, y] = dmap[x, y] + 1; workload.Push((x - 1, y)); } // [W]
if (y < Height - 1 && dmap[x, y + 1] - 1 > dmap[x, y]) { dmap[x, y + 1] = dmap[x, y] + 1; workload.Push((x, y + 1)); } // [S]
}
return dmap;
}
void Attack(Entity src, Entity dst)
{
dst.HitPoints -= src.AttackPower;
if (dst.HitPoints <= 0) { dst.Alive = false; Units.Remove(dst); Map[dst.X, dst.Y] = null; }
}
void DumpPathFinding(int[,] dmap, Entity src, (int X, int Y) dst)
{
StringBuilder b = new StringBuilder();
for (int yy = 0; yy < Height; yy++)
{
b.Append(" ");
for (int xx = 0; xx < Width; xx++)
{
if (xx == src.X && yy == src.Y) b.Append('+');
else if (xx == dst.X && yy == dst.Y) b.Append('O');
else if (dmap[xx, yy] == int.MaxValue) b.Append(' ');
else if (dmap[xx, yy] < 0) b.Append('#');
else if (dmap[xx, yy] <= 9) b.Append(dmap[xx, yy]);
else if (dmap[xx, yy] < 36) b.Append((char)('A' + (dmap[xx, yy] - 10)));
else b.Append('$');
}
b.AppendLine();
}
b.ToString().Dump();
}
void DumpReachable(int[,] rmap, (int X, int Y) src, (int X, int Y) dst)
{
StringBuilder b = new StringBuilder();
for (int yy = 0; yy < Height; yy++)
{
b.Append(": ");
for (int xx = 0; xx < Width; xx++)
{
if (xx == src.X && yy == src.Y) b.Append('+');
else if (xx == dst.X && yy == dst.Y) b.Append('O');
else if (Map[xx,yy]?.Frac==Fraction.Wall) b.Append('#');
else if (rmap[xx, yy] < int.MaxValue) b.Append(' ');
else if (rmap[xx, yy] == int.MaxValue) b.Append('.');
else b.Append('$');
}
b.AppendLine();
}
b.ToString().Dump();
}
void DumpMap(int gen)
{
StringBuilder b = new StringBuilder();
for (int yy = 0; yy < Height; yy++)
{
List extra = new List();
for (int xx = 0; xx < Width; xx++)
{
if (Map[xx, yy] == null) { b.Append('.'); }
else if (Map[xx, yy].Frac == Fraction.Wall) { b.Append('#'); }
else if (Map[xx, yy].Frac == Fraction.Elf) { b.Append('E'); extra.Add($"E({Map[xx, yy].HitPoints})"); }
else if (Map[xx, yy].Frac == Fraction.Goblin) { b.Append('G'); extra.Add($"G({Map[xx, yy].HitPoints})"); }
else throw new Exception($"[{xx}|{yy}] := {Map[xx,yy]}");
}
b.Append($" {string.Join(", ", extra)}{(extra.Any()?", ":"")}");
b.AppendLine();
}
$"After {gen} rounds:".Dump();
b.ToString().Trim().Dump();
$"{new string(' ', Width)} HP[G] := {Units.Where(p => p.Frac == Fraction.Goblin).Sum(p => p.HitPoints)}".Dump();
$"{new string(' ', Width)} HP[E] := {Units.Where(p => p.Frac == Fraction.Elf).Sum(p => p.HitPoints)}".Dump();
"".Dump();
"".Dump();
"".Dump();
}
void Load(string[] input)
{
Width = input[0].Length;
Height = input.Length;
Map = new UserQuery.Entity[Width, Height];
Units = new List();
for (int yy = 0; yy < Height; yy++)
{
for (int xx = 0; xx < Width; xx++)
{
if (input[yy][xx] == '#')
Map[xx, yy] = new Entity { Frac = Fraction.Wall, AttackPower = 0, HitPoints = int.MaxValue, X=xx, Y=yy, Alive=true };
else if (input[yy][xx] == '.')
Map[xx, yy] = null;
else if (input[yy][xx] == 'E')
Units.Add(Map[xx, yy] = new Entity { Frac = Fraction.Elf, AttackPower = 3, HitPoints = 200, X=xx, Y=yy, Alive=true });
else if (input[yy][xx] == 'G')
Units.Add(Map[xx, yy] = new Entity { Frac = Fraction.Goblin, AttackPower = 3, HitPoints = 200, X=xx, Y=yy, Alive=true });
else
throw new Exception($"[{xx}|{yy}] := {input[xx][yy]}");
}
}
}