Conway's Game of Life in Haskell
Today I came across this excellent game of life implementation in Clojure, and also was learning about monads in Haskell. So I ported the former, using the latter!
The logic translates pretty much the same. Wondering if there is more monads to be had on the newCell
assignment line (the one with concatMap
and friends), even at the expense of readability. This is a learning exercise, after all. I went for bonus points by writing a function to render the grid, it didn’t go as well. Would love some feedback on it. Here is a forkable version.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
import Data.List import Control.Monad type Cell = (Int, Int) type Grid = [Cell] -- Game Logic neighbours :: Cell -> Grid neighbours (x, y) = do dx <- [-1..1] dy <- [-1..1] guard (dx /= 0 || dy /= 0) return (x + dx, y + dy) step :: Grid -> Grid step cells = do (newCell, n) <- frequencies $ concatMap neighbours cells guard $ (n == 3) || (n == 2 && newCell `elem` cells) return newCell -- This is the only deviation from the Clojure version, since it is not a -- built-in in Haskell. frequencies :: Ord a => [a] -> [(a, Int)] frequencies xs = do x <- group $ sort xs return (head x, length x) -- UI -- Feel like I'm missing a concept. Not so happy with this function: -- * Can `eol` be done a better way? I tried nested maps but it was urgh. -- * `marker` seems long for a simple tenary. Same issue as `eol` I guess. formatGrid :: Grid -> String formatGrid grid = do y <- ys x <- xs [marker x y] ++ eol x where marker x y | (x, y) `elem` grid = '*' | otherwise = ' ' eol x | x == maximum xs = ['\n'] | otherwise = [] xs = gridRange fst ys = gridRange snd gridRange f = [min grid .. max grid] where min = minimum . map f max = maximum . map f main = do mapM_ printGrid . take 3 $ iterate step beacon where beacon = [(0, 0), (1, 0), (0, 1), (3, 3), (2, 3), (3, 2)] printGrid :: Grid -> IO () printGrid grid = do putStrLn $ formatGrid grid putStrLn "" |