From c39adb2a4609d34ef82aad9eae6de8d23a5a90d8 Mon Sep 17 00:00:00 2001 From: Edgaru089 Date: Tue, 30 Apr 2024 14:48:57 +0800 Subject: [PATCH] Fully working leaderboards --- app.c | 5 ++ app.h | 18 ++++--- leaderboards.c | 6 +-- leaderboards_file.c | 53 ++++++++++++++++++ types.h | 7 +++ ui.h | 5 +- ui_build.c | 127 ++++++++++++++++++++++++++++++++++++++++++-- ui_label.c | 27 ++++++++++ ui_render.cpp | 2 +- 9 files changed, 231 insertions(+), 19 deletions(-) diff --git a/app.c b/app.c index cdb3212..54a32b1 100644 --- a/app.c +++ b/app.c @@ -4,6 +4,7 @@ #include "entity.h" #include "gametime.h" #include "input.h" +#include "leaderboards.h" #include "particle.h" #include "physics.h" #include "player.h" @@ -34,9 +35,12 @@ App *app_NewApp() { app->particle = particle_NewSystem(app); app->time = gametime_NewSystem(app); app->ui = ui_NewSystem(app); + app->lboard = lboard_NewSystem(app); ui_RebuildUI(app->ui); ui_PushState(app->ui, ui_TitleMenu); + lboard_LoadFromFile(app->lboard, "leaderboards.txt"); + app->current_level = NULL; app->switch_level = NULL; app->timescale = 1.0; @@ -58,6 +62,7 @@ void app_DeleteApp(App *app) { particle_DeleteSystem(app->particle); gametime_DeleteSystem(app->time); ui_DeleteSystem(app->ui); + lboard_DeleteSystem(app->lboard); free(app); } diff --git a/app.h b/app.h index 868c094..35f9baf 100644 --- a/app.h +++ b/app.h @@ -2,6 +2,7 @@ #include "entity.h" #include "gametime.h" +#include "leaderboards.h" #include "physics.h" #include "player.h" #include "input.h" @@ -19,14 +20,15 @@ extern "C" { typedef struct _App { void *window; // HWND pointer; window handle set by main() - System_Physics *physics; - System_Player *player; - System_Input *input; - System_Entity *entity; - System_Camera *camera; - System_Particle *particle; - System_GameTime *time; - System_UI *ui; + System_Physics *physics; + System_Player *player; + System_Input *input; + System_Entity *entity; + System_Camera *camera; + System_Particle *particle; + System_GameTime *time; + System_UI *ui; + System_Leaderboards *lboard; char *current_level; char *switch_level; diff --git a/leaderboards.c b/leaderboards.c index f94b249..5f4d856 100644 --- a/leaderboards.c +++ b/leaderboards.c @@ -25,7 +25,7 @@ void lboard_DeleteSystem(System_Leaderboards *sys) { // Free records for (int i = 0; i < vector_Size(sys->records); i++) { - tree_Tree *tree = *(tree_Tree **)vector_At(sys->levelnames, i); + tree_Tree *tree = *(tree_Tree **)vector_At(sys->records, i); for (tree_Node *node = tree_FirstNode(tree); node != NULL; node = tree_Node_Next(node)) { vector_Vector *names = *(vector_Vector **)node->data; if (names) { @@ -57,7 +57,7 @@ void lboard_Insert(System_Leaderboards *sys, const char *level, uintptr_t time_m // Append a new one char *level_copy = copy_malloc(level); tree_Tree *empty_tree = tree_Create(sizeof(vector_Vector *)); - vector_Push(sys->levelnames, level_copy); + vector_Push(sys->levelnames, &level_copy); vector_Push(sys->records, &empty_tree); ASSERT(vector_Size(sys->levelnames) == vector_Size(sys->records)); indice = vector_Size(sys->levelnames) - 1; @@ -67,7 +67,7 @@ void lboard_Insert(System_Leaderboards *sys, const char *level, uintptr_t time_m // Insert or find this node bool added; - tree_Node *node = tree_Insert(tree, time_millisec, &added); + tree_Node *node = tree_InsertNode(tree, time_millisec, &added); if (added) // Brand new node, create a new vector of names *((vector_Vector **)node->data) = vector_Create(sizeof(char *)); diff --git a/leaderboards_file.c b/leaderboards_file.c index 2abe05c..6faf3b6 100644 --- a/leaderboards_file.c +++ b/leaderboards_file.c @@ -1,5 +1,6 @@ #include "leaderboards.h" +#include "types.h" #include @@ -10,6 +11,9 @@ void lboard_LoadFromFile(System_Leaderboards *sys, const char *filename) { return; } + int indice = -1; + char *current_level = NULL; + // Read every line char linebuf[512]; @@ -21,7 +25,56 @@ void lboard_LoadFromFile(System_Leaderboards *sys, const char *filename) { // Skip empty lines if (strlen(linebuf) == 0) continue; + + // Look at the first char + // if '!', begin a new indice + if (linebuf[0] == '!') { + indice++; + + if (current_level != NULL) + free(current_level); + current_level = copy_malloc(&linebuf[1]); + continue; + } + + // Else, find the first space + // Should have a number in front, and name in the back + char *space_pos = strchr(linebuf, ' '); + if (!space_pos) + // Not found? continue + continue; + *space_pos = 0; + + uintptr_t time_millisec = atoll(linebuf); + lboard_Insert(sys, current_level, time_millisec, space_pos + 1); + INFO("Inserted @%s %d.%03ds [%s]", current_level, time_millisec / 1000, time_millisec % 1000, space_pos + 1); } + fclose(f); } void lboard_SaveToFile(System_Leaderboards *sys, const char *filename) { + FILE *f = fopen(filename, "w"); + if (!f) { + WARN("failed to open file\"%s\"", filename); + return; + } + + // Dump! + for (int i = 0; i < vector_Size(sys->records); i++) { + fprintf(f, "\n!%s\n", *(char **)vector_At(sys->levelnames, i)); + tree_Tree *tree = *(tree_Tree **)vector_At(sys->records, i); + INFO("Tree %s has %d nodes", *(char **)vector_At(sys->levelnames, i), tree_Count(tree)); + for (tree_Node *node = tree_FirstNode(tree); node != NULL; node = tree_Node_Next(node)) { + vector_Vector *names = *(vector_Vector **)node->data; + uintptr_t time_millisec = node->key; + if (names) { + INFO("Vector %d has %d names", time_millisec, vector_Size(names)); + for (int j = 0; j < vector_Size(names); j++) { + char *name = *(char **)vector_At(names, j); + fprintf(f, "%llu %s\n", (unsigned long long)time_millisec, name); + } + } + } + } + + fclose(f); } diff --git a/types.h b/types.h index ab9e6a3..821a479 100644 --- a/types.h +++ b/types.h @@ -37,6 +37,13 @@ static inline void *copy_malloc_size(void *src, size_t size) { return p; } +static inline char *copy_realloc(char *original, const char *src) { + size_t len = strlen(src); + char *p = (char *)realloc(original, len + 1); + memcpy(p, src, len + 1); + return p; +} + // A 2d vector of double. typedef struct { diff --git a/ui.h b/ui.h index 085b751..f7aa753 100644 --- a/ui.h +++ b/ui.h @@ -104,8 +104,9 @@ ui_Part ui_Button_NewLeftAligned(Box2 box, const char *label, ui_ActionCallback typedef struct { - char *label; // Allocated & copied, NULL means no label - int align; // <0: left aligned; ==0: center aligned; >0: right aligned + char *label; // Allocated & copied, NULL means no label + int align; // <0: left aligned; ==0: center aligned; >0: right aligned + uint32_t color; } ui_Label; ui_Part ui_Label_New(Box2 box, const char *label, int alignment); void ui_Label_SetLabel(ui_Part *part, const char *label); diff --git a/ui_build.c b/ui_build.c index 531e307..ca8ab59 100644 --- a/ui_build.c +++ b/ui_build.c @@ -1,15 +1,25 @@ +#include "leaderboards.h" #include "render_util.h" +#include "types.h" #include "ui.h" #include "app.h" +#include "util/tree.h" #include "util/vector.h" #include +#include #ifndef RGB #define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) #endif +#define UI_LEADERBOARDS_ITEM_COUNT (8) + + +// Defined in ui_label.c +void _ui_Label_UpdateShiny(System_UI *sys, ui_Part *part, uintptr_t user, Duration deltatime); + static void _ui_Action_ResumeGame(System_UI *sys, ui_Part *part, uintptr_t data) { ui_Action_PopState(sys, part, data); @@ -63,6 +73,48 @@ static void _ui_Action_EndIntermission(System_UI *sys, ui_Part *part, uintptr_t } } +static void _ui_Action_EnterLeaderboards(System_UI *sys, ui_Part *part, uintptr_t data) { + vector_Vector *parts = sys->parts[ui_Leaderboards]; +#define PART(i) ((ui_Part *)vector_At(parts, i)) + + int collected = 0; + int indice = lboard_FindLevel(sys->super->lboard, sys->super->current_level); + if (indice != -1) { + tree_Tree *tree = *(tree_Tree **)vector_At(sys->super->lboard->records, indice); + for (tree_Node *node = tree_FirstNode(tree); node != NULL; node = tree_Node_Next(node)) { + uintptr_t time_millisec = node->key; + vector_Vector *names = *(vector_Vector **)node->data; + if (names) + for (int i = 0; i < vector_Size(names); i++) { + char *name = *(char **)vector_At(names, i); + if (collected < UI_LEADERBOARDS_ITEM_COUNT) { + char buf[64]; + snprintf(buf, sizeof(buf), "%2d %s", collected + 1, name); + ((ui_Label *)PART(collected * 2)->user)->label = copy_realloc(((ui_Label *)PART(collected * 2)->user)->label, buf); + snprintf( + buf, sizeof(buf), + "%02d:%02d.%02d", + time_millisec / 1000 / 60, + time_millisec / 1000 % 60, + time_millisec / 10 % 100); + ((ui_Label *)PART(collected * 2 + 1)->user)->label = copy_realloc(((ui_Label *)PART(collected * 2 + 1)->user)->label, buf); + collected++; + } + } + } + } + + while (collected < UI_LEADERBOARDS_ITEM_COUNT) { + char buf[64]; + snprintf(buf, sizeof(buf), "%2d", collected + 1); + ((ui_Label *)PART(collected * 2)->user)->label = copy_realloc(((ui_Label *)PART(collected * 2)->user)->label, buf); + ((ui_Label *)PART(collected * 2 + 1)->user)->label = copy_realloc(((ui_Label *)PART(collected * 2 + 1)->user)->label, "-- N/A --"); + collected++; + } + + ui_Action_PushState(sys, part, (uintptr_t)ui_Leaderboards); +} + void ui_EnterIntermission(System_UI *sys, const char *next_level) { // Let's malloc & copy this next_level string @@ -71,10 +123,13 @@ void ui_EnterIntermission(System_UI *sys, const char *next_level) { ui_Part *last_part = vector_Back(sys->parts[ui_InterMission]); if (((ui_Button *)last_part->user)->callback_data) free((void *)((ui_Button *)last_part->user)->callback_data); - if (!next_level || strlen(next_level) == 0) + if (!next_level || strlen(next_level) == 0) { ((ui_Button *)last_part->user)->callback_data = 0; - else + ((ui_Button *)last_part->user)->label = copy_realloc(((ui_Button *)last_part->user)->label, "Return to Title"); + } else { ((ui_Button *)last_part->user)->callback_data = (uintptr_t)copy_malloc(next_level); + ((ui_Button *)last_part->user)->label = copy_realloc(((ui_Button *)last_part->user)->label, "Proceed to Next Level"); + } ui_PushState(sys, ui_InterMission); sys->super->paused = true; @@ -82,7 +137,7 @@ void ui_EnterIntermission(System_UI *sys, const char *next_level) { // 0 1 2 3 4 5 6 7 8 9 // "Level Time" "Record Time" vector_Vector *parts = sys->parts[ui_InterMission]; -#define PART(i) ((ui_Part *)vector_At(parts, i)) + // #define PART(i) ((ui_Part *)vector_At(parts, i)) char buf[64]; // @@ -93,6 +148,42 @@ void ui_EnterIntermission(System_UI *sys, const char *next_level) { sys->super->level_playtime.microseconds / 1000 / 1000 % 60, sys->super->level_playtime.microseconds / 10000 % 100); ui_Label_SetLabel(PART(1), buf); + + // Get the record time + uintptr_t record = 0; + int indice = lboard_FindLevel(sys->super->lboard, sys->super->current_level); + if (indice != -1) { + tree_Tree *tree = *(tree_Tree **)vector_At(sys->super->lboard->records, indice); + tree_Node *first = tree_FirstNode(tree); + if (first) + record = first->key; + } + + // + if (record) + snprintf( + buf, sizeof(buf), + "%02d:%02d.%02d", + record / 1000 / 60, + record / 1000 % 60, + record / 10 % 100); + else + strcpy(buf, "--:--.--"); + ui_Label_SetLabel(PART(3), buf); + + // Shiny if lower than record + if (record == 0 || sys->super->level_playtime.microseconds / 1000 < record) + PART(1)->update = &_ui_Label_UpdateShiny; + else { + PART(1)->update = NULL; + ((ui_Label *)PART(1)->user)->color = RGB(255, 255, 255); + } + + // Get user name + DWORD bufsize = sizeof(buf) - 1; + GetUserNameA(buf, &bufsize); + lboard_Insert(sys->super->lboard, sys->super->current_level, sys->super->level_playtime.microseconds / 1000, buf); + lboard_SaveToFile(sys->super->lboard, "leaderboards.txt"); } @@ -164,7 +255,7 @@ void ui_RebuildUI(System_UI *sys) { PUSH_UI(ui_Button_New( box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 250 + 2 * (40 + UI_PADDING)), vec2(400, 40)), "Leaderboard for This Level", - &ui_Action_PushState, (uintptr_t)ui_Leaderboards)) + &_ui_Action_EnterLeaderboards, 0)) PUSH_UI(ui_Button_New( box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 250 + 3 * (40 + UI_PADDING)), vec2(400, 40)), "Options", @@ -228,7 +319,7 @@ void ui_RebuildUI(System_UI *sys) { PUSH_UI(ui_Button_New( ui_BoxBuilder_At(builder, 5, 1), "View Leaderboards for Level", - &ui_Action_PushState, (uintptr_t)ui_Leaderboards)) + &_ui_Action_EnterLeaderboards, 0)) PUSH_UI(ui_Fill_New( box2v( vec2_Add( @@ -240,8 +331,34 @@ void ui_RebuildUI(System_UI *sys) { ui_BoxBuilder_At(builder, 7, 1), "Proceed to Next Level", &_ui_Action_EndIntermission, 0)) + ui_BoxBuilder_Delete(builder); + // Leaderboards p = sys->parts[ui_Leaderboards]; vector_Clear(p); + + builder = ui_BoxBuilder_New(); + builder->line_height = 28; + builder->pivot = vec2(0.5, 0.5625); + builder->position = vec2(SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 2.0); + for (int i = 1; i <= UI_LEADERBOARDS_ITEM_COUNT; i++) + ui_BoxBuilder_SetCount(builder, i, 1); + ui_BoxBuilder_SetCount(builder, UI_LEADERBOARDS_ITEM_COUNT + 1, 1); + ui_BoxBuilder_Assemble(builder); + + for (int i = 1; i <= UI_LEADERBOARDS_ITEM_COUNT; i++) { + char buf[64]; + snprintf(buf, sizeof(buf), "%2d", i); + PUSH_UI(ui_Label_New( + ui_BoxBuilder_At(builder, i, 1), + buf, -1)) + PUSH_UI(ui_Label_New( + ui_BoxBuilder_At(builder, i, 1), + "-- N/A --", 1)) + } + PUSH_UI(ui_Button_New( + ui_BoxBuilder_At(builder, UI_LEADERBOARDS_ITEM_COUNT + 1, 1), + "Return", + &ui_Action_PopState, 0)) } diff --git a/ui_label.c b/ui_label.c index acb0704..9a021fa 100644 --- a/ui_label.c +++ b/ui_label.c @@ -3,9 +3,35 @@ #include "ui.h" #include +#ifndef RGB +#define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) +#endif + void _ui_Fill_Draw(System_UI *sys, ui_Part *part, uintptr_t user); // Defined in ui_render.cpp void _ui_Label_Draw(System_UI *sys, ui_Part *part, uintptr_t user); // Defined in ui_render.cpp +void _ui_Label_UpdateShiny(System_UI *sys, ui_Part *part, uintptr_t user, Duration deltatime) { + ui_Label *label = (ui_Label *)part->user; + // Compute the new color + int r, g, b; + double now = time_Now().microseconds / 1e6; + double cycle = fmod(now, 3.0); + double prog = fmod(now, 1.0); + if (cycle < 1.0) { + r = 255; + g = 255 * prog; + b = 255 * (1 - prog); + } else if (cycle < 2.0) { + r = 255 * (1 - prog); + g = 255; + b = 255 * prog; + } else { + r = 255 * prog; + g = 255 * (1 - prog); + b = 255; + } + label->color = RGB(r, g, b); +} void _ui_Label_Free(System_UI *sys, ui_Part *part, uintptr_t user) { ui_Label *label = (ui_Label *)part->user; if (label->label) @@ -17,6 +43,7 @@ ui_Part ui_Label_New(Box2 box, const char *label_text, int alignment) { if (label_text && strlen(label_text) > 0) label->label = copy_malloc(label_text); label->align = alignment; + label->color = RGB(255, 255, 255); ui_Part part = { .box = box, diff --git a/ui_render.cpp b/ui_render.cpp index c3fbc19..c2f1600 100644 --- a/ui_render.cpp +++ b/ui_render.cpp @@ -92,7 +92,7 @@ extern "C" void _ui_Label_Draw(System_UI *sys, ui_Part *part, uintptr_t user) { padded_box.size.x -= 2 * UI_PADDING; setbkcolor(0); - settextcolor(RGB(255, 255, 255)); + settextcolor(label->color); if (label->align < 0) render_DrawTextEx(label->label, padded_box, DT_LEFT | DT_VCENTER | DT_SINGLELINE);