game-of-life 0.1.0.4 → 0.1.0.5
raw patch · 4 files changed
+136/−12 lines, 4 filesdep +hspecdep ~base
Dependencies added: hspec
Dependency ranges changed: base
Files
- Main.hs +7/−4
- Simulation.hs +16/−6
- game-of-life.cabal +19/−2
- test/Spec.hs +94/−0
Main.hs view
@@ -6,11 +6,14 @@ import Control.Monad(replicateM) import System.Random import Control.Concurrent(threadDelay)-import Data.List(intersperse)+import Data.List(intercalate) import Data.Array +-- Delay between iterations (μs) delay :: Int delay = 50000++-- Probability of a cell being Alive on board generation density :: Float density = 0.37 @@ -21,7 +24,7 @@ _ <- Curses.cursSet Curses.CursorInvisible Curses.timeout 0 (rows, cols) <- Curses.scrSize- randomBoard (rows) (cols-1) >>= life window+ randomBoard rows (cols-1) >>= life window Curses.resetParams Curses.endWin @@ -40,7 +43,7 @@ Curses.wRefresh window boardRep :: Board -> String-boardRep board = concat . intersperse "\n" $ [rowStrings r | r <- [0..rs-1]]+boardRep board = intercalate "\n" [rowStrings r | r <- [0..rs-1]] where rowStrings :: Int -> String rowStrings r = concat [show (grid ! r ! c) | c <- [0..cs-1]]@@ -59,7 +62,7 @@ randomRow = randomListOfCells >>= \cells -> return $ listArray (0, cs-1) cells randomListOfCells :: IO [State]- randomListOfCells = replicateM cs randomIO >>= return . map randomCell+ randomListOfCells = fmap (map randomCell) (replicateM cs randomIO) randomCell :: Float -> State randomCell r
Simulation.hs view
@@ -1,5 +1,7 @@ module Simulation where +-- Chose array for it's constant time random access+-- (Haskell's List has linear time random access) import Data.Array data State = Alive | Dead deriving (Eq)@@ -17,33 +19,40 @@ show Alive = "■" show Dead = " " -directions :: [Coord]-directions = [Coord y x | y <- [-1..1], x <- [-1..1], not (x==0 && y==0)]-+-- Gets the coordinates of the cells that are alive around a+-- coordinate neighbors :: Board -> Coord -> [Coord] neighbors board coord = filter (\c -> state board c == Alive) validCoords where validCoords = filter (validCoord board) coordsAround coordsAround = map (+ coord) directions+ directions = [Coord y x | y <- [-1..1], x <- [-1..1], not (x==0 && y==0)] +-- Determines whether a given coordinate is in bounds validCoord :: Board -> Coord -> Bool validCoord board (Coord y x) = xValid && yValid where xValid = x >= 0 && x < bCols board yValid = y >= 0 && y < bRows board +-- Get number of neighbors numNeighbors :: Board -> Coord -> Int numNeighbors board coord = length $ neighbors board coord +-- Gets the state of a coordinate on the board +-- (TODO: make this a total function) state :: Board -> Coord -> State-state board (Coord y x) = (bGrid board) ! y ! x+state board (Coord y x) = bGrid board ! y ! x +-- Executes a single step for a coordinate on the board singleStep :: Board -> Coord -> State singleStep board coord = stepCell currentState bors where currentState = state board coord bors = numNeighbors board coord +-- Given the current state, and the # of alive neighbors,+-- returns the new state of the cell stepCell :: State -> Int -> State stepCell Dead 3 = Alive stepCell Dead _ = Dead@@ -52,6 +61,7 @@ | n > 3 = Dead | otherwise = Alive +-- Runs one iteration of game of life step :: Board -> Board step board = board {bGrid = newGrid} where@@ -59,8 +69,8 @@ newGrid = listArray (0, rs-1) $ map newRow [0..rs-1] newRow :: Int -> Row- newRow y = listArray (0,cs-1) $- [singleStep board (Coord y x) | x <- [0..(cs)-1]]+ newRow y = listArray (0,cs-1)+ [singleStep board (Coord y x) | x <- [0..cs-1]] cs = bCols board rs = bRows board
game-of-life.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: game-of-life-version: 0.1.0.4 +version: 0.1.0.5 synopsis: Conway's Game of Life -- description: license: MIT@@ -20,7 +20,7 @@ main-is: Main.hs other-modules: Simulation -- other-extensions: - build-depends: base >=4.7 && <4.8,+ build-depends: base >=4.7 && <5, random, hscurses, array,@@ -28,3 +28,20 @@ -- hs-source-dirs: default-language: Haskell2010 -- ghc-options: -O2++test-suite spec+ type:+ exitcode-stdio-1.0+ ghc-options:+ -Wall+ hs-source-dirs:+ test,+ ./+ main-is:+ Spec.hs+ default-language: Haskell2010+ other-modules: Simulation+ build-depends:+ base >=4.7 && <5+ , hspec == 2.*+ , array
+ test/Spec.hs view
@@ -0,0 +1,94 @@+module Main where++import Test.Hspec+import Simulation+import Data.Array++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "stepCell" $ do+ it "revives dead cells if num_neighbors is 3" $ do+ (stepCell Dead 3) `shouldBe` Alive++ it "does not revive dead cells ∀ num_neighbors in [0,3) ∪ (3,8]" $ do+ mapM_ (\x -> (stepCell Dead x) `shouldBe` Dead) ([0..2] ++ [4..8])++ it "kills live cells ∀ num_neighbors in [0,1] ∪ [4,8]" $ do+ mapM_ (\x -> (stepCell Alive x) `shouldBe` Dead) ([0..1] ++ [4..8])++ it "keeps cells alive ∀ num_neighbors in [2,3]" $ do+ mapM_ (\x -> (stepCell Alive x) `shouldBe` Alive) [2,3]++ describeStep++describeStep :: Spec+describeStep = do+ describe "step" $ do+ it "should handle a very simple board" $ do+ let start = [ + [0, 0, 0],+ [0, 1, 0],+ [0, 0, 0] + ]+ let expected_end = [+ [0, 0, 0],+ [0, 0, 0],+ [0, 0, 0] + ]+ shouldBe True $ + test_game start 1 expected_end+ + it "Should progress correctly" $ do+ let zeroth_step = [+ [0, 1, 0],+ [1, 1, 1],+ [0, 1, 0] + ]+ let first_step = [+ [1, 1, 1],+ [1, 0, 1],+ [1, 1, 1] + ]+ let second_step = [+ [1, 0, 1],+ [0, 0, 0],+ [1, 0, 1] + ]++ let board = toBoard zeroth_step+ let two_steps = fmap toTestFormat $ (take 3 . iterate step) board+ shouldBe two_steps [zeroth_step, first_step, second_step]+++test_game :: [[Int]] -> Int -> [[Int]] -> Bool+test_game input n expected = toTestFormat (iterate step board !! n) == expected+ where+ board = toBoard input++-- Everything below is glue to convert to and from the test+-- format (which is easier to type and see visually), and+-- the actual type of the board++toTestFormat :: Board -> [[Int]]+toTestFormat board = (fmap . fmap) stateToInt $ elems (fmap elems (bGrid board))++toBoard :: [[Int]] -> Board+toBoard input = Board {bGrid = grid, bRows = rows, bCols = cols}+ where+ grid = listArray (0, length input-1) $ map rowToArray input+ rowToArray row = listArray (0, length row-1) $ map intToState row+ rows = length input+ -- This is a partial function, but it's only used for+ -- tests and an empty array isn't valid input+ cols = length (input !! 0)++stateToInt :: State -> Int+stateToInt Alive = 1+stateToInt Dead = 0++intToState :: Int -> State+intToState 1 = Alive+intToState _ = Dead