🏡 index : ~doyle/aoc.git

#!/usr/bin/env nix-shell
#!nix-shell --pure -i "runghc -- -i../" -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ ])"

import Aoc (readAndParseStdin)
import qualified Data.Set as Set
import Text.Parsec (char, choice, many1, sepBy)
import Text.Parsec.String (Parser)

main = do
  input <- readAndParseStdin parseInput
  print $ part1 input
  print $ part2 input

part1 :: [[GridTile]] -> Int
part1 input = length $ traverseWithSeen input (0, 0, R)

part2 :: [[GridTile]] -> Int
part2 input =
  let maxY = length input - 1
      maxX = length (head input) - 1
      allStartingPositions = map (,0) [0 .. maxX] ++ map (0,) [0 .. maxY] ++ map (,maxY) [0 .. maxX] ++ map (maxX,) [0 .. maxY]
      allStartingPositionsWithHeadings = [(x, y, h) | (x, y) <- allStartingPositions, h <- allDirections]
   in maximum $ map (length . traverseWithSeen input) allStartingPositionsWithHeadings

traverseWithSeen :: [[GridTile]] -> (Int, Int, Heading) -> Set.Set (Int, Int)
traverseWithSeen grid (x, y, heading) = Set.map (\(x, y, heading) -> (x, y)) $ go (x, y) heading Set.empty
  where
    go :: (Int, Int) -> Heading -> Set.Set (Int, Int, Heading) -> Set.Set (Int, Int, Heading)
    go (x, y) heading acc =
      let newSet = Set.insert (x, y, heading) acc
       in if Set.member (x, y, heading) acc
            then acc
            else case getTile grid (x, y) of
              Just VertSplitter | isHorizontal heading -> go (nextPos (x, y) Up) Up $ go (nextPos (x, y) Down) Down newSet
              Just HorizSplitter | isVertical heading -> go (nextPos (x, y) L) L $ go (nextPos (x, y) R) R newSet
              Just v | v == RightSlantMirror || v == LeftSlantMirror -> go (nextPos (x, y) $ getNextDirection v heading) (getNextDirection v heading) newSet
              Just _ -> go (nextPos (x, y) heading) heading newSet
              Nothing -> acc

parseInput :: Parser [[GridTile]]
parseInput = many1 parseGridTile `sepBy` char '\n'

parseGridTile :: Parser GridTile
parseGridTile =
  choice
    [ Empty <$ char '.',
      RightSlantMirror <$ char '/',
      LeftSlantMirror <$ char '\\',
      VertSplitter <$ char '|',
      HorizSplitter <$ char '-'
    ]

data GridTile = Empty | RightSlantMirror | LeftSlantMirror | VertSplitter | HorizSplitter deriving (Enum, Show, Eq)

data Heading = Up | Down | L | R deriving (Enum, Show, Ord, Eq)

allDirections = [Up, Down, L, R]

getNextDirection :: GridTile -> Heading -> Heading
getNextDirection RightSlantMirror Up = R
getNextDirection RightSlantMirror Down = L
getNextDirection RightSlantMirror R = Up
getNextDirection RightSlantMirror L = Down
getNextDirection LeftSlantMirror Up = L
getNextDirection LeftSlantMirror Down = R
getNextDirection LeftSlantMirror R = Down
getNextDirection LeftSlantMirror L = Up

isHorizontal :: Heading -> Bool
isHorizontal L = True
isHorizontal R = True
isHorizontal _ = False

isVertical :: Heading -> Bool
isVertical = not . isHorizontal

getTile :: [[GridTile]] -> (Int, Int) -> Maybe GridTile
getTile g (x, y)
  | y >= length g || y < 0 = Nothing
  | x >= length (g !! y) || x < 0 = Nothing
  | otherwise = Just $ (g !! y) !! x

nextPos :: (Int, Int) -> Heading -> (Int, Int)
nextPos (x, y) Up = (x, y - 1)
nextPos (x, y) Down = (x, y + 1)
nextPos (x, y) L = (x - 1, y)
nextPos (x, y) R = (x + 1, y)