01:42 pm on Oct 7, 2018 | read the article | tags: howto
in this post i’ll use C to construct a maze, that will be later used for my swarm experiments.
i’ll be using a graph-model for the maze: each vertex is a room from the maze, which is connected by edges to other rooms. in a 2-D classic maze of size m×n, with m spanning from top to bottom and n spanning from left to right, each room has at most 4 neighbors. this means each vertex in my graph will have at most 4 edges: top, bottom, left and right. the total number of edges is m×(n-1) for vertical edges (top, bottom) and (m-1)×n for horizontal edges (left, right). in order to create a maze, in the previously described graph, i’ll choose using a version of Dijkstra algorithm a maximum spanning tree. to make the maze random, i’ll mark each edge with a random weight and then run the maximum spanning tree algorithm.

i’ll be using matrices as data structures. this means that each vertex will be labeled with a pair (i,j) with i from 0 to m-1 and j from 0 to n-1. as the graph is not oriented, i’ll have:

the number of required edges’ weights can be held in a m×(2×n-1)-n vector, corresponding to an incomplete m×(2×n-1) matrix.
this step is quite easy. just loop over all m×(2×n-1)-n and assign them a random value between 1 and 255. i’ll be using 0 to represent edges that are missing.
/**
* the function initializes the weights matrix as described above;
* @param m (int) the dimension of the maze on top-bottom axis;
* @param n (int) the dimension of the maze on left-right axis;
* @returns: an uint8_t matrix, of size (m-1)*2*(n-1) filled with random data;
*/
uint8_t * maze_weight_init (int m, int n) {
/** @var maze the weight matrix */
uint8_t * maze;
/** @var c is a variable index */
int c;
/** i reserve the space in the computer memory for the matrix */
maze = (uint8_t *) malloc ((m * (2 * n - 1) - n) * sizeof (uint8_t));
/** if memory reservation is successful, i'm filling the matrix */
if (maze != NULL)
for (c = 0; c < m * (2 * n - 1) - n; c++)
*(maze + c) = (uint8_t) ((1 + rand ()) % 256);
return maze;
}
tip:
the matrix is stored as a single vector, meaning a cell on the position (i,j) can be accessed by finding as the (i×cols+j)-th component of the vector. this map is bijective, meaning that the i-th component of the vector corresponds to position (i/cols, i%cols) in the matrix, where / is the integer division and % is the reminder operator.
the matrix is not useful like this. it needs to be sorted. i'm using a simple (inefficient) bubble sort algorithm. i'll take advantage of how i stored the matrix so i can simply arrange the associated vector. i'll create a vector into which i'll keep the association of indices.
bubble sort works like this: take the first weight from the vector, and if it's heavier that the one above, swap their places. this has to be repeated until the entire vector is sorted: this happens when there are no swaps on a swipe.
/** the function is sorting the associated vector V, returning a mapping of the
* sorted indices, M, with V(i) being the original vector, while V(M(i)) is the sorted one
* @param weights (uint8_t *) is the original vector
* @param m (int) is the top-bottom size of the maze
* @param n (int) is the left-right size of the maze
* @returns: an int mapping vector between the sorted version and the original one
*/
int * maze_edges_sort (uint8_t * weights, int m, int n) {
/** @var indices the vector containing the sorted mapping */
int * indices;
/**
* @var c a variable index
* @var t a temporary variable used in swapping two indices
* @var o a temporary variable storing last index swapped
* @var l, initially the length of the weights vector,
* after, the length of the unsorted vector
*/
int c, t, o, l = m * (2 * n - 1) - n;
/** initialize indices with an identity mapping i->i */
indices = (int *) malloc (l * sizeof (int *));
for (c = 0; c < l; c++)
*(indices + c) = c;
if (indices != NULL)
/** while there's still some unsorted part of the vector */
while (l > 0) {
/** o holds the last swapped value */
o = 0;
/** i'm looping until the end of the unsorted vector */
for (c = 1; c < l; c++) {
/** if the weights don't respect the sort order, swap them */
if (*(weights + *(indices + c - 1)) > *(weights + *(indices + c))) {
/**
* of course, i'm swapping indices in the mapping,
* not real values, as the initial vector remains
* unchanged, only the mapping function deviates from
* identity map
*/
t = *(indices + c - 1);
*(indices + c - 1) = *(indices + c);
*(indices + c) = t;
/** store the last swapped index */
o = c;
}
}
/** the length of the unsorted vector equals the last swapped index */
l = o;
}
return indices;
}
this algorithm is more complex that the previous ones. for easier understanding, i need to explain some notions:
i'll start with an empty set of sub-trees from the original graph. this will be called a forest set. forests have at least one tree. at the end of the algorithm i'll have in this set a tree that will link all vertices from the original graph. this is called a maximum spanning tree.
with each sorted edge, lighter to heavier, i'll do the following:
/**
* the procedure takes as parameters a weight matrix together with its size
* and modifies in place the weights matrix, removing the unnecessary edges
* an edge is kept, if the weight is strict positive, while an edge is
* deleted, if its weight is zero.
* @param weights (uint8_t *) is the weight vector
* @param m (int) is the maze top-bottom size
* @param n (int) is the maze left-right size
*/
void maze_init (uint8_t * weights, int m, int n) {
/**
* @var indices is the sorted mapping for the weights vector;
* @var matrix is a vector-stored matrix that has 0 on (i,j)
* 0 on (i,j) position, if the (i,j) vertex was not visited
* color>0 on (i,j) position, if the (i,j) vertex was visited
* and (i,j) is part of the "color" sub-tree
*/
int * indices, * matrix;
/**
* @var c,d are variable indices;
* @var row,col are the row and column indices in the @see matrix
* @var n_row,n_col are the indices for the vertex connected to
* (row,col);
* @var min_color is the minimum "color" when merging an existing
* sub-tree with a new edge. the edge can link two existing
* sub-trees, meaning i'll have to choose a single color for both
* @var max_color is analog with @see min_color;
* @var color is the current available color for new sub-trees;
*/
int c, d, row, col, n_row, n_col, min_color, max_color, color = 1;
/** reserve memory for the is-visited? matrix */
matrix = (int *) malloc (m * n * sizeof (int));
for (c = 0; c < m * n; c++)
*(matrix + c) = 0;
/** sort the graph weights */
indices = maze_edges_sort (weights, m, n);
/** loop through sorted edges */
for (c = 0; c < m * (2 * n - 1) - n; c++) {
/** get the edge first vertex label as (row, col) */
row = *(indices + c) / (2 * n - 1);
col = *(indices + c) % (2 * n - 1);
/**
* based on how is stored, get the label for the
* second vertex associated with the edge
*/
if (col < n - 1) {
n_row = row;
n_col = col + 1;
}
else {
col = col - n + 1;
n_row = row + 1;
n_col = col;
}
/**
* check if the current edge can be added to the forest:
* the edge needs to fulfill all requirements:
* - the edge is not already part of a tree, meaning that both
* vertices are the same non-zero color
*/
if (
(*(matrix + row * n + col) == *(matrix + n_row * n + n_col)) &&
*(matrix + row * n + col) > 0
) {
*(weights + *(indices + c)) = 0;
continue;
}
/**
* find the color of the new vertices, by getting minimum and
* maximum color. both can be zero, if we start a new sub-tree
*/
if (*(matrix + row * n + col) < *(matrix + n_row * n + n_col)) {
min_color = *(matrix + row * n + col);
max_color = *(matrix + n_row * n + n_col);
}
else {
min_color = *(matrix + n_row * n + n_col);
max_color = *(matrix + row * n + col);
}
if (min_color == 0) {
/**
* here, min = max = 0, this means we have a new sub-tree
* so i color it with the next available color
*/
if (max_color == 0) {
*(matrix + row * n + col) =
*(matrix + n_row * n + n_col) = color ++;
}
/** here, the edge has an open end, the other is connected */
else {
*(matrix + row * n + col) =
*(matrix + n_row * n + n_col) = max_color;
}
}
else {
/**
* here min, max > 0, both different, this means that the
* edge is connecting two different color sub-trees, so i
* make both the same color (min color)
*/
*(matrix + row * n + col) =
*(matrix + n_row * n + n_col) = min_color;
for (d = 0; d < m * n; d++)
if (*(matrix + d) == max_color)
*(matrix + d) = min_color;
}
}
/** remember to free the reserved memory */
free (indices);
free (matrix);
}
piece of cake. first, i'll have to initialize my weights. than, to build the maximum spanning tree. a good exercise is to display the maze on the screen using ASCII art.
#include/** required for malloc, srand and rand functions */ #include /** useful for printing stuff, with printf */ #include /** i need the uint8_t definition */ #include /** i need the time function */ /** * this is the normal, unix-style format for the program entrypoint * @param argc (int) is the number of arguments from the command line; * @param argv (char **) is an array of strings, containing the command line arguments; * @returns: an integer, zero if no error has occurred. */ int main (int argc, char ** argv) { /** @var weights (uint8_t *) my weights matrix */ uint8_t * weights; /** @var m, n (int) the maze size */ int m = 4, n = 4; /** make the random numbers random :-) */ srand (time (NULL)); /** initialize the weights matrix */ weights = maze_weight_init (m, n); /** build a maximum spanning tree from the matrix */ maze_init (weights, m, n); /** tell the operating system that there's no error */ return 0; }
aceast sait folosește cookie-uri pentru a îmbunătăți experiența ta, ca vizitator. în același scop, acest sait utilizează modulul Facebook pentru integrarea cu rețeaua lor socială. poți accesa aici politica mea de confidențialitate.