main.pony 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. use "collections"
  2. use "files"
  3. use "format"
  4. // The cardinal directions
  5. primitive U
  6. primitive D
  7. primitive L
  8. primitive R
  9. type Dir is (U | D | L | R)
  10. // A basic two-dimensional point class
  11. class val Point is Hashable
  12. let _x: I64
  13. let _y: I64
  14. new val create(__x: I64, __y: I64) =>
  15. _x = __x
  16. _y = __y
  17. fun x(): I64 => _x
  18. fun y(): I64 => _y
  19. // because we want to use this as a key in a hashmap
  20. fun hash(): USize =>
  21. // hey this is probably bad, huh?
  22. _x.hash() + _y.hash()
  23. fun eq(other: Point): Bool =>
  24. (x() == other.x()) and (y() == other.y())
  25. fun ne(other: Point): Bool => not eq(other)
  26. // return the point that is `amt` squares awy from this one in `d` direction
  27. fun in_direction(d: Dir, amt: I64): Point =>
  28. match d
  29. | U => Point(_x, _y-amt)
  30. | D => Point(_x, _y+amt)
  31. | L => Point(_x-amt, _y)
  32. | R => Point(_x+amt, _y)
  33. end
  34. // print prettily
  35. fun show(): String =>
  36. "(" + Format.int[I64](_x) + ", " + Format.int[I64](_y) + ")"
  37. // An 'instruction': a direction and a distance to travel in that
  38. // direction
  39. class val Instr
  40. let _dir: Dir
  41. let _amt: I64
  42. // parse from the string repr
  43. new val from_str(s: String) ? =>
  44. _dir = match s(0)?
  45. | 'U' => U
  46. | 'D' => D
  47. | 'L' => L
  48. | 'R' => R
  49. else error
  50. end
  51. _amt = s.trim(1).i64()?
  52. // given a starting point, return the array of all the points that
  53. // would be covered by this instruction
  54. fun points(start: Point): Array[Point] =>
  55. var p = Array[Point]()
  56. for i in Range[I64](0, _amt) do
  57. p.push(start.in_direction(_dir, i+1))
  58. end
  59. p
  60. // A representation of the 'plan' for a line: nothing more than a
  61. //sequence of 'Instr's
  62. class val LinePlan
  63. let _plan: Array[Instr]
  64. new val from_str(s: String) ? =>
  65. var p = Array[Instr]()
  66. for chunk in s.split(",").values() do
  67. p.push(Instr.from_str(chunk)?)
  68. end
  69. _plan = p
  70. // this just gives the rest of the world access to our plan
  71. // mwahahahaha
  72. fun plan(): this->Array[Instr] => _plan
  73. // The value we stores in the map: a list of which wires exist at that
  74. //grid square as well as their distance along the wire
  75. class Wires
  76. let wires: Array[(U8, U64)]
  77. // we always create this with no wires initialized
  78. new create() => wires = Array[(U8, U64)]()
  79. // this adds a new wire, and if a different wire already exists at
  80. // this spot then return the combined distance from their respective
  81. // roots, otherwise return None
  82. fun ref add_wire(wire: U8, dist: U64): (U64 | None) =>
  83. wires.push((wire, dist))
  84. var min_dist: (U64 | None) = None
  85. for (w, d) in wires.values() do
  86. if wire != w then
  87. let new_dist = d + dist
  88. match min_dist
  89. | None => min_dist = new_dist
  90. | let old_min: U64 if new_dist < old_min => min_dist = new_dist
  91. end
  92. end
  93. end
  94. min_dist
  95. // for pretty-printing, return a character represents this specific
  96. //wire, using '*' for intersections
  97. fun char(): String =>
  98. var s = Set[U8]()
  99. for (w, _) in wires.values() do
  100. s.set(w)
  101. end
  102. if s.size() > 1 then "*" else
  103. try Format.int[U8](s.values().next()?) else "?" end
  104. end
  105. // the actual grid type that contains our grid logic
  106. class Grid
  107. // a map from point to wire content. we could use a 2d array or
  108. //something, but 1. this does not need to grow when we go off the
  109. //'sides' and 2. it is sparse
  110. var storage: Map[Point, Wires]
  111. // this lists the intersections, keyed by their distance
  112. var intersections: Array[(U64, Point)]
  113. new create() =>
  114. storage = Map[Point, Wires]()
  115. intersections = Array[(U64, Point)]()
  116. // print prettily
  117. fun show(env: Env) =>
  118. var min_x: I64 = 0
  119. var min_y: I64 = 0
  120. var max_x: I64 = 0
  121. var max_y: I64 = 0
  122. for p in storage.keys() do
  123. min_x = p.x().min(min_x)
  124. min_y = p.y().min(min_y)
  125. max_x = p.x().max(max_x)
  126. max_y = p.y().max(max_y)
  127. end
  128. for y in Range[I64](min_y, max_y + 1) do
  129. for x in Range[I64](min_x, max_x + 1) do
  130. if (y == 0) and (x == 0) then
  131. env.out.write("O")
  132. else
  133. try
  134. env.out.write(storage(Point(x, y))?.char())
  135. else
  136. env.out.write(".")
  137. end
  138. end
  139. end
  140. env.out.write("\n")
  141. end
  142. // set point `p` to include wire `l` at distance `dist` from its
  143. // origin. this will automatically record an intersection if point
  144. // `p` already includes a different wire
  145. fun ref set(p: Point, l: U8, dist: U64) =>
  146. storage.insert_if_absent(p, Wires.create())
  147. try
  148. let wire = storage(p)?
  149. match wire.add_wire(l, dist)
  150. | let n: U64 => intersections.push((n, p))
  151. | None => None
  152. end
  153. end
  154. // follow each `LinePlan` and adds the description of the wires to the grid
  155. fun ref addLines(plans: Array[LinePlan]) =>
  156. var line_number: U8 = 1
  157. for plan in plans.values() do
  158. var start_point = Point(0, 0)
  159. var dist: U64 = 1
  160. for instr in plan.plan().values() do
  161. for ps in instr.points(start_point).values() do
  162. set(ps, line_number, dist)
  163. start_point = ps
  164. dist = dist + 1
  165. end
  166. end
  167. line_number = line_number + 1
  168. end
  169. // print all the intersection values along with the point they map to.
  170. fun show_intersections(env: Env) =>
  171. for (dist, p) in intersections.values() do
  172. env.out.print(Format.int[U64](dist) + ": " + p.show())
  173. end
  174. actor Main
  175. new create(env: Env) =>
  176. let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
  177. try
  178. with file = OpenFile(FilePath(env.root as AmbientAuth, "input.txt", caps)?) as File do
  179. var line_plans = Array[LinePlan]()
  180. for line in file.lines() do
  181. try line_plans.push(LinePlan.from_str(consume line)?) end
  182. end
  183. var grid = Grid.create()
  184. grid.addLines(line_plans)
  185. grid.show_intersections(env)
  186. end
  187. else
  188. env.err.print("Couldn't read expected file `input.txt'")
  189. env.exitcode(99)
  190. end