| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 | 
							- use "collections"
 
- use "files"
 
- use "format"
 
- use "itertools"
 
- // The cardinal directions
 
- primitive U
 
- primitive D
 
- primitive L
 
- primitive R
 
- type Dir is (U | D | L | R)
 
- // A basic two-dimensional point class
 
- class val Point is Hashable
 
-   let _x: I64
 
-   let _y: I64
 
-   new val create(__x: I64, __y: I64) =>
 
-     _x = __x
 
-     _y = __y
 
-   fun x(): I64 => _x
 
-   fun y(): I64 => _y
 
-   // because we want to use this as a key in a hashmap
 
-   fun hash(): USize =>
 
-     // hey this is probably bad, huh?
 
-     _x.hash() + _y.hash()
 
-   fun eq(other: Point): Bool =>
 
-     (x() == other.x()) and (y() == other.y())
 
-   fun ne(other: Point): Bool => not eq(other)
 
-   // return the point that is `amt` squares awy from this one in `d` direction
 
-   fun in_direction(d: Dir, amt: I64): Point =>
 
-     match d
 
-       | U => Point(_x, _y-amt)
 
-       | D => Point(_x, _y+amt)
 
-       | L => Point(_x-amt, _y)
 
-       | R => Point(_x+amt, _y)
 
-     end
 
-   // print prettily
 
-   fun show(): String =>
 
-     "(" + Format.int[I64](_x) + ", " + Format.int[I64](_y) + ")"
 
- // An 'instruction': a direction and a distance to travel in that
 
- // direction
 
- class val Instr
 
-   let _dir: Dir
 
-   let _amt: I64
 
-   // parse from the string repr
 
-   new val from_str(s: String) ? =>
 
-     _dir = match s(0)?
 
-       | 'U' => U
 
-       | 'D' => D
 
-       | 'L' => L
 
-       | 'R' => R
 
-     else error
 
-     end
 
-     _amt = s.trim(1).i64()?
 
-   // given a starting point, return the array of all the points that
 
-   // would be covered by this instruction
 
-   fun points(start: Point): Array[Point] =>
 
-     var p = Array[Point]()
 
-     for i in Range[I64](0, _amt) do
 
-       p.push(start.in_direction(_dir, i+1))
 
-     end
 
-     p
 
- // A representation of the 'plan' for a line: nothing more than a
 
- //sequence of 'Instr's
 
- class val LinePlan
 
-   let _plan: Array[Instr]
 
-   new val from_str(s: String) ? =>
 
-     var p = Array[Instr]()
 
-     for chunk in s.split(",").values() do
 
-       p.push(Instr.from_str(chunk)?)
 
-     end
 
-     _plan = p
 
-   // this just gives the rest of the world access to our plan
 
-   // mwahahahaha
 
-   fun plan(): this->Array[Instr] => _plan
 
- // The value we stores in the map: a list of which wires exist at that
 
- //grid square as well as their distance along the wire
 
- class Wires
 
-   let wires: Array[(U8, U64)]
 
-   // we always create this with no wires initialized
 
-   new create() => wires = Array[(U8, U64)]()
 
-   // this adds a new wire, and if a different wire already exists at
 
-   // this spot then return the combined distance from their respective
 
-   // roots, otherwise return None
 
-   fun ref add_wire(wire: U8, dist: U64): (U64 | None) =>
 
-     wires.push((wire, dist))
 
-     var min_dist: (U64 | None) = None
 
-     for (w, d) in wires.values() do
 
-       if wire != w then
 
-         let new_dist = d + dist
 
-         match min_dist
 
-           | None => min_dist = new_dist
 
-           | let old_min: U64 if new_dist < old_min => min_dist = new_dist
 
-         end
 
-       end
 
-     end
 
-     min_dist
 
-   // for pretty-printing, return a character represents this specific
 
-   //wire, using '*' for intersections
 
-   fun char(): String =>
 
-     var s = Set[U8]()
 
-     for (w, _) in wires.values() do
 
-       s.set(w)
 
-     end
 
-     if s.size() > 1 then "*" else
 
-       try Format.int[U8](s.values().next()?) else "?" end
 
-     end
 
- // the actual grid type that contains our grid logic
 
- class Grid
 
-   // a map from point to wire content. we could use a 2d array or
 
-   //something, but 1. this does not need to grow when we go off the
 
-   //'sides' and 2. it is sparse
 
-   var storage: Map[Point, Wires]
 
-   // this lists the intersections, keyed by their distance
 
-   var intersections: Array[(U64, Point)]
 
-   new create() =>
 
-     storage = Map[Point, Wires]()
 
-     intersections = Array[(U64, Point)]()
 
-   // print prettily
 
-   fun show(env: Env) =>
 
-     var min_x: I64 = 0
 
-     var min_y: I64 = 0
 
-     var max_x: I64 = 0
 
-     var max_y: I64 = 0
 
-     for p in storage.keys() do
 
-       min_x = p.x().min(min_x)
 
-       min_y = p.y().min(min_y)
 
-       max_x = p.x().max(max_x)
 
-       max_y = p.y().max(max_y)
 
-     end
 
-     for y in Range[I64](min_y, max_y + 1) do
 
-       for x in Range[I64](min_x, max_x + 1) do
 
-         if (y == 0) and (x == 0) then
 
-           env.out.write("O")
 
-         else
 
-           try
 
-             env.out.write(storage(Point(x, y))?.char())
 
-           else
 
-             env.out.write(".")
 
-           end
 
-         end
 
-       end
 
-       env.out.write("\n")
 
-     end
 
-   // set point `p` to include wire `l` at distance `dist` from its
 
-   // origin. this will automatically record an intersection if point
 
-   // `p` already includes a different wire
 
-   fun ref set(p: Point, l: U8, dist: U64) =>
 
-     storage.insert_if_absent(p, Wires.create())
 
-     try
 
-       let wire = storage(p)?
 
-       match wire.add_wire(l, dist)
 
-         | let n: U64 => intersections.push((n, p))
 
-         | None => None
 
-       end
 
-     end
 
-   // follow each `LinePlan` and adds the description of the wires to the grid
 
-   fun ref addLines(plans: Array[LinePlan]) =>
 
-     for (line_number, plan) in Iter[LinePlan](plans.values()).enum[U8]() do
 
-       var start_point = Point(0, 0)
 
-       var dist: U64 = 1
 
-       for instr in plan.plan().values() do
 
-         for ps in instr.points(start_point).values() do
 
-           set(ps, line_number, dist)
 
-           start_point = ps
 
-           dist = dist + 1
 
-         end
 
-       end
 
-     end
 
-   // print all the intersection values along with the point they map to.
 
-   fun show_intersections(env: Env) =>
 
-     for (dist, p) in intersections.values() do
 
-       env.out.print(Format.int[U64](dist) + ": " + p.show())
 
-     end
 
- actor Main
 
-   new create(env: Env) =>
 
-     let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
 
-     try
 
-       with file = OpenFile(FilePath(env.root as AmbientAuth, "input.txt", caps)?) as File do
 
-         var line_plans = Array[LinePlan]()
 
-         for line in file.lines() do
 
-           try line_plans.push(LinePlan.from_str(consume line)?) end
 
-         end
 
-         var grid = Grid.create()
 
-         grid.addLines(line_plans)
 
-         grid.show_intersections(env)
 
-       end
 
-     else
 
-       env.err.print("Couldn't read expected file `input.txt'")
 
-       env.exitcode(99)
 
-     end
 
 
  |