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

Squashed 'minesweeper/' changes from 34b645c51..1a74ea27e

1a74ea27e fix catalog versions
6a1c99dcb combine 1
0cd31f926 move base pack here
REVERT: 34b645c51 Merge pull request #7 from gelin/fix-v0.72.1
REVERT: 94924fa55 fix build for firmware v0.72.1
REVERT: bb6e54cb3 fix data type
REVERT: cf138cd9b cursor wraps around
REVERT: dc2c8d51e cleanup
REVERT: addd984e4 Merge pull request #3 from panki27/flagsbased-uncover
REVERT: d63dce1b4 update README
REVERT: d1bcb7da8 comments, minor cleanup
REVERT: c5d9360e3 Merge branch 'master' into flagsbased-uncover
REVERT: a9d6011f5 fix double freeing of furi string
REVERT: bf8c7f56e add missing return?
REVERT: 1839389e4 Merge branch 'master' of https://github.com/gelin/minesweeper into gelin-master
REVERT: 5f23b4ba0 Merge pull request #1 from Alejandro12120/master
REVERT: eab0d7121 fix cases when mine is got when auto uncovering and some others
REVERT: 94aaa1ec6 add auto uncover cells on second click to the cell with number, as it's done by left-right click on desktop minesweeper
REVERT: eafcf9f67 Update minesweeper.c
REVERT: 796821331 updated to furistring
REVERT: da675b989 remove crash warning, add my github
REVERT: 87de8d9a5 vibration now fixed :)
REVERT: 5dbbd0439 some sanity checks... theres a bug with spammed inputs :(
REVERT: f951a55c8 icon
REVERT: 45ff8cd13 update screenshot
REVERT: 4426c0123 show time in win dialog
REVERT: 4e7970f53 timer
REVERT: ed92ecdb2 show mines left
REVERT: fd345bdab LICENSE
REVERT: 014971ce6 README
REVERT: 1e36565b9 game over + win screen
REVERT: 7e56f353d fix flag
REVERT: 69d2ef162 vibrate on hitting mine, fix empty cell graphic
REVERT: 1171f95a3 rework assets
REVERT: e92122abc flag placement
REVERT: ff45866fe working uncovering, auto uncover
REVERT: b0615c833 minefield generation
REVERT: b3ec5e7aa moving cursor
REVERT: b8aa5feab initial commit, drawing cells

git-subtree-dir: minesweeper
git-subtree-split: 1a74ea27e284a08a0c457daaa345235cf9fcc950
Willy-JL 2 лет назад
Родитель
Сommit
9713d22b3f
6 измененных файлов с 520 добавлено и 411 удалено
  1. 2 0
      README.md
  2. 4 2
      application.fam
  3. 108 12
      assets.h
  4. BIN
      img/1.png
  5. BIN
      img/2.png
  6. 406 397
      minesweeper.c

+ 2 - 0
README.md

@@ -1,5 +1,7 @@
 # Minesweeper
 
+[Original Link](https://github.com/panki27/minesweeper)
+
 This is a Minesweeper implementation for the Flipper Zero device.
 
 ![screenshot](img/screenshot.png)

+ 4 - 2
application.fam

@@ -1,12 +1,14 @@
 App(
     appid="minesweeper",
     name="Minesweeper",
-    apptype=FlipperAppType.PLUGIN,
+    apptype=FlipperAppType.EXTERNAL,
     entry_point="minesweeper_app",
-    cdefines=["APP_MINESWEEPER"],
     requires=["gui"],
     stack_size=8 * 1024,
     fap_category="Games",
     fap_icon="minesweeper_icon.png",
     order=35,
+    fap_author="@panki27 & @xMasterX",
+    fap_version="1.1",
+    fap_description="Minesweeper Game",
 )

+ 108 - 12
assets.h

@@ -1,48 +1,144 @@
 #define tile_0_width 8
 #define tile_0_height 8
 static uint8_t tile_0_bits[] = {
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
+    0x00,
+    0x00,
+    0x00,
+    0x00,
+    0x00,
+    0x00,
+    0x00,
+    0x00,
+};
 #define tile_1_width 8
 #define tile_1_height 8
 static uint8_t tile_1_bits[] = {
-  0x00, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x00, };
+    0x00,
+    0x10,
+    0x18,
+    0x10,
+    0x10,
+    0x10,
+    0x10,
+    0x00,
+};
 #define tile_2_width 8
 #define tile_2_height 8
 static uint8_t tile_2_bits[] = {
-  0x00, 0x1C, 0x20, 0x20, 0x18, 0x04, 0x3C, 0x00, };
+    0x00,
+    0x1C,
+    0x20,
+    0x20,
+    0x18,
+    0x04,
+    0x3C,
+    0x00,
+};
 #define tile_3_width 8
 #define tile_3_height 8
 static uint8_t tile_3_bits[] = {
-  0x00, 0x1C, 0x20, 0x20, 0x18, 0x20, 0x1C, 0x00, };
+    0x00,
+    0x1C,
+    0x20,
+    0x20,
+    0x18,
+    0x20,
+    0x1C,
+    0x00,
+};
 #define tile_4_width 8
 #define tile_4_height 8
 static uint8_t tile_4_bits[] = {
-  0x00, 0x04, 0x14, 0x14, 0x3C, 0x10, 0x10, 0x00, };
+    0x00,
+    0x04,
+    0x14,
+    0x14,
+    0x3C,
+    0x10,
+    0x10,
+    0x00,
+};
 #define tile_5_width 8
 #define tile_5_height 8
 static uint8_t tile_5_bits[] = {
-  0x00, 0x3C, 0x04, 0x1C, 0x20, 0x20, 0x1C, 0x00, };
+    0x00,
+    0x3C,
+    0x04,
+    0x1C,
+    0x20,
+    0x20,
+    0x1C,
+    0x00,
+};
 #define tile_6_width 8
 #define tile_6_height 8
 static uint8_t tile_6_bits[] = {
-  0x00, 0x18, 0x24, 0x04, 0x1C, 0x24, 0x18, 0x00, };
+    0x00,
+    0x18,
+    0x24,
+    0x04,
+    0x1C,
+    0x24,
+    0x18,
+    0x00,
+};
 #define tile_7_width 8
 #define tile_7_height 8
 static uint8_t tile_7_bits[] = {
-  0x00, 0x3C, 0x20, 0x20, 0x10, 0x08, 0x08, 0x00, };
+    0x00,
+    0x3C,
+    0x20,
+    0x20,
+    0x10,
+    0x08,
+    0x08,
+    0x00,
+};
 #define tile_8_width 8
 #define tile_8_height 8
 static uint8_t tile_8_bits[] = {
-  0x00, 0x18, 0x24, 0x18, 0x24, 0x24, 0x18, 0x00, };
+    0x00,
+    0x18,
+    0x24,
+    0x18,
+    0x24,
+    0x24,
+    0x18,
+    0x00,
+};
 #define tile_flag_width 8
 #define tile_flag_height 8
 static uint8_t tile_flag_bits[] = {
-  0xFF, 0x81, 0xB9, 0x89, 0x89, 0x9D, 0x81, 0xFF, };
+    0xFF,
+    0x81,
+    0xB9,
+    0x89,
+    0x89,
+    0x9D,
+    0x81,
+    0xFF,
+};
 #define tile_mine_width 8
 #define tile_mine_height 8
 static uint8_t tile_mine_bits[] = {
-  0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, };
+    0x55,
+    0xAA,
+    0x55,
+    0xAA,
+    0x55,
+    0xAA,
+    0x55,
+    0xAA,
+};
 #define tile_uncleared_width 8
 #define tile_uncleared_height 8
 static uint8_t tile_uncleared_bits[] = {
-  0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF, };
+    0xFF,
+    0x81,
+    0x81,
+    0x81,
+    0x81,
+    0x81,
+    0x81,
+    0xFF,
+};



+ 406 - 397
minesweeper.c

@@ -6,7 +6,8 @@
 
 #include <notification/notification_messages.h>
 #include <dialogs/dialogs.h>
-#include <m-string.h>
+
+#include <dolphin/dolphin.h>
 
 #include "assets.h"
 
@@ -28,45 +29,37 @@ typedef struct {
 } PluginEvent;
 
 typedef enum {
-  TileType0, // this HAS to be in order, for hint assignment to be ez pz
-  TileType1,
-  TileType2,
-  TileType3,
-  TileType4,
-  TileType5,
-  TileType6,
-  TileType7,
-  TileType8,
-  TileTypeUncleared,
-  TileTypeFlag,
-  TileTypeMine
+    TileType0, // this HAS to be in order, for hint assignment to be ez pz
+    TileType1,
+    TileType2,
+    TileType3,
+    TileType4,
+    TileType5,
+    TileType6,
+    TileType7,
+    TileType8,
+    TileTypeUncleared,
+    TileTypeFlag,
+    TileTypeMine
 } TileType;
 
-typedef enum {
-    FieldEmpty,
-    FieldMine
-} Field;
+typedef enum { FieldEmpty, FieldMine } Field;
 
 typedef struct {
-  Field minefield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT];
-  TileType playfield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT];
-  FuriTimer* timer;
-  int cursor_x;
-  int cursor_y;
-  int mines_left;
-  int fields_cleared;
-  int flags_set;
-  bool game_started;
-  uint32_t game_started_tick;
+    FuriMutex* mutex;
+    DialogsApp* dialogs;
+    NotificationApp* notifications;
+    Field minefield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT];
+    TileType playfield[PLAYFIELD_WIDTH][PLAYFIELD_HEIGHT];
+    int cursor_x;
+    int cursor_y;
+    int mines_left;
+    int fields_cleared;
+    int flags_set;
+    bool game_started;
+    uint32_t game_started_tick;
 } Minesweeper;
 
-static void timer_callback(void* ctx) {
-  UNUSED(ctx);
-  NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
-  notification_message(notification, &sequence_reset_vibro);
-  furi_record_close(RECORD_NOTIFICATION);
-}
-
 static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
     furi_assert(event_queue);
 
@@ -75,10 +68,10 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu
 }
 
 static void render_callback(Canvas* const canvas, void* ctx) {
-    const Minesweeper* minesweeper_state = acquire_mutex((ValueMutex*)ctx, 25);
-    if (minesweeper_state == NULL) {
-      return;
-    }
+    furi_assert(ctx);
+    const Minesweeper* minesweeper_state = ctx;
+    furi_mutex_acquire(minesweeper_state->mutex, FuriWaitForever);
+
     FuriString* mineStr;
     FuriString* timeStr;
     mineStr = furi_string_alloc();
@@ -90,417 +83,433 @@ static void render_callback(Canvas* const canvas, void* ctx) {
 
     int seconds = 0;
     int minutes = 0;
-    if (minesweeper_state->game_started) {
-      uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick;
-      seconds = (int) ticks_elapsed / furi_kernel_get_tick_frequency();
-      minutes = (int) seconds / 60;
-      seconds = seconds % 60;
+    if(minesweeper_state->game_started) {
+        uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick;
+        seconds = (int)ticks_elapsed / furi_kernel_get_tick_frequency();
+        minutes = (int)seconds / 60;
+        seconds = seconds % 60;
     }
     furi_string_printf(timeStr, "%01d:%02d", minutes, seconds);
     canvas_draw_str_aligned(canvas, 128, 0, AlignRight, AlignTop, furi_string_get_cstr(timeStr));
 
     uint8_t* tile_to_draw;
 
-    for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
-      for (int x = 0; x < PLAYFIELD_WIDTH; x++) {
-        if ( x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
-          canvas_invert_color(canvas);
-        }
-        switch (minesweeper_state->playfield[x][y]) {
-          case TileType0:
-            tile_to_draw = tile_0_bits;
-            break;
-          case TileType1:
-            tile_to_draw = tile_1_bits;
-            break;
-          case TileType2:
-            tile_to_draw = tile_2_bits;
-            break;
-          case TileType3:
-            tile_to_draw = tile_3_bits;
-            break;
-          case TileType4:
-            tile_to_draw = tile_4_bits;
-            break;
-          case TileType5:
-            tile_to_draw = tile_5_bits;
-            break;
-          case TileType6:
-            tile_to_draw = tile_6_bits;
-            break;
-          case TileType7:
-            tile_to_draw = tile_7_bits;
-            break;
-          case TileType8:
-            tile_to_draw = tile_8_bits;
-            break;
-          case TileTypeFlag:
-            tile_to_draw = tile_flag_bits;
-            break;
-          case TileTypeUncleared:
-            tile_to_draw = tile_uncleared_bits;
-            break;
-          case TileTypeMine:
-            tile_to_draw = tile_mine_bits;
-            break;
-          default:
-            // this should never happen
-            tile_to_draw = tile_mine_bits;
-            break;
-        }
-        canvas_draw_xbm(
-            canvas,
-            x*TILE_HEIGHT, // x
-            8 + (y * TILE_WIDTH), // y
-            TILE_WIDTH,
-            TILE_HEIGHT,
-            tile_to_draw);
-        if ( x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
-          canvas_invert_color(canvas);
+    for(int y = 0; y < PLAYFIELD_HEIGHT; y++) {
+        for(int x = 0; x < PLAYFIELD_WIDTH; x++) {
+            if(x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
+                canvas_invert_color(canvas);
+            }
+            switch(minesweeper_state->playfield[x][y]) {
+            case TileType0:
+                tile_to_draw = tile_0_bits;
+                break;
+            case TileType1:
+                tile_to_draw = tile_1_bits;
+                break;
+            case TileType2:
+                tile_to_draw = tile_2_bits;
+                break;
+            case TileType3:
+                tile_to_draw = tile_3_bits;
+                break;
+            case TileType4:
+                tile_to_draw = tile_4_bits;
+                break;
+            case TileType5:
+                tile_to_draw = tile_5_bits;
+                break;
+            case TileType6:
+                tile_to_draw = tile_6_bits;
+                break;
+            case TileType7:
+                tile_to_draw = tile_7_bits;
+                break;
+            case TileType8:
+                tile_to_draw = tile_8_bits;
+                break;
+            case TileTypeFlag:
+                tile_to_draw = tile_flag_bits;
+                break;
+            case TileTypeUncleared:
+                tile_to_draw = tile_uncleared_bits;
+                break;
+            case TileTypeMine:
+                tile_to_draw = tile_mine_bits;
+                break;
+            default:
+                // this should never happen
+                tile_to_draw = tile_mine_bits;
+                break;
+            }
+            canvas_draw_xbm(
+                canvas,
+                x * TILE_HEIGHT, // x
+                8 + (y * TILE_WIDTH), // y
+                TILE_WIDTH,
+                TILE_HEIGHT,
+                tile_to_draw);
+            if(x == minesweeper_state->cursor_x && y == minesweeper_state->cursor_y) {
+                canvas_invert_color(canvas);
+            }
         }
-      }
     }
 
     furi_string_free(mineStr);
     furi_string_free(timeStr);
-    release_mutex((ValueMutex*)ctx, minesweeper_state);
+    furi_mutex_release(minesweeper_state->mutex);
 }
 
 static void setup_playfield(Minesweeper* minesweeper_state) {
-  int mines_left = MINECOUNT;
-  for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
-    for (int x = 0; x < PLAYFIELD_WIDTH; x++){
-        minesweeper_state->minefield[x][y] = FieldEmpty;
-        minesweeper_state->playfield[x][y] = TileTypeUncleared;
+    int mines_left = MINECOUNT;
+    for(int y = 0; y < PLAYFIELD_HEIGHT; y++) {
+        for(int x = 0; x < PLAYFIELD_WIDTH; x++) {
+            minesweeper_state->minefield[x][y] = FieldEmpty;
+            minesweeper_state->playfield[x][y] = TileTypeUncleared;
+        }
     }
-  }
-  while(mines_left > 0) {
-    int rand_x = rand() % PLAYFIELD_WIDTH;
-    int rand_y = rand() % PLAYFIELD_HEIGHT;
-    // make sure first guess isn't a mine
-    if (minesweeper_state->minefield[rand_x][rand_y] == FieldEmpty &&
-       (minesweeper_state->cursor_x != rand_x && minesweeper_state->cursor_y != rand_y )) {
-       minesweeper_state->minefield[rand_x][rand_y] = FieldMine;
-       mines_left--;
+    while(mines_left > 0) {
+        int rand_x = rand() % PLAYFIELD_WIDTH;
+        int rand_y = rand() % PLAYFIELD_HEIGHT;
+        // make sure first guess isn't a mine
+        if(minesweeper_state->minefield[rand_x][rand_y] == FieldEmpty &&
+           (minesweeper_state->cursor_x != rand_x || minesweeper_state->cursor_y != rand_y)) {
+            minesweeper_state->minefield[rand_x][rand_y] = FieldMine;
+            mines_left--;
+        }
     }
-  }
-  minesweeper_state->mines_left = MINECOUNT;
-  minesweeper_state->fields_cleared = 0;
-  minesweeper_state->flags_set = 0;
-  minesweeper_state->game_started_tick = furi_get_tick();
-  minesweeper_state->game_started = false;
+    minesweeper_state->mines_left = MINECOUNT;
+    minesweeper_state->fields_cleared = 0;
+    minesweeper_state->flags_set = 0;
+    minesweeper_state->game_started_tick = furi_get_tick();
+    minesweeper_state->game_started = false;
 }
 
 static void place_flag(Minesweeper* minesweeper_state) {
-  if (minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == TileTypeUncleared) {
-    minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = TileTypeFlag;
-    minesweeper_state->flags_set++;
-  } else if (minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] == TileTypeFlag) {
-    minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] = TileTypeUncleared;
-    minesweeper_state->flags_set--;
-  }
+    if(minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] ==
+       TileTypeUncleared) {
+        minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] =
+            TileTypeFlag;
+        minesweeper_state->flags_set++;
+    } else if(
+        minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] ==
+        TileTypeFlag) {
+        minesweeper_state->playfield[minesweeper_state->cursor_x][minesweeper_state->cursor_y] =
+            TileTypeUncleared;
+        minesweeper_state->flags_set--;
+    }
 }
 
 static bool game_lost(Minesweeper* minesweeper_state) {
-  // returns true if the player wants to restart, otherwise false
-  DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
-
-  DialogMessage* message = dialog_message_alloc();
-  const char* header_text = "Game Over";
-  const char* message_text = "You hit a mine!";
+    // returns true if the player wants to restart, otherwise false
+    DialogMessage* message = dialog_message_alloc();
 
-  dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop);
-  dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter);
-  dialog_message_set_buttons(message, NULL, "Play again", NULL);
+    dialog_message_set_header(message, "Game Over", 64, 3, AlignCenter, AlignTop);
+    dialog_message_set_text(message, "You hit a mine!", 64, 32, AlignCenter, AlignCenter);
+    dialog_message_set_buttons(message, NULL, "Play again", NULL);
 
-  dialog_message_set_icon(message, NULL, 0, 10);
+    // Set cursor to initial position
+    minesweeper_state->cursor_x = 0;
+    minesweeper_state->cursor_y = 0;
 
-  NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
-  notification_message(notifications, &sequence_set_vibro_on);
-  furi_record_close(RECORD_NOTIFICATION);
-  furi_timer_start(minesweeper_state->timer, (uint32_t) furi_kernel_get_tick_frequency() * 0.2);
+    notification_message(minesweeper_state->notifications, &sequence_single_vibro);
 
-  DialogMessageButton choice = dialog_message_show(dialogs, message);
-  dialog_message_free(message);
-  furi_record_close(RECORD_DIALOGS);
+    DialogMessageButton choice = dialog_message_show(minesweeper_state->dialogs, message);
+    dialog_message_free(message);
 
-  return choice == DialogMessageButtonCenter;
+    return choice == DialogMessageButtonCenter;
 }
 
 static bool game_won(Minesweeper* minesweeper_state) {
-  DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
-
-  FuriString* tempStr;
-  tempStr = furi_string_alloc();
-
-  int seconds = 0;
-  int minutes = 0;
-  uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick;
-  seconds = (int) ticks_elapsed / furi_kernel_get_tick_frequency();
-  minutes = (int) seconds / 60;
-  seconds = seconds % 60;
-
-  DialogMessage* message = dialog_message_alloc();
-  const char* header_text = "Game won!";
-  furi_string_cat_printf(tempStr, "Minefield cleared in %01d:%02d", minutes, seconds);
-  dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop);
-  dialog_message_set_text(message, furi_string_get_cstr(tempStr), 64, 32, AlignCenter, AlignCenter);
-  dialog_message_set_buttons(message, NULL, "Play again", NULL);
-  dialog_message_set_icon(message, NULL, 72, 17);
-
-  DialogMessageButton choice = dialog_message_show(dialogs, message);
-  dialog_message_free(message);
-  furi_string_free(tempStr);
-  furi_record_close(RECORD_DIALOGS);
-  return choice == DialogMessageButtonCenter;
+    FuriString* tempStr;
+    tempStr = furi_string_alloc();
+
+    int seconds = 0;
+    int minutes = 0;
+    uint32_t ticks_elapsed = furi_get_tick() - minesweeper_state->game_started_tick;
+    seconds = (int)ticks_elapsed / furi_kernel_get_tick_frequency();
+    minutes = (int)seconds / 60;
+    seconds = seconds % 60;
+
+    DialogMessage* message = dialog_message_alloc();
+    const char* header_text = "Game won!";
+    furi_string_cat_printf(tempStr, "Minefield cleared in %01d:%02d", minutes, seconds);
+    dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop);
+    dialog_message_set_text(
+        message, furi_string_get_cstr(tempStr), 64, 32, AlignCenter, AlignCenter);
+    dialog_message_set_buttons(message, NULL, "Play again", NULL);
+
+    // Call dolphin deed when we win the game
+    dolphin_deed(DolphinDeedPluginGameWin);
+
+    DialogMessageButton choice = dialog_message_show(minesweeper_state->dialogs, message);
+    dialog_message_free(message);
+    furi_string_free(tempStr);
+    return choice == DialogMessageButtonCenter;
 }
 
 // returns false if the move loses the game - otherwise true
 static bool play_move(Minesweeper* minesweeper_state, int cursor_x, int cursor_y) {
-  if (minesweeper_state->playfield[cursor_x][cursor_y] == TileTypeFlag) {
-    // we're on a flagged field, do nothing
-    return true;
-  }
-  if (minesweeper_state->minefield[cursor_x][cursor_y] == FieldMine) {
-    // player loses - draw mine
-    minesweeper_state->playfield[cursor_x][cursor_y] = TileTypeMine;
-    return false;
-  }
-
-  if (minesweeper_state->playfield[cursor_x][cursor_y] >= TileType1 && minesweeper_state->playfield[cursor_x][cursor_y] <= TileType8) {
-    // click on a cleared cell with a number
-    // count the flags around
-    int flags = 0;
-    for (int y = cursor_y-1; y <= cursor_y+1; y++) {
-      for (int x = cursor_x-1; x <= cursor_x+1; x++) {
-        if ( x == cursor_x && y == cursor_y ) {
-          // we're on the cell the user selected, so ignore.
-          continue;
-        }
-        // make sure we don't go OOB
-        if ( x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) {
-          if (minesweeper_state->playfield[x][y] == TileTypeFlag) {
-              flags ++;
-          }
-        }
-      }
+    if(minesweeper_state->playfield[cursor_x][cursor_y] == TileTypeFlag) {
+        // we're on a flagged field, do nothing
+        return true;
+    }
+    if(minesweeper_state->minefield[cursor_x][cursor_y] == FieldMine) {
+        // player loses - draw mine
+        minesweeper_state->playfield[cursor_x][cursor_y] = TileTypeMine;
+        return false;
     }
-    int mines = minesweeper_state->playfield[cursor_x][cursor_y];   // ¯\_(ツ)_/¯
-    if (flags == mines) {
-      // auto uncover all non-flags around (to win faster ;)
-      for (int auto_y = cursor_y-1; auto_y <= cursor_y+1; auto_y++) {
-        for (int auto_x = cursor_x-1; auto_x <= cursor_x+1; auto_x++) {
-          if ( auto_x == cursor_x && auto_y == cursor_y ) {
-            continue;
-          }
-          if ( auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && auto_y < PLAYFIELD_HEIGHT) {
-            if (minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
-              if(!play_move(minesweeper_state, auto_x, auto_y)) {
-                // flags were wrong, we got a mine!
-                return false;
-              }
+
+    if(minesweeper_state->playfield[cursor_x][cursor_y] >= TileType1 &&
+       minesweeper_state->playfield[cursor_x][cursor_y] <= TileType8) {
+        // click on a cleared cell with a number
+        // count the flags around
+        int flags = 0;
+        for(int y = cursor_y - 1; y <= cursor_y + 1; y++) {
+            for(int x = cursor_x - 1; x <= cursor_x + 1; x++) {
+                if(x == cursor_x && y == cursor_y) {
+                    // we're on the cell the user selected, so ignore.
+                    continue;
+                }
+                // make sure we don't go OOB
+                if(x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) {
+                    if(minesweeper_state->playfield[x][y] == TileTypeFlag) {
+                        flags++;
+                    }
+                }
             }
-          }
         }
-      }
-      // we're done without hitting a mine - so return
-      return true;
-    }
-  }
-
-  // calculate number of surrounding mines.
-  int hint = 0;
-  for (int y = cursor_y-1; y <= cursor_y+1; y++) {
-    for (int x = cursor_x-1; x <= cursor_x+1; x++) {
-      if ( x == cursor_x && y == cursor_y ) {
-        // we're on the cell the user selected, so ignore.
-        continue;
-      }
-      // make sure we don't go OOB
-      if ( x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) {
-        if(minesweeper_state->minefield[x][y] == FieldMine) {
-            hint ++;
+        int mines = minesweeper_state->playfield[cursor_x][cursor_y]; // ¯\_(ツ)_/¯
+        if(flags == mines) {
+            // auto uncover all non-flags around (to win faster ;)
+            for(int auto_y = cursor_y - 1; auto_y <= cursor_y + 1; auto_y++) {
+                for(int auto_x = cursor_x - 1; auto_x <= cursor_x + 1; auto_x++) {
+                    if(auto_x == cursor_x && auto_y == cursor_y) {
+                        continue;
+                    }
+                    if(auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 &&
+                       auto_y < PLAYFIELD_HEIGHT) {
+                        if(minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
+                            if(!play_move(minesweeper_state, auto_x, auto_y)) {
+                                // flags were wrong, we got a mine!
+                                return false;
+                            }
+                        }
+                    }
+                }
+            }
+            // we're done without hitting a mine - so return
+            return true;
         }
-      }
     }
-  }
-  // 〜( ̄▽ ̄〜) don't judge me (〜 ̄▽ ̄)〜
-  minesweeper_state->playfield[cursor_x][cursor_y] = hint;
-  minesweeper_state->fields_cleared++;
-  FURI_LOG_D("Minesweeper", "Setting %d,%d to %d", cursor_x, cursor_y, hint);
-  if (hint == 0) {
-    // the field is "empty"
-    // auto open surrounding fields.
-    for (int auto_y = cursor_y-1; auto_y <= cursor_y+1; auto_y++) {
-      for (int auto_x = cursor_x-1; auto_x <= cursor_x+1; auto_x++) {
-        if ( auto_x == cursor_x && auto_y == cursor_y ) {
-          continue;
+
+    // calculate number of surrounding mines.
+    int hint = 0;
+    for(int y = cursor_y - 1; y <= cursor_y + 1; y++) {
+        for(int x = cursor_x - 1; x <= cursor_x + 1; x++) {
+            if(x == cursor_x && y == cursor_y) {
+                // we're on the cell the user selected, so ignore.
+                continue;
+            }
+            // make sure we don't go OOB
+            if(x >= 0 && x < PLAYFIELD_WIDTH && y >= 0 && y < PLAYFIELD_HEIGHT) {
+                if(minesweeper_state->minefield[x][y] == FieldMine) {
+                    hint++;
+                }
+            }
         }
-        if ( auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 && auto_y < PLAYFIELD_HEIGHT) {
-          if (minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
-            play_move(minesweeper_state, auto_x, auto_y);
-          }
+    }
+    // 〜( ̄▽ ̄〜) don't judge me (〜 ̄▽ ̄)〜
+    minesweeper_state->playfield[cursor_x][cursor_y] = hint;
+    minesweeper_state->fields_cleared++;
+    FURI_LOG_D("Minesweeper", "Setting %d,%d to %d", cursor_x, cursor_y, hint);
+    if(hint == 0) {
+        // the field is "empty"
+        // auto open surrounding fields.
+        for(int auto_y = cursor_y - 1; auto_y <= cursor_y + 1; auto_y++) {
+            for(int auto_x = cursor_x - 1; auto_x <= cursor_x + 1; auto_x++) {
+                if(auto_x == cursor_x && auto_y == cursor_y) {
+                    continue;
+                }
+                if(auto_x >= 0 && auto_x < PLAYFIELD_WIDTH && auto_y >= 0 &&
+                   auto_y < PLAYFIELD_HEIGHT) {
+                    if(minesweeper_state->playfield[auto_x][auto_y] == TileTypeUncleared) {
+                        play_move(minesweeper_state, auto_x, auto_y);
+                    }
+                }
+            }
         }
-      }
     }
-  }
-  return true;
+    return true;
 }
 
 static void minesweeper_state_init(Minesweeper* const minesweeper_state) {
     minesweeper_state->cursor_x = minesweeper_state->cursor_y = 0;
     minesweeper_state->game_started = false;
-    for (int y = 0; y < PLAYFIELD_HEIGHT; y++) {
-      for (int x = 0; x < PLAYFIELD_WIDTH; x++){
-          minesweeper_state->playfield[x][y] = TileTypeUncleared;
-      }
+    for(int y = 0; y < PLAYFIELD_HEIGHT; y++) {
+        for(int x = 0; x < PLAYFIELD_WIDTH; x++) {
+            minesweeper_state->playfield[x][y] = TileTypeUncleared;
+        }
     }
 }
 
 int32_t minesweeper_app(void* p) {
-  UNUSED(p);
-  DialogsApp *dialogs = furi_record_open(RECORD_DIALOGS);
-
-  DialogMessage* message = dialog_message_alloc();
-  const char* header_text = "Minesweeper";
-  const char* message_text = "Hold OK pressed to toggle flags.\ngithub.com/panki27";
-
-  dialog_message_set_header(message, header_text, 64, 3, AlignCenter, AlignTop);
-  dialog_message_set_text(message, message_text, 64, 32, AlignCenter, AlignCenter);
-  dialog_message_set_buttons(message, NULL, "Play", NULL);
-
-  dialog_message_set_icon(message, NULL, 0, 10);
-
-  dialog_message_show(dialogs, message);
-  dialog_message_free(message);
-  furi_record_close(RECORD_DIALOGS);
-
-  FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
-
-  Minesweeper* minesweeper_state = malloc(sizeof(Minesweeper));
-  // setup
-  minesweeper_state_init(minesweeper_state);
-
-  ValueMutex state_mutex;
-  if (!init_mutex(&state_mutex, minesweeper_state, sizeof(minesweeper_state))) {
-      FURI_LOG_E("Minesweeper", "cannot create mutex\r\n");
-      free(minesweeper_state);
-      return 255;
-  }
-  // BEGIN IMPLEMENTATION
-
-  // Set system callbacks
-  ViewPort* view_port = view_port_alloc();
-  view_port_draw_callback_set(view_port, render_callback, &state_mutex);
-  view_port_input_callback_set(view_port, input_callback, event_queue);
-  minesweeper_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypeOnce, &state_mutex);
-
-  // Open GUI and register view_port
-  Gui* gui = furi_record_open("gui");
-  gui_add_view_port(gui, view_port, GuiLayerFullscreen);
-
-  PluginEvent event;
-  for (bool processing = true; processing;) {
-    FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
-    Minesweeper* minesweeper_state = (Minesweeper*)acquire_mutex_block(&state_mutex);
-    if(event_status == FuriStatusOk) {
-      // press events
-      if(event.type == EventTypeKey) {
-        if(event.input.type == InputTypeShort) {
-          switch(event.input.key) {
-            case InputKeyUp:
-              minesweeper_state->cursor_y--;
-              if(minesweeper_state->cursor_y < 0) {
-                 minesweeper_state->cursor_y = PLAYFIELD_HEIGHT - 1;
-              }
-              break;
-            case InputKeyDown:
-              minesweeper_state->cursor_y++;
-              if(minesweeper_state->cursor_y >= PLAYFIELD_HEIGHT) {
-                 minesweeper_state->cursor_y = 0;
-              }
-              break;
-            case InputKeyRight:
-              minesweeper_state->cursor_x++;
-              if(minesweeper_state->cursor_x >= PLAYFIELD_WIDTH) {
-                 minesweeper_state->cursor_x = 0;
-              }
-              break;
-            case InputKeyLeft:
-              minesweeper_state->cursor_x--;
-              if(minesweeper_state->cursor_x < 0) {
-                 minesweeper_state->cursor_x = PLAYFIELD_WIDTH-1;
-              }
-              break;
-            case InputKeyOk:
-              if (!minesweeper_state->game_started) {
-                setup_playfield(minesweeper_state);
-                minesweeper_state->game_started = true;
-              }
-              if (!play_move(minesweeper_state, minesweeper_state->cursor_x, minesweeper_state->cursor_y)) {
-                // ooops. looks like we hit a mine!
-                if (game_lost(minesweeper_state)) {
-                  // player wants to restart.
-                  setup_playfield(minesweeper_state);
-                } else {
-                  // player wants to exit :(
-                  processing = false;
-                }
-              } else {
-                // check win condition.
-                if (minesweeper_state->fields_cleared == (PLAYFIELD_HEIGHT*PLAYFIELD_WIDTH) - MINECOUNT){
-                  if (game_won(minesweeper_state)) {
-                    //player wants to restart
-                    setup_playfield(minesweeper_state);
-                  } else {
-                    processing = false;
-                  }
+    UNUSED(p);
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
+
+    Minesweeper* minesweeper_state = malloc(sizeof(Minesweeper));
+    // setup
+    minesweeper_state_init(minesweeper_state);
+
+    minesweeper_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!minesweeper_state->mutex) {
+        FURI_LOG_E("Minesweeper", "cannot create mutex\r\n");
+        free(minesweeper_state);
+        return 255;
+    }
+    // BEGIN IMPLEMENTATION
+
+    minesweeper_state->dialogs = furi_record_open(RECORD_DIALOGS);
+    minesweeper_state->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    DialogMessage* message = dialog_message_alloc();
+
+    dialog_message_set_header(message, "Minesweeper", 64, 3, AlignCenter, AlignTop);
+    dialog_message_set_text(
+        message,
+        "Hold OK pressed to toggle flags.\ngithub.com/panki27",
+        64,
+        32,
+        AlignCenter,
+        AlignCenter);
+    dialog_message_set_buttons(message, NULL, "Play", NULL);
+
+    dialog_message_show(minesweeper_state->dialogs, message);
+    dialog_message_free(message);
+
+    // Set system callbacks
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, render_callback, minesweeper_state);
+    view_port_input_callback_set(view_port, input_callback, event_queue);
+
+    // Open GUI and register view_port
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    // Call dolphin deed on game start
+    dolphin_deed(DolphinDeedPluginGameStart);
+
+    PluginEvent event;
+    for(bool processing = true; processing;) {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+        if(event_status == FuriStatusOk) {
+            // press events
+            if(event.type == EventTypeKey) {
+                if(event.input.type == InputTypeShort) {
+                    switch(event.input.key) {
+                    case InputKeyUp:
+                        furi_mutex_acquire(minesweeper_state->mutex, FuriWaitForever);
+                        minesweeper_state->cursor_y--;
+                        if(minesweeper_state->cursor_y < 0) {
+                            minesweeper_state->cursor_y = PLAYFIELD_HEIGHT - 1;
+                        }
+                        furi_mutex_release(minesweeper_state->mutex);
+                        break;
+                    case InputKeyDown:
+                        furi_mutex_acquire(minesweeper_state->mutex, FuriWaitForever);
+                        minesweeper_state->cursor_y++;
+                        if(minesweeper_state->cursor_y >= PLAYFIELD_HEIGHT) {
+                            minesweeper_state->cursor_y = 0;
+                        }
+                        furi_mutex_release(minesweeper_state->mutex);
+                        break;
+                    case InputKeyRight:
+                        furi_mutex_acquire(minesweeper_state->mutex, FuriWaitForever);
+                        minesweeper_state->cursor_x++;
+                        if(minesweeper_state->cursor_x >= PLAYFIELD_WIDTH) {
+                            minesweeper_state->cursor_x = 0;
+                        }
+                        furi_mutex_release(minesweeper_state->mutex);
+                        break;
+                    case InputKeyLeft:
+                        furi_mutex_acquire(minesweeper_state->mutex, FuriWaitForever);
+                        minesweeper_state->cursor_x--;
+                        if(minesweeper_state->cursor_x < 0) {
+                            minesweeper_state->cursor_x = PLAYFIELD_WIDTH - 1;
+                        }
+                        furi_mutex_release(minesweeper_state->mutex);
+                        break;
+                    case InputKeyOk:
+                        if(!minesweeper_state->game_started) {
+                            setup_playfield(minesweeper_state);
+                            minesweeper_state->game_started = true;
+                        }
+                        if(!play_move(
+                               minesweeper_state,
+                               minesweeper_state->cursor_x,
+                               minesweeper_state->cursor_y)) {
+                            // ooops. looks like we hit a mine!
+                            if(game_lost(minesweeper_state)) {
+                                // player wants to restart.
+                                setup_playfield(minesweeper_state);
+                            } else {
+                                // player wants to exit :(
+                                processing = false;
+                            }
+                        } else {
+                            // check win condition.
+                            if(minesweeper_state->fields_cleared ==
+                               (PLAYFIELD_HEIGHT * PLAYFIELD_WIDTH) - MINECOUNT) {
+                                if(game_won(minesweeper_state)) {
+                                    //player wants to restart
+                                    setup_playfield(minesweeper_state);
+                                } else {
+                                    processing = false;
+                                }
+                            }
+                        }
+                        break;
+                    case InputKeyBack:
+                        // Exit the plugin
+                        processing = false;
+                        break;
+                    default:
+                        break;
+                    }
+                } else if(event.input.type == InputTypeLong) {
+                    // hold events
+                    FURI_LOG_D("Minesweeper", "Got a long press!");
+                    switch(event.input.key) {
+                    case InputKeyUp:
+                    case InputKeyDown:
+                    case InputKeyRight:
+                    case InputKeyLeft:
+                        break;
+                    case InputKeyOk:
+                        FURI_LOG_D("Minesweeper", "Toggling flag");
+                        furi_mutex_acquire(minesweeper_state->mutex, FuriWaitForever);
+                        place_flag(minesweeper_state);
+                        furi_mutex_release(minesweeper_state->mutex);
+                        break;
+                    case InputKeyBack:
+                        processing = false;
+                        break;
+                    default:
+                        break;
+                    }
                 }
-              }
-              break;
-            case InputKeyBack:
-              // Exit the plugin
-              processing = false;
-              break;
-            case InputKeyMAX:
-              break;
-          }
-        } else if (event.input.type == InputTypeLong) {
-          // hold events
-          FURI_LOG_D("Minesweeper", "Got a long press!");
-          switch(event.input.key) {
-            case InputKeyUp:
-            case InputKeyDown:
-            case InputKeyRight:
-            case InputKeyLeft:
-              break;
-            case InputKeyOk:
-              FURI_LOG_D("Minesweeper", "Toggling flag");
-              place_flag(minesweeper_state);
-              break;
-            case InputKeyBack:
-              processing = false;
-              break;
-            case InputKeyMAX:
-              break;
-          }
+            }
         }
-      }
-    } else {
-      // event timeout
-      ;
+        view_port_update(view_port);
     }
-    view_port_update(view_port);
-    release_mutex(&state_mutex, minesweeper_state);
-  }
-  view_port_enabled_set(view_port, false);
-  gui_remove_view_port(gui, view_port);
-  furi_record_close("gui");
-  view_port_free(view_port);
-  furi_message_queue_free(event_queue);
-  delete_mutex(&state_mutex);
-  furi_timer_free(minesweeper_state->timer);
-  free(minesweeper_state);
-
-  return 0;
+    view_port_enabled_set(view_port, false);
+    gui_remove_view_port(gui, view_port);
+    furi_record_close(RECORD_GUI);
+    furi_record_close(RECORD_DIALOGS);
+    furi_record_close(RECORD_NOTIFICATION);
+    view_port_free(view_port);
+    furi_message_queue_free(event_queue);
+    furi_mutex_free(minesweeper_state->mutex);
+    free(minesweeper_state);
+
+    return 0;
 }