From 0c735ba904709fb6699dc685d6bdccba18e5c19d Mon Sep 17 00:00:00 2001 From: Alex Barry Date: Tue, 24 Dec 2024 14:37:21 -0400 Subject: [PATCH 1/2] multiplayer/core: add APIs to get the multiplayer session ID. This is the 5 digit code in the URL of the web version, used by the websocket server to decide which players to connect a player with. The wxWidgets implementation doesn't need this, since its server is only for a single session. I think I implemented a normal socket client for the native Android version too. --- .../src/main/cpp/alex_games_android_jni.cpp | 11 ++++++++ .../game_api_helper/game_api_helper.cpp | 8 ++++++ src/emscripten/emscripten_api.c | 27 +++++++++++++++++++ src/game_api/game_api.h | 24 ++++++++++++++++- src/html/js/alexgames_wasm_api.js | 4 +++ src/lua_api/lua_api.c | 19 +++++++++++++ .../libs/multiplayer/two_player.lua | 12 +++++++-- .../libs/multiplayer/wait_for_players.lua | 13 +++++++-- src/ui_wxWidgets/wx_main.cpp | 13 +++++++++ 9 files changed, 126 insertions(+), 5 deletions(-) diff --git a/src/android/app/src/main/cpp/alex_games_android_jni.cpp b/src/android/app/src/main/cpp/alex_games_android_jni.cpp index 72914fc..e21c0f5 100644 --- a/src/android/app/src/main/cpp/alex_games_android_jni.cpp +++ b/src/android/app/src/main/cpp/alex_games_android_jni.cpp @@ -435,6 +435,14 @@ static size_t jni_get_user_colour_pref(char *colour_pref_out, size_t max_colour_ return 0; } +static bool jni_is_multiplayer_session_needed() { + return false; +} + +static size_t jni_get_multiplayer_session_id(char *str_out, size_t max_str_out_len) { + return snprintf(str_out, max_str_out_len, ""); +} + static bool jni_is_feature_supported(const char *feature_id, size_t feature_id_len) { // TODO return false; @@ -494,6 +502,9 @@ static const struct game_api_callbacks api = { jni_get_user_colour_pref, + jni_is_multiplayer_session_needed, + jni_get_multiplayer_session_id, + jni_is_feature_supported, jni_destroy_all }; diff --git a/src/cpp_libs/game_api_helper/game_api_helper.cpp b/src/cpp_libs/game_api_helper/game_api_helper.cpp index cd1e02d..675c5a5 100644 --- a/src/cpp_libs/game_api_helper/game_api_helper.cpp +++ b/src/cpp_libs/game_api_helper/game_api_helper.cpp @@ -70,6 +70,12 @@ static void delete_extra_canvases(void) {} static size_t get_user_colour_pref(char *colour_pref_out, size_t max_colour_pref_out_len) { return snprintf(colour_pref_out, max_colour_pref_out_len, ""); } +static size_t get_multiplayer_session_id(char *colour_pref_out, size_t max_colour_pref_out_len) { + return snprintf(colour_pref_out, max_colour_pref_out_len, ""); +} +static bool is_multiplayer_session_id_needed() { + return true; +} static bool is_feature_supported(const char *feature_id, size_t feature_id_len) { return false; } @@ -117,6 +123,8 @@ struct game_api_callbacks create_default_callbacks(void) { /* .set_active_canvas = */ set_active_canvas, /* .delete_extra_canvases = */ delete_extra_canvases, /* .get_user_colour_pref = */ get_user_colour_pref, + /* .is_multiplayer_session_id_needed = */ is_multiplayer_session_id_needed, + /* .get_multiplayer_session_id = */ get_multiplayer_session_id, /* .is_feature_supported = */ is_feature_supported, /* .destroy_all = */ destroy_all, }; diff --git a/src/emscripten/emscripten_api.c b/src/emscripten/emscripten_api.c index 3bfb38e..0a84626 100644 --- a/src/emscripten/emscripten_api.c +++ b/src/emscripten/emscripten_api.c @@ -517,6 +517,31 @@ EM_JS(size_t, js_get_user_colour_pref, (char *colour_str_out, size_t max_colour_ return colour_pref_str_js.length; }); +EM_JS(size_t, js_get_multiplayer_session_id, (char *session_id_out, size_t max_session_id_out_len), { + let session_id_js = get_multiplayer_session_id(); + + let i; + for (i=0; i= max_session_id_out_len) { + console.error("Multiplayer ID session str buff is not long enough"); + return -1; + } + setValue(session_id_out + i, session_id_js.charCodeAt(i), 'i8'); + } + setValue(session_id_out + i, 0, 'i8'); + + return session_id_js.length; +}); + +EM_JS(bool, js_is_multiplayer_session_id_needed, (), { + // For now, all web clients need session IDs. Technically you could have a server that only supports one session, + // but I didn't implement anything like that in the multiplayer protocol yet. + return true; +}); + + + + EM_JS(bool, js_is_feature_supported, (const char *feature_id, size_t feature_id_len), { // TODO: // "draw_graphic_invert": figure out how to check if context.filter supports invert @@ -624,6 +649,8 @@ const struct game_api_callbacks api_callbacks = { .set_active_canvas = js_set_active_canvas, .delete_extra_canvases = js_delete_extra_canvases, .get_user_colour_pref = js_get_user_colour_pref, + .is_multiplayer_session_id_needed = js_is_multiplayer_session_id_needed, + .get_multiplayer_session_id = js_get_multiplayer_session_id, .is_feature_supported = js_is_feature_supported, .destroy_all = js_destroy_all, diff --git a/src/game_api/game_api.h b/src/game_api/game_api.h index b9186e7..de43602 100644 --- a/src/game_api/game_api.h +++ b/src/game_api/game_api.h @@ -270,9 +270,13 @@ struct game_api_callbacks { size_t (*read_stored_data)(void *L, const char *key, uint8_t *value_out, size_t max_val_len); /** - * Gets a new session ID, usually when the player is starting a new game. + * Gets a new **storage** session ID, usually when the player is starting a new game. * This lets the game code store the game's state in the database, and * any new moves in that game will be stored together. + * + * NOTE: these APIs use a session ID used to identify the game in storage, + * it has nothing to do with the multiplayer session ID, which is + * used to identify which players the server should connect the player with. */ int (*get_new_session_id)(void); @@ -337,6 +341,24 @@ struct game_api_callbacks { size_t (*get_user_colour_pref)(char *colour_pref_out, size_t max_colour_pref_out_len); + /** + * Gets whether or not the multiplayer session ID is needed (on this client) or not. + * + * Currently the HTML implementation uses multiplayer session IDs (one server is used for many different clients), + * but on wxWidgets for the socket server, the client actually hosts its own server, so there's no need to + * support multiple sessions. + * + */ + bool (*is_multiplayer_session_id_needed)(); + + /** + * Gets the multiplayer session ID. + * + * This ID should be shown to the user so that they know that it must match their friend's session ID + * so they can play together. + */ + size_t (*get_multiplayer_session_id)(char *multiplayer_session_id_out, size_t max_multiplayer_session_id_out_len); + bool (*is_feature_supported)(const char *feature_id, size_t feature_id_len); /** diff --git a/src/html/js/alexgames_wasm_api.js b/src/html/js/alexgames_wasm_api.js index f694530..3eeeea6 100644 --- a/src/html/js/alexgames_wasm_api.js +++ b/src/html/js/alexgames_wasm_api.js @@ -648,6 +648,10 @@ function set_game_handle(L, game_id_str) { gfx.game_id = game_id_str; } +function get_multiplayer_session_id() { + return g_session_id; +} + function destroy_all(L) { let button_row = document.getElementById("game_button_row"); while (button_row.firstChild) { diff --git a/src/lua_api/lua_api.c b/src/lua_api/lua_api.c index fcf9774..9dd0533 100644 --- a/src/lua_api/lua_api.c +++ b/src/lua_api/lua_api.c @@ -110,6 +110,8 @@ static int lua_set_active_canvas(lua_State *L); static int lua_delete_extra_canvases(lua_State *L); static int lua_get_user_colour_pref(lua_State *L); +static int lua_get_multiplayer_session_id(lua_State *L); +static int lua_is_multiplayer_session_id_needed(lua_State *L); static int lua_is_feature_supported(lua_State *L); #ifdef ENABLE_WORD_DICT @@ -180,6 +182,8 @@ static const struct luaL_Reg lua_c_api[] = { {"set_active_canvas", lua_set_active_canvas }, {"delete_extra_canvases", lua_delete_extra_canvases }, {"get_user_colour_pref", lua_get_user_colour_pref}, + {"is_multiplayer_session_id_needed", lua_is_multiplayer_session_id_needed}, + {"get_multiplayer_session_id", lua_get_multiplayer_session_id}, {"is_feature_supported", lua_is_feature_supported}, {NULL, NULL} @@ -1885,6 +1889,21 @@ static int lua_get_user_colour_pref(lua_State *L) { return 1; } +static int lua_get_multiplayer_session_id(lua_State *L) { + char str_buff[128]; + size_t str_len = api->get_multiplayer_session_id(str_buff, sizeof(str_buff)); + + lua_pushlstring(L, str_buff, str_len); + return 1; +} + +static int lua_is_multiplayer_session_id_needed(lua_State *L) { + bool is_sess_id_needed = api->is_multiplayer_session_id_needed(); + + lua_pushboolean(L, is_sess_id_needed); + return 1; +} + static int lua_is_feature_supported(lua_State *L) { size_t feature_id_len = 0; const char *feature_id = lua_tolstring_notnil(L, 1, &feature_id_len); diff --git a/src/lua_scripts/libs/multiplayer/two_player.lua b/src/lua_scripts/libs/multiplayer/two_player.lua index 98c866b..e1d64d1 100644 --- a/src/lua_scripts/libs/multiplayer/two_player.lua +++ b/src/lua_scripts/libs/multiplayer/two_player.lua @@ -113,8 +113,16 @@ local function need_player_reselect(remote_player) end local function show_player_choice_popup() - local msg = "Share the URL in your address bar with your friend, making sure to include the randomly generated \"ID\" parameter. This is how the server knows to connect you with your friend." - msg = msg .. "\n" .. g_args.get_msg() + local msg = '' + if alexgames.is_multiplayer_session_id_needed() then + local session_id = alexgames.get_multiplayer_session_id(); + msg = string.format("Multiplayer session ID: %s", session_id) + msg = msg .. '\n' .. string.format("Share the URL in your address bar with your friend, making sure to include the \"ID\" parameter (%s). This is how the server knows to connect you with your friend.", session_id) + msg = msg .. '\n' .. string.format("Session ID can be changed in options, or by editing the URL manually.") + --msg = msg .. "\n" .. string.format("Your multiplayer session_id is: %s", session_id) + msg = msg .. '\n' + end + msg = msg .. g_args.get_msg() show_buttons_popup.show_popup(two_player.PLAYER_CHOICE_POPUP_ID, g_args.title, msg, diff --git a/src/lua_scripts/libs/multiplayer/wait_for_players.lua b/src/lua_scripts/libs/multiplayer/wait_for_players.lua index 63bba28..716b34d 100644 --- a/src/lua_scripts/libs/multiplayer/wait_for_players.lua +++ b/src/lua_scripts/libs/multiplayer/wait_for_players.lua @@ -60,8 +60,17 @@ function wait_for_players.players_tentative() end function wait_for_players.show_waiting_for_players_popup() - local body_txt = "Share the URL in your address bar with your friend, making sure to include the randomly generated \"ID\" parameter. This is how the server knows to connect you with your friend." - body_txt = body_txt .. "\n" .. string.format("Players joined: %d", #players) + local body_txt = '' + if alexgames.is_multiplayer_session_id_needed() then + --local body_txt = "Share the URL in your address bar with your friend, making sure to include the randomly generated \"ID\" parameter. This is how the server knows to connect you with your friend." + local session_id = alexgames.get_multiplayer_session_id() + body_txt = string.format("Multiplayer session ID: %s", session_id) + body_txt = body_txt .. '\n' .. string.format("Share the URL in your address bar with your friend, making sure to include the \"ID\" parameter (%s). This is how the server knows to connect you with your friend.", session_id) + body_txt = body_txt .. '\n' .. string.format("Session ID can be changed in options, or by editing the URL manually.") + body_txt = body_txt .. '\n' + end + + body_txt = body_txt .. string.format("Players joined: %d", #players) print("Player is %q", player) for player_id, player_ip in pairs(players) do local more_info = "" diff --git a/src/ui_wxWidgets/wx_main.cpp b/src/ui_wxWidgets/wx_main.cpp index 8d61734..e1cf268 100644 --- a/src/ui_wxWidgets/wx_main.cpp +++ b/src/ui_wxWidgets/wx_main.cpp @@ -93,6 +93,8 @@ static void wx_new_extra_canvas(const char *canvas_id); static void wx_set_active_canvas(const char *canvas_id); static void wx_delete_extra_canvases(void); static size_t wx_get_user_colour_pref(char *colour_pref_out, size_t max_colour_pref_out_len); +static bool wx_is_multiplayer_session_id_needed(); +static size_t wx_get_multiplayer_session_id(char *sess_id_out, size_t sess_id_out_max_len); static bool wx_is_feature_supported(const char *feature_id, size_t feature_id_len); @@ -202,6 +204,8 @@ static const struct game_api_callbacks api = { wx_set_active_canvas, wx_delete_extra_canvases, wx_get_user_colour_pref, + wx_is_multiplayer_session_id_needed, + wx_get_multiplayer_session_id, wx_is_feature_supported, wx_destroy_all, }; @@ -1673,6 +1677,15 @@ static size_t wx_get_user_colour_pref(char *colour_pref_out, size_t max_colour_p return bytes_written; } +static bool wx_is_multiplayer_session_id_needed() { + return false; +} + +static size_t wx_get_multiplayer_session_id(char *sess_id_out, size_t sess_id_out_max_len) { + size_t bytes_written = snprintf(sess_id_out, sess_id_out_max_len, "not needed on this client!"); + return bytes_written; +} + static bool wx_is_feature_supported(const char *feature_id, size_t feature_id_len) { return false; From 6a2f18fd288c6d2ac250054c3cedeb5719d47012 Mon Sep 17 00:00:00 2001 From: Alex Barry Date: Tue, 24 Dec 2024 14:40:22 -0400 Subject: [PATCH 2/2] core: fix compile error in some common code used by wxWidgets and Android implementations --- src/dictionary/c_dictionary.c | 18 ++++++++++++++++-- src/server/socket/socket_server_impl.c | 3 ++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/dictionary/c_dictionary.c b/src/dictionary/c_dictionary.c index a04fbc1..6df6ec7 100644 --- a/src/dictionary/c_dictionary.c +++ b/src/dictionary/c_dictionary.c @@ -140,7 +140,7 @@ static bool get_next_word_from_frame(const struct word_dict_frame *frame, int *p (*pos)++; } (*pos)++; - word_info_out->freq = frame->data + *pos; + word_info_out->freq = (word_freq_t*) frame->data + *pos; //memcpy(freq, frame->data + *pos, sizeof(word_freq_t)); *pos += sizeof(word_freq_t); @@ -180,6 +180,19 @@ int find_char(const char *str, size_t str_len, char c, int start_pos) { return -1; } +float str_and_len_to_float(const char *str, size_t len) { + #define MAX_LEN (128) + char tmp_str[MAX_LEN]; + if (len-1 >= MAX_LEN) { + alex_log_err("[dict] ERROR: str %.*s (len %d) is larger than max buff len %d\n", len, str, len, MAX_LEN); + return 0; + } + memcpy(tmp_str, str, len); + tmp_str[len] = '\0'; + return strtof(tmp_str, NULL); + +} + void *build_word_dict_from_file(const char *fname) { long long start_time = timeInMilliseconds(); //printf("opening file %s...\n", fname); @@ -259,7 +272,8 @@ void *build_word_dict_from_file(const char *fname) { #ifndef ALEXGAMES_WASM // This line is really slow on WASM. // Calling it 200k times seems to add ~500 ms on Firefox 119.0 for linux - word_freq_t freq = strtof(freq_str, freq_str + freq_str_len); + //word_freq_t freq = strtof(freq_str, freq_str + freq_str_len); + word_freq_t freq = str_and_len_to_float(freq_str, freq_str_len); #else // TODO refactor this into a nice portable helper function // don't put emscripten specific stuff here. diff --git a/src/server/socket/socket_server_impl.c b/src/server/socket/socket_server_impl.c index 90cc083..2d7f658 100644 --- a/src/server/socket/socket_server_impl.c +++ b/src/server/socket/socket_server_impl.c @@ -157,7 +157,8 @@ void get_client_name(void *handle_arg, const char *name_out, size_t max_name_len struct sockaddr_storage addr; int len = sizeof(addr); - getpeername(handle->socket, &addr, &len); + //getpeername(handle->socket, &addr, &len); + getpeername(handle->socket, (struct sockaddr*)&addr, &len); char addr_str[256]; addr_str[0] = 0;