

Haskell
Hmm. I’m still not very happy with part 3: it’s a bit slow and messy. Doing state over the list monad for memoization doesn’t work well, so I’m enumerating all possible configurations first and taking advantage of laziness.
import Control.Monad
import Data.Bifunctor
import Data.Ix
import Data.List
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Maybe
import Data.Set.Monad (Set)
import Data.Set.Monad qualified as Set
import Data.Tuple
type Pos = (Int, Int)
readInput :: String -> ((Pos, Pos), Pos, Set Pos, Set Pos)
readInput s =
let grid =
Map.fromList
[ ((i, j), c)
| (i, cs) <- zip [0 ..] $ lines s,
(j, c) <- zip [0 ..] cs
]
in ( ((0, 0), fst $ Map.findMax grid),
fst $ fromJust $ find ((== 'D') . snd) $ Map.assocs grid,
Set.fromList $ Map.keys (Map.filter (== 'S') grid),
Set.fromList $ Map.keys (Map.filter (== '#') grid)
)
moveDragon (i, j) = Set.mapMonotonic (bimap (+ i) (+ j)) offsets
where
offsets = Set.fromList ([id, swap] <*> ((,) <$> [-1, 1] <*> [-2, 2]))
dragonMoves bounds =
iterate (Set.filter (inRange bounds) . (>>= moveDragon)) . Set.singleton
part1 n (bounds, start, sheep, _) =
(!! n)
. map (Set.size . Set.intersection sheep)
. scanl1 Set.union
$ dragonMoves bounds start
part2 n (bounds, dragonStart, sheepStart, hideouts) =
(!! n)
. map ((Set.size sheepStart -) . Set.size)
. scanl'
( \sheep eaten ->
(Set.\\ eaten)
. Set.mapMonotonic (first (+ 1))
. (Set.\\ eaten)
$ sheep
)
sheepStart
. map (Set.\\ hideouts)
$ (tail $ dragonMoves bounds dragonStart)
part3 (bounds, dragonStart, sheepStart, hideouts) =
count (dragonStart, sheepStart)
where
sheepStartByColumn = Map.fromList $ map swap $ Set.elems sheepStart
sheepConfigs =
map
( (Set.fromList . catMaybes)
. zipWith (\j -> fmap (,j)) (Map.keys sheepStartByColumn)
)
. mapM
( ((Nothing :) . map Just)
. (`enumFromTo` (fst $ snd bounds))
)
$ Map.elems sheepStartByColumn
count =
((Map.!) . Map.fromList . map ((,) <*> go))
((,) <$> range bounds <*> sheepConfigs)
go (dragon, sheep)
| null sheep = 1
| otherwise =
(sum . map count) $ do
let movableSheep =
filter (\(_, p) -> p /= dragon || Set.member p hideouts) $
map (\(i, j) -> ((i, j), (i + 1, j))) $
Set.elems sheep
sheepMoves =
if null movableSheep
then [sheep]
else do
(p1, p2) <- movableSheep
return $ Set.insert p2 $ Set.delete p1 sheep
sheep' <- sheepMoves
guard $ all (inRange bounds) sheep'
dragon' <- Set.elems $ moveDragon dragon
guard $ inRange bounds dragon'
let eaten = Set.singleton dragon' Set.\\ hideouts
return (dragon', sheep' Set.\\ eaten)
main = do
readFile "everybody_codes_e2025_q10_p1.txt" >>= print . part1 4 . readInput
readFile "everybody_codes_e2025_q10_p2.txt" >>= print . part2 20 . readInput
readFile "everybody_codes_e2025_q10_p3.txt" >>= print . part3 . readInput





Good luck to you. I sincerely hope it goes better than you imagine.
Although I suspect and hope your friends will be accepting. Best wishes!