End-of-game scores are POSTed to a configurable backend URL; the main menu
gains a "Global Leaderboard" entry that fetches and renders the top N for
the current board. Default `global_leaderboard=0` ships the code dormant
until the backend at leaderboard_url is live.
Architecture:
- httpclient.h: thin async interface. http_post_json / http_get fire and
forget; the user callback runs from http_pump() (native) or the
Emscripten runtime (web). No blocking calls.
- httpclient_native.cpp: libcurl-multi, polled per frame.
- httpclient_web.cpp: emscripten_fetch (-sFETCH=1 link option).
- leaderboard.{h,cpp}: portable game-side module. Hand-rolled JSON
build/parse for the tiny fixed-shape wire format; no JSON dep.
Identity: player name + a player-typed "board name" (shared room code).
A per-install RFC-4122-v4 UUID `client_id` is auto-generated on first run
via SDL_rand_bits and stored in the config — server uses it for rate
limiting / dedup, not as a public identity.
Wire format (informational, server is out-of-tree):
POST /api/scores body: {board, name, client_id, score, skill,
gametype, end_scene, end_lives, end_health,
playtime, start_date, end_date}
resp: {rank, total}
GET /api/scores?board=&limit=50
resp: {board, total, scores:[{name,score,end_scene}]}
Hook points:
- manage.cpp game_start: populate hi.start_date; reset_submission().
- manage.cpp game_stop: populate hi.end_date and the profile name on hi,
then leaderboard::submit_score after scorefile.record.
- kobo.cpp main: http_init/http_shutdown around the run; generate
client_id on first run when empty.
- kobo.cpp kobo_gfxengine_t::frame: http_pump() before gsm.frame().
- states.cpp st_game_over post_render: renders "Submitting…" / "Rank #N of M"
/ "(offline)" under "GAME OVER".
- states.cpp main_menu: "Global Leaderboard" button (only when enabled
+ board name set).
- states.cpp st_global_leaderboard_t: new browser state listing top 16.
- options.cpp game_options: onoff toggle.
Out of scope for this commit (left as follow-ups):
- In-game text editor for board name / URL (form toolkit has no free-text
widget; users edit via config file or CLI flags for now).
- Retry queue across sessions.
- Server implementation. tools/mock-leaderboard.py is a 100-line Python
scratch server for local testing only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
560 lines
13 KiB
C++
560 lines
13 KiB
C++
/*(GPL)
|
|
------------------------------------------------------------
|
|
Kobo Deluxe - An enhanced SDL port of XKobo
|
|
------------------------------------------------------------
|
|
* Copyright (C) 2001-2003 David Olofson
|
|
* Copyright (C) 2002 Jeremy Sheeley
|
|
* Copyright (C) 2007 David Olofson
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#ifndef _KOBO_STATES_H_
|
|
#define _KOBO_STATES_H_
|
|
|
|
#include "gamestate.h"
|
|
#include "gamectl.h"
|
|
#include "form.h"
|
|
#include "kobo.h"
|
|
#include "options.h"
|
|
|
|
#define MENU_TAG_OK 99
|
|
#define MENU_TAG_CANCEL 100
|
|
|
|
|
|
class kobo_basestate_t : public gamestate_t
|
|
{
|
|
public:
|
|
kobo_basestate_t();
|
|
|
|
void frame();
|
|
void pre_render();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Intro
|
|
----------------------------------------------------------*/
|
|
|
|
class st_introbase_t : public kobo_basestate_t
|
|
{
|
|
protected:
|
|
int timer, start_time;
|
|
public:
|
|
int duration;
|
|
st_introbase_t *inext;
|
|
st_introbase_t();
|
|
void enter();
|
|
void reenter();
|
|
void press(int button);
|
|
void frame();
|
|
void pre_render();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
class st_intro_title_t : public st_introbase_t
|
|
{
|
|
public:
|
|
int mode;
|
|
st_intro_title_t();
|
|
void enter();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
class st_intro_instructions_t : public st_introbase_t
|
|
{
|
|
public:
|
|
st_intro_instructions_t();
|
|
void enter();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
class st_intro_highscores_t : public st_introbase_t
|
|
{
|
|
public:
|
|
st_intro_highscores_t();
|
|
void enter();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
class st_intro_credits_t : public st_introbase_t
|
|
{
|
|
public:
|
|
st_intro_credits_t();
|
|
void enter();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
In-game
|
|
----------------------------------------------------------*/
|
|
|
|
class st_game_t : public kobo_basestate_t
|
|
{
|
|
public:
|
|
st_game_t();
|
|
void enter();
|
|
void leave();
|
|
void yield();
|
|
void reenter();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
class st_pause_game_t : public kobo_basestate_t
|
|
{
|
|
public:
|
|
st_pause_game_t();
|
|
void enter();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
class st_get_ready_t : public kobo_basestate_t
|
|
{
|
|
int start_time, frame_time;
|
|
int countdown;
|
|
public:
|
|
st_get_ready_t();
|
|
void enter();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
class st_game_over_t : public kobo_basestate_t
|
|
{
|
|
int start_time, frame_time;
|
|
public:
|
|
st_game_over_t();
|
|
void enter();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Menu Base
|
|
----------------------------------------------------------*/
|
|
|
|
/*
|
|
* Alt 1:
|
|
* Derive from this and override build()
|
|
*
|
|
* The tags of the widgets registered by build() will be
|
|
* passed to select() of your st_menu_base_t derivate when
|
|
* the widgets are activated.
|
|
*
|
|
* BTN_EXIT and BTN_CLOSE will both have the same effect
|
|
* as activating a widget with tags 0 - which means that
|
|
* tags 0 is effectively the universal "exit code". Handle
|
|
* this event if you like, but keep in mind that the menu
|
|
* will be closed and destroyed, and the state popped
|
|
* automatically afterwards.
|
|
*
|
|
* Alt 2: (For advanced custom forms.)
|
|
* Ignore menu_base_t and plug in any other kobo_form_t
|
|
* derivate instead.
|
|
*
|
|
* Your custom form is expected to handle input via the
|
|
* normal select()/change() API of ct_form_t, and widget
|
|
* tags are passed to st_menu_base_t::select() whenever a
|
|
* widget is activated.
|
|
*
|
|
* If your custom form doesn't use tags the way
|
|
* st_menu_base_t expects, override translate() to translate
|
|
* according to these rules:
|
|
* No action: -1
|
|
* Exit/close/cancel: 0
|
|
* Any other functions: >0
|
|
*
|
|
* Either way:
|
|
* Extra communication between the st_menu_base_t::select()
|
|
* handler and your custom menu will have to be implemented
|
|
* in custom ways. See the options forms for examples.
|
|
*
|
|
* Of course, other virtual methods may still be overridden
|
|
* just as with the normal Kobo forms and states this
|
|
* wrapper inherits from - just keep in mind to call the
|
|
* inherited versions to keep things working!
|
|
*
|
|
* If you want to control event sounds yourself, set the
|
|
* 'sounds' field to 0. (Inside open() is soon enough.)
|
|
*/
|
|
class menu_base_t : public kobo_form_t
|
|
{
|
|
public:
|
|
void open();
|
|
void close();
|
|
};
|
|
|
|
/*
|
|
* Derive from this and override open(), close() and select().
|
|
*
|
|
* open() should instantiate, open and return your
|
|
* kobo_form_t derivate.
|
|
*
|
|
* close() should clean up any extra stuff. (The form will be
|
|
* deleted in st_menu_base_t's destructor.)
|
|
*
|
|
* select() is your "widget activated" event handler.
|
|
*
|
|
* translate() is optional, and is used for translating
|
|
* widget a tag depending on, for example, what key is used to
|
|
* "activate" the widget. translate() is plugged in before
|
|
* select(), and is expected to return "new" tag codes that
|
|
* are passed on to select().
|
|
*
|
|
* Default actions:
|
|
* st_menu_base_t implements default widget actions for
|
|
* some keys. These actions are performed after
|
|
* translate() is called, but translate() may temporarily
|
|
* disable this feature by setting the do_default_action
|
|
* member to 0.
|
|
*
|
|
* Override escape() if you want to do something special when
|
|
* the menu wants to exit the "oops, forget about this" way.
|
|
* Note that this is *not* called if your select() handler
|
|
* explicitly makes the menu exit. Also note that select()
|
|
* will still be called with a 0 argument after escape() has
|
|
* been called.
|
|
*/
|
|
class st_menu_base_t : public kobo_basestate_t
|
|
{
|
|
protected:
|
|
kobo_form_t *form;
|
|
int sounds;
|
|
int do_default_action;
|
|
public:
|
|
st_menu_base_t();
|
|
~st_menu_base_t();
|
|
void enter();
|
|
void reenter();
|
|
void leave();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
|
|
virtual kobo_form_t *open() = 0;
|
|
virtual void close() { ; }
|
|
virtual int translate(int tag, int button);
|
|
virtual void select(int tag) = 0;
|
|
virtual void escape() { ; }
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Main Menu
|
|
----------------------------------------------------------*/
|
|
|
|
class main_menu_t : public menu_base_t
|
|
{
|
|
public:
|
|
int start_level;
|
|
virtual void buildStartLevel(int profNum);
|
|
void build();
|
|
void rebuild();
|
|
};
|
|
|
|
class st_main_menu_t : public st_menu_base_t
|
|
{
|
|
main_menu_t *menu;
|
|
public:
|
|
st_main_menu_t() { name = "main_menu"; }
|
|
kobo_form_t *open();
|
|
void reenter();
|
|
int translate(int tag, int button);
|
|
void select(int tag);
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Skill Menu
|
|
----------------------------------------------------------*/
|
|
|
|
class skill_menu_t : public menu_base_t
|
|
{
|
|
int skill;
|
|
public:
|
|
void set_skill(int sk)
|
|
{
|
|
skill = sk;
|
|
};
|
|
void build();
|
|
void rebuild();
|
|
};
|
|
|
|
class st_skill_menu_t : public st_menu_base_t
|
|
{
|
|
skill_menu_t *menu;
|
|
public:
|
|
st_skill_menu_t() { name = "skill_menu"; }
|
|
kobo_form_t *open();
|
|
void press(int button);
|
|
void select(int tag);
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Enter name
|
|
----------------------------------------------------------*/
|
|
class new_player_t : public kobo_form_t
|
|
{
|
|
public:
|
|
int editing;
|
|
char name[60];
|
|
unsigned currentIndex;
|
|
int selection;
|
|
virtual void change(int delta);
|
|
virtual void build();
|
|
void open();
|
|
void close();
|
|
void rebuild();
|
|
};
|
|
|
|
class st_new_player_t : public kobo_basestate_t
|
|
{
|
|
public:
|
|
new_player_t menu;
|
|
st_new_player_t();
|
|
void enter();
|
|
void leave();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Options Main
|
|
----------------------------------------------------------*/
|
|
class options_main_t : public menu_base_t
|
|
{
|
|
public:
|
|
void build();
|
|
};
|
|
|
|
class st_options_main_t : public st_menu_base_t
|
|
{
|
|
public:
|
|
st_options_main_t() { name = "options_main"; }
|
|
kobo_form_t *open();
|
|
void select(int tag);
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Options Base
|
|
----------------------------------------------------------*/
|
|
class st_options_base_t : public st_menu_base_t
|
|
{
|
|
config_form_t *cfg_form;
|
|
public:
|
|
st_options_base_t()
|
|
{
|
|
cfg_form = NULL;
|
|
}
|
|
virtual config_form_t *oopen() = 0;
|
|
kobo_form_t *open();
|
|
void enter();
|
|
void close();
|
|
void select(int tag);
|
|
void press(int button);
|
|
void escape();
|
|
};
|
|
|
|
/*----------------------------------------------------------
|
|
Options Submenues
|
|
----------------------------------------------------------*/
|
|
class st_options_system_t : public st_options_base_t
|
|
{
|
|
public:
|
|
st_options_system_t() { name = "options_system"; }
|
|
config_form_t *oopen() { return new system_options_t; }
|
|
};
|
|
|
|
class st_options_video_t : public st_options_base_t
|
|
{
|
|
public:
|
|
st_options_video_t() { name = "options_video"; }
|
|
config_form_t *oopen() { return new video_options_t; }
|
|
};
|
|
|
|
class st_options_graphics_t : public st_options_base_t
|
|
{
|
|
public:
|
|
st_options_graphics_t() { name = "options_graphics"; }
|
|
config_form_t *oopen() { return new graphics_options_t; }
|
|
};
|
|
|
|
class st_options_audio_t : public st_options_base_t
|
|
{
|
|
public:
|
|
st_options_audio_t() { name = "options_audio"; }
|
|
config_form_t *oopen() { return new audio_options_t; }
|
|
};
|
|
|
|
class st_options_control_t : public st_options_base_t
|
|
{
|
|
public:
|
|
st_options_control_t() { name = "options_control"; }
|
|
config_form_t *oopen() { return new control_options_t; }
|
|
};
|
|
|
|
class st_options_game_t : public st_options_base_t
|
|
{
|
|
public:
|
|
st_options_game_t() { name = "options_game"; }
|
|
config_form_t *oopen() { return new game_options_t; }
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Requesters
|
|
----------------------------------------------------------*/
|
|
|
|
class yesno_menu_t : public menu_base_t
|
|
{
|
|
public:
|
|
void build();
|
|
void rebuild();
|
|
};
|
|
|
|
class st_yesno_base_t : public st_menu_base_t
|
|
{
|
|
yesno_menu_t *menu;
|
|
protected:
|
|
const char *msg;
|
|
public:
|
|
kobo_form_t *open();
|
|
void reenter();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
};
|
|
|
|
class st_ask_exit_t : public st_yesno_base_t
|
|
{
|
|
public:
|
|
st_ask_exit_t();
|
|
void select(int tag);
|
|
};
|
|
|
|
class st_ask_abort_game_t : public st_yesno_base_t
|
|
{
|
|
public:
|
|
st_ask_abort_game_t();
|
|
void select(int tag);
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Global Leaderboard browser
|
|
----------------------------------------------------------*/
|
|
|
|
class st_global_leaderboard_t : public kobo_basestate_t
|
|
{
|
|
int start_time;
|
|
public:
|
|
st_global_leaderboard_t();
|
|
void enter();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Debug: Audio Engine Profiling
|
|
----------------------------------------------------------*/
|
|
|
|
extern "C" {
|
|
#include "a_globals.h"
|
|
};
|
|
|
|
#ifdef PROFILE_AUDIO
|
|
class st_profile_audio_t : public kobo_basestate_t
|
|
{
|
|
int pan, pitch, shift;
|
|
public:
|
|
st_profile_audio_t();
|
|
void enter();
|
|
void press(int button);
|
|
void pre_render();
|
|
void post_render();
|
|
};
|
|
#endif
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Critical Error Screen
|
|
----------------------------------------------------------*/
|
|
|
|
class st_error_t : public kobo_basestate_t
|
|
{
|
|
int start_time, frame_time;
|
|
const char *msg[2];
|
|
public:
|
|
st_error_t();
|
|
void message(const char *error, const char *hint);
|
|
void enter();
|
|
void press(int button);
|
|
void frame();
|
|
void post_render();
|
|
};
|
|
|
|
|
|
extern gamestatemanager_t gsm;
|
|
extern st_intro_title_t st_intro_title;
|
|
extern st_intro_instructions_t st_intro_instructions;
|
|
extern st_intro_highscores_t st_intro_highscores;
|
|
extern st_intro_credits_t st_intro_credits;
|
|
extern st_ask_exit_t st_ask_exit;
|
|
extern st_game_t st_game;
|
|
extern st_ask_abort_game_t st_ask_abort_game;
|
|
extern st_pause_game_t st_pause_game;
|
|
extern st_get_ready_t st_get_ready;
|
|
extern st_game_over_t st_game_over;
|
|
extern st_main_menu_t st_main_menu;
|
|
extern st_skill_menu_t st_skill_menu;
|
|
extern st_new_player_t st_new_player;
|
|
extern st_options_main_t st_options_main;
|
|
extern st_options_system_t st_options_system;
|
|
extern st_options_video_t st_options_video;
|
|
extern st_options_graphics_t st_options_graphics;
|
|
extern st_options_audio_t st_options_audio;
|
|
extern st_options_control_t st_options_control;
|
|
extern st_options_game_t st_options_game;
|
|
extern st_global_leaderboard_t st_global_leaderboard;
|
|
extern st_error_t st_error;
|
|
#ifdef PROFILE_AUDIO
|
|
extern st_profile_audio_t st_profile_audio;
|
|
#endif
|
|
|
|
#endif //_KOBO_STATES_H_
|