Просмотр исходного кода

wait until there are 2 players before game starts

5 second intervals with a 5-minute timeout. Removes player from lobby if they hit `BACK` or if the 5-minute timeout is breached.
jblanked 9 месяцев назад
Родитель
Сommit
cd121603f2
2 измененных файлов с 291 добавлено и 76 удалено
  1. 285 75
      callback/callback.c
  2. 6 1
      flip_world.h

+ 285 - 75
callback/callback.c

@@ -166,12 +166,29 @@ static void vibration_on_change(VariableItem *item);
 static void player_on_change(VariableItem *item);
 static void vgm_x_change(VariableItem *item);
 static void vgm_y_change(VariableItem *item);
+//
+static uint8_t timer_iteration = 0; // timer iteration for the loading screen
+static uint8_t timer_refresh = 5;   // duration for timer to refresh
 
 uint32_t callback_to_submenu(void *context)
 {
     UNUSED(context);
     return FlipWorldViewSubmenu;
 }
+static uint32_t callback_to_submenu_lobby(void *context)
+{
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    furi_check(app);
+    // this is only called by WaitingLobby, so let's send the request
+    furi_timer_stop(app->timer);
+    FlipperHTTP *fhttp = flipper_http_alloc();
+    if (fhttp)
+    {
+        remove_player_from_lobby(fhttp);
+        flipper_http_free(fhttp);
+    }
+    return FlipWorldViewLobby;
+}
 static uint32_t callback_to_wifi_settings(void *context)
 {
     UNUSED(context);
@@ -633,6 +650,40 @@ static bool alloc_game_submenu(void *context)
     return true;
 }
 
+static void waiting_lobby_draw_callback(Canvas *canvas, void *model)
+{
+    UNUSED(model);
+    canvas_clear(canvas);
+    canvas_draw_str(canvas, 0, 10, "Waiting for more players...");
+    // time elapsed based on timer_iteration and timer_refresh
+    // char str[32];
+    // snprintf(str, sizeof(str), "Time elapsed: %d seconds", timer_iteration * timer_refresh);
+    // FURI_LOG_I(TAG, "Time elapsed: %d seconds", timer_iteration * timer_refresh);
+    // canvas_draw_str(canvas, 0, 50, str);
+    canvas_draw_str(canvas, 0, 60, "Press BACK to cancel.");
+}
+static bool alloc_waiting_lobby_view(void *context)
+{
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+        return false;
+    }
+    if (!app->view_waiting_lobby)
+    {
+        if (!easy_flipper_set_view(&app->view_waiting_lobby, FlipWorldViewWaitingLobby, waiting_lobby_draw_callback, NULL, callback_to_submenu_lobby, &app->view_dispatcher, app))
+        {
+            return false;
+        }
+        if (!app->view_waiting_lobby)
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
 void free_game_submenu(void *context)
 {
     FlipWorldApp *app = (FlipWorldApp *)context;
@@ -805,6 +856,35 @@ static void free_submenu_settings(void *context)
         app->submenu_settings = NULL;
     }
 }
+static void free_timer(void *context)
+{
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+        return;
+    }
+    if (app->timer)
+    {
+        furi_timer_free(app->timer);
+        app->timer = NULL;
+    }
+}
+static void free_waiting_lobby_view(void *context)
+{
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "FlipWorldApp is NULL");
+        return;
+    }
+    if (app->view_waiting_lobby)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, FlipWorldViewWaitingLobby);
+        view_free(app->view_waiting_lobby);
+        app->view_waiting_lobby = NULL;
+    }
+}
 static FuriThread *game_thread;
 static bool game_thread_running = false;
 void free_all_views(void *context, bool free_variable_list, bool free_settings_submenu, bool free_submenu_game)
@@ -848,6 +928,9 @@ void free_all_views(void *context, bool free_variable_list, bool free_settings_s
     {
         free_game_submenu(app);
     }
+
+    free_timer(app);
+    free_waiting_lobby_view(app);
 }
 static bool fetch_world_list(FlipperHTTP *fhttp)
 {
@@ -1307,7 +1390,7 @@ static void run(FlipWorldApp *app)
         return;
     }
     free_all_views(app, true, true, false);
-    if (!is_enough_heap(60000, false)) // only need to check if they have 60k free (a 60k memory block is highly unlikely anyways)
+    if (!is_enough_heap(50000, false)) // only need to check if they have 50k free
     {
         easy_flipper_dialog("Error", "Not enough heap memory.\nPlease restart your Flipper.");
         return;
@@ -1709,83 +1792,54 @@ static bool create_pvp_enemy(FuriString *lobby_details)
 }
 // since we aren't using FURI_LOG, we will use easy_flipper_dialog and the last_error_message
 // char last_error_message[64];
-static bool parse_lobby(FlipperHTTP *fhttp)
+static size_t lobby_count(FlipperHTTP *fhttp, FuriString *lobby)
 {
     if (!fhttp)
     {
         FURI_LOG_E(TAG, "FlipperHTTP is NULL");
-        return false;
+        return -1;
     }
-
-    // load the lobby details
-    FuriString *lobby = flipper_http_load_from_file(fhttp->file_path);
     if (!lobby)
     {
-        FURI_LOG_E(TAG, "Failed to load lobby details");
-        return false;
+        FURI_LOG_E(TAG, "Lobby details are NULL");
+        return -1;
     }
-
     // check if the player is in the lobby
-    FuriString *is_in_game = get_json_value_furi("is_in_game", lobby);
     FuriString *player_count = get_json_value_furi("player_count", lobby);
-    if (!is_in_game || !player_count)
+    if (!player_count)
     {
         FURI_LOG_E(TAG, "Failed to get player count");
-        furi_string_free(lobby);
-        if (is_in_game)
-            furi_string_free(is_in_game);
-        if (player_count)
-            furi_string_free(player_count);
-        return false;
+        return -1;
     }
-
-    if (is_str(furi_string_get_cstr(is_in_game), "true"))
+    const size_t count = atoi(furi_string_get_cstr(player_count));
+    furi_string_free(player_count);
+    return count;
+}
+static bool in_lobby(FlipperHTTP *fhttp, FuriString *lobby)
+{
+    if (!fhttp)
     {
-        FURI_LOG_E(TAG, "Player is already in game");
-        furi_string_free(lobby);
-        furi_string_free(is_in_game);
-        furi_string_free(player_count);
+        FURI_LOG_E(TAG, "FlipperHTTP is NULL");
         return false;
     }
-
-    // add the user to the lobby
-    if (!join_lobby(fhttp, lobby_list[lobby_index]))
+    if (!lobby)
     {
-        FURI_LOG_E(TAG, "Failed to join lobby");
-        furi_string_free(lobby);
-        furi_string_free(is_in_game);
-        furi_string_free(player_count);
+        FURI_LOG_E(TAG, "Lobby details are NULL");
         return false;
     }
-
-    if (atoi(furi_string_get_cstr(player_count)) == 0)
-    {
-        // player is the only one in lobby so don't process any enemies yet
-        FURI_LOG_I(TAG, "No enemies in lobby");
-        furi_string_free(lobby);
-        furi_string_free(is_in_game);
-        furi_string_free(player_count);
-        return true;
-    }
-
-    // only an enemy is in the lobby
-
-    // parse the lobby details
-    if (!create_pvp_enemy(lobby))
+    // check if the player is in the lobby
+    FuriString *is_in_game = get_json_value_furi("is_in_game", lobby);
+    if (!is_in_game)
     {
-        FURI_LOG_I(TAG, "Failed to create pvp enemy data");
-        furi_string_free(lobby);
+        FURI_LOG_E(TAG, "Failed to get is_in_game");
         furi_string_free(is_in_game);
-        furi_string_free(player_count);
         return false;
     }
-
-    furi_string_free(lobby);
+    const bool in_game = is_str(furi_string_get_cstr(is_in_game), "true");
     furi_string_free(is_in_game);
-    furi_string_free(player_count);
-
-    return true;
+    return in_game;
 }
+
 static bool start_ws(FlipperHTTP *fhttp, char *lobby_name)
 {
     if (!fhttp)
@@ -1813,6 +1867,126 @@ static bool start_ws(FlipperHTTP *fhttp, char *lobby_name)
     }
     return true;
 }
+// this will free both the fhttp and lobby
+static void start_pvp(FlipperHTTP *fhttp, FuriString *lobby, void *context)
+{
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    furi_check(app, "FlipWorldApp is NULL");
+    // only thing left to do is create the enemy data and start the websocket session
+    if (!create_pvp_enemy(lobby))
+    {
+        FURI_LOG_E(TAG, "Failed to create pvp enemy context.");
+        easy_flipper_dialog("Error", "Failed to create pvp enemy context. Press BACK to return.");
+        flipper_http_free(fhttp);
+        furi_string_free(lobby);
+        return;
+    }
+
+    furi_string_free(lobby);
+
+    // start the websocket session
+    if (!start_ws(fhttp, lobby_list[lobby_index]))
+    {
+        FURI_LOG_E(TAG, "Failed to start websocket session");
+        easy_flipper_dialog("Error", "Failed to start websocket session. Press BACK to return.");
+        flipper_http_free(fhttp);
+        return;
+    }
+
+    flipper_http_free(fhttp);
+
+    // start the game thread
+    if (!start_game_thread(app))
+    {
+        FURI_LOG_E(TAG, "Failed to start game thread");
+        easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
+        return;
+    }
+};
+static void waiting_timer_callback(void *context)
+{
+    furi_check(context, "timer_callback: Context is NULL");
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, FlipWorldCustomEventWaitingLobby);
+}
+static void waiting_loader_process_callback(void *context)
+{
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    furi_check(app, "FlipWorldApp is NULL");
+    // check if another player has joined the lobby
+    FlipperHTTP *fhttp = flipper_http_alloc();
+    if (!fhttp)
+    {
+        FURI_LOG_E(TAG, "Failed to allocate FlipperHTTP");
+        return;
+    }
+    // fetch the lobby details
+    if (!fetch_lobby(fhttp, lobby_list[lobby_index]))
+    {
+        FURI_LOG_E(TAG, "Failed to fetch lobby details");
+        easy_flipper_dialog("Error", "Failed to fetch lobby details. Press BACK to return.");
+        flipper_http_free(fhttp);
+        return;
+    }
+    // load the lobby details
+    FuriString *lobby = flipper_http_load_from_file(fhttp->file_path);
+    if (!lobby)
+    {
+        FURI_LOG_E(TAG, "Failed to load lobby details");
+        easy_flipper_dialog("Error", "Failed to load lobby details. Press BACK to return.");
+        flipper_http_free(fhttp);
+        return;
+    }
+    // get the player count
+    const size_t count = lobby_count(fhttp, lobby);
+    if (count == 2)
+    {
+        // break out of this and start the game
+        furi_timer_stop(app->timer);
+        free_timer(app);
+        start_pvp(fhttp, lobby, app); // this will free both the fhttp and lobby
+        return;
+    }
+    timer_iteration++;
+    if (timer_iteration >= 60) // 5 seconds per ieration * 60 = 5 minutes
+    {
+        // if no one has joined after 5 minutes, show an error message and return to the main menu
+        FURI_LOG_E(TAG, "No players joined within the timeout");
+        timer_iteration = -1;
+        furi_timer_stop(app->timer);
+        free_timer(app);
+        remove_player_from_lobby(fhttp);
+        easy_flipper_dialog("Error", "No players joined within the timeout. Press BACK to return.");
+        view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewLobby);
+    }
+    furi_string_free(lobby);
+    flipper_http_free(fhttp);
+}
+static bool waiting_custom_event_callback(void *context, uint32_t index)
+{
+    UNUSED(index);
+    furi_check(context, "waiting_custom_event_callback: Context is NULL");
+    waiting_loader_process_callback(context);
+    return true;
+}
+static void waiting_lobby(void *context)
+{
+    FlipWorldApp *app = (FlipWorldApp *)context;
+    furi_check(app, "FlipWorldApp is NULL");
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, waiting_custom_event_callback);
+    free_timer(app);
+    app->timer = furi_timer_alloc(waiting_timer_callback, FuriTimerTypePeriodic, app);
+    furi_timer_start(app->timer, 1000 * timer_refresh); // check if another player has joined every timer_refresh seconds
+    free_waiting_lobby_view(app);
+    if (!alloc_waiting_lobby_view(app))
+    {
+        FURI_LOG_E(TAG, "Failed to allocate waiting lobby view");
+        easy_flipper_dialog("Error", "Failed to allocate waiting lobby view. Press BACK to return.");
+        return;
+    }
+    timer_iteration = 0;
+    view_dispatcher_switch_to_view(app->view_dispatcher, FlipWorldViewWaitingLobby);
+};
 static void callback_submenu_lobby_choices(void *context, uint32_t index)
 {
     /* Handle other game lobbies
@@ -1821,11 +1995,7 @@ static void callback_submenu_lobby_choices(void *context, uint32_t index)
              3. start the game thread (the rest will be handled by game_start and player_update)
              */
     FlipWorldApp *app = (FlipWorldApp *)context;
-    if (!app)
-    {
-        FURI_LOG_E(TAG, "FlipWorldApp is NULL");
-        return;
-    }
+    furi_check(app, "FlipWorldApp is NULL");
     if (index >= FlipWorldSubmenuIndexLobby && index < FlipWorldSubmenuIndexLobby + 10)
     {
         lobby_index = index - FlipWorldSubmenuIndexLobby;
@@ -1846,32 +2016,72 @@ static void callback_submenu_lobby_choices(void *context, uint32_t index)
             flipper_http_free(fhttp);
             return;
         }
-        if (!parse_lobby(fhttp))
+
+        // load the lobby details
+        FuriString *lobby = flipper_http_load_from_file(fhttp->file_path);
+        if (!lobby)
         {
-            FURI_LOG_E(TAG, "Failed to parse lobby details, join the lobby, or create pvp enemy context.")
-            easy_flipper_dialog("Error", "Failed to parse lobby details, join the lobby, or create pvp enemy context. Press BACK to return.");
+            FURI_LOG_E(TAG, "Failed to load lobby details");
             flipper_http_free(fhttp);
             return;
         }
 
-        // start the websocket session
-        if (!start_ws(fhttp, lobby_list[lobby_index]))
+        // if there are no players, add the user to the lobby and make the user wait until another player joins
+        // if there is one player and it's the user, make the user wait until another player joins
+        // if there is one player and it's not the user, parse_lobby and start websocket
+        // if there are 2 players (which there shouldn't be at this point), show an error message saying the lobby is full
+        switch (lobby_count(fhttp, lobby))
         {
-            FURI_LOG_E(TAG, "Failed to start websocket session");
-            easy_flipper_dialog("Error", "Failed to start websocket session. Press BACK to return.");
+        case -1:
+            FURI_LOG_E(TAG, "Failed to get player count");
+            easy_flipper_dialog("Error", "Failed to get player count. Press BACK to return.");
             flipper_http_free(fhttp);
+            furi_string_free(lobby);
             return;
-        }
-
-        flipper_http_free(fhttp);
-
-        // start the game thread
-        if (!start_game_thread(app))
-        {
-            FURI_LOG_E(TAG, "Failed to start game thread");
-            easy_flipper_dialog("Error", "Failed to start game thread. Press BACK to return.");
+        case 0:
+            // add the user to the lobby
+            if (!join_lobby(fhttp, lobby_list[lobby_index]))
+            {
+                FURI_LOG_E(TAG, "Failed to join lobby");
+                easy_flipper_dialog("Error", "Failed to join lobby. Press BACK to return.");
+                flipper_http_free(fhttp);
+                furi_string_free(lobby);
+                return;
+            }
+            // send the user to the waiting screen
+            waiting_lobby(app);
             return;
-        }
+        case 1:
+            // check if the user is in the lobby
+            if (in_lobby(fhttp, lobby))
+            {
+                // send the user to the waiting screen
+                FURI_LOG_I(TAG, "User is in the lobby");
+                flipper_http_free(fhttp);
+                furi_string_free(lobby);
+                waiting_lobby(app);
+                return;
+            }
+            // add the user to the lobby
+            if (!join_lobby(fhttp, lobby_list[lobby_index]))
+            {
+                FURI_LOG_E(TAG, "Failed to join lobby");
+                easy_flipper_dialog("Error", "Failed to join lobby. Press BACK to return.");
+                flipper_http_free(fhttp);
+                furi_string_free(lobby);
+                return;
+            }
+            break;
+        case 2:
+            // show an error message saying the lobby is full
+            FURI_LOG_E(TAG, "Lobby is full");
+            easy_flipper_dialog("Error", "Lobby is full. Press BACK to return.");
+            flipper_http_free(fhttp);
+            furi_string_free(lobby);
+            return;
+        };
+
+        start_pvp(fhttp, lobby, app); // this will free both the fhttp and lobby, and start the game
     }
 }
 

+ 6 - 1
flip_world.h

@@ -41,6 +41,7 @@ typedef enum
     FlipWorldViewMessage,          // The about, loading screen
     FlipWorldViewSettings,         // The settings screen
     FlipWorldViewLobby,            // The lobby screen
+    FlipWorldViewWaitingLobby,     // The waiting lobby screen
     FlipWorldViewVariableItemList, // The variable item list screen
     FlipWorldViewTextInput,        // The text input screen
     //
@@ -52,7 +53,8 @@ typedef enum
 typedef enum
 {
     FlipWorldCustomEventProcess,
-    FlipWorldCustomEventPlay, // Play the game
+    FlipWorldCustomEventPlay,         // Play the game
+    FlipWorldCustomEventWaitingLobby, // waiting lobby for PvP
 } FlipWorldCustomEvent;
 
 // Each screen will have its own view
@@ -63,6 +65,7 @@ typedef struct
     //
     ViewDispatcher *view_dispatcher;       // Switches between our views
     View *view_message;                    // The about, loading screen
+    View *view_waiting_lobby;              // The waiting lobby screen
     Submenu *submenu;                      // The submenu
     Submenu *submenu_game;                 // The game submenu
     Submenu *submenu_settings;             // The settings submenu
@@ -87,6 +90,8 @@ typedef struct
     char *text_input_buffer;         // Buffer for the text input
     char *text_input_temp_buffer;    // Temporary buffer for the text input
     uint32_t text_input_buffer_size; // Size of the text input buffer
+    //
+    FuriTimer *timer;
 } FlipWorldApp;
 
 extern char *fps_choices_str[];