fast-abelian-sandpile/sandpile_fifo.c

130 lines
3.4 KiB
C

#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include <stdio.h>
#include <math.h>
#include "common.c"
/// This one is the "smart" one, instead of doing a full scan of the
/// grid, a FIFO of points to fire is kept up to date each time a cell
/// is firered. Sadly "smart" is not smart.
typedef struct point {
int x;
int y;
} point_t;
typedef struct todo {
int width;
int height;
int size;
int idx;
int qty;
point_t *points;
bool *dedup; // Used to avoid adding twice the same point, stored as x*width+y
} todo_t;
// The todo is deduplicated list of points.
todo_t *todo_new(size_t width, size_t height)
{
todo_t *todo = malloc(sizeof(*todo));
todo->qty = 0;
todo->width = width;
todo->height = height;
todo->size = width * 4;
todo->dedup = calloc(todo->width * todo->height, sizeof(bool));
todo->points = malloc(todo->size * sizeof(point_t));
return todo;
}
static void todo_write(todo_t *todo, point_t point)
{
if (todo->dedup[point.x * todo->width + point.y])
return;
if (todo->qty == todo->size - 1) {
todo->points = realloc(todo->points, sizeof(point_t) * todo->size * 2);
todo->size *= 4;
}
todo->dedup[point.x * todo->width + point.y] = true;
todo->points[todo->qty] = point;
todo->qty += 1;
}
static point_t todo_read(todo_t *todo)
{
assert(todo->qty > 0);
todo->qty -= 1;
todo->dedup[todo->points[todo->qty].x * todo->width + todo->points[todo->qty].y] = false;
return todo->points[todo->qty];
}
void unstable_to_todo(int width, int **terrain, todo_t *todo)
{
for (int x = 0; x < width; x++)
for (int y = 0; y < width; y++)
if (terrain[x][y] >= 4)
todo_write(todo, (point_t){x, y});
}
void apply_gravity(int width, int **terrain)
{
int div;
todo_t *todo;
point_t point;
unsigned int collapses = 0;
todo = todo_new(width, width);
unstable_to_todo(width, terrain, todo);
while (todo->qty) {
point = todo_read(todo);
collapses += 1;
div = terrain[point.x][point.y] / 4;
terrain[point.x][point.y] %= 4;
terrain[point.x - 1][point.y] += div;
terrain[point.x + 1][point.y] += div;
terrain[point.x][point.y + 1] += div;
terrain[point.x][point.y - 1] += div;
if (terrain[point.x - 1][point.y] >= 4)
todo_write(todo, (point_t){point.x-1, point.y});
if (terrain[point.x + 1][point.y] >= 4)
todo_write(todo, (point_t){point.x+1, point.y});
if (terrain[point.x][point.y + 1] >= 4)
todo_write(todo, (point_t){point.x, point.y+1});
if (terrain[point.x][point.y - 1] >= 4)
todo_write(todo, (point_t){point.x, point.y-1});
}
}
int **sandpile_new(int width)
{
int **terrain = calloc(width, sizeof(int*));
int *data = calloc(width * width, sizeof(int));
for (int i = 0; i < width; i++) {
terrain[i] = data + i * width;
}
return terrain;
}
int main(int argc, char **argv)
{
args args = {0};
if (parse_args(argc, argv, &args))
return EXIT_FAILURE;
int width = sandpile_width(args.height);
int **terrain = sandpile_new(width);
terrain[width / 2][width / 2] = args.height;
apply_gravity(width, terrain);
save_terrain(width, terrain, &args);
return EXIT_SUCCESS;
}