Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- > 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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement