Eddie on Games

A Pathfinding Optimization Using Flood Fill Source Code

Posted on July 30th, 2008

The overview of this code is found here.

int gCellWidth = 20;
int gNumRows = 10;
int gNumColumns = 30;
int[][] gCells;
 
int gCurrentColorIndex = 0;
 
// This number of colors we generate can certainly be reduced if we need to save space
int gNumColors = gNumRows * gNumColumns;
color[] gColors;
 
// The number of cells referencing each color
int[] gColorRefCount;
 
// A percentage [0.0, 1.0] indicating how many of the cells are walls
float gWallPercentage = 0.4;
 
// We don't want to redraw the map every frame, only when the user clicks a cell
bool bMapNeedsRedraw = true;
 
// Returns: true if the specified row falls within legal bounds, otherwise false.
bool validRow(int row)
{
  if(row >= 0 && row < gNumRows)
  {
    return true;
  }
  return false;
}
 
// Returns: true if the specified column falls within legal bounds, otherwise false.
bool validColumn(int column)
{
  if(column >= 0 && column < gNumColumns)
  {
    return true;
  }
  return false;
}
 
// Returns: the next available color index
int getNextColorIndex()
{
  // TODO: this can certainly be optimized if need be
  for(int i = 0; i < gNumColors; i++)
  {
    if( gColorRefCount[i] == 0 )
    {
      return i;
    }
  }
  // TODO: handle the case where we run out of free colors
  return -1;
}
 
// If the value of the specified cell matches the initialValue, changes the value of the cell
// to the desiredValue.  Then calls the same operation on all of the cells neighbors.
// Note: This is a recursive implementation for simplicity.  It might cause a stack overflow
// when performed on larger maps. In that case we will need to switch to an iterative implementation.
void floodFill(int row, int column, int initialValue, int desiredValue)
{
  if(validRow(row) && validColumn(column) && initialValue != -1 &&
    gCells[row][column] == initialValue && initialValue != desiredValue)
  {
    gCells[row][column] = desiredValue;
    gColorRefCount[initialValue]--;
    gColorRefCount[desiredValue]++;
 
    floodFill(row-1, column, initialValue, desiredValue);
    floodFill(row+1, column, initialValue, desiredValue);
    floodFill(row, column-1, initialValue, desiredValue);
    floodFill(row, column+1, initialValue, desiredValue);
  }
}
 
// Effects: Loops over cells in the map and performs
// floodfill (with a unique color) on each walkable, uncolored cell.
void colorInitialMap()
{
  for(int row = 0; row < gNumRows; row++)
  {
    for(int column = 0; column < gNumColumns; column++)
    {
      if(gCells[row][column] == 0)
      {
        floodFill(row, column, 0, getNextColorIndex());
      }
    }
  }
}
 
// This is the first function that gets called.  It is the c++ equivalent of main()
void setup()
{
  // Pad by 2 pixels because we want lines drawn at the canvas edges to look the same as all other lines
  size(gCellWidth * gNumColumns + 2, gCellWidth * gNumRows + 2);
  smooth();    // anti-alias
  strokeWeight(1);  // Draw lines one pixel thick
  stroke(0);  // Draw shapes in black
  fill(255);  // Fill shapes with white 
 
  gColors = new color[gNumColors];
  gColorRefCount = new int[gNumColors];
 
  // Just create random colors for now. Constrain them so they aren't too dark.
  // Might want to make sure colors are distinct enough in the future,
  // but that will be purely cosmetic.
  for(int i = 0; i < gNumColors; i++)
  {
    gColors[i] = color(random(100, 255), random(100, 255), random(100, 255));
  }
 
  gCells = new int[gNumRows][gNumColumns];
 
  // Randomly initialize cells to indicate they are unwalkable (-1) or walkable (0)
  for(int x = 0; x < gNumRows; x += 1)
  {
    for(int y = 0; y < gNumColumns; y += 1)
    {
      float f = random(0.0, 1.0);
      if(f < gWallPercentage)
      {
        gCells[x][y] = -1;
      }
      else
      {
        gCells[x][y] = 0;
        gColorRefCount[0]++;
      }
    }
  }
 
  // The important part of this demo.  Scan the map for walkable cells
  // and use floodfill make sure they are all colored.
  colorInitialMap();
}
 
// Effects: Sets the color used to fill shapes based on the index.
//   Unwalkable cells are black and walkable cells are non-black.
void setColorFromIndex(int index)
{
  if( index == -1 )
  {
    fill(0);
  }
  else
  {
    fill(gColors[index]);
  }
}
 
// This is the "game loop" that gets called continuously
void draw()
{
  if( bMapNeedsRedraw )
  {
    bMapNeedsRedraw = false;
 
    for(int column = 0; column < gNumColumns; column += 1)
    {
      for(int row = 0; row < gNumRows; row += 1)
      {
        setColorFromIndex( gCells[row][column] );
        rect(1 + column * gCellWidth, 1 + row * gCellWidth, gCellWidth, gCellWidth);
      }
    }
  }
}
 
// Returns: the max color index of the specified cell's neighbors
int getMaxColorIndexOfNeighbors(int row, int column)
{
  int result = -1;
 
  if( validRow(row) && validColumn(column) )
  {
    if( validRow(row-1) )
    {
      result = max(result, gCells[row-1][column]);
    }
    if( validRow(row+1) )
    {
      result = max(result, gCells[row+1][column]);
    }
    if( validColumn(column-1) )
    {
      result = max(result, gCells[row][column-1]);
    }
    if( validColumn(column+1) )
    {
      result = max(result, gCells[row][column+1]);
    }
  }
 
  return result;
}
 
void mousePressed()
{
  int row = int(float(mouseY) / gCellWidth);
  int column = int(float(mouseX) / gCellWidth);
 
  bMapNeedsRedraw = true;
 
  // Change cell from walkable to unwalkable
  if( gCells[row][column] >= 0 )
  {
    int oldColor = gCells[row][column];
    gCells[row][column] = -1;
    gColorRefCount[oldColor]--;
 
    // We don't know if (or how) the 4 neighbors are connected,
    // so take the easy way out and flood fill each with a new color
    if( validRow(row-1) )
    {
      floodFill(row-1, column, oldColor, getNextColorIndex());
    }
    if( validRow(row+1) )
    {
      floodFill(row+1, column, oldColor, getNextColorIndex());
    }
    if( validColumn(column-1) )
    {
      floodFill(row, column-1, oldColor, getNextColorIndex());
    }
    if( validColumn(column+1) )
    {
      floodFill(row, column+1, oldColor, getNextColorIndex());
    }
  }
  // Change cell from unwalkable to walkable
  else
  {
    // There are a handful of cases for how neighbors may be colored.
    // They may be walls and some, all, or none may be the same color.
    int newColor = getMaxColorIndexOfNeighbors(row, column);
    gCells[row][column] = newColor;
    gColorRefCount[newColor]++;
 
    //If all the neighbors aren't walkable, then this cell gets a new color
    if( newColor == -1 )
    {
      newColor = getNextColorIndex();
      gCells[row][column] = newColor;
      gColorRefCount[newColor]++;
    }
    else
    {
      if( validRow(row-1) )
      {
        floodFill(row-1, column, gCells[row-1][column], newColor);
      }
      if( validRow(row+1) )
      {
        floodFill(row+1, column, gCells[row+1][column], newColor);
      }
      if( validColumn(column-1) )
      {
        floodFill(row, column-1, gCells[row][column-1], newColor);
      }
      if( validColumn(column+1) )
      {
        floodFill(row, column+1, gCells[row][column+1], newColor);
      }
    }
  }
}

One Response to 'A Pathfinding Optimization Using Flood Fill Source Code'

Subscribe to comments with RSS or TrackBack to 'A Pathfinding Optimization Using Flood Fill Source Code'.

  1. [...] Source code [...]

Leave a Reply