Reputation: 43
I am trying to implement a maze solver based on the algorithm described in the below link in Haskell.
http://www.cs.bu.edu/teaching/alg/maze/
I am pretty new to haskell and functional programming, and I basically try to code the algorithm as described in the link, I tried to go through many other resources online but I am stuck in the part where the walking stops when the goal (It doesn't stop, it back tracks to the origin) is reached, and I am unable to unmark the bad positions in the maze.
The maze looks like
.########
.......#.
#.####..#
#.#######
...#.G...
##...####
#..######
my code is as follows
findPath :: Int->Int->Maze->(Bool,Maze)
findPath x y maze
| not (isSafe maze x y) = (False,maze)
| isChar maze x y 'G' = trace ("Solution: "++ show maze)(True,maze)
| isChar maze x y '#' = (False,maze)
| isChar maze x y '!' = (False,maze)
| fst walkNorth = (True,markedList)
| fst walkEast = (True,markedList)
| fst walkSouth = (True,markedList)
| fst walkWest = (True,markedList)
| otherwise = (False,unMarkedList)
where markedList = replaceCharInMaze maze x y '+'
unMarkedList = replaceCharInMaze maze x y '!'
walkNorth = findPath x (y-1) markedList
walkEast = findPath (x+1) y markedList
walkSouth = findPath x (y+1) markedList
walkWest = findPath (x-1) y markedList
the isSafe function just checks for bounds, the isChar is just character matching at a given x,y position and the replaceCharInMaze function replaces the character at the x,y position with the supplied character.
isSafe :: [String]->Int->Int->Bool
isSafe list x y
| y >= 0 && y < length (head list) && x >= 0 && x < length list && (isChar list xy '.' || isChar list x y 'G') = True
| otherwise = False
So, I have two problems
I am not able to persist the un-marking being done at the Otherwise case to the next recursion call, how do I go about persisting the state of the maze, so that even the un-marked state be part of the solution?
Then as the algorithm proceeds, It walks till the goal and comes back to the start, how to stop this from happening?
As I am new to Haskell and algorithms, I looked in to topics such has state monads, which seemed to be like the solution, but I am not quite sure about proceeding with it, I also tried looking into other stack overflow posts, but couldn't find anything that would help me.
The output for a maze obtained during the trace statement is as follows
+++#..###..#.
.#+++#+++.##.
####+#+#+#-##
...#+++#+#...
.#.####++.##.
.#G+++++#....
.############
....####.....
But it doesn't stop there it comes backtracks to the origin and prints the output as
+..#..###..#.
.#...#....##.
####.#.#.#.##
...#...#.#...
.#.####...##.
.#G.....#....
.############
....####.....
Upvotes: 3
Views: 2861
Reputation: 591
Here's what happens when you run your program with your example. The first four guards are clearly False, so not much happens up to that point. It evaluates walkNorth
by recursing once, in order to find that fst walkNorth
is also False. Then it evaluates walkEast
, which takes a while, since eventually that leads to the goal. It finds that fst walkEast
is True, so it returns (True,markedList)
. It's important to realise that the markedList
in the returned pair has only been 'marked' once (hence there is a single '+' in your output). Lots of 'markings' have been happening on the way to the goal, but those aren't visible from where the program returns its output. Each time you pass a markedList
into one of the walkXXX
functions, you're essentially creating a new list, with an additional marking, which can only be seen in the function call you're passing it into. What you actually want is the maze with the markings at the point where it's been solved. At the end of the day, the walkXXX
functions either return (False,maze)
when walking in the XXX direction doesn't lead to the goal (because the 1st or 3rd guard evaluates to True), or (True,maze)
if it does lead to the goal (the 2nd guard evaluates to True), in which case, maze
at that point will have all the correct markings. So instead of returning markedList
for the fst walkXXX
cases, return snd walkXXX
. i.e.
| fst walkNorth = (True,snd walkNorth)
| walkEast = (True,snd walkEast)
| walkSouth = (True,snd walkSouth)
| walkWest = (True,snd walkWest)
Your first question is a bit complicated. I think what you want is to change your walkXXX
definitions to something very roughly like this:
walkNorth = findPath x (y-1) markedList
walkEast = findPath (x+1) y (replaceCharInMaze markedList x (y-1))
walkSouth = findPath x (y+1) (replaceCharInMaze (replaceCharInMaze markedList x (y-1)) (x+1) y)
and I'll let you fill in the last one. If you are walking east, you know that you've tried walking north and not found the goal, so you can unmark it, and so on. (This isn't quite going to work, at the very least because it will probably try to replace walls and characters outside of the maze, but the idea is there.)
You seem to be not yet accustomed to Haskell's lack of mutable state and frequent recursion. Some other things (I'm not certain about these): I don't think your otherwise
case ever runs, and it doesn't really do anything -- try taking it out and see what happens; I also don't think the True
s in your (True,markedList)
pairs ever have any effect -- try changing them to False.
Upvotes: 1