128 lines
4.5 KiB
Haskell
Raw Normal View History

2025-12-04 10:00:15 +00:00
module Puzzles.Day4 (puzzle) where
2025-12-08 12:48:49 +00:00
import Pre
2025-12-04 12:23:48 +00:00
import Data.Sequence qualified as Seq
2025-12-05 12:09:49 +00:00
import Data.Stream.Infinite qualified as S
import Data.Text qualified as T
import Data.Text.IO qualified as T
2025-12-04 10:00:15 +00:00
puzzle :: Puzzle
puzzle =
Puzzle
{ number = 4
, parser = const $ (some $ asum $ enumerate <&> \t -> char (inToChar t) $> t) `sepEndBy` newline
2025-12-04 10:00:15 +00:00
, parts =
( (\g -> countRolls g - countRolls (removeAccessibleRolls $ findAccessible g))
2025-12-05 12:09:49 +00:00
. mkGrid
)
/\ ( (id &&& generateFrames) . mkGrid
, \(g, fs) ->
T.show $ countRolls g - countRolls (fst $ S.head $ S.filter (noneAccessible . snd) fs)
)
/\\ nil
2026-01-05 14:37:25 +00:00
, extraTests = \isRealData path ->
[ test
2026-01-05 14:37:25 +00:00
"round trip"
( \(input, _) -> do
t <- liftIO $ T.readFile if isRealData then "../inputs/real/4" else "../inputs/examples/4"
2026-01-05 14:37:25 +00:00
assertEqual t $ drawGrid (mkGrid input <&> \case InEmpty -> OutEmpty; InRoll -> OutRoll)
)
[]
, test
2026-01-05 14:37:25 +00:00
"frames"
( \(_, (HCons _ (HCons (_, fmap snd -> frameStream) HNil))) ->
2026-01-05 14:37:25 +00:00
pure $ Seq.fromList $ takeUntil noneAccessible frameStream
)
let nFrames = if isRealData then 58 else 9
lookupFrame n frames =
maybe (assertFailure $ "frame list index not found: " <> T.show n) pure $
Seq.lookup n frames
2026-01-05 14:37:25 +00:00
in map
( \n ->
test
2026-01-05 14:37:25 +00:00
(mkTestName $ show n)
( \frames -> do
g <- lookupFrame n frames
golden (path <> "frames/" <> show n) $ drawGrid g
2026-01-05 14:37:25 +00:00
)
[]
)
[0 .. nFrames]
<> [ test
2026-01-05 14:37:25 +00:00
"end"
( \frames -> do
assertEqual (nFrames + 1) (Seq.length frames)
g <- lookupFrame nFrames frames
2026-01-05 14:37:25 +00:00
assert "accessible tile found" $ noneAccessible g
)
[]
]
]
2025-12-04 10:00:15 +00:00
}
2025-12-04 12:23:48 +00:00
2025-12-05 12:09:49 +00:00
newtype Grid a = Grid (Seq (Seq (V2 Int, a)))
deriving (Functor, Show)
deriving newtype (NFData)
2025-12-04 20:00:53 +00:00
2025-12-04 12:23:48 +00:00
data InTile
= InEmpty
| InRoll
deriving (Eq, Ord, Show, Enum, Bounded, Generic, NFData)
2025-12-04 12:23:48 +00:00
inToChar :: InTile -> Char
inToChar = \case
InEmpty -> '.'
InRoll -> '@'
data OutTile
= OutEmpty
| OutRoll
| OutAccessible
deriving (Eq, Ord, Show, Enum, Bounded, Generic, NFData)
2025-12-04 12:23:48 +00:00
outToChar :: OutTile -> Char
outToChar = \case
2025-12-05 12:09:49 +00:00
OutEmpty -> inToChar InEmpty
OutRoll -> inToChar InRoll
2025-12-04 12:23:48 +00:00
OutAccessible -> 'x'
drawGrid :: Grid OutTile -> T.Text
drawGrid (Grid g) = T.unlines . toList . fmap (T.pack . toList . fmap outToChar) $ snd <<$>> g
2025-12-05 12:09:49 +00:00
mkGrid :: [[a]] -> Grid a
mkGrid = Grid . Seq.fromList . map Seq.fromList . zipWith (map . first . V2) [0 ..] . map (zip [0 ..])
findAccessible :: Grid InTile -> Grid OutTile
findAccessible (Grid inGrid) =
Grid $
inGrid <<&>> \(v, t) -> (v,) case t of
InEmpty -> OutEmpty
InRoll ->
if length (filter ((== Just InRoll) . fmap snd) neighbours) < 4
then OutAccessible
else OutRoll
where
neighbours = do
x <- [-1 .. 1]
y <- [-1 .. 1]
guard $ not (x == 0 && y == 0)
let V2 x' y' = v + V2 x y
pure $ Seq.lookup x' inGrid >>= Seq.lookup y'
removeAccessibleRolls :: Grid OutTile -> Grid InTile
removeAccessibleRolls = fmap \case
2025-12-04 20:00:53 +00:00
OutEmpty -> InEmpty
OutRoll -> InRoll
OutAccessible -> InEmpty
2025-12-05 12:09:49 +00:00
generateFrames :: Grid InTile -> Stream (Grid InTile, Grid OutTile)
generateFrames = unfoldMutual findAccessible removeAccessibleRolls
noneAccessible :: Grid OutTile -> Bool
noneAccessible (Grid g) = not $ any (elem OutAccessible . fmap snd) g
countRolls :: Grid InTile -> Int
countRolls (Grid g) = length $ concatMap (filter (== InRoll) . toList . fmap snd) g
unfoldMutual :: (a -> b) -> (b -> a) -> a -> Stream (a, b)
unfoldMutual f g a = let b = f a in (a, b) :> unfoldMutual f g (g b)