I seem to only use git for projects I already got into a functioning state.

This commit is contained in:
overflowerror 2019-02-26 22:48:40 +01:00
parent 6e13be87b5
commit e5ffe2516d
15 changed files with 923 additions and 0 deletions

23
Makefile Normal file
View file

@ -0,0 +1,23 @@
LFLAGS =
CFLAGS =
all: gif
gif: LFLAGS += -lgif
gif: CFLAGS += -DGIF_SUPPORT
gif: gameoflife
gameoflife: src/main.o src/field.o src/misc.o src/cli.o src/gif.o
gcc $(LFLAGS) -o $@ $^
src/main.o: src/field.h src/misc.h src/cli.h
src/field.o: src/field.h src/misc.h
src/misc.o: src/misc.h
src/cli.o: src/field.h
%.o: %.c
gcc $(CFLAGS) -c -o $@ $<
clean:
@rm src/*.o
@rm gameoflife

5
demos/diehard.gol Normal file
View file

@ -0,0 +1,5 @@
#50,30;
27,6;
21,7;22,7;
22,8;26,8;27,8;28,8;

23
demos/glider-gun.gol Normal file
View file

@ -0,0 +1,23 @@
#200,200;
1,6;2,6;
1,7;2,7;
13,4;14,4;
12,5;16,5;
11,6;17,6;
11,7;15,7;17,7;18,7;
11,8;17,8;
12,9;16,9;
13,10;14,10;
25,2;
23,3;25,3;
21,4;22,4;
21,5;22,5;
21,6;22,6;
23,7;25,7;
25,8;
35,4;36,4;
35,5;36,4;

5
demos/glider.gol Normal file
View file

@ -0,0 +1,5 @@
#30,30;
1,0;
2,1;
0,2;1,2;2,2;

6
demos/lwss.gol Normal file
View file

@ -0,0 +1,6 @@
#70,7;
0,1;4,1;
5,2;
1,3;5,3;
2,4;3,4;4,4;5,4;

14
demos/pentadecathlon.gol Normal file
View file

@ -0,0 +1,14 @@
#11,18;
4,3;5,3;6,3;
5,4;
5,5;
4,6;5,6;6,6;
4,8;5,8;6,8;
4,9;5,9;6,9;
4,11;5,11;6,11;
5,12;
5,13;
4,14;5,14;6,14;

43
src/cli.c Normal file
View file

@ -0,0 +1,43 @@
#include <stdio.h>
#include <stdbool.h>
#include "cli.h"
#include "field.h"
cli_settings_t cli_settings;
void cli_init(cli_settings_t _settings) {
cli_settings = _settings;
}
int height;
void cli_output(field_t field) {
height = field.height;
if (cli_settings.border)
for (int x = 0; x < field.width + 2; x++) {
printf("#");
}
printf("\n");
for (int y = 0; y < field.height; y++) {
if (cli_settings.border)
printf("#");
for (int x = 0; x < field.width; x++) {
cell_t cell = field.cells[x][y];
if (cell == UNDEFINED)
cell = DEAD;
printf("%c", cli_settings.charset[cell]);
}
if (cli_settings.border)
printf("#");
printf("\n");
}
if (cli_settings.border)
for (int x = 0; x < field.width + 2; x++) {
printf("#");
}
printf("\n\033[%iA", height + (cli_settings.border ? 2 : 0));
}
void cli_end() {
printf("\33[%iB", height + (cli_settings.border ? 2 : 0));
}

17
src/cli.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef CLI_H
#define CLI_H
#include <stdbool.h>
#include "field.h"
typedef struct {
bool border;
const char* charset;
} cli_settings_t;
void cli_init(cli_settings_t);
void cli_output(field_t);
void cli_end();
#endif

164
src/field.c Normal file
View file

@ -0,0 +1,164 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "misc.h"
#include "field.h"
settings_t settings;
int generationCount;
cell_t*** generations = NULL;
void initField(settings_t _settings) {
settings = _settings;
generationCount = settings.periodThreshold;
if (generationCount < 1)
generationCount = 2;
generations = malloc(generationCount * sizeof(cell_t**));
if (generations == NULL)
error(EXIT_PANIC, "field_setup", NULL);
for (int g = 0; g < generationCount; g++) {
generations[g] = malloc(settings.dimensions.width * sizeof(cell_t*));
if (generations[g] == NULL)
error(EXIT_PANIC, "field_setup", NULL);
for (int x = 0; x < settings.dimensions.width; x++) {
generations[g][x] = malloc(settings.dimensions.height * sizeof(cell_t));
if (generations[g][x] == NULL)
error(EXIT_PANIC, "field_setup", NULL);
for (int y = 0; y < settings.dimensions.height; y++) {
generations[g][x][y] = g == 0 ? DEAD : UNDEFINED;
}
}
}
}
inline cell_t normalize(cell_t cell) {
if (cell == NEW)
return ALIVE;
if (cell == DIED)
return DEAD;
if (cell == UNDEFINED)
return DEAD;
return cell;
}
static inline cell_t normalizeForPeriodCheck(cell_t cell) {
if (cell == UNDEFINED)
return cell;
return normalize(cell);
}
static inline int getBoundarySafeState(int x, int y, cell_t** field) {
if (x < 0)
return 0;
if (y < 0)
return 0;
if (x >= settings.dimensions.width)
return 0;
if (y >= settings.dimensions.height)
return 0;
return normalize(field[x][y]) == ALIVE ? 1 : 0;
}
static inline int countNeighbors(int x, int y, cell_t** field) {
int count = 0;
count += getBoundarySafeState(x + 1, y - 1, field);
count += getBoundarySafeState(x + 1, y, field);
count += getBoundarySafeState(x + 1, y + 1, field);
count += getBoundarySafeState(x, y + 1, field);
count += getBoundarySafeState(x - 1, y + 1, field);
count += getBoundarySafeState(x - 1, y, field);
count += getBoundarySafeState(x - 1, y - 1, field);
count += getBoundarySafeState(x, y - 1, field);
return count;
}
static inline cell_t nextState(int count, cell_t current) {
current = normalize(current);
if (current == ALIVE) {
switch (settings.rules.neighbors[count]) {
case DIE:
return DIED;
case CREATE:
return ALIVE;
case KEEP:
return ALIVE;
case TOGGLE:
return DIED;
default:
error(EXIT_PANIC, "field_nextState", "unknown case");
}
} else {
switch (settings.rules.neighbors[count]) {
case DIE:
return DEAD;
case CREATE:
return NEW;
case KEEP:
return DEAD;
case TOGGLE:
return NEW;
default:
error(EXIT_PANIC, "field_nextState", "unknown case");
}
}
}
static inline int getPeriod() {
if (settings.periodThreshold < 0)
return -1;
for (int g = 1; g < generationCount; g++) {
bool diff = false;
for (int x = 0; x < settings.dimensions.width; x++) {
for (int y = 0; y < settings.dimensions.height; y++) {
//printf("x: %i, y: %i, g: %i, %i, %i\n", x, y, g, normalizeForPeriodCheck(generations[0][x][y]), normalizeForPeriodCheck(generations[g][x][y]));
if (normalizeForPeriodCheck(generations[0][x][y]) != normalizeForPeriodCheck(generations[g][x][y])) {
diff = true;
break;
}
}
if (diff)
break;
}
//printf("diff: %i\n", diff);
if (!diff)
return g;
}
return -1;
}
int nextGeneration() {
for (int x = 0; x < settings.dimensions.width; x++)
free(generations[generationCount - 1][x]);
for (int g = generationCount - 2; g >= 0; g--) {
memcpy(generations[g + 1], generations[g], settings.dimensions.width * sizeof(cell_t*));
}
for (int x = 0; x < settings.dimensions.width; x++) {
generations[0][x] = malloc(settings.dimensions.height * sizeof(cell_t));
if (generations[0][x] == NULL)
error(EXIT_PANIC, "field_nextGeneration", NULL);
for (int y = 0; y < settings.dimensions.height; y++) {
generations[0][x][y] = nextState(countNeighbors(x, y, generations[1]), generations[1][x][y]);
}
}
return getPeriod();
}
field_t getField() {
return (field_t) {
.height = settings.dimensions.height,
.width = settings.dimensions.width,
.cells = generations[0]
};
}

43
src/field.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef FIELD_H
#define FIELD_H
#include <stdbool.h>
typedef enum {
DEAD = 0, DIED = 1, NEW = 2, ALIVE = 3, UNDEFINED = -1
} cell_t;
typedef enum {
DIE = 0, CREATE = 1, KEEP = 2, TOGGLE = 3
} behaviour_t;
typedef struct {
behaviour_t neighbors[9];
} rules_t;
typedef struct {
int height;
int width;
} dimensions_t;
typedef struct {
rules_t rules;
dimensions_t dimensions;
int periodThreshold;
} settings_t;
typedef struct {
int height;
int width;
cell_t** cells;
} field_t;
#define DEFAULT_RULES ((rules_t) { .neighbors = {DIE, DIE, KEEP, CREATE, DIE, DIE, DIE, DIE, DIE}})
#define DEFAULT_PERIOD_THRESHOLD (30)
void initField(settings_t);
int nextGeneration();
field_t getField();
cell_t normalize(cell_t);
#endif

134
src/gif.c Normal file
View file

@ -0,0 +1,134 @@
#ifdef GIF_SUPPORT
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gif_lib.h>
#include "gif.h"
#include "field.h"
#include "misc.h"
gif_settings_t gif_settings;
GifFileType* image;
void putGraficsControlBlock() {
int delay = gif_settings.sleep;
static unsigned char
ExtStr[4] = { 0x04, 0x00, 0x00, 0xff };
ExtStr[0] = (false) ? 0x06 : 0x04;
ExtStr[1] = delay % 256;
ExtStr[2] = delay / 256;
EGifPutExtension(image, GRAPHICS_EXT_FUNC_CODE, 4, ExtStr);
}
void enableLoop() {
char nsle[12] = "NETSCAPE2.0";
char subblock[3];
if (EGifPutExtensionLeader(image, APPLICATION_EXT_FUNC_CODE) != GIF_OK) {
error(EXIT_PANIC, "gif_init", "put extension leader");
}
if (EGifPutExtensionBlock(image, 11, nsle) != GIF_OK) {
error(EXIT_PANIC, "gif_init", "put extension");
}
subblock[0] = 1;
subblock[2] = 0; // loop count lo
subblock[1] = 0; // loop count hi
if (EGifPutExtensionBlock(image, 3, subblock) != GIF_OK) {
error(EXIT_PANIC, "gif_init", "put extension");
}
if (EGifPutExtensionTrailer(image) != GIF_OK) {
error(EXIT_PANIC, "gif_init", "put extension trailer");
}
}
void gif_init(gif_settings_t _settings) {
gif_settings = _settings;
int errorCode = 0;
image = EGifOpenFileHandle(gif_settings.fh, &errorCode);
if (errorCode != E_GIF_SUCCEEDED) {
error(EXIT_PANIC, "gif_init", "open file handler");
}
GifColorType colors[4];
for (int i = 0; i < 4; i++) {
colors[i].Red = gif_settings.colors[i][0];
colors[i].Green = gif_settings.colors[i][1];
colors[i].Blue = gif_settings.colors[i][2];
}
ColorMapObject* colorMap = GifMakeMapObject(4, &(colors[0]));
EGifSetGifVersion(image, true);
if (EGifPutScreenDesc(image, gif_settings.dimensions.width, gif_settings.dimensions.height, 4, 0, colorMap) != GIF_OK) {
error(EXIT_PANIC, "gif_init", "put screen desc");
}
enableLoop();
}
void gif_output(field_t field) {
putGraficsControlBlock();
if (EGifPutImageDesc(image, 0, 0, field.width, field.height, false, NULL) != GIF_OK) {
error(EXIT_PANIC, "git_output", "put image desc");
}
for (int y = 0; y < field.height; y++) {
for(int x = 0; x < field.width; x++) {
cell_t cell = field.cells[x][y];
if (cell == UNDEFINED)
cell = DEAD;
if (EGifPutPixel(image, (GifPixelType) cell) != GIF_OK) {
error(EXIT_PANIC, "gif_output", "put pixel");
}
}
}
}
void gif_end() {
int errorCode;
EGifCloseFile(image, &errorCode);
if (errorCode != E_GIF_SUCCEEDED) {
fprintf(stderr, "%d\n", errorCode);
error(EXIT_PANIC, "gif_end", "close file");
}
}
unsigned char hex2byte(const char* string, int offset) {
char tmp[3];
memcpy(&(tmp[0]), string + offset, 2);
tmp[3] = '\0';
char* endptr;
unsigned char result = strtol(tmp, &endptr, 16);
return result;
}
void hex2color(unsigned char* color, const char* string) {
if (strlen(string) == 7) {
color[0] = hex2byte(string, 1);
color[1] = hex2byte(string, 3);
color[2] = hex2byte(string, 5);
} else {
}
}
void gif_setColor(gif_settings_t* settings, const char* dead, const char* died, const char* new, const char* alive) {
hex2color(&(settings->colors[DEAD][0]), dead);
hex2color(&(settings->colors[DIED][0]), died);
hex2color(&(settings->colors[NEW][0]), new);
hex2color(&(settings->colors[ALIVE][0]), alive);
}
#endif

19
src/gif.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef GIF_H
#define GIF_H
#include "field.h"
typedef struct {
int fh;
dimensions_t dimensions;
unsigned char colors[4][3];
int sleep;
} gif_settings_t;
void gif_init(gif_settings_t _settings);
void gif_output(field_t field);
void gif_setColor(gif_settings_t* settings, const char* dead, const char* died, const char* new, const char* alive);
void hex2color(unsigned char* color, const char* string);
void gif_end();
#endif

386
src/main.c Normal file
View file

@ -0,0 +1,386 @@
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <getopt.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "misc.h"
#include "field.h"
#include "cli.h"
#include "gif.h"
char behaviourMap[4] = {
'd', 'c', 'k', 't'
};
void optionError(const char* string) {
fprintf(stderr, "Error: %s\n", string);
fprintf(stderr, "User -y for help.\n");
exit(EXIT_USER_ERROR);
}
void rules2string(char* string, rules_t rules) {
for (int i = 0; i < 9; i++) {
string[i] = behaviourMap[rules.neighbors[i]];
}
string[10] = '\0';
}
rules_t string2rules(const char* string) {
if (strlen(string) != 9) {
optionError("Rules string malformed");
}
rules_t rules;
for(int i = 0; i < strlen(string); i++) {
bool okay = false;
for (int j = 0; j < 4; j++) {
if (string[i] == behaviourMap[j]) {
rules.neighbors[i] = j;
okay = true;
break;
}
}
if (!okay) {
optionError("Rules string malformed");
}
}
return rules;
}
void help() {
printf("SYNOPSIS: %s [-y] [-c [-lbn] [-s TIME] [-t CHARSET]]\n"
"%*s%s[-p LIMIT] [-r RULES] [-h HEIGHT -w WIDTH] FILE\n\n", getProgName(), strlen(getProgName()), "",
#ifdef GIF_SUPPORT
"[-g [-s TIME] [{-o COLOR}]] "
#else
""
#endif
);
printf("OPTIONS:\n");
printf(" -y show help\n");
printf(" -c use CLI output (default)\n");
printf(" -g use GIF output\n");
printf(" -p LIMIT the search limit for periodes (default: %i)\n", DEFAULT_PERIOD_THRESHOLD);
char tmp[11];
rules2string(tmp, DEFAULT_RULES);
printf(" -r RULES set custom rules (default: %s)\n", tmp);
printf(" -h HEIGHT override the height of the output\n");
printf(" -w WIDTH override the width of the output\n");
printf(" FILE the input file (- for stdin)\n");
printf("\n");
printf("CLI OPTIONS:\n");
printf(" -l loop the output even if periodes occure\n");
printf(" -b draw border (defaul)\n");
printf(" -n don't draw border\n");
printf(" -s TIME sleep in ms (default: 200)\n");
printf(" -t CHARSET charset for drawing (default: \" **\")\n");
printf("\n");
#ifdef GIF_SUPPORT
printf("GIF OPTIONS:\n");
printf(" -s TIME sleep in 1/10 s (default: 20)\n");
printf(" -o COLOR colors to use\n");
printf(" COLOR := DEAD|DIED|NEW|ALIVE:HEX_COLOR\n");
printf(" HEX_COLOR := #[a-f0-9]{6}\n");
printf(" (defaults: DEAD/DIED = #000000; NEW/ALIVE = #ffffff)\n");
printf("\n");
#endif
printf("INPUT FORMAT:\n");
printf("[#WIDTH,HEIGHT;]{X,Y;}\n\n");
printf(" WDITH the width of the output\n");
printf(" HEIGHT the width of the output\n");
printf(" X the x coordinate of a cell\n");
printf(" Y the y coordinate of a cell\n\n");
printf("Example:\n");
printf("#3,3;1,0;1,2;1,3;\n");
exit(EXIT_SUCCESS);
}
int readCoordinates(FILE* file, int* x, int* y) {
int c;
char tmp[10];
char* endptr;
int position = 0;
*x = -1;
*y = -1;
bool has = false;
while((c = getc(file)) != EOF) {
switch(c) {
case ',':
tmp[position] = '\0';
position = 0;
*x = strtol(tmp, &endptr, 10);
if (*endptr != '\0') {
return -1;
}
break;
case ';':
tmp[position] = '\0';
position = 0;
*y = strtol(tmp, &endptr, 10);
if (*endptr != '\0') {
return -1;
}
if ((*x < 0) || (*y < 0))
return -1;
return 0;
case ' ':
case '\n':
case '\t':
break;
default:
has = true;
if (position > 9) {
return -1;
}
tmp[position++] = c;
break;
}
}
if (!has)
return 1;
return -1;
}
void setSize(FILE* file, int* width, int* height) {
int c = getc(file);
if (c != '#') {
ungetc(c, file);
return;
}
c = readCoordinates(file, width, height);
if (c != 0) {
error(EXIT_USER_ERROR, "setSize", "malformed file format");
}
}
void setField(FILE* file, field_t field) {
int r, x, y;
while((r = readCoordinates(file, &x, &y)) == 0) {
if ((x < 0) || (y < 0) || (x >= field.width) || (y >= field.height)) {
error(EXIT_USER_ERROR, "setField", "coordinates out of bounds");
}
field.cells[x][y] = ALIVE;
}
if (r < 0) {
error(EXIT_USER_ERROR, "setField", "malformed input format");
}
};
void parseColor(gif_settings_t* settings, char* optarg) {
char* color = NULL;
int celltype = -1;
for (int i = 0; i < strlen(optarg); i++) {
if (optarg[i] == ':') {
color = optarg + i + 1;
optarg[i] = '\0';
break;
}
}
if (color == NULL) {
optionError("color format malformed");
}
if (strcmp(optarg, "DEAD") == 0) {
celltype = DEAD;
} else if (strcmp(optarg, "DIED") == 0) {
celltype = DIED;
} else if (strcmp(optarg, "NEW") == 0) {
celltype = NEW;
} else if (strcmp(optarg, "ALIVE") == 0) {
celltype = ALIVE;
} else {
optionError("unknown cell type");
}
hex2color(&(settings->colors[celltype][0]), color);
}
int main(int argc, char** argv) {
initMisc(argv[0]);
bool gif = false;
bool loop = false;
int width = -1;
int height = -1;
const char* filename;
FILE* file;
long sleep = -1;
settings_t settings;
settings.dimensions.width = -1;
settings.dimensions.height = -1;
settings.rules = DEFAULT_RULES;
settings.periodThreshold = DEFAULT_PERIOD_THRESHOLD;
cli_settings_t cli_settings;
cli_settings.border = true;
cli_settings.charset = " **";
gif_settings_t gif_settings;
const char* gif_filename = "-";
gif_setColor(&gif_settings, "#000000", "#000000", "#ffffff", "#ffffff");
int opt;
char* endptr;
while((opt = getopt(argc, argv, "ycgp:r:h:w:lbns:t:o:")) != -1) {
switch(opt) {
case 'y':
help();
break;
case 'c':
gif = false;
break;
case 'g':
gif = true;
break;
case 'p':
settings.periodThreshold = strtol(optarg, &endptr, 10);
if (*endptr != '\0') {
optionError("Argument has to be a number.");
}
break;
case 'r':
settings.rules = string2rules(optarg);
break;
case 'h':
settings.dimensions.height = strtol(optarg, &endptr, 10);
if (*endptr != '\0') {
optionError("Argument has to be a number.");
}
break;
case 'w':
settings.dimensions.width = strtol(optarg, &endptr, 10);
if (*endptr != '\0') {
optionError("Argument has to be a number.");
}
break;
case 'l':
loop = true;
break;
case 'b':
cli_settings.border = true;
break;
case 'n':
cli_settings.border = false;
break;
case 's':
sleep = strtol(optarg, &endptr, 10);
if (*endptr != '\0') {
optionError("Argument has to be a number.");
}
break;
case 't':
if (strlen(optarg) != 4) {
optionError("Argument needs to be length 4");
}
cli_settings.charset = optarg;
break;
case 'o':
parseColor(&gif_settings, optarg);
break;
default:
optionError("unknown option");
break;
}
}
if (optind >= argc) {
optionError("No file given.");
}
filename = argv[optind];
if (strcmp("-", filename) == 0) {
file = stdin;
} else {
file = fopen(filename, "r");
if (file == NULL)
error(EXIT_USER_ERROR, "main", NULL);
}
setSize(file, &width, &height);
if ((width < 0) || (height < 0)) {
optionError("Width or height missing");
}
settings.dimensions.width = width;
settings.dimensions.height = height;
initField(settings);
setField(file, getField());
if (gif) {
#ifndef GIF_SUPPORT
error(EXIT_COMPILE_TIME_ERROR, "main", "no GIF support");
#else
if (sleep < 0)
sleep = 20;
if (strcmp(gif_filename, "-") == 0) {
freopen(NULL, "wb", stdout);
gif_settings.fh = fileno(stdout);
} else {
gif_settings.fh = open(gif_filename, O_WRONLY | O_CREAT | O_TRUNC);
if (gif_settings.fh < 0) {
error(EXIT_USER_ERROR, gif_filename, NULL);
}
}
gif_settings.dimensions = settings.dimensions;
gif_settings.sleep = sleep;
gif_init(gif_settings);
gif_output(getField());
int period;
while(((period = nextGeneration()) < 0)) {
gif_output(getField());
}
gif_end();
#endif
} else {
if (sleep < 0)
sleep = 200;
cli_init(cli_settings);
cli_output(getField());
int period;
while(((period = nextGeneration()) < 0) || loop) {
usleep(sleep * 1000);
cli_output(getField());
}
cli_output(getField());
cli_end();
}
return EXIT_SUCCESS;
}

28
src/misc.c Normal file
View file

@ -0,0 +1,28 @@
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "misc.h"
const char* progName;
void initMisc(const char* _progName) {
progName = _progName;
}
const char* getProgName() {
return progName;
}
void error(int exitcode, const char* module, const char* error) {
(void) fprintf(stderr, "%s: %s", progName, module);
if (error != NULL) {
(void) fprintf(stderr, ": %s", error);
}
if (errno != 0) {
(void) fprintf(stderr, ": %s", strerror(errno));
}
(void) fprintf(stderr, "\n");
exit(exitcode);
}

13
src/misc.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef MISC_H
#define MISC_H
#define EXIT_PANIC 42
#define EXIT_SUCCESS 0
#define EXIT_USER_ERROR 1
#define EXIT_COMPILE_TIME_ERROR 5
void initMisc(const char*);
const char* getProgName();
void error(int, const char*, const char*);
#endif