Exyxtra
Exyxtra

Reputation: 11

Haskell Snake program move fuction

I need to do a project in Haskell, which does a snake program, but I'm stuck the last part.

These two test case should give True, but I dont have an idea how should I do that:

doInstruction Move (North, [(5, 7), (5, 6)], 2)        == (North, [(5, 8), (5, 7)], 2)
doInstruction Move (East, [(2, 1), (1, 1), (1, 0)], 3) == (East, [(3, 1), (2, 1), (1, 1)], 3)

My code until that part

data Dir = West | North | East | South deriving (Eq, Show)

type Position = (Int, Int)

type Snake = (Dir, [Position], Int)

data Instruction = Move | Turn Dir deriving (Eq, Show)

isOppositeDir :: Dir -> Dir -> Bool
isOppositeDir West East = True
isOppositeDir East West = True
isOppositeDir North South = True
isOppositeDir South North = True
isOppositeDir _ _ = False

oppositeDir :: Dir -> Dir
oppositeDir West = East
oppositeDir East = West
oppositeDir North = South
oppositeDir South = North

nextPos :: Dir -> Position -> Position
nextPos x (a,b)
    | x == West = (a - 1 , b)
    | x == East = (a + 1 , b)
    | x == North = (a , b + 1)
    | x == South = (a , b - 1)
    | otherwise = (a , b)

doInstruction :: Instruction -> Snake -> Snake
doInstruction (Turn dir) (x, p, y) =  if isOppositeDir dir x then (x,p,y) else (dir,p,y)

Upvotes: 1

Views: 104

Answers (1)

Mark Seemann
Mark Seemann

Reputation: 233307

The following is in no way a complete implementation of doInstruction, but should enable you to move forward. It passes your two test cases.

Just show me the code

Okay, here's a partial implementation of doInstruction:

doInstruction :: Instruction -> Snake -> Snake
doInstruction Move (currDir, p@(x:_), l) = (currDir, take l $ nextPos currDir x : p, l)

If you load it into GHCi, it'll pass the two test cases from the OP:

> doInstruction Move (North, [(5, 7), (5, 6)], 2)        == (North, [(5, 8), (5, 7)], 2)
True
> doInstruction Move (East, [(2, 1), (1, 1), (1, 0)], 3) == (East, [(3, 1), (2, 1), (1, 1)], 3)
True

It'll still crash on lots of other input because it doesn't handle e.g. Turn instructions.

Explanation

doInstruction Move handles the Move instruction exclusively. (currentDir, p@(x:_), l) pattern-matches the snake's current direction, it's positions, and it's length (I'm assuming that the last integer is the length).

The p@ syntax captures the entire position into the variable p. The function also needs the head of the list, so it also uses pattern matching on the list. Here, it matches only the head and ignores the tail: (x:_). The underscore is the wildcard character that ignores the match, in this case the tail.

This pattern match is also partial, because it's not going to match an empty list. You probably don't allow snakes of zero length, but if you did, this wouldn't match.

The function uses nextPos to calculate the next position of the head of the snake. It conses (uses :) the resulting Position value onto the p list. This in itself creates a list that's too long, but then the function uses take l to take only the first l elements of that list.

Next steps

You should add more cases to the doInstruction function, e.g.

doInstruction :: Instruction -> Snake -> Snake
doInstruction Move (currDir, p@(x:_), l) = (currDir, take l $ nextPos currDir x : p, l)
doInstruction (Turn dir) snake = -- More code goes here...

Upvotes: 1

Related Questions