Kaynağa Gözat

Merge minesweeper from https://github.com/xMasterX/all-the-plugins

# Conflicts:
#	minesweeper/helpers/mine_sweeper_haptic.c
#	minesweeper/helpers/mine_sweeper_storage.c
#	minesweeper/scenes/info_scene.c
#	minesweeper/scenes/settings_scene.c
#	minesweeper/views/minesweeper_game_screen.c
Willy-JL 1 yıl önce
ebeveyn
işleme
354e084884

+ 1 - 1
minesweeper/application.fam

@@ -15,7 +15,7 @@ App(
     fap_icon="assets/minesweeper.png",  # 10x10 1-bit PNG
     fap_category="Games",
     # Optional values
-    fap_version="1.1",
+    fap_version="1.2",
     fap_description="Flipper Zero Minesweeper Implementation",
     fap_author="Alexander Rodriguez",
     fap_weburl="https://github.com/squee72564/F0_Minesweeper_Fap",

+ 8 - 23
minesweeper/docs/changelog.md

@@ -1,32 +1,17 @@
-
 # Changelog
-## TODO:
-- Add settings options to toggle hardware feedback
-- Maybe take a look at the board verifier algo and try to make faster/multi-thread or anything to allow better maps
+
+## Version 1.2.0 - 1/25/2024
+
+Added ability to toggle sound and haptic feedback.
+
+Minor reformats and fixes.
 
 ## Version 1.1.0 - 1/11/2024
 
 Added haptic / led functionality
 
-## Added
-- Haptic feedback on all button presses.
-    - Out of bounds movement
-    - Ok to clear tiles
-    - Holding back for flags
-    - Different haptic feedback on win/loss
-- LED changes on win loss
-    - Initially LED is just reset
-    - Set to red on loss
-    - Set to blue on win
-- Sound on some presses
+Code Reformats
 
 ## Version 1.0.0 - 1/10/2024
 
-Initial Full release for the game.
-
-## Added
-- Mine sweeper game with settings menu to choose variable board dimensions from 16x7 to 32x32.
-- Ability to set difficulty, width, and height for the game board in settings.
-- Ability to enable solvable boards only
-- The core game functionality is complete for the game with multiple types of button presses registered for different inputs.
-- The game will save your settings (besides "enable solvable") when you leave the game.
+Initial Full release for the game.

+ 5 - 5
minesweeper/helpers/mine_sweeper_haptic.c

@@ -4,7 +4,7 @@
 void mine_sweeper_play_happy_bump(void* context) {
     MineSweeperApp* app = context;
 
-    notification_message(app->notification, &sequence_set_vibro_on);
+    if(app->feedback_enabled) notification_message(app->notification, &sequence_set_vibro_on);
     furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
     notification_message(app->notification, &sequence_reset_vibro);
 }
@@ -13,7 +13,7 @@ void mine_sweeper_play_long_ok_bump(void* context) {
     MineSweeperApp* app = context;
 
     for(int i = 0; i < 2; i++) {
-        notification_message(app->notification, &sequence_set_vibro_on);
+        if(app->feedback_enabled) notification_message(app->notification, &sequence_set_vibro_on);
         furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
         notification_message(app->notification, &sequence_reset_vibro);
         furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
@@ -23,7 +23,7 @@ void mine_sweeper_play_long_ok_bump(void* context) {
 void mine_sweeper_play_oob_bump(void* context) {
     MineSweeperApp* app = context;
 
-    notification_message(app->notification, &sequence_set_vibro_on);
+    if(app->feedback_enabled) notification_message(app->notification, &sequence_set_vibro_on);
     furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
     notification_message(app->notification, &sequence_reset_vibro);
 }
@@ -31,7 +31,7 @@ void mine_sweeper_play_oob_bump(void* context) {
 void mine_sweeper_play_lose_bump(void* context) {
     MineSweeperApp* app = context;
 
-    notification_message(app->notification, &sequence_set_vibro_on);
+    if(app->feedback_enabled) notification_message(app->notification, &sequence_set_vibro_on);
     furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
     notification_message(app->notification, &sequence_reset_vibro);
     furi_thread_flags_wait(0, FuriFlagWaitAny, 400);
@@ -41,7 +41,7 @@ void mine_sweeper_play_win_bump(void* context) {
     MineSweeperApp* app = context;
 
     for(int i = 0; i < 4; i++) {
-        notification_message(app->notification, &sequence_set_vibro_on);
+        if(app->feedback_enabled) notification_message(app->notification, &sequence_set_vibro_on);
         furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
         notification_message(app->notification, &sequence_reset_vibro);
         furi_thread_flags_wait(0, FuriFlagWaitAny, 100);

+ 23 - 6
minesweeper/helpers/mine_sweeper_speaker.c

@@ -5,7 +5,10 @@ static const float volume = 0.8f;
 
 void mine_sweeper_play_ok_sound(void* context) {
     MineSweeperApp* app = context;
-    UNUSED(app);
+
+    if(!app->feedback_enabled) {
+        return;
+    }
 
     if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
         furi_hal_speaker_start(NOTE_LOSE, volume);
@@ -14,7 +17,10 @@ void mine_sweeper_play_ok_sound(void* context) {
 
 void mine_sweeper_play_flag_sound(void* context) {
     MineSweeperApp* app = context;
-    UNUSED(app);
+
+    if(!app->feedback_enabled) {
+        return;
+    }
 
     if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
         furi_hal_speaker_start(NOTE_FLAG, volume);
@@ -23,7 +29,10 @@ void mine_sweeper_play_flag_sound(void* context) {
 
 void mine_sweeper_play_oob_sound(void* context) {
     MineSweeperApp* app = context;
-    UNUSED(app);
+
+    if(!app->feedback_enabled) {
+        return;
+    }
 
     if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
         furi_hal_speaker_start(NOTE_OOB, volume);
@@ -32,7 +41,10 @@ void mine_sweeper_play_oob_sound(void* context) {
 
 void mine_sweeper_play_win_sound(void* context) {
     MineSweeperApp* app = context;
-    UNUSED(app);
+
+    if(!app->feedback_enabled) {
+        return;
+    }
 
     if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
         furi_hal_speaker_start(NOTE_WIN, volume);
@@ -41,8 +53,10 @@ void mine_sweeper_play_win_sound(void* context) {
 
 void mine_sweeper_play_lose_sound(void* context) {
     MineSweeperApp* app = context;
-    UNUSED(app);
 
+    if(!app->feedback_enabled) {
+        return;
+    }
     if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
         furi_hal_speaker_start(NOTE_LOSE, volume);
     }
@@ -50,7 +64,10 @@ void mine_sweeper_play_lose_sound(void* context) {
 
 void mine_sweeper_stop_all_sound(void* context) {
     MineSweeperApp* app = context;
-    UNUSED(app);
+
+    if(!app->feedback_enabled) {
+        return;
+    }
 
     if(furi_hal_speaker_is_mine()) {
         furi_hal_speaker_stop();

+ 12 - 13
minesweeper/helpers/mine_sweeper_storage.c

@@ -47,16 +47,14 @@ void mine_sweeper_save_settings(void* context) {
     // Store Settings
     flipper_format_write_header_cstr(
         fff_file, MINESWEEPER_SETTINGS_HEADER, MINESWEEPER_SETTINGS_FILE_VERSION);
-    flipper_format_write_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
-    flipper_format_write_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
-    flipper_format_write_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_LED, &app->led, 1);
 
     uint32_t w = app->settings_info.board_width, h = app->settings_info.board_height,
-             d = app->settings_info.difficulty;
+             d = app->settings_info.difficulty, f = app->feedback_enabled;
 
     flipper_format_write_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_WIDTH, &w, 1);
     flipper_format_write_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_HEIGHT, &h, 1);
     flipper_format_write_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_DIFFICULTY, &d, 1);
+    flipper_format_write_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_FEEDBACK, &f, 1);
 
     if(!flipper_format_rewind(fff_file)) {
         FURI_LOG_E(TAG, "Rewind error");
@@ -108,19 +106,20 @@ bool mine_sweeper_read_settings(void* context) {
         return false;
     }
 
-    uint32_t w = 7, h = 16, d = 0;
+    uint32_t w = 7, h = 16, d = 0, f = 1;
     flipper_format_read_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_WIDTH, &w, 1);
     flipper_format_read_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_HEIGHT, &h, 1);
     flipper_format_read_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_DIFFICULTY, &d, 1);
+    flipper_format_read_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_FEEDBACK, &f, 1);
 
-    if(w > 146) {
-        w = 146;
+    if(w > 32) {
+        w = 32;
     }
     if(w < 16) {
         w = 16;
     }
-    if(h > 64) {
-        h = 64;
+    if(h > 32) {
+        h = 32;
     }
     if(h < 7) {
         h = 7;
@@ -128,14 +127,14 @@ bool mine_sweeper_read_settings(void* context) {
     if(d > 2) {
         d = 2;
     }
+    if(f > 1) {
+        f = 1;
+    }
 
     app->settings_info.board_width = (uint8_t)w;
     app->settings_info.board_height = (uint8_t)h;
     app->settings_info.difficulty = (uint8_t)d;
-
-    flipper_format_read_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
-    flipper_format_read_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
-    flipper_format_read_uint32(fff_file, MINESWEEPER_SETTINGS_KEY_LED, &app->led, 1);
+    app->feedback_enabled = (uint8_t)f;
 
     flipper_format_rewind(fff_file);
 

+ 7 - 9
minesweeper/helpers/mine_sweeper_storage.h

@@ -7,21 +7,19 @@
 #include <flipper_format/flipper_format.h>
 #include "../minesweeper.h"
 
-#define MINESWEEPER_SETTINGS_FILE_VERSION 1
-#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/f0_mine_sweeper")
-#define MINESWEEPER_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/f0_mine_sweeper.conf"
+#define MINESWEEPER_SETTINGS_FILE_VERSION 2
+#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/mine_sweeper_redux")
+#define MINESWEEPER_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/mine_sweeper_redux.conf"
 #define MINESWEEPER_SETTINGS_SAVE_PATH_TMP MINESWEEPER_SETTINGS_SAVE_PATH ".tmp"
-#define MINESWEEPER_SETTINGS_HEADER "Mine Sweeper Config File"
+#define MINESWEEPER_SETTINGS_HEADER "Mine Sweeper Redux Config File"
 
 #define MINESWEEPER_SETTINGS_KEY_WIDTH "BoardWidth"
 #define MINESWEEPER_SETTINGS_KEY_HEIGHT "BoardHeight"
 #define MINESWEEPER_SETTINGS_KEY_DIFFICULTY "BoardDifficulty"
+#define MINESWEEPER_SETTINGS_KEY_FEEDBACK "FeedbackEnabled"
 
-#define MINESWEEPER_SETTINGS_KEY_HAPTIC "Haptic"
-#define MINESWEEPER_SETTINGS_KEY_LED "Led"
-#define MINESWEEPER_SETTINGS_KEY_SPEAKER "Speaker"
-
+void mine_sweeper_quick_save_feedback(void* context);
 void mine_sweeper_save_settings(void* context);
 bool mine_sweeper_read_settings(void* context);
 
-#endif
+#endif

+ 1 - 3
minesweeper/minesweeper.c

@@ -55,9 +55,7 @@ static MineSweeperApp* app_alloc() {
         app->settings_info.board_width = 16;
         app->settings_info.board_height = 7;
         app->settings_info.difficulty = 0;
-        app->haptic = 1;
-        app->speaker = 1;
-        app->led = 1;
+        app->feedback_enabled = 1;
 
         mine_sweeper_save_settings(app);
     } else {

+ 2 - 4
minesweeper/minesweeper.h

@@ -58,12 +58,10 @@ typedef struct MineSweeperApp {
     MineSweeperAppSettings settings_info;
     MineSweeperAppSettings t_settings_info;
 
-    bool is_settings_changed;
+    uint8_t is_settings_changed;
     bool ensure_map_solvable;
 
-    uint32_t haptic;
-    uint32_t speaker;
-    uint32_t led;
+    uint8_t feedback_enabled;
 } MineSweeperApp;
 
 // View Id Enumeration

+ 13 - 5
minesweeper/scenes/info_scene.c

@@ -1,5 +1,5 @@
 #include "../minesweeper.h"
-static const char* info_string = "GAME INFO BELOW\n\n"
+static const char* info_string = "--   GAME INFO BELOW   --\n\n"
                                  "1. Press OK to clear a tile.\n\n"
                                  "2. Hold OK on a numbered tile\n"
                                  "to clear all surrounding\n"
@@ -10,18 +10,26 @@ static const char* info_string = "GAME INFO BELOW\n\n"
                                  "4. Hold back on a cleared\n"
                                  "tile to jump to the\n"
                                  "closest tile.\n\n"
-                                 "SETTINGS INFO\n\n"
+                                 "---    SETTINGS INFO    ---\n\n"
                                  "Difficulty and map\n"
                                  "dimensions can be changed\n"
                                  "in the settings with a\n"
                                  "max map size of 1024\n"
-                                 "tiles (32x32).\n\n"
-                                 "ENSURE SOLVABLE\n"
+                                 "tiles (32x32).\n"
+                                 "You will be prompted to\n"
+                                 "confirm any changes to these\n"
+                                 "settings as it will reset the\n"
+                                 "board.\n\n"
+                                 "----      FEEDBACK     ----\n"
+                                 "This settings enables/disables\n"
+                                 "Haptic/Sound feedback for the\n"
+                                 "game.\n\n"
+                                 "-  ENSURE SOLVABLE  -\n"
                                  "This is a setting that\n"
                                  "enables a board verifier\n"
                                  "when generating a new\n"
                                  "board.\n\n"
-                                 "-- WARNING --\n"
+                                 "WARNING!:\n"
                                  "This setting will introduce\n"
                                  "a variable amount of\n"
                                  "overhead when generating\n"

+ 35 - 3
minesweeper/scenes/settings_scene.c

@@ -21,6 +21,7 @@ typedef enum {
     MineSweeperSettingsScreenEventHeightChange,
     MineSweeperSettingsScreenEventSolvableChange,
     MineSweeperSettingsScreenEventInfoChange,
+    MineSweeperSettingsScreenEventFeedbackChange,
 } MineSweeperSettingsScreenEvent;
 
 static const char* settings_screen_difficulty_text[MineSweeperSettingsScreenDifficultyTypeNum] = {
@@ -167,6 +168,23 @@ static void minesweeper_scene_settings_screen_set_solvable(VariableItem* item) {
         app->view_dispatcher, MineSweeperSettingsScreenEventSolvableChange);
 }
 
+static void minesweeper_scene_settings_screen_set_feedback(VariableItem* item) {
+    furi_assert(item);
+
+    MineSweeperApp* app = variable_item_get_context(item);
+
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    app->feedback_enabled = index;
+
+    FURI_LOG_I(TAG, "FEEDBACK CALLBACK INDEX %d", app->feedback_enabled);
+
+    variable_item_set_current_value_text(item, ((index) ? "Enabled" : "Disabled"));
+
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, MineSweeperSettingsScreenEventFeedbackChange);
+}
+
 static void minesweeper_scene_settings_screen_set_info(VariableItem* item) {
     furi_assert(item);
 
@@ -247,6 +265,16 @@ void minesweeper_scene_settings_screen_on_enter(void* context) {
 
     variable_item_set_current_value_text(item, settings_screen_verifier_text[idx]);
 
+    variable_item_set_current_value_text(item, settings_screen_verifier_text[idx]);
+
+    // Set sound feedback item
+    item = variable_item_list_add(
+        va, "Feedback", 2, minesweeper_scene_settings_screen_set_feedback, app);
+
+    variable_item_set_current_value_index(item, app->feedback_enabled);
+
+    variable_item_set_current_value_text(item, ((app->feedback_enabled) ? "Enabled" : "Disabled"));
+
     // Set info item
     item = variable_item_list_add(
         va, "Right For Info", 2, minesweeper_scene_settings_screen_set_info, app);
@@ -291,19 +319,23 @@ bool minesweeper_scene_settings_screen_on_event(void* context, SceneManagerEvent
 
         case MineSweeperSettingsScreenEventInfoChange:
 
-            scene_manager_next_scene(app->scene_manager, MineSweeperSceneInfoScreen);
+        case MineSweeperSettingsScreenEventFeedbackChange:
+            // If only the feedback option is changed we can just save without restarting
+            mine_sweeper_save_settings(app);
             break;
-
         default:
             break;
         };
         consumed = true;
 
     } else if(event.type == SceneManagerEventTypeBack) {
-        // If there are changes in the width, height, or difficulty go to confirmation scren
         if(app->is_settings_changed) {
+            // If there are changes in the width, height, or difficulty go to confirmation screen for restart
+
             scene_manager_next_scene(app->scene_manager, MineSweeperSceneConfirmationScreen);
         } else {
+            // Otherwise just go back
+
             memset(&app->t_settings_info, 0, sizeof(app->t_settings_info));
 
             if(!scene_manager_search_and_switch_to_previous_scene(

+ 410 - 482
minesweeper/views/minesweeper_game_screen.c

@@ -1,12 +1,4 @@
 #include "minesweeper_game_screen.h"
-#include "minesweeper_redux_icons.h"
-
-#include <gui/elements.h>
-#include <gui/icon_animation.h>
-#include <input/input.h>
-
-#include <furi.h>
-#include <furi_hal.h>
 
 static const Icon* tile_icons[13] = {
     &I_tile_empty_8x8,
@@ -77,8 +69,9 @@ typedef struct {
     uint32_t start_tick;
     FuriString* info_str;
     bool ensure_solvable_board;
-    bool is_win_triggered;
+    bool is_restart_triggered;
     bool is_holding_down_button;
+    bool has_lost_game;
 } MineSweeperGameScreenModel;
 
 // Multipliers for ratio of mines to tiles
@@ -143,15 +136,15 @@ static void mine_sweeper_game_screen_set_board_information(
 
 static bool try_clear_surrounding_tiles(MineSweeperGameScreenModel* model);
 
-static Point bfs_to_closest_tile(MineSweeperGameScreenModel* model);
+static void
+    bfs_to_closest_tile(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model);
 
 // Currently not using enter/exit callback
 static void mine_sweeper_game_screen_view_enter(void* context);
 static void mine_sweeper_game_screen_view_exit(void* context);
 
 // Different input/draw callbacks for play/win/lose state
-static void mine_sweeper_game_screen_view_win_draw_callback(Canvas* canvas, void* _model);
-static void mine_sweeper_game_screen_view_lose_draw_callback(Canvas* canvas, void* _model);
+static void mine_sweeper_game_screen_view_end_draw_callback(Canvas* canvas, void* _model);
 static void mine_sweeper_game_screen_view_play_draw_callback(Canvas* canvas, void* _model);
 
 // These consolidate the function calls for led/haptic/sound for specific events
@@ -163,6 +156,17 @@ static void mine_sweeper_oob_effect(void* context);
 static void mine_sweeper_lose_effect(void* context);
 static void mine_sweeper_win_effect(void* context);
 
+static inline bool handle_player_move(
+    MineSweeperGameScreen* instance,
+    MineSweeperGameScreenModel* model,
+    InputEvent* event);
+static int8_t
+    handle_short_ok_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model);
+static int8_t
+    handle_long_ok_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model);
+static bool
+    handle_long_back_flag_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model);
+
 static bool mine_sweeper_game_screen_view_end_input_callback(InputEvent* event, void* context);
 static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event, void* context);
 
@@ -197,7 +201,7 @@ static void setup_board(MineSweeperGameScreen* instance) {
      * and manipulate then save to actual model
      */
     MineSweeperGameScreenTileType tiles[MINESWEEPER_BOARD_MAX_TILES];
-    memset(&tiles, MineSweeperGameScreenTileNone, sizeof(tiles));
+    memset(&tiles, MineSweeperGameScreenTileZero, sizeof(tiles));
 
     // Randomly place tiles except in the corners to help guarantee solvability
     for(uint16_t i = 0; i < num_mines; i++) {
@@ -272,7 +276,8 @@ static void setup_board(MineSweeperGameScreen* instance) {
             model->curr_pos.y_abs = 0;
             model->right_boundary = MINESWEEPER_SCREEN_TILE_WIDTH;
             model->bottom_boundary = MINESWEEPER_SCREEN_TILE_HEIGHT;
-            model->is_win_triggered = false;
+            model->is_restart_triggered = false;
+            model->has_lost_game = false;
         },
         true);
 }
@@ -638,7 +643,7 @@ static bool try_clear_surrounding_tiles(MineSweeperGameScreenModel* model) {
 
     MineSweeperTile tile = model->board[curr_pos_1d];
 
-    // Return true if tile is zero tile or not cleared
+    // Return false if tile is zero tile or not cleared
     if(tile.tile_state != MineSweeperGameScreenTileStateCleared ||
        tile.tile_type == MineSweeperGameScreenTileZero) {
         return false;
@@ -695,7 +700,8 @@ static bool try_clear_surrounding_tiles(MineSweeperGameScreenModel* model) {
  * Function is used on a long backpress on a cleared tile and returns the position
  * of the first found uncleared tile using a bfs search
  */
-static inline Point bfs_to_closest_tile(MineSweeperGameScreenModel* model) {
+static void
+    bfs_to_closest_tile(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model) {
     furi_assert(model);
 
     // Init both the set and dequeue
@@ -706,7 +712,7 @@ static inline Point bfs_to_closest_tile(MineSweeperGameScreenModel* model) {
     point_set_init(set);
 
     // Return the value in this point
-    Point result;
+    Point result = (Point){.x = 0, .y = 0};
 
     // Point_t pos will be used to keep track of the current point
     Point_t pos;
@@ -757,100 +763,317 @@ static inline Point bfs_to_closest_tile(MineSweeperGameScreenModel* model) {
     point_set_clear(set);
     point_deq_clear(deq);
 
-    return result;
+    // Save cursor to new closest tile position
+    // If the cursor moves outisde of the model boundaries we need to
+    // move the boundary appropriately
+
+    model->curr_pos.x_abs = result.x;
+    model->curr_pos.y_abs = result.y;
+
+    bool is_outside_top_boundary = model->curr_pos.x_abs <
+                                   (model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT);
+
+    bool is_outside_bottom_boundary = model->curr_pos.x_abs >= model->bottom_boundary;
+
+    bool is_outside_left_boundary = model->curr_pos.y_abs <
+                                    (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH);
+
+    bool is_outside_right_boundary = model->curr_pos.y_abs >= model->right_boundary;
+
+    if(is_outside_top_boundary) {
+        model->bottom_boundary = model->curr_pos.x_abs + MINESWEEPER_SCREEN_TILE_HEIGHT;
+    } else if(is_outside_bottom_boundary) {
+        model->bottom_boundary = model->curr_pos.x_abs + 1;
+    }
+
+    if(is_outside_right_boundary) {
+        model->right_boundary = model->curr_pos.y_abs + 1;
+    } else if(is_outside_left_boundary) {
+        model->right_boundary = model->curr_pos.y_abs + MINESWEEPER_SCREEN_TILE_WIDTH;
+    }
+
+    mine_sweeper_play_happy_bump(instance->context);
 }
 
-static void mine_sweeper_game_screen_view_enter(void* context) {
+static void mine_sweeper_short_ok_effect(void* context) {
     furi_assert(context);
-    UNUSED(context);
+    MineSweeperGameScreen* instance = context;
+
+    mine_sweeper_led_blink_magenta(instance->context);
+    mine_sweeper_play_ok_sound(instance->context);
+    mine_sweeper_play_happy_bump(instance->context);
+    mine_sweeper_stop_all_sound(instance->context);
 }
 
-static void mine_sweeper_game_screen_view_exit(void* context) {
+static void mine_sweeper_long_ok_effect(void* context) {
     furi_assert(context);
-    UNUSED(context);
+    MineSweeperGameScreen* instance = context;
+
+    mine_sweeper_led_blink_magenta(instance->context);
+    mine_sweeper_play_ok_sound(instance->context);
+    mine_sweeper_play_long_ok_bump(instance->context);
+    mine_sweeper_stop_all_sound(instance->context);
 }
 
-static void mine_sweeper_game_screen_view_win_draw_callback(Canvas* canvas, void* _model) {
-    furi_assert(canvas);
-    furi_assert(_model);
-    MineSweeperGameScreenModel* model = _model;
+static void mine_sweeper_flag_effect(void* context) {
+    furi_assert(context);
+    MineSweeperGameScreen* instance = context;
 
-    canvas_clear(canvas);
+    mine_sweeper_led_blink_cyan(instance->context);
+    mine_sweeper_play_flag_sound(instance->context);
+    mine_sweeper_play_happy_bump(instance->context);
+    mine_sweeper_stop_all_sound(instance->context);
+}
 
-    canvas_set_color(canvas, ColorBlack);
+static void mine_sweeper_move_effect(void* context) {
+    furi_assert(context);
+    MineSweeperGameScreen* instance = context;
 
-    uint16_t cursor_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
+    mine_sweeper_play_happy_bump(instance->context);
+}
 
-    for(uint8_t x_rel = 0; x_rel < MINESWEEPER_SCREEN_TILE_HEIGHT; x_rel++) {
-        uint16_t x_abs = (model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT) + x_rel;
+static void mine_sweeper_oob_effect(void* context) {
+    furi_assert(context);
+    MineSweeperGameScreen* instance = context;
 
-        for(uint8_t y_rel = 0; y_rel < MINESWEEPER_SCREEN_TILE_WIDTH; y_rel++) {
-            uint16_t y_abs = (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH) + y_rel;
+    mine_sweeper_led_blink_red(instance->context);
+    mine_sweeper_play_flag_sound(instance->context);
+    mine_sweeper_play_oob_bump(instance->context);
+    mine_sweeper_stop_all_sound(instance->context);
+}
 
-            uint16_t curr_rendering_tile_pos_1d = x_abs * model->board_width + y_abs;
-            MineSweeperTile tile = model->board[curr_rendering_tile_pos_1d];
+static void mine_sweeper_lose_effect(void* context) {
+    furi_assert(context);
+    MineSweeperGameScreen* instance = context;
 
-            if(cursor_pos_1d == curr_rendering_tile_pos_1d) {
-                canvas_set_color(canvas, ColorWhite);
-            } else {
-                canvas_set_color(canvas, ColorBlack);
-            }
+    mine_sweeper_led_set_rgb(instance->context, 255, 0, 000);
+    mine_sweeper_play_lose_sound(instance->context);
+    mine_sweeper_play_lose_bump(instance->context);
+    mine_sweeper_stop_all_sound(instance->context);
+}
 
-            canvas_draw_icon(
-                canvas,
-                y_rel * icon_get_width(tile.icon_element.icon),
-                x_rel * icon_get_height(tile.icon_element.icon),
-                tile.icon_element.icon);
+static void mine_sweeper_win_effect(void* context) {
+    furi_assert(context);
+    MineSweeperGameScreen* instance = context;
+
+    mine_sweeper_led_set_rgb(instance->context, 0, 0, 255);
+    mine_sweeper_play_win_sound(instance->context);
+    mine_sweeper_play_win_bump(instance->context);
+    mine_sweeper_stop_all_sound(instance->context);
+}
+
+static inline bool handle_player_move(
+    MineSweeperGameScreen* instance,
+    MineSweeperGameScreenModel* model,
+    InputEvent* event) {
+    bool consumed = false;
+    bool is_outside_boundary;
+
+    switch(event->key) {
+    case InputKeyUp:
+        (model->curr_pos.x_abs - 1 < 0) ? mine_sweeper_oob_effect(instance) :
+                                          mine_sweeper_move_effect(instance);
+
+        model->curr_pos.x_abs = (model->curr_pos.x_abs - 1 < 0) ? 0 : model->curr_pos.x_abs - 1;
+
+        is_outside_boundary = model->curr_pos.x_abs <
+                              (model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT);
+
+        if(is_outside_boundary) {
+            model->bottom_boundary--;
+        }
+
+        consumed = true;
+        break;
+
+    case InputKeyDown:
+
+        (model->curr_pos.x_abs + 1 >= model->board_height) ? mine_sweeper_oob_effect(instance) :
+                                                             mine_sweeper_move_effect(instance);
+
+        model->curr_pos.x_abs = (model->curr_pos.x_abs + 1 >= model->board_height) ?
+                                    model->board_height - 1 :
+                                    model->curr_pos.x_abs + 1;
+
+        is_outside_boundary = model->curr_pos.x_abs >= model->bottom_boundary;
+
+        if(is_outside_boundary) {
+            model->bottom_boundary++;
+        }
+
+        consumed = true;
+        break;
+
+    case InputKeyLeft:
+        (model->curr_pos.y_abs - 1 < 0) ? mine_sweeper_oob_effect(instance) :
+                                          mine_sweeper_move_effect(instance);
+
+        model->curr_pos.y_abs = (model->curr_pos.y_abs - 1 < 0) ? 0 : model->curr_pos.y_abs - 1;
+
+        is_outside_boundary = model->curr_pos.y_abs <
+                              (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH);
+
+        if(is_outside_boundary) {
+            model->right_boundary--;
+        }
+
+        consumed = true;
+        break;
+
+    case InputKeyRight:
+        (model->curr_pos.y_abs + 1 >= model->board_width) ? mine_sweeper_oob_effect(instance) :
+                                                            mine_sweeper_move_effect(instance);
+
+        model->curr_pos.y_abs = (model->curr_pos.y_abs + 1 >= model->board_width) ?
+                                    model->board_width - 1 :
+                                    model->curr_pos.y_abs + 1;
+
+        is_outside_boundary = model->curr_pos.y_abs >= model->right_boundary;
+
+        if(is_outside_boundary) {
+            model->right_boundary++;
         }
+
+        consumed = true;
+        break;
+
+    default:
+        consumed = true;
+        break;
     }
 
-    canvas_set_color(canvas, ColorBlack);
-    // If any borders are at the limits of the game board we draw a border line
+    return consumed;
+}
 
-    // Right border
-    if(model->right_boundary == model->board_width) {
-        canvas_draw_line(canvas, 127, 0, 127, 63 - 8);
+static int8_t
+    handle_short_ok_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model) {
+    furi_assert(instance);
+    furi_assert(model);
+
+    uint16_t curr_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
+    bool is_win_condition_triggered = false;
+    bool is_lose_condition_triggered = false;
+
+    MineSweeperGameScreenTileState state = model->board[curr_pos_1d].tile_state;
+    MineSweeperGameScreenTileType type = model->board[curr_pos_1d].tile_type;
+
+    // LOSE/WIN CONDITION OR TILE CLEAR
+    if(state == MineSweeperGameScreenTileStateUncleared && type == MineSweeperGameScreenTileMine) {
+        is_lose_condition_triggered = true;
+        model->board[curr_pos_1d].tile_state = MineSweeperGameScreenTileStateCleared;
+
+    } else if(state == MineSweeperGameScreenTileStateUncleared) {
+        uint16_t tiles_cleared = bfs_tile_clear(
+            model->board,
+            model->board_width,
+            model->board_height,
+            (uint16_t)model->curr_pos.x_abs,
+            (uint16_t)model->curr_pos.y_abs);
+
+        model->tiles_left -= tiles_cleared;
+
+        // Check win condition
+        if(model->mines_left == 0 && model->flags_left == 0 && model->tiles_left == 0) {
+            is_win_condition_triggered = true;
+        } else {
+            // if not met play ok effect
+            mine_sweeper_short_ok_effect(instance);
+        }
     }
 
-    // Left border
-    if((model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH) == 0) {
-        canvas_draw_line(canvas, 0, 0, 0, 63 - 8);
+    if(is_lose_condition_triggered) {
+        return -1;
+    } else if(is_win_condition_triggered) {
+        return 1;
     }
 
-    // Bottom border
-    if(model->bottom_boundary == model->board_height) {
-        canvas_draw_line(canvas, 0, 63 - 8, 127, 63 - 8);
+    return 0;
+}
+
+static int8_t
+    handle_long_ok_input(MineSweeperGameScreen* instance, MineSweeperGameScreenModel* model) {
+    furi_assert(instance);
+    furi_assert(model);
+
+    uint16_t curr_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
+    bool is_win_condition_triggered = false;
+    bool is_lose_condition_triggered = false;
+
+    MineSweeperGameScreenTileType type = model->board[curr_pos_1d].tile_type;
+
+    // Try to clear surrounding tiles if correct number is flagged.
+    is_lose_condition_triggered = try_clear_surrounding_tiles(model);
+    model->is_holding_down_button = true;
+
+    // Check win condition
+    if(model->mines_left == 0 && model->flags_left == 0 && model->tiles_left == 0) {
+        is_win_condition_triggered = true;
     }
 
-    // Top border
-    if((model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT) == 0) {
-        canvas_draw_line(canvas, 0, 0, 127, 0);
+    // We need to check if it is ok to play this or else we conflict
+    // with the lose effect and crash
+    if(!is_win_condition_triggered && !is_lose_condition_triggered &&
+       type != MineSweeperGameScreenTileZero) {
+        mine_sweeper_long_ok_effect(instance);
     }
 
-    // Draw win text
-    furi_string_printf(model->info_str, "YOU WIN!");
+    if(is_lose_condition_triggered) {
+        return -1;
+    } else if(is_win_condition_triggered) {
+        return 1;
+    }
 
-    canvas_draw_str_aligned(
-        canvas, 0, 64 - 7, AlignLeft, AlignTop, furi_string_get_cstr(model->info_str));
+    return 0;
+}
 
-    // Draw time text
-    uint32_t ticks_elapsed = furi_get_tick() - model->start_tick;
-    uint32_t sec = ticks_elapsed / furi_kernel_get_tick_frequency();
-    uint32_t minutes = sec / 60;
-    sec = sec % 60;
+static bool handle_long_back_flag_input(
+    MineSweeperGameScreen* instance,
+    MineSweeperGameScreenModel* model) {
+    furi_assert(instance);
+    furi_assert(model);
 
-    furi_string_printf(model->info_str, "%02ld:%02ld", minutes, sec);
+    uint16_t curr_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
+    MineSweeperGameScreenTileState state = model->board[curr_pos_1d].tile_state;
 
-    canvas_draw_str_aligned(
-        canvas,
-        126 - canvas_string_width(canvas, furi_string_get_cstr(model->info_str)),
-        64 - 7,
-        AlignLeft,
-        AlignTop,
-        furi_string_get_cstr(model->info_str));
+    bool is_win_condition_triggered = false;
+
+    if(state == MineSweeperGameScreenTileStateFlagged) {
+        if(model->board[curr_pos_1d].tile_type == MineSweeperGameScreenTileMine)
+            model->mines_left++;
+        model->board[curr_pos_1d].tile_state = MineSweeperGameScreenTileStateUncleared;
+        model->flags_left++;
+
+    } else if(model->flags_left > 0) {
+        if(model->board[curr_pos_1d].tile_type == MineSweeperGameScreenTileMine)
+            model->mines_left--;
+        model->board[curr_pos_1d].tile_state = MineSweeperGameScreenTileStateFlagged;
+        model->flags_left--;
+    }
+
+    // WIN CONDITION
+    // This can be a win condition where the non-mine tiles are cleared and they place the last flag
+    if(model->flags_left == 0 && model->mines_left == 0 && model->tiles_left == 0) {
+        is_win_condition_triggered = true;
+        mine_sweeper_win_effect(instance);
+        mine_sweeper_led_set_rgb(instance->context, 0, 0, 255);
+    } else {
+        mine_sweeper_flag_effect(instance);
+    }
+
+    return is_win_condition_triggered;
+}
+
+static void mine_sweeper_game_screen_view_enter(void* context) {
+    furi_assert(context);
+    UNUSED(context);
 }
 
-static void mine_sweeper_game_screen_view_lose_draw_callback(Canvas* canvas, void* _model) {
+static void mine_sweeper_game_screen_view_exit(void* context) {
+    furi_assert(context);
+    UNUSED(context);
+}
+
+static void mine_sweeper_game_screen_view_end_draw_callback(Canvas* canvas, void* _model) {
     furi_assert(canvas);
     furi_assert(_model);
     MineSweeperGameScreenModel* model = _model;
@@ -905,8 +1128,16 @@ static void mine_sweeper_game_screen_view_lose_draw_callback(Canvas* canvas, voi
         canvas_draw_line(canvas, 0, 0, 127, 0);
     }
 
-    // Draw lose text
-    furi_string_printf(model->info_str, "YOU LOSE!");
+    const char* end_status_str = "";
+
+    if(model->has_lost_game) {
+        end_status_str = "YOU LOSE!  PRESS OK.\0";
+    } else {
+        end_status_str = "YOU WIN!   PRESS OK.\0";
+    }
+
+    // Draw win/lose text
+    furi_string_printf(model->info_str, "%s", end_status_str);
 
     canvas_draw_str_aligned(
         canvas, 0, 64 - 7, AlignLeft, AlignTop, furi_string_get_cstr(model->info_str));
@@ -1041,73 +1272,6 @@ static void mine_sweeper_game_screen_view_play_draw_callback(Canvas* canvas, voi
         furi_string_get_cstr(model->info_str));
 }
 
-static void mine_sweeper_short_ok_effect(void* context) {
-    furi_assert(context);
-    MineSweeperGameScreen* instance = context;
-
-    mine_sweeper_led_blink_magenta(instance->context);
-    mine_sweeper_play_ok_sound(instance->context);
-    mine_sweeper_play_happy_bump(instance->context);
-    mine_sweeper_stop_all_sound(instance->context);
-}
-
-static void mine_sweeper_long_ok_effect(void* context) {
-    furi_assert(context);
-    MineSweeperGameScreen* instance = context;
-
-    mine_sweeper_led_blink_magenta(instance->context);
-    mine_sweeper_play_ok_sound(instance->context);
-    mine_sweeper_play_long_ok_bump(instance->context);
-    mine_sweeper_stop_all_sound(instance->context);
-}
-
-static void mine_sweeper_flag_effect(void* context) {
-    furi_assert(context);
-    MineSweeperGameScreen* instance = context;
-
-    mine_sweeper_led_blink_cyan(instance->context);
-    mine_sweeper_play_flag_sound(instance->context);
-    mine_sweeper_play_happy_bump(instance->context);
-    mine_sweeper_stop_all_sound(instance->context);
-}
-
-static void mine_sweeper_move_effect(void* context) {
-    furi_assert(context);
-    MineSweeperGameScreen* instance = context;
-
-    mine_sweeper_play_happy_bump(instance->context);
-}
-
-static void mine_sweeper_oob_effect(void* context) {
-    furi_assert(context);
-    MineSweeperGameScreen* instance = context;
-
-    mine_sweeper_led_blink_red(instance->context);
-    mine_sweeper_play_flag_sound(instance->context);
-    mine_sweeper_play_oob_bump(instance->context);
-    mine_sweeper_stop_all_sound(instance->context);
-}
-
-static void mine_sweeper_lose_effect(void* context) {
-    furi_assert(context);
-    MineSweeperGameScreen* instance = context;
-
-    mine_sweeper_led_set_rgb(instance->context, 255, 0, 000);
-    mine_sweeper_play_lose_sound(instance->context);
-    mine_sweeper_play_lose_bump(instance->context);
-    mine_sweeper_stop_all_sound(instance->context);
-}
-
-static void mine_sweeper_win_effect(void* context) {
-    furi_assert(context);
-    MineSweeperGameScreen* instance = context;
-
-    mine_sweeper_led_set_rgb(instance->context, 0, 0, 255);
-    mine_sweeper_play_win_sound(instance->context);
-    mine_sweeper_play_win_bump(instance->context);
-    mine_sweeper_stop_all_sound(instance->context);
-}
-
 static bool mine_sweeper_game_screen_view_end_input_callback(InputEvent* event, void* context) {
     furi_assert(context);
     furi_assert(event);
@@ -1119,104 +1283,58 @@ static bool mine_sweeper_game_screen_view_end_input_callback(InputEvent* event,
         instance->view,
         MineSweeperGameScreenModel * model,
         {
-            if(event->type == InputTypeRelease) {
+            if(model->is_holding_down_button && event->type == InputTypeRelease) {
+                //When we lose we are holding the button down, record this release
+
                 model->is_holding_down_button = false;
                 consumed = true;
+            }
 
-            } else if(
-                !model->is_holding_down_button &&
-                (event->type == InputTypePress || event->type == InputTypeRepeat)) {
-                bool is_outside_boundary;
-                switch(event->key) {
-                case InputKeyUp:
-                    model->curr_pos.x_abs =
-                        (model->curr_pos.x_abs - 1 < 0) ? 0 : model->curr_pos.x_abs - 1;
-
-                    is_outside_boundary = model->curr_pos.x_abs < (model->bottom_boundary -
-                                                                   MINESWEEPER_SCREEN_TILE_HEIGHT);
-
-                    if(is_outside_boundary) {
-                        model->bottom_boundary--;
-                    }
-
-                    consumed = true;
-                    break;
-
-                case InputKeyDown:
-                    model->curr_pos.x_abs = (model->curr_pos.x_abs + 1 >= model->board_height) ?
-                                                model->board_height - 1 :
-                                                model->curr_pos.x_abs + 1;
-
-                    is_outside_boundary = model->curr_pos.x_abs >= model->bottom_boundary;
-
-                    if(is_outside_boundary) {
-                        model->bottom_boundary++;
-                    }
-
-                    consumed = true;
-                    break;
-
-                case InputKeyLeft:
-                    model->curr_pos.y_abs =
-                        (model->curr_pos.y_abs - 1 < 0) ? 0 : model->curr_pos.y_abs - 1;
-
-                    is_outside_boundary = model->curr_pos.y_abs <
-                                          (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH);
-
-                    if(is_outside_boundary) {
-                        model->right_boundary--;
-                    }
-
-                    consumed = true;
-                    break;
-
-                case InputKeyRight:
-                    model->curr_pos.y_abs = (model->curr_pos.y_abs + 1 >= model->board_width) ?
-                                                model->board_width - 1 :
-                                                model->curr_pos.y_abs + 1;
+            if(!model->is_holding_down_button && event->key == InputKeyOk &&
+               event->type == InputTypeRelease) {
+                // After release when user presses and releases ok we want to restart the next time this function is pressed
 
-                    is_outside_boundary = model->curr_pos.y_abs >= model->right_boundary;
+                model->is_restart_triggered = true;
+                consumed = true;
 
-                    if(is_outside_boundary) {
-                        model->right_boundary++;
-                    }
+            } else if(
+                !model->is_holding_down_button && model->is_restart_triggered &&
+                event->key == InputKeyOk) {
+                // After restart flagged is triggered this should also trigger and restart the game
 
-                    consumed = true;
-                    break;
+                mine_sweeper_led_reset(instance->context);
 
-                default: // Anything other than movement around the screen should restart game
-                    mine_sweeper_led_reset(instance->context);
+                mine_sweeper_game_screen_reset_clock(instance);
+                view_set_draw_callback(
+                    instance->view, mine_sweeper_game_screen_view_play_draw_callback);
+                view_set_input_callback(
+                    instance->view, mine_sweeper_game_screen_view_play_input_callback);
 
-                    mine_sweeper_game_screen_reset_clock(instance);
-                    view_set_draw_callback(
-                        instance->view, mine_sweeper_game_screen_view_play_draw_callback);
-                    view_set_input_callback(
-                        instance->view, mine_sweeper_game_screen_view_play_input_callback);
+                // Here we are going to generate a valid map for the player
+                bool is_valid_board = false;
 
-                    // Here we are going to generate a valid map for the player
-                    bool is_valid_board = false;
+                size_t memsz = sizeof(MineSweeperTile) * MINESWEEPER_BOARD_MAX_TILES;
 
-                    size_t memsz = sizeof(MineSweeperTile) * MINESWEEPER_BOARD_MAX_TILES;
+                do {
+                    setup_board(instance);
 
-                    do {
-                        setup_board(instance);
+                    memset(board_t, 0, memsz);
+                    memcpy(
+                        board_t,
+                        model->board,
+                        sizeof(MineSweeperTile) * (model->board_width * model->board_height));
 
-                        memset(board_t, 0, memsz);
-                        memcpy(
-                            board_t,
-                            model->board,
-                            sizeof(MineSweeperTile) * (model->board_width * model->board_height));
+                    is_valid_board = check_board_with_verifier(
+                        board_t, model->board_width, model->board_height, model->mines_left);
 
-                        is_valid_board = check_board_with_verifier(
-                            board_t, model->board_width, model->board_height, model->mines_left);
+                } while(model->ensure_solvable_board && !is_valid_board);
 
-                    } while(model->ensure_solvable_board && !is_valid_board);
+                consumed = true;
 
-                    consumed = true;
-                    break;
-                }
+            } else if((event->type == InputTypePress || event->type == InputTypeRepeat)) {
+                // Any other input we consider generic player movement
 
-                consumed = true;
+                consumed = handle_player_move(instance, model, event);
             }
         },
         false);
@@ -1231,300 +1349,110 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
     MineSweeperGameScreen* instance = context;
     bool consumed = false;
 
-    // Checking button types
+    with_view_model(
+        instance->view,
+        MineSweeperGameScreenModel * model,
+        {
+            // Checking button types
 
-    if(event->type == InputTypeRelease) {
-        with_view_model(
-            instance->view,
-            MineSweeperGameScreenModel * model,
-            {
+            if(event->type == InputTypeRelease) {
                 model->is_holding_down_button = false;
                 consumed = true;
-            },
-            true);
-    }
-
-    if(!consumed &&
-       event->key == InputKeyOk) { // Attempt to Clear Space !! THIS CAN BE A LOSE CONDITION
 
-        bool is_lose_condition_triggered = false;
-        bool is_win_condition_triggered = false;
+            } else if(event->key == InputKeyOk) { // Attempt to Clear Space !! THIS CAN BE A LOSE CONDITION
 
-        with_view_model(
-            instance->view,
-            MineSweeperGameScreenModel * model,
-            {
-                uint16_t curr_pos_1d =
-                    model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
+                bool is_lose_condition_triggered = false;
+                bool is_win_condition_triggered = false;
 
                 if(!model->is_holding_down_button && event->type == InputTypePress) {
-                    MineSweeperGameScreenTileState state = model->board[curr_pos_1d].tile_state;
-                    MineSweeperGameScreenTileType type = model->board[curr_pos_1d].tile_type;
-
-                    // LOSE/WIN CONDITION OR TILE CLEAR
-                    if(state == MineSweeperGameScreenTileStateUncleared &&
-                       type == MineSweeperGameScreenTileMine) {
-                        is_lose_condition_triggered = true;
-                        model->board[curr_pos_1d].tile_state =
-                            MineSweeperGameScreenTileStateCleared;
-
-                    } else if(state == MineSweeperGameScreenTileStateUncleared) {
-                        uint16_t tiles_cleared = bfs_tile_clear(
-                            model->board,
-                            model->board_width,
-                            model->board_height,
-                            (uint16_t)model->curr_pos.x_abs,
-                            (uint16_t)model->curr_pos.y_abs);
-
-                        model->tiles_left -= tiles_cleared;
-
-                        // Check win condition
-                        if(model->mines_left == 0 && model->flags_left == 0 &&
-                           model->tiles_left == 0) {
-                            is_win_condition_triggered = true;
-                        } else {
-                            // if not met play ok effect
-                            mine_sweeper_short_ok_effect(instance);
-                        }
+                    //ret : -1 lose condition, 1 win condition, 0 neutral
+                    int8_t ret = handle_short_ok_input(instance, model);
+
+                    if(ret != 0) {
+                        (ret == -1) ? (is_lose_condition_triggered = true) :
+                                      (is_win_condition_triggered = true);
                     }
 
                     // LOSE/WIN CONDITION OR CLEAR SURROUNDING
                 } else if(!model->is_holding_down_button && event->type == InputTypeLong) {
-                    // Try to clear surrounding tiles if correct number is flagged.
-                    is_lose_condition_triggered = try_clear_surrounding_tiles(model);
-                    model->is_holding_down_button = true;
-
-                    // Check win condition
-                    if(model->mines_left == 0 && model->flags_left == 0 &&
-                       model->tiles_left == 0) {
-                        is_win_condition_triggered = true;
-                    }
+                    //ret : -1 lose condition, 1 win condition, 0 neutral
+                    int8_t ret = handle_long_ok_input(instance, model);
 
-                    // We need to check if it is ok to play this or else we conflict
-                    // with the lose effect and crash
-                    if(!is_win_condition_triggered && !is_lose_condition_triggered &&
-                       model->board[curr_pos_1d].tile_type != MineSweeperGameScreenTileZero) {
-                        mine_sweeper_long_ok_effect(instance);
+                    if(ret != 0) {
+                        (ret == -1) ? (is_lose_condition_triggered = true) :
+                                      (is_win_condition_triggered = true);
                     }
                 }
-            },
-            true);
 
-        // Check  if win or lose condition was triggered on OK press
-        if(is_lose_condition_triggered) {
-            mine_sweeper_lose_effect(instance);
-            view_set_draw_callback(
-                instance->view, mine_sweeper_game_screen_view_lose_draw_callback);
-            view_set_input_callback(
-                instance->view, mine_sweeper_game_screen_view_end_input_callback);
-        } else if(is_win_condition_triggered) {
-            mine_sweeper_win_effect(instance);
-            view_set_draw_callback(
-                instance->view, mine_sweeper_game_screen_view_win_draw_callback);
-            view_set_input_callback(
-                instance->view, mine_sweeper_game_screen_view_end_input_callback);
-        }
-
-        consumed = true;
-    }
-
-    if(!consumed &&
-       (event->key == InputKeyBack)) { // We can use holding the back button for either
-        // Setting a flag on a covered tile, or moving to
-        // the next closest covered tile on when on a uncovered
-        // tile
-
-        if(event->type == InputTypeLong ||
-           event->type == InputTypeRepeat) { // Only process longer back keys;
-            // short presses should take
-            // us to the menu
-            with_view_model(
-                instance->view,
-                MineSweeperGameScreenModel * model,
-                {
-                    uint16_t curr_pos_1d =
-                        model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
-
-                    MineSweeperGameScreenTileState state = model->board[curr_pos_1d].tile_state;
+                // Check  if win or lose condition was triggered on OK press
+                if(is_lose_condition_triggered) {
+                    model->has_lost_game = true;
+                    mine_sweeper_lose_effect(instance);
 
-                    if(state == MineSweeperGameScreenTileStateCleared) {
-                        // BFS to closest uncovered position
-                        Point res = bfs_to_closest_tile(model);
+                    view_set_draw_callback(
+                        instance->view, mine_sweeper_game_screen_view_end_draw_callback);
+                    view_set_input_callback(
+                        instance->view, mine_sweeper_game_screen_view_end_input_callback);
 
-                        // Save cursor to new closest tile position
-                        // If the cursor moves outisde of the model boundaries we need to
-                        // move the boundary appropriately
+                } else if(is_win_condition_triggered) {
+                    mine_sweeper_win_effect(instance);
 
-                        model->curr_pos.x_abs = res.x;
-                        model->curr_pos.y_abs = res.y;
+                    view_set_draw_callback(
+                        instance->view, mine_sweeper_game_screen_view_end_draw_callback);
+                    view_set_input_callback(
+                        instance->view, mine_sweeper_game_screen_view_end_input_callback);
+                }
 
-                        bool is_outside_top_boundary =
-                            model->curr_pos.x_abs <
-                            (model->bottom_boundary - MINESWEEPER_SCREEN_TILE_HEIGHT);
+                consumed = true;
 
-                        bool is_outside_bottom_boundary = model->curr_pos.x_abs >=
-                                                          model->bottom_boundary;
+            } else if(event->key == InputKeyBack) { // We can use holding the back button for either
+                // Setting a flag on a covered tile, or moving to
+                // the next closest covered tile on when on a uncovered
+                // tile
 
-                        bool is_outside_left_boundary =
-                            model->curr_pos.y_abs <
-                            (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH);
+                if(event->type == InputTypeLong ||
+                   event->type == InputTypeRepeat) { // Only process longer back keys;
+                    // short presses should take
+                    // us to the menu
 
-                        bool is_outside_right_boundary = model->curr_pos.y_abs >=
-                                                         model->right_boundary;
+                    bool is_win_condition_triggered = false;
 
-                        if(is_outside_top_boundary) {
-                            model->bottom_boundary =
-                                model->curr_pos.x_abs + MINESWEEPER_SCREEN_TILE_HEIGHT;
-                        } else if(is_outside_bottom_boundary) {
-                            model->bottom_boundary = model->curr_pos.x_abs + 1;
-                        }
+                    uint16_t curr_pos_1d =
+                        model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
+                    MineSweeperGameScreenTileState state = model->board[curr_pos_1d].tile_state;
 
-                        if(is_outside_right_boundary) {
-                            model->right_boundary = model->curr_pos.y_abs + 1;
-                        } else if(is_outside_left_boundary) {
-                            model->right_boundary =
-                                model->curr_pos.y_abs + MINESWEEPER_SCREEN_TILE_WIDTH;
-                        }
+                    if(state == MineSweeperGameScreenTileStateCleared) {
+                        // BFS to closest uncovered position
+                        bfs_to_closest_tile(instance, model);
 
-                        mine_sweeper_play_happy_bump(instance->context);
                         model->is_holding_down_button = true;
 
                         // Flag or Unflag tile and check win condition
                     } else if(
                         !model->is_holding_down_button &&
-                        (state == MineSweeperGameScreenTileStateUncleared ||
-                         state == MineSweeperGameScreenTileStateFlagged)) {
-                        if(state == MineSweeperGameScreenTileStateFlagged) {
-                            if(model->board[curr_pos_1d].tile_type ==
-                               MineSweeperGameScreenTileMine)
-                                model->mines_left++;
-                            model->board[curr_pos_1d].tile_state =
-                                MineSweeperGameScreenTileStateUncleared;
-                            model->flags_left++;
-                            model->is_holding_down_button = true;
-
-                        } else if(model->flags_left > 0) {
-                            if(model->board[curr_pos_1d].tile_type ==
-                               MineSweeperGameScreenTileMine)
-                                model->mines_left--;
-                            model->board[curr_pos_1d].tile_state =
-                                MineSweeperGameScreenTileStateFlagged;
-                            model->flags_left--;
-                            model->is_holding_down_button = true;
-                        }
-
-                        // WIN CONDITION
-                        // This can be a win condition where the non-mine tiles are cleared and they place the last flag
-                        if(model->flags_left == 0 && model->mines_left == 0 &&
-                           model->tiles_left == 0) {
-                            //mine_sweeper_play_long_bump(instance->context);
+                        state != MineSweeperGameScreenTileStateCleared) {
+                        is_win_condition_triggered = handle_long_back_flag_input(instance, model);
 
-                            mine_sweeper_win_effect(instance);
-                            mine_sweeper_led_set_rgb(instance->context, 0, 0, 255);
+                        model->is_holding_down_button = true;
 
+                        if(is_win_condition_triggered) {
                             view_set_draw_callback(
-                                instance->view, mine_sweeper_game_screen_view_win_draw_callback);
+                                instance->view, mine_sweeper_game_screen_view_end_draw_callback);
                             view_set_input_callback(
                                 instance->view, mine_sweeper_game_screen_view_end_input_callback);
-                        } else {
-                            // Making sure that win and flag effect are not played together
-                            mine_sweeper_flag_effect(instance);
                         }
                     }
-                },
-                false);
-
-            consumed = true;
-        }
-    }
-
-    if(!consumed &&
-       (event->type == InputTypePress || event->type == InputTypeRepeat)) { // Finally handle move
-
-        with_view_model(
-            instance->view,
-            MineSweeperGameScreenModel * model,
-            {
-                bool is_outside_boundary;
-                switch(event->key) {
-                case InputKeyUp:
-                    (model->curr_pos.x_abs - 1 < 0) ? mine_sweeper_oob_effect(instance) :
-                                                      mine_sweeper_move_effect(instance);
-
-                    model->curr_pos.x_abs =
-                        (model->curr_pos.x_abs - 1 < 0) ? 0 : model->curr_pos.x_abs - 1;
-
-                    is_outside_boundary = model->curr_pos.x_abs < (model->bottom_boundary -
-                                                                   MINESWEEPER_SCREEN_TILE_HEIGHT);
-
-                    if(is_outside_boundary) {
-                        model->bottom_boundary--;
-                    }
-
-                    consumed = true;
-                    break;
-
-                case InputKeyDown:
-
-                    (model->curr_pos.x_abs + 1 >= model->board_height) ?
-                        mine_sweeper_oob_effect(instance) :
-                        mine_sweeper_move_effect(instance);
-
-                    model->curr_pos.x_abs = (model->curr_pos.x_abs + 1 >= model->board_height) ?
-                                                model->board_height - 1 :
-                                                model->curr_pos.x_abs + 1;
-
-                    is_outside_boundary = model->curr_pos.x_abs >= model->bottom_boundary;
-
-                    if(is_outside_boundary) {
-                        model->bottom_boundary++;
-                    }
 
                     consumed = true;
-                    break;
-
-                case InputKeyLeft:
-                    (model->curr_pos.y_abs - 1 < 0) ? mine_sweeper_oob_effect(instance) :
-                                                      mine_sweeper_move_effect(instance);
-
-                    model->curr_pos.y_abs =
-                        (model->curr_pos.y_abs - 1 < 0) ? 0 : model->curr_pos.y_abs - 1;
-
-                    is_outside_boundary = model->curr_pos.y_abs <
-                                          (model->right_boundary - MINESWEEPER_SCREEN_TILE_WIDTH);
-
-                    if(is_outside_boundary) {
-                        model->right_boundary--;
-                    }
-
-                    consumed = true;
-                    break;
-
-                case InputKeyRight:
-                    (model->curr_pos.y_abs + 1 >= model->board_width) ?
-                        mine_sweeper_oob_effect(instance) :
-                        mine_sweeper_move_effect(instance);
-
-                    model->curr_pos.y_abs = (model->curr_pos.y_abs + 1 >= model->board_width) ?
-                                                model->board_width - 1 :
-                                                model->curr_pos.y_abs + 1;
-
-                    is_outside_boundary = model->curr_pos.y_abs >= model->right_boundary;
-
-                    if(is_outside_boundary) {
-                        model->right_boundary++;
-                    }
-
-                    consumed = true;
-                    break;
-
-                default:
-                    consumed = true;
-                    break;
                 }
-            },
-            true);
-    }
+            } else if(
+                event->type == InputTypePress ||
+                event->type == InputTypeRepeat) { // Finally handle move
+
+                consumed = handle_player_move(instance, model, event);
+            }
+        },
+        true);
 
     if(!consumed && instance->input_callback != NULL) {
         consumed = instance->input_callback(event, instance->context);

+ 7 - 0
minesweeper/views/minesweeper_game_screen.h

@@ -8,6 +8,13 @@
 
 #include <gui/view.h>
 
+#include <gui/elements.h>
+#include <gui/icon_animation.h>
+#include <input/input.h>
+#include <furi.h>
+#include <furi_hal.h>
+
+#include "minesweeper_redux_icons.h"
 #include "minesweeper_game_screen_i.h"
 #include "../helpers/mine_sweeper_haptic.h"
 #include "../helpers/mine_sweeper_led.h"