diff --git a/.gitignore b/.gitignore index 97fedc6..ecf54ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,2 @@ -/bin/elem -/bin/elem.exe -/bin/elem_32.exe -/inv.txt -/inv_users.txt -/polls.txt -/elem.tar.gz -/combos.txt \ No newline at end of file +/elem +/elem.exe \ No newline at end of file diff --git a/Makefile b/Makefile index 5baff72..75f31d0 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC?=gcc -BIN?=bin/elem +BIN?=elem make: src/main.c - $(CC) -ffunction-sections -fdata-sections -Wl,--gc-sections -Os -o $(BIN) src/map.c src/loader.c src/command.c src/main.c \ No newline at end of file + $(CC) -o $(BIN) src/map.c src/loader.c src/command.c src/main.c \ No newline at end of file diff --git a/README.md b/README.md index d5d6b6f..d331afb 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,2 @@ -# Elemental on Command Line -This is an elemental combination game written in C, with optional multi-player support. - -## Clients -- [PenguinMod client](https://studio.penguinmod.com/?fps=200&clones=Infinity&offscreen&size=850x480#7218964246) for playing the game online with a fancy interface -- [Discord client](https://discord.gg/DKZTWyWH3B) for playing the game publicly with other Discord members -- [Web client](https://elem.dervland.net/index.html) for a demo of the multiplayer version -- [Original client](https://git.dervland.net/elemental/elemental-on-terminal/releases) for developers, administrators, and command line fans - -## Single-player -A single-player example pack is provided as an example in ``bin/combos.txt``. This can be customized by the user, although internal limitations require the combination string to be in alphabetical order. - -## Multi-player -To set up multi-player mode, use the guide with the [Web proxy](https://git.dervland.net/elemental/elemental-to-web). \ No newline at end of file +# Elemental on CLI +Elemental combination game written in C. \ No newline at end of file diff --git a/bin/combos.txt b/bin/combos.txt deleted file mode 100644 index 25d42eb..0000000 --- a/bin/combos.txt +++ /dev/null @@ -1,41 +0,0 @@ -wind;air;air -dust;air;earth -heat;air;fire -cloud;air;water -land;earth;earth -lava;earth;fire -mud;earth;water -wildfire;fire;fire -smoke;fire;water -puddle;water;water -elements;air;earth;fire;water -dirt;dust;earth -clay;dirt;wind -cobble;air;lava -stone;cobble;earth -gravel;cobble;wind -blue;fire;heat -steam;heat;water -energy;steam;steam -sky;air;wind -sky;air;cloud -space;air;sky -space;sky;sky -void;space;space -zero;energy;void -numbers;elements;zero -one;numbers;numbers -color;blue;numbers -spectrum;color;numbers -pond;earth;puddle -pond;puddle;puddle -lake;pond;water -lake;pond;pond -sea;lake;lake -sea;lake;water -ocean;sea;sea -ocean;land;sea -planet;earth;ocean -planet;land;ocean -star;fire;sky -pollution;air;dust diff --git a/bin/inv_base.txt b/bin/inv_base.txt deleted file mode 100644 index 07496fd..0000000 --- a/bin/inv_base.txt +++ /dev/null @@ -1,4 +0,0 @@ -air -earth -fire -water diff --git a/combos.txt b/combos.txt new file mode 100644 index 0000000..a71a4bd --- /dev/null +++ b/combos.txt @@ -0,0 +1,11 @@ +wind;air;air +dust;air;earth +heat;air;fire +cloud;air;water +land;earth;earth +lava;earth;fire +mud;earth;water +wildfire;fire;fire +smoke;fire;water +puddle;water;water +elements;air;earth;fire;water diff --git a/src/command.c b/src/command.c index 749cbdc..0b1b5a6 100644 --- a/src/command.c +++ b/src/command.c @@ -1,41 +1,11 @@ -#include "map.h" -#include +#include #include #include #include #include -#include -#include "loader.h" -#include "main.h" -// huge cleanup operation soon - -struct pager { - int page; - int i; - int is_poll; - hashmap *cmd; -}; - -struct verifier { - char *name; - char *sugg; -}; - -struct succ { - char *sugg; - int *points; -}; - -long stoi(const char *s) { - long i; - i = 0; - while (*s >= '0' && *s <= '9') { - i = i * 10 + (*s - '0'); - s++; - } - return i; -} +#define MAX_BUF_LENGTH 1024 +#define MAX_COMBO_LENGTH 1024 int fix_delim(char *command, char *needle) { char *delim = strstr(command, needle); @@ -52,307 +22,8 @@ int sort_comp(const void *a, const void *b) { return strcmp(*(char **)a, *(char **)b); } -int inv_handler(const void *key, size_t size, uintptr_t val, void *usr) { - struct pager *i = usr; - if (val == 0) - return 0; - char *key2 = (char *)key; - char *val2 = (char *)val; - - if (i->is_poll) { - char *key3 = calloc(strlen(key2) + 1, sizeof(char)); - memcpy(key3, key2, strstr(key2, "_") - key2); - - char *val3 = strstr(val2, ";") + 1; - uintptr_t result; - - hashmap_get(i->cmd, val3, strlen(val3) - 1, &result); - - if (result != 0) { - free(key3); - return 1; - } - - i->i++; - if (i->i < i->page * 10 || i->i >= (i->page + 1) * 10) { - return 0; - } - - if (val2[strlen(val2) - 1] == '\n') { - printf("- user:%s suggested %s", key3, val2); - } else { - printf("- user:%s suggested %s\n", key3, val2); - } - - free(key3); - return 1; - } - - i->i++; - if (i->i < i->page * 10 || i->i >= (i->page + 1) * 10) { - return 0; - } - - if (strlen(key2) > 0 && key2[strlen(key2) - 1] == '\n') { - printf("- %s", key2); - } else { - printf("- %s\n", key2); - } - return 1; -} - -int polls_handler(const void *key, size_t size, uintptr_t val, void *usr) { - struct verifier *verified = (struct verifier *)usr; - - char *val2 = (char *)val; - char *val3 = malloc(strlen(val2) + 1); - strcpy(val3, val2); - - if (val3[strlen(val3) - 1] == '\n') { - val3[strlen(val3) - 1] = '\0'; - } - - if (strncmp(verified->name, key, strlen(verified->name)) == 0 && - strcmp(verified->sugg, val3) == 0) { - free(val3); - return -1; - } - - free(val3); - return 0; -} - -int success_handler(const void *key, size_t size, uintptr_t val, void *usr) { - struct succ *verified = (struct succ *)usr; - - char *val2 = (char *)val; - char *val3 = malloc(strlen(val2) + 1); - strcpy(val3, val2); - - if (val3[strlen(val3) - 1] == '\n') { - val3[strlen(val3) - 1] = '\0'; - } - - if (strcmp(verified->sugg, val3) == 0) { - verified->points[0]++; - } - - free(val3); - - return 0; -} - -char *handle_pages(char *command, char *invs) { - char *data; - char *data2; - if (strncmp(command, invs, strlen(invs)) == 0) { - data = &command[strlen(invs)]; - data2 = malloc(strlen(data) + 1); - memcpy(data2,data,strlen(data) + 1); - return data2; - } else { - return 0; - } -} - -int handle_pages_int(char *command, char *invs) { - if (strncmp(command, invs, strlen(invs)) == 0) { - return stoi(&command[strlen(invs)]); - } else if (strncmp(command, invs, strlen(invs) - 1) == 0) { - return 1; - } else { - return 0; - } -} - -int suggest_command(char *command, char *command_re, hashmap *polls, hashmap *combos, char *name, - int was_combination) { - - char *page = handle_pages(command, "/suggest "); - - if (!page) - return 0; - page[strlen(page) - 1] = '\0'; - - if (strstr(page, "\n") || strstr(page, ";") || strstr(page, ",") || - strstr(page, "+")) { - printf("This element contains illegal characters.\n"); - return 1; - } - - char *val = calloc(MAX_BUF_LENGTH, sizeof(char)); - sprintf(val, "%s;%s", page, command_re); - - srand(time(NULL)); - - char *key = calloc(MAX_BUF_LENGTH, sizeof(char)); - sprintf(key, "%s_%i", name, (int)rand()); - - int point_thing = 0; - - struct succ succer = {.sugg = val, .points = &point_thing}; - hashmap_iterate(polls, success_handler, (void *)&succer); - - if (was_combination == 2 && point_thing == 0) { - printf("You cannot create unique polls with elements you do not have.\n"); - return 1; - } - - struct verifier verified = {.name = name, .sugg = val}; - if (hashmap_iterate(polls, polls_handler, (void *)&verified) == -1) { - printf("You already suggested this!\n"); - return 1; - } - - hashmap_set(polls, key, strlen(key), (uintptr_t)val, 1); - - point_thing++; - - if (point_thing == UPVOTE_IN) { - printf("Poll was added into the game!\n"); - FILE *fptr; - - hashmap_set(combos, command_re, strlen(command_re), (uintptr_t)page, 0); - - write_elements(combos, "../elem_data/" COMBO_FILE, 0); - return 1; - } - - printf("Suggested %s = %s.\n", command_re, page); - - // todo: clean old polls - - FILE *fptr; - - write_elements(polls, "../elem_data/" POLL_FILE, 3); - - return 1; -} - -int help_command(char *command) { - char *page = handle_pages(command, "/help"); - - if (!page) - return 0; - printf("Available " - "commands:\n- elem1;elem2...\n- elem1+elem2...\n- elem1,elem2...\n- " - "/help\n- /inv [page]\n- /suggest [combo]\n- /polls [page]\n"); - return 1; -} - -int polls_command(char *command, hashmap *polls, hashmap *cmd) { - - int page = handle_pages_int(command, "/polls "); - if (page == 0) - return 0; - - printf("Current polls (page %i):\n", page); - struct pager i = {.page = page - 1, .i = -1, .is_poll = 1, .cmd = cmd}; - hashmap_iterate(polls, inv_handler, &i); - printf("Total: %i\n", i.i + 1); - return 1; -} - -int path_command(char *command, hashmap *elements_rev, hashmap *already_done, - int top, hashmap *inv) { - char *page2; - if (top) { - page2 = handle_pages(command, "/path "); - if (page2 == 0) - return 0; - } else { - page2 = command; - } - - char *page = malloc(strlen(page2) + 1); - strcpy(page, page2); - if (page[strlen(page) - 1] == '\n') { - page[strlen(page) - 1] = '\0'; - } - if (strlen(page) == 0) { - return 1; - } - - if (top) { - printf("Path of %s:\n", page); - hashmap_free(already_done); - already_done = hashmap_create(); - } - - uintptr_t result; - hashmap_get(inv, page, strlen(page), &result); - - if (top && result != 1) { - printf("You don't have %s.\n", page); - return 1; - } - - hashmap_get(already_done, page, strlen(page), &result); - - if (result != 0) { - return 1; - } - hashmap_set(already_done, page, strlen(page), (uintptr_t)1, 0); - - hashmap_get(elements_rev, page, strlen(page) - 1, &result); - - if (result == 0 || strlen((char *)result) < 1) { - return 1; - } - - // todo: refactor; - char *tmp = (char *)result, *tmp2 = 0; - if (tmp[strlen(tmp) - 1] == '\n') { - tmp[strlen(tmp) - 1] = '\0'; - } - - int combos = 0; - for (int i = 1; i < MAX_COMBO_LENGTH; i++) { - combos = i; - tmp2 = tmp; - if (tmp2[0] == ';') { - tmp2++; - } - tmp = strstr(tmp2, ";"); - if (tmp == 0) { - tmp = &tmp2[strlen(tmp2)]; - } - - char *tmp23 = malloc(strlen(tmp2) - strlen(tmp) + 2); - memcpy(tmp23, &tmp2[0], strlen(tmp2) - strlen(tmp) + 2); - tmp23[strlen(tmp2) - strlen(tmp) + 1] = '\0'; - char *strstrd = strstr(tmp23, ";"); - if (strstrd != 0) { - strstrd[0] = '\0'; - } - - path_command(tmp23, elements_rev, already_done, 0, inv); - - if (strlen(tmp23) < 1) { - break; - } - free(tmp23); - } - - printf("<- %s\n-> %s\n", (char *)result, (char *)page); - - return 1; -} - -int slash_command(char *command, hashmap *inv) { - - int page = handle_pages_int(command, "/inv "); - if (page == 0) - return 0; - - printf("Your inventory (page %i):\n", page); - struct pager i = {.page = page - 1, .i = -1, .is_poll = 0}; - hashmap_iterate(inv, inv_handler, &i); - printf("Total: %i\n", i.i + 1); - return 1; -} - -int get_command(char *command, char *command_re, char **sort_tmp) { +char *get_command(char *command, char *command_re, char **sort_tmp) { + fgets(command, MAX_BUF_LENGTH - 1, stdin); while (1) { int get_out = 0; @@ -361,29 +32,21 @@ int get_command(char *command, char *command_re, char **sort_tmp) { if (!get_out) break; } - - if (command[0] == ';') { - command++; - } - int cl = strlen(command); - if (cl < 2) - return 0; - command[cl - 1] = '\0'; + for (int i = 0; i < cl - 1; i++) { + command[i] = tolower(command[i]); + } + sort_tmp[0] = command; int combos = 0; for (int i = 1; i < MAX_COMBO_LENGTH; i++) { combos = i; - sort_tmp[i] = strstr(sort_tmp[i - 1], ";"); - if (sort_tmp[i] == 0 || sort_tmp[i] == sort_tmp[i - 1]) + sort_tmp[i] = strstr(command, ";"); + if (sort_tmp[i] == 0) break; - if (strlen(sort_tmp[i]) < 2) { - sort_tmp[i][0] = '\0'; - break; - } sort_tmp[i][0] = '\0'; sort_tmp[i]++; } @@ -401,5 +64,5 @@ int get_command(char *command, char *command_re, char **sort_tmp) { command_re_tmp += strlen(last); } - return combos; + return command_re; } \ No newline at end of file diff --git a/src/command.h b/src/command.h index cdd78bf..aead233 100644 --- a/src/command.h +++ b/src/command.h @@ -1,9 +1 @@ -#include "map.h" -int get_command(char *command, char *command_re, char **sort_tmp); -int slash_command(char *command, hashmap *inv); -int suggest_command(char *command, char *command_re, hashmap *polls, - hashmap *combos, char *name, int was_combination); -int help_command(char *command); -int polls_command(char *command, hashmap *polls, hashmap *cmd); -int path_command(char *command, hashmap *elements_rev, hashmap *already_done, - int top, hashmap *inv); \ No newline at end of file +char *get_command(char *command, char *command_re, char **sort_tmp); \ No newline at end of file diff --git a/src/loader.c b/src/loader.c index a41a19d..e712c63 100644 --- a/src/loader.c +++ b/src/loader.c @@ -4,126 +4,26 @@ #include #include -#include "main.h" +#define MAX_FILE_SIZE 1024 * 16 -struct write_struct { - FILE *fptr; - int mode; -}; - -int entry_handler(const void *key, size_t size, uintptr_t val, void *usr) { - struct write_struct *usr2 = (struct write_struct *)usr; - - char *val2 = (char *)val; - char *key2 = (char *)key; - - if (usr2->mode == 0) { - fwrite(val2, sizeof(char), strlen(val2), usr2->fptr); - fwrite(";", sizeof(char), 1, usr2->fptr); - fwrite(key2, sizeof(char), strlen(key2), usr2->fptr); - if (key2[strlen(key2) - 1] != '\n') { - fwrite("\n", sizeof(char), 1, usr2->fptr); - } - } else if (usr2->mode == 1) { - fwrite(key2, sizeof(char), strlen(key2), usr2->fptr); - if (key2[strlen(key2) - 1] != '\n') { - fwrite("\n", sizeof(char), 1, usr2->fptr); - } - } else { - fwrite(key2, sizeof(char), strlen(key2), usr2->fptr); - fwrite(";", sizeof(char), 1, usr2->fptr); - fwrite(val2, sizeof(char), strlen(val2), usr2->fptr); - if (val2[strlen(val2) - 1] != '\n') { - fwrite("\n", sizeof(char), 1, usr2->fptr); - } - } - - return 1; -} - -void write_elements(hashmap *m, char *table, int mode) { - FILE *fptr; - - fptr = fopen(table, "w"); - - if (fptr == NULL) - return; - - struct write_struct writer = {.fptr = fptr, .mode = mode}; - - hashmap_iterate(m, entry_handler, (void *)&writer); - //fwrite("\n", sizeof(char), 1, fptr); - - fclose(fptr); -} - -int load_elements(hashmap *m, char *table, int mode) { +void load_elements(hashmap *m, char *table) { FILE *fptr; fptr = fopen(table, "r"); - if (fptr == NULL) - return 0; - char *str; - char *str2; - - int did_something = 0; - - int lines_get = 0; while (1) { - str2 = calloc(MAX_FILE_SIZE, sizeof(char)); - if (!fgets(str2, MAX_FILE_SIZE, fptr)) { - free(str2); + str = calloc(MAX_FILE_SIZE, sizeof(char)); + if (!fgets(str, MAX_FILE_SIZE, fptr)) break; - } - lines_get++; + char *combo = strstr(str, ";"); + combo[0] = '\0'; + combo++; - str = calloc(strlen(str2) + 1, sizeof(char)); - strcpy(str, str2); - free(str2); - - did_something = 1; - - if (mode == 1) { - hashmap_set(m, str, strlen(str) - 1, (uintptr_t)1, 0); - continue; - } - - char *combo_o = strstr(str, ";"); - combo_o[0] = '\0'; - combo_o++; - - char *combo = calloc(strlen(combo_o) + 1, sizeof(char)); - strcpy(combo, combo_o); - - if (mode == 3) { - uintptr_t result; - hashmap_get(m, str, strlen(str) - 1, &result); - - char *res = (char *)result; - - if (res != 0) - continue; - } - - if (mode == 2 || mode == 3) { - hashmap_set(m, str, strlen(str) - 1, (uintptr_t)combo, 1); - continue; - } - - hashmap_set(m, combo, strlen(combo) - 1, (uintptr_t)str, 1); + hashmap_set(m, combo, strlen(combo) - 1, (uintptr_t)str); } - if (!did_something) { - free(str); - } - - fclose(fptr); - - return did_something; - // todo: properly free this } \ No newline at end of file diff --git a/src/loader.h b/src/loader.h index ad57b8d..2f1bdde 100644 --- a/src/loader.h +++ b/src/loader.h @@ -1,3 +1,2 @@ #include "map.h" -int load_elements(hashmap *m, char *table, int mode); -void write_elements(hashmap *m, char *table, int mode); \ No newline at end of file +void load_elements(hashmap *m, char *table); \ No newline at end of file diff --git a/src/main.c b/src/main.c index 0028bb7..463c8b7 100644 --- a/src/main.c +++ b/src/main.c @@ -1,158 +1,36 @@ -#include "command.h" -#include "loader.h" -#include "map.h" -#include #include #include #include #include +#include "command.h" +#include "loader.h" -#include "main.h" - -// todo: spacing in combos, clean init_tables - -void init_tables(hashmap *elements, hashmap *inv, hashmap *polls, - hashmap *elements_rev, int do_inv) { - load_elements(elements, "../elem_data/" COMBO_FILE, 0); - load_elements(elements, COMBO_FILE, 0) || - load_elements(elements, "bin/" COMBO_FILE, 0); - - load_elements(elements_rev, "../elem_data/" COMBO_FILE, 3); - load_elements(elements_rev, COMBO_FILE, 3) || - load_elements(elements_rev, "bin/" COMBO_FILE, 3); - - load_elements(polls, "../elem_data/" POLL_FILE, 3); - - if (!do_inv) - return; - - load_elements(inv, "../elem_data/" INV_BASE_FILE, 1) || - load_elements(inv, INV_BASE_FILE, 1) || - load_elements(inv, "bin/" INV_BASE_FILE, 1); - - load_elements(inv, INV_FILE, 1); -} +#define MAX_BUF_LENGTH 1024 +#define MAX_COMBO_LENGTH 1024 int main(int argc, char *argv[]) { - char *name; - if (argc < 2) { - name = "guest"; - } else { - name = argv[1]; - } - hashmap *elements = hashmap_create(); - hashmap *elements_rev = hashmap_create(); - hashmap *already_done = hashmap_create(); hashmap *inv = hashmap_create(); - hashmap *polls = hashmap_create(); char *command = calloc(MAX_BUF_LENGTH, sizeof(char)); char *command_re = calloc(MAX_BUF_LENGTH, sizeof(char)); char **sort_tmp = calloc(MAX_COMBO_LENGTH, sizeof(char **)); - int was_combination = 0; + load_elements(elements, "combos.txt"); - init_tables(elements, inv, polls, elements_rev, 1); - - printf("user:%s, welcome to Elemental on Command Line!\nType /help for " - "commands.\n", - name); - int newline = 1; while (1) { // todo: separate into functions - fflush(stdout); - if (newline) { - printf("\n"); - } - fgets(command, MAX_BUF_LENGTH - 1, stdin); - - int cl = strlen(command); - - if (cl < 2) { - newline = 0; - continue; - } else { - newline = 1; - } - - for (int i = 0; i < cl - 1; i++) { - command[i] = tolower(command[i]); - } - - if (was_combination && - suggest_command(command, command_re, polls, elements, name, was_combination)) { - continue; - } - was_combination = 0; - if (help_command(command)) - continue; - if (polls_command(command, polls, elements)) { - hashmap_free(polls); - polls = hashmap_create(); - init_tables(elements, inv, polls, elements_rev, 0); - } - if (slash_command(command, inv)) - continue; - - if (path_command(command, elements_rev, already_done, 1, inv)) - continue; - - int combos = get_command(command, command_re, sort_tmp); - - if (combos < 2) - continue; - - int failed = 0; - for (int i = 0; i < combos; i++) { - uintptr_t result; - hashmap_get(inv, sort_tmp[i], strlen(sort_tmp[i]), &result); - if (result != 1) { - printf("You don't have %s.\n", sort_tmp[i]); - failed = 1; - continue; - } - } + command_re = get_command(command, command_re, sort_tmp); uintptr_t result; hashmap_get(elements, command_re, strlen(command_re), &result); if (result == 0) { - init_tables(elements, inv, polls, elements_rev, 0); - - hashmap_get(elements, command_re, strlen(command_re), &result); - } - - if (result == 0 && failed) { - was_combination = 2; - printf("Use /suggest to upvote a pre-existing combination.\n"); + printf("You didn't make anything.\n"); continue; } - - if (result == 0) { - was_combination = 1; - printf("You didn't make anything; use /suggest to suggest an element.\n"); - continue; - } - - if (failed) - continue; - - char *res_str = (char *)result; - - uintptr_t result2; - - hashmap_get(inv, res_str, strlen(res_str), &result2); - - if (result2 == 1) { - printf("You made %s, but you already have it.\n", res_str); - continue; - } - - hashmap_set(inv, res_str, strlen(res_str), (uintptr_t)1, 0); - printf("You made %s!\n", res_str); - write_elements(inv, INV_FILE, 1); + printf("You made %s!\n", (char *)result); } // free(command); diff --git a/src/main.h b/src/main.h deleted file mode 100644 index 9e1ac77..0000000 --- a/src/main.h +++ /dev/null @@ -1,12 +0,0 @@ -#define INV_FILE "inv.txt" -#define INV_BASE_FILE "inv_base.txt" -#define COMBO_FILE "combos.txt" -#define POLL_FILE "polls.txt" -#define POLLS_LOCK_FILE "polls_lock.txt" -#define LB_FILE "lb.txt" - -#define MAX_BUF_LENGTH 1024 -#define MAX_COMBO_LENGTH 1024 -#define UPVOTE_IN 2 -#define MAX_FILE_SIZE 1024 -long stoi(const char *s); \ No newline at end of file diff --git a/src/map.c b/src/map.c index 6f0aad3..a93b2b4 100644 --- a/src/map.c +++ b/src/map.c @@ -2,11 +2,9 @@ // map.h // // Created by Mashpoe on 1/15/21. -// Modifications by BiglyDerv // #include "map.h" -#include #include #include @@ -169,8 +167,7 @@ static struct bucket *find_entry(hashmap *m, const void *key, size_t ksize, } } -int hashmap_set(hashmap *m, const void *key, size_t ksize, uintptr_t val, - int free_old) { +int hashmap_set(hashmap *m, const void *key, size_t ksize, uintptr_t val) { if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { if (hashmap_resize(m) == -1) return -1; @@ -189,16 +186,71 @@ int hashmap_set(hashmap *m, const void *key, size_t ksize, uintptr_t val, entry->ksize = ksize; entry->hash = hash; } - - if (free_old && entry->value != 0 && entry->value != val && - strcmp((char *)entry->value, (char *)val) == 0) { - free((char *)val); - return 0; - } entry->value = val; return 0; } +int hashmap_get_set(hashmap *m, const void *key, size_t ksize, + uintptr_t *out_in) { + if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { + if (hashmap_resize(m) == -1) + return -1; + } + + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + if (entry->key == NULL) { + m->last->next = entry; + m->last = entry; + entry->next = NULL; + + ++m->count; + + entry->value = *out_in; + entry->key = key; + entry->ksize = ksize; + entry->hash = hash; + + return 0; + } + *out_in = entry->value; + return 1; +} + +int hashmap_set_free(hashmap *m, const void *key, size_t ksize, uintptr_t val, + hashmap_callback c, void *usr) { + if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) { + if (hashmap_resize(m) == -1) + return -1; + } + + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + if (entry->key == NULL) { + m->last->next = entry; + m->last = entry; + entry->next = NULL; + + ++m->count; + + entry->key = key; + entry->ksize = ksize; + entry->hash = hash; + entry->value = val; + // there was no overwrite, exit the function. + return 0; + } + // allow the callback to free entry data. + // use old key and value so the callback can free them. + // the old key and value will be overwritten after this call. + int error = c(entry->key, ksize, entry->value, usr); + + // overwrite the old key pointer in case the callback frees it. + entry->key = key; + entry->value = val; + return error; +} + int hashmap_get(hashmap *m, const void *key, size_t ksize, uintptr_t *out_val) { uint32_t hash = hash_data(key, ksize); struct bucket *entry = find_entry(m, key, ksize, hash); @@ -209,6 +261,40 @@ int hashmap_get(hashmap *m, const void *key, size_t ksize, uintptr_t *out_val) { return entry->key != NULL ? 1 : 0; } +// doesn't "remove" the element per se, but it will be ignored. +// the element will eventually be removed when the map is resized. +void hashmap_remove(hashmap *m, const void *key, size_t ksize) { + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + + if (entry->key != NULL) { + + // "tombstone" entry is signified by a NULL key with a nonzero value + // element removal is optional because of the overhead of tombstone checks + entry->key = NULL; + entry->value = 0xDEAD; // I mean, it's a tombstone... + + ++m->tombstone_count; + } +} + +void hashmap_remove_free(hashmap *m, const void *key, size_t ksize, + hashmap_callback c, void *usr) { + uint32_t hash = hash_data(key, ksize); + struct bucket *entry = find_entry(m, key, ksize, hash); + + if (entry->key != NULL) { + c(entry->key, entry->ksize, entry->value, usr); + + // "tombstone" entry is signified by a NULL key with a nonzero value + // element removal is optional because of the overhead of tombstone checks + entry->key = NULL; + entry->value = 0xDEAD; // I mean, it's a tombstone... + + ++m->tombstone_count; + } +} + int hashmap_size(hashmap *m) { return m->count - m->tombstone_count; } int hashmap_iterate(hashmap *m, hashmap_callback c, void *user_ptr) { diff --git a/src/map.h b/src/map.h index ca305c8..b6543a9 100644 --- a/src/map.h +++ b/src/map.h @@ -2,7 +2,6 @@ // map.h // // Created by Mashpoe on 1/15/21. -// Modifications by BiglyDerv // #ifndef map_h @@ -11,6 +10,10 @@ #define hashmap_str_lit(str) (str), sizeof(str) - 1 #define hashmap_static_arr(arr) (arr), sizeof(arr) +// removal of map elements is disabled by default because of its slight +// overhead. if you want to enable this feature, uncomment the line below: +// #define __HASHMAP_REMOVABLE + #include #include @@ -40,17 +43,68 @@ struct hashmap { struct bucket *last; }; +// hashmaps can associate keys with pointer values or integral types. typedef struct hashmap hashmap; +// a callback type used for iterating over a map/freeing entries: +// `int (const void* key, size_t size, uintptr_t value, void* +// usr)` `usr` is a user pointer which can be passed through `hashmap_iterate`. typedef int (*hashmap_callback)(const void *key, size_t ksize, uintptr_t value, void *usr); hashmap *hashmap_create(void); + +// only frees the hashmap object and buckets. +// does not call free on each element's `key` or `value`. +// to free data associated with an element, call `hashmap_iterate`. void hashmap_free(hashmap *map); -int hashmap_set(hashmap *map, const void *key, size_t ksize, uintptr_t value, - int free_old); + +// does not make a copy of `key`. +// you must copy it yourself if you want to guarantee its lifetime, +// or if you intend to call `hashmap_key_free`. +// returns -1 on error. +int hashmap_set(hashmap *map, const void *key, size_t ksize, uintptr_t value); + +// adds an entry if it doesn't exist, using the value of `*out_in`. +// if it does exist, it sets value in `*out_in`, meaning the value +// of the entry will be in `*out_in` regardless of whether or not +// it existed in the first place. +// returns -1 on error. +// returns 1 if the entry already existed, returns 0 otherwise. +int hashmap_get_set(hashmap *map, const void *key, size_t ksize, + uintptr_t *out_in); + +// similar to `hashmap_set()`, but when overwriting an entry, +// you'll be able properly free the old entry's data via a callback. +// unlike `hashmap_set()`, this function will overwrite the original key +// pointer, which means you can free the old key in the callback if applicable. +int hashmap_set_free(hashmap *map, const void *key, size_t ksize, + uintptr_t value, hashmap_callback c, void *usr); + int hashmap_get(hashmap *map, const void *key, size_t ksize, uintptr_t *out_val); + +#ifdef __HASHMAP_REMOVABLE +void hashmap_remove(hashmap *map, const void *key, size_t ksize); + +// same as `hashmap_remove()`, but it allows you to free an entry's data first +// via a callback. +void hashmap_remove_free(hashmap *m, const void *key, size_t ksize, + hashmap_callback c, void *usr); +#endif + int hashmap_size(hashmap *map); + +// iterate over the map, calling `c` on every element. +// goes through elements in the order they were added. +// the element's key, key size, value, and `usr` will be passed to `c`. +// if `c` returns -1 the iteration is aborted. +// returns the last result of `c` int hashmap_iterate(hashmap *map, hashmap_callback c, void *usr); + +// dumps bucket info for debugging. +// allows you to see how many collisions you are getting. +// `0` is an empty bucket, `1` is occupied, and `x` is removed. +// void bucket_dump(hashmap *m); + #endif // map_h diff --git a/wrap.sh b/wrap.sh deleted file mode 100755 index dce3017..0000000 --- a/wrap.sh +++ /dev/null @@ -1,6 +0,0 @@ -make -CC=x86_64-w64-mingw32-gcc BIN=bin/elem.exe make -CC=i686-w64-mingw32-gcc BIN=bin/elem_32.exe make -strip --strip-all bin/elem -rm elem.tar.gz -tar -cvf elem.tar.gz bin \ No newline at end of file