2

I am attempting to make a simple 2D sand simulation using basic cellular automata concepts (for more information watch: https://www.youtube.com/watch?v=VLZjd_Y1gJ8) and display it in the console but for some reason random particles appear near the bottom line of the matrix and there is a lot of chaos if you modify the matrix in the slightest(e.g. if you draw a line across horizontally in the middle or a random point somewhere low it will appear completely different), I believe this could be due to an error in my in bounds check if(grid[y][x] <= sizeY && grid[y][x] <=sizeX) but I am not sure.

Code:

#include <stdio.h>
#include <stdlib.h>
#ifdef __unix__
# include <unistd.h>
#elif defined _WIN32
# include <windows.h>
#define sleep(x) Sleep(1000 * (x))
#endif

int sizeX = 20;
int sizeY = 20;
int alive = 1;
int dead = 0;

int grid[20][20] = {{0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
                    {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}};

int drawScreen(int x, int y) {
  system("cls"); // Clear screen
  for (x = 0; x <= sizeX; x++) {
    printf("\n");
    for (y = 0; y <= sizeY; y++) {
      if (grid[x][y] == alive) {
        printf("@");
      } else {
        printf(" ");
      }
    }
  }
}

int sand(int grid[sizeX][sizeY], int x, int y) {
  if (grid[y + 1][x] == dead) {
    grid[y][x] = dead;
    grid[y + 1][x] = alive;
  } else if (grid[y + 1][x - 1] == dead) {
    grid[y][x] = dead;
    grid[y + 1][x - 1] = alive;
  } else if (grid[y + 1][x + 1] == dead) {
    grid[y][x] = dead;
    grid[y + 1][x + 1] = alive;
  }
}

int main(void) {
  int x, y, iterations;

  for (iterations = 0; iterations < 1000; iterations++) {
    for (y = sizeY - 1; y >= 0; y--) {
      for (x = sizeX - 1; x >= 0; x--) {
        if (grid[y][x] == alive) {
          if (grid[y + 1][x] <= sizeY && grid[y][x + 1] <= sizeX) { // Bounds
            sand(grid, x, y);
          }
        }
      }
    }
    drawScreen(x, y);
  }
}
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Saw
  • 115
  • 1
  • 16
  • 2
    On a few unrelated notes: `#include` is attempting to include what used to be a DOS specific header files, still available in Windows. It's not available in Linux or on macOS, and isn't usually needed at all. Also, `system("cls")` is very Windows-specific and won't work on Linux or macOS. You should try to avoid these things when attempting to be platform independent. – Some programmer dude Aug 24 '22 at 13:40
  • 2
    You should avoid using globals unless absolutely necessary. – Fiddling Bits Aug 24 '22 at 13:43
  • 3
    As for your problems, you start out with `x` and `y` equal to `sizeX` and `sizeY`. That means both `x` and `y` will be *out of bounds* immediately. And `x+1` and `y+1` even more so. Also when `x` and `y` are both equal to `0`, `x-1` and `y-1` will be out of bounds again. All this out-of-bounds access will lead to *undefined behavior*, making your program ill-formed. – Some programmer dude Aug 24 '22 at 13:44
  • Springboarding off previous comment, the first evidence during run-time that array out of bounds occurs is here: `if(grid[y][x] == alive){` where the message on my machine is: `FATAL RUN-TIME ERROR: "exec.c", line 74, col 17, thread id 4308: Dereference of out-of-bounds pointer: 81 bytes (21 elements) past end of array.` – ryyker Aug 24 '22 at 13:53
  • @Some programmer dude Thanks for your advice on your first comment but I am using windows so I do not belive that is the source of the problem, as for you second comment, how can I fix this? – Saw Aug 24 '22 at 14:00

1 Answers1

2

Your issues boil down to undefined behavior reading array memory out of bounds.

Arrays are zero-indexed, so loops should run x < sizeX, not x <= sizeX.

if (grid[y + 1][x] <= size_y && grid[y][x + 1] <= size_x) { // Bounds

isn't a good bounds check because it's testing the grid values, not y and x. You probably meant to test y < size_y - 1 and x < size_x - 1.

Now, the bounds check should be precise. If a cell is on the right side, we should still potentially move it downward or to the left, but skip the x + 1 rightward check/move. Same for other bounds. With this in mind, I'd bake the bounds checks into the sand() function (since functions are actions, I'd rename this as simulate_sand()).

You've also switched grid[y][x] to grid[x][y] in your drawScreen() function. Since your grid has the same height and width, it doesn't cause any problems, but if they weren't the same, this would be out of bounds.

You can compile with gcc -Wall -ggdb3 your_file.c and then use valgrind ./a to get clear error messages for this UB. -Wall tells us that drawScreen and sand return nothing when they're typed to return int. Change them to void.

I'd caution against scoping your loop control variables at the top of the function, which can lead to bugs. drawScreen() doesn't need x and y parameters. Those can be purely local.

Scoping the grid globally isn't ideal, either, but I'll leave it to preserve your original intent.

#include <stdio.h>
#include <windows.h>

const int size_x = 20;
const int size_y = 20;
const int alive = 1;
const int dead = 0;
int grid[20][20] = {
  {0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0},
  {1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,1,0,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0},
  {0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0},
  {0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,0,0,1,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0},
  {0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};

void draw_screen() {
  system("cls");

  for (int y = 0; y < size_y; y++) {
    for (int x = 0; x < size_x; x++) {
      putc(grid[y][x] == alive ? '@' : ' ', stdout);
    }

    putc('\n', stdout);
  }
}

void simulate_sand(const int x, const int y) {
  // do nothing if dead or already at the bottom of the screen
  if (grid[y][x] == dead || y >= size_y - 1) {
    return;
  }
  // move down if empty
  else if (grid[y+1][x] == dead) {
    grid[y][x] = dead;
    grid[y+1][x] = alive;
  }
  // move down and left if empty
  else if (x > 0 && grid[y+1][x-1] == dead) {
    grid[y][x] = dead;
    grid[y+1][x-1] = alive;
  }
  // move down and right if empty
  else if (x < size_x - 1 && grid[y+1][x+1] == dead) {
    grid[y][x] = dead;
    grid[y+1][x+1] = alive;
  }
  // else do nothing; sand is resting on sand cells beneath it
}

int main(void) {
  for (int iterations = 0; iterations < 1000; iterations++) {
    for (int y = size_y - 1; y >= 0; y--) {
      for (int x = size_x - 1; x >= 0; x--) {
        simulate_sand(x, y);
      }
    }

    draw_screen();
  }
}
ggorlen
  • 44,755
  • 7
  • 76
  • 106