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.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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 either side fills up, we
  81. # recursively call ourselves to deal with the new situation.
  82. #
  83. # Finally, in other cases the cells inbetween the extents become
  84. # flowing water and we return false.
  85. proc flowOut(grid: TableRef[Point, GroundType], point: Point): bool =
  86. let
  87. left = grid.findEdge(point, -1)
  88. right = grid.findEdge(point, 1)
  89. if left.enclosed and right.enclosed:
  90. # Both sides are enclosed, just fill'er up and make our
  91. # parent recalculate.
  92. for x in left.last..right.last:
  93. grid[(x, point.y)] = gtStillWater
  94. return true
  95. var leftFilled, rightFilled: bool
  96. if not left.enclosed:
  97. leftFilled = grid.flowDown((left.last, point.y + 1))
  98. if not right.enclosed:
  99. rightFilled = grid.flowDown((right.last, point.y + 1))
  100. if leftFilled or rightFilled:
  101. # We've filled in some holes and now our extents are no longer
  102. # valid. Recursively call ourselves to figure out what's what.
  103. return flowOut(grid, point)
  104. else:
  105. # We didn't fill anything, so this is the layer of flowing
  106. # water on top of a platform.
  107. for x in left.last..right.last:
  108. grid[(x, point.y)] = gtFlowingWater
  109. return false
  110. proc findEdge(grid: TableRef[Point, GroundType], point: Point, direction: int): tuple[enclosed: bool, last: int] =
  111. var x = point.x
  112. while grid.supportsWater((x, point.y + 1)) and not grid.isAt(gtClay, (x + direction, point.y)):
  113. x += direction
  114. result.last = x
  115. result.enclosed = grid.getOrDefault((x + direction, point.y), gtSand) == gtClay
  116. for line in readFile("data/17.txt").strip.splitlines:
  117. let
  118. parts = line.split(", ")
  119. first = parts[0].substr(2).parseInt
  120. numbers = parts[1].substr(2).split("..")
  121. isX = parts[1][0] == 'x'
  122. for i in numbers[0].parseInt..numbers[1].parseInt:
  123. var x,y: int
  124. if isX:
  125. x = i
  126. y = first
  127. else:
  128. x = first
  129. y = i
  130. grid[(x, y)] = gtClay
  131. miny = min(miny, y)
  132. maxy = max(maxy, y)
  133. discard grid.flowDown((500, miny))
  134. # grid.print()
  135. let result = grid.countWater
  136. echo result.all
  137. echo result.still