436 lines
9.8 KiB
C
436 lines
9.8 KiB
C
#include "canvas.h"
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <stdlib.h>
|
|
|
|
Chunk *createChunk(int x, int y)
|
|
{
|
|
Chunk *c = malloc(sizeof(Chunk));
|
|
c->x = x;
|
|
c->y = y;
|
|
|
|
for (int i = 0; i < 8; ++i)
|
|
c->around[i] = NULL;
|
|
|
|
c->count = 0;
|
|
c->current = malloc(sizeof(bool) * CHUNK_SIZE * CHUNK_SIZE);
|
|
c->next = malloc(sizeof(bool) * CHUNK_SIZE * CHUNK_SIZE);
|
|
c->down = NULL;
|
|
for (int i = 0; i < CHUNK_SIZE * CHUNK_SIZE; ++i) {
|
|
c->current[i] = false;
|
|
c->next[i] = false;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
void chunkPrint(Chunk *c, int w, int h)
|
|
{
|
|
if (w <= 0 || w > CHUNK_SIZE)
|
|
w = CHUNK_SIZE;
|
|
if (h <= 0 || h > CHUNK_SIZE)
|
|
h = CHUNK_SIZE;
|
|
|
|
for (int y = 0; y < w; y++) {
|
|
for (int x = 0; x < w; x++) {
|
|
if (c->current[x + y * CHUNK_SIZE])
|
|
printf("#");
|
|
else
|
|
printf(".");
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
#define HASH(W, X, Y) ((X) + 100*(Y)) % (W)->hash_table_size
|
|
|
|
Chunk *canvasGetChunk(Canvas *w, int cx, int cy)
|
|
{
|
|
int hash = HASH(w, cx, cy);
|
|
if (hash < 0)
|
|
hash += w->hash_table_size;
|
|
|
|
Chunk *cursor = w->hash_table[hash];
|
|
while (cursor) {
|
|
if (cursor->x == cx && cursor->y == cy)
|
|
return cursor;
|
|
else
|
|
cursor = cursor->down;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void chunkLink(EDIR dir1, EDIR dir2, Chunk *one, Chunk *two)
|
|
{
|
|
if (two) {
|
|
one->around[dir1] = two;
|
|
two->around[dir2] = one;
|
|
} //else {
|
|
//fprintf(stderr, "No link possible for (%d, %d)\n", one->x, one->y);
|
|
//}
|
|
}
|
|
|
|
Chunk *canvasCreateChunk(Canvas *w, int cx, int cy)
|
|
{
|
|
Chunk *c = createChunk(cx, cy);
|
|
|
|
int hash = HASH(w, cx, cy);
|
|
if (hash < 0)
|
|
hash += w->hash_table_size;
|
|
|
|
if (w->hash_table[hash]) {
|
|
Chunk *cursor = w->hash_table[hash];
|
|
while (cursor->down)
|
|
cursor = cursor->down;
|
|
cursor->down = c;
|
|
} else
|
|
w->hash_table[hash] = c;
|
|
|
|
chunkLink(ED_T, ED_B, c, canvasGetChunk(w, cx, cy - 1));
|
|
chunkLink(ED_TR, ED_BL, c, canvasGetChunk(w, cx + 1, cy - 1));
|
|
chunkLink(ED_R, ED_L, c, canvasGetChunk(w, cx + 1, cy));
|
|
chunkLink(ED_BR, ED_TL, c, canvasGetChunk(w, cx + 1, cy + 1));
|
|
chunkLink(ED_B, ED_T, c, canvasGetChunk(w, cx, cy + 1));
|
|
chunkLink(ED_BL, ED_TR, c, canvasGetChunk(w, cx - 1, cy + 1));
|
|
chunkLink(ED_L, ED_R, c, canvasGetChunk(w, cx - 1, cy));
|
|
chunkLink(ED_TL, ED_BR, c, canvasGetChunk(w, cx - 1, cy - 1));
|
|
|
|
return c;
|
|
}
|
|
|
|
Canvas *createCanvas()
|
|
{
|
|
Canvas *world = malloc(sizeof(Canvas));
|
|
world->hash_table_size = 1000;
|
|
world->hash_table = malloc(sizeof(Chunk*) * world->hash_table_size);
|
|
for (int i = 0; i < world->hash_table_size; ++i)
|
|
world->hash_table[i] = NULL;
|
|
|
|
world->center = canvasCreateChunk(world, 0, 0);
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
world->rule_birth[i] = false;
|
|
world->rule_survive[i] = false;
|
|
}
|
|
// Ruleset will be set by calling canvasParseRuleset().
|
|
|
|
return world;
|
|
}
|
|
|
|
typedef enum {
|
|
EPS_None = 0,
|
|
EPS_B,
|
|
EPS_S
|
|
} EParseState;
|
|
|
|
bool canvasParseRuleset(Canvas *world, const char *ruleset)
|
|
{
|
|
fprintf(stderr, "Ruleset: '%s'.\n", ruleset);
|
|
for (int i = 0; i < 8; ++i) {
|
|
world->rule_birth[i] = false;
|
|
world->rule_survive[i] = false;
|
|
}
|
|
|
|
EParseState state = EPS_None;
|
|
for (int i = 0; i < strlen(ruleset); ++i) {
|
|
char ch = ruleset[i];
|
|
if (ch == 'B')
|
|
state = EPS_B;
|
|
else if (ch == 'S')
|
|
state = EPS_S;
|
|
else if (isdigit(ch)) {
|
|
if (state == EPS_B)
|
|
world->rule_birth[ch - '0'] = true;
|
|
else if (state == EPS_S)
|
|
world->rule_survive[ch - '0'] = true;
|
|
else {
|
|
fprintf(stderr, "Ruleset parse error: expecting B or S first, "
|
|
"before any numbers!\n");
|
|
return false;
|
|
}
|
|
} else
|
|
state = EPS_None;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void canvasSetCell(Canvas *w, int x, int y, int cell)
|
|
{
|
|
int cx = floor((float)x / (float)CHUNK_SIZE);
|
|
int cy = floor((float)y / (float)CHUNK_SIZE);
|
|
|
|
Chunk *c = canvasGetChunk(w, cx, cy);
|
|
if (!c)
|
|
c = canvasCreateChunk(w, cx, cy);
|
|
|
|
int inner_x = x % CHUNK_SIZE;
|
|
int inner_y = y % CHUNK_SIZE;
|
|
if (inner_x < 0)
|
|
inner_x = inner_x + CHUNK_SIZE;
|
|
if (inner_y < 0)
|
|
inner_y = inner_y + CHUNK_SIZE;
|
|
|
|
bool b4 = c->current[inner_x + inner_y * CHUNK_SIZE];
|
|
if (cell == 2)
|
|
cell = !b4;
|
|
|
|
if (cell == 1 && !b4)
|
|
c->count++;
|
|
else if (cell == 0 && b4)
|
|
c->count--;
|
|
|
|
c->current[inner_x + inner_y * CHUNK_SIZE] = cell;
|
|
}
|
|
|
|
void chunkCollectNeighbours(Chunk *c, int neighbours[CHUNK_SIZE][CHUNK_SIZE])
|
|
{
|
|
for (int inner_y = 0; inner_y < CHUNK_SIZE; ++inner_y)
|
|
for (int inner_x = 0; inner_x < CHUNK_SIZE; ++inner_x) {
|
|
neighbours[inner_y][inner_x] = 0;
|
|
}
|
|
|
|
for (int inner_y = 0; inner_y < CHUNK_SIZE; ++inner_y)
|
|
for (int inner_x = 0; inner_x < CHUNK_SIZE; ++inner_x) {
|
|
if (c->current[inner_x + inner_y * CHUNK_SIZE]) {
|
|
bool has_above = (inner_y > 0);
|
|
bool has_left = (inner_x > 0);
|
|
bool has_below = (inner_y < CHUNK_SIZE - 1);
|
|
bool has_right = (inner_x < CHUNK_SIZE - 1);
|
|
|
|
// Left column
|
|
if (has_left) {
|
|
if (has_above)
|
|
neighbours[inner_y - 1][inner_x - 1] += 1;
|
|
|
|
neighbours[inner_y][inner_x - 1] += 1;
|
|
|
|
if (has_below)
|
|
neighbours[inner_y + 1][inner_x - 1] += 1;
|
|
}
|
|
|
|
// Middle column
|
|
{
|
|
if (has_above)
|
|
neighbours[inner_y - 1][inner_x] += 1;
|
|
|
|
if (has_below)
|
|
neighbours[inner_y + 1][inner_x] += 1;
|
|
}
|
|
|
|
// Right column
|
|
if (has_right) {
|
|
if (has_above)
|
|
neighbours[inner_y - 1][inner_x + 1] += 1;
|
|
|
|
neighbours[inner_y][inner_x + 1] += 1;
|
|
|
|
if (has_below)
|
|
neighbours[inner_y + 1][inner_x + 1] += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Horizontal Scans
|
|
Chunk *other = NULL;
|
|
other = c->around[ED_T];
|
|
if (other) {
|
|
for (int i = 0; i < CHUNK_SIZE; ++i) {
|
|
int x = i;
|
|
int y = CHUNK_SIZE - 1;
|
|
if (other->current[x + y * CHUNK_SIZE]) {
|
|
if (i > 0)
|
|
neighbours[0][i - 1] += 1;
|
|
|
|
neighbours[0][i] += 1;
|
|
|
|
if (i < CHUNK_SIZE - 1)
|
|
neighbours[0][i + 1] += 1;
|
|
}
|
|
}
|
|
}
|
|
other = c->around[ED_B];
|
|
if (other) {
|
|
for (int i = 0; i < CHUNK_SIZE; ++i) {
|
|
if (other->current[i]) {
|
|
|
|
if (i > 0)
|
|
neighbours[CHUNK_SIZE - 1][i - 1] += 1;
|
|
|
|
neighbours[CHUNK_SIZE - 1][i] += 1;
|
|
|
|
if (i < CHUNK_SIZE - 1)
|
|
neighbours[CHUNK_SIZE - 1][i + 1] += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Vertical Scans
|
|
other = c->around[ED_R];
|
|
if (other) {
|
|
for (int i = 0; i < CHUNK_SIZE; ++i) {
|
|
if (other->current[i * CHUNK_SIZE]) {
|
|
if (i > 0)
|
|
neighbours[i - 1][CHUNK_SIZE - 1] += 1;
|
|
|
|
neighbours[i][CHUNK_SIZE - 1] += 1;
|
|
|
|
if (i < CHUNK_SIZE - 1)
|
|
neighbours[i + 1][CHUNK_SIZE - 1] += 1;
|
|
}
|
|
}
|
|
}
|
|
other = c->around[ED_L];
|
|
if (other) {
|
|
for (int i = 0; i < CHUNK_SIZE; ++i) {
|
|
if (other->current[CHUNK_SIZE - 1 + i * CHUNK_SIZE]) {
|
|
if (i > 0)
|
|
neighbours[i - 1][0] += 1;
|
|
|
|
neighbours[i][0] += 1;
|
|
|
|
if (i < CHUNK_SIZE - 1)
|
|
neighbours[i + 1][0] += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Diagonals
|
|
other = c->around[ED_TL];
|
|
if (other && other->current[CHUNK_SIZE - 1 + (CHUNK_SIZE - 1) * CHUNK_SIZE])
|
|
neighbours[0][0] += 1;
|
|
|
|
other = c->around[ED_BL];
|
|
if (other && other->current[CHUNK_SIZE - 1])
|
|
neighbours[CHUNK_SIZE - 1][0] += 1;
|
|
|
|
other = c->around[ED_TR];
|
|
if (other && other->current[(CHUNK_SIZE - 1) * CHUNK_SIZE])
|
|
neighbours[0][CHUNK_SIZE - 1] += 1;
|
|
|
|
other = c->around[ED_BR];
|
|
if (other && other->current[0])
|
|
neighbours[CHUNK_SIZE - 1][CHUNK_SIZE - 1] += 1;
|
|
}
|
|
|
|
void chunkStep(Canvas *world, Chunk *c)
|
|
{
|
|
// As an optimisation, collect neighbours by only looping through cells once
|
|
//fprintf(stderr, " - Collecting neighbours.\n");
|
|
int neighbours[CHUNK_SIZE][CHUNK_SIZE];
|
|
chunkCollectNeighbours(c, neighbours);
|
|
|
|
// Print neighbours
|
|
/*fprintf(stderr, " - Calculating neighbours:\n");
|
|
for (int inner_y = 0; inner_y < CHUNK_SIZE; ++inner_y) {
|
|
fprintf(stderr, " ");
|
|
for (int inner_x = 0; inner_x < CHUNK_SIZE; ++inner_x) {
|
|
fprintf(stderr, "%d ", neighbours[inner_y][inner_x]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}*/
|
|
|
|
// Now loop through all cells and update their state
|
|
for (int inner_y = 0; inner_y < CHUNK_SIZE; ++inner_y)
|
|
for (int inner_x = 0; inner_x < CHUNK_SIZE; ++inner_x) {
|
|
bool alive = c->current[inner_x + inner_y * CHUNK_SIZE];
|
|
int cneigh = neighbours[inner_y][inner_x];
|
|
if (alive && !world->rule_survive[cneigh]) {
|
|
c->next[inner_x + inner_y * CHUNK_SIZE] = false;
|
|
c->count--;
|
|
} else if (!alive && world->rule_birth[cneigh]) {
|
|
c->next[inner_x + inner_y * CHUNK_SIZE] = true;
|
|
c->count++;
|
|
} else {
|
|
c->next[inner_x + inner_y * CHUNK_SIZE] = alive;
|
|
}
|
|
}
|
|
}
|
|
|
|
void canvasStep(Canvas *w)
|
|
{
|
|
clock_t start = clock();
|
|
|
|
for (int i = 0; i < w->hash_table_size; ++i) {
|
|
Chunk *c = w->hash_table[i];
|
|
while (c) {
|
|
chunkStep(w, c);
|
|
c = c->down;
|
|
}
|
|
}
|
|
|
|
int count = 0;
|
|
for (int i = 0; i < w->hash_table_size; ++i) {
|
|
Chunk *c = w->hash_table[i];
|
|
while (c) {
|
|
count++;
|
|
bool *tmp = c->current;
|
|
c->current = c->next;
|
|
c->next = tmp;
|
|
c = c->down;
|
|
}
|
|
}
|
|
|
|
|
|
clock_t end = clock();
|
|
|
|
fprintf(stderr, "Stepped %d cells (%d chunks). Took %d ms.\n",
|
|
count * CHUNK_SIZE * CHUNK_SIZE, count,
|
|
(int)floor(1000.0f * ((double) (end - start)) / CLOCKS_PER_SEC));
|
|
}
|
|
|
|
void canvasGrow(Canvas *world)
|
|
{
|
|
for (int i = 0; i < world->hash_table_size; ++i) {
|
|
Chunk *c = world->hash_table[i];
|
|
while (c) {
|
|
if (c->count > 0) {
|
|
if (!c->around[ED_T])
|
|
canvasCreateChunk(world, c->x, c->y - 1);
|
|
if (!c->around[ED_B])
|
|
canvasCreateChunk(world, c->x, c->y + 1);
|
|
|
|
if (!c->around[ED_L])
|
|
canvasCreateChunk(world, c->x - 1, c->y);
|
|
if (!c->around[ED_R])
|
|
canvasCreateChunk(world, c->x + 1, c->y);
|
|
|
|
if (!c->around[ED_TL])
|
|
canvasCreateChunk(world, c->x - 1, c->y - 1);
|
|
if (!c->around[ED_TR])
|
|
canvasCreateChunk(world, c->x + 1, c->y - 1);
|
|
|
|
if (!c->around[ED_BL])
|
|
canvasCreateChunk(world, c->x - 1, c->y + 1);
|
|
if (!c->around[ED_BR])
|
|
canvasCreateChunk(world, c->x + 1, c->y + 1);
|
|
}
|
|
|
|
c = c->down;
|
|
}
|
|
}
|
|
}
|
|
|
|
void canvasRandomise(Canvas *world)
|
|
{
|
|
for (int i = 0; i < world->hash_table_size; ++i) {
|
|
Chunk *c = world->hash_table[i];
|
|
while (c) {
|
|
c->count = 0;
|
|
for (int y = 0; y < CHUNK_SIZE; ++y)
|
|
for (int x = 0; x < CHUNK_SIZE; ++x) {
|
|
if (rand() % 2) {
|
|
c->count++;
|
|
c->current[x + y * CHUNK_SIZE] = true;
|
|
} else{
|
|
c->current[x + y * CHUNK_SIZE] = false;
|
|
}
|
|
}
|
|
c = c->down;
|
|
}
|
|
}
|
|
}
|