[Haskell-beginners] A game of life implementation
Michel Haber
michelhaber1994 at gmail.com
Sat Feb 23 15:36:24 UTC 2019
Hello everyone,
I'm a new haskeller, and (like many others, I assume) I thought I'd try my
hand
at Conway's "Game of Life".
So here is my code that seems to work (up to this point).
I am looking for feedback in order to improve my Haskell code on all levels.
Especially (In no particular order):
0- Find and fix bugs
1- Write more performance optimal code.
2- Good use of polymorphic types.
3- Good use of higher-order functions.
4- Good use of Haskell's common (and uncommon) abstractions.
5- Coding style (I'm finding it hard to let go of the function types :p)
6- Good code structuring allowing for reuse and updates.
7- Best options to give to the compiler.
8- Anything else that comes to your mind!
So I'd really appreciate your feedback :)
This is the wikipedia reference for the game of life:
https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
And this is the code:
START OF CODE
-- Game of life Haskell Implementation
import Data.List
import Control.Monad.State
import qualified Data.Map as M
-- The cell state is isomorphic to Bool.
type CellState = Bool
-- The coordinates of a cell
type Coord = (Int, Int)
-- The board size is (length, width)
type Size = (Int, Int)
-- The state of the board is simply the coordinates of its live cells
type Board = [Coord]
-- The state carried in the State Monad, used to count tags for cells
type TallyState = State (M.Map Coord (CellState, Int)) ()
-- The type of the game rules
type Rules = (Coord, CellState, Int) -> CellState
-- The type for the neighbor functions
type Neighbors = Coord -> [Coord]
-- Tally the live neighbors of live cells and relevant dead cells
tallyBoard :: Neighbors -> Board -> TallyState
tallyBoard nb = mapM_ $ tallyCoord nb
-- Tally a live cell: Set its state to True (alive) and tag its neighbors
-- This function takes the neighbors function as its first argument. We can
use
-- different neighbor functions to change the zone of influence of a cell
tallyCoord :: Neighbors -> Coord -> TallyState
tallyCoord nb c = do
let merge (a1,b1) (a2,b2) = (a1 || a2, b1 + b2)
s <- get
let s' = M.insertWith merge c (True, 0) s
let neighbors = nb c
put $ foldl' (\acc x -> M.insertWith merge x (False, 1) acc) s'
neighbors
-- Extract the results from a TallyState
toResults :: TallyState -> [(Coord, CellState, Int)]
toResults s = map flatten . M.toList . execState s $ M.empty
where flatten (x,(y,z)) = (x,y,z)
-- Use A Rules and Neighbors function to advance the board one step in time
advance :: Rules -> Neighbors -> Board -> Board
advance rules nb = map first . filter rules . toResults . tallyBoard nb
where first (x,_,_) = x
-- The standard neighbors function
stdNeighbors :: Neighbors
stdNeighbors (x,y) =
[ (a,b)
| a <- [x-1, x, x+1]
, b <- [y-1, y, y+1]
, (a /= x) || (b /= y)
]
-- Standard game rules
stdRules :: Size -> Rules
stdRules (a,b) ((x,y),_,_)
| (x < 0) || (y < 0) || (x >= a) || (y >= b) = False
stdRules _ (_,True,c)
| (c == 2) || (c == 3) = True
| otherwise = False
stdRules _ (_,False,3) = True
stdRules _ _ = False
-- Main loop
loop :: (Board -> Board) -> Board -> IO ()
loop f b = do
print b
unless (null b) $ loop f (f b)
-- Main function
main :: IO ()
main = do
putStrLn "Choose board size (x,y)"
input <- getLine
putStrLn "Choose starting points"
start <- getLine
putStrLn "Game:"
let size = read input
let rules = stdRules size
let initial = map read . words $ start
let game = advance rules stdNeighbors
loop game initial
END OF CODE
Thanks :)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/beginners/attachments/20190223/2bf3507d/attachment.html>
More information about the Beginners
mailing list