1 {-# LANGUAGE OverloadedStrings, DeriveFunctor #-}
3 -- Heavily based on https://gist.github.com/gatlin/21d669321c617836a317693aef63a3c3
7 import Data.Text (Text)
8 import qualified Data.Text.IO as TIO
10 import Data.Void (Void)
12 import Text.Megaparsec
13 import Text.Megaparsec.Char
14 import qualified Text.Megaparsec.Char.Lexer as L
15 import qualified Control.Applicative as CA
17 import Control.Applicative
18 import Control.Comonad
20 import Control.Monad (forM_)
22 -- import qualified Data.Set as S
27 -- text <- TIO.readFile "data/advent12.txt"
28 -- let (initial, rules) = successfulParse text
35 let ig = tape X initialGame
36 let tl = timeline conwayRules ig
37 forM_ (takeS 5 tl) $ \s -> do
42 -- These two typeclasses probably make some people groan.
43 class LeftRight t where
47 -- | An infinite list of values
48 data Stream a = a :> Stream a
54 -- | Allows finitary beings to view slices of a 'Stream'
55 takeS :: Int -> Stream a -> [a]
57 takeS n (x :> xs) = x : (takeS (n-1) xs)
59 -- | Build a 'Stream' from a generator function
60 unfoldS :: (b -> (a, b)) -> b -> Stream a
61 unfoldS f c = x :> unfoldS f d
64 -- | Build a 'Stream' by mindlessly repeating a value
65 repeatS :: a -> Stream a
66 repeatS x = x :> repeatS x
68 -- | Build a 'Stream' from a finite list, filling in the rest with a designated
70 fromS :: a -> [a] -> Stream a
71 fromS def [] = repeatS def
72 fromS def (x:xs) = x :> (fromS def xs)
74 -- | Prepend values from a list to an existing 'Stream'
75 prependList :: [a] -> Stream a -> Stream a
76 prependList [] str = str
77 prependList (x:xs) str = x :> (prependList xs str)
79 -- | A 'Stream' is a comonad
80 instance Comonad Stream where
82 duplicate s@(a :> as) = s :> (duplicate as)
84 -- | It is also a 'ComonadApply'
85 instance ComonadApply Stream where
86 (f :> fs) <@> (x :> xs) = (f x) :> (fs <@> xs)
88 -- * Tapes, our workhorse
90 -- | A 'Tape' is always focused on one value in a 1-dimensional infinite stream
91 data Tape a = Tape (Stream a) a (Stream a)
94 -- | We can go left and right along a tape!
95 instance LeftRight Tape where
96 left (Tape (l :> ls) c rs) = Tape ls l (c :> rs)
97 right (Tape ls c (r :> rs)) = Tape (c :> ls) r rs
99 -- | Build out the left, middle, and right parts of a 'Tape' with generator
107 unfoldT prev center next =
113 -- | A simplified unfolding mechanism that will be useful shortly
119 tapeIterate prev next = unfoldT (dup . prev) id (dup . next)
122 -- | Create a 'Tape' from a list where everything to the left is some default
123 -- value and the list is the focused value and everything to the right.
124 tapeFromList :: a -> [a] -> Tape a
125 tapeFromList def xs = right $ Tape background def $ fromS def xs
126 where background = repeatS def
128 tapeToList :: Int -> Tape a -> [a]
129 tapeToList n (Tape ls x rs) =
130 reverse (takeS n ls) ++ [x] ++ (takeS n rs)
132 instance Comonad Tape where
133 extract (Tape _ c _) = c
134 duplicate = tapeIterate left right
136 instance ComonadApply Tape where
137 (Tape fl fc fr) <@> (Tape xl xc xr) =
138 Tape (fl <@> xl) (fc xc) (fr <@> xr)
141 -- | Construct an infinite tape in all directions from a base value.
142 background :: a -> Tape a
143 background x = Tape (repeatS x) x (repeatS x)
145 -- | This just makes some things more readable
146 (&) :: a -> (a -> b) -> b
149 -- | A cell can be alive ('O') or dead ('X').
150 data Cell = X | O deriving (Eq)
152 instance Show Cell where
156 -- | The first list is of numbers of live neighbors to make a dead 'Cell' become
157 -- alive; the second is numbers of live neighbors that keeps a live 'Cell'
159 type Ruleset = ([Int], [Int])
161 cellToInt :: Cell -> Int
165 conwayRules :: Ruleset
166 conwayRules = ([3], [2, 3])
168 executeRules :: Ruleset -> Tape Cell -> Cell
169 executeRules rules s = go (extract s) where
171 -- there is probably a more elegant way of getting all 8 neighbors
172 neighbors = [ s & left & left & extract
175 , s & right & extract
176 , s & right & right & extract
178 liveCount = sum $ map cellToInt neighbors
179 go O | liveCount `elem` persist = O
181 go X | liveCount `elem` born = O
185 initialGame :: [Cell]
186 initialGame = map cToCell "#..#.#..##......###...###"
187 where cToCell c = if c == '#' then O else X
189 -- | A 'Stream' of Conway games.
190 timeline :: Ruleset -> Tape Cell -> Stream (Tape Cell)
191 timeline rules ig = go ig where
192 go ig = ig :> (go (rules' <@> (duplicate ig)))
193 rules' = background $ executeRules rules
196 applyRule rule cells =
199 -- Parse the input file
201 type Parser = Parsec Void Text
204 sc = L.space (skipSome spaceChar) CA.empty CA.empty
207 potP = char '.' <|> char '#'
209 initialPrefix = symb "initial state:"
212 fileP = (,) <$> initialP <*> many ruleP
213 initialP = initialPrefix *> many potP <* sc
214 ruleP = (,) <$> ruleLHS <* ruleSepP <*> ruleRHS
215 ruleLHS = count 5 potP <* sc
218 successfulParse :: Text -> (String, [(String, Char)])
219 successfulParse input =
220 case parse fileP "input" input of
221 Left _error -> ("", []) -- TIO.putStr $ T.pack $ parseErrorPretty err