My solutions to 2018's advent of code
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
Repozitorijs ir arhivēts. Tam var aplūkot failus un to var klonēt, bet nevar iesūtīt jaunas izmaiņas, kā arī atvērt jaunas problēmas/izmaiņu pieprasījumus.

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