-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlife.hs
109 lines (92 loc) · 3.24 KB
/
life.hs
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
module Main where
import System.Environment
import System.Random
import System.Console.ANSI
import System.Timeout
import System.IO
import Control.Monad
import Control.DeepSeq
import Control.Applicative
data Cell = Alive | Dead
deriving Eq
data World = World Int Int [Cell]
instance Show Cell where
show Alive = "O"
show Dead = " "
-- given width and height as arguments initializes a random World
-- and starts the main loop
main :: IO ()
main = do
args <- getArgs
if length args /= 2 then putStrLn "Usage: life width height"
else do
hSetBuffering stdin NoBuffering
hideCursor
cls
loop $ initWorld (read $ head args) (read $ args !! 1)
-- displays the World, evolves it and checks for input
loop :: IO World -> IO ()
loop world = do
w @ (World sw sh cs) <- world
setSGR [SetColor Foreground Vivid Cyan]
printWorld w
setSGR []
showInfo cs
input <- timeout 50000 getChar
case input of
Just i | i == 'q' -> setSGR [] >> showCursor
| i == 'r' -> initWorld sw sh >>= step
| otherwise -> step w
Nothing -> step w
where step w = cls >> loop (return $ evolve w)
-- clears the screen and resets cursor position
cls :: IO ()
cls = clearScreen >> setCursorPosition 0 0
-- given width and height, returns a random World
initWorld :: Int -> Int -> IO World
initWorld w h = World w h <$> replicateM (w * h) initCell
-- returns a random Cell
initCell :: IO Cell
initCell = do
r <- randomRIO(0, 1) :: IO Int
return $ if r == 0 then Alive else Dead
-- given a World, return the World's next (evolved) state
evolve :: World -> World
evolve world @ (World w h cs) = World w h [evolveCell x y world | y <- ys, x <- xs]
where
xs = [0..w-1]
ys = [0..h-1]
-- given the coordinates of a cell in the given world returns that Cell's next state
evolveCell :: Int -> Int -> World -> Cell
evolveCell x y w
| n == 2 && isAlive x y w || n == 3 = Alive
| otherwise = Dead
where n = countNeighbours x y w
-- returns the number of adjacent living Cells to the Cell at (x, y)
countNeighbours :: Int -> Int -> World -> Int
countNeighbours x y w = length . filter (True==) . map (\(i, j) -> isAlive i j w) $ ls
where ls = [(i, j) | i <- [x-1..x+1], j <- [y-1..y+1], i /= x || j /= y ]
-- checks whether the Cell at (x, y) is dead or alive
isAlive :: Int -> Int -> World -> Bool
isAlive x y w = case getCell x y w of
Alive -> True
Dead -> False
-- returns the Cell that is on (x, y) in the given World
getCell :: Int -> Int -> World -> Cell
getCell x y (World w h cs)
| isInside = cs !! (y * w + x)
| otherwise = Dead
where isInside = x >= 0 && y >= 0 && x < w && y < h
-- display the given World
printWorld :: World -> IO ()
printWorld w = putStrLn $!! showWorld w
-- takes a World and returns it's String representation
showWorld :: World -> String
showWorld (World _ _ []) = []
showWorld (World w h cs) = concatMap show line
++ "\n" ++ showWorld (World w h rest)
where (line, rest) = splitAt w cs
-- given the list of current Cells, display's some information
showInfo :: [Cell] -> IO ()
showInfo cs = putStrLn ("(q - quit) (r - reset) live cells: "
++ (show . length $ filter (Alive==) cs))