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

Add solitaire from https://github.com/doofy-dev/flipper_solitaire

git-subtree-dir: solitaire
git-subtree-mainline: bbad44fcb3b53bf6d4c8fa935c40c8779b95eb13
git-subtree-split: 8f7c749e5dafad8740d9c10be14f0b07d4130c58
Willy-JL 2 лет назад
Родитель
Сommit
32fd089097

+ 3 - 0
solitaire/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "common"]
+	path = common
+	url = https://github.com/teeebor/flipper_helpers.git

+ 1 - 0
solitaire/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/doofy-dev/flipper_solitaire main

+ 21 - 0
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.

+ 30 - 0
solitaire/README.md

@@ -0,0 +1,30 @@
+[![GitHub release](https://img.shields.io/github/release/teeebor/flipper_solitaire?include_prereleases=&sort=semver&color=blue)](https://github.com/teeebor/flipper_solitaire/releases/)
+[![License](https://img.shields.io/badge/License-MIT-blue)](/LICENSE)
+[![issues - flipper-zero_authenticator](https://img.shields.io/github/issues/teeebor/flipper_solitaire)](https://github.com/teeebor/flipper_solitaire/issues)
+![maintained - yes](https://img.shields.io/badge/maintained-yes-blue)
+![contributions - welcome](https://img.shields.io/badge/contributions-welcome-blue)
+# Solitaire game for Flipper Zero
+
+![Play screen](screenshots/solitaire.png)
+
+![Play screen](screenshots/solitaire.gif)
+
+### Shortcuts
+* Long press up skips the navigation inside the bottom column
+* Long press center to automatically place the card to the top rigth section
+
+## Building
+> The app should be compatible with the official and custom flipper firmwares. If not, follow these steps to build it
+> yourself
+* Download your firmware's source code
+* Clone the repository recursively `git clone REPO_URL --recursive` into the firmware's applications_user folder
+* Navigate into the firmwares root folder
+* Make sure you can use
+  the [Fipper build tool](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md)
+* To build the project, type this into your console:
+  #### Linux
+  > ./fbt fap_{APP_NAME}
+  #### Windows
+  > fbt.cmd fap_{APP_NAME}
+* the finished build will be in the following location, copy this into your SD card:
+  > build\f7-firmware-D\.extapps\blackjack.fap

+ 13 - 0
solitaire/application.fam

@@ -0,0 +1,13 @@
+App(
+    appid="solitaire",
+    name="Solitaire",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="solitaire_app",
+    cdefines=["APP_SOLITAIRE"],
+    requires=["gui","storage","canvas"],
+    stack_size=2 * 1024,
+    order=30,
+    fap_icon="solitaire_10px.png",
+    fap_category="Games",
+    fap_icon_assets="assets"
+)

BIN
solitaire/assets/card_graphics.png


BIN
solitaire/assets/solitaire_main.png


+ 1 - 0
solitaire/common

@@ -0,0 +1 @@
+Subproject commit 4ef796c450428521fc576c8e5c993d027061414d

+ 63 - 0
solitaire/defines.h

@@ -0,0 +1,63 @@
+#pragma once
+#include <furi.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <flipper_format/flipper_format.h>
+#include <flipper_format/flipper_format_i.h>
+#include "common/card.h"
+#include "common/queue.h"
+
+#define APP_NAME "Solitaire"
+
+typedef enum {
+    EventTypeTick,
+    EventTypeKey,
+} EventType;
+
+typedef struct {
+    EventType type;
+    InputEvent input;
+} AppEvent;
+
+typedef enum {
+    GameStateGameOver,
+    GameStateStart,
+    GameStatePlay,
+    GameStateAnimate
+} PlayState;
+
+typedef struct {
+    uint8_t *buffer;
+    Card card;
+    int8_t deck;
+    int indexes[4];
+    float x;
+    float y;
+    float vx;
+    float vy;
+    bool started;
+} CardAnimation;
+
+typedef struct {
+    Deck deck;
+    Hand bottom_columns[7];
+    Card top_cards[4];
+    bool dragging_deck;
+    uint8_t dragging_column;
+    Hand dragging_hand;
+
+    InputKey input;
+
+    bool started;
+    bool had_change;
+    bool processing;
+    bool longPress;
+    PlayState state;
+    unsigned int last_tick;
+    uint8_t selectRow;
+    uint8_t selectColumn;
+    int8_t selected_card;
+    CardAnimation animation;
+    uint8_t *buffer;
+    FuriMutex* mutex;
+} GameState;

BIN
solitaire/screenshots/solitaire.gif


BIN
solitaire/screenshots/solitaire.png


+ 565 - 0
solitaire/solitaire.c

@@ -0,0 +1,565 @@
+#include <stdlib.h>
+#include <dolphin/dolphin.h>
+#include <furi.h>
+#include <gui/canvas_i.h>
+#include "defines.h"
+#include "common/ui.h"
+#include "solitaire_icons.h"
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+void init(GameState *game_state);
+
+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,
+};
+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},
+};
+
+bool can_place_card(Card where, Card what) {
+    bool a_black = where.pip == 0 || where.pip == 3;
+    bool b_black = what.pip == 0 || what.pip == 3;
+    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;
+
+    return (a_letter - 1) == b_letter;
+}
+
+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);
+        }
+
+        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);*/
+
+        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) {
+        draw_scene(canvas, game_state);
+    } else {
+        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);
+
+    }
+
+    clone_buffer(get_buffer(canvas), game_state->animation.buffer);
+}
+
+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);
+
+}
+
+void remove_drag(GameState *game_state) {
+    if (game_state->dragging_deck) {
+        remove_from_deck(game_state->deck.index, &(game_state->deck));
+        game_state->dragging_deck = false;
+    } else if (game_state->dragging_column < 7) {
+        game_state->dragging_column = 8;
+    }
+    game_state->dragging_hand.index = 0;
+}
+
+bool handleInput(GameState *game_state) {
+    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++;
+                } 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 {
+                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;
+            }
+            break;
+        case InputKeyLeft:
+            if (game_state->selectColumn > 0) {
+                game_state->selectColumn--;
+                game_state->selected_card = 0;
+            }
+            break;
+        case InputKeyOk:
+            return true;
+            break;
+        default:
+            break;
+    }
+    if (game_state->selectRow == 0 && game_state->selectColumn == 2) {
+        if (game_state->input == InputKeyRight)
+            game_state->selectColumn++;
+        else
+            game_state->selectColumn--;
+    }
+    if (game_state->dragging_hand.index > 0)
+        game_state->selected_card = 0;
+    return false;
+}
+
+bool place_on_top(Card *where, Card what) {
+    if (where->disabled && what.character == 12) {
+        where->disabled = what.disabled;
+        where->pip = what.pip;
+        where->character = what.character;
+        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) {
+            where->disabled = what.disabled;
+            where->pip = what.pip;
+            where->character = what.character;
+            return true;
+        }
+    }
+    return false;
+}
+
+void tick(GameState *game_state, NotificationApp *notification) {
+    game_state->last_tick = furi_get_tick();
+    uint8_t row = game_state->selectRow;
+    uint8_t column = game_state->selectColumn;
+    if (game_state->state != GameStatePlay && game_state->state != GameStateAnimate) return;
+    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) {
+            game_state->state = GameStateAnimate;
+            game_state->had_change=true;
+            dolphin_deed(DolphinDeedPluginGameWin);
+
+            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])) {
+                        remove_drag(game_state);
+                        wasAction = true;
+                        break;
+                    }
+                }
+            } else {
+                if (row == 0 && column == 0 && game_state->dragging_hand.index == 0) {
+                    FURI_LOG_D(APP_NAME, "Drawing card");
+                    game_state->deck.index++;
+                    wasAction = true;
+                    if (game_state->deck.index >= (game_state->deck.card_count))
+                        game_state->deck.index = -1;
+                }
+                    //pick/place from deck
+                else if (row == 0 && column == 1) {
+                    //place
+                    if (game_state->dragging_deck) {
+                        wasAction = true;
+                        game_state->dragging_deck = false;
+                        game_state->dragging_hand.index = 0;
+                    }
+                        //pick
+                    else {
+                        if (game_state->dragging_hand.index == 0 && game_state->deck.index >= 0) {
+                            wasAction = true;
+                            game_state->dragging_deck = true;
+                            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) {
+                    column -= 3;
+                    Card currCard = game_state->dragging_hand.cards[0];
+                    wasAction = place_on_top(&(game_state->top_cards[column]), currCard);
+                    if (wasAction)
+                        remove_drag(game_state);
+                }
+                    //pick/place from bottom
+                else if (row == 1) {
+                    Hand *curr_hand = &(game_state->bottom_columns[column]);
+                    //pick up
+                    if (game_state->dragging_hand.index == 0) {
+                        Card curr_card = curr_hand->cards[curr_hand->index - 1];
+                        if (curr_card.flipped) {
+                            curr_hand->cards[curr_hand->index - 1].flipped = false;
+                            wasAction = true;
+                        } else {
+                            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->dragging_column = column;
+                                wasAction = true;
+                            }
+                        }
+                    }
+                        //place
+                    else {
+                        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)
+                                ) {
+                            add_hand_region(curr_hand, &(game_state->dragging_hand));
+                            remove_drag(game_state);
+                            wasAction = true;
+                        }
+                    }
+                }
+            }
+
+
+            if (!wasAction) {
+                notification_message(notification, &sequence_fail);
+            }
+        }
+    }
+    if (game_state->state == GameStateAnimate) {
+        if (game_state->animation.started && !game_state->longPress && game_state->input==InputKeyOk) {
+            init(game_state);
+            game_state->state = GameStateStart;
+        }
+
+        game_state->animation.started = true;
+        if (game_state->animation.x < -20 || game_state->animation.x > 128) {
+            game_state->animation.deck++;
+            if (game_state->animation.deck > 3)
+                game_state->animation.deck = 0;
+            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) {
+                init(game_state);
+                game_state->state = GameStateStart;
+                return;
+            }
+
+            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.vx = -(rand() % 3 + 1) * (rand() % 2 == 1 ? 1 : -1);
+            game_state->animation.vy = (rand() % 3 + 1);
+            game_state->animation.x = columns[game_state->animation.deck + 3][0];
+            game_state->animation.y = columns[game_state->animation.deck + 3][1];
+        }
+        game_state->animation.x += game_state->animation.vx;
+        game_state->animation.y -= game_state->animation.vy;
+        game_state->animation.vy -= 1;
+        if (game_state->animation.vy < -10)game_state->animation.vy = -10;
+
+        if (game_state->animation.y > 41) {
+            game_state->animation.y = 41;
+            game_state->animation.vy = -(game_state->animation.vy * 0.7f);
+        }
+    }
+}
+
+void init(GameState *game_state) {
+    dolphin_deed(DolphinDeedPluginGameStart);
+    game_state->selectColumn = 0;
+    game_state->selected_card = 0;
+    game_state->selectRow = 0;
+    generate_deck(&(game_state->deck), 1);
+    shuffle_deck(&(game_state->deck));
+    game_state->dragging_deck = false;
+    game_state->animation.started = false;
+    game_state->animation.deck = -1;
+    game_state->animation.x = -21;
+    game_state->state = GameStatePlay;
+    game_state->dragging_column = 8;
+
+    for (uint8_t i = 0; i < 7; i++) {
+        free_hand(&(game_state->bottom_columns[i]));
+        init_hand(&(game_state->bottom_columns[i]), 21);
+        game_state->bottom_columns[i].index = 0;
+        for (uint8_t j = 0; j <= i; j++) {
+            Card cur = remove_from_deck(0, &(game_state->deck));
+            cur.flipped = i != j;
+            add_to_hand(&(game_state->bottom_columns[i]), cur);
+        }
+    }
+
+    for (uint8_t i = 0; i < 4; i++) {
+        game_state->animation.indexes[i] = 0;
+        game_state->top_cards[i] = (Card) {0, 0, true, false};
+    }
+    game_state->deck.index = -1;
+}
+
+void init_start(GameState *game_state) {
+    game_state->input = InputKeyMAX;
+    for (uint8_t i = 0; i < 7; i++)
+        init_hand(&(game_state->bottom_columns[i]), 21);
+
+    init_hand(&(game_state->dragging_hand), 13);
+    game_state->animation.buffer = make_buffer();
+
+}
+
+
+static void input_callback(InputEvent *input_event, FuriMessageQueue *event_queue) {
+    furi_assert(event_queue);
+    AppEvent event = {.type = EventTypeKey, .input = *input_event};
+    furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void update_timer_callback(FuriMessageQueue *event_queue) {
+    furi_assert(event_queue);
+    AppEvent event = {.type = EventTypeTick};
+    furi_message_queue_put(event_queue, &event, 0);
+}
+
+int32_t solitaire_app(void *p) {
+    UNUSED(p);
+    int32_t return_code = 0;
+    FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
+    GameState *game_state = malloc(sizeof(GameState));
+    init_start(game_state);
+    set_card_graphics(&I_card_graphics);
+
+    game_state->state = GameStateStart;
+
+    game_state->processing = true;
+    game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if (!game_state->mutex) {
+        FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
+        return_code = 255;
+        goto free_and_exit;
+    }
+    NotificationApp *notification = furi_record_open(RECORD_NOTIFICATION);
+
+    notification_message_block(notification, &sequence_display_backlight_enforce_on);
+
+    ViewPort *view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, render_callback, game_state);
+    view_port_input_callback_set(view_port, input_callback, event_queue);
+
+    FuriTimer *timer =
+            furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
+    furi_timer_start(timer, furi_kernel_get_tick_frequency() / 30);
+
+    Gui *gui = furi_record_open("gui");
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    AppEvent event;
+    for (bool processing = true; processing;) {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 150);
+        furi_mutex_acquire(game_state->mutex, FuriWaitForever);
+        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;
+                    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) {
+                    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;
+                                init(game_state);
+                            }
+                            else {
+                                game_state->input = event.input.key;
+                            }
+                            break;
+                        case InputKeyBack:
+                            init(game_state);
+                            processing = false;
+                            return_code = 1;
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            } else if (event.type == EventTypeTick) {
+                tick(game_state, notification);
+                processing = game_state->processing;
+                game_state->input = InputKeyMAX;
+            }
+        } else {
+            FURI_LOG_W(APP_NAME, "osMessageQueue: event timeout");
+            // event timeout
+        }
+
+        view_port_update(view_port);
+        furi_mutex_release(game_state->mutex);
+    }
+
+
+    notification_message_block(notification, &sequence_display_backlight_enforce_auto);
+    furi_timer_free(timer);
+    view_port_enabled_set(view_port, false);
+    gui_remove_view_port(gui, view_port);
+    furi_record_close(RECORD_GUI);
+    furi_record_close(RECORD_NOTIFICATION);
+    view_port_free(view_port);
+    furi_mutex_free(game_state->mutex);
+
+    free_and_exit:
+    free(game_state->animation.buffer);
+    ui_cleanup();
+    for (uint8_t i = 0; i < 7; i++)
+        free_hand(&(game_state->bottom_columns[i]));
+
+    free(game_state->deck.cards);
+    free(game_state);
+    furi_message_queue_free(event_queue);
+
+    return return_code;
+}

BIN
solitaire/solitaire_10px.png