> import System.Posix.Unistd > import System.Environment > import Data.Char > import Maybe Game of life example from section 9.7 of Programming in Haskell, Graham Hutton, Cambridge University Press, 2007. Note: the control characters used in this example may not work on some Haskell systems, such as WinHugs. Derived primitives ------------------ > cls :: IO () > cls = putStr "\ESC[2J" > > type Pos = (Int,Int) > > goto :: Pos -> IO () > goto (x,y) = putStr ("\ESC[" ++ show y ++ ";" ++ show x ++ "H") > > writeat :: Pos -> String -> IO () > writeat p xs = do goto p > putStr xs > > seqn :: [IO a] -> IO () > seqn [] = return () > seqn (a:as) = do a > seqn as Game of life ------------ > width :: Int > width = 400 > > height :: Int > height = 90 > > type Board = [Pos] > > glider :: Board > glider = [(4,2),(2,3),(4,3),(3,4),(4,4)] > > showcells :: Board -> IO () > showcells b = seqn [writeat p "O" | p <- b] > > isAlive :: Board -> Pos -> Bool > isAlive b p = elem p b > > isEmpty :: Board -> Pos -> Bool > isEmpty b p = not (isAlive b p) > > neighbs :: Pos -> [Pos] > neighbs (x,y) = map wrap [(x-1,y-1), (x,y-1), > (x+1,y-1), (x-1,y), > (x+1,y) , (x-1,y+1), > (x,y+1) , (x+1,y+1)] > > wrap :: Pos -> Pos > wrap (x,y) = (((x-1) `mod` width) + 1, ((y-1) `mod` height + 1)) > > liveneighbs :: Board -> Pos -> Int > liveneighbs b = length . filter (isAlive b) . neighbs > > survivors :: Board -> [Pos] > survivors b = [p | p <- b, elem (liveneighbs b p) [2,3]] > > births b = [p | p <- rmdups (concat (map neighbs b)), > isEmpty b p, > liveneighbs b p == 3] > > rmdups :: Eq a => [a] -> [a] > rmdups [] = [] > rmdups (x:xs) = x : rmdups (filter (/= x) xs) > > nextgen :: Board -> Board > nextgen b = survivors b ++ births b > > life :: Board -> IO () > life b = do cls > showcells b > wait 200 > life (nextgen b) > > wait :: Int -> IO () > wait n = usleep n >> return () -- -- This main reads the command line for a list of -- files to be loaded up. -- Try running it as "life gosperglidergun_106.lif", for example. -- it assumes a laaaarge terminal window (400x100). Change the width and height constants -- above to change that. > main = do > args <- getArgs > boards <- sequence (map parseFile args) > case (sequence boards) of > Just bs -> life $ center $ (concat bs) > Nothing -> print "Parse error" -- For testing this simple interactive main kicks things off with a specific file > imain = do > board <- parseFile "gosperglidergun_106.lif" > life $ center $ fromJust board Life file parser (Life 1.06 format, see http://www.conwaylife.com/wiki/Life_1.06 for details) --------------------- > parseLines :: [ String ] -> Maybe Board > parseLines [] = Nothing > parseLines ( x : xs ) | confirmVersion x = parseCells (noblanks xs) > | otherwise = Nothing > where noblanks = filter (/= []) > confirmVersion x = x == "#Life 1.06" > parseCells :: [ String ] -> Maybe Board > parseCells cs = sequence (map parseCell cs) > parseCell :: String -> Maybe Pos > parseCell str | valid p1 && valid p2 && null rest = Just (x, y) > | otherwise = Nothing > where valid = not . null > p1 = reads str > [ (x, str1) ] = p1 > p2 = reads str1 > [ (y, str2) ] = p2 > rest = filter isSpace str2 > -- test patterns: Glider, Gosper Glider Gun > testPattern1 = lines "#Life 1.06\n0 -1\n1 0\n-1 1\n0 1\n1 1" > testPattern2 = lines "#Life 1.06\n6 -4\n4 -3\n6 -3\n-6 -2\n-5 -2\n2 -2\n3 -2\n16 -2\n17 -2\n-7 -1\n-3 -1\n2 -1\n3 -1\n16 -1\n17 -1\n-18 0\n-17 0\n-8 0\n-2 0\n2 0\n3 0\n-18 1\n-17 1\n-8 1\n-4 1\n-2 1\n-1 1\n4 1\n6 1\n-8 2\n-2 2\n6 2\n-7 3\n-3 3\n-6 4\n-5 4" -- Pattern files from the life wiki come with MS line endings > dos2unix :: String -> String > dos2unix = filter (/= '\r') > parseFile :: String -> IO (Maybe Board) > parseFile filename = do > s <- readFile filename > return ( parseLines $ lines $ dos2unix s) > loadFile :: String -> IO Board > loadFile s = do > mf <- parseFile s > case mf of > Just b -> return b > Nothing -> do > print "Parse error" > return [] Centering Pattern files from the life wiki can have negative coordinates which doesn't work well with our model the centerBoard function will transform a board so that it is centered on our world --------- > bounds :: Board -> (Pos, Pos) > bounds b = ( (minx, miny), (maxx, maxy) ) > where (minx, maxx) = (minimum xs, maximum xs) > (miny, maxy) = (minimum ys, maximum ys) > (xs, ys) = unzip b > translate :: (Int,Int) -> Board -> Board > translate (dx,dy) b = map trans b > where trans (x,y) = (x+dx, y+dy) > center :: Board -> Board > center b = translate ( ( (width - maxx - minx) `div` 2) - minx, ( (height - maxy - miny) `div` 2) - miny) b > where ( (minx, miny), (maxx, maxy) ) = bounds b