module Day20 import Prelude import Node import Aoc import SortedMap gridPoints : String → List (Char × Int × Int) gridPoints text = go 0 0 (unpack text) Nil where -- might as well be tail recursive go : Int → Int → List Char → List (Char × Int × Int) → List (Char × Int × Int) go row col Nil points = points go row col ('\n' :: cs) points = go (row + 1) 0 cs points go row col (c :: cs) points = go row (col + 1) cs ((c,row,col) :: points) State Grid Next Cheat : U Grid = SortedMap Point Char State = SortedMap Point Int Next = (Point × Int) Cheat = (Point × Point) data Dir : U where North East South West : Dir dirs : List Dir dirs = (North :: South :: East :: West :: Nil) move : Point → Dir → Point move (r, c) North = (r - 1, c) move (r, c) East = (r, c + 1) move (r, c) South = (r + 1, c) move (r, c) West = (r, c - 1) distances : SortedMap Point Char → Point → State distances grid start = go EmptyMap ((start,0) :: Nil) Lin where valid : State → Point → Bool valid st pt = case lookupMap pt grid of Just (_, '.') => case lookupMap pt st of Just _ => False Nothing => True _ => False go : State → List (Point × Int) → SnocList (Point × Int) → State go st Nil Lin = st go st xs Lin = go st Nil (Lin <>< xs) go st xs (ys :< (pt, l)) = let next = map (flip _,_ $ l + 1) $ filter (valid st) $ map (move pt) dirs st = foldMap const st next in go st (next ++ xs) ys manh : Point → Point → Int manh (r,c) (r',c') = let dr = if r < r' then r' - r else r - r' dc = if c < c' then c' - c else c - c' in dr + dc candidates : Point → Int → List Point candidates pt@(sr,sc) dist = go (sr - dist) (sc - dist) Nil where go : Int → Int → List Point → List Point go r' c' acc = if sr + dist < r' then acc else if sc + dist < c' then go (r' + 1) (sc - dist) acc else if manh pt (r',c') <= dist then go r' (c' + 1) ((r', c') :: acc) else go r' (c' + 1) acc calcCheats : State → State → Int → Int → Int calcCheats start dists fuel threshold = go (toList start) 0 where -- only '.' have dist tryCand : Int → Point → Point → Int tryCand l pt cand = case lookupMap cand dists of Just (_, dist) => if l + manh pt cand + dist <= threshold then 1 else 0 Nothing => 0 go : List (Point × Int) → Int → Int go Nil acc = acc go (x@(pt,l) :: xs) acc = let acc = foldl _+_ acc $ map (tryCand l pt) $ candidates pt fuel in go xs acc run : String → Int → IO Unit run fn time = do putStrLn fn text <- readFile fn let pts = map swap $ gridPoints text let grid = foldMap const EmptyMap pts let (Just (start,_)) = find (_==_ 'S' ∘ snd) pts | _ => putStrLn "no start" let (Just (end,_)) = find (_==_ 'E' ∘ snd) pts | _ => putStrLn "no end" let grid' = updateMap start '.' $ updateMap end '.' grid let dists = distances grid' end -- this bit threw me off for a while let dists = updateMap end 0 dists let dists' = distances grid' start let dists' = updateMap start 0 dists' let (Just (_,base)) = lookupMap start dists | _ => putStrLn "no start in dists" let p1 = calcCheats dists dists' 2 (base - time) putStrLn $ "base " ++ show base putStrLn $ "part1 " ++ show p1 let p2 = calcCheats dists dists' 20 (base - time) putStrLn $ "part2 " ++ show p2 pure MkUnit main : IO Unit main = do run "aoc2024/day20/eg.txt" 50 run "aoc2024/day20/input.txt" 100