a one dimensional cellular automata, using comonads
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
4.9KB

  1. module Main where
  2. --import System.Random
  3. import Control.Monad
  4. import System.Process
  5. import System.Random
  6. import System.Console.GetOpt
  7. import System.Environment(getArgs, getProgName)
  8. import Data.Maybe (fromMaybe)
  9. -------------------
  10. -- comonad class --
  11. -------------------
  12. class Functor w => Comonad w
  13. where
  14. (=>>) :: w a -> (w a -> b) -> w b
  15. extract :: w a -> a
  16. duplicate :: w a -> w (w a)
  17. x =>> f = fmap f (duplicate x)
  18. ------------
  19. -- spaces --
  20. ------------
  21. -- a locally focussed space
  22. data Space t = Space [t] t [t]
  23. -- spaces are also functors
  24. instance Functor Space where
  25. fmap f (Space l c r) = Space (map f l) (f c) (map f r)
  26. -- our space is a comonad
  27. instance Comonad Space where
  28. -- duplicate will create a new space where
  29. -- the focussed element is our original space
  30. -- and each side is increasingly shifted copies
  31. -- in that direction
  32. duplicate w =
  33. Space (tail $ iterate left w)
  34. w
  35. (tail $ iterate right w)
  36. -- extract simply returns the focussed element
  37. extract (Space _ c _) = c
  38. -- functions for moving the point
  39. -- of locality.
  40. -- todo: question the empty list cases
  41. -- most spaces should be infinite
  42. right :: Space t -> Space t
  43. right s@(Space l c []) = s
  44. right (Space l c (r:rs)) = Space (c:l) r rs
  45. left :: Space t -> Space t
  46. left s@(Space [] c r) = s
  47. left (Space (l:ls) c r) = Space ls l (c:r)
  48. -- bound will take an infinite space
  49. -- and bound it by i and j on each side
  50. -- (not including the focus) and
  51. -- turn it into a list for printing
  52. bound :: Int -> Int -> Space t -> [t]
  53. bound i j (Space l c r) = (reverse (take i l)) ++ (c:(take j r))
  54. -- boundw works as above, but the
  55. -- entire list will be the size
  56. -- given
  57. boundw :: Int -> Space t -> [t]
  58. boundw n = bound (x-m) x
  59. where
  60. o = if odd n then 1 else 0
  61. m = if even n then 1 else 0
  62. x = (n - o) `div` 2
  63. -----------------------
  64. -- cellular automata --
  65. -----------------------
  66. -- the states our cells can be in
  67. -- may need to provide an ordering
  68. -- may need to generalise the number
  69. -- of states
  70. data CellState = Alive | Dead
  71. deriving Eq
  72. -- how the states are displayed on screen
  73. -- this should probably be input to a function
  74. -- rather than hardcoded
  75. instance Show CellState
  76. where
  77. show Alive = "█"
  78. show Dead = " "
  79. -- a rule stating how a cell is determined
  80. rule :: Space CellState -> CellState
  81. rule (Space (l:_) _ (r:_))
  82. | l == r = Dead
  83. | otherwise = Alive
  84. -- take a space and a rule and
  85. -- return the next space
  86. step :: (Space t -> t) -> Space t -> Space t
  87. step f w = w =>> f
  88. ---------------
  89. -- rng stuff --
  90. ---------------
  91. -- takes a generator and returns
  92. -- an infinite list of bools
  93. ilobs :: StdGen -> [Bool]
  94. ilobs rng = b : (ilobs r)
  95. where
  96. (b,r) = random rng
  97. -----------------
  98. -- gross io bs --
  99. -----------------
  100. -- everything below this line deals with
  101. -- input/output, and is therefore gross
  102. -- i will clean this up one day, but it
  103. -- hurts my soul.
  104. ------------------------
  105. -- command line flags --
  106. ------------------------
  107. -- structure containing the programs options
  108. data Options = Options
  109. { optWidth :: Int
  110. , optGenerations :: Int
  111. } deriving Show
  112. -- the default options for the program
  113. -- the width and generations are injected
  114. -- and intended to be gotten at runtime
  115. -- to match the window dimensions
  116. defaultOptions :: Int -> Int -> Options
  117. defaultOptions w h = Options
  118. { optWidth = w
  119. , optGenerations = h
  120. }
  121. -- the avaliable options
  122. options :: [OptDescr (Options -> Options)]
  123. options =
  124. [ Option ['w'] ["width"]
  125. (ReqArg (\w opts -> opts { optWidth = (read w) }) "WIDTH")
  126. "term width"
  127. , Option ['g'] ["generations"]
  128. (ReqArg (\t opts -> opts { optGenerations = (read t) }) "GENERATIONS")
  129. "time steps to simulate"
  130. ]
  131. -- parse the options into the structure
  132. -- erroring if encountering a flag not known to us
  133. parseArgs :: IO Options
  134. parseArgs = do
  135. argv <- getArgs
  136. progName <- getProgName
  137. tw <- readProcess "tput" [ "cols" ] ""
  138. th <- readProcess "tput" [ "lines" ] ""
  139. case getOpt RequireOrder options argv of
  140. (opts, [], []) -> return (foldl (flip id) (defaultOptions (read tw) (read th)) opts)
  141. (_, _, errs) -> ioError (userError (concat errs ++ helpMessage))
  142. where
  143. header = "Usage: " ++ progName ++ " [OPTION...]"
  144. helpMessage = usageInfo header options
  145. ---------------
  146. -- main loop --
  147. ---------------
  148. -- simply print the current space, then recurse to the next
  149. runAutomata :: Space CellState -> Int -> Int -> IO ()
  150. runAutomata s 0 w = putStrLn $ concat $ map show $ boundw w s
  151. runAutomata s n w = do
  152. putStrLn $ concat $ map show $ boundw w s
  153. runAutomata (step rule s) (n - 1) w
  154. main :: IO ()
  155. main = do
  156. options <- parseArgs
  157. rng <- getStdGen
  158. let cs = map (\x -> if x then Alive else Dead) $ ilobs rng
  159. let w = (optWidth options)
  160. let h = (optGenerations options)
  161. let wh = (w + 1) `div` 2
  162. let m = head cs
  163. let l = take wh $ drop 1 cs
  164. let r = take wh $ drop wh $ drop 1 cs
  165. let s = Space (l ++ (repeat Dead)) m (r ++ (repeat Dead))
  166. runAutomata s h w