// generate a random "walk" of letters from A to Z in a 2D grid, assuming a
// character set with contiguous values of uppercase letters (e.g. ASCII);
// stops if the "walker" gets stuck
// example of a successful walk:
// A . . . I J K . . .
// B C F G H . L . . .
// . D E . . . M . . .
// . . . . . O N . . .
// . . . . . P . . . .
// . . . . . Q . . . .
// . . . . . R S . . .
// . . . . . . T U . .
// . . . . . . . V W Z
// . . . . . . . . X Y
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#include <ctype.h>
#define GRID_SIZE 10
// movement directions
#define DIR_NUM 4
#define UP 0
#define LEFT 1
#define DOWN 2
#define RIGHT 3
#define ANY (-1)
bool can_move(int dir, size_t rpos, size_t cpos, size_t rows, size_t cols,
char grid[rows][cols]);
void generate_random_walk(size_t rows, size_t cols, char grid[rows][cols]);
void print_array(size_t rows, size_t cols, char grid[rows][cols]);
int main(void)
{
char grid[GRID_SIZE][GRID_SIZE] = { 0 };
generate_random_walk(GRID_SIZE, GRID_SIZE, grid);
print_array(GRID_SIZE, GRID_SIZE, grid);
return 0;
}
void print_array(size_t rows, size_t cols, char grid[rows][cols])
{
for (size_t r = 0; r < rows; ++r) {
for (size_t c = 0; c < cols; ++c) {
char grid_c = grid[r][c];
printf(" %c ", isalpha(grid_c) ? grid_c : '.');
}
putchar('\n');
}
}
// checks all the directions by default (ANY)
bool can_move(int dir, size_t rpos, size_t cpos, size_t rows, size_t cols,
char grid[rows][cols])
{
bool cangoup = (rpos > 0) && grid[rpos - 1][cpos] == 0;
bool cangoleft = (cpos > 0) && grid[rpos][cpos - 1] == 0;
bool cangodown = (rpos < rows - 1) && grid[rpos + 1][cpos] == 0;
bool cangoright = (cpos < cols - 1) && grid[rpos][cpos + 1] == 0;
switch (dir) {
case UP: return cangoup;
case LEFT: return cangoleft;
case DOWN: return cangodown;
case RIGHT: return cangoright;
default: return cangoup || cangoleft || cangodown || cangoright; // ANY
}
}
void generate_random_walk(size_t rows, size_t cols, char grid[rows][cols])
{
size_t rpos, cpos;
srand((unsigned int) time(NULL));
rpos = cpos = 0;
grid[0][0] = 'A';
for (char c = 'B'; c <= 'Z' && can_move(ANY, rpos, cpos, rows, cols, grid); c++) {
// move in a random direction
for (int dir;;) {
dir = rand() % DIR_NUM;
if (can_move(dir, rpos, cpos, rows, cols, grid)) {
switch (dir) {
case UP: --rpos; break;
case LEFT: --cpos; break;
case DOWN: ++rpos; break;
case RIGHT: ++cpos; break;
default:
printf("Impossible movement direction.\n");
exit(EXIT_FAILURE);
}
break; // break out of the loop
}
}
// leave a trail
grid[rpos][cpos] = c;
}
}
To summarize, this program prints a grid with a random walk of uppercase letters in order. If the "walker" cannot move in any direction due to lack of space around it (up, down, left, and right), the walking terminates.
What I'm interested in, aside from general tips regarding my code that you may be able to give me, are two things:
I've read that functions shouldn't normally take more than 2–3 parameters; do you think my functions really "suffer" from taking more than 3 parameters, and if so, how would I go about mitigating that?
Should I be using a
size_t
when looping over an array or similar, or should I use anint
instead? I've read here on Stack Exchange various opinions concerning this: some people saysize_t
should always be used since it's essentially undefined behavior if the array's size is greater thanMAX_INT
, etc., while others say unsigned and signed types ought not to be mixed (and I generally have to use them in expressions that undergo the usual arithmetic conversions), and this is a major source of bugs. Pragmatically, I believe I should therefore use a regularint
. There's another thing I've noticed, and that is if I have to actually use these counters to perform some calculation: in those cases I really have no recourse, and what I end up doing is simply casting and praying, hoping that the array is of a reasonable size (i.e. that mysize_t
counter, or the result of the expression isn't out of the representable range for anint
). I think this is even worse than using anint
from the beginning, since the interface that thesize_t
type provides is not conformed to, so my code is "lying" to both itself and the reader.
Note that I would use an enum
instead of the #define
definitions for the directions, but my book hasn't gotten to them yet, so I think we shouldn't focus on this. Also, I've looked at other questions with this same exercise, but the code/approach presented there is somewhat different.