1 {-# LANGUAGE OverloadedStrings, DeriveFunctor #-}
5 import Data.Text (Text)
6 import qualified Data.Text.IO as TIO
8 import Data.Void (Void)
10 import Text.Megaparsec
11 import Text.Megaparsec.Char
12 import qualified Text.Megaparsec.Char.Lexer as L
13 import qualified Control.Applicative as CA
15 import Control.Applicative
16 import Control.Comonad
18 import Control.Monad (forM_)
20 -- import qualified Data.Set as S
25 -- text <- TIO.readFile "data/advent12.txt"
26 -- let (initial, rules) = successfulParse text
33 let ig = tape X initialGame
34 let tl = timeline conwayRules ig
35 forM_ (takeS 5 tl) $ \s -> do
40 -- These two typeclasses probably make some people groan.
41 class LeftRight t where
45 -- | An infinite list of values
46 data Stream a = a :> Stream a
52 -- | Allows finitary beings to view slices of a 'Stream'
53 takeS :: Int -> Stream a -> [a]
55 takeS n (x :> xs) = x : (takeS (n-1) xs)
57 -- | Build a 'Stream' from a generator function
58 unfoldS :: (b -> (a, b)) -> b -> Stream a
59 unfoldS f c = x :> unfoldS f d
62 -- | Build a 'Stream' by mindlessly repeating a value
63 repeatS :: a -> Stream a
64 repeatS x = x :> repeatS x
66 -- | Build a 'Stream' from a finite list, filling in the rest with a designated
68 fromS :: a -> [a] -> Stream a
69 fromS def [] = repeatS def
70 fromS def (x:xs) = x :> (fromS def xs)
72 -- | Prepend values from a list to an existing 'Stream'
73 prependList :: [a] -> Stream a -> Stream a
74 prependList [] str = str
75 prependList (x:xs) str = x :> (prependList xs str)
77 -- | A 'Stream' is a comonad
78 instance Comonad Stream where
80 duplicate s@(a :> as) = s :> (duplicate as)
82 -- | It is also a 'ComonadApply'
83 instance ComonadApply Stream where
84 (f :> fs) <@> (x :> xs) = (f x) :> (fs <@> xs)
86 -- * Tapes, our workhorse
88 -- | A 'Tape' is always focused on one value in a 1-dimensional infinite stream
89 data Tape a = Tape (Stream a) a (Stream a)
92 -- | We can go left and right along a tape!
93 instance LeftRight Tape where
94 left (Tape (l :> ls) c rs) = Tape ls l (c :> rs)
95 right (Tape ls c (r :> rs)) = Tape (c :> ls) r rs
97 -- | Build out the left, middle, and right parts of a 'Tape' with generator
105 unfoldT prev center next =
111 -- | A simplified unfolding mechanism that will be useful shortly
117 tapeIterate prev next = unfoldT (dup . prev) id (dup . next)
120 -- | Create a 'Tape' from a list where everything to the left is some default
121 -- value and the list is the focused value and everything to the right.
122 tapeFromList :: a -> [a] -> Tape a
123 tapeFromList def xs = right $ Tape background def $ fromS def xs
124 where background = repeatS def
126 tapeToList :: Int -> Tape a -> [a]
127 tapeToList n (Tape ls x rs) =
128 reverse (takeS n ls) ++ [x] ++ (takeS n rs)
130 instance Comonad Tape where
131 extract (Tape _ c _) = c
132 duplicate = tapeIterate left right
134 instance ComonadApply Tape where
135 (Tape fl fc fr) <@> (Tape xl xc xr) =
136 Tape (fl <@> xl) (fc xc) (fr <@> xr)
139 -- | Construct an infinite tape in all directions from a base value.
140 background :: a -> Tape a
141 background x = Tape (repeatS x) x (repeatS x)
143 -- | This just makes some things more readable
144 (&) :: a -> (a -> b) -> b
147 -- | A cell can be alive ('O') or dead ('X').
148 data Cell = X | O deriving (Eq)
150 instance Show Cell where
154 -- | The first list is of numbers of live neighbors to make a dead 'Cell' become
155 -- alive; the second is numbers of live neighbors that keeps a live 'Cell'
157 type Ruleset = ([Int], [Int])
159 cellToInt :: Cell -> Int
163 conwayRules :: Ruleset
164 conwayRules = ([3], [2, 3])
166 executeRules :: Ruleset -> Tape Cell -> Cell
167 executeRules rules s = go (extract s) where
169 -- there is probably a more elegant way of getting all 8 neighbors
170 neighbors = [ s & left & left & extract
173 , s & right & extract
174 , s & right & right & extract
176 liveCount = sum $ map cellToInt neighbors
177 go O | liveCount `elem` persist = O
179 go X | liveCount `elem` born = O
183 initialGame :: [Cell]
184 initialGame = map cToCell "#..#.#..##......###...###"
185 where cToCell c = if c == '#' then O else X
187 -- | A 'Stream' of Conway games.
188 timeline :: Ruleset -> Tape Cell -> Stream (Tape Cell)
189 timeline rules ig = go ig where
190 go ig = ig :> (go (rules' <@> (duplicate ig)))
191 rules' = background $ executeRules rules
194 applyRule rule cells =
197 -- Parse the input file
199 type Parser = Parsec Void Text
202 sc = L.space (skipSome spaceChar) CA.empty CA.empty
205 potP = char '.' <|> char '#'
207 initialPrefix = symb "initial state:"
210 fileP = (,) <$> initialP <*> many ruleP
211 initialP = initialPrefix *> many potP <* sc
212 ruleP = (,) <$> ruleLHS <* ruleSepP <*> ruleRHS
213 ruleLHS = count 5 potP <* sc
216 successfulParse :: Text -> (String, [(String, Char)])
217 successfulParse input =
218 case parse fileP "input" input of
219 Left _error -> ("", []) -- TIO.putStr $ T.pack $ parseErrorPretty err