/* Integer-only, C89 ecosystem for AIX 4.3.3. No external libraries. */ #include #include #include #include #include #include #define W 96 #define H 64 #define N (W * H) #define SCALE 4 #define MAX_ENERGY 600 typedef struct { int alive, pos, energy, age, generation; unsigned char speed, appetite, thrift, sense; } Organism; static Organism beings[N]; static int occupied[N], food[N], fertility[N]; static unsigned long rng; static long births, deaths, mutations; static int population; static unsigned long random32(void) { rng ^= (rng << 13) & 0xffffffffUL; rng ^= rng >> 17; rng ^= (rng << 5) & 0xffffffffUL; rng &= 0xffffffffUL; return rng; } static int pick(int n) { return (int)(random32() % (unsigned long)n); } static int neighbor(int p, int d) { int x, y; x = p % W; y = p / W; if (d == 0) x = (x + 1) % W; if (d == 1) x = (x + W - 1) % W; if (d == 2) y = (y + 1) % H; if (d == 3) y = (y + H - 1) % H; return y * W + x; } static unsigned char mutate(unsigned char value) { int v; if (pick(100) >= 12) return value; v = (int)value + pick(33) - 16; if (v < 0) v = 0; if (v > 255) v = 255; if (v != (int)value) mutations++; return (unsigned char)v; } static void initialize(void) { int i, x, y, dx, dy, f; for (i = 0; i < N; i++) { occupied[i] = -1; x = i % W; y = i / W; dx = x - 24; dy = y - 20; f = (dx * dx + dy * dy < 225) ? 4 : 0; dx = x - 70; dy = y - 44; if (dx * dx + dy * dy < 256) f += 4; fertility[i] = f + 1; food[i] = 80 + pick(176); } for (i = 0; i < 160; i++) { Organism *o; o = &beings[i]; do { o->pos = pick(N); } while (occupied[o->pos] != -1); o->alive = 1; o->energy = 100 + pick(60); o->speed = (unsigned char)pick(256); o->appetite = (unsigned char)pick(256); o->thrift = (unsigned char)pick(256); o->sense = (unsigned char)pick(256); occupied[o->pos] = i; } population = 160; } static void step(int tick) { int i, j, p, q, d, start, best, score, amount, cost, season; unsigned char active[N]; Organism *o, *child; /* Snapshot membership: newborns cannot take a turn until the next tick. */ for (i = 0; i < N; i++) active[i] = (unsigned char)beings[i].alive; /* A triangular seasonal cycle; opposite halves have opposite seasons. */ season = tick % 800; if (season > 400) season = 800 - season; for (p = 0; p < N; p++) { amount = (p % W < W / 2) ? season : 400 - season; amount = 1 + fertility[p] * amount / 200; food[p] += amount; if (food[p] > 255) food[p] = 255; } start = pick(N); for (j = 0; j < N; j++) { i = (start + j) % N; o = &beings[i]; if (!active[i] || !o->alive) continue; o->age++; cost = 1 + o->appetite / 96 + o->sense / 128; if (pick(256) < o->speed) { d = pick(4); q = neighbor(o->pos, d); if (pick(256) < o->sense) { best = -1; for (p = 0; p < 4; p++) { int candidate; candidate = neighbor(o->pos, (d + p) % 4); score = occupied[candidate] == -1 ? food[candidate] : -1; if (score > best) { best = score; q = candidate; } } } if (occupied[q] == -1) { occupied[o->pos] = -1; o->pos = q; occupied[q] = i; cost += 2 + o->speed / 64; } } amount = 6 + o->appetite / 8; if (amount > food[o->pos]) amount = food[o->pos]; food[o->pos] -= amount; o->energy += amount - cost; if (o->energy > MAX_ENERGY) o->energy = MAX_ENERGY; if (o->energy <= 0 || o->age > 600 + o->thrift * 4) { occupied[o->pos] = -1; o->alive = 0; population--; deaths++; continue; } if (o->energy < 180 + o->thrift / 2) continue; d = pick(4); q = -1; for (p = 0; p < 4; p++) { int candidate; candidate = neighbor(o->pos, (d + p) % 4); if (occupied[candidate] == -1) { q = candidate; break; } } if (q == -1 || population >= N) continue; /* Do not recycle an original participant's slot during this tick. */ for (p = 0; p < N; p++) if (!beings[p].alive && !active[p]) break; if (p == N) continue; child = &beings[p]; *child = *o; child->pos = q; child->age = 0; child->generation++; child->energy = o->energy * 2 / 5; o->energy -= child->energy + 12; child->speed = mutate(o->speed); child->appetite = mutate(o->appetite); child->thrift = mutate(o->thrift); child->sense = mutate(o->sense); occupied[q] = p; population++; births++; } } static int validate(void) { int i, count; count = 0; for (i = 0; i < N; i++) { if (food[i] < 0 || food[i] > 255) return 0; if (occupied[i] >= 0 && (occupied[i] >= N || !beings[occupied[i]].alive || beings[occupied[i]].pos != i)) return 0; if (beings[i].alive) { count++; if (beings[i].pos < 0 || beings[i].pos >= N || occupied[beings[i].pos] != i || beings[i].energy <= 0 || beings[i].energy > MAX_ENERGY) return 0; } } return count == population; } static int snapshot(const char *dir, int tick) { char path[1024]; FILE *f; unsigned char row[W * SCALE * 3]; int x, y, sx, sy, p, index, a; Organism *o; sprintf(path, "%s/frame-%06d.ppm", dir, tick); f = fopen(path, "wb"); if (!f) { perror(path); return 0; } fprintf(f, "P6\n%d %d\n255\n", W * SCALE, H * SCALE); for (y = 0; y < H; y++) for (sy = 0; sy < SCALE; sy++) { for (x = 0; x < W; x++) for (sx = 0; sx < SCALE; sx++) { p = y * W + x; index = (x * SCALE + sx) * 3; row[index] = (unsigned char)(8 + food[p] / 14); row[index + 1] = (unsigned char)(15 + food[p] / 3); row[index + 2] = (unsigned char)(20 + fertility[p] * 3); a = occupied[p]; if (a >= 0 && sx > 0 && sy > 0) { o = &beings[a]; row[index] = (unsigned char)(70 + o->speed * 185 / 255); row[index + 1] = (unsigned char)(65 + o->sense * 160 / 255); row[index + 2] = (unsigned char)(75 + o->thrift * 180 / 255); } } if (fwrite(row, 1, sizeof(row), f) != sizeof(row)) { fclose(f); return 0; } } return fclose(f) == 0; } static void record(FILE *csv, FILE *journal, int tick) { long energy, resources, speed, appetite, thrift, sense; int i, generation; energy = resources = speed = appetite = thrift = sense = 0; generation = 0; for (i = 0; i < N; i++) { resources += food[i]; if (!beings[i].alive) continue; energy += beings[i].energy; speed += beings[i].speed; appetite += beings[i].appetite; thrift += beings[i].thrift; sense += beings[i].sense; if (beings[i].generation > generation) generation = beings[i].generation; } fprintf(csv, "%d,%d,%ld,%ld,%ld,%ld,%d,%ld,%ld,%ld,%ld,%ld\n", tick, population, births, deaths, mutations, resources, generation, energy, speed, appetite, thrift, sense); fprintf(journal, "Tick %d: %d alive, %ld births, %ld deaths, %ld trait mutations; " "oldest surviving lineage depth %d.\n", tick, population, births, deaths, mutations, generation); } static int number(const char *s, unsigned long *out) { char *end; if (!*s || *s == '-') return 0; errno = 0; *out = strtoul(s, &end, 10); return !errno && !*end; } int main(int argc, char **argv) { const char *dir; unsigned long seed, n; int ticks, interval, i, tick; char path[1024]; FILE *csv, *journal; seed = 604; ticks = 4000; interval = 40; dir = "run-604"; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--help")) { puts("Usage: ecosystem [--seed 1..4294967295] [--ticks 1..1000000]" " [--snapshot-every 1..1000000] [--output NEW_DIRECTORY]"); return 0; } if (i + 1 >= argc) { fputs("Missing option value\n", stderr); return 1; } if (!strcmp(argv[i], "--output")) { dir = argv[++i]; continue; } if (!number(argv[i + 1], &n) || n == 0) { fputs("Expected a positive integer\n", stderr); return 1; } if (!strcmp(argv[i], "--seed") && n <= 0xffffffffUL) seed = n; else if (!strcmp(argv[i], "--ticks") && n <= 1000000UL) ticks = (int)n; else if (!strcmp(argv[i], "--snapshot-every") && n <= 1000000UL) interval = (int)n; else { fputs("Unknown option or out-of-range value\n", stderr); return 1; } i++; } if (strlen(dir) > 900 || !*dir) { fputs("Invalid output path\n", stderr); return 1; } if (mkdir(dir, 0755) != 0) { perror(dir); return 1; } rng = seed; initialize(); sprintf(path, "%s/population.csv", dir); csv = fopen(path, "w"); sprintf(path, "%s/journal.txt", dir); journal = fopen(path, "w"); if (!csv || !journal) { perror("Opening logs"); return 1; } fprintf(journal, "Obsidian ecosystem v1. Seed %lu; %dx%d toroidal world; " "%d ticks; snapshot interval %d.\n", seed, W, H, ticks, interval); fprintf(csv, "tick,population,births,deaths,mutations,food,max_generation," "energy_sum,speed_sum,appetite_sum,thrift_sum,sense_sum\n"); for (tick = 0; tick <= ticks; tick++) { if (tick) step(tick); if (!validate()) { fprintf(stderr, "Invariant failure at %d\n", tick); return 2; } if (tick % interval == 0 || tick == ticks) { record(csv, journal, tick); if (!snapshot(dir, tick)) { fputs("Snapshot write failed\n", stderr); return 1; } } } if (fclose(csv) != 0 || fclose(journal) != 0) return 1; printf("Seed %lu: %d ticks, %d survivors, %ld births, %ld deaths, %ld mutations\n", seed, ticks, population, births, deaths, mutations); return 0; }