Why are my matrix values disappearing in C?

Viewed 102

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);
  }
}
1 Answers

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();
  }
}
Related