MX пре 2 година
родитељ
комит
978271b755

+ 1 - 1
base_pack/minesweeper/application.fam

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

+ 18 - 1
base_pack/minesweeper/docs/changelog.md

@@ -1,7 +1,24 @@
 
 # Changelog
 ## TODO:
-- Implement LED and haptic feedback
+- 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.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
 
 ## Version 1.0.0 - 1/10/2024
 

+ 50 - 0
base_pack/minesweeper/helpers/mine_sweeper_haptic.c

@@ -0,0 +1,50 @@
+#include "mine_sweeper_haptic.h"
+#include "../minesweeper.h"
+
+
+void mine_sweeper_play_happy_bump(void* context) {
+    MineSweeperApp* app = context;
+
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+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);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+        notification_message(app->notification, &sequence_reset_vibro);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    }
+}
+
+void mine_sweeper_play_oob_bump(void* context) {
+    MineSweeperApp* app = context;
+
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void mine_sweeper_play_lose_bump(void* context) {
+    MineSweeperApp* app = context;
+
+    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);
+}
+
+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);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
+        notification_message(app->notification, &sequence_reset_vibro);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    }
+}

+ 17 - 0
base_pack/minesweeper/helpers/mine_sweeper_haptic.h

@@ -0,0 +1,17 @@
+#ifndef MINESWEEPER_HAPTIC_H
+#define MINESWEEPER_HAPTIC_H
+
+#include <notification/notification_messages.h>
+
+void mine_sweeper_play_happy_bump(void* context);
+
+void mine_sweeper_play_long_ok_bump(void* context);
+
+void mine_sweeper_play_oob_bump(void* context);
+
+void mine_sweeper_play_lose_bump(void* context);
+
+void mine_sweeper_play_win_bump(void* context);
+
+
+#endif

+ 61 - 0
base_pack/minesweeper/helpers/mine_sweeper_led.c

@@ -0,0 +1,61 @@
+#include "mine_sweeper_led.h"
+#include "../minesweeper.h"
+
+
+
+void mine_sweeper_led_set_rgb(void* context, int red, int green, int blue) {
+    MineSweeperApp* app = context;
+
+    NotificationMessage notification_led_message_1;
+    notification_led_message_1.type = NotificationMessageTypeLedRed;
+    NotificationMessage notification_led_message_2;
+    notification_led_message_2.type = NotificationMessageTypeLedGreen;
+    NotificationMessage notification_led_message_3;
+    notification_led_message_3.type = NotificationMessageTypeLedBlue;
+
+    notification_led_message_1.data.led.value = red;
+    notification_led_message_2.data.led.value = green;
+    notification_led_message_3.data.led.value = blue;
+    const NotificationSequence notification_sequence = {
+        &notification_led_message_1,
+        &notification_led_message_2,
+        &notification_led_message_3,
+        &message_do_not_reset,
+        NULL,
+    };
+    notification_message(app->notification, &notification_sequence);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set    
+}
+
+void mine_sweeper_led_blink_red(void* context) {
+    furi_assert(context);
+    MineSweeperApp* app = context;
+
+    notification_message(app->notification, &sequence_blink_red_100);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set    
+}
+
+void mine_sweeper_led_blink_magenta(void* context) {
+    furi_assert(context);
+    MineSweeperApp* app = context;
+
+    notification_message(app->notification, &sequence_blink_magenta_100);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set    
+}
+
+void mine_sweeper_led_blink_cyan(void* context) {
+    furi_assert(context);
+    MineSweeperApp* app = context;
+
+    notification_message(app->notification, &sequence_blink_cyan_100);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set    
+}
+
+void mine_sweeper_led_reset(void* context) {
+    MineSweeperApp* app = context;
+    notification_message(app->notification, &sequence_reset_red);
+    notification_message(app->notification, &sequence_reset_green);
+    notification_message(app->notification, &sequence_reset_blue);
+    
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 300); //Delay, prevent removal from RAM before LED value set    
+}

+ 15 - 0
base_pack/minesweeper/helpers/mine_sweeper_led.h

@@ -0,0 +1,15 @@
+#ifndef MINESWEEPER_LED_H
+#define MINESWEEPER_LED_H
+
+void mine_sweeper_led_set_rgb(void* context, int red, int green, int blue);
+
+void mine_sweeper_led_blink_red(void* context);
+
+void mine_sweeper_led_blink_magenta(void* context);
+
+void mine_sweeper_led_blink_cyan(void* context);
+
+void mine_sweeper_led_reset(void* context);
+
+
+#endif

+ 64 - 0
base_pack/minesweeper/helpers/mine_sweeper_speaker.c

@@ -0,0 +1,64 @@
+#include "mine_sweeper_speaker.h"
+#include "../minesweeper.h"
+
+static const float volume = 0.8f;
+
+void mine_sweeper_play_ok_sound(void* context) {
+    MineSweeperApp* app = context;
+    UNUSED(app);
+
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_LOSE, volume);
+    }
+
+}
+
+void mine_sweeper_play_flag_sound(void* context) {
+    MineSweeperApp* app = context;
+    UNUSED(app);
+
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_FLAG, volume);
+    }
+
+}
+
+void mine_sweeper_play_oob_sound(void* context) {
+    MineSweeperApp* app = context;
+    UNUSED(app);
+
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_OOB, volume);
+    }
+
+}
+
+void mine_sweeper_play_win_sound(void* context) {
+    MineSweeperApp* app = context;
+    UNUSED(app);
+
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_WIN, volume);
+    }
+
+}
+
+void mine_sweeper_play_lose_sound(void* context) {
+    MineSweeperApp* app = context;
+    UNUSED(app);
+
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_LOSE, volume);
+    }
+
+}
+
+void mine_sweeper_stop_all_sound(void* context) {
+    MineSweeperApp* app = context;
+    UNUSED(app);
+
+    if(furi_hal_speaker_is_mine()) {
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    }
+}

+ 18 - 0
base_pack/minesweeper/helpers/mine_sweeper_speaker.h

@@ -0,0 +1,18 @@
+#ifndef MINESWEEPER_SPEAKER_H
+#define MINESWEEPER_SPEAKER_H
+
+#define NOTE_OK 3078.95f //G_4
+#define NOTE_FLAG 384.87f //G_4
+#define NOTE_OOB 342.88f  //F_4
+#define NOTE_WIN 432.00f  //Divine
+#define NOTE_LOSE 4170.00f //Cursed
+
+void mine_sweeper_play_ok_sound(void* context);
+void mine_sweeper_play_flag_sound(void* context);
+void mine_sweeper_play_oob_sound(void* context);
+void mine_sweeper_play_win_sound(void* context);
+void mine_sweeper_play_lose_sound(void* context);
+void mine_sweeper_stop_all_sound(void* context);
+
+
+#endif

+ 4 - 1
base_pack/minesweeper/helpers/mine_sweeper_storage.h

@@ -1,4 +1,5 @@
-#pragma once
+#ifndef MINESWEEPER_STORAGE_H
+#define MINESWEEPER_STORAGE_H
 
 #include <stdlib.h>
 #include <string.h>
@@ -22,3 +23,5 @@
 
 void mine_sweeper_save_settings(void* context);
 bool mine_sweeper_read_settings(void* context);
+
+#endif

+ 31 - 41
base_pack/minesweeper/minesweeper.c

@@ -19,31 +19,27 @@ static void minesweeper_tick_event_callback(void* context) {
     return scene_manager_handle_tick_event(app->scene_manager);
 }
 
-static MineSweeperApp* app_alloc() {
+static MineSweeperApp* app_alloc() { 
     MineSweeperApp* app = (MineSweeperApp*)malloc(sizeof(MineSweeperApp));
-
+    
     // NotificationApp Service
-    NotificationApp* notification_app = furi_record_open(RECORD_NOTIFICATION);
+    app->notification = furi_record_open(RECORD_NOTIFICATION);
 
     // Turn backlight on when app starts
-    notification_message(notification_app, &sequence_display_backlight_on);
+    notification_message(app->notification, &sequence_display_backlight_on);
 
-    furi_record_close(RECORD_NOTIFICATION);
 
-    // Alloc Scene Manager and set handlers for on_enter, on_event, on_exit
+    // Alloc Scene Manager and set handlers for on_enter, on_event, on_exit 
     app->scene_manager = scene_manager_alloc(&minesweeper_scene_handlers, app);
-
+    
     // Alloc View Dispatcher and enable queue
     app->view_dispatcher = view_dispatcher_alloc();
     view_dispatcher_enable_queue(app->view_dispatcher);
     // Set View Dispatcher event callback context and callbacks
     view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
-    view_dispatcher_set_custom_event_callback(
-        app->view_dispatcher, minesweeper_custom_event_callback);
-    view_dispatcher_set_navigation_event_callback(
-        app->view_dispatcher, minesweeper_navigation_event_callback);
-    view_dispatcher_set_tick_event_callback(
-        app->view_dispatcher, minesweeper_tick_event_callback, 500);
+    view_dispatcher_set_custom_event_callback(app->view_dispatcher, minesweeper_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(app->view_dispatcher, minesweeper_navigation_event_callback);
+    view_dispatcher_set_tick_event_callback(app->view_dispatcher, minesweeper_tick_event_callback, 500);
 
     // Set setting info to default
     app->settings_info.width_str = furi_string_alloc();
@@ -52,7 +48,7 @@ static MineSweeperApp* app_alloc() {
     app->is_settings_changed = false;
 
     // If we cannot read the save file set to default values
-    if(!(mine_sweeper_read_settings(app))) {
+    if (!(mine_sweeper_read_settings(app))) {
         FURI_LOG_I(TAG, "Cannot read save file, loading defaults");
         app->settings_info.board_width = 16;
         app->settings_info.board_height = 7;
@@ -69,19 +65,18 @@ static MineSweeperApp* app_alloc() {
     // Alloc views and add to view dispatcher
     app->start_screen = start_screen_alloc();
     view_dispatcher_add_view(
-        app->view_dispatcher,
-        MineSweeperStartScreenView,
-        start_screen_get_view(app->start_screen));
+            app->view_dispatcher,
+            MineSweeperStartScreenView,
+            start_screen_get_view(app->start_screen));
 
     app->loading = loading_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, MineSweeperLoadingView, loading_get_view(app->loading));
+    view_dispatcher_add_view(app->view_dispatcher, MineSweeperLoadingView, loading_get_view(app->loading));
 
     app->game_screen = mine_sweeper_game_screen_alloc(
-        app->settings_info.board_width,
-        app->settings_info.board_height,
-        app->settings_info.difficulty,
-        false);
+            app->settings_info.board_width,
+            app->settings_info.board_height,
+            app->settings_info.difficulty,
+            false);
 
     view_dispatcher_add_view(
         app->view_dispatcher,
@@ -89,29 +84,21 @@ static MineSweeperApp* app_alloc() {
         mine_sweeper_game_screen_get_view(app->game_screen));
 
     app->menu_screen = dialog_ex_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, MineSweeperMenuView, dialog_ex_get_view(app->menu_screen));
+    view_dispatcher_add_view(app->view_dispatcher, MineSweeperMenuView, dialog_ex_get_view(app->menu_screen));
 
     app->settings_screen = variable_item_list_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher,
-        MineSweeperSettingsView,
-        variable_item_list_get_view(app->settings_screen));
+    view_dispatcher_add_view(app->view_dispatcher, MineSweeperSettingsView, variable_item_list_get_view(app->settings_screen));
 
     app->confirmation_screen = dialog_ex_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher,
-        MineSweeperConfirmationView,
-        dialog_ex_get_view(app->confirmation_screen));
+    view_dispatcher_add_view(app->view_dispatcher, MineSweeperConfirmationView, dialog_ex_get_view(app->confirmation_screen));
 
     app->info_screen = text_box_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, MineSweeperInfoView, text_box_get_view(app->info_screen));
+    view_dispatcher_add_view(app->view_dispatcher, MineSweeperInfoView, text_box_get_view(app->info_screen));
 
     Gui* gui = furi_record_open(RECORD_GUI);
 
     view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
-
+    
     furi_record_close(RECORD_GUI);
 
     return app;
@@ -119,11 +106,10 @@ static MineSweeperApp* app_alloc() {
 
 static void app_free(MineSweeperApp* app) {
     furi_assert(app);
-
+    
     // Remove each view from View Dispatcher
-    for(MineSweeperView minesweeper_view = (MineSweeperView)0;
-        minesweeper_view < MineSweeperViewCount;
-        minesweeper_view++) {
+    for (MineSweeperView minesweeper_view = (MineSweeperView)0; minesweeper_view < MineSweeperViewCount; minesweeper_view++) {
+
         view_dispatcher_remove_view(app->view_dispatcher, minesweeper_view);
     }
 
@@ -134,17 +120,21 @@ static void app_free(MineSweeperApp* app) {
     // Free views
     loading_free(app->loading);
     start_screen_free(app->start_screen);
-    mine_sweeper_game_screen_free(app->game_screen);
+    mine_sweeper_game_screen_free(app->game_screen);  
     dialog_ex_free(app->menu_screen);
     variable_item_list_free(app->settings_screen);
     dialog_ex_free(app->confirmation_screen);
     text_box_free(app->info_screen);
 
+
     furi_string_free(app->settings_info.width_str);
     furi_string_free(app->settings_info.height_str);
 
+    furi_record_close(RECORD_NOTIFICATION);
+
     // Free app structure
     free(app);
+
 }
 
 int32_t minesweeper_app(void* p) {

+ 2 - 0
base_pack/minesweeper/minesweeper.h

@@ -42,6 +42,8 @@ typedef struct {
 typedef struct MineSweeperApp {
     SceneManager* scene_manager;
     ViewDispatcher* view_dispatcher;
+    
+    NotificationApp* notification;
 
     StartScreen* start_screen;
     Loading* loading;

+ 144 - 30
base_pack/minesweeper/views/minesweeper_game_screen.c

@@ -78,7 +78,7 @@ typedef struct {
     uint32_t start_tick;
     FuriString* info_str;
     bool ensure_solvable_board;
-    bool is_making_first_move;
+    bool is_win_triggered;
     bool is_holding_down_button;
 } MineSweeperGameScreenModel;
 
@@ -154,11 +154,19 @@ static void mine_sweeper_game_screen_view_win_draw_callback(Canvas* canvas, void
 static void mine_sweeper_game_screen_view_lose_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
+static void mine_sweeper_long_ok_effect(void* context);
+static void mine_sweeper_short_ok_effect(void* context);
+static void mine_sweeper_flag_effect(void* context);
+static void mine_sweeper_move_effect(void* context);
+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 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);
 
 
-
 /**************************************************************
  * Function definitions
  *************************************************************/
@@ -273,7 +281,7 @@ 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_making_first_move = true;         
+            model->is_win_triggered = false;         
         },
         true
     );
@@ -562,8 +570,8 @@ static inline uint16_t bfs_tile_clear(
             continue;
         } 
         
-        // If it is cleared continue
-        if (board[curr_pos_1d].tile_state == MineSweeperGameScreenTileStateCleared) {
+        // If it is not uncleared continue
+        if (board[curr_pos_1d].tile_state != MineSweeperGameScreenTileStateUncleared) {
             continue;
         }
         
@@ -1110,6 +1118,76 @@ static void mine_sweeper_game_screen_view_play_draw_callback(Canvas* canvas, voi
 
 }
 
+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);
@@ -1184,7 +1262,8 @@ static bool mine_sweeper_game_screen_view_end_input_callback(InputEvent* event,
                         break;
 
                     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,
@@ -1235,25 +1314,33 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
     MineSweeperGameScreen* instance = context;
     bool consumed = false;
 
-    // Checking button types
-
-    if (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;
+    // 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);
+    }
 
-                    model->is_holding_down_button = false;
+    if (!consumed && event->key == InputKeyOk) { // Attempt to Clear Space !! THIS CAN BE A LOSE CONDITION
 
-                } else if (!model->is_holding_down_button && event->type == InputTypePress) { 
+        bool is_lose_condition_triggered = false;
+        bool is_win_condition_triggered = false;
 
-                    uint16_t curr_pos_1d = model->curr_pos.x_abs * model->board_width + model->curr_pos.y_abs;
+        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;
 
+                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;
 
@@ -1277,6 +1364,9 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                         // 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);
                         }
                     }
 
@@ -1292,6 +1382,13 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                         is_win_condition_triggered = true;
                     }
 
+                    // 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);
+                    }
+
                 } 
 
             },
@@ -1300,13 +1397,15 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
         
         // 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;
 
@@ -1317,19 +1416,7 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                                                    // the next closest covered tile on when on a uncovered
                                                    // tile
 
-        if (event->type == InputTypeRelease) {
-            with_view_model(
-                instance->view,
-                MineSweeperGameScreenModel * model,
-                {
-                    model->is_holding_down_button = false;
-                },
-                true
-            );
-            
-            consumed = true;
-
-        } else if (event->type == InputTypeLong || event->type == InputTypeRepeat) {    // Only process longer back keys;
+        if (event->type == InputTypeLong || event->type == InputTypeRepeat) {    // Only process longer back keys;
                                                                                         // short presses should take
                                                                                         // us to the menu
             with_view_model(
@@ -1376,10 +1463,13 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                             model->right_boundary = model->curr_pos.y_abs + MINESWEEPER_SCREEN_TILE_WIDTH;
                         }
                         
+                        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)) { 
+                    } 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++;
@@ -1397,8 +1487,16 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                         // 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);
+                            
+                            mine_sweeper_win_effect(instance);
+                            mine_sweeper_led_set_rgb(instance->context, 0, 0, 255);
+
                             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);
+                        } else {
+                            // Making sure that win and flag effect are not played together
+                            mine_sweeper_flag_effect(instance);
                         }
 
                     }
@@ -1421,6 +1519,9 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                 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 <
@@ -1434,6 +1535,10 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                         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;
 
@@ -1447,6 +1552,9 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                         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 <
@@ -1460,6 +1568,9 @@ static bool mine_sweeper_game_screen_view_play_input_callback(InputEvent* event,
                         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;
 
@@ -1583,6 +1694,9 @@ void mine_sweeper_game_screen_reset(MineSweeperGameScreen* instance, uint8_t wid
     
     instance->input_callback = NULL;
     
+    // Reset led
+    mine_sweeper_led_reset(instance->context);
+    
     // We need to initize board width and height before setup
     mine_sweeper_game_screen_set_board_information(instance, width, height, difficulty, ensure_solvable);
 

+ 3 - 0
base_pack/minesweeper/views/minesweeper_game_screen.h

@@ -9,6 +9,9 @@
 #include <gui/view.h>
 
 #include "minesweeper_game_screen_i.h"
+#include "../helpers/mine_sweeper_haptic.h"
+#include "../helpers/mine_sweeper_led.h"
+#include "../helpers/mine_sweeper_speaker.h"
 
 // MAX TILES ALLOWED
 #define MINESWEEPER_BOARD_MAX_TILES  (1<<10)