commit 2c99555ed7c327324ade873b29e8319b05198c2d Author: Aadhavan Srinivasan Date: Fri Jan 17 11:44:24 2025 -0500 First commit diff --git a/HELP.md b/HELP.md new file mode 100644 index 0000000..bd8d5f9 --- /dev/null +++ b/HELP.md @@ -0,0 +1,87 @@ +# Help + +## Running the tests + +To run the test suite, execute the following command: + +```bash +stack test +``` + +#### If you get an error message like this... + +``` +No .cabal file found in directory +``` + +or + +``` +RedownloadInvalidResponse Request { +... +} + "/home/username/.stack/build-plan/lts-xx.yy.yaml" (Response {responseStatus = Status {statusCode = 404, statusMessage = "Not Found"}, +``` + +You are probably running an old stack version and need +to upgrade it. Try running: + +```bash +stack upgrade +``` + +Or see other options for upgrading at [Stack documentation](https://docs.haskellstack.org/en/stable/install_and_upgrade/#upgrade). + +#### Otherwise, if you get an error message like this... + +``` +No compiler found, expected minor version match with... +Try running "stack setup" to install the correct GHC... +``` + +Just do as it says and it will download and install +the correct compiler version: + +```bash +stack setup +``` + +If you want to play with your solution in GHCi, just run the command: + +```bash +stack ghci +``` + +## Submitting your solution + +You can submit your solution using the `exercism submit src/GameOfLife.hs package.yaml` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Haskell track's documentation](https://exercism.org/docs/tracks/haskell) +- The [Haskell track's programming category on the forum](https://forum.exercism.org/c/programming/haskell) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +## Getting Started + +Please refer to the [installation](https://exercism.io/tracks/haskell/installation) +and [learning](https://exercism.io/tracks/haskell/learning) help pages. + +## Feedback, Issues, Pull Requests + +The [exercism/haskell](https://github.com/exercism/haskell) repository on +GitHub is the home for all of the Haskell exercises. + +If you have feedback about an exercise, or want to help implementing a new +one, head over there and create an issue. We'll do our best to help you! \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a438011 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Game of Life + +Welcome to Game of Life on Exercism's Haskell Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Introduction + +[Conway's Game of Life][game-of-life] is a fascinating cellular automaton created by the British mathematician John Horton Conway in 1970. + +The game consists of a two-dimensional grid of cells that can either be "alive" or "dead." + +After each generation, the cells interact with their eight neighbors via a set of rules, which define the new generation. + +[game-of-life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life + +## Instructions + +After each generation, the cells interact with their eight neighbors, which are cells adjacent horizontally, vertically, or diagonally. + +The following rules are applied to each cell: + +- Any live cell with two or three live neighbors lives on. +- Any dead cell with exactly three live neighbors becomes a live cell. +- All other cells die or stay dead. + +Given a matrix of 1s and 0s (corresponding to live and dead cells), apply the rules to each cell, and return the next generation. + +## Source + +### Created by + +- @tofische + +### Based on + +Wikipedia - https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life \ No newline at end of file diff --git a/game-of-life.cabal b/game-of-life.cabal new file mode 100644 index 0000000..9fa105a --- /dev/null +++ b/game-of-life.cabal @@ -0,0 +1,34 @@ +cabal-version: 1.12 + +-- This file has been generated from package.yaml by hpack version 0.37.0. +-- +-- see: https://github.com/sol/hpack + +name: game-of-life +version: 1.0.0.0 +build-type: Simple + +library + exposed-modules: + GameOfLife + other-modules: + Paths_game_of_life + hs-source-dirs: + src + ghc-options: -Wall + build-depends: + base + default-language: Haskell2010 + +test-suite test + type: exitcode-stdio-1.0 + main-is: Tests.hs + other-modules: + Paths_game_of_life + hs-source-dirs: + test + build-depends: + base + , game-of-life + , hspec + default-language: Haskell2010 diff --git a/package.yaml b/package.yaml new file mode 100644 index 0000000..101d175 --- /dev/null +++ b/package.yaml @@ -0,0 +1,21 @@ +name: game-of-life +version: 1.0.0.0 + +dependencies: + - base + +library: + exposed-modules: GameOfLife + source-dirs: src + ghc-options: -Wall + # dependencies: + # - foo # List here the packages you + # - bar # want to use in your solution. + +tests: + test: + main: Tests.hs + source-dirs: test + dependencies: + - game-of-life + - hspec diff --git a/src/GameOfLife.hs b/src/GameOfLife.hs new file mode 100644 index 0000000..e3808b4 --- /dev/null +++ b/src/GameOfLife.hs @@ -0,0 +1,59 @@ +module GameOfLife (tick) where + +type Grid = [[Int]] + +-- Maps a function over every element in a grid +gridMap :: (a -> b) -> [[a]] -> [[b]] +gridMap f = map (map f) + +-- Param 1: The current value of the cell +-- Param 2: The number of living neighbors +-- Return value: The next value of the cell +nextIterHelper :: Int -> Int -> Int +nextIterHelper 1 2 = 1 +nextIterHelper 1 3 = 1 +nextIterHelper 0 3 = 1 +nextIterHelper _ _ = 0 + +-- Returns the value of a specific cell in a grid - out-of-bounds is always zero +cellVal :: Grid -> (Int, Int) -> Int +cellVal grid (x,y) + | x < 0 || y < 0 = 0 + | y >= length (grid) || x >= length (grid!!0) = 0 + | otherwise = (grid!!y)!!x + +-- Given a tuple (representing the location of a cell), +-- it returns a list of tuples that are the locations of the neighbors +neighbors :: (Int, Int) -> [(Int, Int)] +neighbors (x, y) = [(a,b) | a <- dx, b <- dy, a /= x || b /= y] + where dx = [x-1..x+1] + dy = [y-1..y+1] + +-- Predicate - returns whether the cell is alive or not +isAlive :: Grid -> (Int,Int) -> Bool +isAlive grid tup = if (cellVal grid tup) == 1 then True else False + +-- Filter function that takes a list of indexes in a grid, and only returns the ones +-- where the cell value is 1 +keepAliveCells :: Grid -> [(Int,Int)] -> [(Int,Int)] +keepAliveCells grid tups = filter (isAlive grid) tups + +-- Returns the number of alive neighbors of the given cell +numAliveNeighbors :: Grid -> (Int,Int) -> Int +numAliveNeighbors grid tup = (length . keepAliveCells grid . neighbors) tup + +-- Given the grid and the location of a cell, returns the next value of the cell +nextIter :: Grid -> (Int, Int) -> Int +nextIter grid tup = nextIterHelper (cellVal grid tup) (numAliveNeighbors grid tup) + +-- Returns a grid of all cell indices in the grid +cellIndices :: Grid -> [[(Int,Int)]] +cellIndices [[]] = [[]] +cellIndices grid = [[(x,y) | x <- [0..xdim-1]] | y <- [0..ydim-1]] + where xdim = length (grid!!0) + ydim = length grid + +tick :: Grid -> Grid +tick [] = [] +tick [[]] = [[]] +tick grid = gridMap (nextIter grid) (cellIndices grid) diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..1158782 --- /dev/null +++ b/stack.yaml @@ -0,0 +1 @@ +resolver: lts-20.18 diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000..d4302c7 --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,12 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + sha256: 9fa4bece7acfac1fc7930c5d6e24606004b09e80aa0e52e9f68b148201008db9 + size: 649606 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/18.yaml + original: lts-20.18 diff --git a/test/Tests.hs b/test/Tests.hs new file mode 100644 index 0000000..8ca7db9 --- /dev/null +++ b/test/Tests.hs @@ -0,0 +1,125 @@ +{-# OPTIONS_GHC -fno-warn-type-defaults #-} +{-# LANGUAGE RecordWildCards #-} + +import Data.Foldable (for_) +import Test.Hspec (Spec, describe, it, shouldBe) +import Test.Hspec.Runner (configFailFast, defaultConfig, hspecWith) + +import GameOfLife (tick) + +main :: IO () +main = hspecWith defaultConfig {configFailFast = True} specs + +specs :: Spec +specs = describe "tick" $ for_ cases test + where + + test Case{..} = it description assertion + where + assertion = tick matrix `shouldBe` expected + +data Case = Case { description :: String + , matrix :: [[Int]] + , expected :: [[Int]] + } + +cases :: [Case] +cases = [ Case { description = "Empty matrix" + , matrix = [] + , expected = [] + } + , Case { description = "Live cells with zero live neighbors die" + , matrix = [ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0] + ] + , expected = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ] + } + , Case { description = "Live cells with only one live neighbor die" + , matrix = [ + [0, 0, 0], + [0, 1, 0], + [0, 1, 0] + ] + , expected = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ] + } + , Case { description = "Live cells with two live neighbors stay alive" + , matrix = [ + [1, 0, 1], + [1, 0, 1], + [1, 0, 1] + ] + , expected = [ + [0, 0, 0], + [1, 0, 1], + [0, 0, 0] + ] + } + , Case { description = "Live cells with three live neighbors stay alive" + , matrix = [ + [0, 1, 0], + [1, 0, 0], + [1, 1, 0] + ] + , expected = [ + [0, 0, 0], + [1, 0, 0], + [1, 1, 0] + ] + } + , Case { description = "Dead cells with three live neighbors become alive" + , matrix = [ + [1, 1, 0], + [0, 0, 0], + [1, 0, 0] + ] + , expected = [ + [0, 0, 0], + [1, 1, 0], + [0, 0, 0] + ] + } + , Case { description = "Live cells with four or more neighbors die" + , matrix = [ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1] + ] + , expected = [ + [1, 0, 1], + [0, 0, 0], + [1, 0, 1] + ] + } + , Case { description = "Bigger matrix" + , matrix = [ + [1, 1, 0, 1, 1, 0, 0, 0], + [1, 0, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 0], + [1, 0, 0, 0, 1, 1, 0, 0], + [1, 1, 0, 0, 0, 1, 1, 1], + [0, 0, 1, 0, 1, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 1, 1] + ] + , expected = [ + [1, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 0], + [1, 0, 1, 1, 1, 1, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 1], + [1, 1, 0, 0, 1, 0, 0, 1], + [1, 1, 0, 1, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 1] + ] + } + ]