My solutions to 2018's advent of code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

day15.nim 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import algorithm, deques, sequtils, strutils
  2. type
  3. Species = enum
  4. spElf, spGoblin
  5. Point = tuple[x,y: int]
  6. Creep = ref object
  7. pos: Point
  8. hp,ap: int
  9. species: Species
  10. proc cmpCreeps(x, y: Creep): int =
  11. if x.pos.y == y.pos.y:
  12. x.pos.x - y.pos.x
  13. else:
  14. x.pos.y - y.pos.y
  15. iterator moves(pos: Point): Point =
  16. yield (pos.x, pos.y-1)
  17. yield (pos.x-1, pos.y)
  18. yield (pos.x+1, pos.y)
  19. yield (pos.x, pos.y+1)
  20. proc highAt(bitmasks: seq[uint64], pos: Point): bool {.inline.} =
  21. let mask = 1'u64 shl pos.x
  22. (bitmasks[pos.y] and mask) == mask
  23. func setLowAt(bitmasks: var seq[uint64], pos: Point) {.inline.} =
  24. bitmasks[pos.y] = bitmasks[pos.y] and not (1'u64 shl pos.x)
  25. func setHighAt(bitmasks: var seq[uint64], pos: Point) {.inline.} =
  26. bitmasks[pos.y] = bitmasks[pos.y] or (1'u64 shl pos.x)
  27. proc inRange(targets: seq[uint64], pos: Point): bool =
  28. for move in pos.moves:
  29. if targets.highAt(move):
  30. return true
  31. proc allDead(targets: seq[uint64]): bool =
  32. for line in targets:
  33. if line != 0:
  34. return false
  35. return true
  36. proc totalHp(creeps: seq[Creep]): int =
  37. for creep in creeps:
  38. if creep.hp > 0:
  39. result += creep.hp
  40. proc step(targets: seq[uint64], passable: seq[uint64], pos: Point): tuple[valid: bool, step: Point] =
  41. type Step = tuple[firstStep, pos: Point]
  42. var
  43. queue = initDeque[Step]()
  44. allowed: seq[uint64]
  45. allowed.deepCopy(passable)
  46. for firstStep in pos.moves:
  47. if allowed.highAt(firstStep):
  48. queue.addLast((firstStep, firstStep))
  49. allowed.setLowAt(firstStep)
  50. while queue.len > 0:
  51. let lastStep = queue.popFirst()
  52. for nextStep in lastStep.pos.moves:
  53. if targets.highAt(nextStep):
  54. return (true, lastStep.firstStep)
  55. if allowed.highAt(nextStep):
  56. queue.addLast((lastStep.firstStep, nextStep))
  57. allowed.setLowAt(nextStep)
  58. let
  59. input = readFile("data/15.txt").strip.splitlines
  60. var
  61. maxx = int.low
  62. creeps: seq[Creep]
  63. passable: seq[uint64]
  64. goblins: seq[uint64]
  65. elves: seq[uint64]
  66. proc load(elfAttackPower: int = 3) =
  67. maxx = int.low
  68. creeps.setLen(0)
  69. passable.setLen(0)
  70. goblins.setLen(0)
  71. elves.setLen(0)
  72. for y, line in input:
  73. var linePassable, lineGoblins, lineElves: uint64
  74. for x, c in line:
  75. maxx = max(x, maxx)
  76. if c == 'E' or c == 'G':
  77. var creep = new(Creep)
  78. creep.pos = (x,y)
  79. creep.hp = 200
  80. if c == 'E':
  81. creep.species = spElf
  82. lineElves = lineElves or (1'u64 shl x)
  83. creep.ap = elfAttackPower
  84. else:
  85. creep.species = spGoblin
  86. lineGoblins = lineGoblins or (1'u64 shl x)
  87. creep.ap = 3
  88. creeps.add(creep)
  89. elif c == '.':
  90. linePassable = linePassable or (1'u64 shl x)
  91. passable.add(linePassable)
  92. goblins.add(lineGoblins)
  93. elves.add(lineElves)
  94. proc run(elfAttackPower: int = 3, allowElvesToDie: bool = true): int =
  95. load(elfAttackPower)
  96. var runs: int
  97. while true:
  98. creeps.sort(cmpCreeps)
  99. for creep in creeps:
  100. if creep.hp <= 0:
  101. continue
  102. let targets = if creep.species == spGoblin: elves else: goblins
  103. if targets.allDead:
  104. return runs * creeps.totalHp
  105. if not targets.inRange(creep.pos):
  106. let move = targets.step(passable, creep.pos)
  107. if move.valid:
  108. if creep.species == spGoblin:
  109. goblins.setLowAt(creep.pos)
  110. goblins.setHighAt(move.step)
  111. else:
  112. elves.setLowAt(creep.pos)
  113. elves.setHighAt(move.step)
  114. passable.setHighAt(creep.pos)
  115. passable.setLowAt(move.step)
  116. creep.pos = move.step
  117. if targets.inRange(creep.pos):
  118. let spaces = toSeq(creep.pos.moves)
  119. var
  120. lowestHp = int.high
  121. bestCreep: Creep
  122. for other in creeps:
  123. # Find an enemy that's alive
  124. if other.species != creep.species and other.hp > 0 and
  125. # ... That's in range
  126. other.pos in spaces and
  127. # ... With lower HP
  128. (other.hp < lowestHp or
  129. # ... Or the same HP and higher up
  130. (other.hp == lowestHp and (other.pos.y < bestCreep.pos.y or
  131. # ... Or the same HP and height but further left
  132. (other.pos.y == bestCreep.pos.y and other.pos.x < bestCreep.pos.x)))):
  133. lowestHp = other.hp
  134. bestCreep = other
  135. bestCreep.hp -= creep.ap
  136. if bestCreep.hp <= 0:
  137. if bestCreep.species == spGoblin:
  138. goblins.setLowAt(bestCreep.pos)
  139. else:
  140. elves.setLowAt(bestCreep.pos)
  141. if not allowElvesToDie:
  142. return -1
  143. passable.setHighAt(bestCreep.pos)
  144. runs.inc
  145. echo run()
  146. var ap = 4
  147. while true:
  148. let res = run(ap, false)
  149. if res > -1:
  150. echo res
  151. break
  152. ap.inc