Homework 4: Graph algorithms
CIS 194: Homework 4
Due Tuesday, September 27
The general remarks about style and submittion from the first week still apply.
Exercise 0: Import the list of mazes
We have collected your submitted mazes from last week. You can download the code of the mazes. It contains a list of mazes (mazes
) and a longer list including broken mazes (extraMazes
). Paste them into your file, at the very end.
Because the starting position is relevant, we added a data type to go along with the maze:
data Maze = Maze Coord (Coord -> Tile)
mazes :: List Maze
mazes = …
extraMazes :: List Maze
extraMazes = …
Exercise 1: More polymorphic list function
Implement these generally useful functions:
elemList :: Eq a => a -> List a -> Bool
appendList :: List a -> List a -> List a
listLength :: List a -> Integer
filterList :: (a -> Bool) -> List a -> List a
nth :: List a -> Integer -> a
These should do what their name and types imply:
elemList x xs
isTrue
if and only if at least one entry inxs
equals tox
.appendList xs ys
should be the list containing the entries ofxs
followed by those ofys
, in that order.listLength xs
should be the number of entries inxs
.filterList p xs
should be the list containing those entriesx
ofxs
for whichp x
is true.nths xs n
extracts the \(n\)th entry of the list (start counting with 1). If \(n\) is too large, you may abort the program (by writingerror "list too short"
, which is an expression that can be used at any type). This is not good style, but shall do for now.
Exercise 2: Graph search
(Read exercise 3 first, to have understand why this is an interesting function.)
The algorithm you have to implement below can be phrased very generally, and we want it to be general. So implement a function
isGraphClosed :: Eq a => a -> (a -> List a) -> (a -> Bool) -> Bool
so that in a call isGraphClosed initial adjacent isOk
, where the parameters are
initial
, an initial node,adjacent
, a function that for every node lists all walkable adjacent nodes andisOk
, which checks if the node is ok to have in the graph,
the function returns True
if all reachable nodes are “ok” and False
otherwise.
Note that the graph described by adjacent
can have circles, and you do not want your program to keep running in circles. So you will have to remember what nodes you have already visted.
The algorithm follows quite naturally from handling the various cases in a local helper function go
that takes two arguments, namely a list of seen nodes and a list of nodes that need to be handled. If the latter list is empty, you are done. If it is not empty, look at the first entry. Ignore it if you have seen it before. Otherwise, if it is not ok, you are also donw. Otherwise, add its adjacent elements to the list of nodes to look ak.
You might find it helpful to define a list allDirections :: List Direction
and use mapList
and filterList
when implementing adjacent
.
Exercise 3: Check closedness of mazes
Write a function
isClosed :: Maze -> Bool
that checks whether the maze is closed. A maze is closed if
- the starting position is either
Ground
orStorage
and - every reachable tile is either
Ground
,Storage
orBox
.
Use isGraphClosed
to do the second check. Implement adjacent
so that isGraphClosed
walks everywhere where there is not a Wall
(including Blank
). Implement isOk
so that Blank
tiles are not ok.
With the following function you can visualize a list of booleans:
pictureOfBools :: List Bool -> Picture
pictureOfBools xs = translated (-fromIntegral k /2) (fromIntegral k) (go 0 xs)
where n = listLength xs
k = findK 0 -- k is the integer square of n
findK i | i * i >= n = i
| otherwise = findK (i+1)
go _ Empty = blank
go i (Entry b bs) =
translated (fromIntegral (i `mod` k))
(-fromIntegral (i `div` k))
(pictureOfBool b)
& go (i+1) bs
pictureOfBool True = colored green (solidCircle 0.4)
pictureOfBool False = colored red (solidCircle 0.4)
Let exercise3 :: IO ()
be the visualization of isClosed
applied to every element of extraMazes
. Obviously, mapList
wants to be used here.
Exercise 4: Multi-Level Sokoban
Extend your game from last week (or the code from the lecture) to implement multi-level sokoban.
- Extend the
State
with a field of typeInteger
, to indicate the current level (start counting at 1). - The initial state should start with level 1. The initial coordinate is obtained read from the entry in
maze
. Your
handle
anddraw
functions will now need to take an additional argument, the current maze, of typeCoord -> Tile
, instead of refering to a top-levelmaze
function. Any helper functions (e.g.noBoxMaze
) will also have to take this as an argument. This requires many, but straight-forward changes to the code: You can mostly, without much thinking:- Check the compier errors for an affected function, say
foo
. - Add
(Coord -> Tile) ->
to the front offoo
’s type signature, . - Add a new first parameter
maze
tofoo
- Everywhere where
foo
is called, addmaze
as an argument. - Repeat.
To get the current maze, use
nth
from exercise 1. Of course, make sure you never usenth
with a too-short list. A variantnthMaze :: Integer -> (Coord -> Tile)
that gets the maze component of the corresponding entry inmazes
will also be handy whenever you have theState
, but need amaze :: Coord -> Tile
.- Check the compier errors for an affected function, say
If the level is solved and the current level is not the last (use
There is some code to be shared with the calculation of the initial state! Maybe the same functionlistLength
from above) the space bar should load the next level.loadLevel :: Integer -> State
can be used in both situations.If the level is solved and the current leve is the last, show a differnt message (e.g. “All done” instead of “You won”).
Let exercise4 :: IO ()
be this interaction, wrapped in withUndo
, withStartScreen
and resetable
.