cepheid

An Aurora 4X clone
Log | Files | Refs | README

commit 6746c27699ac81ff9a0355675c4589517d2376f4
Author: hhvn <dev@hhvn.uk>
Date:   Fri,  3 Jun 2022 22:25:10 +0100

Init

Diffstat:
A.gitignore | 7+++++++
AMakefile | 42++++++++++++++++++++++++++++++++++++++++++
Adata/DejaVuSansMono.ttf | 0
Adata/Makefile | 22++++++++++++++++++++++
Adata/worlds-parse.awk | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adb/Makefile | 11+++++++++++
Adb/db.c | 607+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adb/db.h | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adb/dbtool.c | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/data.c | 6++++++
Asrc/main.c | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/main.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/save.c | 17+++++++++++++++++
Asrc/str.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/struct.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/style.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/system.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui.c | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
18 files changed, 1624 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,7 @@ +*.o +data/*.h +data/sol +data/worlds.tsv +db/dbtool +game +testdb/ diff --git a/Makefile b/Makefile @@ -0,0 +1,42 @@ +DATADIR = data +DBDIR = db +DBLIB = $(DBDIR)/db.o +DBTOOL = $(DBDIR)/dbtool +SRCDIR = src +SRC = $(shell find $(SRCDIR) -name "*.c") +OBJ = $(SRC:.c=.o) +BIN = game +RAYLIB = -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 +LDFLAGS = $(RAYLIB) $(DBLIB) +CFLAGS = -g3 -O0 + +all: db data $(BIN) + +$(BIN): $(OBJ) + $(CC) $(LDFLAGS) -o $(BIN) $(OBJ) + +clean: db-clean + rm -f $(BIN) $(OBJ) + +db: + @echo $(DBDIR): make $(DBLIB) + @cd $(DBDIR); make `basename $(DBLIB)` +db-clean: + @echo $(DBDIR): make clean + @cd $(DBDIR); make clean +dbtool: + @echo $(DBDIR): make $(DBTOOL) + @cd $(DBDIR); make dbtool + +data: + @echo $(DATADIR): make + @cd $(DATADIR); make +data-clean: + @echo $(DATADIR): make clean + @cd $(DATADIR); make clean + +# ignore generated headers +sloccount: + sloccount $(SRC) $(DBDIR) + +.PHONY: all db db-clean dbtool data data-clean diff --git a/data/DejaVuSansMono.ttf b/data/DejaVuSansMono.ttf Binary files differ. diff --git a/data/Makefile b/data/Makefile @@ -0,0 +1,22 @@ +FONTS = $(shell find . -name "*.ttf") +HEADERS = $(FONTS:.ttf=.h) + +all: $(HEADERS) sol + +sol: worlds.tsv + rm -rf sol + ./worlds-parse.awk < $< + printf "main\tsol\n" > sol/index + +# Thanks Jonathan +worlds.tsv: + curl https://planet4589.org/space/gcat/tsv/worlds/worlds.tsv | \ + sed 's/ *\t/\t/g' > $@ + +.ttf.h: + xxd -i < $< > $@ + +clean: + rm -rf $(HEADERS) sol + +.SUFFIXES: .h .ttf diff --git a/data/worlds-parse.awk b/data/worlds-parse.awk @@ -0,0 +1,130 @@ +#!/bin/awk -f + +BEGIN { + FS = "\t" +} + +!/^#/ { + id = $1 + idname = $2 + name = $3 + alt = $4 + radius = $5 + pradius = $6 + mass = $7 + maxorb = $8 + minorb = $9 + ecc = $10 + inc = $11 + node = $12 + peri = $13 + anomoly = $14 + epoch = $15 + rotdays = $16 + orbdays = $17 + ephemeris = $18 + type = $19 + parent = $20 + + if (type == "DB" || type == "DL") # not a body + next + + if (name == "-") + name = idname + + if (name == "Sun") { + name = "Sol" + alt = "Sun" + minorb = 0 + maxorb = 0 + orbdays = 0 + } + + if (type == "*") nicetype = "Star" + if (type == "A") nicetype = "Asteroid" + if (type == "AS") nicetype = "Asteroid moon" + if (type == "M") nicetype = "Moon" + if (type == "P") nicetype = "Planet" + + if (type == "W") { + if (parent == "-") + nicetype = "Dwarf planet" + else + nicetype = "Moon" + } + + if (type == "CP" || type == "C") nicetype = "Comet" + + if (parent == "EMB") { + if (name == "Earth") + parent = "-" + else + parent = "Earth" + } + + if (parent == "-") + parent = "Sol" + + if (mass ~ /\?$/) + sub(/\?$/, "", mass) + + if (mass ~ /M$/) { + sub(/M$/, "", mass) + massmult = 1000000 + } else { + massmult = 1 + } + + if (mass ~ /e+/) { + mantissa = mass + sub(/e+.*/, "", mantissa) + exponent = mass + sub(/.*e+/, "", exponent) + mass = mantissa * (10 ^ exponent) + } + + mass = mass * massmult + + if (minorb ~ /M$/) { + sub(/M$/, "", minorb) + minorb = minorb * 1000000 + } + + if (maxorb ~ /M$/) { + sub(/M$/, "", maxorb) + maxorb = maxorb * 1000000 + } + + if (minorb == 0) + minorb = maxorb + + if (orbdays ~ /yr$/) { + sub(/yr$/, "", orbdays) + orbdays = orbdays * 365.25 # good enough + } + + if (name != "Sol" && (radius ~ /^[-?0]$/ || maxorb ~ /^[-?0]$/ || mass ~ /^[-?0]$/ || orbdays ~ /^[-?0]$/)) + next + + file = sprintf("sol/%s", name) + system(sprintf("mkdir -p $(dirname \"%s\")", file)) + + if (name != "Sol") + printf("parent\t%s\n", parent) > file + printf("type\t%s\n", nicetype) > file + printf("radius\t%d\n", radius) > file + printf("mass\t%d\n", mass) > file + if (rotdays !~ /^[-?0]$/) + printf("rotdays\t%f\n", rotdays) > file + printf("orbdays\t%f\n", orbdays) > file + + if (nicetype == "Comet") { + printf("mindist\t%d\n", minorb) > file + printf("maxdist\t%d\n", maxorb) > file + printf("curdist\t%d\n", (rand() * (maxorb - minorb)) + minorb) > file + } else { + printf("dist\t%d\n", (minorb + maxorb) / 2) > file + } + + printf("curtheta\t%d\n", rand() * 360) > file +} diff --git a/db/Makefile b/db/Makefile @@ -0,0 +1,11 @@ +SRC = db.c dbtool.c +OBJ = $(SRC:.c=.o) +BIN = dbtool + +$(BIN): $(OBJ) + $(CC) $(CFLAGS) $(LDFLAGS) -o $(BIN) $(OBJ) + +clean: + rm -f $(OBJ) $(BIN) + +.PHONY: all clean diff --git a/db/db.c b/db/db.c @@ -0,0 +1,607 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <limits.h> +#include <dirent.h> +#include <sys/stat.h> +#include "db.h" + +typedef struct dbGroup dbGroup; +typedef struct dbPair dbPair; + +struct dbGroup { + char *dir; + char *name; + char *path; + dbPair *pairs; +}; + +struct dbPair { + dbPair *prev; + char *key; + char *val; + dbPair *next; +}; + +/* produced only by dirlist() + * A linked list is much easier to append to than a dynamically sized array. + * This is especially the case in a recursive function, like dirlist(), + * where I would probably need to use static variables to keep track + * of the size and how much has been used. It is preferable to not use static + * variables in case I ever want to use multithreading. + * + * A possible alternative would be to have a dirlist_backend() function that + * returns the files/dirs in a dir. The frontend would then append the files to + * its return list, and append the dirs to another list: it keeps calling the + * backend for each element in this list until none remain. */ +struct Dirlist { + char *path; + struct Dirlist *next; +}; + +static int track(dbGroup *group); +static int untrack(dbGroup *group); +static dbGroup *gettracked(char *dir, char *group); + +static int dirlist(struct Dirlist **list, char *dir); + +static dbGroup *dbinitgroup_p(char *dir, char *group); +static size_t dblistkeys_p(char ***ret, dbGroup *group); +static dbGroup *dbgetgroup_p(char *dir, char *group, int init); +static dbPair *dbgetpair_p(dbGroup *group, char *key); +static int dbset_p(dbPair *pair, char *val); +static int dbdelgroup_p(dbGroup *group); +static void dbdelpair_p(dbGroup *group, dbPair *pair); +static int dbwritegroup_p(dbGroup *group); +static void dbfreegroup_p(dbGroup *group); + +static dbGroup *tracked[MAXGROUPS] = {NULL}; + +/* + * Tracking loaded groups + */ +static int +track(dbGroup *group) { + int i; + if (!group) return -1; + for (i = 0; i < MAXGROUPS; i++) { + if (!tracked[i]) { + tracked[i] = group; + return 0; + } + } + return -1; +} + +static int +untrack(dbGroup *group) { + int i; + if (!group) return -1; + for (i = 0; i < MAXGROUPS; i++) { + if (tracked[i] == group) { + tracked[i] = NULL; + return 0; + } + } + return -1; +} + +static dbGroup * +gettracked(char *dir, char *group) { + int i; + for (i = 0; i < MAXGROUPS; i++) + if (tracked[i] && + strcmp(dir, tracked[i]->dir) == 0 && + strcmp(group, tracked[i]->name) == 0) + return tracked[i]; + return NULL; +} + +/* + * Various struct/data/etc handling + */ +static int +appendpair(dbPair **pairs, char *key, char *val) { + dbPair *p, *new; + + if (!pairs || !key) { + errno = EINVAL; + return -1; + } + + new = malloc(sizeof(dbPair)); + if (!new) return -1; /* ENOMEM */ + new->key = strdup(key); + new->val = val ? strdup(val) : NULL; + if (!new->key || (val && !new->val)) { + free(new->key); + free(new->val); + free(new); + errno = ENOMEM; + return -1; + } + + if (!*pairs) { + *pairs = new; + new->prev = new->next = NULL; + } else { + for (p = *pairs; p && p->next; p = p->next); + p->next = new; + new->prev = p; + new->next = NULL; + } + return 0; +} + +char ** +dblistdup(char **list, size_t len) { + char **ret; + int i; + + if (!len || !list) { + errno = EINVAL; + return NULL; + } + ret = malloc(len * sizeof(char *)); + if (!ret) return NULL; + for (i = 0; i < len; i++) { + *(ret + i) = *(list + i) ? strdup(*(list + i)) : NULL; + if (*(list + i) && !*(ret + i)) { + while (i--) + free(*(ret + i)); + errno = ENOMEM; + return NULL; + } + } + return ret; +} + +void +dblistfree(char **list, size_t len) { + int i; + + if (!len || !list) return; + for (i = 0; i < len; i++) + free(*(list + i)); + free(list); +} + +int +dblisteq(char **l1, size_t s1, char **l2, size_t s2) { + int i; + /* The order of these if statements are significant. */ + if (s1 != s2) + return 0; + if (l1 == l2) /* previous statement handles different sizes */ + return 1; + if (!l1 || !l2) /* previous statement handles both being null */ + return 0; + for (i = 0; i < s1; i++) + if (strcmp(*(l1 + i), *(l2 + i)) != 0) + return 0; + return 1; +} + +static void +freepair(dbPair *pair) { + if (pair) { + free(pair->key); + free(pair->val); + free(pair); + } +} + +/* + * Init + */ +static dbGroup * +dbinitgroup_p(char *dir, char *group) { + dbGroup *ret; + + if (!dir || !group) { + errno = EINVAL; + return NULL; + } + if ((ret = gettracked(dir, group))) + return ret; + ret = malloc(sizeof(dbGroup)); + if (!ret) return NULL; + ret->path = malloc(strlen(dir) + strlen(group) + 2); + ret->dir = strdup(dir); + ret->name = strdup(group); + ret->pairs = NULL; + if (!ret->path || !ret->dir || !ret->name) { + free(ret->path); + free(ret->dir); + free(ret->name); + free(ret); + errno = ENOMEM; + return NULL; + } + sprintf(ret->path, "%s/%s", dir, group); + track(ret); + return ret; +} + +static dbGroup * +dbloadgroup_p(char *dir, char *group) { + char path[PATH_MAX]; + char buf[8192]; + char *key, *val; + dbGroup *ret; + FILE *fp; + + if (!dir || !group) { + errno = EINVAL; + return NULL; + } + if ((ret = gettracked(dir, group))) + return ret; + + snprintf(path, sizeof(path), "%s/%s", dir, group); + if (!(fp = fopen(path, "r"))) + return NULL; /* errno set by fopen */ + if (!(ret = dbinitgroup_p(dir, group))) + return NULL; + while (fgets(buf, sizeof(buf), fp)) { + buf[strlen(buf) - 1] = '\0'; /* remove \n */ + key = strtok_r(buf, "\t", &val); + appendpair(&ret->pairs, key, val); + } + return ret; +} + +/* + * List + */ +static int +dirlist(struct Dirlist **list, char *dir) { + struct dirent **dirent; + struct stat st; + struct Dirlist *p, *prev; + char path[PATH_MAX]; + int n, i; + + n = scandir(dir, &dirent, 0, alphasort); + if (n < 0) { + return -1; /* scandir sets errno */ + } else { + for (i = 0; i < n; i++) { + snprintf(path, sizeof(path), "%s/%s", dir, dirent[i]->d_name); + if (strcmp(dirent[i]->d_name, "..") != 0 && + strcmp(dirent[i]->d_name, ".") != 0 && + stat(path, &st) != -1) { + if (S_ISDIR(st.st_mode)) { + dirlist(list, path); + } else { + if (!(p = malloc(sizeof(struct Dirlist))) || !(p->path = strdup(path))) { + free(p); + for (prev = NULL; p; p = p->next) { + free(p->path); + free(prev); + prev = p; + } + errno = ENOMEM; + return -1; + } + p->next = *list; + *list = p; + } + } + free(dirent[i]); + } + free(dirent); + } + return 0; +} + +size_t +dblistgroups(char ***ret, char *dir) { + struct Dirlist *res, *p, *prev; + size_t len, i; + + if (!ret || !dir) { + errno = EINVAL; + if (ret) *ret = NULL; + return 0; + } + if (dirlist(&res, dir) == -1) { + *ret = NULL; + return 0; + } + for (p = res, len = 0; p; p = p->next) + len++; + *ret = malloc(len * sizeof(char *)); + if (!*ret) { + *ret = NULL; + return 0; /* malloc sets errno */ + } + for (p = res, prev = NULL; p; p = p->next) { + *((*ret) + i++) = strdup(p->path + strlen(dir) + 1); + free(p->path); + free(prev); + prev = p; + } + return len; +} + +static size_t +dblistkeys_p(char ***ret, dbGroup *group) { + size_t i = 0; + size_t len; + dbPair *p; + + if (!ret || !group) { + errno = EINVAL; + if (ret) *ret = NULL; + return 0; + } + + for (p = group->pairs, len = 0; p; p = p->next) + len++; + + *ret = malloc(len * sizeof(char *)); + if (!*ret) { + *ret = NULL; + return 0; + } + for (p = group->pairs; p && i < len; p = p->next) + *((*ret) + i++) = p->key ? strdup(p->key) : NULL; + return len; +} + +size_t +dblistkeys(char ***ret, char *dir, char *group) { + dbGroup *p; + if (!(p = dbgetgroup_p(dir, group, 0))) { + *ret = NULL; + return 0; + } + return dblistkeys_p(ret, p); +} + +/* + * Get + */ +static dbGroup * +dbgetgroup_p(char *dir, char *group, int init) { + dbGroup *p; + + if (!dir || !group) { + errno = EINVAL; + return NULL; + } + if ((p = gettracked(dir, group))) + return p; + if ((p = dbloadgroup_p(dir, group))) + return p; + if (init) + return dbinitgroup_p(dir, group); + errno = ENOENT; + return NULL; +} + +static dbPair * +dbgetpair_p(dbGroup *group, char *key) { + dbPair *p; + + if (!group || !key) { + errno = EINVAL; + return NULL; + } + for (p = group->pairs; p; p = p->next) + if (strcmp(p->key, key) == 0) + return p; + errno = EINVAL; + return NULL; +} + +char * +dbget(char *dir, char *group, char *key) { + dbGroup *gp; + dbPair *pp; + if (!(gp = dbgetgroup_p(dir, group, 0)) || + !(pp = dbgetpair_p(gp, key))) + return NULL; + return pp->val; +} + +/* + * Set + */ +static int +dbset_p(dbPair *pair, char *val) { + if (!pair) { + errno = EINVAL; + return -1; + } + free(pair->val); + pair->val = val ? strdup(val) : NULL; + if (val && !pair->val) return -1; /* ENOMEM */ + return 0; +} + +int +dbset(char *dir, char *group, char *key, char *val) { + dbGroup *gp; + dbPair *pp; + if (!(gp = dbgetgroup_p(dir, group, 1))) + return -1; + if (!(pp = dbgetpair_p(gp, key))) + return appendpair(&gp->pairs, key, val); + return dbset_p(pp, val); +} + +/* + * Del + */ +static int +dbdelgroup_p(dbGroup *group) { + int ret = 0, serrno; + ret = unlink(group->path); + serrno = errno; + dbfreegroup_p(group); + if (ret == -1) + errno = serrno; + return ret; +} + +int +dbdelgroup(char *dir, char *group) { + dbGroup *p; + if (!(p = dbgetgroup_p(dir, group, 0))) + return -1; + return dbdelgroup_p(p); +} + +static void +dbdelpair_p(dbGroup *group, dbPair *pair) { + if (!pair) return; + if (pair == group->pairs) + group->pairs = pair->next; + if (pair->next) + pair->next->prev = pair->prev; + if (pair->prev) + pair->prev->next = pair->next; + freepair(pair); +} + +int +dbdelpair(char *dir, char *group, char *key) { + dbGroup *gp; + dbPair *pp; + if (!(gp = dbgetgroup_p(dir, group, 0)) || + !(pp = dbgetpair_p(gp, key))) + return -1; + dbdelpair_p(gp, pp); + return 0; +} + +/* + * Write + */ +static int +mkdirp(char *path, mode_t mode) { + struct stat st; + int serrno; + char *p; + + path = strdup(path); + if (!path) + return -1; /* use strdup's errno */ + + p = path; + while (p) { + p = strchr(p + 1, '/'); + if (p) { + while (*(p + 1) == '/') + p++; + *p = '\0'; + } + if (mkdir(path, mode) == -1) { + serrno = errno; + if (stat(path, &st) == -1) { + errno = serrno; + return -1; + } else if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + return -1; + } + } + if (p) *p = '/'; + } + return 0; +} + +static int +dbwritegroup_p(dbGroup *group) { + FILE *fp; + dbPair *p; + + if (!group || !group->path) { + errno = EINVAL; + return -1; + } + if (mkdirp(group->dir, 0755) == -1) + return -1; /* errno set by mkdirp */ + if (!(fp = fopen(group->path, "w"))) + return -1; /* errno set by fopen */ + + for (p = group->pairs; p; p = p->next) + fprintf(fp, "%s\t%s\n", p->key, p->val); + return 0; +} + +int +dbwritegroup(char *dir, char *group) { + dbGroup *p; + if (!(p = dbgetgroup_p(dir, group, 0))) + return -1; + return dbwritegroup_p(p); +} + +int +dbwrite(char *dir) { + int ret = 0, i; + + if (!dir) { + errno = EINVAL; + return -1; + } + for (i = 0; i < MAXGROUPS; i++) + if (tracked[i] && strcmp(dir, tracked[i]->dir) == 0) + if (dbwritegroup_p(tracked[i]) == -1) + ret = -1; + return ret; +} + +/* + * Free + */ + +void +dbfree(char *dir) { + int i; + + for (i = 0; i < MAXGROUPS; i++) + if (tracked[i] && strcmp(dir, tracked[i]->dir) == 0) + dbfreegroup_p(tracked[i]); +} + +static void +dbfreegroup_p(dbGroup *group) { + dbPair *p, *prev; + if (group) { + untrack(group); + prev = group->pairs; + if (prev) + p = prev->next; + while (prev) { + freepair(prev); + prev = p; + if (p) p = p->next; + } + free(group->dir); + free(group->name); + free(group->path); + free(group); + } +} + +void +dbfreegroup(char *dir, char *group) { + dbGroup *p; + if ((p = dbgetgroup_p(dir, group, 0))) + dbfreegroup_p(p); +} + +void +dbcleanup(void) { + int i; + + for (i = 0; i < MAXGROUPS; i++) + if (tracked[i]) + dbfreegroup_p(tracked[i]); +} diff --git a/db/db.h b/db/db.h @@ -0,0 +1,119 @@ +#include <stddef.h> + +#define MAXGROUPS 256 +#define TODO + +/* ----- + * List functions + */ + +/* Set *ret to a list of groups, and return the length of the list. + * On failure, set *ret to NULL, and return 0; + * The data returned by this function must be free'd using dblistfree() + * Errors may be those of scandir(3) or: + * EINVAL ret, dir or group are NULL + * ENOMEM allocation function failed + */ +size_t dblistgroups(char ***ret, char *dir); + +/* Set *ret to a list of keys, and return the length of the list. + * On failure, set *ret to NULL, and return 0; + * The data returned by this function must be free'd using dblistfree() + * Errors: + * EINVAL ret, dir or group are NULL + * ENOENT no such group + * ENOMEM allocation function failed + */ +size_t dblistkeys(char ***ret, char *dir, char *group); + +/* Returns a duplicated list, or NULL for failure. + * The data returned by this function must be free'd using dblistfree() + * Errors: + * EINVAL list is NULL, or len is 0 + * ENOMEM allocation function failed + */ +char **dblistdup(char **list, size_t len); + +/* Free memory allocated by: + * dblistgroups(), dblistkeys(), dblistdup() */ +void dblistfree(char **list, size_t len); + +/* Returns 1 if lists are equal, and 0 if not. */ +int dblisteq(char **l1, size_t s1, char **l2, size_t s2); + +/* ----- + * Manipulating values + */ + +/* Returns a value, or NULL for failure. + * Errors: + * EINVAL dir, group, or key are NULL + * ENOENT no such group + */ +char *dbget(char *dir, char *group, char *key); + +/* Set a value. + * Returns 0 for success, or -1 for failure. + * Errors: + * EINVAL dir, group, or key is NULL + * ENOENT no such group + * ENOMEM allocation function failed + */ +int dbset(char *dir, char *group, char *key, char *val); + +/* ----- + * Deleting groups/pairs + */ + +/* Free a group and delete it from storage. + * Returns 0 for success or -1 for failure. + * Errors are that of unlink(3) or: + * EINVAL dir or group are NULL + * ENOENT no such group + */ +int dbdelgroup(char *dir, char *group); + +/* Free a pair and remove it from the group. + * dbwrite() or dbwritegroup() must still be called on + * the parent group to remove this pair from storage. + * Returns 0 for success or -1 for failure. + * Errors: + * EINVAL dir, group or key is NULL + * ENOENT no such group/key + */ +int dbdelpair(char *dir, char *group, char *key); + +/* ----- + * Writing db data to storage + */ + +/* Write a group to file. + * Returns 0 for success or -1 for failure. + * Errors are that of mkdir(3), fopen(3), or: + * EINVAL dir or group are NULL + * ENOENT no such group + */ +int dbwritegroup(char *dir, char *group); + +/* Writes all groups in a directory to file. + * Returns 0 for success or -1 for failure. + * Every write is attempted even if one fails. + * Errors are that of mkdir(3), fopen(3), or: + * EINVAL dir is NULL + */ +int dbwrite(char *dir); + +/* ----- + * Cleanup + */ + +/* Discards and frees the content of groups in a directory. */ +void dbfree(char *dir); + +/* Discards and frees the content of a group. */ +void dbfreegroup(char *dir, char *group); + +/* Discards and frees everything used by db.c. + * Data from dblistgroups(), dblistkeys() and dblistdup() are excluded: + * dblistfree() must be used on the data returned by these functions. */ +void dbcleanup(void); diff --git a/db/dbtool.c b/db/dbtool.c @@ -0,0 +1,95 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libgen.h> +#include "db.h" + +void +fail(char *func) { + perror(func); + dbcleanup(); + exit(EXIT_FAILURE); +} + +void +usage(char *name) { + char *base; + base = basename(name); + fprintf(stderr, "usage: %s <dir>\n", base); + fprintf(stderr, " %s <dir> dump [group]\n", base); + fprintf(stderr, " %s <dir> get <group> <key>\n", base); + fprintf(stderr, " %s <dir> set <group> <key> [val]\n", base); + fprintf(stderr, " %s <dir> del <group> [key]\n", base); +} + +void +listpairs(char *dir, char *group) { + char **list; + size_t len, i; + + len = dblistkeys(&list, dir, group); + if (!list) + fail("dblistkeys()"); + for (i = 0; i < len; i++) + printf("%s=%s\n", *(list + i), dbget(dir, group, *(list + i))); + dblistfree(list, len); +} + +int +main(int argc, char *argv[]) { + char *p; + char **list; + size_t len, i; + + if (argc == 2) { + len = dblistgroups(&list, argv[1]); + if (!list) + fail("dblistgroups()"); + for (i = 0; i < len; i++) + printf("%s\n", *(list + i)); + } else if (argc == 3 && strcmp(argv[2], "dump") == 0) { + len = dblistgroups(&list, argv[1]); + if (!list) + fail("dblistgroups()"); + for (i = 0; i < len; i++) { + printf("%s:\n", *(list + i)); + listpairs(argv[1], *(list + i)); + if (i != len - 1) + printf("\n"); + } + } else if (argc == 4 && strcmp(argv[2], "dump") == 0) { + listpairs(argv[1], argv[3]); + } else if (argc == 4 && strcmp(argv[2], "get") == 0) { + len = dblistkeys(&list, argv[1], argv[3]); + if (!list) + fail("dblistkeys()"); + for (i = 0; i < len; i++) + printf("%s\n", *(list + i)); + dblistfree(list, len); + } else if (argc == 5 && strcmp(argv[2], "get") == 0) { + if (!(p = dbget(argv[1], argv[3], argv[4]))) + fail("dbget()"); + printf("%s\n", p); + } else if (argc == 5 && strcmp(argv[2], "set") == 0) { + if (dbset(argv[1], argv[3], argv[4], NULL) == -1) + fail("dbset()"); + dbwrite(argv[1]); + } else if (argc == 6 && strcmp(argv[2], "set") == 0) { + if (dbset(argv[1], argv[3], argv[4], argv[5]) == -1) + fail("dbset()"); + dbwrite(argv[1]); + } else if (argc == 4 && strcmp(argv[2], "del") == 0) { + if (dbdelgroup(argv[1], argv[3]) == -1) + fail("dbdelgroup()"); + dbwrite(argv[1]); + } else if (argc == 5 && strcmp(argv[2], "del") == 0) { + if (dbdelpair(argv[1], argv[3], argv[4]) == -1) + fail("dbdelpair()"); + dbwrite(argv[1]); + } else { + usage(argv[0]); + } + + dbcleanup(); + return EXIT_SUCCESS; +} diff --git a/src/data.c b/src/data.c @@ -0,0 +1,6 @@ +#include <stddef.h> + +unsigned char DejaVuSansMono_ttf[] = { +#include "../data/DejaVuSansMono.h" +}; +size_t DejaVuSansMono_ttf_size = sizeof(DejaVuSansMono_ttf); diff --git a/src/main.c b/src/main.c @@ -0,0 +1,41 @@ +#include <stdio.h> +#include <raylib.h> +#include "main.h" + +#define TESTSAVE "testdb" + +Save *save = NULL; + +int +main(void) { + int w, h; + ui_init(); + Tabs test = { + 2, 0, {{"Display", 0}, {"Minerals", 0}} + }; + + save = save_init(TESTSAVE); + + while (!WindowShouldClose()) { + ui_clickable_update(); + + if (IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT)) { + /* AAAAAAAAAAHHHHHHHHHHHH. WHY NOT JUST USE KEY_1, KEY_2..! */ + if (IsKeyPressed(KEY_ONE)) view_tabs.sel = 0; + else if (IsKeyPressed(KEY_TWO)) view_tabs.sel = 1; + else if (IsKeyPressed(KEY_THREE)) view_tabs.sel = 2; + else if (IsKeyPressed(KEY_FOUR)) view_tabs.sel = 3; + else if (IsKeyPressed(KEY_FIVE)) view_tabs.sel = 4; + else if (IsKeyPressed(KEY_SIX)) view_tabs.sel = 5; + } + + BeginDrawing(); + ClearBackground(COL_BG); + ui_draw_views(); + view_drawers[view_tabs.sel](); + EndDrawing(); + } + + ui_deinit(); + return 0; +} diff --git a/src/main.h b/src/main.h @@ -0,0 +1,57 @@ +#include <stddef.h> +#include <stdarg.h> +#include <raylib.h> +#include "struct.h" +#include "style.h" +#include "../db/db.h" + +#define ELEMS(array) (sizeof(array)/sizeof(array[0])) + +/* main.c */ +extern Save *save; + +/* str.c */ +char * vsmprintf(char *fmt, va_list args); +char * smprintf(char *fmt, ...); /* return allocated string */ +char * vsbprintf(char *fmt, va_list args); +char * sbprintf(char *fmt, ...); /* return string that is usable until next call */ + +/* ui.c */ +#define VIEWS_MAX_WIDTH (UI_VIEW_LAST*100) +#define VIEWS_HEIGHT 25 +#define WINDOW_TAB_HEIGHT 20 +extern Tabs view_tabs; +extern void (*view_drawers[UI_VIEW_LAST])(void); +void ui_init(void); +void ui_deinit(void); +void ui_print(int x, int y, Color col, char *format, ...); +int ui_textsize(char *text); +void ui_clickable_register(int x, int y, int w, int h, enum UiElements type, void *elem); +void ui_clickable_handle(Vector2 mouse, MouseButton button, Clickable *clickable); +void ui_clickable_update(void); +void ui_draw_views(void); +void ui_draw_border(int x, int y, int w, int h, int px); +void ui_draw_tabs(Tabs *tabs, int x, int y, int w, int h); +void ui_draw_tabbed_window(Tabs *tabs, int x, int y, int w, int h); +void ui_draw_view_main(void); +void ui_draw_view_colonies(void); +void ui_draw_view_fleets(void); +void ui_draw_view_design(void); +void ui_draw_view_systems(void); +void ui_draw_view_settings(void); + +/* system.c */ +Vector2 system_vectorize(Polar polar); +Vector2 system_vectorize_around(Vector2 around, Polar polar); +Polar system_polarize(Vector2 vector); +Polar system_sum_polar(Polar absolute, Polar relative); +Vector2 system_get_vector(char *system, char *body); +Polar system_get_polar(char *system, char *body); + +/* save.c */ +Save * save_init(char *savedir); + +/* data.c */ +#include <stddef.h> +extern unsigned char DejaVuSansMono_ttf[]; +extern size_t DejaVuSansMono_ttf_size; diff --git a/src/save.c b/src/save.c @@ -0,0 +1,17 @@ +#include <stdlib.h> +#include <string.h> +#include "main.h" + +Save * +save_init(char *dir) { + Save *ret; + + if (!dir || !(ret = malloc(sizeof(Save)))) + return NULL; + + ret->dir = strdup(dir); + ret->races = smprintf("%s/Races", dir); + ret->systems = smprintf("%s/Systems", dir); + ret->fleets = smprintf("%s/Fleets", dir); + return ret; +}; diff --git a/src/str.c b/src/str.c @@ -0,0 +1,59 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +char * +vsmprintf(char *fmt, va_list args) { + va_list ap; + int size; + char *ret; + + va_copy(ap, args); + size = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + + if (size == 0) /* -1 */ + return NULL; + + ret = malloc(size); + if (!ret) return NULL; + + va_copy(ap, args); + vsnprintf(ret, size, fmt, ap); + va_end(ap); + return ret; +} + +char * +smprintf(char *fmt, ...) { + va_list ap; + char *ret; + + va_start(ap, fmt); + ret = vsmprintf(fmt, ap); + va_end(ap); + return ret; +} + +char * +vsbprintf(char *fmt, va_list args) { + static char *ret = NULL; + va_list ap; + + free(ret); + va_copy(ap, args); + ret = vsmprintf(fmt, args); + va_end(ap); + return ret; +} + +char * +sbprintf(char *fmt, ...) { + va_list ap; + char *ret; + + va_start(ap, fmt); + ret = vsbprintf(fmt, ap); + va_end(ap); + return ret; +} diff --git a/src/struct.h b/src/struct.h @@ -0,0 +1,53 @@ +#include <stddef.h> + +typedef struct Save Save; +struct Save { + /* db locator strings. + * dir the base, everything else is "dir/var" */ + char *dir; + char *races; + char *systems; + char *fleets; +}; + +/* ui.c */ +#define TABS_MAX 16 +typedef struct Tabs Tabs; +struct Tabs { + int n; + int sel; + struct { + char *name; + int w; /* 0 = fill */ + } tabs[TABS_MAX]; +}; + +enum UiViews { + UI_VIEW_MAIN, + UI_VIEW_COLONIES, + UI_VIEW_FLEETS, + UI_VIEW_DESIGN, + UI_VIEW_SYSTEMS, + UI_VIEW_SETTINGS, + UI_VIEW_LAST +}; + +enum UiElements { + UI_TAB, +}; + +#define CLICKABLE_MAX 64 +typedef struct Clickable Clickable; +struct Clickable { + int x, y; + int w, h; + enum UiElements type; + void *elem; +}; + +/* system.c */ +typedef struct Polar Polar; +struct Polar { + float r; + float theta; +}; diff --git a/src/style.h b/src/style.h @@ -0,0 +1,56 @@ +/* undefining default raylib colours (copied from raylib.h v4.1 */ +#undef LIGHTGRAY +#undef GRAY +#undef DARKGRAY +#undef YELLOW +#undef GOLD +#undef ORANGE +#undef PINK +#undef RED +#undef MAROON +#undef GREEN +#undef LIME +#undef DARKGREEN +#undef SKYBLUE +#undef BLUE +#undef DARKBLUE +#undef PURPLE +#undef VIOLET +#undef DARKPURPLE +#undef BEIGE +#undef BROWN +#undef DARKBROWN +#undef WHITE +#undef BLACK +#undef BLANK +#undef MAGENTA +#undef RAYWHITE + +#define X_WHITE 0xe6e6e6ff +#define X_AURORA_GREY 0x1e1e1eff +#define X_AURORA_BLUE 0x00003cff +#define X_DARKBLUE 0x00002cff +#define X_AURORA_GREEN 0x649696ff + +#define UNHEX(col) ((Color){ \ + ((col & (0xff << 24)) >> 24), \ + ((col & (0xff << 16)) >> 16), \ + ((col & (0xff << 8)) >> 8), \ + (col & 0xff)}) + +#define WHITE UNHEX(X_WHITE) +#define AURORA_GREY UNHEX(X_AURORA_GREY) +#define AURORA_BLUE UNHEX(X_AURORA_BLUE) +#define DARKBLUE UNHEX(X_DARKBLUE) +#define AURORA_GREEN UNHEX(X_AURORA_GREEN) + +/* colours */ +#define COL_FG WHITE +#define COL_BG AURORA_BLUE +#define COL_UNSELBG DARKBLUE +#define COL_BORDER AURORA_GREY +#define COL_INFO AURORA_GREEN +#define COL_ORBIT AURORA_GREEN + +/* font */ +#define FONT_SIZE 10 diff --git a/src/system.c b/src/system.c @@ -0,0 +1,61 @@ +#include <math.h> +#include <string.h> +#include <stdlib.h> +#include "raylib.h" +#include "main.h" + +Vector2 +system_vectorize(Polar polar) { + return (Vector2) { + polar.r * cosf(polar.theta), + polar.r * sinf(polar.theta) + }; +} + +Vector2 +system_vectorize_around(Vector2 around, Polar polar) { + Vector2 relative = system_vectorize(polar); + return (Vector2) { + around.x + relative.x, + around.y + relative.y + }; +} + +Polar +system_polarize(Vector2 vector) { + return (Polar) { + hypotf(vector.x, vector.y), + atan2f(vector.y, vector.x) + }; +} + +Polar +system_sum_polar(Polar absolute, Polar relative) { + return system_polarize( + system_vectorize_around(system_vectorize(absolute), relative)); +} + +Vector2 +system_get_vector(char *system, char *body) { + return system_vectorize(system_get_polar(system, body)); +} + +Polar +system_get_polar(char *system, char *body) { + Polar polar; + char *group, *dist, *parent; + + group = sbprintf("%s/%s", system, body); + if (strcmp(dbget(save->systems, group, "type"), "Comet") == 0) + dist = dbget(save->systems, group, "curdist"); + else + dist = dbget(save->systems, group, "dist"); + + parent = dbget(save->systems, group, "parent"); + polar.r = strtof(dist, NULL); + polar.theta = strtof(dbget(save->systems, group, "curtheta"), NULL); + if (!parent) + return (Polar) {polar.r, polar.theta}; + else + return system_sum_polar(system_get_polar(system, parent), polar); +} diff --git a/src/ui.c b/src/ui.c @@ -0,0 +1,241 @@ +#include <raylib.h> +#include "main.h" + +static Font font; +static Clickable clickable[CLICKABLE_MAX]; + +void (*view_drawers[UI_VIEW_LAST])(void) = { + [UI_VIEW_MAIN] = ui_draw_view_main, + [UI_VIEW_COLONIES] = ui_draw_view_colonies, + [UI_VIEW_FLEETS] = ui_draw_view_fleets, + [UI_VIEW_DESIGN] = ui_draw_view_design, + [UI_VIEW_SYSTEMS] = ui_draw_view_systems, + [UI_VIEW_SETTINGS] = ui_draw_view_settings, +}; + +Tabs view_tabs = { + UI_VIEW_LAST, 0, {{"Main", 0}, {"Colonies", 0}, {"Fleets", 0}, {"Design", 0}, + {"Systems", 0}, {"Settings", 0}} +}; + +void +ui_init(void) { + InitWindow(500, 500, "testing raylib"); + SetWindowState(FLAG_WINDOW_RESIZABLE); + SetTargetFPS(30); + + font = LoadFontFromMemory(".ttf", + DejaVuSansMono_ttf, DejaVuSansMono_ttf_size, + FONT_SIZE, NULL, 0); +} + +void +ui_deinit(void) { + CloseWindow(); +} + +void +ui_print(int x, int y, Color col, char *fmt, ...) { + va_list ap; + Vector2 pos; + + pos.x = x; + pos.y = y; + va_start(ap, fmt); + DrawTextEx(font, vsbprintf(fmt, ap), pos, (float)FONT_SIZE, FONT_SIZE/10, col); + va_end(ap); +} + +int +ui_textsize(char *text) { + return MeasureTextEx(font, text, FONT_SIZE, FONT_SIZE/10).x; +} + +void +ui_clickable_register(int x, int y, int w, int h, enum UiElements type, void *elem) { + int i; + + for (i = 0; i < CLICKABLE_MAX; i++) { + if (!clickable[i].elem) { + clickable[i].x = x; + clickable[i].y = y; + clickable[i].w = w; + clickable[i].h = h; + clickable[i].type = type; + clickable[i].elem = elem; + return; + } + } + + /* welp, we ran out */ +} + +void +ui_clickable_handle(Vector2 mouse, MouseButton button, Clickable *clickable) { + Tabs *tabs; + int ftabw, fw, fn, tabw, x; + int i; + + switch (clickable->type) { + case UI_TAB: + if (button != MOUSE_BUTTON_LEFT) + return; + tabs = clickable->elem; + for (fw = clickable->w, fn = i = 0; i < tabs->n; i++) { + if (!tabs->tabs[i].w) + fn++; + else + fw -= tabs->tabs[i].w; + } + ftabw = fw / fn; + for (i = 0, x = clickable->x; i < tabs->n; x += tabw, i++) { + if (i == tabs->n - 1) + tabw = clickable->x + clickable->w - x; + else if (!tabs->tabs[i].w) + tabw = ftabw; + else + tabw = tabs->tabs[i].w; + if (mouse.x >= x && mouse.x <= x + tabw) { + tabs->sel = i; + return; + } + } + break; + } +} + +void +ui_clickable_update(void) { + Vector2 mouse; + MouseButton button; + int i; + + mouse = GetMousePosition(); + /* I wish there was a: int GetMouseButton(void) */ + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) button = MOUSE_BUTTON_LEFT; + else if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) button = MOUSE_BUTTON_MIDDLE; + else if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) button = MOUSE_BUTTON_RIGHT; + else button = -1; + + for (i = 0; i < CLICKABLE_MAX; i++) { + if (clickable[i].elem && + mouse.x >= clickable[i].x && + mouse.x <= clickable[i].x + clickable[i].w && + mouse.y >= clickable[i].y && + mouse.y <= clickable[i].y + clickable[i].h) + ui_clickable_handle(mouse, button, &clickable[i]); + clickable[i].elem = NULL; + } +} + +void +ui_draw_views(void) { + int sw = GetScreenWidth(); + if (sw > VIEWS_MAX_WIDTH) sw = VIEWS_MAX_WIDTH; + ui_draw_tabs(&view_tabs, 0, 0, sw, VIEWS_HEIGHT); +} + +void +ui_draw_border(int x, int y, int w, int h, int px) { + DrawRectangle(x, y, w, px, COL_BORDER); /* top */ + DrawRectangle(x, y, px, h, COL_BORDER); /* left */ + DrawRectangle(x, y + h - px, w, px, COL_BORDER); /* bottom */ + DrawRectangle(x + w - px, y, px, h, COL_BORDER); /* right */ +} + +void +ui_draw_tabs(Tabs *tabs, int x, int y, int w, int h) { + int fw, fn, ftabw; + int tabw; + int padx, pady; + int cx, selx = -1; + int i; + + for (fw = w, fn = i = 0; i < tabs->n; i++) { + if (!tabs->tabs[i].w) + fn++; + else + fw -= tabs->tabs[i].w; + } + + ftabw = fw / fn; + pady = (h - FONT_SIZE) / 2; + + for (i = 0, cx = x; i < tabs->n; i++, cx += tabw) { + if (i == tabs->n - 1) + tabw = x + w - cx; + else if (!tabs->tabs[i].w) + tabw = ftabw; + else + tabw = tabs->tabs[i].w; + padx = (tabw - ui_textsize(tabs->tabs[i].name)) / 2; + if (i == tabs->sel) + selx = cx; + else + DrawRectangle(cx, y, tabw, h, COL_UNSELBG); + ui_print(cx + padx, y + pady, COL_FG, "%s", tabs->tabs[i].name); + DrawRectangle(cx + tabw - 1, y, 1, h, COL_BORDER); + } + + if (tabs->sel != tabs->n - 1) { + if (!tabs->tabs[i].w) + tabw = ftabw; + else + tabw = w / tabs->n; + } + + ui_draw_border(x, y, w, h, 1); + if (selx != -1) DrawRectangle(selx - 1, y + h - 1, tabw + 1, 1, COL_BG); /* undraw bottom border */ + if (tabs->sel == 0) DrawRectangle(x, y + 1, 1, h - 1, COL_BG); /* undraw left border */ + if (tabs->sel == tabs->n - 1) DrawRectangle(x + w - 1, y + 1, 1, h - 1, COL_BG); /* undraw right border */ + + ui_clickable_register(x, y, w, h, UI_TAB, tabs); +} + +void +ui_draw_tabbed_window(Tabs *tabs, int x, int y, int w, int h) { + ui_draw_tabs(tabs, x, y, w, WINDOW_TAB_HEIGHT); + ui_draw_border(x, y, w, h, 2); +} + +void +ui_draw_view_main(void) { + static Tabs window_tabs = { + 2, 0, {{"Display", 0}, {"Minerals", 0}} + }; + ui_draw_tabbed_window(&window_tabs, 10, VIEWS_HEIGHT + 10, 200, 400); + ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Pannable body view here"); +} + +void +ui_draw_view_colonies(void) { + ui_print(10, VIEWS_HEIGHT + 10, COL_FG, "Stars/colonies here"); + ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 10, COL_FG, "Tabs here"); + ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Management stuff here"); +} + +void +ui_draw_view_fleets(void) { + ui_print(10, VIEWS_HEIGHT + 10, COL_FG, "Groups/fleets/subfleets/ships here"); + ui_print(GetScreenWidth() / 2, VIEWS_HEIGHT + 10, COL_FG, "Tabs here"); + ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Management stuff here"); +} + +void +ui_draw_view_design(void) { + ui_print(10, VIEWS_HEIGHT + 10, COL_FG, "Designations/classes here"); + ui_print(GetScreenWidth() / 4, VIEWS_HEIGHT + 10, COL_FG, "Selectable components here"); + ui_print((GetScreenWidth() / 4) * 2, VIEWS_HEIGHT + 10, COL_FG, "Selected components"); + ui_print((GetScreenWidth() / 4) * 3, VIEWS_HEIGHT + 10, COL_FG, "Class info"); +} + +void +ui_draw_view_systems(void) { + ui_print(10, GetScreenHeight() / 2, COL_FG, "System info/settings menu"); + ui_print(GetScreenWidth() / 2, GetScreenHeight() / 2, COL_FG, "Pannable system view"); +} + +void +ui_draw_view_settings(void) { + ui_print(10, VIEWS_HEIGHT + 10, COL_FG, "Settings here"); +}