Initial commit

master
rubenwardy 2019-08-24 01:12:23 +01:00
commit 763537bb61
17 changed files with 1496 additions and 0 deletions

104
.gitignore vendored Normal file
View File

@ -0,0 +1,104 @@
colony
check
life
# Created by https://www.gitignore.io/api/c,linux,windows
# Edit at https://www.gitignore.io/?templates=c,linux,windows
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/c,linux,windows

19
Makefile Normal file
View File

@ -0,0 +1,19 @@
default: c
c: check.c colony.c life.c
gcc -std=c99 check.c -o check
gcc -std=c99 colony.c -o colony
gcc $(X) -std=c99 -O3 -pedantic -Wall life.c canvas.c player.c -o life -lm -lSDL2
test:
./colony 0 | ./life | ./check 0
./colony 1 | ./life | ./check 1
./colony 2 | ./life | ./check 2
./colony 3 | ./life | ./check 3
./colony 4 | ./life | ./check 4
./colony 5 | ./life | ./check 5
./colony 6 | ./life | ./check 6
clean:
rm -f *.hi *.o
rm check colony life

126
README.md Normal file
View File

@ -0,0 +1,126 @@
# Infinite and Generic Cellular Automaton Simulator
Defaults to Game of Life. **See screenshot folder**.
An infinite game of life universe, with a graphical player.
License: MIT
![](screenshots/player_annotated.png)
## Overview
* Chunk based infinite universe. Automatically expands.
* Interactive player and universe editor using SDL.
* Can set the Born/Survive ruleset code (from command line).
* Can choose the number to steps to take before opening the player
or outputting to stdout (from command line).
* Has tests.
## Contents
* Usage Guide
* Dependencies.
* Compilation and Tests.
* Command line tool.
* Using the player.
* Development
* Extensions.
* Inspiration / Sources.
* Technical Details
* The world.
* Stepping.
* Limitations / Possible improvements.
# Usage Guide
## Dependencies
Requires SDL2.
## Compilation and Tests
# Compilation.
make c
# Run test.
./test.sh # does make test, but uses program return codes.
./life --tests # runs life's tests.
## Command line tool
All the following commands either have or implicitly use --stdout
# Accept a colony from stdin and step it.
./life
# Accept a colony from stdin and step it n times.
./life n
# eg:
./live 100
# Accept a colony from stdin and use the B1357/S1357 ruleset
./life B1357/S1357
# Run tests, then accept stdin, and step it, if tests successful.
./life --tests --stdout
# Step a colony from ./colony
./colony 6 | ./life
## Using the player
Space to toggle between pause and play.
S to step / advance one frame.
R to randomly set cells to dead or alive.
Left click to toggle a cell's state.
Drag the view using the middle or right mouse button.
All the following commands have --player
# Accept a colony from stdin and open the player immediately.
./life --player
# Accept a colony from stdin, step it n times, then open the player.
./life n --player
# Accept a colony from stdin, use the B1357/S1357 ruleset and open the player immediately.
./life B1357/S1357 --player
# Run tests, accept stdin and step it, if tests successful.
./life --tests --player
# Open the player.
./life --player --no-stdin
# Load and play a colony from ./colony
./colony 6 | ./life --player
# Technical Details
## The world
The world is split up into "Chunks". Each chunk has a pointer to the chunks
surrounding it. Chunks are also in a hash table to speed up searching for a chunk
at a position.
## Stepping
The step logic is double buffered. An int array is used in order to count the
neighbours, as each cell accessing the cells around it would be quite expensive.
Whilst updating a chunk the neighbour finding logic looks at surrounding chunks
for overhanging cells.
## Limitations / Possible Improvements
* A possible improvement would be to store the neighbours int array in the
chunk, and then only update it when a cell is set.
* A looping universe may be an interesting thing to experiment with.
* Store chunk contents as a 64 bit integer instead of a bool array. I was
originally planning this, but instead spent the time doing the player instead.
* A zooming function, a fill function, or a rectangle drag function would
be nice.
* Being able to change the ruleset at runtime in the editor would be nice -
however I rejected this as it would require some sort of SDL GUI library.
Ctrl+D would have disabled stdin by the point of the player running.

435
canvas.c Normal file
View File

@ -0,0 +1,435 @@
#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;
}
}
}

60
canvas.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef CANVAS_H_INCLUDED
#define CANVAS_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef enum {
ED_T = 0,
ED_TR,
ED_R,
ED_BR,
ED_B,
ED_BL,
ED_L,
ED_TL
} EDIR;
#define CHUNK_SIZE 8
struct SChunk
{
int count;
bool *current;
bool *next;
int x;
int y;
struct SChunk *down;
struct SChunk *around[8];
};
typedef struct SChunk Chunk;
struct SCanvas
{
Chunk *center;
Chunk **hash_table;
int hash_table_size;
bool rule_birth[8];
bool rule_survive[8];
};
typedef struct SCanvas Canvas;
Chunk *createChunk(int x, int y);
void chunkPrint(Chunk *c, int w, int h);
void chunkLink(EDIR dir1, EDIR dir2, Chunk *one, Chunk *two);
Chunk *canvasGetChunk(Canvas *w, int cx, int cy);
Chunk *canvasCreateChunk(Canvas *w, int cx, int cy);
Canvas *createCanvas();
bool canvasParseRuleset(Canvas *world, const char *ruleset);
void canvasSetCell(Canvas *w, int x, int y, int cell);
void chunkCollectNeighbours(Chunk *c, int neighbours[CHUNK_SIZE][CHUNK_SIZE]);
void canvasStep(Canvas *w);
void canvasGrow(Canvas *world);
void canvasRandomise(Canvas *world);
#endif

139
check.c Normal file
View File

@ -0,0 +1,139 @@
/* Test a colony which has been output from a game-of-life program. */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
// The number of rows or columns allowed for an example, plus one to allow for
// terminators.
enum { MAX=10 };
// A colony is a 2D array of characters. Each row is a null-terminated string,
// and the rows are terminated by an empty string.
typedef char colony[MAX][MAX];
// Define the example colonies, each one tick further on than the corresponding
// one in colony.c.
colony colonies[] = {
{
".....",
"..#..",
"..#..",
"..#..",
".....",
""
}, {
".....",
".....",
".###.",
".....",
".....",
""
}, {
"....",
".##.",
".##.",
"....",
""
}, {
"......",
"......",
".#.#..",
"..##..",
"..#...",
"......",
""
}, {
"......",
"......",
"...#..",
".#.#..",
"..##..",
"......",
""
}, {
"......",
"......",
"..#...",
"...##.",
"..##..",
"......",
""
}, {
"......",
"......",
"...#..",
"....#.",
"..###.",
"......",
""
}
};
// Find the number of colonies in the above list.
int ncolonies = sizeof(colonies) / sizeof(colony);
void fail() {
fprintf(stderr, "Use e.g.: ./check 3\n");
fprintf(stderr, "to check example 3 of a life colony\n");
exit(1);
}
// Read a line of text from the standard input into the given character array.
// Make sure that any pending text on the standard output has been printed
// first. Remove the newline (\r or \n or both).
void readline(int n, char line[n]) {
fflush(stdout);
fgets(line, n, stdin);
line[strcspn(line, "\r\n")] = '\0';
}
// Read a colony from the standard input into the given matrix, with the rows
// terminated by an empty string.
void readColony(colony col) {
for (int i=0; ! feof(stdin); i++) {
readline(MAX, col[i]);
if (feof(stdin)) strcpy(col[i], "");
}
}
// Print out a colony.
void print(colony col) {
for (int i=0; strlen(col[i]) != 0; i++) {
printf("%s\n", col[i]);
}
}
// Check a colony against the n'th example.
void check(colony actual, int n) {
bool ok = true, end = false;
for (int i=0; ! end; i++) {
if (strcmp(actual[i], colonies[n][i]) != 0) ok = false;
if (strlen(colonies[n][i]) == 0) end = true;
}
if (ok) {
printf("OK\n");
}
else {
printf("actual colony read in:\n");
print(actual);
printf("colony expected:\n");
print(colonies[n]);
}
}
// Get the colony number from the command line, then check to see if
// the input colony matches the expected one.
int main(int n, char *args[n]) {
if (n != 2) fail();
char *s = args[1];
int len = strlen(s);
if (len < 1) fail();
for (int i=0; i<len; i++) if (! isdigit(s[i])) fail();
int index = atoi(s);
if (index < 0 | index >= ncolonies) fail();
colony col;
readColony(col);
check(col, index);
}

98
colony.c Normal file
View File

@ -0,0 +1,98 @@
/* Generate a colony as test input to a game-of-life program. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// The number of rows or columns allowed for an example, plus one to allow for
// terminators.
enum { MAX=10 };
// A colony is a 2D array of characters. Each row is a null-terminated string,
// and the rows are terminated by an empty string.
typedef char colony[MAX][MAX];
// Define the example colonies.
colony colonies[] = {
{
".....",
".....",
".###.",
".....",
".....",
""
}, {
".....",
"..#..",
"..#..",
"..#..",
".....",
""
}, {
"....",
".##.",
".##.",
"....",
""
}, {
"......",
"..#...",
"...#..",
".###..",
"......",
"......",
""
}, {
"......",
"......",
".#.#..",
"..##..",
"..#...",
"......",
""
}, {
"......",
"......",
"...#..",
".#.#..",
"..##..",
"......",
""
}, {
"......",
"......",
"..#...",
"...##.",
"..##..",
"......",
""
}
};
// Find the number of colonies in the above list.
int ncolonies = sizeof(colonies) / sizeof(colony);
void fail() {
fprintf(stderr, "Use: ./colony i\n");
fprintf(stderr, "where i is a number from 0 to %d\n", ncolonies - 1);
exit(1);
}
// Print out the n'th colony.
void print(int n) {
for (int i=0; strlen(colonies[n][i]) != 0; i++) {
printf("%s\n", colonies[n][i]);
}
}
// Get the colony number from the command line, then call print.
int main(int n, char *args[n]) {
if (n != 2) fail();
char *s = args[1];
int len = strlen(s);
if (len < 1) fail();
for (int i=0; i<len; i++) if (! isdigit(s[i])) fail();
int index = atoi(s);
if (index < 0 | index >= ncolonies) fail();
print(index);
}

300
life.c Normal file
View File

@ -0,0 +1,300 @@
#include "canvas.h"
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "player.h"
#define MAX_STDIN_SIZE 1024
int readFromStdin(Canvas *world, int *w, int *h)
{
int y = 0;
char line[MAX_STDIN_SIZE];
fprintf(stderr, "Waiting for world input.\n Ctrl+D to stop. '.' is dead, '#' is alive\n\n");
while (!feof(stdin)) {
while (fgets(line, MAX_STDIN_SIZE, stdin) == NULL && !feof(stdin));
if (feof(stdin))
break;
// Process Input
for (int x = 0; x < MAX_STDIN_SIZE; x++) {
char ch = line[x];
if (ch == '\0')
break;
else if (ch == '#')
canvasSetCell(world, x, y, true);
else if (ch == '.')
canvasSetCell(world, x, y, false);
else if (ch == '\n') // row number is here in case of long lines in files
y++;
else {
fprintf(stderr, "Syntax error on line %d character %d ('%c')\n", x, y, ch);
return 1;
}
if (x > (*w))
(*w) = x;
}
}
(*h) = y;
return 0;
}
#define TEST(cond) if (!(cond)) { printf("Test failed: " #cond "\n"); return false;}
bool run_tests()
{
Canvas *world = createCanvas();
canvasSetCell(world, -1, -1, true);
canvasSetCell(world, -1, 0, true);
canvasSetCell(world, 4, -1, true);
canvasSetCell(world, 0, -1, true);
canvasSetCell(world, 0, 0, true);
Chunk *cursor = world->center;
int neighbours[CHUNK_SIZE][CHUNK_SIZE];
TEST(cursor);
TEST(cursor->x == 0);
TEST(cursor->y == 0);
TEST(cursor->around[ED_T]);
TEST(cursor->around[ED_T]->current[0 + (CHUNK_SIZE - 1) * CHUNK_SIZE]);
TEST(cursor->around[ED_L]);
TEST(cursor->around[ED_TL]);
TEST(!cursor->around[ED_TR]);
TEST(!cursor->around[ED_R]);
TEST(!cursor->around[ED_BR]);
TEST(!cursor->around[ED_B]);
TEST(!cursor->around[ED_BL]);
{
chunkCollectNeighbours(cursor, neighbours);
int expected[CHUNK_SIZE][CHUNK_SIZE] = {
{3, 2, 0, 1, 1, 1, 0, 0},
{2, 1, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0}
};
for (int x = 0; x < CHUNK_SIZE; ++x)
for (int y = 0; y < CHUNK_SIZE; ++y) {
if (neighbours[y][x] != expected[y][x])
fprintf(stderr, "Check (%d, %d), got %d expected %d\n",
x, y, neighbours[y][x], expected[y][x]);
TEST(neighbours[y][x] == expected[y][x]);
}
}
cursor = cursor->around[ED_T];
TEST(cursor);
TEST(cursor->around[ED_B]);
TEST(cursor->around[ED_L]);
TEST(cursor->around[ED_BL]);
TEST(!cursor->around[ED_TL]);
TEST(!cursor->around[ED_T]);
TEST(!cursor->around[ED_TR]);
TEST(!cursor->around[ED_R]);
TEST(!cursor->around[ED_BR]);
{
chunkCollectNeighbours(cursor, neighbours);
int expected[CHUNK_SIZE][CHUNK_SIZE] = {
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{2, 1, 0, 1, 1, 1, 0, 0},
{3, 2, 0, 1, 0, 1, 0, 0}
};
for (int x = 0; x < CHUNK_SIZE; ++x)
for (int y = 0; y < CHUNK_SIZE; ++y) {
if (neighbours[y][x] != expected[y][x])
fprintf(stderr, "Check (%d, %d), got %d expected %d\n",
x, y, neighbours[y][x], expected[y][x]);
TEST(neighbours[y][x] == expected[y][x]);
}
}
cursor = cursor->around[ED_L];
TEST(cursor);
TEST(cursor->around[ED_B]);
TEST(!cursor->around[ED_L]);
TEST(!cursor->around[ED_BL]);
TEST(!cursor->around[ED_TL]);
TEST(!cursor->around[ED_T]);
TEST(!cursor->around[ED_TR]);
TEST(cursor->around[ED_R]);
TEST(cursor->around[ED_BR]);
{
chunkCollectNeighbours(cursor, neighbours);
int expected[CHUNK_SIZE][CHUNK_SIZE] = {
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 1, 2},
{0, 0, 0, 0, 0, 0, 2, 3}
};
for (int x = 0; x < CHUNK_SIZE; ++x)
for (int y = 0; y < CHUNK_SIZE; ++y) {
if (neighbours[y][x] != expected[y][x])
fprintf(stderr, "Check (%d, %d), got %d expected %d\n",
x, y, neighbours[y][x], expected[y][x]);
TEST(neighbours[y][x] == expected[y][x]);
}
}
cursor = cursor->around[ED_B];
TEST(cursor);
TEST(!cursor->around[ED_B]);
TEST(!cursor->around[ED_L]);
TEST(!cursor->around[ED_BL]);
TEST(!cursor->around[ED_TL]);
TEST(cursor->around[ED_T]);
TEST(cursor->around[ED_TR]);
TEST(cursor->around[ED_R]);
TEST(!cursor->around[ED_BR]);
{
chunkCollectNeighbours(cursor, neighbours);
int expected[CHUNK_SIZE][CHUNK_SIZE] = {
{0, 0, 0, 0, 0, 0, 2, 3},
{0, 0, 0, 0, 0, 0, 1, 2},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0}
};
for (int x = 0; x < CHUNK_SIZE; ++x)
for (int y = 0; y < CHUNK_SIZE; ++y) {
if (neighbours[y][x] != expected[y][x])
fprintf(stderr, "Check (%d, %d), got %d expected %d\n",
x, y, neighbours[y][x], expected[y][x]);
TEST(neighbours[y][x] == expected[y][x]);
}
}
fprintf(stderr, "All auto tests passed!\n");
return true;
}
void usage()
{
fprintf(stderr, "USAGE:\n"
" ./life\n"
" ./life --player\n"
" ./life --player --no-stdin\n"
" ./life --tests\n"); // , ./life n, where n is the number of steps, defaults to 1.
}
typedef enum {
EM_Auto = 0,
EM_Stdout,
EM_Player
} OutputMode;
int main(int n, char *args[n])
{
// Parse command line input
int steps = -1;
char *ruleset = "B3/S23";
OutputMode mode_output = EM_Auto;
bool mode_from_stdin = true;
bool mode_enable_tests = false;
for (int i = 1; i < n; ++i) {
char *arg = args[i];
if (strlen(arg) >= 2 && strncmp("--", arg, 2) == 0) {
arg += 2;
if (strcmp(arg, "tests") == 0) {
mode_enable_tests = true;
} else if (strcmp(arg, "player") == 0) {
mode_output = EM_Player;
} else if (strcmp(arg, "stdout") == 0) {
mode_output = EM_Stdout;
} else if (strcmp(arg, "no-stdin") == 0) {
mode_from_stdin = false;
} else if (strcmp(arg, "stdin") == 0) {
mode_from_stdin = true;
} else {
fprintf(stderr, "Invalid switch: %s\n", arg);
usage();
return 1;
}
} else {
fprintf(stderr, "Mode: %s\n", arg);
char *ptr = NULL;
long a = strtol(arg, &ptr, 10);
if (ptr == arg + strlen(arg) && a >= 0) {
fprintf(stderr, "Set steps from command line: %ld.\n", a);
steps = a;
} else if (toupper(arg[0]) == 'B') {
fprintf(stderr, "Set ruleset from command line: '%s'.\n", arg);
ruleset = arg;
} else {
fprintf(stderr, "Not a valid number or ruleset: '%s'.\n", arg);
usage();
return 1;
}
}
}
srand(time(NULL));
// Do tests?
if (mode_enable_tests) {
if (!run_tests())
return 1;
}
if (mode_output != EM_Auto || !mode_enable_tests) {
Canvas *world = createCanvas();
if (!canvasParseRuleset(world, ruleset))
return 1;
// Read from stdin
int w = 0;
int h = 0;
if (mode_from_stdin) {
int ret = readFromStdin(world, &w, &h);
if (ret > 0)
return ret;
}
if (mode_output == EM_Player) {
if (steps < 0)
steps = 0;
for (int i = 0; i < steps; ++i) {
canvasStep(world);
canvasGrow(world);
}
return runPlayer(world);
} else {
if (steps < 0)
steps = 1;
for (int i = 0; i < steps; ++i) {
canvasStep(world);
canvasGrow(world);
}
chunkPrint(world->center, w, h);
return 0;
}
}
return 0;
}

201
player.c Normal file
View File

@ -0,0 +1,201 @@
#include "player.h"
#include "math.h"
#include <SDL2/SDL.h>
struct SPlayerState
{
bool paused;
Canvas *world;
int x;
int y;
int last_mouse_x;
int last_mouse_y;
SDL_Window *window;
SDL_Surface *screenSurface;
SDL_Renderer *renderer;
};
typedef struct SPlayerState PlayerState;
const int PIXEL_SIZE = 10;
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
bool initSDL(PlayerState *state)
{
// Init SDL
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
return false;
}
// Create window
state->window = SDL_CreateWindow("Game of Life",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (!state->window) {
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
return false;
}
// Create surface
state->screenSurface = SDL_GetWindowSurface(state->window);
if (!state->screenSurface) {
printf("Surface could not be created! SDL_Error: %s\n", SDL_GetError());
return false;
}
// Create Renderer
state->renderer = SDL_CreateRenderer(state->window, 0, SDL_RENDERER_ACCELERATED);
if (!state->renderer) {
printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
return false;
}
return true;
}
bool acceptInput(PlayerState *state)
{
SDL_Event e;
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) {
return false;
} else if (e.type == SDL_KEYUP) {
switch (e.key.keysym.sym) {
case SDLK_SPACE:
state->paused = !state->paused;
break;
case SDLK_s:
canvasStep(state->world);
canvasGrow(state->world);
break;
case SDLK_r:
canvasRandomise(state->world);
break;
}
}
if (e.type == SDL_MOUSEBUTTONDOWN) {
if (e.button.button != SDL_BUTTON_LEFT) {
int x, y;
SDL_GetMouseState(&x, &y);
state->last_mouse_x = x;
state->last_mouse_y = y;
}
} else if (e.type == SDL_MOUSEBUTTONUP) {
if (e.button.button == SDL_BUTTON_LEFT) {
// Do set
int x, y;
SDL_GetMouseState(&x, &y);
x = floor((float)(x + state->x) / (float)PIXEL_SIZE);
y = floor((float)(y + state->y) / (float)PIXEL_SIZE);
fprintf(stderr, "User toggled %d, %d\n", x, y);
canvasSetCell(state->world, x, y, 2);
} else {
state->last_mouse_x = -1;
state->last_mouse_y = -1;
}
} else if (e.type == SDL_MOUSEMOTION &&
state->last_mouse_x > -1 && state->last_mouse_y > -1) {
int x, y;
SDL_GetMouseState(&x, &y);
int dx = x - state->last_mouse_x;
int dy = y - state->last_mouse_y;
state->last_mouse_x = x;
state->last_mouse_y = y;
state->x -= dx;
state->y -= dy;
}
}
return true;
}
void renderChunk(PlayerState *state, Chunk *c)
{
int px = c->x * PIXEL_SIZE * CHUNK_SIZE;
int py = c->y * PIXEL_SIZE * CHUNK_SIZE;
if (px > state->x + 700 || px < state->x - 700 ||
py > state->y + 550 || py < state->y - 550)
return;
for (int x = 0; x < CHUNK_SIZE; x++)
for (int y = 0; y < CHUNK_SIZE; y++) {
if (c->current[x + y *CHUNK_SIZE]) {
SDL_SetRenderDrawColor(state->renderer, 0xFF, 0xFF, 0xFF, 0xFF);
} else {
SDL_SetRenderDrawColor(state->renderer, 0x22, 0x22, 0x22, 0xFF);
}
SDL_Rect fillRect = {
(c->x * CHUNK_SIZE + x) * PIXEL_SIZE - state->x + 1,
(c->y * CHUNK_SIZE + y) * PIXEL_SIZE - state->y + 1,
PIXEL_SIZE - 2,
PIXEL_SIZE - 2};
SDL_RenderFillRect(state->renderer, &fillRect);
}
}
void render(PlayerState *state)
{
SDL_SetRenderDrawColor(state->renderer, 0x00, 0x00, 0x00, 0xFF);
SDL_RenderClear(state->renderer);
SDL_SetRenderDrawColor(state->renderer, 0xFF, 0xFF, 0xFF, 0xFF);
for (int i = 0; i < state->world->hash_table_size; ++i) {
Chunk *c = state->world->hash_table[i];
while (c) {
renderChunk(state, c);
c = c->down;
}
}
SDL_RenderPresent(state->renderer);
}
int runPlayer(Canvas *world)
{
fprintf(stderr, "Running graphical player!\n");
PlayerState state;
state.paused = false;
state.world = world;
state.x = 0;
state.y = 0;
state.last_mouse_x = -1;
state.last_mouse_y = -1;
if (!initSDL(&state))
return 1;
bool running = true;
int timer = SDL_GetTicks();
float count = 0;
while (running) {
int now = SDL_GetTicks();
float dtime = (float)(now - timer) / 1000.0f;
timer = now;
if (state.paused)
count = 0;
else {
count += dtime;
if (count > 0.1) {
count = 0;
canvasStep(world);
canvasGrow(world);
}
}
running = acceptInput(&state);
render(&state);
}
return 0;
}

8
player.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef PLAYER_H_INCLUDED
#define PLAYER_H_INCLUDED
#include "canvas.h"
int runPlayer(Canvas *world);
#endif

BIN
screenshots/player.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
screenshots/player_B2S.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

6
test.sh Executable file
View File

@ -0,0 +1,6 @@
RES=$(make test) && [ $(echo "$RES" | grep "OK" | wc -l) == "7" ] && echo "All C/L/C tests passed" && exit 0
echo $RES
echo "Failed!"
exit 1