Get Help
Maze Solving
Introduction
Solving a maze is fun and will help you build your roboticist skills up- thinking about every instruction that Sparki needs to not just wander around the maze, but actually complete it. At this point, with the things that we have already learned in the previous lessons, we can take advantage of some of Sparki’s sensors to implement some maze solving programs. To understand this lesson, it’s strongly recommended that you have learned the concepts from the Line Following and the Moving the Robot lessons first. So, let’s help Sparki get through the maze!What You’ll Need
- A Sparki.
- A maze. What we will use here is the poster in ArcBotics’ Sparki Materials Packs. You can also make your own with a white cardboard as the base, using black insulating plastic tape to “print” the walls. You can also download a ready to print maze from this link (please note that this maze can be printed in standard plotters, since it’s 90 cm wide). If you’re really into mazes, you can download that maze’s source SVG file, and modify it using the Inkscape open source vector graphics editor (or any other SVG editor of your choice).

How It Works
One of the simplest ways of solving a maze with a robot is by using the Wall Follower algorithm, also know as the left-hand rule (or right-hand rule). Forget about the robot for a while, and suppose that you are a person inside a maze. Finding the exit could be done just by keeping one of your hands always touching a wall. And by “always”, we really mean always here. It might take you a while and you might wind up taking all the wrong paths in the maze before getting to the end but by keeping your left hand on the wall of the maze you wind up taking every single left hand turn (sometimes turning around completely) and, as long as you keep taking steps forward, eventually you’ll get to the end of the maze. Take a look to the following animation, where the red point represents the person:

Maze Geometry and Sparki
We can freely draw a maze and try to program Sparki to escape from it. But sooner or later we will find that not every robot can solve every maze. There are some shape and size constraints that we should think of when leaving a poor robot alone in a maze. Some are easier to spot, like the size of the robot to the maze. Suppose that your robot is too big (or too small) for the maze:



The Main Idea
At a higher level, the Wall Follower algorithm consists of these four simple steps:- If you can turn left, do it.
- Else (if you can’t turn left), if you can continue going straight, just go straight.
- Else (if you can’t do either of the previous steps), if you can turn right, do it.
- If you reached a dead end, turn back by turning around (in either direction) 180 degrees.
Getting Sparki’s Bearings
So, our first problem is figuring out how to make Sparki go straight through a maze corridor. We need to make sure that Sparki knows where the wall on the left hand side of the corridor is and that Sparki travels along while keeping that wall on it’s left hand side. This sounds easy, but there’s actually a couple different ways to this and we have to use different sensors for checking the walls drawn on the maze than we would use if there were actual maze walls. (We’re going to use the infrared line and edge sensors, what sensors might Sparki use if there were real walls?) We’ll start with code that’s similar to the way we programmed Sparki to follow a line but we’ll change the code (a lot) so that it fits our needs. Programmers often do this, even though writing code from scratch is a much better way to teach yourself how to code, sometimes it’s nice to work with some code that already does something and then change it a little bit. So we will use that knowledge here, but adapt it to our specific problem. In the Line Following lesson, the robot followed a line that was between the Line Left Sensor and the Line Right Sensor. This time, the lines we want to check are on the outside of the Edge Right Sensor and Edge Left Sensor. The walls of our maze are just a little bit narrower than the width between Sparki’s edge sensors. We’ll assume that Sparki is going to start off kind of pointed straight down a maze corridor, but we’ll need to make sure that the code helps keep it that way. For this particular tutorial’s code you do need to place Sparki a little bit to the right of the center of the maze corridor. (If you’re good with code you’ll be able to easily change this code so that Sparki is ok with being placed to the left of the center of the maze corridor, or even to either side.)

1 |
sparki.moveLeft(1); //turn Sparki to the left a tiny bit |
1 2 3 4 5 6 7 |
#include <Sparki.h> // include the sparki library boolean foundLeftWall = false; boolean calibrated = false; boolean rightWall = false; void setup() |
1 2 3 4 5 6 7 8 9 |
if( foundLeftWall == false && calibrated == false ) //if Sparki hasn't found a left wall and hasn't been calibrated yet { sparki.moveLeft(1); //turn Sparki to the left a tiny bit if ( edgeLeft < threshold ) // if there is a line below left edge sensor { foundLeftWall = true; //Sparki found the left wall } } |

1 2 3 4 5 6 7 8 9 10 11 12 13 |
if( foundLeftWall == true && calibrated == false ) //if Sparki found a wall but isn't calibrated yet { if( rightWall == false ) { sparki.motorRotate(MOTOR_LEFT, DIR_CCW, 15); //move forward while rotating to the right a little sparki.motorRotate(MOTOR_RIGHT, DIR_CW, 55); //move forward while rotating to the right a little } if( edgeLeft < threshold && edgeRight < threshold ) //if there is a wall beneath both sensors we're at -30 degrees from the wall { //this is also called a "known good" position because we know, no matter where Sparki started, where it is now in relation to the wall rightWall = true; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
void calibrateSparki() { rightWall = false; //variable so we know if Sparki found the right wall if( foundLeftWall == false && calibrated == false ) //if Sparki hasn't found a left wall and hasn't been calibrated yet { sparki.moveLeft(1); //turn Sparki to the left a tiny bit if ( edgeLeft < threshold ) // if there is a line below left edge sensor { foundLeftWall = true; //Sparki found the left wall } } if( foundLeftWall == true && calibrated == false ) //if Sparki found a wall but isn't calibrated yet { if( rightWall == false ) { sparki.motorRotate(MOTOR_LEFT, DIR_CCW, 15); //move forward while rotating to the right a little sparki.motorRotate(MOTOR_RIGHT, DIR_CW, 55); //move forward while rotating to the right a little } if( edgeLeft < threshold && edgeRight < threshold ) //if there is a wall beneath both sensors we're at -30 degrees from the wall { //this is also called a "known good" position because we know, no matter where Sparki started, where it is now in relation to the wall rightWall = true; //turn 28 degrees clockwise and scoot back close to the beginning sparki.moveForward(); delay(1650); //this variable got changed a little sparki.moveRight(30); //this variable got changed a little sparki.moveBackward(); delay(1000); sparki.motorStop(MOTOR_LEFT); sparki.motorStop(MOTOR_RIGHT); calibrated = true; //set calibrate flag to true so we exit calibrated code } } } |
1 2 |
edgeLeft = sparki.edgeLeft(); // measure the left IR sensor edgeRight = sparki.edgeRight(); // measure the left line IR sensor |
1 2 3 4 5 6 7 8 |
boolean rightWall = false; int edgeLeft; int edgeRight; int threshold; void setup() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
#include <Sparki.h> // include the sparki library //for sensors int edgeLeft; int edgeRight; int threshold; //for calibrating boolean foundLeftWall = false; boolean calibrated = false; boolean rightWall = false; void setup() { //indicates to the user that the program started: sparki.beep(440, 300); delay(300); sparki.beep(880, 500); Serial.begin(9600); } void loop() { threshold = 700; readSensors(); calibrateSparki(); //sparki.moveForward(); sparki.clearLCD(); // wipe the screen sparki.print("Edge Left: "); // show left line sensor on screen sparki.println(edgeLeft); sparki.print("Edge Right: "); // show left line sensor on screen sparki.println(edgeRight); sparki.updateLCD(); // display all of the information written to the screen } void readSensors() { edgeLeft = sparki.edgeLeft(); // measure the left IR sensor edgeRight = sparki.edgeRight(); // measure the left line IR sensor //int lineRight = sparki.lineRight(); // measure the right IR sensor } void calibrateSparki() { rightWall = false; //variable so we know if Sparki found the right wall if( foundLeftWall == false && calibrated == false ) //if Sparki hasn't found a left wall and hasn't been calibrated yet { sparki.moveLeft(1); //turn Sparki to the left a tiny bit if ( edgeLeft < threshold ) // if there is a line below left edge sensor { foundLeftWall = true; //Sparki found the left wall } } if( foundLeftWall == true && calibrated == false ) //if Sparki found a wall but isn't calibrated yet { if( rightWall == false ) { sparki.motorRotate(MOTOR_LEFT, DIR_CCW, 15); //move forward while rotating to the right a little sparki.motorRotate(MOTOR_RIGHT, DIR_CW, 55); //move forward while rotating to the right a little } if( edgeLeft < threshold && edgeRight < threshold ) //if there is a wall beneath both sensors we're at -30 degrees from the wall { //this is also called a "known good" position because we know, no matter where Sparki started, where it is now in relation to the wall rightWall = true; //turn 28 degrees clockwise and scoot back close to the beginning sparki.moveForward(); delay(1000); //this variable got changed a little sparki.moveRight(24); //this variable got changed a little sparki.moveBackward(); delay(1000); sparki.motorStop(MOTOR_LEFT); sparki.motorStop(MOTOR_RIGHT); calibrated = true; //set calibrate flag to true so we exit calibrated code } } } |

Going Straight
Next we need to create some code that makes Sparki move straight ahead down a corridor. After we get our function for moving straight ahead set up we’ll back up and create the code that tells Sparki when to move straight ahead, as well as some code to display what Sparki is doing on the LCD display. Ok. Pseudo-code time! There are a bunch of parts to this code-- Declaring some variables or “flags” to help Sparki go straight.
- Adding code to the sensor function to test to see if Sparki found a wall.
- Setting the variables to true or false.
- Adding an “if” statement that makes Sparki actually move in the loop.
- An “else” statement to stop Sparki if it didn’t find a wall.
1 2 3 4 5 6 7 |
int threshold; //for wall finding boolean foundLWall; //set false at the beginnning //for calibrating boolean foundLeftWall = false; |
1 2 3 4 5 6 7 8 9 10 |
void readSensors() { edgeLeft = sparki.edgeLeft(); edgeRight = sparki.edgeRight(); if(edgeLeft < threshold) { foundLWall = true; } } |
1 2 3 4 5 6 7 8 9 10 |
void readSensors() { foundLWall = false; edgeLeft = sparki.edgeLeft(); edgeRight = sparki.edgeRight(); if(edgeLeft < threshold) { foundLWall = true; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
readSensors(); if(calibrated == false) { calibrateSparki(); } else { if(foundLWall) { sparki.moveForward(); } else { sparki.moveStop(); sparki.println("Didn't find wall."); sparki.updateLCD(); } } } |
Sparki Lost the Wall or Came to a Branch in the Maze
Sometimes Sparki will lose the wall, so we really only want Sparki to move forward when there is a wall underneath the Edge Left Sensor. We already took care of that. But, if Sparki doesn’t detect a wall underneath the Edge Left Sensor then that may mean that there is a turn in the maze Sparki needs to take. (Remember, Sparki will always turn left if it can in our algorithm.) It’s time for… you guessed it, pseudo-code. Here are the steps we want to take for staying with the wall or finding the two different types of left hand turn that Sparki might take. (Remember, there’s a 90° left hand turn and a 180° left hand turn, these are different and will require different code for each.) We’ll put all of this code inside of a function called something like wallFinder( ). 1. Check the Edge Left Sensor, if there is no wall underneath it Sparki needs to turn off the straight( ) function and begin a wall finding function. 2. First, assume we just lost the wall somehow and try this pseudo-code- If there is no line underneath the Edge Left Sensor move forward and to the right a little trying to find the line again. If Sparki doesn’t find the wall again that way, back up exactly the same amount in the same direction to get back to where Sparki started searching and try the same thing again, only this time moving forward and to the left. If that doesn’t result in Sparki finding the wall then have Sparki move back to the starting position. If it did result in Sparki finding the wall, Sparki still needs to make sure it is pointed forward again so it doesn’t immediately lose the wall again. 3. So, Sparki didn’t find the wall, that means Sparki is probably at a left hand turn of some sort. To test for a 90° turn we’ll try this pseudo-code- Have Sparki move forward until the wheels are at the last place Sparki knows there was a wall. Now have Sparki turn to the left 90° and check the sensors to see if there is a wall underneath the Edge Left Sensor. 4. Still no wall! This means that Sparki is probably (hopefully) at a 180° turn. Sparki has already turned 90°, so Sparki just needs to turn another 90°, all the while looking for a wall. Hmmm. This pseudo-code sounds almost exactly like the code in section 3. We may be able to reuse some code for this…. 5. At this point we’ve exhausted all the possibilities we can think of for Sparki in a maze. (That’s not exactly true, what about a dead end in a maze? We’ll get to that later.) But often, between code errors and the unpredictability of the real world, situations like this will pop up. Always have a backup plan. There are a couple different options for Sparki now- Sparki can start its wall finding code all over again, it can do something like beep, or Sparki can just sit there waiting for a human to pick it up and help it out. Variables- Let’s think about the variables we’re going to need to achieve the pseudo-code above before launching into writing anything. We’re going to need a variable to keep track of how much Sparki has turned and which portion of our wallFinder code Sparki is currently using. We’ll call these variables wallFindType and turnCount. So, our first move is creating those variables-
1 2 3 4 5 6 7 8 9 10 |
int threshold; //for wall finding boolean foundLWall; int wallFindType = 0; int turnCount = 0; boolean foundWall; void setup(){ |
Also, our conditional is a little different. By using two ampersands (&&) we can check two conditions to see if they are true. Only if foundLWall is false and wallFindType is zero will the code inside of the “else if” code execute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
void loop() { threshold = 700; readSensors(); if( calibrated == false ) { calibrateSparki(); } else { if( foundLWall ) { wallFindType = 0; sparki.moveForward(); sparki.println("Found wall."); } else if( foundLWall == false && wallFindType == 0 ) { sparki.moveStop(); sparki.println("Didn't find wall."); sparki.updateLCD(); wallFindType = 1; //now we enter the wall finding routine sparki.moveForward(.5); //move forward a little } if( wallFindType > 0 ){ wallFind(); } sparki.updateLCD(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void wallFind(){ switch(wallFindType){ case 1: //Sparki might just be off track a little, search around 5 degrees //our code in the case that wallFindType = 1 goes here break; case 2://we didn't just lose the wall, there was a turn, check for 90 degree turn //our code in the case that wallFindType = 2 goes here break; default: //the default case is here just in case wallFindType is equal to a value we didn't cover with our other cases break; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
void wallFind(){ switch(wallFindType){ case 1: //Sparki might just be off track a little, search around 5 degrees while( foundLWall == false && turnCount < 5 ) { //as long as we haven't found a wall or turned 10 degrees yet sparki.moveRight(1); turnCount ++; readSensors(); sparki.println("Searching right"); } if(turnCount >= 5) { sparki.moveLeft(5);//turn back to original position sparki.println("Done searching right"); } else{ //otherwise Sparki found a wall wallFindType = 0; turnCount = 0; return; } turnCount = 0; break; case 2://we didn't just lose the wall, there was a turn, check for 90 degree turn break; default: break; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
void wallFind(){ switch(wallFindType) { case 1: //Sparki might just be off track a little, search around 5 degrees while( foundLWall == false && turnCount < 5 ){ //as long as we haven't found a wall or turned 10 degrees yet sparki.moveRight(1); turnCount ++; readSensors(); sparki.println("Searching right"); } if( turnCount >= 5 ) { sparki.moveLeft(5);//turn back to original position sparki.println("Done searching right"); } else { //otherwise Sparki found a wall wallFindType = 0; turnCount = 0; return; } turnCount = 0; while( foundLWall == false && turnCount < 5 ) { sparki.moveLeft(1); turnCount ++; readSensors(); sparki.println("Searching left"); } if(turnCount >= 5) { sparki.moveRight(5);//turn back to original position wallFindType = 2;//move on to next wall finding type sparki.println("Done searching left"); } else {//otherwise Sparki found a wall sparki.println(turnCount); wallFindType = 0; turnCount = 0; return; } turnCount = 0; break; case 2://we didn't just lose the wall, there was a turn, check for 90 degree turn break; default: break; } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
break; case 2://we didn't just lose the wall, there was a turn, check for 90 degree turn sparki.moveForward(7); sparki.moveLeft(90); sparki.moveStop(); sparki.println("Search Level 2"); wallFindType = 0; break; default: break; } |
1 2 3 4 5 6 |
int turnCount = 0; boolean foundWall; boolean foundDeadEnd; //the variable void setup(){ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void readSensors() { foundLWall = false; foundDeadEnd = false; edgeLeft = sparki.edgeLeft(); edgeRight = sparki.edgeRight(); lineCenter = sparki.lineCenter(); if( edgeLeft < threshold ) { foundLWall = true; } if( edgeLeft < threshold && lineCenter < threshold ) { foundLWall = false; foundDeadEnd = true; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
readSensors(); if( foundDeadEnd && wallFindType == 0 ) { sparki.moveRight(90); sparki.moveForward(); } else if( foundLWall ) { wallFindType = 0; sparki.moveForward(); } |

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
void loop(){ threshold = 700; readSensors(); if( calibrated == false ) { calibrateSparki(); } else { if( foundDeadEnd && wallFindType == 0 ) { sparki.moveRight(90); sparki.moveBackward(4); //added this line of code sparki.moveForward(); } else if( foundLWall ) { wallFindType = 0; sparki.moveForward(); } else if( foundLWall == false && wallFindType == 0 ) { sparki.moveStop(); wallFindType = 1; //now we enter the wall finding routine sparki.moveForward(.5); } if( wallFindType > 0 ) { wallFind(); } } } |

Here’s the final maze solving code for download-