1 -- Writeup at https://work.njae.me.uk/2023/12/02/advent-of-code-2023-day-02/
4 import Data.Text (Text)
5 import qualified Data.Text.IO as TIO
6 import Data.Attoparsec.Text hiding (Result)
7 import Control.Applicative
10 data Game = Game {getID :: Int, getRevelations :: [Revelation]}
12 data Revelation = Revelation Int Int Int deriving (Eq, Show)
14 instance Semigroup Revelation where
15 (Revelation r0 g0 b0) <> (Revelation r1 g1 b1) =
16 Revelation (max r0 r1) (max g0 g1) (max b0 b1)
18 instance Monoid Revelation where
19 mempty = Revelation 0 0 0
22 newtype Merging = Merging { getMerging :: Revelation } deriving (Eq, Show)
24 instance Semigroup Merging where
25 (Merging (Revelation r0 g0 b0)) <> (Merging (Revelation r1 g1 b1)) =
26 Merging (Revelation (r0 + r1) (g0 + g1) (b0 + b1))
28 instance Monoid Merging where
29 mempty = Merging (Revelation 0 0 0)
31 data ParsedGame = ParsedGame Int [Showings] deriving (Eq, Show)
32 type Showings = [Cube]
33 data Cube = Cube Colour Int deriving (Eq, Show)
34 data Colour = Red | Green | Blue deriving (Eq, Show)
38 do dataFileName <- getDataFileName
39 text <- TIO.readFile dataFileName
40 let parsedGames = successfulParse text
42 let games = fmap engame parsedGames
48 part1, part2 :: [Game] -> Int
49 part1 games = sum $ fmap getID $ filter (`possible` limit) games
50 where limit = Revelation 12 13 14
52 part2 = sum . fmap (power . required)
55 compatibleWith :: Revelation -> Revelation -> Bool
56 compatibleWith (Revelation r0 g0 b0) (Revelation r1 g1 b1) =
57 (r0 <= r1) && (g0 <= g1) && (b0 <= b1)
59 possible :: Game -> Revelation -> Bool
61 all (`compatibleWith` limit) $ getRevelations game
63 required :: Game -> Revelation
64 required = mconcat . getRevelations
66 power :: Revelation -> Int
67 power (Revelation r g b) = r * g * b
70 -- conversion from parsed to real data
72 engame :: ParsedGame -> Game
73 engame (ParsedGame n showings) = Game n (fmap revealify showings)
75 revealify :: Showings -> Revelation
76 revealify = getMerging . mconcat . (fmap reveal)
78 reveal :: Cube -> Merging
79 reveal (Cube Red n) = Merging (Revelation n 0 0)
80 reveal (Cube Green n) = Merging (Revelation 0 n 0)
81 reveal (Cube Blue n) = Merging (Revelation 0 0 n)
84 -- Parse the input file
86 gamesP :: Parser [ParsedGame]
87 gameP :: Parser ParsedGame
88 showingsP :: Parser [Showings]
89 showingP :: Parser Showings
91 colourP, redP, greenP, blueP :: Parser Colour
93 gamesP = gameP `sepBy` endOfLine
94 gameP = ParsedGame <$> (("Game " *> decimal) <* ": ") <*> showingsP
96 showingsP = showingP `sepBy` "; "
97 showingP = cubeP `sepBy` ", "
98 cubeP = (flip Cube) <$> (decimal <* " ") <*> colourP
100 colourP = redP <|> greenP <|> blueP
102 greenP = Green <$ "green"
103 blueP = Blue <$ "blue"
106 successfulParse :: Text -> [ParsedGame]
107 successfulParse input =
108 case parseOnly gamesP input of
109 Left _err -> [] -- TIO.putStr $ T.pack $ parseErrorPretty err
110 Right matches -> matches