Floodfill

Hello everyone,

I am busy with a school project in which I'm making a micromouse. Right now I've finished the micromouse and I've already programmed a code which uses right wall following. However, right now I'm stuck as I want to program a code using floodfill. I've already done a lot of research but I simply don't understand it how to implement floodfill in my code.

I did find this sketch, however I don't understand how it works though.

#define NORTH      1  
#define EAST       2  
#define SOUTH      4  
#define WEST       8             
#define VISITED      16  
#define ONROUTE      32  
  
#define NOTNORTH  (255-NORTH)  
#define NOTEAST   (255-EAST)  
#define NOTSOUTH  (255-SOUTH)  
#define NOTWEST   (255-WEST)  
#define ALLWALLS  (NORTH|EAST|SOUTH|WEST)       
  
// maze storage  
#define NUMCELLS 256  
extern CELL maze[NUMCELLS] ;  
extern CELL map[NUMCELLS];  
  
/* 
  floodMaze solves the maze using wall data found in the array maze 
  and places the flooding data - effectively the routemap - into 
  the array map. The destination cell is passed in to the function 
  to allow solving for any location. 
  The state of map on entry is not important and it is not important 
  whether all the walls have been found. The algorithm simply performs 
  the flood based on what it knows at the time it is called. 
  At worst, up to 255 passes are made through the maze date in generating 
  the map. If that many passes are made, there has been an error and 
  the maze should be considered to be seriously in error. 
  Passes of the algorithm are made until either the limit has been reached 
  or the change flag indicates that none of the map data has changed in 
  that pass. At that point the maze can be considered to have been solved. 
  Clearly, unless all the walls have been observed, the map will contain 
  invalid paths. The mouse will discover new walls and call the solver again 
  as needed. 
  The function returns the number of passes required to solve the maze. 
  A returned value of 255 indicates an error. 
  Note that the array is allowed to wrap around in a most dangerous 
  manner by relying on the 8 bit pointer. 
  This should not be a real problem in as much as the existence of the outer 
  walls means we never have to make use of the value 
*/  
  
UCHAR floodMaze(UCHAR goal)  
{  
  UCHAR i,j;  
  UCHAR now,next;  
  UCHAR passes;  
  UCHAR cellwalls;    // the wall data for a given cell  
  UCHAR changed;  
  
  i = 0;  
  do{  
    map[i--] = 255;  
  } while (i);  
  
  map[goal]=0;  
  passes = 0;  
  now = 0;  
  next = now+1;  
  do  
  {  
    changed = 0;  
    i = 0;  
    do  
    {  
      if (map[i]==now)  
      {  
        cellwalls=maze[i];  
        if ((cellwalls & NORTH) == 0)  
        {  
          j = i+1;  
          if (map[j] == 255){ map[j] = next; changed=1;}  
        }  
        if ((cellwalls & EAST) == 0)  
        {  
          j = i + 16;  
          if (map[j] == 255){ map[j] = next; changed=1;}  
        }  
        if ((cellwalls & SOUTH) == 0)  
        {  
          j = i + 255;  
          if (map[j] == 255){ map[j] = next; changed=1;}  
        }  
        if ((cellwalls & WEST) == 0)  
        {  
          j = i + 240;  
          if (map[j] == 255){ map[j] = next; changed=1;}  
        }  
      }  
      i--;  
    } while(i);  
    now  = now+1;  
    next = now+1;  
    passes++;  
  } while(changed);  
  return passes;  
}

Could anyone maybe help me explaining this code or just explaining how to implement floodfill in general?

Thank you before hand!

Thank you for using code tags!

I do not know what floodfill is, but I do know that the definitions for CELL and UCHAR are missing.

First of many google results for "flood fill algorithm" Flood Fill Algorithm Explained

I see, but still I don't get it how to implement it for a micromouse (so with updating the walls and the target cells).

Well, for starters you have to be able to define what "cell" the micromouse is in, and have some ability to move to another "cell".

We can't do that for you, because you have posted no details about your project. Please see "How to use this forum" for helpful hints.

I do know how to use this forum, to make it clear I am not a beginner. I'm already busy with this project for a couple of months.

This is a link to a forum that I used at the start of the project: Micromouse - Project Guidance - Arduino Forum

I'm just stuck at the implementing of the floodfill algorithm and I would appreciate it if anyone could explain how to do this...

Cornedej:
I do know how to use this forum, to make it clear I am not a beginner.
...
....

I'm just stuck at the implementing

Calibration complete.

This question has come up before.

When I did this I had assumed that the maze had already been mapped and you would use the flood fill to find the optimal route to the center.

But I suspect that you can use the exact same method to explore. You just need to recalculate every time you find a new wall.

If you really want to optimize though, you also need to deal with diagonal moves. Maze designers like to put in sections where diagonals can get you extra speed if your mouse is smart enough to use them.

I'm just stuck at the implementing of the floodfill algorithm

What have you tried, and where are you stuck?

Well the thing is, I believe I do know how the floodfill algorithm works. The problem is that I don't understand how to translate the floodfill algorithm to a working sketch with Arduino. I am trying right now to understand example sketches so I can implement it in my own code, however I simply do not understand how the sketches works. If we for example look at the sketch that I posted above.

#define NORTH      1 
#define EAST       2 
#define SOUTH      4 
#define WEST       8             
#define VISITED      16 
#define ONROUTE      32 
 
#define NOTNORTH  (255-NORTH) 
#define NOTEAST   (255-EAST) 
#define NOTSOUTH  (255-SOUTH) 
#define NOTWEST   (255-WEST) 
#define ALLWALLS  (NORTH|EAST|SOUTH|WEST)       
 
// maze storage 
#define NUMCELLS 256 
extern CELL maze[NUMCELLS] ; 
extern CELL map[NUMCELLS]; 
 
/*
  floodMaze solves the maze using wall data found in the array maze
  and places the flooding data - effectively the routemap - into
  the array map. The destination cell is passed in to the function
  to allow solving for any location.
  The state of map on entry is not important and it is not important
  whether all the walls have been found. The algorithm simply performs
  the flood based on what it knows at the time it is called.
  At worst, up to 255 passes are made through the maze date in generating
  the map. If that many passes are made, there has been an error and
  the maze should be considered to be seriously in error.
  Passes of the algorithm are made until either the limit has been reached
  or the change flag indicates that none of the map data has changed in
  that pass. At that point the maze can be considered to have been solved.
  Clearly, unless all the walls have been observed, the map will contain
  invalid paths. The mouse will discover new walls and call the solver again
  as needed.
  The function returns the number of passes required to solve the maze.
  A returned value of 255 indicates an error.
  Note that the array is allowed to wrap around in a most dangerous
  manner by relying on the 8 bit pointer.
  This should not be a real problem in as much as the existence of the outer
  walls means we never have to make use of the value
*/ 
 
UCHAR floodMaze(UCHAR goal) 
{ 
  UCHAR i,j; 
  UCHAR now,next; 
  UCHAR passes; 
  UCHAR cellwalls;    // the wall data for a given cell 
  UCHAR changed; 
 
  i = 0; 
  do{ 
    map[i--] = 255; 
  } while (i); 
 
  map[goal]=0; 
  passes = 0; 
  now = 0; 
  next = now+1; 
  do 
  { 
    changed = 0; 
    i = 0; 
    do 
    { 
      if (map[i]==now) 
      { 
        cellwalls=maze[i]; 
        if ((cellwalls & NORTH) == 0) 
        { 
          j = i+1; 
          if (map[j] == 255){ map[j] = next; changed=1;} 
        } 
        if ((cellwalls & EAST) == 0) 
        { 
          j = i + 16; 
          if (map[j] == 255){ map[j] = next; changed=1;} 
        } 
        if ((cellwalls & SOUTH) == 0) 
        { 
          j = i + 255; 
          if (map[j] == 255){ map[j] = next; changed=1;} 
        } 
        if ((cellwalls & WEST) == 0) 
        { 
          j = i + 240; 
          if (map[j] == 255){ map[j] = next; changed=1;} 
        } 
      } 
      i--; 
    } while(i); 
    now  = now+1; 
    next = now+1; 
    passes++; 
  } while(changed); 
  return passes; 
}

When I look at this, when we look at the UCHAR they declare i and j, I assume these are for the x and y coordinates? But what about the passes and changed, where are does used for?

And then the whole bunch of code below that, I assume that they look at the walls and but a 1 for the walls that are present, but then they say

j = i+1;  
          if (map[j] == 255){ map[j] = next; changed=1;}

What are they doing over there?

They're using an array with wall data and are using it to populate another array with the distance each cell is from the goal. You will have four goal cells of course.

So to start with, they populate the routing array with 255s, which tells you that that cell's distance hasn't been established yet.

Then they set the goal cell's distance to zero - when you get there, there are no more moves necessary.

Then they iterate through the distance array looking for cells a particular distance from the goal, starting with 0. That's held in "now" when they find one, they look at the wall data for that cell. If there's not one to the north, and that northerly cell has no distance yet, that cell gets set to now+1, which they hold in "next". Same for east, west and south.

Then they increment now and next and do it all over again.

Changed tells them whether any cells had their distance set on this pass. If not, there's no point continuing.

There's some odd stuff though. As you see in the thread I linked, the algorithm should set the distance if it hasn't been set or if the distance it has is greater than what you're planning to set it to. That isn't happening here, so I suspect that it doesn't calculate optimal routing in all scenarios.

Also, rather than using a 2D array, they're using a single vector and figuring out manually which cells are adjacent which looks strange at first glance.

It's more conventional to do this recursively. It may be in this case that lack of RAM forced this approach on the author(s).

I see, thank you for trying to help me!
I believe that you cleared up some things for me, still I found it hard to really implement it in my code. You said in the linked thread that you, too, made a micromouse. Would you mind giving me the part of your code where you do the floodfill in your sketch if you still have it?

Maybe that would help me further, since I found it really hard how to really implement it in my code. Like where do I say wether the walls are present and how to decide where it must go after it updated the floodfill etc.

I'll check whether the laptop I used will still boot - it's over twenty years old I think.

However, it should be fairly simple. Define a struct that has wall data and a distance variable. You could pack the wall information into a byte or have separate bytes for north south east west. Distance can be a byte too.

Then create a two dimensional array of those structs. Note that it's inefficient because all the internal walls will be represented in two cells, but it's probably easier to use for navigation and the flood fill.

Please explain how you know where your mouse is located in the maze, and where the walls are, or how you detect them.

You might find this explanation useful: http://web.cecs.pdx.edu/~edam/Reports/2001/DWillardson.pdf

Well my laptop still boots, but actually getting the code off it would be rather tricky I'm afraid. I'd have to find a machine that can read a floppy disk, which I don't have. Not sure I have any floppy disks either.

wildbill:
Well my laptop still boots, but actually getting the code off it would be rather tricky I'm afraid. I'd have to find a machine that can read a floppy disk, which I don't have. Not sure I have any floppy disks either.

Well I didn't expect it to be that old... Would be amazing if you could still receive the code but if you can't it's okay. Anyways thank you already for the work :slight_smile:

jremington:
You might find this explanation useful: http://web.cecs.pdx.edu/~edam/Reports/2001/DWillardson.pdf

This was quite useful indeed, I will try tomorrow wether I am able to make a floodfill algorithm in Arduino, though an example sketch in Arduino would be quite handy...

I'm having a hard time programming the floodfill algorithm...
So I looked up some shares codes online to get me started, although I believe they are all way too complex?... Since the pseudo codes which you find are only a couple of lines.

This is the smallest sketch I've found, is it just me or are they making it way too complex?...
I'm just gettting lost in the amount of code and find it hard to link all the parts of the codes together.

// Scott Camarena
// IEEE Micromouse 2012
// Maze Class - C++ Implimentation
//===============================================================

#ifndef _MAZE_H_
#define _MAZE_H_

#include "../ByteStack/ByteStack.h"

#define NORTH 1
#define EAST 2
#define SOUTH 4
#define WEST 8
#define VISITED 128

typedef unsigned char byte;

// Holds the maze and everything known of the maze
//		in relation to the mouse.

class Maze
{
public:
	byte walls[256]; // One to hold the walls
	byte dist[256]; // Second to hold distance from center
	byte curr; // Current cell index
	byte facing; // Direction facing
	ByteStack stack; // Holds cell indexes to fill


	Maze();

	byte block( byte current, byte dir ); // Returns the neighbor block
	byte getNESW( char& dir ); // Converts relative dir to absolute
	char getRelDir( byte nesw ); // Converts absolute dir to relative
	void movePos( char dir ); // Moves known current position
	//char moveNext(); // Returns recommended next move
	void updateWalls( bool front, bool right, bool back, bool left );
	void flood();
	void blockUnvisited();
	byte minOpenDir( byte cell );

	void cxxPrint(); // Prints distance array when testing in C++
	void cxxCommand( char comm ); // Executes given command
};

byte Maze::block( byte current, byte dir )
{
	switch( dir )
	{
		case 0:
			return current + 16;
		case 1:
			return current + 1;
		case 2:
			return current - 16;
		case 3:
			return current - 1;
	}
}

Maze::Maze()
	: curr(0), facing(0)
{
	//curr = 0; // Set current cell to (0,0)
	//facing = 0; // direction is north
	for( short i = 255; i >= 0; --i )
		walls[i] = 0;
	// Count toward center row from top and bottom
	for( byte col = 0, col2 = 15, dc = 7; col <= col2;
		++col, --col2, --dc )
		// toward center column
		for( byte i = col, dr = 7; i < 128;
			i += 16, --dr )
		{
			dist[i] = dr + dc;
			dist[240 + col - (i - col)] = dr + dc;
			dist[i + col2-col] = dr + dc;
			dist[255 - col - (i - col)] = dr + dc;
		}
		/*dist[119] = 0;
		dist[120] = 0;
		dist[135] = 0;
		dist[136] = 0;*/
}

byte Maze::getNESW( char& dir )
{
	switch( dir )
	{
		case 'f':
			return facing;
		case 'l':
			return facing > 0 ? facing - 1 : 3;
		case 'r':
			return facing < 3 ? facing + 1 : 0;
		case 'b':
			return facing > 1 ? facing - 2 : facing + 2;
	}
}

char Maze::getRelDir( byte nesw )
{
	switch( facing )
	{
		case 0:
			switch( nesw )
			{
				case 0: return 'f';
				case 1: return 'r';
				case 2: return 'b';
				case 3: return 'l';
			}
		case 1:
			switch( nesw )
			{
				case 0: return 'l';
				case 1: return 'f';
				case 2: return 'r';
				case 3: return 'b';
			}
		case 2:
			switch( nesw )
			{
				case 0: return 'b';
				case 1: return 'l';
				case 2: return 'f';
				case 3: return 'r';
			}
		case 3:
			switch( nesw )
			{
				case 0: return 'r';
				case 1: return 'b';
				case 2: return 'l';
				case 3: return 'f';
			}
	}
}

void Maze::movePos( char dir )
{
	byte newDir = getNESW( dir );
	if( dir != 'b' ) facing = newDir;
	switch( newDir )
	{
		case 0:
			curr = curr + 16;
			break;
		case 1:
			curr = curr + 1;
			break;
		case 2:
			curr = curr - 16;
			break;
		case 3:
			curr = curr - 1;
	}
}

void Maze::updateWalls( bool front, bool right, bool back, bool left )
{
	// Make absolute directions
	bool temp;
	switch( facing )
	{
		case 0:
			break;
		case 1:
			temp = front;
			front = left;
			left = back;
			back = right;
			right = temp;
			break;
		case 2:
			temp = left;
			left = right;
			right = temp;
			temp = front;
			front = back;
			back = temp;
			break;
		case 3:
			temp = front;
			front = right;
			right = back;
			back = left;
			left = temp;
	}
	
	// Assuming facing North
	// If there's a wall: OR the current known walls with it
	if( front )
		walls[curr] = walls[curr] | NORTH;
	if( right )
		walls[curr] = walls[curr] | EAST;
	if( back )
		walls[curr] = walls[curr] | SOUTH;
	if( left )
		walls[curr] = walls[curr] | WEST;
		
	walls[curr] = walls[curr] | VISITED;
}

void Maze::flood()
{
	// If stack is empty, push curr
	if( stack.empty() )
	{
		stack.push( curr );
	}
	
	byte cell, minDir;
	// While stack is not empty
	while( !stack.empty() )
	{
		// Pop and test a cell
		cell = stack.pop();
		Serial.print("Cell "); Serial.print( cell );
		Serial.println(" popped");
		minDir = minOpenDir( cell );
		// If the current dist. is NOT 1 + min
		if( dist[cell] != 1 + dist[block( cell, minDir )] )
		{
			//  update this dist value to 1 + min
			dist[cell] = 1 + dist[block( cell, minDir )];
			// push other open neighbors on the stack
			if( !(walls[cell] & NORTH) )
				stack.push( block( cell, 0 ) );
			if( !(walls[cell] & EAST) )
				stack.push( block( cell, 1 ) );
			if( !(walls[cell] & SOUTH) )
				stack.push( block( cell, 2 ) );
			if( !(walls[cell] & WEST) )
				stack.push( block( cell, 3 ) );
		}
	}
}

void Maze::blockUnvisited()
{
	for( short int i = 0; i < 256; ++i )
		if( !(walls[i] & VISITED) )
			dist[i] = 254;
}

// Choose which neighbor cell has lowest dist
//   giving priority to the current facing direction
byte Maze::minOpenDir( byte cell )
{
	byte lowDir = 255;
	byte lowVal = 255;
	bool wallInFront = true;
	// North
	if( !(walls[cell] & NORTH) && dist[block( cell, 0 )] <= lowVal )
	{
		lowVal = dist[block( cell, 0 )];
		lowDir = 0;
		if( facing == 0 ) wallInFront = false;
	}
	// East
	if( !(walls[cell] & EAST) && dist[block( cell, 1 )] <= lowVal )
	{
		lowVal = dist[block( cell, 1 )];
		lowDir = 1;
		if( facing == 1 ) wallInFront = false;
	}
	// South
	if( !(walls[cell] & SOUTH) && dist[block( cell, 2 )] <= lowVal )
	{
		lowVal = dist[block( cell, 2 )];
		lowDir = 2;
		if( facing == 2 ) wallInFront = false;
	}
	// West
	if( !(walls[cell] & WEST) && dist[block( cell, 3 )] <= lowVal )
	{
		lowVal = dist[block( cell, 3 )];
		lowDir = 3;
		if( facing == 3 ) wallInFront = false;
	}
/*
	// If the lowest dir is != facing dir, and lowVal = facing_val change
	if( lowVal == dist[block(cell, facing)]
			&& lowDir != facing && !wallInFront )
		return facing;*/
	return lowDir;
}

#endif

Cornedej:
I believe they are all way too complex?... Since the pseudo codes which you find are only a couple of lines.

So, implement the pseudocode. Can you give an example of the pseudocode you would like to implement? How about this one:

Create a 16x16 array to hold maze elements
Create a stack that can hold 256 entries
Put zero into the finish cells
Fill the rest of the cells with the most optimistic distance in cells
to the goal (i.e. pretend there are no walls until found otherwise.) 
Push the current cell onto the stack
While the stack is not empty:
	Pull a cell from the stack
	Is the cell’s value one greater than the minimum value of its
accessible neighbors?
		If yes, keep pulling cells and checking them.
		If no, change the cell’s value to one greater than the minimum
value of its accessible neighbors, and push all of the cell’s
accessible neighbors onto the stack. 
End While

For each cell you need to know what neighbors are accessible so you need a way to keep track of the walls you find. Make sure when you add a wall to the current cell you also add a wall to the inaccessible neighbor cell.

Here's the flood fill as I remember it, tweaked for Arduino:

const byte size=16;
const byte Empty=255;

const byte North=1;
const byte South=2;
const byte East=4;
const byte West=8;

const byte Walls[size][size] PROGMEM=
{ 
  {West|North,North,North,North,North,North,North,North,North,North,North,North,North,North,North,North|East},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,West|North,North,North,North,North,North,North,North,North,North,North,North,East|North},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,West,0,0,0,West|North,East,0,0,0,0,0,0,East},
  {West,0,0,West,0,0,0,West|South,South,0,0,0,0,0,0,East},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,West|South,South,South,South,South,South,South,South,South,South,South,South,East|South},
  {West,0,0,West,0,0,0,0,0,0,0,0,0,0,0,East},
  {West,0,0,0,West,0,0,0,0,0,0,0,0,0,0,East},
  {West|South,South,South,South,South,South,South,South,South,South,South,South,South,South,South,South|East}
};

typedef struct 
{
//byte walls;
byte steps;  
}Cell;

Cell Maze[size][size];

void setup()
{
  Serial.begin(115200);
  ClearMaze();
  SetGoalCell(7,7);
  SetGoalCell(7,8);
  SetGoalCell(8,7);
  SetGoalCell(8,8);
  PrintWalls();
  Serial.println();
  PrintMaze();
  Serial.println();
  FloodFill(7,7);
  FloodFill(7,8);
  FloodFill(8,7);
  FloodFill(8,8);
  PrintMaze();
}

void ClearMaze()
{
for(byte row=0;row<size;row++)
  for(byte col=0;col<size;col++)
    Maze[row][col].steps=Empty;
}

void SetGoalCell(byte col,byte row)
{
Maze[row][col].steps=0;  
}

void FloodFill(byte row,byte col)
{
byte Next=Maze[row][col].steps+1;
byte wall = pgm_read_byte_near(&(Walls[row][col]));
if(row && (wall&North)==0)
  {
  if(Maze[row-1][col].steps>Next)
    {
    Maze[row-1][col].steps=Next;
    FloodFill(row-1,col);
    }
  } 
if(col<size-1 && (wall&East)==0)
  {
  if(Maze[row][col+1].steps>Next)
    {
    Maze[row][col+1].steps=Next;
    FloodFill(row,col+1);
    }
  } 
if(row<size-1 && (wall&South)==0)
  {
  if(Maze[row+1][col].steps>Next)
    {
    Maze[row+1][col].steps=Next;
    FloodFill(row+1,col);
    }
  } 
  if(col && (wall&West)==0)
  {
  if(Maze[row][col-1].steps>Next)
    {
    Maze[row][col-1].steps=Next;
    FloodFill(row,col-1);
    }
  } 
}

void PrintMaze()
{
for(byte row=0;row<size;row++)
  {
  for(byte col=0;col<size;col++)
    {
    Serial.print(Maze[row][col].steps);
    Serial.print("\t");  
    }
  Serial.println();
  }
}

void PrintWalls()
{
for(byte row=0;row<size;row++)
  {
  for(byte col=0;col<size;col++)
    {
    Serial.print(pgm_read_byte_near(&(Walls[row][col])));
    Serial.print(",");  
    }
  Serial.println();
  }
}

void loop()
{
}

This seems to work, but only because I walled off some of the maze. When I ran it with fewer walls it crashed, presumably by stack overflow. I suspect it would be alright if it were running against a real maze because a dead end would roll back to less stack depth. I was using an Uno. Anything with more RAM would likely be ok even with no walls. Eliminating the temporary wall and next variables would help too I expect.

Recursion is a powerful tool, but it's easy to shoot yourself in the foot with it if your stack is tiny :wink: