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)
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...
Related
I have an input of [ ( (Int,Int) , (Int,Int) ) ] which is the coordinate of the ends of the path. All paths run either horizontally (same y-value for both ends) or vertically (same x-value for both ends).
So I want to write a function that generates all the coordinates of the path from the input.
For example, the input pair of ends is [((0, 0), (0, 3)), ((0, 2), (2, 2))](this is two paths), the output of the function I need is [[(0, 0), (0, 1), (0, 2), (0, 3)], [(0, 2), (1, 2), (2, 2)]]
getPaths :: [ ( (Integer,Integer) , (Integer,Integer) ) ] -> [[(Integer,Integer)]]
getPaths [((xa, ya), (xb, yb))]
| xa == xb : [(xa, yb + 1) | yb < ya]++
| xa == xb : [(xa, ya + 1) | ya < yb]++
| ya == yb : [(xa + 1, ya) | xa < xb]++
| ya == yb : [(xb + 1, ya) | xb < xa]++
I'm not sure if its right, could anyone have a look at it?
I think it is better to first make a function that works with a single pair of coordinates:
path :: ((Int, Int), (Int, Int)) -> [(Int, Int)]
path = …
then getPaths is just a mapping of this:
getPaths :: [((Int, Int), (Int, Int))] -> [[(Int, Int)]]
getPaths = map path
as for the path, you can make use of ranges, indeed [x₁ .. x₂] will produce all (integral) values between x₁ and x₂ (both inclusive).
The function thus can be constructed with list comprehension with:
path :: ((Int, Int), (Int, Int)) -> [(Int, Int)]
path ((x₁, y₁), (x₂, y₂)) = [ … | … <- …, … <- … ]
where you still need to fill in the … parts.
I need to draw arrows between two arbitrary "nodes". The arrow ends needs to enter or exit the nodes from one of the four cardinal directions: N, S, E, W.
data Dir = N | S | E | W
deriving (Eq, Ord, Show)
cir, circles :: Diagram B
cir = circle 0.3 # showOrigin # lw thick
circles = (cir # named "1") ||| strutX 3 ||| (cir # named "2")
ctrlPoint :: Dir -> V2 Double
ctrlPoint N = r2 (0, 1)
ctrlPoint S = r2 (0, -1)
ctrlPoint E = r2 (1, 0)
ctrlPoint W = r2 (-1, 0)
-- This function should specify an arrow shaft entering nodes from directions dir1 and dir2
shaft :: Dir -> Dir -> Trail V2 Double
shaft dir1 dir2 = trailFromSegments [bézier3 (controlPoint dir1) (controlPoint dir2) (r2 (3, 0))]
example = circles # connect' (with ... & arrowShaft .~ shaft N S ) "1" "2"
In the picture above, the arrow enters correctly from North in the first circle, and South in the second.
However, if I setup the points vertically, everything is rotated:
circles = (cir # named "1") === strutY 3 === (cir # named "2")
This is not correct, because I wanted the arrow to enter from North and South, respectively. It seems the shaft of the arrow is rotated altogether...
How to write my function shaft :: Dir -> Dir -> Trail V2 Double?
Thanks
I found an answer using arrowFromLocatedTrail' instead:
-- control points for bézier curves
control :: Dir -> V2 Double
control N = r2 (0, 0.5)
control S = r2 (0, -0.5)
control E = r2 (0.5, 0)
control W = r2 (-0.5, 0)
-- shaft of arrows
shaft :: (P2 Double, Dir) -> (P2 Double, Dir) -> Located (Trail V2 Double)
shaft (p, d) (p', d') = trailFromSegments [bézier3 (control d) ((p' .-. p) - (control d')) (p' .-. p)] `at` p
-- create a single arrow
mkArrow :: (P2 Double, Dir) -> (P2 Double, Dir) -> Diagram B
mkArrow a b = arrowFromLocatedTrail' (with & arrowHead .~ dart
& lengths .~ veryLarge
& shaftStyle %~ lw thick) (shaft a b)
This version performs the necessary transformations:
bézier3 (control d) ((p' .-. p) + (control d')) (p' .-. p)
Here is the signature ofbézier:
bézier3 :: v n -> v n -> v n -> Segment Closed v n
It takes 3 vectors, named here V1, V2 and V3.
bézier curve are by default not located in Diagrams, they just specify how to move.
So, to draw the bézier curve, we set:
V1 = control d
V2 = (p' .-. p) + (control d')
V3 = p' .-. p
The resulting bézier curve will located at p.
I need to write a simple snake game for my final school project but i got stuck on my last function. I guess I don't really know how to use the datastructures perfectly. The funcition doInstruction should give back True for this testcase doInstruction (Turn East) (North, [(5, 7), (5, 6)], 2) == (East, [(5, 7), (5, 6)], 2) What am I doing wrong?
My code:
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 ins s
| ins == Turn Dir = (oppositeDir Dir, [Position], length [Position])
The errors I get:
E:\\ELTE\2019_1_Funkcprog\hf13.hs:34:19: error:
Data constructor not in scope: Dir :: Dir
|
34 | | ins == Turn Dir = (oppositeDir Dir, [Position], length [Position])
| ^^^
E:\\ELTE\2019_1_Funkcprog\hf13.hs:34:38: error:
Data constructor not in scope: Dir :: Dir
|
34 | | ins == Turn Dir = (oppositeDir Dir, [Position], length [Position])
| ^^^
E:\\ELTE\2019_1_Funkcprog\hf13.hs:34:44: error:
Data constructor not in scope: Position :: Position
|
34 | | ins == Turn Dir = (oppositeDir Dir, [Position], length [Position])
| ^^^^^^^^
E:\\ELTE\2019_1_Funkcprog\hf13.hs:34:63: error:
Data constructor not in scope: Position
|
34 | | ins == Turn Dir = (oppositeDir Dir, [Position], length [Position])
| ^^^^^^^^
You need to use pattern matching to "unpack" the data constructor. Variables in Haskell start with a lowercase. We thus can unpack the Instruction object, and the 3-tuple that represents the snake:
doInstruction :: Instruction -> Snake -> Snake
doInstruction (Turn dir) (_, p, l) = (dir, p, l)
I want to be able to filter the below data so I can find specific data for example if I wanted to find an item with only apples it would look similar to this output: [("apple","crate",6),("apple","box",3)]
fruit :: [(String, String, Int)]
fruit = [("apple", "crate", 6), ("pear", "crate", 5), ("mango", "box", 4),
("apple", "box", 3), ("banana", "box", 5), ("pear", "box", 10), ("apricot",
"box", 4), ("peach", "box", 5), ("walnut", "box", 4), ("blueberry", "tray", 10),
("blackberry", "tray", 4), ("watermelon", "piece", 8), ("marrow", "piece", 7),
("hazelnut", "sack", 2), ("walnut", "sack", 4)]
first :: (a, b, c) -> a
first (x, _, _) = x
second :: (a, b, c) -> b
second (_, y, _) = y
third :: (a, b, c) -> c
third (_, _, z) = z
A couple of alternatives:
filter ((=="apple") . first) fruit
[ f | f#("apple",_,_) <- fruit ]
The first one exploits your first projection, checking whether its result is equal to "apple".
The second one instead exploits list comprehensions, where elements that fail to pattern match are discarded.
Perhaps an even more basic approach is using a lambda abstraction and equality.
filter (\(s,_,_) -> s == "apple") fruit
I want to write a function that takes a chess figure with a position and tell me which moves this figure can make with his next move.
For the king I was able to do this.
type Position = (Int,Int)
data Piece = Piece {
_position :: Position,
_color :: String}
nextMoves :: Piece -> [Position]
nextMoves king = filter (onTheBoard) (filter (/= (xOld,yOld))
[(x,y) | x <- [(xOld-1)..(xOld+1)], y <- [(yOld-1)..(yOld+1)]])
where
xOld = fst(_position king)
yOld = snd(_position king)
He stands on (1,3) and my function gives me [(1,2),(1,4),(2,2),(2,3),(2,4)].
Now I want to do the same for a bishop or a knight. But how can I use filter for this issue?
Thanks for a tipp.
//edit: I've changed my check to a help function:
onTheBoard :: Position -> Bool
onTheBoard (x,y) = elem x [1..8] && elem y [1..8]
You have no way to tell which piece (king, bishop, knight, etc.) a Piece is. nextMoves king matches all pieces, regardless of which type it is. To figure out the moves for other pieces, you'll need to be able to discriminate between them. You'll need something like
data PieceType = Pawn | Knight | Bishop | Rook | Queen | King
data Piece = Piece {
_position :: Position,
_color :: String,
_pieceType :: PieceType}
Then you can write more cases for nextMoves
nextMoves :: Piece -> [Position]
nextMoves piece =
case _pieceType piece of
King -> filter (<= (1,1)) (filter (>= (8,8)) (filter (/= (xOld,yOld))
[(x,y) | x <- [(xOld-1)..(xOld+1)], y <- [(yOld-1)..yOld+1)]]))(yOld+1)]]))
Queen -> ...
Rook -> ...
Bishop -> ...
Knight -> ...
Pawn -> ...
where
xOld = fst(_position piece)
yOld = snd(_position piece)
You could extract the filtering that the move must be on the board from the rest of the rules.
If you are aiming for the actual game of chess, there are other rules for whether a move is valid that depend on the rest of the state of the board.
Filtering
You also have a problem with the filtering, as pointed out by Priyatham. The Ord instance for typles (,) first compares the first element of the tuple, then compares the second one. That means, for instance, that (1, 9) < (2, 0). This is called a lexographic ordering, like how entries are alphabetized: first by their first letter, then by their second, etc...
You need to check that each component is individually in the range, which is easily done with Data.Ix's inRange and the fst and snd functions, for example:
filter (inRange (1,8) . fst) . filter (inRange (1,8) . snd) . filter (/= (8,8)) $ [(7,9), 9,7), (7,7), (8, 8)]
The Ix instance for tuples (,) checks that both components are inRange, so this is equivalent to
filter (inRange ((1,1), (8,8))) . filter (/= (8,8)) $ [(7,9), (9,7), (7,7), (8, 8)]