Sandpiles
About the code
Sandpiles rules
First attempt
static int table[2][SCREEN_WIDTH][SCREEN_HEIGHT];
int currentTable = 0;
We initialize one of the arrays with full 0s except for the center cell which get a big value:
// initialise the table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
table[currentTable][x][y] = 0;
table[currentTable][SCREEN_WIDTH / 2][SCREEN_HEIGHT / 2] = 400000;
To draw the cells we will need a palette that assigns a color to each possible cell value.
// init palette
Color palette[5];
palette[0] = Color(0, 0, 255);
palette[1] = Color(0, 255, 255);
palette[2] = Color(255, 255, 0);
palette[3] = Color(255, 0, 0);
palette[4] = Color(255, 255, 255);
The drawing part is simple, we simply find the right color in the palette for each cell:
// draw the current table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
int color = table[currentTable][x][y];
if (color > 4)
color = 4;
gfx.setPixel(x, y, palette[color]);
}
gfx.render();
sys.processEvents();
The update part is quite straigthforward too. First we copy the old grid to the current one.
// update the table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
table[1 - currentTable][x][y] = table[currentTable][x][y];
}
Then we loop through each cell again to apply the topple rule if we find a value greater than 3.
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
if (table[currentTable][x][y] >= 4)
{
table[1 - currentTable][x][y] -= 4;
if (x - 1 >= 0)
table[1 - currentTable][x - 1][y] += 1;
if (x + 1 < SCREEN_WIDTH)
table[1 - currentTable][x + 1][y] += 1;
if (y - 1 >= 0)
table[1 - currentTable][x][y - 1] += 1;
if (y + 1 < SCREEN_HEIGHT)
table[1 - currentTable][x][y + 1] += 1;
}
}
Finally we switch the grids.
currentTable = 1 - currentTable;
Download source code
Download executable for Windows
This is the result of ths program:
Speeding up (1)
static int table[2][SCREEN_WIDTH + 2][SCREEN_HEIGHT + 2];
[...]
for (int y = 1; y <= SCREEN_HEIGHT; ++y)
for (int x = 1; x <= SCREEN_WIDTH; ++x)
{
if (table[currentTable][x][y] >= 4)
{
table[1 - currentTable][x][y] -= 4;
table[1 - currentTable][x - 1][y] += 1;
table[1 - currentTable][x + 1][y] += 1;
table[1 - currentTable][x][y - 1] += 1;
table[1 - currentTable][x][y + 1] += 1;
}
}
And instead of writing a loop to copy the old grid into the current one before processing it, we can use the more
int screenSize = (SCREEN_WIDTH + 2) * (SCREEN_HEIGHT + 2);
memcpy(table[1 - currentTable], table[currentTable], screenSize * sizeof(int));
Download source code
Download executable for Windows
This code takes about 26 minutes and 40s on my computer.
Speeding up (2)
int counter = 0;
while (sys.isQuitRequested() == false)
{
[...]
counter++;
if ((counter % 16) == 0)
gfx.render();
Download source code
Download executable for Windows
This codes runs in about 18 minutes and 30s.
Speeding up (3)
for (int y = 1; y <= SCREEN_HEIGHT; ++y)
for (int x = 1; x <= SCREEN_WIDTH; ++x)
{
while (table[currentTable][x][y] >= 4)
{
table[1 - currentTable][x][y] -= 4;
table[1 - currentTable][x - 1][y] += 1;
table[1 - currentTable][x + 1][y] += 1;
table[1 - currentTable][x][y - 1] += 1;
table[1 - currentTable][x][y + 1] += 1;
table[currentTable][x][y] -= 4;
}
}
Download source code
Download executable for Windows
Notice that we use the old grid's cell as a counter. We decrease it and we check it to get out of the loop.
Speeding up (4)
for (int y = 1; y <= SCREEN_HEIGHT; ++y)
for (int x = 1; x <= SCREEN_WIDTH; ++x)
{
int height = table[currentTable][x][y] / 4;
table[1 - currentTable][x][y] -= 4 * height;
table[1 - currentTable][x - 1][y] += height;
table[1 - currentTable][x + 1][y] += height;
table[1 - currentTable][x][y - 1] += height;
table[1 - currentTable][x][y + 1] += height;
}
Now we saw that we use the old grid as a counter.
#include <stdio.h>
#include <stdlib.h>
#include "main.h"
#include "Graphics.h"
#include "System.h"
#define SCREEN_WIDTH 600
#define SCREEN_HEIGHT 600
int main(int argc, char* argv[])
{
// init the window
gfx.init("Sandpiles 5", SCREEN_WIDTH, SCREEN_HEIGHT);
gfx.init2D();
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
static int table[SCREEN_WIDTH + 2][SCREEN_HEIGHT + 2];
// initialise the table
for (int y = 1; y <= SCREEN_HEIGHT; ++y)
for (int x = 1; x <= SCREEN_WIDTH; ++x)
table[x][y] = 0;
table[SCREEN_WIDTH / 2][SCREEN_HEIGHT / 2] = 400000;
// init palette
Color palette[5];
palette[0] = Color(0, 0, 255);
palette[1] = Color(0, 255, 255);
palette[2] = Color(255, 255, 0);
palette[3] = Color(255, 0, 0);
palette[4] = Color(255, 255, 255);
int counter = 0;
while (sys.isQuitRequested() == false)
{
// draw the current table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
int color = table[x + 1][y + 1];
if (color > 4)
color = 4;
gfx.setPixel(x, y, palette[color]);
}
counter++;
if ((counter % 16) == 0)
gfx.render();
sys.processEvents();
// update the table
int screenSize = (SCREEN_WIDTH + 2) * (SCREEN_HEIGHT + 2);
for (int y = 1; y <= SCREEN_HEIGHT; ++y)
for (int x = 1; x <= SCREEN_WIDTH; ++x)
{
int height = table[x][y] / 4;
table[x][y] -= height * 4;
table[x - 1][y] += height;
table[x + 1][y] += height;
table[x][y - 1] += height;
table[x][y + 1] += height;
}
}
gfx.quit();
return EXIT_SUCCESS;
}
Download source code
Download executable for Windows
And this program takes only 1min 33s to draw the final image. That's 20 times faster than the first one.
Several starting points
int cx = SCREEN_WIDTH / 2 + 1;
int cy = SCREEN_HEIGHT / 2 + 1;
table[cx][cy-70] = 200000;
table[cx-50][cy+50] = 150000;
table[cx+50][cy+50] = 150000;
Download source code
Download executable for Windows
Here is a picture of what we see at the beginning of the execution:
Trying with a square
int cx = SCREEN_WIDTH / 2 + 1;
int cy = SCREEN_HEIGHT / 2 + 1;
for (int i = -50; i <= 49; ++i)
{
table[cx - 50][cy + i] = 2500;
table[cx + 49][cy + i] = 2500;
table[cx + i][cy - 50] = 2500;
table[cx + i][cy + 49] = 2500;
}
Download source code
Download executable for Windows
This is what we see at the beginning:
Initial space not empty
// initialise the table
for (int y = 1; y <= SCREEN_HEIGHT; ++y)
for (int x = 1; x <= SCREEN_WIDTH; ++x)
table[x][y] = (y > x ? 1 : 0);
int cx = SCREEN_WIDTH / 2;
int cy = SCREEN_HEIGHT / 2;
table[cx][cy] = 400000;
Download source code
Download executable for Windows
This is what we get:
Pattern
// initialise the table
for (int y = 1; y <= SCREEN_HEIGHT; ++y)
for (int x = 1; x <= SCREEN_WIDTH; ++x)
table[x][y] = (y > x ? (((y+x)&1) ? 2 : 1) : 0);
int cx = SCREEN_WIDTH / 2 + 1;
int cy = SCREEN_HEIGHT / 2 + 1;
table[cx][cy] = 400000;
Download source code
Download executable for Windows
Here is the final image: