I have to write a function drawLines that creates a view of defined lines. The first parameter is a tuple (columns, rows) defining the size of the resulting view. Left top corner has a coordinate (0,0). The second argument is a list of lines.
There is function for print:
pt :: Result -> IO ()
pt x = putStr (concat (map (++"\n") x))
Function:
drawLines :: (Int,Int) -> [Line] -> Result
Data types representing a point and a line:
data Point = Point Int Int
data Line = Line Point Point
There is IO example:
Prelude>pt(drawLines (31,15) [Line (Point x y) (Point 15 7)|(x,y)<-concat [[(x,y)|y<-[0,7,14]]|x<-[0,15,30]]])
##.............#.............##
..##...........#...........##..
....##.........#.........##....
.....###.......#.......###.....
........##.....#.....##........
..........###..#..###..........
.............#####.............
###############################
.............#####.............
..........###..#..###..........
........##.....#.....##........
.....###.......#.......###.....
....##.........#.........##....
..##...........#...........##..
##.............#.............##
Please help me. I tried something, but it doesn't work. Can someone explain to me how it works.
I tried this:
type Result = [String]
pt :: Result -> IO ()
pt x = putStr (concat (map (++"\n") x))
drawLines :: (Int,Int) -> [(Int,Int)] -> Result
drawLines (0,0) (x:xs) = [] drawLines (a,b) [] = []
drawLines (a,b) (x:xs) =[ [if a == fst x && b == snd x then 'x' else '.' | b
<- [1..b]]| a <- [1..a]] ++ drawLines (a,b) xs
I'm a beginner in Haskell language.
Any advice will be good for me.
Thank you.
This is called "rasterization". There is a simple trick by which libraries such as diagrams rasterize their vector images, and I will let you in on it. Then you will have easy time writing the code.
The trick is this:
Define a function that determines if a point with given coordinates should be painted black or white.
Map this function all over the grid.
For example, suppose you have a line Line (Point 1 2) (Point 3 4). You must be able to answer the question: should Point 2 3 be black? Does the given line pass close enough to the given point for it to be painted black?
shouldIPaint :: Point -> Line -> Colour
Now, if you have 2 lines, and either one is close enough, you paint, and so on for any number of lines.
overlay :: [Colour] -> Colour
shouldIPaintMany :: Point -> [Line] -> Colour
shouldIPaintMany point = overlay . fmap (shouldIPaint point)
Seeing how a view is a collection of points, you need to determine the value of this function for each point. To this end, you may first create a view where every point holds its coordinates as values, then convert the coordinates to colour.
initialView :: Point -> View Point
solution :: Point -> [Line] -> View Colour
solution point lines = fmap (flip shouldIPaintMany lines) (initialView point)
Done! And the beauty here is that it is straightforward to extend this solution to paint in 3 dimensional space, or to paint circles as well as lines, and so on.
I do not really expect you to be able to fill in the missing parts all by yourself. You may have any number of questions by now, but these will be specific, sharp questions. Some of them will be about Mathematics, others will be about Haskell. If you want to be successful, you should write down these sharp questions, then try to answer them, and if you cannot, then ask them here on Stack Overflow or, maybe, at Mathematics Stack Exchange. You can start by writing me a comment below this answer, and I will tell you if you are on the right track. It is also a possibility that you will not understand the solution I offered at first, but you really need to persist about it, and ask me if something does not make sense despite your best effort.
Related
The following definitions represent a shape composed of coloured squares at specific coordinates on a grid:
type AltShape = [Point]
data Point = P Colour (Int,Int) deriving Eq
data Colour = Black | Red | Green | Blue deriving Eq
I should asume that coordinates always are positive and the coordinate (0,0) refers to the top left square of the picture, and the y-coordinates grow downwards
A Red L-shape could be represented by
lShape = [P Red (0,0), P Red (0,1), P Red (0,2), P Red (1,2)]
A different way to represent such shapes is as a list-of-lists, one list for each row:
type Shape = [Row]
type Row = [Square]
type Square = Maybe Colour
For example, the red L-shape above would be represented by the following value of type Shape:
lShape2 = [[x,o]
,[x,o]
,[x,x]] where x = Just Red
o = Nothing
My task is to define a function toShape :: AltShape -> Shape that converts from a AltShape to an Shape. I had another task to define a function fromShape :: Shape -> AltShape but where data Shape = S [Row]. I found that rather simple and wrote it like this:
fromShape :: Shape -> AltShape
fromShape (S rows) = [ P c (x,y) | (y,row) <- index rows, (x,Just c) <- index row]
where index = zip [0..]
However, I am having more trouble with this one. I started by creating the function
colourAndCoords :: Point -> (Colour,(Int,Int))
colourAndCoords ( P c (x,y) ) = (c,(x,y))
I then created a function
coords :: [Point] -> [(Int,Int)]
coords ps = map snd (map colourAndCoords ps)
My thought was to compare this list to another list of all possible coordinations and where there was a match add the right colour, and where there wasn't I would add Nothing. However, my teacher said I was making it too complicated and that I should think of another solution. But I am having a hard time thinking of one. So I guess my question is what is the easier way? I am not asking for a solution, just a nudge in the right direction.
Thanks a ton for anyone taking the time to read this and respond!!
If I come up with a solution I will come back and update this thread.
If you want an efficient solution, the accumArray function does almost exactly what you need (after computing the appropriate bounds).
λ> arr = accumArray (const Just) Nothing ((0, 0), (2, 1)) [((y, x), c) | P c (x, y) <- lShape]
λ> arr
array ((0,0),(2,1)) [((0,0),Just Red),((0,1),Nothing),((1,0),Just Red),((1,1),Nothing),((2,0),Just Red),((2,1),Just Red)]
λ> elems arr
[Just Red,Nothing,Just Red,Nothing,Just Red,Just Red]
Now the problem is reduced to split elements into groups.
λ> chunksOf 2 (elems arr)
[[Just Red,Nothing],[Just Red,Nothing],[Just Red,Just Red]]
For a real application you’d probably want to leave it as an array, since array indexing is fast (O(1)) and list indexing is slow (O(n)).
If efficiency is no concern, you could consider this approach:
Figure out the largest x coordinate (call it w), and the largest y coordinate (call it h).
Create a rectangular list of lists, length w by h, with a list comprehension or similar. At each position, look through the entire list of points for one with a matching position, and use its color if you find one (Nothing otherwise).
If efficiency is a concern, you could consider a more complicated approach:
Turn your [AltShape] into an IntMap (IntMap Colour), e.g. using this technique, which maps y and x to colour.
Iterate over occupied rows with toAscList; within each, iterate over occupied columns with toAscList. You will need to manually pad unoccupied rows with [] and unoccupied columns with Nothing.
An advantage (or possibly disadvantage, depending on your goals!) of the second approach is that it will naturally produce "ragged" Shapes which omit trailing Nothings.
I have the following function:
blockToPicture :: Int -> [Picture] -> Picture
blockToPicture n [pic1,pic2,pic3] | n==0 = ...
| n==1 = ...
| otherwise = ...
If n==0 I want to select pic1, if n==1 I want to select pic2. Otherwise I want to select pic3. The problem is when one of the pictures doesn't load, so it doesn't appear on the list.
Instead of [pic1,pic2,pic3] I have something like [Pic1,Pic3].
When the function is supposed to select a picture that isn't on the list I want it to write "X" instead. For that I'll use the function
text "X" instead. The problem is that I don't know how to make it write the "X" instead of selecting the wrong picture.
Edit:
I've created the following function but for some reason I'm getting the error "Variable not in scope" to the pictures.
blocoParaPicture :: Int -> [Picture] -> Picture
blocoParaPicture b l | b==0 = if elem pic1 l then pic1 else text "X"
| b==1 = if elem pic2 l then pic2 else text "X"
| otherwise = if elem pic3 l then pic3 else text "X"
You can't just discard a picture that doesn't load; if you tried to load 3 pictures and end up with [some_pic, some_other_pic], how do you know which one didn't load? You need a list of type [Maybe Picture], with Just pic representing a successfully loaded picture and Nothing a failure. Then your function would look like
blockToPicture :: Int -> [Maybe Picture] -> Maybe Picture
blockToPicture _ [] = Nothing -- No pictures to choose from
blockToPicture 0 (Nothing:_) = Nothing -- Desired picture failed to load
blockToPicutre 0 (x:_) = x -- Found desired picture!
blockToPicture n (_:xs) = blockToPicture (n-1) xs -- This isn't it; try the next one
Adapting Jorge Adriano's suggestion to use lookup (which is a good one)
import Control.Monad
blockToPicture :: Int -> [Maybe Picture] -> Maybe Picture
blockToPicture n pics = join (lookup n (zip [0..] pics))
Since lookup :: a -> [(a,b)] -> Maybe b and b here is Maybe Picture, we have
a scenario where lookup returns Nothing if n is too big; Just Nothing if the desired picture fails to load, and Just (Just pic) if the desired picture is found. The join function from Control.Monad reduces the Maybe (Maybe Picture) value that lookup returns to the "regular" Maybe Picture that we want.
blocoParaPicture :: Int -> [Picture] -> Picture
blocoParaPicture b l | b==0 = if elem pic1 l then pic1 else text "X"
| b==1 = if elem pic2 l then pic2 else text "X"
| otherwise = if elem pic3 l then pic3 else text "X"
I'm getting the error "Variable not in scope" to the pictures.
The expression elem x xs checks if a given x is in a list xs. In your code when you write pic1, there's no such variable in scope, it isn't defined anywhere. In any case you don't want to search for a specific value in the list, rather you want to know if a given position "exists", that is if the list is long enough.
Also you can't just "write" inside a function with this type. In Haskell input and output is reflected on the types. This is a pure function, that takes some arguments and calculates a result, no side effects.
So what you can do here is return a Maybe Picture, which has values Nothing or Just pic depending whether you can return a picture or not. Or you can use Either String Picture, where values are of the form Left string or Right pic. Lets go for this latter option.
blocoParaPicture :: Int -> [Picture] -> Either String Picture
In terms of implementation we could diverge from the subject to get into a discussion of error management (since the problem is that access to a position may fail). But at this point I think it's best to avoid that detour so lets keep it (relatively) simple.
direct recursion (simplest)
The simplest most direct method would be direct recursion (as suggested by #chepner in the comments below).
blocoParaPicture :: Int -> [Picture] -> Either String Picture
blocoParaPicture _ [] = Left "X"
blocoParaPicture 0 (x:_) = Right x
blocoParaPicture n (x:xs) = safe (n-1) xs
making sure !! succeds
If you do want to use the standard access function !!, one way to go about it (but potentially inefficient in the general case) would be to construct a "safe" infinite list.
import Data.List
blocoParaPicture :: Int -> [Picture] -> Either String Picture
blocoParaPicture n xs = zs !! n
where zs = [Right x | x <- xs] ++ repeat (Left "X")
The list zs is an infinite list made up of two lists. First [Right x | x <- xs] which just like your original list, but each element x becomes Right x. Then from then onwards all elements are of the form Left "X" to indicate failure. In general the above approach can be inefficient. If you look for a big n in a list:
[Right 1, Right 2] ++ [Left "X", Left "X", ...
you are doing many unnecessary steps, since you could stop when the first list ends. But works just fine for small n.
using lookup
Yet another possibility, similar to your attempt to use the elem function, would be to use lookup on indices. This function is safe by design.
lookup :: Eq a => a -> [(a, b)] -> Maybe b
Following this approach you first construct the list,
[(0,x0), (1,x1), (2,x2) ...(k,xk)]
and then look for your given n to return the associated xn (or Nothing).
blocoParaPicture' :: Int -> [Picture] -> Maybe Picture
blocoParaPicture' n xs = lookup n (zip [1..] xs)
This returns Nothing when not found though. But if you wish to, you can convert to Either via maybe :: b -> (a -> b) -> Maybe a -> b.
blocoParaPicture :: Int -> [Picture] -> Either String Picture
blocoParaPicture n xs = maybe (Left "X") Right (lookup n (zip [1..] xs))
This is certainly a bit too complex when all you want is a simple access function. But can be handy in situations where things are not as simple.
So I know many variations of this question have been asked but I've been reading posts for hours and I'm just stuck. I'm getting better at haskell but not fully comprehending everything.
For starters, this is an assignment so while I don't want the answer looking for some guidance.
We have these 4 types declared for this assignment:
type Node = Integer
type Edge = (Integer, Integer)
type Graph = [Edge]
type Path = [Node]
What I am currently working is a function which takes a Node and a Graph and returns a list of Nodes that are connected to the Node passed in.
I'm really struggling with how to recursively return the second element in the tuple once I've found the source node i'm looking for. I know I need to be using fst to grab the first element of each tuple for comparison but then I get lost here, I'm stuck on how to correctly return the second element (add it to my list) and then proceed through the remaining list of tuples.
Neighbors :: Node -> Graph -> [Node]
Neighbors nd gr =
I've been reading about filter but I'm not sure that would work in this scenario as that returns pairs if I understand correctly. Looking for any guidance, thank you
You can indeed use filter to find edges that leaves from a given node. Then you need to take second element of each of this edge. You need map for that. So you write:
neighbors nd gr = map snd $ filter (\x -> fst x == nd) gr
(Note that Haskell would not allow you to start name of the function with capital letter).
If you want to be cool you can write it in almost point-free way:
neighbors nd = map snd . filter ((==nd) . fst )
And if you want to make it easier to read you can use list comprehensions:
neighbors nd gr = [ y | (x,y) <- gr, x==nd ]
filter could indeed be used to return only those edges in the graph that are of interest to you (ie, those leading out from the target node). Then you'd have a list of edges, when what you really want is a list of nodes. Can you think of a function that can transform a list of edges into a list of nodes, after you've filtered down to only the edges you want?
edges :: [Edge]
f :: Edge -> Bool
filter f edges :: [Edge]
g :: [Edge] -> [Node]
g (filter f edges) :: [Node]
Above is a reasonable set of steps you could take, if you can think of appropriate f and g functions.
I'm really struggling with Haskell atm.
It took me almost 6 hours to write a function that does what I want. Unfortunately I'm not satisfied with the look of it.
Could someone please give me any hints how to rewrite it?
get_connected_area :: Eq generic_type => [[generic_type]] -> (Int, Int) -> [(Int,Int)] -> generic_type -> [(Int,Int)]
get_connected_area habitat point area nullValue
| elem point area = area
| not ((fst point) >= 0) = area
| not ((snd point) >= 0) = area
| not ((fst point) < (length habitat)) = area
| not ((snd point) < (length (habitat!!0))) = area
| (((habitat!!(fst point))!!(snd point))) == nullValue = area
| otherwise =
let new_area = point : area
in
get_connected_area habitat (fst point+1, snd point) (
get_connected_area habitat (fst point-1, snd point) (
get_connected_area habitat (fst point, snd point+1) (
get_connected_area habitat (fst point, snd point-1) new_area nullValue
) nullValue
) nullValue
) nullValue
The function get's a [[generic_type]] (representing a landscape-map) and searches the fully connected area around a point that isn't equal to the given nullValue.
Eg.:
If the function gets called like this:
get_connected_area [[0,1,0],[1,1,1],[0,1,0],[1,0,0]] (1,1) [] 0
That literally means
0 1 0
1 1 1
0 1 0
1 0 0
Represents a map (like google maps). Start from the point (coordinates) (1,1) I want to get all coordinates of the elements that form a connected area with the given point.
The result therefore should be:
0 1 0
1 1 1
0 1 0
1 0 0
And the corresponting return value (list of coordinates of bold 1s):
[(2,1),(0,1),(1,2),(1,0),(1,1)]
One small change is that you can use pattern matching for the variable point. This means you can use (x, y) instead of point in the function declaration:
get_connected_area habitat (x, y) area nullValue = ...
Now everywhere you have fst point, just put x, and everywhere you have snd point, put y.
Another modification is to use more variables for subexpressions. This can help with the nested recursive calls. For example, make a variable for the inner-most nested call:
....
where foo = get_connected_area habitat (x, y-1) new_area nullValue
Now just put foo instead of the call. This technique can now be repeated for the "new" inner-most call. (Note that you should pick a more descriptive name than foo. Maybe down?)
Note that not (x >= y) is the same as x < y. Use this to simplify all of the conditions. Since these conditions test if a point is inside a bounding rectangle, most of this logic can be factored to a function isIn :: (Int, Int) -> (Int, Int) -> (Int, Int) -> Bool which will make get_connected_area more readable.
This would be my first quick pass through the function, and sort of the minimum that might pass a code review (just in terms of style):
getConnectedArea :: Eq a => [[a]] -> a -> (Int, Int) -> [(Int,Int)] -> [(Int,Int)]
getConnectedArea habitat nullValue = go where
go point#(x,y) area
| elem point area = area
| x < 0 = area
| y < 0 = area
| x >= length habitat = area
| y >= length (habitat!!0) = area
| ((habitat!!x)!!y) == nullValue = area
| otherwise =
foldr go (point : area)
[ (x+1, y), (x-1, y), (x, y+1), (x, y-1) ]
We bind habitat and nullValue once at the top level (clarifying what the recursive work is doing), remove indirection in the predicates, use camel-case (underdashes obscure where function application is happening), replace generic_type with a (using a noisy variable here actually has the opposite effect from the one you intended; I end up trying to figure out what special semantics you're trying to call out when the interesting thing is that the type doesn't matter (so long as it can be compared for equality)).
At this point there are lots of things we can do:
pretend we're writing real code and worry about asymptotics of treating lists as arrays (!!, and length) and sets (elem), and use proper array and set data structures instead
move your bounds checking (and possible null value checking) into a new lookup function (the goal being to have only a single ... = area clause if possible
consider improvements to the algorithm: can we avoid recursively checking the cell we just came from algorithmically? can we avoid passing area entirely (making our search nicely lazy/"productive")?
Here is my take:
import qualified Data.Set as Set
type Point = (Int, Int)
getConnectedArea :: (Point -> Bool) -> Point -> Set.Set Point
getConnectedArea habitat = \p -> worker p Set.empty
-- \p is to the right of = to keep it out of the scope of the where clause
where
worker p seen
| p `Set.member` seen = seen
| habitat p = foldr worker (Set.insert p seen) (neighbors p)
| otherwise = seen
neighbors (x,y) = [(x-1,y), (x+1,y), (x,y-1), (x,y+1)]
What I've done
foldr over the neighbors, as some commenters suggested.
Since the order of points doesn't matter, I use a Set instead of a list, so it's semantically a better fit and faster to boot.
Named some helpful intermediate abstractions such as Point and neighbors.
A better data structure for the habitat would also be good, since lists are linear time to access, maybe a 2D Data.Array—but as far as this function cares, all you need is an indexing function Point -> Bool (out of bounds and null value are treated the same), so I've replaced the data structure parameter with the indexing function itself (this is a common transformation in FP).
We can see that it would also be possible to abstract away the neighbors function and then we would arrive at a very general graph traversal method
traverseGraph :: (Ord a) => (a -> [a]) -> a -> Set.Set a
in terms of which you could write getConnectedArea. I recommend doing this for educational purposes—left as an exercise.
EDIT
Here's an example of how to call the function in terms of (almost) your old function:
import Control.Monad ((<=<))
-- A couple helpers for indexing lists.
index :: Int -> [a] -> Maybe a
index _ [] = Nothing
index 0 (x:_) = x
index n (_:xs) = index (n-1) xs
index2 :: (Int,Int) -> [[a]] -> Maybe a
index2 (x,y) = index x <=< index y
-- index2 uses Maybe's monadic structure, and I think it's quite pretty.
-- But if you're not ready for that, you might prefer
index2' (x,y) xss
| Just xs <- index y xss = index x xs
| otherwise = Nothing
getConnectedArea' :: (Eq a) => [[a]] -> Point -> a -> [a]
getConnectedArea' habitat point nullValue = Set.toList $ getConnectedArea nonnull point
where
nonnull :: Point -> Bool
nonnull p = case index2 p habitat of
Nothing -> False
Just x -> x /= nullValue
OK i will try to simplify your code. However there are already good answers and that's why i will tackle this with a slightly more conceptual approach.
I believe you could chose better data types. For instance Data.Matrix seems to provide an ideal data type in the place of your [[generic_type]] type. Also for coordinates i wouldn't chose a tuple type since tuple type is there to pack different types. It's functor and monad instances are not very helpful when it is chosen as a coordinate system. Yet since it seems Data.Matrix is just happy with tuples as coordinates i will keep them.
OK your rephrased code is as follows;
import Data.Matrix
gca :: Matrix Int -> (Int, Int) -> Int -> [(Int,Int)]
gca fld crd nil = let nbs = [id, subtract 1, (+1)] >>= \f -> [id, subtract 1, (+1)]
>>= \g -> return (f,g)
>>= \(f,g) -> return ((f . fst) crd, (g . snd) crd)
in filter (\(x,y) -> fld ! (x,y) /= nil) nbs
*Main> gca (fromLists [[0,1,0],[1,1,1],[0,1,0],[1,0,0]]) (2,2) 0
[(2,2),(2,1),(2,3),(1,2),(3,2)]
The first thing to note is, the Matrix data type is index 1 based. So we have our center point at (2,2).
The second is... we have a three element list of functions defined as [id, subtract 1, (+1)]. The contained functions are all Num a => a -> a type and i need them to define the surrounding pixels of the given coordinate including the given coordinate. So we have a line just like if we did;
[1,2,3] >>= \x -> [1,2,3] >>= \y -> return [x,y] would result [[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[3,2],[3,3]] which, in our case, would yield a 2 combinations of all functions in the place of the numbers 1,2 and 3.
Which then we apply to our given coordinate one by one with a cascading instruction
>>= \[f,g] -> return ((f . fst) crd, (g . snd) crd)
which yields all neighboring coordinates.
Then its nothing more than filtering the neighboring filters by checking if they are not equal to the nil value within out matrix.
guys can you help me fix this code?
import Data.Char
import Data.List
-- 1 2 3 4 5 6 7 8
colors = [1,2,3,4,5]
--game :: [Integer] -> [Char]
game a = do
let black = test a color
white = (test2 a color) - black
let x = [a] ++ createScore black white
show x
test [] [] = 0
test (x:xs) (y:ys) = if x == y then 1+test xs ys else 0+test xs ys
test2 a b = length (intersection a b)
intersection first second = [n | n <- first , isInfixOf [n] second]
createScore c b = [(take c (repeat 1) ++ take b (repeat 0))]
start = do
a <- getLine
let b = map read $ words a
--print b
game b
start
I have problem with IO and nonIO functions.
description of the program:
read data from line
convert data to List of int
call function game (which takes List of int like parametr)
make some calculation
print score
start again from 1
Problem is in function start and I have no idea how to fix it.
Thanks for help.
there are a few things that don't quite work with the code you've given us.
I'll work through the compile errors.
On first load we get a name error:
ex1.hs:10:26:
Not in scope: 'color'
Perhaps you meant 'colors' (line 6)
(and again at line 11)
Of course the compiler is correct and we just need to change the appropriate names to match.
Next we get the interesting one I assume you're referring to with regards to IO and non-IO functions:
ex1.hs:28:7:
Couldn't match type '[]' with 'IO'
Expected type: IO Char
Actual type: String
In the return type of a call of 'game'
In a stmt of a 'do' block: game b
In the expression:
do { a <- getLine;
let b = map read $ words a;
game b;
start }
The error is your use of game b in the IO block.
The type annotation you have commented out over the function definition of game is actually correct -- it is [Integer] -> [Char].
As such it's a pure function and we don't need to use the do notation to describe it like we would with something that deals with IO -- because you have used the notation here with an argument of a list, the do expression represents a computation in the context of a list, not an IO computation, so calling it from start has a type mismatch, it expects IO, but it has found [].
We can start fixing it up by turning game into a pure function, using a let-in expression.
game :: [Integer] -> [Char]
game a = let black = test a colors
white = (test2 a colors) - black
x = [a] ++ createScore black white
in show x
So now we have a function that returns the string of the input and it's score.
The compiler now gives the error Expected type: IO Char, Actual type: [Char], this is because we are still trying to use a non-IO expression in the main do block.
We can fix this by actually printing the string to stdout, just using print, so your original
--print b
game b
can just be
print $ game b
At this point the program compiles!
Unfortunately it's still not quite right, when we run this and type in a list of integers like 1 2 3 we get the exception ex1.hs:(14, 1)-(15,66): Non-exhaustive patterns in function test.
This one comes down to your definition of test as:
test [] [] = 0
test (x:xs) (y:ys)
Doesn't account for the possibility of one list being empty -- because the check is always between the head elements of the lists probably the smallest change to fix this could just be:
test (x:xs) (y:ys) = if x == y then 1+test xs ys else 0+test xs ys
test _ _ = 0
And now the program compiles and executes. Hopefully that makes sense.
game is (or should be) a pure function, so you need to call it with a function that returns an IO value in your start function, e. g. print $ game b.
There is also no reason to use do notation there (you can because [a] is also a monad, but you don't take advantage of it in any way). You can just use game a = show x and replace the let statements with a where block (or use let ... in).
test should be able to deal with the case of one list being empty and the other not, or you need to ensure that both lists are always the same size.