Last active
January 5, 2026 21:29
-
-
Save llbit/c3d9604383696aefcfdd84f7487e7783 to your computer and use it in GitHub Desktop.
Reverse Game of Life (C implementation)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <time.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #define b_north ((1<<0) + (1<<1) + (1<<2)) | |
| #define b_south ((1<<6) + (1<<7) + (1<<8)) | |
| #define b_ew ((1<<3) + (1<<4) + (1<<5)) | |
| #define b_west ((1<<0) + (1<<3) + (1<<6)) | |
| #define b_east ((1<<2) + (1<<5) + (1<<8)) | |
| #define b_ns ((1<<1) + (1<<4) + (1<<7)) | |
| #define b_n (b_north | b_ew) | |
| #define b_s (b_south | b_ew) | |
| #define b_e (b_east | b_ns) | |
| #define b_w (b_west | b_ns) | |
| typedef struct Line Line; | |
| struct Line { | |
| char* text; | |
| Line* next; | |
| }; | |
| typedef struct { | |
| int n; | |
| unsigned* data; | |
| } Array; | |
| static int count_ones(uint64_t x) | |
| { | |
| int s = 0; | |
| while (x != 0) { | |
| s += x&1; | |
| x >>= 1; | |
| } | |
| return s; | |
| } | |
| void disp(unsigned* board, int H, int W) | |
| { | |
| for (int r = 0; r < H; ++r) { | |
| for (int c = 0; c < W; ++c) { | |
| printf("%c", board[r * W + c] ? '#' : ' '); | |
| } | |
| printf("\n"); | |
| } | |
| } | |
| int neighbors[][2] = { | |
| {-1, -1}, {-1, 0}, {-1, 1}, | |
| {0, -1 }, {0, 1 }, | |
| {1, -1 }, {1, 0 }, {1, 1 }, | |
| }; | |
| int count(unsigned* B, int H, int W, int r, int c) | |
| { | |
| int n = 0; | |
| for (int i = 0; i < 8; ++i) { | |
| n += B[(r + neighbors[i][0])*W + c + neighbors[i][1]]; | |
| } | |
| return n; | |
| } | |
| void sim(unsigned* start, unsigned* next, int H, int W) | |
| { | |
| for (int r = 1; r < H-1; ++r) { | |
| for (int c = 1; c < W-1; ++c) { | |
| // count neighbors | |
| int n = count(start, H, W, r, c); | |
| if (start[r*W + c]) { | |
| // 2-3 neighbors stay alive | |
| next[r * W + c] = (n == 2 || n == 3) ? 1 : 0; | |
| } else { | |
| // 3 neighbors get born | |
| next[r * W + c] = n == 3 ? 1 : 0; | |
| } | |
| } | |
| } | |
| } | |
| static void enum_states(Array* live, Array* dead) | |
| { | |
| int scratch[5*5]; | |
| int out[5*5]; | |
| memset(scratch, 0, sizeof(int) * 5 * 5); | |
| for (int p = 0; p < (1<<9); ++p) { | |
| for (int r = 0; r < 3; ++r) { | |
| for (int c = 0; c < 3; ++c) { | |
| scratch[(r+1)*5 + c + 1] = (p >> (r*3 + c)) & 1; | |
| } | |
| } | |
| sim(scratch, out, 5, 5); | |
| if (out[2*5 + 2]) { | |
| live->data[live->n++] = p; | |
| } else { | |
| dead->data[dead->n++] = p; | |
| } | |
| } | |
| } | |
| static void search(Array* A, int H, int W) | |
| { | |
| int n = 0; | |
| int L[H*W][2]; | |
| for (int c = 1; c < W-1; ++c) { | |
| for (int r = 1; r < H-1; ++r) { | |
| L[n][0] = r; | |
| L[n++][1] = c; | |
| } | |
| } | |
| int Q[n]; | |
| int S[n]; | |
| S[0] = 0; | |
| int i = 0; | |
| while (true) { | |
| int r = L[i][0]; | |
| int c = L[i][1]; | |
| int mask = 0; | |
| int value = 0; | |
| if (r > 1) { | |
| value = Q[i-1] >> 3; | |
| mask = b_north | b_ew; | |
| } | |
| if (c > 1) { | |
| value |= (Q[i-H+2] >> 1) & (b_west | b_ns); | |
| mask |= b_west | b_ns; | |
| } | |
| value &= mask; | |
| Array aa = A[r*W + c]; | |
| while (S[i] < aa.n) { | |
| int p = aa.data[S[i]]; | |
| if ((p & mask) == value) { | |
| Q[i] = p; | |
| break; | |
| } | |
| S[i] += 1; | |
| } | |
| if (S[i] < aa.n) { | |
| i += 1; | |
| if (i == n) { | |
| break; | |
| } | |
| S[i] = 0; | |
| } else { | |
| S[i] = 0; | |
| i -= 1; | |
| if (i < 0) { | |
| fprintf(stderr, "No solution found.\n"); | |
| return; | |
| } | |
| S[i] += 1; | |
| } | |
| } | |
| int board[H*W]; | |
| memset(board, 0, sizeof(int)*H*W); | |
| for (i = 0; i < n; ++i) { | |
| int r = L[i][0]; | |
| int c = L[i][1]; | |
| board[r*W + c] = (Q[i] >> 4) & 1; | |
| } | |
| disp(board, H, W); | |
| } | |
| static int cmp_live(const void* pa, const void* pb) | |
| { | |
| int a = count_ones(*(unsigned*) pa); | |
| int b = count_ones(*(unsigned*) pb); | |
| if ((a&(1<<4)) && !(b&(1<<4))) { | |
| return -1; | |
| } | |
| if (!(a&(1<<4)) && (b&(1<<4))) { | |
| return 1; | |
| } | |
| if (a < b) return -1; | |
| if (a > b) return 1; | |
| return 0; | |
| } | |
| static int cmp_dead(const void* pa, const void* pb) | |
| { | |
| int a = count_ones(*(unsigned*) pa); | |
| int b = count_ones(*(unsigned*) pb); | |
| if ((a&(1<<4)) && !(b&(1<<4))) { | |
| return 1; | |
| } | |
| if (!(a&(1<<4)) && (b&(1<<4))) { | |
| return -1; | |
| } | |
| if (a < b) return -1; | |
| if (a > b) return 1; | |
| return 0; | |
| } | |
| static void reverse(unsigned* ref, int H, int W) | |
| { | |
| Array live = { | |
| .n = 0, | |
| .data = alloca(sizeof(unsigned)<<9), | |
| }; | |
| Array dead = { | |
| .n = 0, | |
| .data = alloca(sizeof(unsigned)<<9), | |
| }; | |
| enum_states(&live, &dead); | |
| qsort(live.data, live.n, sizeof(int), cmp_live); | |
| qsort(dead.data, dead.n, sizeof(int), cmp_dead); | |
| Array A[H*W]; | |
| for (int i = 0; i < H*W; ++i) { | |
| Array* set; | |
| if (ref[i]) { | |
| set = &live; | |
| } else { | |
| set = &dead; | |
| } | |
| A[i].n = set->n; | |
| A[i].data = malloc(sizeof(unsigned) * set->n); | |
| memcpy(A[i].data, set->data, sizeof(unsigned) * set->n); | |
| } | |
| for (int r = 0; r < H; ++r) { | |
| Array* pA = A + (r*W); | |
| int i = 0; | |
| while (i < pA->n) { | |
| if (pA->data[i] & b_w) { | |
| pA->n -= 1; | |
| pA->data[i] = pA->data[pA->n]; | |
| } else { | |
| i += 1; | |
| } | |
| } | |
| pA = A + (r*W + W-1); | |
| i = 0; | |
| while (i < pA->n) { | |
| if (pA->data[i] & b_e) { | |
| pA->n -= 1; | |
| pA->data[i] = pA->data[pA->n]; | |
| } else { | |
| i += 1; | |
| } | |
| } | |
| } | |
| for (int c = 0; c < W; ++c) { | |
| Array* pA = A + c; | |
| int i = 0; | |
| while (i < pA->n) { | |
| if (pA->data[i] & b_n) { | |
| pA->n -= 1; | |
| pA->data[i] = pA->data[pA->n]; | |
| } else { | |
| i += 1; | |
| } | |
| } | |
| pA = A + ((H-1)*W + c); | |
| i = 0; | |
| while (i < pA->n) { | |
| if (pA->data[i] & b_s) { | |
| pA->n -= 1; | |
| pA->data[i] = pA->data[pA->n]; | |
| } else { | |
| i += 1; | |
| } | |
| } | |
| } | |
| for (int r = 0; r < H; ++r) { | |
| for (int c = 0; c < W; ++c) { | |
| char buf[30]; | |
| snprintf(buf, sizeof(buf), "%d", A[r*W+c].n); | |
| fprintf(stderr, "%3s ", buf); | |
| } | |
| fprintf(stderr, "\n"); | |
| } | |
| search(A, H, W); | |
| for (int i = 0; i < H*W; ++i) { | |
| free(A[i].data); | |
| } | |
| } | |
| int main(int argc, char** argv) | |
| { | |
| int H = 0; | |
| int W = 0; | |
| char temp[1<<10]; | |
| size_t bufsize = sizeof(temp); | |
| Line* head = NULL; | |
| Line* tail = NULL; | |
| while (1) { | |
| char* buf = NULL; | |
| size_t t; | |
| ssize_t r = getline(&buf, &t, stdin); | |
| if (r < 0) { | |
| free(buf); | |
| break; | |
| } | |
| Line* line = malloc(sizeof(Line)); | |
| *line = (Line) { | |
| .text = buf, | |
| .next = NULL, | |
| }; | |
| if (!head) { | |
| head = tail = line; | |
| } else { | |
| tail->next = line; | |
| tail = line; | |
| } | |
| H += 1; | |
| } | |
| tail = head; | |
| while (tail) { | |
| char* p = tail->text; | |
| tail = tail->next; | |
| int linew = 0; | |
| while (*p) { | |
| if (*p != '\n' && *p != '\r') { | |
| linew += 1; | |
| } | |
| p++; | |
| } | |
| if (linew > W) W = linew; | |
| } | |
| H += 2; | |
| W += 2; | |
| size_t boardsz = H * W * sizeof(unsigned); | |
| unsigned* start = calloc(H * W, sizeof(unsigned)); | |
| tail = head; | |
| int r = 0; | |
| int pop = 0; | |
| while (tail) { | |
| char* p = tail->text; | |
| tail = tail->next; | |
| int c = 0; | |
| while (*p) { | |
| if (*p == '#' || *p == 'O') { | |
| start[(r + 1)*W + c + 1] = 1; | |
| pop += 1; | |
| c += 1; | |
| } else if (*p != '\n' && *p != '\r') { | |
| c += 1; | |
| } | |
| p++; | |
| } | |
| r += 1; | |
| } | |
| if (argc > 1 && !strcmp(argv[1], "sim")) { | |
| struct timespec ts = { | |
| .tv_sec = 0, | |
| .tv_nsec = 300000000, | |
| }; | |
| unsigned* next = malloc(boardsz); | |
| memcpy(next, start, boardsz); | |
| int step = 0; | |
| printf("\033[2J"); // Clear screen. | |
| while (1) { | |
| printf("\033[Hstep %d\n", step++); | |
| disp(start, H, W); | |
| fflush(stdout); | |
| nanosleep(&ts, NULL); | |
| sim(start, next, H, W); | |
| unsigned* t = start; | |
| start = next; | |
| next = t; | |
| } | |
| } else { | |
| fprintf(stderr, "Population: %d\n", pop); | |
| reverse(start, H, W); | |
| } | |
| free(start); | |
| while (head) { | |
| tail = head; | |
| tail = head->next; | |
| free(head->text); | |
| free(head); | |
| head = tail; | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment