-
Notifications
You must be signed in to change notification settings - Fork 0
/
Life.hs
90 lines (67 loc) · 2.67 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
module Life where
import Data.List (nub, union)
import System.Posix (sleep, nanosleep)
import System.Console.ANSI (clearScreen)
type Cell = (Int, Int) -- a cell on the board
type Board = [Cell] -- a board is a list of living cells
-- E.g., the board [(1,0),(2,0),(2,1),(5,0)] represents
-- _XX__X_
-- __X____
-- _______
-- (the board extends infinitely);
neighbors :: Cell -> [Cell]
neighbors cell =
let deltas = [(-1,-1),(0,-1),(1,-1),(1,0),(1,1),(0,1),(-1,1),(-1,0)]
in map (\(x, y) -> (fst cell + x, snd cell + y)) deltas
alive :: Board -> Cell -> Bool
alive board cell = cell `elem` board
livingNeighbors :: Board -> Cell -> [Cell]
livingNeighbors board cell = filter (alive board) (neighbors cell)
numLivingNeighbors :: Board -> Cell -> Int
numLivingNeighbors board cell = length (livingNeighbors board cell)
cellsWithLivingNeighbor :: Board -> Board
cellsWithLivingNeighbor board =
nub $ foldl (\acc c -> neighbors c ++ acc) [] board
deadCellsWithLivingNeighbor :: Board -> Board
deadCellsWithLivingNeighbor living =
let all = cellsWithLivingNeighbor living
in filter (\c -> not $ c `elem` living) all
-- Rule: Dead cells with exactly 3 living neighbors become live cells
birthCells :: Board -> Board
birthCells board =
let potentialKids = deadCellsWithLivingNeighbor board
in foldl (\acc c -> if numLivingNeighbors board c == 3 then c : acc else acc) [] potentialKids
-- Rule: Living cells with fewer than 2 living neighbors die
removeStarvedCells board = filter (\c -> numLivingNeighbors board c >= 2) board
-- Rule: Living cells with more than 3 living neighbors die
removeOverpopulatedCells board = filter (\c -> numLivingNeighbors board c <= 3) board
next :: Board -> Board
next board = union (removeOverpopulatedCells $ removeStarvedCells board) (birthCells board)
-- Test boards
board :: Board
board = [(1,0),(2,0),(3,0),(4,0),(5,0),(2,1),(3,1),(10,2),(11,2),(11,0),(-2,-4),(-2,-5),(-2,-6)]
glider :: Board
glider = [(3,0),(3,1),(3,2),(2,2),(1,1)]
-- play and display function inspired by
-- https://github.com/nbartlomiej/gameofhaskell/blob/master/GameOfHaskell.hs
terminalCols = 79
terminalRows = 24
terminalLeft = 0 - terminalCols `div` 2
terminalRight = terminalCols `div` 2
terminalTop = 0 - terminalRows `div` 2
terminalBottom = terminalRows `div` 2
main = do
clearScreen
play board
play :: Board -> IO ()
play board = do
display board
getLine -- comment out and uncomment below for "animation"
-- nanosleep 200000000 -- 200 ms
play $ next board
display :: Board -> IO ()
display board = do
mapM_ (\x -> do
mapM_ (\y -> putChar $ if alive board (y,x) then 'X' else ' ') [terminalLeft .. terminalRight]
putChar '\n'
) [terminalTop .. terminalBottom]