1 {-# LANGUAGE DeriveFunctor #-}
3 -- From https://gist.github.com/gatlin/21d669321c617836a317693aef63a3c3
6 import Control.Applicative
9 import Control.Monad (forM_)
11 -- These two typeclasses probably make some people groan.
12 class LeftRight t where
20 -- | An infinite list of values
21 data Stream a = a :> Stream a
27 -- | Allows finitary beings to view slices of a 'Stream'
28 takeS :: Int -> Stream a -> [a]
30 takeS n (x :> xs) = x : (takeS (n-1) xs)
32 -- | Build a 'Stream' from a generator function
33 unfoldS :: (b -> (a, b)) -> b -> Stream a
34 unfoldS f c = x :> unfoldS f d
37 -- | Build a 'Stream' by mindlessly repeating a value
38 repeatS :: a -> Stream a
39 repeatS x = x :> repeatS x
41 -- | Build a 'Stream' from a finite list, filling in the rest with a designated
43 fromS :: a -> [a] -> Stream a
44 fromS def [] = repeatS def
45 fromS def (x:xs) = x :> (fromS def xs)
47 -- | Prepend values from a list to an existing 'Stream'
48 prependList :: [a] -> Stream a -> Stream a
49 prependList [] str = str
50 prependList (x:xs) str = x :> (prependList xs str)
52 -- | A 'Stream' is a comonad
53 instance Comonad Stream where
55 duplicate s@(a :> as) = s :> (duplicate as)
57 -- | It is also a 'ComonadApply'
58 instance ComonadApply Stream where
59 (f :> fs) <@> (x :> xs) = (f x) :> (fs <@> xs)
61 -- * Tapes, our workhorse
63 -- | A 'Tape' is always focused on one value in a 1-dimensional infinite stream
64 data Tape a = Tape (Stream a) a (Stream a)
67 -- | We can go left and right along a tape!
68 instance LeftRight Tape where
69 left (Tape (l :> ls) c rs) = Tape ls l (c :> rs)
70 right (Tape ls c (r :> rs)) = Tape (c :> ls) r rs
72 -- | Build out the left, middle, and right parts of a 'Tape' with generator
80 unfoldT prev center next =
86 -- | A simplified unfolding mechanism that will be useful shortly
92 tapeIterate prev next = unfoldT (dup . prev) id (dup . next)
95 -- | Create a 'Tape' from a list where everything to the left is some default
96 -- value and the list is the focused value and everything to the right.
97 tapeFromList :: a -> [a] -> Tape a
98 tapeFromList def xs = right $ Tape background def $ fromS def xs
99 where background = repeatS def
101 tapeToList :: Int -> Tape a -> [a]
102 tapeToList n (Tape ls x rs) =
103 reverse (takeS n ls) ++ [x] ++ (takeS n rs)
105 instance Comonad Tape where
106 extract (Tape _ c _) = c
107 duplicate = tapeIterate left right
109 instance ComonadApply Tape where
110 (Tape fl fc fr) <@> (Tape xl xc xr) =
111 Tape (fl <@> xl) (fc xc) (fr <@> xr)
113 -- * Sheets! We're so close!
115 -- | A 2-dimensional 'Tape' of 'Tape's.
116 newtype Sheet a = Sheet (Tape (Tape a))
119 instance UpDown Sheet where
120 up (Sheet p) = Sheet (left p)
121 down (Sheet p) = Sheet (right p)
123 instance LeftRight Sheet where
124 left (Sheet p) = Sheet (fmap left p)
125 right (Sheet p) = Sheet (fmap right p)
127 -- Helper functions to take a given 'Sheet' and make an infinite 'Tape' of it.
128 horizontal :: Sheet a -> Tape (Sheet a)
129 horizontal = tapeIterate left right
131 vertical :: Sheet a -> Tape (Sheet a)
132 vertical = tapeIterate up down
134 instance Comonad Sheet where
135 extract (Sheet p) = extract $ extract p
136 -- | See? Told you 'tapeIterate' would be useful
137 duplicate s = Sheet $ fmap horizontal $ vertical s
139 instance ComonadApply Sheet where
140 (Sheet (Tape fu fc fd)) <@> (Sheet (Tape xu xc xd)) =
141 Sheet $ Tape (fu `ap` xu) (fc <@> xc) (fd `ap` xd)
143 where ap t1 t2 = (fmap (<@>) t1) <@> t2
145 -- | Produce a 'Sheet' from a list of lists, where each inner list is a row.
146 sheet :: a -> [[a]] -> Sheet a
147 sheet def grid = Sheet cols
148 where cols = fmap (tapeFromList def) rows
149 rows = tapeFromList [def] grid
151 -- | Slice a sheet for viewing purposes.
152 sheetToList :: Int -> Sheet a -> [[a]]
153 sheetToList n (Sheet zs) = tapeToList n $ fmap (tapeToList n) zs
155 -- | Construct an infinite sheet in all directions from a base value.
156 background :: a -> Sheet a
157 background x = Sheet . duplicate $ Tape (repeatS x) x (repeatS x)
159 printSheet :: Show a => Int -> Sheet a -> IO ()
160 printSheet n sh = forM_ (sheetToList n sh) $ putStrLn . show
162 -- | This just makes some things more readable
163 (&) :: a -> (a -> b) -> b
166 -- | A cell can be alive ('O') or dead ('X').
167 data Cell = X | O deriving (Eq)
169 instance Show Cell where
173 -- | The first list is of numbers of live neighbors to make a dead 'Cell' become
174 -- alive; the second is numbers of live neighbors that keeps a live 'Cell'
176 type Ruleset = ([Int], [Int])
178 cellToInt :: Cell -> Int
182 conwayRules :: Ruleset
183 conwayRules = ([3], [2, 3])
185 executeRules :: Ruleset -> Sheet Cell -> Cell
186 executeRules (born, persist) s = go (extract s) where
188 -- there is probably a more elegant way of getting all 8 neighbors
189 neighbors = [ s & up & extract
192 , s & right & extract
193 , s & up & right & extract
194 , s & up & left & extract
195 , s & down & left & extract
196 , s & down & right & extract ]
197 liveCount = sum $ map cellToInt neighbors
198 go O | liveCount `elem` persist = O
200 go X | liveCount `elem` born = O
204 initialGame :: [[Cell]]
205 initialGame = [ [ X, X, O ]
209 -- | A 'Stream' of Conway games.
210 timeline :: Ruleset -> Sheet Cell -> Stream (Sheet Cell)
211 timeline rules ig = go ig where
212 go ig = ig :> (go (rules' <@> (duplicate ig)))
213 rules' = background $ executeRules rules
217 let ig = sheet X initialGame
218 let tl = timeline conwayRules ig
219 forM_ (takeS 5 tl) $ \s -> do