From f55db7a7fd829d3e14043ed8312442df19376776 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Thu, 3 Apr 2025 13:29:03 -0400 Subject: [PATCH 01/14] fix polls randomly coiming back --- src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index 69f9365..a82e4d2 100644 --- a/src/main.c +++ b/src/main.c @@ -20,7 +20,7 @@ void init_tables(hashmap *elements, hashmap *inv, hashmap *polls, load_elements(elements_rev, combo_file, 3) || load_elements(elements_rev, "bin/" combo_file, 3); - load_elements(polls, "../elem_data/" poll_file, 2); + load_elements(polls, "../elem_data/" poll_file, 3); if (!do_inv) return; From bab758e93f70e34e0cf428b0b2041c523ee7956f Mon Sep 17 00:00:00 2001 From: biglyderv Date: Thu, 3 Apr 2025 13:46:08 -0400 Subject: [PATCH 02/14] avoid duping polls --- src/command.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/command.c b/src/command.c index e03572f..24a7776 100644 --- a/src/command.c +++ b/src/command.c @@ -80,7 +80,7 @@ int inv_handler(const void *key, size_t size, uintptr_t val, void *usr) { if (val2[strlen(val2) - 1] == '\n') { printf("- user:%s suggested %s", key3, val2); } else { - printf("- user:%s suggested %s\n", key3, val2); + printf("- user:%s suggested %s\n", key3, val2); } free(key3); @@ -103,21 +103,28 @@ int inv_handler(const void *key, size_t size, uintptr_t val, void *usr) { int polls_handler(const void *key, size_t size, uintptr_t val, void *usr) { struct verifier *verified = (struct verifier *)usr; - if (((char *)val)[strlen(((char *)val)) - 1] == '\n') { - ((char *)val)[strlen(((char *)val)) - 1] = '\0'; + 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, (char *)val) == 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 *val2 = (char *)val; if ((char *)val != verified->sugg && val2[strlen(val2) - 1] == '\n') { val2[strlen(val2) - 1] = '\0'; From 1f70dd700061a609c1a1154cd6c600cd97e71a8e Mon Sep 17 00:00:00 2001 From: biglyderv Date: Thu, 3 Apr 2025 13:48:03 -0400 Subject: [PATCH 03/14] ugh --- src/command.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/command.c b/src/command.c index 24a7776..4ac7293 100644 --- a/src/command.c +++ b/src/command.c @@ -125,15 +125,19 @@ 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 ((char *)val != verified->sugg && val2[strlen(val2) - 1] == '\n') { - val2[strlen(val2) - 1] = '\0'; + if (val3[strlen(val3) - 1] == '\n') { + val3[strlen(val3) - 1] = '\0'; } - if (strcmp(verified->sugg, (char *)val) == 0) { + if (strcmp(verified->sugg, val3) == 0) { verified->points[0]++; } + free(val3); + return 0; } From 090d4d5d8d8d41501586e905139a14fb05bf97b6 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Sat, 5 Apr 2025 15:49:21 -0400 Subject: [PATCH 04/14] i put this in the wrong place so now it doesn't actually reduce the size --- wrap.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrap.sh b/wrap.sh index a15f60a..dce3017 100755 --- a/wrap.sh +++ b/wrap.sh @@ -1,6 +1,6 @@ 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 -strip --strip-all bin/elem \ No newline at end of file +tar -cvf elem.tar.gz bin \ No newline at end of file From 5a6bb6e982afc8ec0bc65368c2550bea773b10c3 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Mon, 7 Apr 2025 11:04:24 -0400 Subject: [PATCH 05/14] improve the guide --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index f95e7a8..773e060 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,18 @@ # 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/) for a demo of the multiplayer version +- [Original client](#multi-player) 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 +*This guide is outdated!* + To set up multi-player mode, create a directory ``../elem_data``. Also, create separate directories for each user that plays. The game can now be played with the following syntax, inside your user directory: From 6b5452c923af2404080143a30b2ef0d234c4b6d4 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Wed, 9 Apr 2025 12:12:49 -0400 Subject: [PATCH 06/14] clarity; link to the releases page --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 773e060..7aa5d8d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This is an elemental combination game written in C, with optional multi-player s - [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/) for a demo of the multiplayer version -- [Original client](#multi-player) for developers, administrators, and command line fans +- [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. From d0553e657548d77d8dd94d7765b9e5da80df38f5 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Wed, 9 Apr 2025 12:14:48 -0400 Subject: [PATCH 07/14] redirect guide --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index 7aa5d8d..b2904f8 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,4 @@ This is an elemental combination game written in C, with optional multi-player s 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 -*This guide is outdated!* - -To set up multi-player mode, create a directory ``../elem_data``. Also, create separate directories for each user that plays. - -The game can now be played with the following syntax, inside your user directory: -```sh -cd ../your_user_dir -../elemental-command-line/bin/elem your_username -``` \ No newline at end of file +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 From 881d3602e7b5db3e430e065f5066a090065641c9 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Sat, 12 Apr 2025 09:44:14 -0400 Subject: [PATCH 08/14] clean up I/O database --- .gitignore | 3 ++- src/command.c | 22 +++++-------------- src/command.h | 2 +- src/loader.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++---- src/loader.h | 3 ++- src/main.c | 34 +++++++++++----------------- src/main.h | 12 +++++----- 7 files changed, 87 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 7ac3b40..97fedc6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /inv.txt /inv_users.txt /polls.txt -/elem.tar.gz \ No newline at end of file +/elem.tar.gz +/combos.txt \ No newline at end of file diff --git a/src/command.c b/src/command.c index 4ac7293..c89104e 100644 --- a/src/command.c +++ b/src/command.c @@ -4,7 +4,7 @@ #include #include #include - +#include "loader.h" #include "main.h" // huge cleanup operation soon @@ -159,7 +159,7 @@ int handle_pages_int(char *command, char *invs) { } } -int suggest_command(char *command, char *command_re, hashmap *polls, char *name, +int suggest_command(char *command, char *command_re, hashmap *polls, hashmap *combos, char *name, int was_combination) { char *page = handle_pages(command, "/suggest "); @@ -204,12 +204,9 @@ int suggest_command(char *command, char *command_re, hashmap *polls, char *name, printf("Poll was added into the game!\n"); FILE *fptr; - fptr = fopen("../elem_data/" combo_file, "a"); - if (fptr == NULL) - return 1; - fwrite(val, sizeof(char), strlen(val), fptr); - fwrite("\n", sizeof(char), 1, fptr); - fclose(fptr); + hashmap_set(combos, command_re, strlen(command_re), (uintptr_t)page, 0); + + write_elements(combos, "../elem_data/" COMBO_FILE, 0); return 1; } @@ -219,14 +216,7 @@ int suggest_command(char *command, char *command_re, hashmap *polls, char *name, FILE *fptr; - fptr = fopen("../elem_data/" poll_file, "a"); - if (fptr == NULL) - return 1; - fwrite(key, sizeof(char), strlen(key), fptr); - fwrite(";", sizeof(char), 1, fptr); - fwrite(val, sizeof(char), strlen(val), fptr); - fwrite("\n", sizeof(char), 1, fptr); - fclose(fptr); + write_elements(polls, "../elem_data/" POLL_FILE, 3); return 1; } diff --git a/src/command.h b/src/command.h index bba66a4..cdd78bf 100644 --- a/src/command.h +++ b/src/command.h @@ -2,7 +2,7 @@ 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, - char *name, int was_combination); + 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, diff --git a/src/loader.c b/src/loader.c index 67c3efe..28668f5 100644 --- a/src/loader.c +++ b/src/loader.c @@ -6,7 +6,56 @@ #include "main.h" -int load_elements(hashmap *m, char *table, int use_inv) { +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); + 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) { FILE *fptr; fptr = fopen(table, "r"); @@ -19,6 +68,8 @@ int load_elements(hashmap *m, char *table, int use_inv) { int did_something = 0; + int lines_get = 0; + while (1) { str2 = calloc(MAX_FILE_SIZE, sizeof(char)); if (!fgets(str2, MAX_FILE_SIZE, fptr)) { @@ -26,13 +77,15 @@ int load_elements(hashmap *m, char *table, int use_inv) { break; } + lines_get++; + str = calloc(strlen(str2) + 1, sizeof(char)); strcpy(str, str2); free(str2); did_something = 1; - if (use_inv == 1) { + if (mode == 1) { hashmap_set(m, str, strlen(str) - 1, (uintptr_t)1, 0); continue; } @@ -44,7 +97,7 @@ int load_elements(hashmap *m, char *table, int use_inv) { char *combo = calloc(strlen(combo_o) + 1, sizeof(char)); strcpy(combo, combo_o); - if (use_inv == 3) { + if (mode == 3) { uintptr_t result; hashmap_get(m, str, strlen(str) - 1, &result); @@ -54,7 +107,7 @@ int load_elements(hashmap *m, char *table, int use_inv) { continue; } - if (use_inv == 2 || use_inv == 3) { + if (mode == 2 || mode == 3) { hashmap_set(m, str, strlen(str) - 1, (uintptr_t)combo, 1); continue; } diff --git a/src/loader.h b/src/loader.h index 116d3ad..ad57b8d 100644 --- a/src/loader.h +++ b/src/loader.h @@ -1,2 +1,3 @@ #include "map.h" -int load_elements(hashmap *m, char *table, int use_inv); \ No newline at end of file +int load_elements(hashmap *m, char *table, int mode); +void write_elements(hashmap *m, char *table, int mode); \ No newline at end of file diff --git a/src/main.c b/src/main.c index a82e4d2..67e3c96 100644 --- a/src/main.c +++ b/src/main.c @@ -12,24 +12,24 @@ 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, "../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(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); + 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, "../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); + load_elements(inv, INV_FILE, 1); } int main(int argc, char *argv[]) { @@ -81,7 +81,7 @@ int main(int argc, char *argv[]) { } if (was_combination && - suggest_command(command, command_re, polls, name, was_combination)) { + suggest_command(command, command_re, polls, elements, name, was_combination)) { continue; } was_combination = 0; @@ -148,15 +148,7 @@ int main(int argc, char *argv[]) { hashmap_set(inv, res_str, strlen(res_str), (uintptr_t)1, 0); printf("You made %s!\n", res_str); - - FILE *fptr; - - fptr = fopen(inv_file, "a"); - if (fptr == NULL) - continue; - fwrite(res_str, sizeof(char), strlen(res_str), fptr); - fwrite("\n", sizeof(char), 1, fptr); - fclose(fptr); + write_elements(inv, INV_FILE, 1); } // free(command); diff --git a/src/main.h b/src/main.h index 7761ac3..9e1ac77 100644 --- a/src/main.h +++ b/src/main.h @@ -1,9 +1,9 @@ -#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 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 From be291628b81efe26989c2a25f59d9f169a0d4f39 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Mon, 14 Apr 2025 20:36:53 -0400 Subject: [PATCH 09/14] fix weird interference with polls --- src/command.c | 3 +++ src/main.c | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/command.c b/src/command.c index c89104e..aab75ef 100644 --- a/src/command.c +++ b/src/command.c @@ -1,4 +1,5 @@ #include "map.h" +#include #include #include #include @@ -177,6 +178,8 @@ int suggest_command(char *command, char *command_re, hashmap *polls, hashmap *co 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()); diff --git a/src/main.c b/src/main.c index 67e3c96..0028bb7 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ #include "command.h" #include "loader.h" +#include "map.h" #include #include #include @@ -87,8 +88,11 @@ int main(int argc, char *argv[]) { was_combination = 0; if (help_command(command)) continue; - if (polls_command(command, polls, elements)) - 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; From 9b196f0b1328589fdb148df9640551f7c16c5c34 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Thu, 17 Apr 2025 09:27:13 -0400 Subject: [PATCH 10/14] fix weird newline issue --- src/loader.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/loader.c b/src/loader.c index 28668f5..a41a19d 100644 --- a/src/loader.c +++ b/src/loader.c @@ -26,7 +26,9 @@ int entry_handler(const void *key, size_t size, uintptr_t val, void *usr) { } } else if (usr2->mode == 1) { fwrite(key2, sizeof(char), strlen(key2), usr2->fptr); - fwrite("\n", sizeof(char), 1, 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); From fac2179c233ba65bda87447154bdeb6ab4376128 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Thu, 17 Apr 2025 09:43:20 -0400 Subject: [PATCH 11/14] hopefully fix the paging issue --- src/command.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/command.c b/src/command.c index aab75ef..749cbdc 100644 --- a/src/command.c +++ b/src/command.c @@ -143,8 +143,13 @@ int success_handler(const void *key, size_t size, uintptr_t val, void *usr) { } char *handle_pages(char *command, char *invs) { + char *data; + char *data2; if (strncmp(command, invs, strlen(invs)) == 0) { - return &command[strlen(invs)]; + data = &command[strlen(invs)]; + data2 = malloc(strlen(data) + 1); + memcpy(data2,data,strlen(data) + 1); + return data2; } else { return 0; } From 692688dbbdcc635ba075d9c9db73c30bd8d54127 Mon Sep 17 00:00:00 2001 From: biglyderv Date: Sun, 20 Apr 2025 20:10:50 -0400 Subject: [PATCH 12/14] fix url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2904f8..c7ad847 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is an elemental combination game written in C, with optional multi-player s ## 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/) for a demo of the multiplayer version +- [Web client](https://elem.dervland.net/game/) 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 From c7cbcd39ad805c66f20f21753fffb54c14ce7ebb Mon Sep 17 00:00:00 2001 From: biglyderv Date: Sun, 20 Apr 2025 20:11:10 -0400 Subject: [PATCH 13/14] nvm --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c7ad847..b2904f8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is an elemental combination game written in C, with optional multi-player s ## 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/game/) for a demo of the multiplayer version +- [Web client](https://elem.dervland.net/) 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 From 40e38578bb60457997ef6d3f6c69e611bb7ebd1c Mon Sep 17 00:00:00 2001 From: biglyderv Date: Sun, 20 Apr 2025 20:16:30 -0400 Subject: [PATCH 14/14] c --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2904f8..d5d6b6f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is an elemental combination game written in C, with optional multi-player s ## 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/) for a demo of the multiplayer version +- [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