patternMinor
Snake in Haskell
Viewed 0 times
haskellsnakestackoverflow
Problem
I wrote a partial implementation of Snake in Haskell. As of now, it only supports the movement of the snake. However, since the code is getting complex, I'm requesting for a review of the code before I add on the food, growing and scoring functions.
I'd be glad to hear any suggestions on how this code could be written in a more idiomatic and simpler way (namely,
``
then Just (head, snake'', tail')
else Nothing
where (head, snake') = case S.viewl snake of
S.EmptyL -> error "snake is empty"
x S.: (x, xs)
tail = case S.viewr snake of
S.EmptyR -> error "snake is empty"
_ S.:> x -> x
tail' = stepCoord tail dir
snake'' = snake' S.|> tail'
data Game = Game {
grid :: Grid,
snake :: Snake
} deriving Show
newGame :: Game
newGame = Game {
g
I'd be glad to hear any suggestions on how this code could be written in a more idiomatic and simpler way (namely,
stepSnake and step are long and messy).``
-- Snake implementation in Haskell.
module Snake (
newGame
) where
import Control.Lens (set, ix)
import Data.Foldable (toList)
import qualified Data.Sequence as S
-- Size of the grid
n :: Int
n = 5
data Marking = Empty | Food | Snake
deriving (Show, Read, Eq)
type Grid = [[Marking]]
emptyGrid :: Int -> Grid
emptyGrid n = replicate n $ replicate n Empty
type Coord = (Int, Int)
data Direction = N | E | W | S
deriving (Show, Read, Eq)
stepCoord :: Coord -> Direction -> Coord
stepCoord (i, j) dir = case dir of
N -> (i - 1, j)
E -> (i, j + 1)
W -> (i, j - 1)
S -> (i + 1, j)
setCoord :: Marking -> Coord -> Grid -> Grid
setCoord mark (i, j) grid = set (ix i . ix j) mark grid
validCoord :: Coord -> Bool
validCoord (i, j) = i > 0 && j > 0 && i Snake
newSnake n = S.fromList [c, c', c'']
where c = (n div 2, n div 2)
c' = stepCoord c E
c'' = stepCoord c' E
stepSnake :: Snake -> Direction -> Maybe (Coord, Snake, Coord)
stepSnake snake dir = if validCoord tail' && not (tail' elem` toList snake')then Just (head, snake'', tail')
else Nothing
where (head, snake') = case S.viewl snake of
S.EmptyL -> error "snake is empty"
x S.: (x, xs)
tail = case S.viewr snake of
S.EmptyR -> error "snake is empty"
_ S.:> x -> x
tail' = stepCoord tail dir
snake'' = snake' S.|> tail'
data Game = Game {
grid :: Grid,
snake :: Snake
} deriving Show
newGame :: Game
newGame = Game {
g
Solution
It's a little dangerous to define a top-level constant like
I can see why you may have used a
Or you can write a handy little utility function and reorder your arguments to write a point-free function.
Generally all your types should be at the top of the file, i.e.
I might use a list comprehension in writing
Right-hand sides composed solely of an if-statement are good candidates for writing guards.
This is a very dense function though, I think it might be easier to write if you wrap the version using a
Use
n, the name isn't descriptive or distinctive and you end up shadowing it quite a bit. A compiling but incorrect typo is almost inevitable. Since you gave it a definitive comment, why not just give it that name instead? Try defaultGridSize.I can see why you may have used a
case statement in stepCoord (to save typing out stepCoord (i, j) repeatedly), but I have a strong preference in the other direction. All pattern-matching which can happen at the top-level generally should.stepCoord :: Coord -> Direction -> Coord
stepCoord (i, j) N = (i - 1, j)
-- etc...Or you can write a handy little utility function and reorder your arguments to write a point-free function.
tuply :: (a -> c, b -> d) -> (a, b) -> (c, d) -- tuple, apply, two ply!
tuply (f, g) (a, b) = (f a, g b)
stepCoord :: Direction -> Coord -> Coord
stepCoord N = tuply (subtract 1, id )
stepCoord E = tuply (id , + 1 )
stepCoord W = tuply (id , subtract 1)
stepCoord S = tuply (+ 1 , id )Generally all your types should be at the top of the file, i.e.
Snake and Game.I might use a list comprehension in writing
newSnake to clean it up a little and reduce potential errors from referencing the wrong c('(')) value.newSnake :: Int -> Snake
newSnake boardSize = S.fromList [(mid, j) | j <- [mid .. mid + 2]]
where mid = boardSize `div` 2Right-hand sides composed solely of an if-statement are good candidates for writing guards.
stepSnake snake dir
| validCoord tail' && not (tail' `elem` toList snake') = Just (head, snake'', tail')
| otherwise = NothingThis is a very dense function though, I think it might be easier to write if you wrap the version using a
Seq snake around a primitive list version. Your snakes will presumably never grow so long as to actually cause a user noticeable slowdown between steps due to list processing, so you might think about tossing out Seq altogether.stepSnake :: Snake -> Direction -> Maybe (Coord, Snake, Coord)
stepSnake snake dir = fmap (second fromList) (stepSnakeList (toList snake) dir)
where second f (a, b, c) = (a, f b, c)
stepSnakeList :: [Coord] -> Direction -> Maybe (Coord, [Coord], Coord)
stepSnakeList snake dir
| validCoord newHead && not (newHead `elem` snake) = Just (oldTail, newSnake, newHead)
| otherwise = Nothing
where
oldTail = head snake
body = tail snake
oldHead = last snake
newHead = stepCoord oldHead dir
newSnake = body ++ [newHead]Use
foldl' from Data.List instead of foldl for newGame and step. Why? It's a long story.Code Snippets
stepCoord :: Coord -> Direction -> Coord
stepCoord (i, j) N = (i - 1, j)
-- etc...tuply :: (a -> c, b -> d) -> (a, b) -> (c, d) -- tuple, apply, two ply!
tuply (f, g) (a, b) = (f a, g b)
stepCoord :: Direction -> Coord -> Coord
stepCoord N = tuply (subtract 1, id )
stepCoord E = tuply (id , + 1 )
stepCoord W = tuply (id , subtract 1)
stepCoord S = tuply (+ 1 , id )newSnake :: Int -> Snake
newSnake boardSize = S.fromList [(mid, j) | j <- [mid .. mid + 2]]
where mid = boardSize `div` 2stepSnake snake dir
| validCoord tail' && not (tail' `elem` toList snake') = Just (head, snake'', tail')
| otherwise = NothingstepSnake :: Snake -> Direction -> Maybe (Coord, Snake, Coord)
stepSnake snake dir = fmap (second fromList) (stepSnakeList (toList snake) dir)
where second f (a, b, c) = (a, f b, c)
stepSnakeList :: [Coord] -> Direction -> Maybe (Coord, [Coord], Coord)
stepSnakeList snake dir
| validCoord newHead && not (newHead `elem` snake) = Just (oldTail, newSnake, newHead)
| otherwise = Nothing
where
oldTail = head snake
body = tail snake
oldHead = last snake
newHead = stepCoord oldHead dir
newSnake = body ++ [newHead]Context
StackExchange Code Review Q#90054, answer score: 5
Revisions (0)
No revisions yet.