Rope Bridge
Description: https://adventofcode.com/2022/day/9
open System
open System.IO
type Index(x: int, y: int) =
member _.X = x
member _.Y = y
member this.Add(index: Index) =
new Index(this.X + index.X, this.Y + index.Y)
override this.ToString() = $"{this.X} {this.Y}"
override this.Equals(index: obj) =
match index with
| :? Index as x -> this.X = x.X && this.Y = x.Y
| _ -> false
override this.GetHashCode() = HashCode.Combine(this.X, this.Y)
type Motion =
| Left of int
| Right of int
| Up of int
| Down of int
static member parse(s: string) : Motion =
let parts = s.Split ' '
let direction = parts[0]
let distance = parts[1] |> int
match direction with
| "L" -> Left distance
| "R" -> Right distance
| "D" -> Down distance
| "U" -> Up distance
| x -> failwith $"Invalid option {x}"
type Bridge(?hasNext: int) =
let mutable headPosition = new Index(0, 0)
let mutable tailPosition = new Index(0, 0)
let mutable tailVisited: Index[] = [| tailPosition |]
let next =
let knots = defaultArg hasNext 0
if knots > 0 then
Some(new Bridge(hasNext = (knots - 1)))
else
None
let notTouching () : bool =
pown (tailPosition.X - headPosition.X) 2
+ pown (tailPosition.Y - headPosition.Y) 2 > 2
let tailMove () : Index =
let dirY = headPosition.Y - tailPosition.Y
let dirX = headPosition.X - tailPosition.X
let atan = Math.Atan2(dirY, dirX)
let angle = (atan * (180.0 / Math.PI) + 360.0) % 360.0
match angle with
| 0.0 -> new Index(1, 0)
| x when x > 0 && x < 90 -> new Index(1, 1)
| 90.0 -> new Index(0, 1)
| x when x > 90 && x < 180 -> new Index(-1, 1)
| 180.0 -> new Index(-1, 0)
| x when x > 180 && x < 270 -> new Index(-1, -1)
| 270.0 -> new Index(0, -1)
| x when x > 270 && x < 360 -> new Index(1, -1)
| x -> failwith $"Unknown angle {x}"
member _.TailVisited: Index[] =
match next with
| Some n -> n.TailVisited
| None -> tailVisited
member this.TailVisitedCount = this.TailVisited |> Array.distinct |> Array.length
member this.PrintBridge() =
let minX = this.TailVisited |> Array.minBy (fun x -> x.X)
let maxX = this.TailVisited |> Array.maxBy (fun x -> x.X)
let minY = this.TailVisited |> Array.minBy (fun x -> x.Y)
let maxY = this.TailVisited |> Array.maxBy (fun x -> x.Y)
for y in maxY.Y .. -1 .. minY.Y do
for x in minX.X .. maxX.X do
let contains = Array.contains (new Index(x, y)) this.TailVisited
printf "%c" (if contains then '#' else '.')
printfn ""
member _.MoveHead(index: Index) =
headPosition <- headPosition.Add(index)
if notTouching () then
let move = tailMove ()
tailPosition <- tailPosition.Add(move)
tailVisited <- Array.append tailVisited [| tailPosition |]
match next with
| Some b -> b.MoveHead(move)
| None -> ()
member this.Move(motion: Motion) =
match motion with
| Left x -> x, new Index(-1, 0)
| Right x -> x, new Index(1, 0)
| Up x -> x, new Index(0, 1)
| Down x -> x, new Index(0, -1)
|> (fun (count, direction) ->
for _ in 1..count do
this.MoveHead(direction))
type Input() =
member _.Compute(part: int) =
use stream = new StreamReader "./input.txt"
let mutable endOfFile = false
let bridge = new Bridge(hasNext = if part <> 1 then 8 else 0)
while (not endOfFile) do
let line = stream.ReadLine()
if (line = null) then
endOfFile <- true
else
Motion.parse line |> bridge.Move
bridge.PrintBridge()
bridge.TailVisitedCount
let input = Input()
let part = (Environment.GetCommandLineArgs()[1]) |> int
printfn "%d" (input.Compute(part))