Program.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. namespace D15._2
  7. {
  8. abstract class Unit
  9. {
  10. private int MaxHP = 200;
  11. public int HP = 0;
  12. public (int X, int Y) Coord;
  13. private (int X, int Y) InitCoord;
  14. public bool IsHealthy = true;
  15. public Unit((int x, int y) coord)
  16. {
  17. HP = MaxHP;
  18. InitCoord = coord;
  19. Coord = InitCoord;
  20. }
  21. public abstract int GetAtk();
  22. public void Move((int mx, int my) mv) => Coord = (Coord.X + mv.mx, Coord.Y + mv.my);
  23. public void Attack(Unit target)
  24. {
  25. target.ReceiveAttack(GetAtk());
  26. }
  27. private void ReceiveAttack(int atk)
  28. {
  29. HP -= atk;
  30. if (HP <= 0) IsHealthy = false;
  31. }
  32. public void Wololo()
  33. {
  34. HP = MaxHP;
  35. IsHealthy = true;
  36. Coord = InitCoord;
  37. }
  38. }
  39. class Gob : Unit
  40. {
  41. public Gob((int x, int y) coord) : base(coord) { }
  42. public override int GetAtk()
  43. {
  44. return 3;
  45. }
  46. }
  47. class Elf : Unit
  48. {
  49. public Elf((int x, int y) coord) : base(coord) { }
  50. public override int GetAtk()
  51. {
  52. return Program.ElfAtk;
  53. }
  54. }
  55. class Map : HashSet<(int x, int y)> { }
  56. class Units : List<Unit> { }
  57. class Program
  58. {
  59. public static int ElfAtk = 4;
  60. static readonly (int mx, int my)[] MoveSequence = new[] { (0, -1), (-1, 0), (1, 0), (0, 1) };
  61. static int round = 0;
  62. //static bool PRINT = false;
  63. static void Main(string[] args)
  64. {
  65. if (args.Length < 1) return;
  66. if (File.Exists(args[0]) == false) return;
  67. var file = File.OpenText(args[0]);
  68. var map = new Map();
  69. var strMap = new List<string>();
  70. var units = new Units();
  71. FillMap(file, map, strMap, units);
  72. while (true)
  73. {
  74. Console.WriteLine($"Testing atk power of : {ElfAtk}");
  75. round = 0;
  76. bool continueCombat = true;
  77. foreach (var unit in units) unit.Wololo();
  78. do
  79. {
  80. continueCombat = Tick(map, units);
  81. if (continueCombat) round++;
  82. } while (continueCombat);
  83. var deadElf = 0;
  84. foreach (var unit in units)
  85. if (unit is Elf && unit.IsHealthy == false) deadElf++;
  86. if (deadElf == 0) break;
  87. ElfAtk++;
  88. Console.WriteLine($"Elves suffered heavy losses...\n");
  89. }
  90. Console.WriteLine($"Elves win !");
  91. TheAnswerIs(units, round);
  92. }
  93. private static void TheAnswerIs(Units units, int round)
  94. {
  95. var hitPoints = 0;
  96. foreach (var unit in units)
  97. {
  98. Console.WriteLine($"Fighter {unit.GetType().Name} : { (unit.IsHealthy ? $"{unit.HP}HP" : $" - DEAD - ({unit.HP})") }");
  99. if (unit.IsHealthy) hitPoints += unit.HP;
  100. }
  101. Console.WriteLine($"\nCombat ends after { round } rounds with { hitPoints } remaining HP");
  102. Console.WriteLine($"The answer is : { hitPoints * round }\n");
  103. }
  104. private static bool Tick(Map map, Units units)
  105. {
  106. units.Sort((a, b) =>
  107. {
  108. if (a.Coord.Y == b.Coord.Y) return a.Coord.X - b.Coord.X;
  109. return a.Coord.Y - b.Coord.Y;
  110. });
  111. for (int i = 0; i < units.Count; ++i)
  112. {
  113. var unit = units[i];
  114. if (unit.IsHealthy == false) continue;
  115. List<Unit> filtered = FilterOnHealthyFoes(units, unit);
  116. if (filtered == null || filtered.Count == 0) return false;
  117. var inRange = new Map();
  118. var unitNeedsToMove = GetTilesInRange(map, inRange, unit, filtered);
  119. if (unitNeedsToMove) MoveUnit(map, units, unit, inRange);
  120. var targetHasDied = AttackNearestTarget(unit, filtered);
  121. // If last target has died we end up the simulation
  122. if (targetHasDied && filtered.Count == 1)
  123. {
  124. bool isFullRound = true;
  125. for (var j = i + 1; j < units.Count; ++j)
  126. {
  127. if (units[j].IsHealthy == false) continue;
  128. isFullRound = false;
  129. break;
  130. }
  131. return isFullRound;
  132. }
  133. }
  134. return true;
  135. }
  136. private static bool AttackNearestTarget(Unit unit, List<Unit> filtered)
  137. {
  138. var target = GetTarget(unit, filtered);
  139. if (target != null)
  140. {
  141. unit.Attack(target);
  142. if (target.IsHealthy == false)
  143. return true;
  144. }
  145. return false;
  146. }
  147. private static Unit GetTarget(Unit unit, List<Unit> filtered)
  148. {
  149. Unit minHpTarget = null;
  150. foreach (var mv in MoveSequence)
  151. {
  152. (int x, int y) targetc = (unit.Coord.X + mv.mx, unit.Coord.Y + mv.my);
  153. var target = filtered.FirstOrDefault(f => f.Coord == targetc);
  154. if (target != default && (minHpTarget == null || target.HP < minHpTarget.HP))
  155. minHpTarget = target;
  156. }
  157. return minHpTarget;
  158. }
  159. private static void MoveUnit(Map map, Units units, Unit unit, Map inRange)
  160. {
  161. var inRangeActions = new Dictionary<(int x, int y), List<(int x, int y)>>();
  162. Parallel.ForEach(inRange, (r) =>
  163. {
  164. GetBreadthFirstSearch(unit, map, units, inRangeActions, r);
  165. });
  166. if (inRangeActions.Count == 0)
  167. return;
  168. var shortest = inRangeActions
  169. .OrderBy(ir => ir.Value.Count)
  170. .FirstOrDefault();
  171. unit.Move(shortest.Value.First());
  172. }
  173. // https://en.wikipedia.org/wiki/Breadth-first_search
  174. private static void GetBreadthFirstSearch(Unit unit, Map map, Units units, Dictionary<(int x, int y), List<(int x, int y)>> inRangeActions, (int x, int y) root)
  175. {
  176. var nodesToVisit = new Queue<(int x, int y)>();
  177. var visitedNodes = new HashSet<(int x, int y)>();
  178. var meta = new Dictionary<(int x, int y), (int x, int y)>()
  179. {
  180. { unit.Coord, (0, 0) }
  181. };
  182. nodesToVisit.Enqueue(unit.Coord);
  183. while (nodesToVisit.Count > 0)
  184. {
  185. var node = nodesToVisit.Dequeue();
  186. // Found it!
  187. if (node == root)
  188. {
  189. GetActionList(inRangeActions, root, meta, node);
  190. break;
  191. }
  192. foreach (var mv in MoveSequence)
  193. {
  194. (int x, int y) successor = (node.x + mv.mx, node.y + mv.my);
  195. // Continue if successor is not a valid tile
  196. if (map.Contains(successor) == false) continue;
  197. if (units.FirstOrDefault(u => u.IsHealthy && u.Coord == successor) != default) continue;
  198. if (visitedNodes.Contains(successor)) continue;
  199. if (nodesToVisit.Contains(successor) == false)
  200. {
  201. meta.TryAdd(successor, mv);
  202. nodesToVisit.Enqueue(successor);
  203. }
  204. }
  205. visitedNodes.Add(node);
  206. }
  207. }
  208. private static readonly object rangeLock = new object();
  209. private static void GetActionList(Dictionary<(int x, int y), List<(int x, int y)>> inRangeActions, (int x, int y) root, Dictionary<(int x, int y), (int x, int y)> meta, (int x, int y) node)
  210. {
  211. var actionList = new List<(int x, int y)>();
  212. while (meta[node] != (0, 0))
  213. {
  214. var action = meta[node];
  215. node = (node.x - action.x, node.y - action.y);
  216. actionList.Add(action);
  217. }
  218. actionList.Reverse();
  219. lock (rangeLock) inRangeActions.Add(root, actionList);
  220. }
  221. private static bool GetTilesInRange(Map map, Map inRange, Unit unit, List<Unit> filtered)
  222. {
  223. foreach (var foe in filtered)
  224. {
  225. foreach (var move in MoveSequence)
  226. {
  227. (int x, int y) nc = (foe.Coord.X + move.mx, foe.Coord.Y + move.my);
  228. // Unit has no need to move
  229. if (nc.x == unit.Coord.X && nc.y == unit.Coord.Y)
  230. return false;
  231. if (map.Contains(nc)) inRange.Add(nc);
  232. }
  233. }
  234. return inRange.Count > 0;
  235. }
  236. private static List<Unit> FilterOnHealthyFoes(Units units, Unit unit)
  237. {
  238. List<Unit> filtered = null;
  239. switch (unit)
  240. {
  241. case var un when un is Gob: filtered = units.Where(u => u is Elf && u.IsHealthy).ToList(); break;
  242. case var un when un is Elf: filtered = units.Where(u => u is Gob && u.IsHealthy).ToList(); break;
  243. }
  244. return filtered;
  245. }
  246. private static void FillMap(StreamReader file, Map map, List<string> strMap, Units units)
  247. {
  248. int y = 0;
  249. do
  250. {
  251. var line = file.ReadLine();
  252. if (line == null) break;
  253. strMap.Add(line);
  254. for (int x = 0; x < line.Length; ++x)
  255. {
  256. (int x, int y) coord = (x, y);
  257. if (line[x] == 'G') units.Add(new Gob(coord));
  258. if (line[x] == 'E') units.Add(new Elf(coord));
  259. if (line[x] != '#') map.Add(coord);
  260. }
  261. y++;
  262. } while (true);
  263. }
  264. }
  265. }