Fully working leaderboards

This commit is contained in:
Edgaru089 2024-04-30 14:48:57 +08:00
parent d0beff76fe
commit c39adb2a46
9 changed files with 231 additions and 19 deletions

5
app.c
View File

@ -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);
}

18
app.h
View File

@ -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;

View File

@ -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 *));

View File

@ -1,5 +1,6 @@
#include "leaderboards.h"
#include "types.h"
#include <stdio.h>
@ -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);
}

View File

@ -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 {

5
ui.h
View File

@ -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);

View File

@ -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 <stdio.h>
#include <windows.h>
#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" <level_time> "Record Time" <record_time> <seperator> <replay_level> <return_to_title> <leaderboards> <sep> <next_level>
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];
// <level_time>
@ -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;
}
// <record_time>
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))
}

View File

@ -3,9 +3,35 @@
#include "ui.h"
#include <string.h>
#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,

View File

@ -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);