2023-12-14 13:46:32 -05:00
|
|
|
|
{-# options_ghc -Wall #-}
|
2023-12-19 20:08:54 -05:00
|
|
|
|
{-# language BlockArguments, LambdaCase #-}
|
2023-12-14 13:46:32 -05:00
|
|
|
|
import Data.List
|
|
|
|
|
import System.Environment
|
|
|
|
|
import qualified Data.Map as M
|
|
|
|
|
import Data.Maybe
|
2023-12-19 20:08:54 -05:00
|
|
|
|
import qualified Data.Array as A
|
2023-12-14 13:46:32 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data Block = Rock | Space | Wall deriving (Eq, Ord, Show)
|
|
|
|
|
|
2023-12-19 20:08:54 -05:00
|
|
|
|
|
|
|
|
|
data Rotation = U | L | D | R deriving (Eq, Ord, Show, Bounded, Enum)
|
|
|
|
|
|
|
|
|
|
left, right, opp :: Rotation -> Rotation
|
|
|
|
|
left = \case U -> L; L -> D; D -> R; R -> U
|
|
|
|
|
right = \case U -> R; R -> D; D -> L; L -> U
|
|
|
|
|
opp = \case U -> D; D -> U; L -> R; R -> L
|
|
|
|
|
|
|
|
|
|
instance Semigroup Rotation where
|
|
|
|
|
U <> r = r
|
|
|
|
|
L <> r = left r
|
|
|
|
|
R <> r = right r
|
|
|
|
|
D <> r = opp r
|
|
|
|
|
|
|
|
|
|
instance Monoid Rotation where mempty = U
|
|
|
|
|
|
|
|
|
|
type I2 = (Int, Int)
|
|
|
|
|
|
|
|
|
|
data RArray e = RA !Rotation !(A.Array I2 e)
|
|
|
|
|
|
|
|
|
|
rotateI :: Rotation -> (I2, I2) -> I2 -> I2
|
|
|
|
|
rotateI U _ i = i
|
|
|
|
|
rotateI L (_, (xhi, _)) (x, y) = (y, xhi - x - 1)
|
|
|
|
|
rotateI D (_, (xhi, yhi)) (x, y) = (xhi - x - 1, yhi - y - 1)
|
|
|
|
|
rotateI R (_, (_, yhi)) (x, y) = (yhi - y - 1, x)
|
|
|
|
|
|
|
|
|
|
(!) :: RArray e -> I2 -> e
|
|
|
|
|
RA r arr ! i = arr A.! rotateI r (A.bounds arr) i
|
|
|
|
|
|
|
|
|
|
rotate :: Rotation -> RArray e -> RArray e
|
|
|
|
|
rotate r1 (RA r2 arr) = RA (r1 <> r2) arr
|
|
|
|
|
|
|
|
|
|
|
2023-12-14 13:46:32 -05:00
|
|
|
|
block :: Char -> Maybe Block
|
|
|
|
|
block 'O' = Just Rock
|
|
|
|
|
block '.' = Just Space
|
|
|
|
|
block '#' = Just Wall
|
|
|
|
|
block _ = Nothing
|
|
|
|
|
|
|
|
|
|
type Board = [[Block]]
|
|
|
|
|
|
|
|
|
|
board :: String -> Board
|
|
|
|
|
board = map (map block') . lines where
|
|
|
|
|
block' :: Char -> Block
|
|
|
|
|
block' c = fromMaybe (error $ "unrecognised " ++ show c) $ block c
|
|
|
|
|
|
|
|
|
|
chunks :: (a -> Bool) -> [a] -> [[a]]
|
|
|
|
|
chunks _ [] = []
|
|
|
|
|
chunks want (x : xs)
|
|
|
|
|
| want x = let (as, bs) = span want xs in (x : as) : chunks want bs
|
|
|
|
|
| otherwise = let (as, bs) = break want xs in (x : as) : chunks want bs
|
|
|
|
|
|
|
|
|
|
fall :: Ord a => (a -> Bool) -> [a] -> [a]
|
|
|
|
|
fall p = concat . map sort . chunks p
|
|
|
|
|
|
|
|
|
|
rotateLeft :: [[a]] -> [[a]]
|
|
|
|
|
rotateLeft = transpose . map reverse
|
|
|
|
|
|
|
|
|
|
rotateRight :: [[a]] -> [[a]]
|
|
|
|
|
rotateRight = map reverse . transpose
|
|
|
|
|
|
|
|
|
|
rotate180 :: [[a]] -> [[a]]
|
|
|
|
|
rotate180 = reverse . map reverse
|
|
|
|
|
|
|
|
|
|
fallLeft, fallUp, fallRight, fallDown :: Board -> Board
|
|
|
|
|
fallLeft = map $ fall (/= Wall)
|
|
|
|
|
fallUp = rotateRight . fallLeft . rotateLeft
|
|
|
|
|
fallRight = rotate180 . fallLeft . rotate180
|
|
|
|
|
fallDown = rotateLeft . fallLeft . rotateRight
|
|
|
|
|
|
|
|
|
|
scoreWith :: (a -> Bool) -> [[a]] -> Int
|
|
|
|
|
scoreWith p xss =
|
|
|
|
|
let ℓ = length xss; ℓs = [ℓ, ℓ-1 .. 0] in
|
|
|
|
|
sum $ zipWith (\xs y -> length (filter p xs) * y) xss ℓs
|
|
|
|
|
|
|
|
|
|
score :: Board -> Int
|
|
|
|
|
score = scoreWith (== Rock)
|
|
|
|
|
|
|
|
|
|
part1 :: Board -> Int
|
|
|
|
|
part1 = score . fallUp
|
|
|
|
|
|
|
|
|
|
type Cycle = (Board, Board, Board, Board)
|
|
|
|
|
|
|
|
|
|
cycle1 :: Board -> Cycle
|
|
|
|
|
cycle1 xs =
|
|
|
|
|
let as = fallUp xs
|
|
|
|
|
bs = fallLeft as
|
|
|
|
|
cs = fallDown bs
|
|
|
|
|
ds = fallRight cs in
|
|
|
|
|
(as, bs, cs, ds)
|
|
|
|
|
|
|
|
|
|
nextCycle :: Cycle -> Cycle
|
|
|
|
|
nextCycle (_, _, _, xs) = cycle1 xs
|
|
|
|
|
|
|
|
|
|
cycles :: Board -> [Cycle]
|
|
|
|
|
cycles start = let c = cycle1 start in iterate nextCycle c
|
|
|
|
|
|
|
|
|
|
fourth :: (a,b,c,d) -> d
|
|
|
|
|
fourth (_,_,_,d) = d
|
|
|
|
|
|
|
|
|
|
findRepeat :: Ord a =>
|
|
|
|
|
M.Map a Int -> M.Map Int a ->
|
|
|
|
|
Int -> [a] -> (Int, Int, M.Map Int a)
|
|
|
|
|
findRepeat ai ia i (a:as)
|
|
|
|
|
| Just j <- M.lookup a ai = (j, i, ia)
|
|
|
|
|
| otherwise = findRepeat ai' ia' (i + 1) as
|
|
|
|
|
where ai' = M.insert a i ai; ia' = M.insert i a ia
|
|
|
|
|
findRepeat _ _ _ _ = error "you said it was infinite"
|
|
|
|
|
|
|
|
|
|
part2 :: Board -> Int
|
|
|
|
|
part2 ℓs =
|
|
|
|
|
let loops = 1_000_000_000
|
|
|
|
|
(m, n, xs) = findRepeat M.empty M.empty 0 $ cycles ℓs
|
|
|
|
|
index = (loops - n) `mod` (n - m) - 1 in
|
|
|
|
|
score $ fourth $ xs M.! (m + index)
|
|
|
|
|
|
|
|
|
|
main :: IO ()
|
|
|
|
|
main = do
|
|
|
|
|
[part, file] <- getArgs
|
|
|
|
|
ℓs <- board <$> readFile file
|
|
|
|
|
case part of
|
|
|
|
|
"1" -> print $ part1 ℓs
|
|
|
|
|
"2" -> print $ part2 ℓs
|
|
|
|
|
_ -> error "usage: $0 <part> <file>"
|
|
|
|
|
|
2023-12-19 20:08:54 -05:00
|
|
|
|
-- example1 :: [String]
|
|
|
|
|
-- example1 =
|
|
|
|
|
-- ["O....#....",
|
|
|
|
|
-- "O.OO#....#",
|
|
|
|
|
-- ".....##...",
|
|
|
|
|
-- "OO.#O....O",
|
|
|
|
|
-- ".O.....O#.",
|
|
|
|
|
-- "O.#..O.#.#",
|
|
|
|
|
-- "..O..#O..O",
|
|
|
|
|
-- ".......O..",
|
|
|
|
|
-- "#....###..",
|
|
|
|
|
-- "#OO..#...."]
|