| MAIN | NEWS | FAQS | LINKS | ARTICLES | WORKSHOP |
Send Mail To:Steve Register


Finding Explorable Regions - Ross Morgan-Linial.
This article was originally written for Darren Hebden's RLNews

This program takes a map and divides it into regions that are fully 
connected - that is, not split in half by walls, so the player can 
get anywhere in the region starting from a random point. I wrote it 
to determine if all of a dungeon level can be reached from the 
stairs, but you might find other uses for it; for example, you could 
use the results to find out where corridors need to be added to 
result in a fully connected level. Without further ado, here it is.

#include <stdio.h>

/*
 * This program divides a set of 'grids', which can be either wall or
 * floor, into connected regions. Every grid in a region is connected
 * to every other square in that region by vertical or horizontal (not
 * diagonal) connections, and not connected to grids in any other
 * region.
 *
 * We maintain a map that shows which region each grid is in. To avoid
 * iterating over the entire map every time we discover two candidate
 * regions are joined, we use a table that maps the numbers used on
 * the grid to actual region numbers. At the end of the computation,
 * we renumber the grid using this table.
 */

/* Change this to 1 to see how it works */
#define debug 0

#define MAP_WIDTH   12
#define MAP_HEIGHT  12

/*
 * Our map - 0 is wall, 1 is floor.
 *
 * We have to have a border of walls, or we may get strange errors.
 */
char grid[MAP_HEIGHT][MAP_WIDTH] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0},
{0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0},
{0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0},
{0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0},
{0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0},
{0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0},
{0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0},
{0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0},
{0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};

#define MAX_REGIONS 16
#define NO_REGION   0   /* 0 is also a valid region */

int region_grid[MAP_HEIGHT][MAP_WIDTH];
int region_map[MAX_REGIONS];
int num_regions;

/*
 * To reduce processing time, when two regions are found to connect
 * (they're really the same region), we just mark them as the same
 * without actually renumbering grids. At the end of the computation,
 * or if we run out of region numbers, we use this function to update
 * all grids to true region numbers. It also compacts the region
 * numbers, so that afterwards all region numbers 0..num_regions-1
 * are still used.
 */
void remap_regions(void)
{
    int region_index[MAX_REGIONS];
    int region_index2[MAX_REGIONS];
    int new_regions;
    int x, y;
    int i;

    new_regions = 0;

    /*
     * Iterate through the regions, and assign a new number to each
     * non-remapped region so that they'll end up compacted.
     */
    for (i = 0; i < num_regions; i++)
    {
        /* Has this region not been mapped to another region? */
        if (region_map[i] == i)
        {
            /* No, it hasn't. Assign it a new region number. */
            if (debug)
            {
                printf("Mapping region: #%i -> #%i\n", i,             
                       new_regions);
			}
            region_index[i] = new_regions++;
        }
        else
        {
            /* It's been renumbered. Check for processing errors. */
            if (region_map[region_map[i]] != region_map[i])
            {
                /*
                 * We've encountered a bug: this region has been
                 * mapped to a region that has also been mapped. Give
                 * an error and abort.
                 */
                fprintf(stderr, "Map %i->%i->%i\n", i, region_map[i],
                        region_map[region_map[i]]);
                abort();
            }

            /* Assign an in-bounds but otherwise meaningless value. */
            region_index[i] = NO_REGION;
        }
    }

    /*
     * Construct a table of the double-indirection through remapping
     * and compaction.
     */
    for (i = 0; i < num_regions; i++)
        region_index2[i] = region_index[region_map[i]];

    /* Renumber the grids, using the table we just computed. */
    for (y = 0; y < MAP_HEIGHT; y++)
        for (x = 0; x < MAP_WIDTH; x++)
            region_grid[y][x] = region_index2[region_grid[y][x]];

    /* Create a new do-nothing region mapping table. */
    for (i = 0; i < new_regions; i++)
        region_map[i] = i;

    /* Save the new number of regions. */
    num_regions = new_regions;
}

/*
 * Join two candidate regions together. This just involves updating
 * the region remapping table.
 */
void join_regions(int r1, int r2)
{
    int i;
    int r1_map, r2_map;

    /* We have to operate on primary (unremapped) regions here */
    r1_map = region_map[r1];
    r2_map = region_map[r2];

    if (debug)
    {
        printf("Joining regions #%i (%i) and #%i (%i)\n", r1, r1_map,
               r2, r2_map);
    }

    /* Modify the region mapping table. */
    for (i = 0; i < num_regions; i++)
        if (region_map[i] == r2_map)
        {
            if (debug)
            {
                printf("Mapping region #%i (%i) -> #%i\n", i,
                       region_map[i], r1_map);
            }
            region_map[i] = r1_map;
        }
}

/*
 * Create a new region.
 */
int new_region(void)
{
    int i;

    /* If we're out of regions, compact them. */
    if (num_regions >= MAX_REGIONS)
    {
        if (debug)
            printf("Remapping regions\n");
        remap_regions();
    }

    /* If we're still out of regions, we have a problem. */
    if (num_regions >= MAX_REGIONS)
    {
        fprintf(stderr, "Too many regions\n");
        abort();
    }

    /* Allocate a new region. */
    i = num_regions++;
    region_map[i] = i;

    if (debug)
        printf("New region: #%i\n", i);

    return i;
}

void find_regions(void)
{
    int x, y;
    int i;

    if (debug)
        printf("Clearing region grid\n");

    /* Clear the region grid to a valid (but meaningless) value. */
    for (y = 0; y < MAP_HEIGHT; y++)
        for (x = 0; x < MAP_WIDTH; x++)
            region_grid[y][x] = NO_REGION;

    /* Initialize the remapping table to a null map. */
    for (i = 0; i < MAX_REGIONS; i++)
        region_map[i] = i;

    /* Start with no regions. */
    num_regions = 0;

    /* Consider each grid, except the borders (leave them as walls) */
    for (y = 1; y < MAP_HEIGHT - 1; y++)
    {
        for (x = 1; x < MAP_WIDTH - 1; x++)
        {
            /* Skip wall grids. */
            if (!grid[y][x])
                continue;

            if (debug)
                printf("(%i, %i) ", x, y);

            /* 
             * Consider the possible combinations of left, above
             * grids.
             */
            if (!grid[y - 1][x])
            {
                if (!grid[y][x - 1])
                {
                    /* No floor left or above */
                    region_grid[y][x] = new_region();
                }
                else
                {
                    /* Floor left, but not above */
                    if (debug)
                    {
                        printf("Copying over #%i\n", 
                               region_grid[y][x - 1]);
                    }
                    region_grid[y][x] = region_grid[y][x - 1];
                }
            }
            else
            {
                if (!grid[y][x - 1])
                {
                    /* Floor above, but not left */
                    if (debug)
                    {
                        printf("Copying down #%i\n", 
                               region_grid[y - 1][x]);
                    }
                    region_grid[y][x] = region_grid[y - 1][x];
                }
                else
                {
                    /*
                     * Floor both left and above - are they the same 
                     * region?
                     */
                    if (region_map[region_grid[y - 1][x]] !=
                        region_map[region_grid[y][x - 1]])
                    {
                        /* No, join them. */
                        join_regions(region_grid[y - 1][x],
                                     region_grid[y][x - 1]);
                    }

                    /* They're now the same region, so copy one. */
                    if (debug)
                    {
                        printf("Copying down #%i\n", 
                               region_grid[y - 1][x]);
                    }
                    region_grid[y][x] = region_grid[y - 1][x];
                }
            }
        }
    }

    /* Do a final remapping, to make the region_grid[][] accurate. */
    remap_regions();
}

/*
 * Print our results.
 */
void print_map(void)
{
    int x, y;

    for (y = 1; y < MAP_HEIGHT - 1; y++)
    {
        for (x = 1; x < MAP_WIDTH - 1; x++)
        {
            if (grid[y][x])
            {
                printf("%c", 'A' + region_grid[y][x]);
            }
            else
                printf(" ");
        }
        printf("\n");
    }
}

int main(void)
{
    find_regions();

    print_map();

    return 0;
}
Copyright 2001 Steve Register.