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 -> aThese should do what their name and types imply:
elemList x xsisTrueif and only if at least one entry inxsequals tox.appendList xs ysshould be the list containing the entries ofxsfollowed by those ofys, in that order.listLength xsshould be the number of entries inxs.filterList p xsshould be the list containing those entriesxofxsfor whichp xis true.nths xs nextracts 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) -> Boolso 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 -> Boolthat checks whether the maze is closed. A maze is closed if
- the starting position is either
GroundorStorageand - every reachable tile is either
Ground,StorageorBox.
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
Statewith 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
handleanddrawfunctions will now need to take an additional argument, the current maze, of typeCoord -> Tile, instead of refering to a top-levelmazefunction. 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
mazetofoo - Everywhere where
foois called, addmazeas an argument. - Repeat.
To get the current maze, use
nthfrom exercise 1. Of course, make sure you never usenthwith a too-short list. A variantnthMaze :: Integer -> (Coord -> Tile)that gets the maze component of the corresponding entry inmazeswill 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 functionlistLengthfrom above) the space bar should load the next level.loadLevel :: Integer -> Statecan 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.