packages feed

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 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