MX 2 лет назад
Родитель
Сommit
5b5c743a64

+ 21 - 0
base_pack/solitaire/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Tibor Tálosi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 9 - 3
base_pack/solitaire/defines.h

@@ -19,10 +19,15 @@ typedef struct {
     InputEvent input;
     InputEvent input;
 } AppEvent;
 } AppEvent;
 
 
-typedef enum { GameStateGameOver, GameStateStart, GameStatePlay, GameStateAnimate } PlayState;
+typedef enum {
+    GameStateGameOver,
+    GameStateStart,
+    GameStatePlay,
+    GameStateAnimate
+} PlayState;
 
 
 typedef struct {
 typedef struct {
-    uint8_t* buffer;
+    uint8_t *buffer;
     Card card;
     Card card;
     int8_t deck;
     int8_t deck;
     int indexes[4];
     int indexes[4];
@@ -44,6 +49,7 @@ typedef struct {
     InputKey input;
     InputKey input;
 
 
     bool started;
     bool started;
+    bool had_change;
     bool processing;
     bool processing;
     bool longPress;
     bool longPress;
     PlayState state;
     PlayState state;
@@ -52,6 +58,6 @@ typedef struct {
     uint8_t selectColumn;
     uint8_t selectColumn;
     int8_t selected_card;
     int8_t selected_card;
     CardAnimation animation;
     CardAnimation animation;
-    uint8_t* buffer;
+    uint8_t *buffer;
     FuriMutex* mutex;
     FuriMutex* mutex;
 } GameState;
 } GameState;

+ 296 - 304
base_pack/solitaire/solitaire.c

@@ -7,257 +7,235 @@
 #include "solitaire_icons.h"
 #include "solitaire_icons.h"
 #include <notification/notification.h>
 #include <notification/notification.h>
 #include <notification/notification_messages.h>
 #include <notification/notification_messages.h>
-void init(GameState* game_state);
+void init(GameState *game_state);
+
 const NotificationSequence sequence_fail = {
 const NotificationSequence sequence_fail = {
-    &message_vibro_on,
-    &message_note_c4,
-    &message_delay_10,
-    &message_vibro_off,
-    &message_sound_off,
-    &message_delay_10,
-
-    &message_vibro_on,
-    &message_note_a3,
-    &message_delay_10,
-    &message_vibro_off,
-    &message_sound_off,
-    NULL,
+        &message_vibro_on,
+        &message_note_c4,
+        &message_delay_10,
+        &message_vibro_off,
+        &message_sound_off,
+        &message_delay_10,
+
+        &message_vibro_on,
+        &message_note_a3,
+        &message_delay_10,
+        &message_vibro_off,
+        &message_sound_off,
+        NULL,
 };
 };
 int8_t columns[7][3] = {
 int8_t columns[7][3] = {
-    {1, 1, 25},
-    {19, 1, 25},
-    {37, 1, 25},
-    {55, 1, 25},
-    {73, 1, 25},
-    {91, 1, 25},
-    {109, 1, 25},
+        {1,   1, 25},
+        {19,  1, 25},
+        {37,  1, 25},
+        {55,  1, 25},
+        {73,  1, 25},
+        {91,  1, 25},
+        {109, 1, 25},
 };
 };
 
 
 bool can_place_card(Card where, Card what) {
 bool can_place_card(Card where, Card what) {
-    FURI_LOG_D(
-        APP_NAME,
-        "TESTING pip %i, letter %i with pip %i, letter %i",
-        where.pip,
-        where.character,
-        what.pip,
-        what.character);
     bool a_black = where.pip == 0 || where.pip == 3;
     bool a_black = where.pip == 0 || where.pip == 3;
     bool b_black = what.pip == 0 || what.pip == 3;
     bool b_black = what.pip == 0 || what.pip == 3;
-    if(a_black == b_black) return false;
+    if (a_black == b_black) return false;
 
 
-    int8_t a_letter = (int8_t)where.character;
-    int8_t b_letter = (int8_t)what.character;
-    if(a_letter == 12) a_letter = -1;
-    if(b_letter == 12) b_letter = -1;
+    int8_t a_letter = (int8_t) where.character;
+    int8_t b_letter = (int8_t) what.character;
+    if (a_letter == 12) a_letter = -1;
+    if (b_letter == 12) b_letter = -1;
 
 
     return (a_letter - 1) == b_letter;
     return (a_letter - 1) == b_letter;
 }
 }
 
 
-static void draw_scene(Canvas* const canvas, const GameState* game_state) {
-    int deckIndex = game_state->deck.index;
-    if(game_state->dragging_deck) deckIndex--;
-
-    if((game_state->deck.index < (game_state->deck.card_count - 1) ||
-        game_state->deck.index == -1) &&
-       game_state->deck.card_count > 0) {
-        draw_card_back_at(columns[0][0], columns[0][1], canvas);
-        if(game_state->selectRow == 0 && game_state->selectColumn == 0) {
-            draw_rounded_box(
-                canvas,
-                columns[0][0] + 1,
-                columns[0][1] + 1,
-                CARD_WIDTH - 2,
-                CARD_HEIGHT - 2,
-                Inverse);
-        }
-    } else
-        draw_card_space(
-            columns[0][0],
-            columns[0][1],
-            game_state->selectRow == 0 && game_state->selectColumn == 0,
-            canvas);
-    //deck side
-    if(deckIndex >= 0) {
-        Card c = game_state->deck.cards[deckIndex];
-        draw_card_at_colored(
-            columns[1][0],
-            columns[1][1],
-            c.pip,
-            c.character,
-            game_state->selectRow == 0 && game_state->selectColumn == 1,
-            canvas);
-    } else
-        draw_card_space(
-            columns[1][0],
-            columns[1][1],
-            game_state->selectRow == 0 && game_state->selectColumn == 1,
-            canvas);
-
-    for(uint8_t i = 0; i < 4; i++) {
-        Card current = game_state->top_cards[i];
-        bool selected = game_state->selectRow == 0 && game_state->selectColumn == (i + 3);
-        if(current.disabled) {
-            draw_card_space(columns[i + 3][0], columns[i + 3][1], selected, canvas);
-        } else {
-            draw_card_at(
-                columns[i + 3][0], columns[i + 3][1], current.pip, current.character, canvas);
-            if(selected) {
-                draw_rounded_box(
-                    canvas, columns[i + 3][0], columns[i + 3][1], CARD_WIDTH, CARD_HEIGHT, Inverse);
+static void draw_scene(Canvas *const canvas, const GameState *game_state) {
+
+    if(game_state->had_change){
+        int deckIndex = game_state->deck.index;
+        if (game_state->dragging_deck)
+            deckIndex--;
+
+        if ((game_state->deck.index < (game_state->deck.card_count - 1) || game_state->deck.index == -1) && game_state->deck.card_count>0) {
+            draw_card_back_at(columns[0][0], columns[0][1], canvas);
+            if (game_state->selectRow == 0 && game_state->selectColumn == 0) {
+                draw_rounded_box(canvas, columns[0][0] + 1, columns[0][1] + 1, CARD_WIDTH - 2, CARD_HEIGHT - 2,
+                                 Inverse);
+            }
+        } else
+            draw_card_space(columns[0][0], columns[0][1],
+                            game_state->selectRow == 0 && game_state->selectColumn == 0,
+                            canvas);
+        //deck side
+        if (deckIndex >= 0) {
+            Card c = game_state->deck.cards[deckIndex];
+            draw_card_at_colored(columns[1][0], columns[1][1], c.pip, c.character,
+                                 game_state->selectRow == 0 && game_state->selectColumn == 1, canvas);
+        } else
+            draw_card_space(columns[1][0], columns[1][1],
+                            game_state->selectRow == 0 && game_state->selectColumn == 1,
+                            canvas);
+
+        for (uint8_t i = 0; i < 4; i++) {
+            Card current = game_state->top_cards[i];
+            bool selected = game_state->selectRow == 0 && game_state->selectColumn == (i + 3);
+            if (current.disabled) {
+                draw_card_space(columns[i + 3][0], columns[i + 3][1], selected, canvas);
+            } else {
+                draw_card_at(columns[i + 3][0], columns[i + 3][1], current.pip, current.character, canvas);
+                if (selected) {
+                    draw_rounded_box(canvas, columns[i + 3][0], columns[i + 3][1], CARD_WIDTH, CARD_HEIGHT,
+                                     Inverse);
+                }
             }
             }
         }
         }
-    }
 
 
-    for(uint8_t i = 0; i < 7; i++) {
-        bool selected = game_state->selectRow == 1 && game_state->selectColumn == i;
-        int8_t index = (game_state->bottom_columns[i].index - 1 - game_state->selected_card);
-        if(index < 0) index = 0;
-        draw_hand_column(
-            game_state->bottom_columns[i],
-            columns[i][0],
-            columns[i][2],
-            selected ? index : -1,
-            canvas);
-    }
+        for (uint8_t i = 0; i < 7; i++) {
+            bool selected = game_state->selectRow == 1 && game_state->selectColumn == i;
+            int8_t index= (game_state->bottom_columns[i].index - 1 - game_state->selected_card);
+            if(index<0)index=0;
+            draw_hand_column(game_state->bottom_columns[i], columns[i][0], columns[i][2],
+                             selected ? index : -1, canvas);
+        }
 
 
-    int8_t pos[2] = {
-        columns[game_state->selectColumn][0],
-        columns[game_state->selectColumn][game_state->selectRow + 1]};
+        int8_t pos[2] = {columns[game_state->selectColumn][0],
+                         columns[game_state->selectColumn][game_state->selectRow + 1]};
 
 
-    /*     draw_icon_clip(canvas, &I_card_graphics, pos[0] + CARD_HALF_WIDTH, pos[1] + CARD_HALF_HEIGHT, 30, 5, 5, 5,
-                        Filled);*/
+        /*     draw_icon_clip(canvas, &I_card_graphics, pos[0] + CARD_HALF_WIDTH, pos[1] + CARD_HALF_HEIGHT, 30, 5, 5, 5,
+                            Filled);*/
+
+        if (game_state->dragging_hand.index > 0) {
+            draw_hand_column(game_state->dragging_hand,
+                             pos[0] + CARD_HALF_WIDTH + 3, pos[1] + CARD_HALF_HEIGHT + 3,
+                             -1, canvas);
+        }
 
 
-    if(game_state->dragging_hand.index > 0) {
-        draw_hand_column(
-            game_state->dragging_hand,
-            pos[0] + CARD_HALF_WIDTH + 3,
-            pos[1] + CARD_HALF_HEIGHT + 3,
-            -1,
-            canvas);
+        clone_buffer(get_buffer(canvas), game_state->animation.buffer);
+    }else{
+        clone_buffer(game_state->animation.buffer, get_buffer(canvas));
     }
     }
 }
 }
 
 
-static void draw_animation(Canvas* const canvas, const GameState* game_state) {
-    if(!game_state->animation.started) {
+static void draw_animation(Canvas *const canvas, const GameState *game_state) {
+    if (!game_state->animation.started) {
         draw_scene(canvas, game_state);
         draw_scene(canvas, game_state);
     } else {
     } else {
         clone_buffer(game_state->animation.buffer, get_buffer(canvas));
         clone_buffer(game_state->animation.buffer, get_buffer(canvas));
 
 
-        draw_card_at(
-            (int8_t)game_state->animation.x,
-            (int8_t)game_state->animation.y,
-            game_state->animation.card.pip,
-            game_state->animation.card.character,
-            canvas);
+        draw_card_at((int8_t) game_state->animation.x, (int8_t) game_state->animation.y, game_state->animation.card.pip,
+                     game_state->animation.card.character, canvas);
+
     }
     }
 
 
     clone_buffer(get_buffer(canvas), game_state->animation.buffer);
     clone_buffer(get_buffer(canvas), game_state->animation.buffer);
 }
 }
 
 
-static void render_callback(Canvas* const canvas, void* ctx) {
-    furi_assert(ctx);
-    const GameState* game_state = ctx;
-    furi_mutex_acquire(game_state->mutex, FuriWaitForever);
-
-    switch(game_state->state) {
-    case GameStateAnimate:
-        draw_animation(canvas, game_state);
-        break;
-    case GameStateStart:
-        canvas_draw_icon(canvas, 0, 0, &I_solitaire_main);
-        break;
-    case GameStatePlay:
-        draw_scene(canvas, game_state);
-        break;
-    default:
-        break;
+static void render_callback(Canvas *const canvas, void *ctx) {
+    const GameState *game_state = ctx;
+    furi_mutex_acquire(game_state->mutex, 25);
+    if (game_state == NULL) {
+        return;
+    }
+
+    switch (game_state->state) {
+        case GameStateAnimate:
+            draw_animation(canvas, game_state);
+            break;
+        case GameStateStart:
+            canvas_draw_icon(canvas, 0, 0, &I_solitaire_main);
+            break;
+        case GameStatePlay:
+            draw_scene(canvas, game_state);
+            break;
+        default:
+            break;
     }
     }
 
 
     furi_mutex_release(game_state->mutex);
     furi_mutex_release(game_state->mutex);
+
 }
 }
 
 
-void remove_drag(GameState* game_state) {
-    if(game_state->dragging_deck) {
+void remove_drag(GameState *game_state) {
+    if (game_state->dragging_deck) {
         remove_from_deck(game_state->deck.index, &(game_state->deck));
         remove_from_deck(game_state->deck.index, &(game_state->deck));
         game_state->dragging_deck = false;
         game_state->dragging_deck = false;
-    } else if(game_state->dragging_column < 7) {
+    } else if (game_state->dragging_column < 7) {
         game_state->dragging_column = 8;
         game_state->dragging_column = 8;
     }
     }
     game_state->dragging_hand.index = 0;
     game_state->dragging_hand.index = 0;
 }
 }
 
 
-bool handleInput(GameState* game_state) {
+bool handleInput(GameState *game_state) {
     Hand currentHand = game_state->bottom_columns[game_state->selectColumn];
     Hand currentHand = game_state->bottom_columns[game_state->selectColumn];
-    switch(game_state->input) {
-    case InputKeyUp:
-        if(game_state->selectRow > 0) {
-            int first = first_non_flipped_card(currentHand);
-            first = currentHand.index - first;
-            if((first - 1) > game_state->selected_card && game_state->dragging_hand.index == 0 &&
-               !game_state->longPress) {
-                game_state->selected_card++;
+    switch (game_state->input) {
+        case InputKeyUp:
+            if (game_state->selectRow > 0) {
+                int first = first_non_flipped_card(currentHand);
+                first = currentHand.index - first;
+                if ((first - 1) > game_state->selected_card && game_state->dragging_hand.index == 0 &&
+                    !game_state->longPress) {
+                    game_state->selected_card++;
+                } else {
+                    game_state->selectRow--;
+                    game_state->selected_card = 0;
+                }
+            }
+            break;
+        case InputKeyDown:
+            if (game_state->selectRow < 1) {
+                game_state->selectRow++;
+                game_state->selected_card = 0;
             } else {
             } else {
-                game_state->selectRow--;
+                if (game_state->selected_card > 0) {
+                    if (game_state->longPress)
+                        game_state->selected_card = 0;
+                    else
+                        game_state->selected_card--;
+                }
+            }
+            break;
+        case InputKeyRight:
+            if (game_state->selectColumn < 6) {
+                game_state->selectColumn++;
                 game_state->selected_card = 0;
                 game_state->selected_card = 0;
             }
             }
-        }
-        break;
-    case InputKeyDown:
-        if(game_state->selectRow < 1) {
-            game_state->selectRow++;
-            game_state->selected_card = 0;
-        } else {
-            if(game_state->selected_card > 0) {
-                if(game_state->longPress)
-                    game_state->selected_card = 0;
-                else
-                    game_state->selected_card--;
+            break;
+        case InputKeyLeft:
+            if (game_state->selectColumn > 0) {
+                game_state->selectColumn--;
+                game_state->selected_card = 0;
             }
             }
-        }
-        break;
-    case InputKeyRight:
-        if(game_state->selectColumn < 6) {
-            game_state->selectColumn++;
-            game_state->selected_card = 0;
-        }
-        break;
-    case InputKeyLeft:
-        if(game_state->selectColumn > 0) {
-            game_state->selectColumn--;
-            game_state->selected_card = 0;
-        }
-        break;
-    case InputKeyOk:
-        return true;
-        break;
-    default:
-        break;
+            break;
+        case InputKeyOk:
+            return true;
+            break;
+        default:
+            break;
     }
     }
-    if(game_state->selectRow == 0 && game_state->selectColumn == 2) {
-        if(game_state->input == InputKeyRight)
+    if (game_state->selectRow == 0 && game_state->selectColumn == 2) {
+        if (game_state->input == InputKeyRight)
             game_state->selectColumn++;
             game_state->selectColumn++;
         else
         else
             game_state->selectColumn--;
             game_state->selectColumn--;
     }
     }
-    if(game_state->dragging_hand.index > 0) game_state->selected_card = 0;
+    if (game_state->dragging_hand.index > 0)
+        game_state->selected_card = 0;
     return false;
     return false;
 }
 }
 
 
-bool place_on_top(Card* where, Card what) {
-    if(where->disabled && what.character == 12) {
+bool place_on_top(Card *where, Card what) {
+    if (where->disabled && what.character == 12) {
         where->disabled = what.disabled;
         where->disabled = what.disabled;
         where->pip = what.pip;
         where->pip = what.pip;
         where->character = what.character;
         where->character = what.character;
         return true;
         return true;
-    } else if(where->pip == what.pip) {
-        int8_t a_letter = (int8_t)where->character;
-        int8_t b_letter = (int8_t)what.character;
-        if(a_letter == 12) a_letter = -1;
-        if(b_letter == 12) b_letter = -1;
-
-        if(where->disabled && b_letter != -1) return false;
-
-        if((a_letter + 1) == b_letter) {
+    } else if (where->pip == what.pip) {
+        int8_t a_letter = (int8_t) where->character;
+        int8_t b_letter = (int8_t) what.character;
+        if (a_letter == 12) a_letter = -1;
+        if (b_letter == 12) b_letter = -1;
+        if(where->disabled && b_letter!=-1)
+            return false;
+
+        if ((a_letter + 1) == b_letter) {
             where->disabled = what.disabled;
             where->disabled = what.disabled;
             where->pip = what.pip;
             where->pip = what.pip;
             where->character = what.character;
             where->character = what.character;
@@ -267,92 +245,91 @@ bool place_on_top(Card* where, Card what) {
     return false;
     return false;
 }
 }
 
 
-void tick(GameState* game_state, NotificationApp* notification) {
+void tick(GameState *game_state, NotificationApp *notification) {
     game_state->last_tick = furi_get_tick();
     game_state->last_tick = furi_get_tick();
     uint8_t row = game_state->selectRow;
     uint8_t row = game_state->selectRow;
     uint8_t column = game_state->selectColumn;
     uint8_t column = game_state->selectColumn;
-    if(game_state->state != GameStatePlay && game_state->state != GameStateAnimate) return;
+    if (game_state->state != GameStatePlay && game_state->state != GameStateAnimate) return;
     bool wasAction = false;
     bool wasAction = false;
-    if(game_state->state == GameStatePlay) {
-        if(game_state->top_cards[0].character == 11 && game_state->top_cards[1].character == 11 &&
-           game_state->top_cards[2].character == 11 && game_state->top_cards[3].character == 11) {
-            dolphin_deed(DolphinDeedPluginGameWin);
+    if (game_state->state == GameStatePlay) {
+        if (game_state->top_cards[0].character == 11 && game_state->top_cards[1].character == 11 &&
+            game_state->top_cards[2].character == 11 && game_state->top_cards[3].character == 11) {
             game_state->state = GameStateAnimate;
             game_state->state = GameStateAnimate;
+            game_state->had_change=true;
+            dolphin_deed(DolphinDeedPluginGameWin);
+
             return;
             return;
         }
         }
     }
     }
-    if(handleInput(game_state)) {
-        if(game_state->state == GameStatePlay) {
-            if(game_state->longPress && game_state->dragging_hand.index == 1) {
-                for(uint8_t i = 0; i < 4; i++) {
-                    if(place_on_top(
-                           &(game_state->top_cards[i]), game_state->dragging_hand.cards[0])) {
+    if (handleInput(game_state)) {
+        if (game_state->state == GameStatePlay) {
+            if (game_state->longPress && game_state->dragging_hand.index == 1) {
+                for (uint8_t i = 0; i < 4; i++) {
+                    if (place_on_top(&(game_state->top_cards[i]), game_state->dragging_hand.cards[0])) {
                         remove_drag(game_state);
                         remove_drag(game_state);
                         wasAction = true;
                         wasAction = true;
                         break;
                         break;
                     }
                     }
                 }
                 }
             } else {
             } else {
-                if(row == 0 && column == 0 && game_state->dragging_hand.index == 0) {
+                if (row == 0 && column == 0 && game_state->dragging_hand.index == 0) {
                     FURI_LOG_D(APP_NAME, "Drawing card");
                     FURI_LOG_D(APP_NAME, "Drawing card");
                     game_state->deck.index++;
                     game_state->deck.index++;
                     wasAction = true;
                     wasAction = true;
-                    if(game_state->deck.index >= (game_state->deck.card_count))
+                    if (game_state->deck.index >= (game_state->deck.card_count))
                         game_state->deck.index = -1;
                         game_state->deck.index = -1;
                 }
                 }
-                //pick/place from deck
-                else if(row == 0 && column == 1) {
+                    //pick/place from deck
+                else if (row == 0 && column == 1) {
                     //place
                     //place
-                    if(game_state->dragging_deck) {
+                    if (game_state->dragging_deck) {
                         wasAction = true;
                         wasAction = true;
                         game_state->dragging_deck = false;
                         game_state->dragging_deck = false;
                         game_state->dragging_hand.index = 0;
                         game_state->dragging_hand.index = 0;
                     }
                     }
-                    //pick
+                        //pick
                     else {
                     else {
-                        if(game_state->dragging_hand.index == 0 && game_state->deck.index >= 0) {
+                        if (game_state->dragging_hand.index == 0 && game_state->deck.index >= 0) {
                             wasAction = true;
                             wasAction = true;
                             game_state->dragging_deck = true;
                             game_state->dragging_deck = true;
-                            add_to_hand(
-                                &(game_state->dragging_hand),
-                                game_state->deck.cards[game_state->deck.index]);
+                            add_to_hand(&(game_state->dragging_hand), game_state->deck.cards[game_state->deck.index]);
                         }
                         }
                     }
                     }
                 }
                 }
-                //place on top row
-                else if(row == 0 && game_state->dragging_hand.index == 1) {
+                    //place on top row
+                else if (row == 0 && game_state->dragging_hand.index == 1) {
                     column -= 3;
                     column -= 3;
                     Card currCard = game_state->dragging_hand.cards[0];
                     Card currCard = game_state->dragging_hand.cards[0];
                     wasAction = place_on_top(&(game_state->top_cards[column]), currCard);
                     wasAction = place_on_top(&(game_state->top_cards[column]), currCard);
-                    if(wasAction) remove_drag(game_state);
+                    if (wasAction)
+                        remove_drag(game_state);
                 }
                 }
-                //pick/place from bottom
-                else if(row == 1) {
-                    Hand* curr_hand = &(game_state->bottom_columns[column]);
+                    //pick/place from bottom
+                else if (row == 1) {
+                    Hand *curr_hand = &(game_state->bottom_columns[column]);
                     //pick up
                     //pick up
-                    if(game_state->dragging_hand.index == 0) {
+                    if (game_state->dragging_hand.index == 0) {
                         Card curr_card = curr_hand->cards[curr_hand->index - 1];
                         Card curr_card = curr_hand->cards[curr_hand->index - 1];
-                        if(curr_card.flipped) {
+                        if (curr_card.flipped) {
                             curr_hand->cards[curr_hand->index - 1].flipped = false;
                             curr_hand->cards[curr_hand->index - 1].flipped = false;
                             wasAction = true;
                             wasAction = true;
                         } else {
                         } else {
-                            if(curr_hand->index > 0) {
-                                extract_hand_region(
-                                    curr_hand,
-                                    &(game_state->dragging_hand),
-                                    curr_hand->index - game_state->selected_card - 1);
+                            if (curr_hand->index > 0) {
+                                extract_hand_region(curr_hand, &(game_state->dragging_hand),
+                                                    curr_hand->index - game_state->selected_card - 1);
                                 game_state->selected_card = 0;
                                 game_state->selected_card = 0;
                                 game_state->dragging_column = column;
                                 game_state->dragging_column = column;
                                 wasAction = true;
                                 wasAction = true;
                             }
                             }
                         }
                         }
                     }
                     }
-                    //place
+                        //place
                     else {
                     else {
                         Card first = game_state->dragging_hand.cards[0];
                         Card first = game_state->dragging_hand.cards[0];
-                        if(game_state->dragging_column == column ||
-                           (curr_hand->index == 0 && first.character == 11) ||
-                           can_place_card(curr_hand->cards[curr_hand->index - 1], first)) {
+                        if (game_state->dragging_column == column ||
+                            (curr_hand->index == 0 && first.character == 11) ||
+                            can_place_card(curr_hand->cards[curr_hand->index - 1], first)
+                                ) {
                             add_hand_region(curr_hand, &(game_state->dragging_hand));
                             add_hand_region(curr_hand, &(game_state->dragging_hand));
                             remove_drag(game_state);
                             remove_drag(game_state);
                             wasAction = true;
                             wasAction = true;
@@ -361,34 +338,41 @@ void tick(GameState* game_state, NotificationApp* notification) {
                 }
                 }
             }
             }
 
 
-            if(!wasAction) {
+
+            if (!wasAction) {
                 notification_message(notification, &sequence_fail);
                 notification_message(notification, &sequence_fail);
             }
             }
         }
         }
     }
     }
-    if(game_state->state == GameStateAnimate) {
-        if(game_state->animation.started && !game_state->longPress &&
-           game_state->input == InputKeyOk) {
+    if (game_state->state == GameStateAnimate) {
+        if (game_state->animation.started && !game_state->longPress && game_state->input==InputKeyOk) {
             init(game_state);
             init(game_state);
             game_state->state = GameStateStart;
             game_state->state = GameStateStart;
         }
         }
 
 
         game_state->animation.started = true;
         game_state->animation.started = true;
-        if(game_state->animation.x < -20 || game_state->animation.x > 128) {
+        if (game_state->animation.x < -20 || game_state->animation.x > 128) {
             game_state->animation.deck++;
             game_state->animation.deck++;
-            if(game_state->animation.deck > 3) game_state->animation.deck = 0;
+            if (game_state->animation.deck > 3)
+                game_state->animation.deck = 0;
             int8_t cardIndex = 11 - game_state->animation.indexes[game_state->animation.deck];
             int8_t cardIndex = 11 - game_state->animation.indexes[game_state->animation.deck];
 
 
-            if(game_state->animation.indexes[0] == 13 && game_state->animation.indexes[1] == 13 &&
-               game_state->animation.indexes[2] == 13 && game_state->animation.indexes[3] == 13) {
+            if (game_state->animation.indexes[0] == 13 &&
+                game_state->animation.indexes[1] == 13 &&
+                game_state->animation.indexes[2] == 13 &&
+                game_state->animation.indexes[3] == 13) {
                 init(game_state);
                 init(game_state);
                 game_state->state = GameStateStart;
                 game_state->state = GameStateStart;
                 return;
                 return;
             }
             }
 
 
-            if(cardIndex == -1) cardIndex = 12;
-            game_state->animation.card = (Card){
-                game_state->top_cards[game_state->animation.deck].pip, cardIndex, false, false};
+            if (cardIndex == -1)
+                cardIndex = 12;
+            game_state->animation.card = (Card) {
+                    game_state->top_cards[game_state->animation.deck].pip,
+                    cardIndex,
+                    false, false
+            };
             game_state->animation.indexes[game_state->animation.deck]++;
             game_state->animation.indexes[game_state->animation.deck]++;
             game_state->animation.vx = -(rand() % 3 + 1) * (rand() % 2 == 1 ? 1 : -1);
             game_state->animation.vx = -(rand() % 3 + 1) * (rand() % 2 == 1 ? 1 : -1);
             game_state->animation.vy = (rand() % 3 + 1);
             game_state->animation.vy = (rand() % 3 + 1);
@@ -398,16 +382,17 @@ void tick(GameState* game_state, NotificationApp* notification) {
         game_state->animation.x += game_state->animation.vx;
         game_state->animation.x += game_state->animation.vx;
         game_state->animation.y -= game_state->animation.vy;
         game_state->animation.y -= game_state->animation.vy;
         game_state->animation.vy -= 1;
         game_state->animation.vy -= 1;
-        if(game_state->animation.vy < -10) game_state->animation.vy = -10;
+        if (game_state->animation.vy < -10)game_state->animation.vy = -10;
 
 
-        if(game_state->animation.y > 41) {
+        if (game_state->animation.y > 41) {
             game_state->animation.y = 41;
             game_state->animation.y = 41;
             game_state->animation.vy = -(game_state->animation.vy * 0.7f);
             game_state->animation.vy = -(game_state->animation.vy * 0.7f);
         }
         }
     }
     }
 }
 }
 
 
-void init(GameState* game_state) {
+void init(GameState *game_state) {
+    dolphin_deed(DolphinDeedPluginGameStart);
     game_state->selectColumn = 0;
     game_state->selectColumn = 0;
     game_state->selected_card = 0;
     game_state->selected_card = 0;
     game_state->selectRow = 0;
     game_state->selectRow = 0;
@@ -420,49 +405,52 @@ void init(GameState* game_state) {
     game_state->state = GameStatePlay;
     game_state->state = GameStatePlay;
     game_state->dragging_column = 8;
     game_state->dragging_column = 8;
 
 
-    for(uint8_t i = 0; i < 7; i++) {
+    for (uint8_t i = 0; i < 7; i++) {
         free_hand(&(game_state->bottom_columns[i]));
         free_hand(&(game_state->bottom_columns[i]));
         init_hand(&(game_state->bottom_columns[i]), 21);
         init_hand(&(game_state->bottom_columns[i]), 21);
         game_state->bottom_columns[i].index = 0;
         game_state->bottom_columns[i].index = 0;
-        for(uint8_t j = 0; j <= i; j++) {
+        for (uint8_t j = 0; j <= i; j++) {
             Card cur = remove_from_deck(0, &(game_state->deck));
             Card cur = remove_from_deck(0, &(game_state->deck));
             cur.flipped = i != j;
             cur.flipped = i != j;
             add_to_hand(&(game_state->bottom_columns[i]), cur);
             add_to_hand(&(game_state->bottom_columns[i]), cur);
         }
         }
     }
     }
 
 
-    for(uint8_t i = 0; i < 4; i++) {
+    for (uint8_t i = 0; i < 4; i++) {
         game_state->animation.indexes[i] = 0;
         game_state->animation.indexes[i] = 0;
-        game_state->top_cards[i] = (Card){0, 0, true, false};
+        game_state->top_cards[i] = (Card) {0, 0, true, false};
     }
     }
     game_state->deck.index = -1;
     game_state->deck.index = -1;
 }
 }
 
 
-void init_start(GameState* game_state) {
+void init_start(GameState *game_state) {
     game_state->input = InputKeyMAX;
     game_state->input = InputKeyMAX;
-    for(uint8_t i = 0; i < 7; i++) init_hand(&(game_state->bottom_columns[i]), 21);
+    for (uint8_t i = 0; i < 7; i++)
+        init_hand(&(game_state->bottom_columns[i]), 21);
 
 
     init_hand(&(game_state->dragging_hand), 13);
     init_hand(&(game_state->dragging_hand), 13);
     game_state->animation.buffer = make_buffer();
     game_state->animation.buffer = make_buffer();
+
 }
 }
 
 
-static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+
+static void input_callback(InputEvent *input_event, FuriMessageQueue *event_queue) {
     furi_assert(event_queue);
     furi_assert(event_queue);
     AppEvent event = {.type = EventTypeKey, .input = *input_event};
     AppEvent event = {.type = EventTypeKey, .input = *input_event};
     furi_message_queue_put(event_queue, &event, FuriWaitForever);
     furi_message_queue_put(event_queue, &event, FuriWaitForever);
 }
 }
 
 
-static void update_timer_callback(FuriMessageQueue* event_queue) {
+static void update_timer_callback(FuriMessageQueue *event_queue) {
     furi_assert(event_queue);
     furi_assert(event_queue);
     AppEvent event = {.type = EventTypeTick};
     AppEvent event = {.type = EventTypeTick};
     furi_message_queue_put(event_queue, &event, 0);
     furi_message_queue_put(event_queue, &event, 0);
 }
 }
 
 
-int32_t solitaire_app(void* p) {
+int32_t solitaire_app(void *p) {
     UNUSED(p);
     UNUSED(p);
     int32_t return_code = 0;
     int32_t return_code = 0;
-    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
-    GameState* game_state = malloc(sizeof(GameState));
+    FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
+    GameState *game_state = malloc(sizeof(GameState));
     init_start(game_state);
     init_start(game_state);
     set_card_graphics(&I_card_graphics);
     set_card_graphics(&I_card_graphics);
 
 
@@ -470,85 +458,87 @@ int32_t solitaire_app(void* p) {
 
 
     game_state->processing = true;
     game_state->processing = true;
     game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
     game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
-    if(!game_state->mutex) {
+    if (!game_state->mutex) {
         FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
         FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
         return_code = 255;
         return_code = 255;
         goto free_and_exit;
         goto free_and_exit;
     }
     }
-    NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+    NotificationApp *notification = furi_record_open(RECORD_NOTIFICATION);
 
 
     notification_message_block(notification, &sequence_display_backlight_enforce_on);
     notification_message_block(notification, &sequence_display_backlight_enforce_on);
 
 
-    ViewPort* view_port = view_port_alloc();
+    ViewPort *view_port = view_port_alloc();
     view_port_draw_callback_set(view_port, render_callback, game_state);
     view_port_draw_callback_set(view_port, render_callback, game_state);
     view_port_input_callback_set(view_port, input_callback, event_queue);
     view_port_input_callback_set(view_port, input_callback, event_queue);
 
 
-    FuriTimer* timer = furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
+    FuriTimer *timer =
+            furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
     furi_timer_start(timer, furi_kernel_get_tick_frequency() / 30);
     furi_timer_start(timer, furi_kernel_get_tick_frequency() / 30);
 
 
-    Gui* gui = furi_record_open(RECORD_GUI);
+    Gui *gui = furi_record_open("gui");
     gui_add_view_port(gui, view_port, GuiLayerFullscreen);
     gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 
 
     AppEvent event;
     AppEvent event;
-
-    // Call Dolphin deed on game start
-    dolphin_deed(DolphinDeedPluginGameStart);
-
-    for(bool processing = true; processing;) {
+    for (bool processing = true; processing;) {
         FuriStatus event_status = furi_message_queue_get(event_queue, &event, 150);
         FuriStatus event_status = furi_message_queue_get(event_queue, &event, 150);
         furi_mutex_acquire(game_state->mutex, FuriWaitForever);
         furi_mutex_acquire(game_state->mutex, FuriWaitForever);
-        if(event_status == FuriStatusOk) {
-            if(event.type == EventTypeKey) {
-                if(event.input.type == InputTypeLong) {
+        game_state->had_change = false;
+        if (event_status == FuriStatusOk) {
+            if (event.type == EventTypeKey) {
+                game_state->had_change = true;
+                if (event.input.type == InputTypeLong) {
                     game_state->longPress = true;
                     game_state->longPress = true;
-                    switch(event.input.key) {
-                    case InputKeyUp:
-                    case InputKeyDown:
-                    case InputKeyRight:
-                    case InputKeyLeft:
-                    case InputKeyOk:
-                        game_state->input = event.input.key;
-                        break;
-                    case InputKeyBack:
-                        processing = false;
-                        return_code = 1;
-                    default:
-                        break;
+                    switch (event.input.key) {
+                        case InputKeyUp:
+                        case InputKeyDown:
+                        case InputKeyRight:
+                        case InputKeyLeft:
+                        case InputKeyOk:
+                            game_state->input = event.input.key;
+                            break;
+                        case InputKeyBack:
+                            processing = false;
+                            return_code = 1;
+                        default:
+                            break;
                     }
                     }
-                } else if(event.input.type == InputTypePress) {
+                } else if (event.input.type == InputTypePress) {
                     game_state->longPress = false;
                     game_state->longPress = false;
-                    switch(event.input.key) {
-                    case InputKeyUp:
-                    case InputKeyDown:
-                    case InputKeyRight:
-                    case InputKeyLeft:
-                    case InputKeyOk:
-                        if(event.input.key == InputKeyOk && game_state->state == GameStateStart) {
-                            game_state->state = GameStatePlay;
+                    switch (event.input.key) {
+                        case InputKeyUp:
+                        case InputKeyDown:
+                        case InputKeyRight:
+                        case InputKeyLeft:
+                        case InputKeyOk:
+                            if (event.input.key == InputKeyOk && game_state->state == GameStateStart) {
+                                game_state->state = GameStatePlay;
+                                init(game_state);
+                            }
+                            else {
+                                game_state->input = event.input.key;
+                            }
+                            break;
+                        case InputKeyBack:
                             init(game_state);
                             init(game_state);
-                        } else {
-                            game_state->input = event.input.key;
-                        }
-                        break;
-                    case InputKeyBack:
-                        init(game_state);
-                        processing = false;
-                        return_code = 1;
-                        break;
-                    default:
-                        break;
+                            processing = false;
+                            return_code = 1;
+                            break;
+                        default:
+                            break;
                     }
                     }
                 }
                 }
-            } else if(event.type == EventTypeTick) {
+            } else if (event.type == EventTypeTick) {
                 tick(game_state, notification);
                 tick(game_state, notification);
                 processing = game_state->processing;
                 processing = game_state->processing;
                 game_state->input = InputKeyMAX;
                 game_state->input = InputKeyMAX;
             }
             }
         }
         }
+
         view_port_update(view_port);
         view_port_update(view_port);
         furi_mutex_release(game_state->mutex);
         furi_mutex_release(game_state->mutex);
     }
     }
 
 
+
     notification_message_block(notification, &sequence_display_backlight_enforce_auto);
     notification_message_block(notification, &sequence_display_backlight_enforce_auto);
     furi_timer_free(timer);
     furi_timer_free(timer);
     view_port_enabled_set(view_port, false);
     view_port_enabled_set(view_port, false);
@@ -558,13 +548,15 @@ int32_t solitaire_app(void* p) {
     view_port_free(view_port);
     view_port_free(view_port);
     furi_mutex_free(game_state->mutex);
     furi_mutex_free(game_state->mutex);
 
 
-free_and_exit:
+    free_and_exit:
     free(game_state->animation.buffer);
     free(game_state->animation.buffer);
     ui_cleanup();
     ui_cleanup();
-    for(uint8_t i = 0; i < 7; i++) free_hand(&(game_state->bottom_columns[i]));
+    for (uint8_t i = 0; i < 7; i++)
+        free_hand(&(game_state->bottom_columns[i]));
 
 
     free(game_state->deck.cards);
     free(game_state->deck.cards);
     free(game_state);
     free(game_state);
     furi_message_queue_free(event_queue);
     furi_message_queue_free(event_queue);
+
     return return_code;
     return return_code;
 }
 }

+ 8 - 0
non_catalog_apps/sudoku/LICENSE

@@ -0,0 +1,8 @@
+The MIT License (MIT)
+Copyright © 2023 @profelis
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 1 - 1
non_catalog_apps/sudoku/application.fam

@@ -14,6 +14,6 @@ App(
     fap_icon="sudoku.png",
     fap_icon="sudoku.png",
     fap_description="Sudoku game",
     fap_description="Sudoku game",
     fap_author="@profelis",
     fap_author="@profelis",
-    # fap_weburl="https://github.com/user/template",
+    fap_weburl="https://github.com/profelis/fz-sudoku",
     # fap_icon_assets="images",  # Image assets to compile for this application
     # fap_icon_assets="images",  # Image assets to compile for this application
 )
 )

+ 29 - 9
non_catalog_apps/sudoku/sudoku.c

@@ -41,6 +41,7 @@ typedef struct {
     GameState state;
     GameState state;
     int8_t menuCursor;
     int8_t menuCursor;
     int8_t lastGameMode;
     int8_t lastGameMode;
+    bool blockInputUntilRelease;
 } SudokuState;
 } SudokuState;
 
 
 #define MENU_ITEMS_COUNT 5
 #define MENU_ITEMS_COUNT 5
@@ -93,7 +94,7 @@ static int get_mode_gaps(int index) {
     return HARD_GAPS;
     return HARD_GAPS;
 }
 }
 
 
-#define SAVE_VERSION 1
+#define SAVE_VERSION 2
 #define SAVE_FILE APP_DATA_PATH("save.dat")
 #define SAVE_FILE APP_DATA_PATH("save.dat")
 
 
 static bool load_game(SudokuState* state) {
 static bool load_game(SudokuState* state) {
@@ -414,6 +415,7 @@ static bool start_game(SudokuState* state) {
     state->state = GameStateRunning;
     state->state = GameStateRunning;
     state->cursorX = 0;
     state->cursorX = 0;
     state->cursorY = 0;
     state->cursorY = 0;
+    state->blockInputUntilRelease = false;
     init_board(state);
     init_board(state);
     shuffle_board(state, 10);
     shuffle_board(state, 10);
     add_gaps(state, get_mode_gaps(state->lastGameMode));
     add_gaps(state, get_mode_gaps(state->lastGameMode));
@@ -496,23 +498,31 @@ int32_t sudoku_main(void* p) {
                 }
                 }
             }
             }
 
 
+            bool invalidLidAndRow =
+                !(state->horizontalFlags & (1 << state->cursorY) ||
+                  state->vertivalFlags & (1 << state->cursorX));
+
             if(event.type == InputTypePress || event.type == InputTypeLong ||
             if(event.type == InputTypePress || event.type == InputTypeLong ||
                event.type == InputTypeRepeat) {
                event.type == InputTypeRepeat) {
                 switch(event.key) {
                 switch(event.key) {
                 case InputKeyLeft:
                 case InputKeyLeft:
+                    state->blockInputUntilRelease = false;
                     state->cursorX = (state->cursorX + BOARD_SIZE - 1) % BOARD_SIZE;
                     state->cursorX = (state->cursorX + BOARD_SIZE - 1) % BOARD_SIZE;
                     break;
                     break;
                 case InputKeyRight:
                 case InputKeyRight:
+                    state->blockInputUntilRelease = false;
                     state->cursorX = (state->cursorX + 1) % BOARD_SIZE;
                     state->cursorX = (state->cursorX + 1) % BOARD_SIZE;
                     break;
                     break;
                 case InputKeyUp:
                 case InputKeyUp:
+                    state->blockInputUntilRelease = false;
                     state->cursorY = (state->cursorY + BOARD_SIZE - 1) % BOARD_SIZE;
                     state->cursorY = (state->cursorY + BOARD_SIZE - 1) % BOARD_SIZE;
                     break;
                     break;
                 case InputKeyDown:
                 case InputKeyDown:
+                    state->blockInputUntilRelease = false;
                     state->cursorY = (state->cursorY + 1) % BOARD_SIZE;
                     state->cursorY = (state->cursorY + 1) % BOARD_SIZE;
                     break;
                     break;
                 case InputKeyOk:
                 case InputKeyOk:
-                    if(userInput) {
+                    if(userInput && !state->blockInputUntilRelease) {
                         int flags = state->board[state->cursorX][state->cursorY] & FLAGS_MASK;
                         int flags = state->board[state->cursorX][state->cursorY] & FLAGS_MASK;
                         int value = state->board[state->cursorX][state->cursorY] & VALUE_MASK;
                         int value = state->board[state->cursorX][state->cursorY] & VALUE_MASK;
                         state->board[state->cursorX][state->cursorY] = flags | ((value + 1) % 10);
                         state->board[state->cursorX][state->cursorY] = flags | ((value + 1) % 10);
@@ -522,14 +532,24 @@ int32_t sudoku_main(void* p) {
                 default:
                 default:
                     break;
                     break;
                 }
                 }
+            } else if(event.type == InputTypeRelease) {
+                state->blockInputUntilRelease = false;
             }
             }
-            if(invalidField && validate_board(state)) {
-                dolphin_deed(DolphinDeedPluginGameWin);
-                state->state = GameStateVictory;
-                state->menuCursor = 0;
-                for(int i = 0; i != BOARD_SIZE; ++i) {
-                    for(int j = 0; j != BOARD_SIZE; ++j) {
-                        state->board[i][j] &= ~USER_INPUT_FLAG;
+            if(invalidField) {
+                if(validate_board(state)) {
+                    dolphin_deed(DolphinDeedPluginGameWin);
+                    state->state = GameStateVictory;
+                    state->menuCursor = 0;
+                    for(int i = 0; i != BOARD_SIZE; ++i) {
+                        for(int j = 0; j != BOARD_SIZE; ++j) {
+                            state->board[i][j] &= ~USER_INPUT_FLAG;
+                        }
+                    }
+                } else {
+                    bool isValidLineOrRow = state->horizontalFlags & (1 << state->cursorY) ||
+                                            state->vertivalFlags & (1 << state->cursorX);
+                    if(invalidLidAndRow && isValidLineOrRow) {
+                        state->blockInputUntilRelease = true;
                     }
                     }
                 }
                 }
             }
             }