My solutions to 2018's advent of code
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
Ce dépôt est archivé. Vous pouvez voir les fichiers et le cloner, mais vous ne pouvez pas pousser ni ouvrir de ticket/demande d'ajout.

day17.nim 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import sequtils, strformat, strutils, tables
  2. type
  3. Point = tuple[x,y: int]
  4. GroundType = enum
  5. gtSand, gtClay, gtStillWater, gtFlowingWater
  6. var
  7. grid = newTable[Point, GroundType]()
  8. miny = int.high
  9. maxy = int.low
  10. func down(point: Point): Point =
  11. (point.x, point.y + 1)
  12. func isAt(grid: TableRef[Point, GroundType], gtype: GroundType, point: Point): bool =
  13. grid.getOrDefault(point, gtSand) == gtype
  14. func supportsWater(grid: TableRef[Point, GroundType], point: Point): bool =
  15. let ground = grid.getOrDefault(point, gtSand)
  16. ground == gtClay or ground == gtStillWater
  17. func countWater(grid: TableRef[Point, GroundType]): tuple[all,still: int] =
  18. for val in grid.values:
  19. if val == gtFlowingWater:
  20. result.all.inc
  21. if val == gtStillWater:
  22. result.all.inc
  23. result.still.inc
  24. # Debug utility to print the grid nicely. Requires a small terminal font size.
  25. proc print(grid: TableRef[Point, GroundType]) =
  26. for y in miny-1..maxy+1:
  27. var line = fmt "{y:4} "
  28. for x in 300..700:
  29. case grid.getOrDefault((x, y), gtSand):
  30. of gtSand: line &= " "
  31. of gtClay: line &= "█"
  32. of gtStillWater: line &= "~"
  33. of gtFlowingWater: line &= "|"
  34. echo line
  35. echo ""
  36. # These all call each other so we have to forward declare them
  37. proc flowDown(grid: TableRef[Point, GroundType], point: Point): bool
  38. proc flowOut(grid: TableRef[Point, GroundType], point: Point): bool
  39. proc findEdge(grid: TableRef[Point, GroundType], point: Point, direction: int): tuple[enclosed: bool, last: int]
  40. # flowDown() is responsible for recursively following a stream of water
  41. # downwards until it hits something or surpasses the maximum y extent.
  42. #
  43. # The proc returns `true` if the downward flow has spread out (e.g.
  44. # has filled in the bottom row of a "U" shaped area). If this happens
  45. # the caller will also attempt to flow out to fill the next row.
  46. proc flowDown(grid: TableRef[Point, GroundType], point: Point): bool =
  47. let
  48. next = point.down
  49. cell = grid.getOrDefault(next, gtSand)
  50. if point.y > maxy:
  51. return false
  52. if cell == gtSand:
  53. # There's nothing below us, keep flowing. Check to see if the flow
  54. # below is fills out, and if it does flow out on this row too;
  55. # otherwise, this is just a normal downward flow and we return false
  56. # as our parents don't need to flow out.
  57. if grid.flowDown(next):
  58. return grid.flowOut(point)
  59. else:
  60. grid[point] = gtFlowingWater
  61. return false
  62. elif cell == gtClay or cell == gtStillWater:
  63. # We've reached the bottom of a hole (or the existing water
  64. # level within a complex shape), flow outwards
  65. return grid.flowOut(point)
  66. elif cell == gtFlowingWater:
  67. # We've encountered some flowing water (i.e. we have a parallel
  68. # stream that has already flowed out below us). There's no need
  69. # to recheck anything, just join up with the other flow.
  70. grid[point] = gtFlowingWater
  71. return false
  72. # flowOut() is responsible for expanding a flow of water horizontally
  73. # after it hits something. It scans left and right to find the maximum
  74. # extents, and whether or not they're enclosed.
  75. #
  76. # If both sides are enclosed then all the cells inbetween fill with
  77. # still water, and we return true so that the row above us flows out.
  78. #
  79. # Sides that aren't enclosed are flowed down. In complex shapes these
  80. # may fill up areas below them. Where both sides fill up, or one side
  81. # is enclosed and the other fills up, we recursively call ourselves
  82. # to deal with the new situation.
  83. #
  84. # Finally, in other cases the cells inbetween the extents become
  85. # flowing water and we return false.
  86. proc flowOut(grid: TableRef[Point, GroundType], point: Point): bool =
  87. let
  88. left = grid.findEdge(point, -1)
  89. right = grid.findEdge(point, 1)
  90. if left.enclosed and right.enclosed:
  91. # Both sides are enclosed, just fill'er up and make our
  92. # parent recalculate.
  93. for x in left.last..right.last:
  94. grid[(x, point.y)] = gtStillWater
  95. return true
  96. var leftFilled, rightFilled: bool
  97. if not left.enclosed:
  98. leftFilled = grid.flowDown((left.last, point.y + 1))
  99. if not right.enclosed:
  100. rightFilled = grid.flowDown((right.last, point.y + 1))
  101. if (left.enclosed or leftFilled) and (right.enclosed or rightFilled):
  102. # We've filled in some holes and now our extents are no longer
  103. # valid. Recursively call ourselves to figure out what's what.
  104. return flowOut(grid, point)
  105. else:
  106. # We didn't fill anything, so this is the layer of flowing
  107. # water on top of a platform.
  108. for x in left.last..right.last:
  109. grid[(x, point.y)] = gtFlowingWater
  110. return false
  111. proc findEdge(grid: TableRef[Point, GroundType], point: Point, direction: int): tuple[enclosed: bool, last: int] =
  112. var x = point.x
  113. while grid.supportsWater((x, point.y + 1)) and not grid.isAt(gtClay, (x + direction, point.y)):
  114. x += direction
  115. result.last = x
  116. result.enclosed = grid.getOrDefault((x + direction, point.y), gtSand) == gtClay
  117. for line in readFile("data/17.txt").strip.splitlines:
  118. let
  119. parts = line.split(", ")
  120. first = parts[0].substr(2).parseInt
  121. numbers = parts[1].substr(2).split("..")
  122. isX = parts[1][0] == 'x'
  123. for i in numbers[0].parseInt..numbers[1].parseInt:
  124. var x,y: int
  125. if isX:
  126. x = i
  127. y = first
  128. else:
  129. x = first
  130. y = i
  131. grid[(x, y)] = gtClay
  132. miny = min(miny, y)
  133. maxy = max(maxy, y)
  134. discard grid.flowDown((500, miny))
  135. # grid.print()
  136. let result = grid.countWater
  137. echo result.all
  138. echo result.still