main.pony 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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. var buf = recover String((((max_x - min_x) + 1) * (max_y - min_y)).usize()) end
  129. for y in Range[I64](min_y, max_y + 1) do
  130. for x in Range[I64](min_x, max_x + 1) do
  131. if (y == 0) and (x == 0) then
  132. env.out.write("O")
  133. else
  134. try
  135. env.out.write(storage(Point(x, y))?.char())
  136. else
  137. env.out.write(".")
  138. end
  139. end
  140. end
  141. env.out.write("\n")
  142. end
  143. // set point `p` to include wire `l` at distance `dist` from its
  144. // origin. this will automatically record an intersection if point
  145. // `p` already includes a different wire
  146. fun ref set(p: Point, l: U8, dist: U64) =>
  147. storage.insert_if_absent(p, Wires.create())
  148. try
  149. let wire = storage(p)?
  150. match wire.add_wire(l, dist)
  151. | let n: U64 => intersections.push((n, p))
  152. | None => None
  153. end
  154. end
  155. // follow each `LinePlan` and adds the description of the wires to the grid
  156. fun ref addLines(plans: Array[LinePlan]) =>
  157. var line_number: U8 = 1
  158. for plan in plans.values() do
  159. var start_point = Point(0, 0)
  160. var dist: U64 = 1
  161. for instr in plan.plan().values() do
  162. for ps in instr.points(start_point).values() do
  163. set(ps, line_number, dist)
  164. start_point = ps
  165. dist = dist + 1
  166. end
  167. end
  168. line_number = line_number + 1
  169. end
  170. // print all the intersection values along with the point they map to.
  171. fun show_intersections(env: Env) =>
  172. for (dist, p) in intersections.values() do
  173. env.out.print(Format.int[U64](dist) + ": " + p.show())
  174. end
  175. actor Main
  176. new create(env: Env) =>
  177. let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
  178. try
  179. with file = OpenFile(FilePath(env.root as AmbientAuth, "input.txt", caps)?) as File do
  180. var line_plans = Array[LinePlan]()
  181. for line in file.lines() do
  182. try line_plans.push(LinePlan.from_str(consume line)?) end
  183. end
  184. var grid = Grid.create()
  185. grid.addLines(line_plans)
  186. grid.show_intersections(env)
  187. end
  188. else
  189. env.err.print("Couldn't read expected file `input.txt'")
  190. env.exitcode(99)
  191. end