فهرست منبع

Squashed 'blackjack/' changes from 76a3685f1..e45359294

e45359294 fixing some big bugs
63a64949d move base pack here
REVERT: 76a3685f1 dolphin deed change applied
REVERT: 9422c8145 re-added dolphin deeds
REVERT: 76e09823e Merge remote-tracking branch 'origin/main' into main
REVERT: 867a968f6 updated to the new mutex api
REVERT: 046b4abbe Update README.md
REVERT: d42a1e04d Update README.md
REVERT: 935663d7f updated images
REVERT: 4e979aa20 fixed typo
REVERT: 009a07d5f updated Readme
REVERT: 53183dfee updated Readme
REVERT: db62897ce Extracted game from https://github.com/teeebor/flipper_games
REVERT: b570c2857 Initial commit

git-subtree-dir: blackjack
git-subtree-split: e453592943a4d3a16131b8c3a5dbb338bcb511f2
Willy-JL 2 سال پیش
والد
کامیت
d2b64cd78c
27فایلهای تغییر یافته به همراه1871 افزوده شده و 456 حذف شده
  1. 0 3
      .gitmodules
  2. 0 21
      LICENSE
  3. 0 25
      README.md
  4. 4 2
      application.fam
  5. BIN
      assets/poker.png
  6. BIN
      assets/poker2.png
  7. BIN
      assets/poker_design.png
  8. BIN
      assets/poker_home.png
  9. 355 292
      blackjack.c
  10. 0 1
      common
  11. 353 0
      common/card.c
  12. 192 0
      common/card.h
  13. 53 0
      common/dml.c
  14. 116 0
      common/dml.h
  15. 103 0
      common/menu.c
  16. 77 0
      common/menu.h
  17. 69 0
      common/queue.c
  18. 70 0
      common/queue.h
  19. 257 0
      common/ui.c
  20. 105 0
      common/ui.h
  21. 3 4
      defines.h
  22. BIN
      screenshots/blackjack.gif
  23. BIN
      screenshots/play_scene.png
  24. BIN
      screenshots/welcome.png
  25. 80 74
      ui.c
  26. 8 8
      ui.h
  27. 26 26
      util.c

+ 0 - 3
.gitmodules

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

+ 0 - 21
LICENSE

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

+ 0 - 25
README.md

@@ -1,25 +0,0 @@
-[![GitHub release](https://img.shields.io/github/release/teeebor/flipper_blackjack?include_prereleases=&sort=semver&color=blue)](https://github.com/teeebor/flipper_blackjack/releases/)
-[![License](https://img.shields.io/badge/License-MIT-blue)](/LICENSE)
-[![issues - flipper-zero_authenticator](https://img.shields.io/github/issues/teeebor/flipper_blackjack)](https://github.com/teeebor/flipper_blackjack/issues)
-![maintained - yes](https://img.shields.io/badge/maintained-yes-blue)
-![contributions - welcome](https://img.shields.io/badge/contributions-welcome-blue)
-# Blackjack for Flipper Zero
-
-
-![Play screen](screenshots/blackjack.gif)
-
-## 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

+ 4 - 2
application.fam

@@ -3,11 +3,13 @@ App(
     name="Blackjack",
     apptype=FlipperAppType.EXTERNAL,
     entry_point="blackjack_app",
-    cdefines=["APP_BLACKJACK"],
     requires=["gui","storage","canvas"],
     stack_size=2 * 1024,
     order=30,
     fap_icon="blackjack_10px.png",
     fap_category="Games",
-    fap_icon_assets="assets"
+    fap_icon_assets="assets",
+    fap_author="@teeebor",
+    fap_version="1.1",
+    fap_description="Blackjack Game",
 )

BIN
assets/poker.png


BIN
assets/poker2.png


BIN
assets/poker_design.png


BIN
assets/poker_home.png


+ 355 - 292
blackjack.c

@@ -18,47 +18,43 @@
 
 #define DEALER_MAX 17
 
-void start_round(GameState *game_state);
+void start_round(GameState* game_state);
 
-void init(GameState *game_state);
-
-static void draw_ui(Canvas *const canvas, const GameState *game_state) {
+void init(GameState* game_state);
 
+static void draw_ui(Canvas* const canvas, const GameState* game_state) {
     draw_money(canvas, game_state->player_score);
 
     draw_score(canvas, true, hand_count(game_state->player_cards, game_state->player_card_count));
 
-    if (!game_state->queue_state.running && game_state->state == GameStatePlay) {
-        render_menu(game_state->menu,canvas, 2, 47);
+    if(!game_state->queue_state.running && game_state->state == GameStatePlay) {
+        render_menu(game_state->menu, canvas, 2, 47);
     }
 }
 
-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;
-    }
+static void render_callback(Canvas* const canvas, void* ctx) {
+    furi_assert(ctx);
+    const GameState* game_state = ctx;
+    furi_mutex_acquire(game_state->mutex, FuriWaitForever);
 
     canvas_set_color(canvas, ColorBlack);
     canvas_draw_frame(canvas, 0, 0, 128, 64);
 
-    if (game_state->state == GameStateStart) {
+    if(game_state->state == GameStateStart) {
         canvas_draw_icon(canvas, 0, 0, &I_blackjack);
     }
-    if (game_state->state == GameStateGameOver) {
+    if(game_state->state == GameStateGameOver) {
         canvas_draw_icon(canvas, 0, 0, &I_endscreen);
     }
 
-    if (game_state->state == GameStatePlay || game_state->state == GameStateDealer) {
-        if (game_state->state == GameStatePlay)
+    if(game_state->state == GameStatePlay || game_state->state == GameStateDealer) {
+        if(game_state->state == GameStatePlay)
             draw_player_scene(canvas, game_state);
         else
             draw_dealer_scene(canvas, game_state);
         render_queue(&(game_state->queue_state), game_state, canvas);
         draw_ui(canvas, game_state);
-    } else if (game_state->state == GameStateSettings) {
+    } else if(game_state->state == GameStateSettings) {
         settings_page(canvas, game_state);
     }
 
@@ -66,25 +62,24 @@ static void render_callback(Canvas *const canvas, void *ctx) {
 }
 
 //region card draw
-Card draw_card(GameState *game_state) {
+Card draw_card(GameState* game_state) {
     Card c = game_state->deck.cards[game_state->deck.index];
     game_state->deck.index++;
     return c;
 }
 
-
-void drawPlayerCard(void *ctx) {
-    GameState *game_state = ctx;
+void drawPlayerCard(void* ctx) {
+    GameState* game_state = ctx;
     Card c = draw_card(game_state);
     game_state->player_cards[game_state->player_card_count] = c;
     game_state->player_card_count++;
-    if(game_state->player_score < game_state->settings.round_price || game_state->doubled){
+    if(game_state->player_score < game_state->settings.round_price || game_state->doubled) {
         set_menu_state(game_state->menu, 0, false);
     }
 }
 
-void drawDealerCard(void *ctx) {
-    GameState *game_state = ctx;
+void drawDealerCard(void* ctx) {
+    GameState* game_state = ctx;
     Card c = draw_card(game_state);
     game_state->dealer_cards[game_state->dealer_card_count] = c;
     game_state->dealer_card_count++;
@@ -92,318 +87,362 @@ void drawDealerCard(void *ctx) {
 //endregion
 
 //region queue callbacks
-void to_lose_state(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
-    if (game_state->settings.message_duration == 0)
-        return;
+void to_lose_state(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
+    if(game_state->settings.message_duration == 0) return;
     popup_frame(canvas);
     elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost");
 }
 
-void to_bust_state(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
-    if (game_state->settings.message_duration == 0)
-        return;
+void to_bust_state(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
+    if(game_state->settings.message_duration == 0) return;
     popup_frame(canvas);
     elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!");
 }
 
-void to_draw_state(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
-    if (game_state->settings.message_duration == 0)
-        return;
+void to_draw_state(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
+    if(game_state->settings.message_duration == 0) return;
     popup_frame(canvas);
     elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw");
 }
 
-void to_dealer_turn(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
-    if (game_state->settings.message_duration == 0)
-        return;
+void to_dealer_turn(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
+    if(game_state->settings.message_duration == 0) return;
     popup_frame(canvas);
     elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn");
 }
 
-void to_win_state(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
-    if (game_state->settings.message_duration == 0)
-        return;
+void to_win_state(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
+    if(game_state->settings.message_duration == 0) return;
     popup_frame(canvas);
     elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win");
 }
 
-void to_start(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
-    if (game_state->settings.message_duration == 0)
-        return;
+void to_start(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
+    if(game_state->settings.message_duration == 0) return;
     popup_frame(canvas);
     elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started");
 }
 
-void before_start(void *ctx) {
-    GameState *game_state = ctx;
+void before_start(void* ctx) {
+    GameState* game_state = ctx;
     game_state->dealer_card_count = 0;
     game_state->player_card_count = 0;
 }
 
-
-void start(void *ctx) {
-    GameState *game_state = ctx;
+void start(void* ctx) {
+    GameState* game_state = ctx;
     start_round(game_state);
 }
 
-void draw(void *ctx) {
-    GameState *game_state = ctx;
+void draw(void* ctx) {
+    GameState* game_state = ctx;
     game_state->player_score += game_state->bet;
     game_state->bet = 0;
-    enqueue(&(game_state->queue_state), game_state, start, before_start, to_start,
-            game_state->settings.message_duration);
+    enqueue(
+        &(game_state->queue_state),
+        game_state,
+        start,
+        before_start,
+        to_start,
+        game_state->settings.message_duration);
 }
 
-void game_over(void *ctx) {
-    GameState *game_state = ctx;
+void game_over(void* ctx) {
+    GameState* game_state = ctx;
     game_state->state = GameStateGameOver;
 }
 
-void lose(void *ctx) {
-    GameState *game_state = ctx;
+void lose(void* ctx) {
+    GameState* game_state = ctx;
     game_state->state = GameStatePlay;
     game_state->bet = 0;
-    if (game_state->player_score >= game_state->settings.round_price) {
-        enqueue(&(game_state->queue_state), game_state, start, before_start, to_start,
-                game_state->settings.message_duration);
+    if(game_state->player_score >= game_state->settings.round_price) {
+        enqueue(
+            &(game_state->queue_state),
+            game_state,
+            start,
+            before_start,
+            to_start,
+            game_state->settings.message_duration);
     } else {
-        enqueue(&(game_state->queue_state), game_state, game_over, NULL, NULL,
-                0);
+        enqueue(&(game_state->queue_state), game_state, game_over, NULL, NULL, 0);
     }
 }
 
-void win(void *ctx) {
-    dolphin_deed(DolphinDeedPluginGameWin);
-    GameState *game_state = ctx;
+void win(void* ctx) {
+    GameState* game_state = ctx;
     game_state->state = GameStatePlay;
     game_state->player_score += game_state->bet * 2;
     game_state->bet = 0;
-    enqueue(&(game_state->queue_state), game_state, start, before_start, to_start,
-            game_state->settings.message_duration);
+    enqueue(
+        &(game_state->queue_state),
+        game_state,
+        start,
+        before_start,
+        to_start,
+        game_state->settings.message_duration);
 }
 
-
-void dealerTurn(void *ctx) {
-    GameState *game_state = ctx;
+void dealerTurn(void* ctx) {
+    GameState* game_state = ctx;
     game_state->state = GameStateDealer;
 }
 
-float animationTime(const GameState *game_state){
-    return (float) (furi_get_tick() - game_state->queue_state.start) /
-           (float) (game_state->settings.animation_duration);
+float animationTime(const GameState* game_state) {
+    return (float)(furi_get_tick() - game_state->queue_state.start) /
+           (float)(game_state->settings.animation_duration);
 }
 
-void dealer_card_animation(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
+void dealer_card_animation(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
     float t = animationTime(game_state);
 
     Card animatingCard = game_state->deck.cards[game_state->deck.index];
-    if (game_state->dealer_card_count > 1) {
+    if(game_state->dealer_card_count > 1) {
         Vector end = card_pos_at_index(game_state->dealer_card_count);
-        draw_card_animation(animatingCard,
-                            (Vector) {0, 64},
-                            (Vector) {0, 32},
-                            end,
-                            t,
-                            true,
-                            canvas);
+        draw_card_animation(animatingCard, (Vector){0, 64}, (Vector){0, 32}, end, t, true, canvas);
     } else {
-        draw_card_animation(animatingCard,
-                            (Vector) {32, -CARD_HEIGHT},
-                            (Vector) {64, 32},
-                            (Vector) {2, 2},
-                            t,
-                            false,
-                            canvas);
+        draw_card_animation(
+            animatingCard,
+            (Vector){32, -CARD_HEIGHT},
+            (Vector){64, 32},
+            (Vector){2, 2},
+            t,
+            false,
+            canvas);
     }
 }
 
-void dealer_back_card_animation(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
+void dealer_back_card_animation(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
     float t = animationTime(game_state);
 
-    Vector currentPos = quadratic_2d((Vector) {32, -CARD_HEIGHT}, (Vector) {64, 32}, (Vector) {13, 5}, t);
+    Vector currentPos =
+        quadratic_2d((Vector){32, -CARD_HEIGHT}, (Vector){64, 32}, (Vector){13, 5}, t);
     draw_card_back_at(currentPos.x, currentPos.y, canvas);
 }
 
-void player_card_animation(const void *ctx, Canvas *const canvas) {
-    const GameState *game_state = ctx;
+void player_card_animation(const void* ctx, Canvas* const canvas) {
+    const GameState* game_state = ctx;
     float t = animationTime(game_state);
 
     Card animatingCard = game_state->deck.cards[game_state->deck.index];
     Vector end = card_pos_at_index(game_state->player_card_count);
 
-    draw_card_animation(animatingCard,
-                        (Vector) {32, -CARD_HEIGHT},
-                        (Vector) {0, 32},
-                        end,
-                        t,
-                        true,
-                        canvas);
+    draw_card_animation(
+        animatingCard, (Vector){32, -CARD_HEIGHT}, (Vector){0, 32}, end, t, true, canvas);
 }
 //endregion
 
-void player_tick(GameState *game_state) {
+void player_tick(GameState* game_state) {
     uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count);
-    if ((game_state->doubled && score <= 21) || score == 21) {
-        enqueue(&(game_state->queue_state), game_state, dealerTurn, NULL, to_dealer_turn,
-                game_state->settings.message_duration);
-    } else if (score > 21) {
-        enqueue(&(game_state->queue_state), game_state, lose, NULL, to_bust_state,
-                game_state->settings.message_duration);
+    if((game_state->doubled && score <= 21) || score == 21) {
+        enqueue(
+            &(game_state->queue_state),
+            game_state,
+            dealerTurn,
+            NULL,
+            to_dealer_turn,
+            game_state->settings.message_duration);
+    } else if(score > 21) {
+        enqueue(
+            &(game_state->queue_state),
+            game_state,
+            lose,
+            NULL,
+            to_bust_state,
+            game_state->settings.message_duration);
     } else {
-        if(game_state->selectDirection == DirectionUp || game_state->selectDirection == DirectionDown){
+        if(game_state->selectDirection == DirectionUp ||
+           game_state->selectDirection == DirectionDown) {
             move_menu(game_state->menu, game_state->selectDirection == DirectionUp ? -1 : 1);
         }
 
-        if (game_state->selectDirection == Select){
+        if(game_state->selectDirection == Select) {
             activate_menu(game_state->menu, game_state);
-
         }
     }
 }
 
-void dealer_tick(GameState *game_state) {
+void dealer_tick(GameState* game_state) {
     uint8_t dealer_score = hand_count(game_state->dealer_cards, game_state->dealer_card_count);
     uint8_t player_score = hand_count(game_state->player_cards, game_state->player_card_count);
 
-    if (dealer_score >= DEALER_MAX) {
-        if (dealer_score > 21 || dealer_score < player_score) {
-            enqueue(&(game_state->queue_state), game_state, win, NULL, to_win_state,
-                    game_state->settings.message_duration);
-        } else if (dealer_score > player_score) {
-            enqueue(&(game_state->queue_state), game_state, lose, NULL, to_lose_state,
-                    game_state->settings.message_duration);
-        } else if (dealer_score == player_score) {
-            enqueue(&(game_state->queue_state), game_state, draw, NULL, to_draw_state,
-                    game_state->settings.message_duration);
+    if(dealer_score >= DEALER_MAX) {
+        if(dealer_score > 21 || dealer_score < player_score) {
+            dolphin_deed(DolphinDeedPluginGameWin);
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                win,
+                NULL,
+                to_win_state,
+                game_state->settings.message_duration);
+        } else if(dealer_score > player_score) {
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                lose,
+                NULL,
+                to_lose_state,
+                game_state->settings.message_duration);
+        } else if(dealer_score == player_score) {
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                draw,
+                NULL,
+                to_draw_state,
+                game_state->settings.message_duration);
         }
     } else {
-        enqueue(&(game_state->queue_state), game_state, drawDealerCard, NULL, dealer_card_animation,
-                game_state->settings.animation_duration);
+        enqueue(
+            &(game_state->queue_state),
+            game_state,
+            drawDealerCard,
+            NULL,
+            dealer_card_animation,
+            game_state->settings.animation_duration);
     }
 }
 
-void settings_tick(GameState *game_state) {
-    if (game_state->selectDirection == DirectionDown && game_state->selectedMenu < 4) {
+void settings_tick(GameState* game_state) {
+    if(game_state->selectDirection == DirectionDown && game_state->selectedMenu < 4) {
         game_state->selectedMenu++;
     }
-    if (game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) {
+    if(game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) {
         game_state->selectedMenu--;
     }
 
-    if (game_state->selectDirection == DirectionLeft || game_state->selectDirection == DirectionRight) {
+    if(game_state->selectDirection == DirectionLeft ||
+       game_state->selectDirection == DirectionRight) {
         int nextScore = 0;
-        switch (game_state->selectedMenu) {
-            case 0:
-                nextScore = game_state->settings.starting_money;
-                if (game_state->selectDirection == DirectionLeft)
-                    nextScore -= 10;
-                else
-                    nextScore += 10;
-                if (nextScore >= (int) game_state->settings.round_price && nextScore < 400)
-                    game_state->settings.starting_money = nextScore;
-                break;
-            case 1:
-                nextScore = game_state->settings.round_price;
-                if (game_state->selectDirection == DirectionLeft)
-                    nextScore -= 10;
-                else
-                    nextScore += 10;
-                if (nextScore >= 5 && nextScore <= (int) game_state->settings.starting_money)
-                    game_state->settings.round_price = nextScore;
-                break;
-            case 2:
-                nextScore = game_state->settings.animation_duration;
-                if (game_state->selectDirection == DirectionLeft)
-                    nextScore -= 100;
-                else
-                    nextScore += 100;
-                if (nextScore >= 0 && nextScore < 2000)
-                    game_state->settings.animation_duration = nextScore;
-                break;
-            case 3:
-                nextScore = game_state->settings.message_duration;
-                if (game_state->selectDirection == DirectionLeft)
-                    nextScore -= 100;
-                else
-                    nextScore += 100;
-                if (nextScore >= 0 && nextScore < 2000)
-                    game_state->settings.message_duration = nextScore;
-                break;
-            case 4:
-                game_state->settings.sound_effects = !game_state->settings.sound_effects;
-            default:
-                break;
+        switch(game_state->selectedMenu) {
+        case 0:
+            nextScore = game_state->settings.starting_money;
+            if(game_state->selectDirection == DirectionLeft)
+                nextScore -= 10;
+            else
+                nextScore += 10;
+            if(nextScore >= (int)game_state->settings.round_price && nextScore < 400)
+                game_state->settings.starting_money = nextScore;
+            break;
+        case 1:
+            nextScore = game_state->settings.round_price;
+            if(game_state->selectDirection == DirectionLeft)
+                nextScore -= 10;
+            else
+                nextScore += 10;
+            if(nextScore >= 5 && nextScore <= (int)game_state->settings.starting_money)
+                game_state->settings.round_price = nextScore;
+            break;
+        case 2:
+            nextScore = game_state->settings.animation_duration;
+            if(game_state->selectDirection == DirectionLeft)
+                nextScore -= 100;
+            else
+                nextScore += 100;
+            if(nextScore >= 0 && nextScore < 2000)
+                game_state->settings.animation_duration = nextScore;
+            break;
+        case 3:
+            nextScore = game_state->settings.message_duration;
+            if(game_state->selectDirection == DirectionLeft)
+                nextScore -= 100;
+            else
+                nextScore += 100;
+            if(nextScore >= 0 && nextScore < 2000)
+                game_state->settings.message_duration = nextScore;
+            break;
+        case 4:
+            game_state->settings.sound_effects = !game_state->settings.sound_effects;
+        default:
+            break;
         }
     }
-
 }
 
-void tick(GameState *game_state) {
+void tick(GameState* game_state) {
     game_state->last_tick = furi_get_tick();
     bool queue_ran = run_queue(&(game_state->queue_state), game_state);
 
-    switch (game_state->state) {
-        case GameStateGameOver:
-        case GameStateStart:
-            if (game_state->selectDirection == Select)
-                init(game_state);
-            else if (game_state->selectDirection == DirectionRight) {
-                game_state->selectedMenu = 0;
-                game_state->state = GameStateSettings;
-            }
-            break;
-        case GameStatePlay:
-            if (!game_state->started) {
-                game_state->selectedMenu = 0;
-                game_state->started = true;
-                enqueue(&(game_state->queue_state), game_state, drawDealerCard, NULL, dealer_back_card_animation,
-                        game_state->settings.animation_duration);
-                enqueue(&(game_state->queue_state), game_state, drawPlayerCard, NULL, player_card_animation,
-                        game_state->settings.animation_duration);
-                enqueue(&(game_state->queue_state), game_state, drawDealerCard, NULL, dealer_card_animation,
-                        game_state->settings.animation_duration);
-                enqueue(&(game_state->queue_state), game_state, drawPlayerCard, NULL, player_card_animation,
-                        game_state->settings.animation_duration);
-            }
-            if (!queue_ran)
-                player_tick(game_state);
-            break;
-        case GameStateDealer:
-            if (!queue_ran)
-                dealer_tick(game_state);
-            break;
-        case GameStateSettings:
-            settings_tick(game_state);
-            break;
-        default:
-            break;
+    switch(game_state->state) {
+    case GameStateGameOver:
+    case GameStateStart:
+        if(game_state->selectDirection == Select)
+            init(game_state);
+        else if(game_state->selectDirection == DirectionRight) {
+            game_state->selectedMenu = 0;
+            game_state->state = GameStateSettings;
+        }
+        break;
+    case GameStatePlay:
+        if(!game_state->started) {
+            game_state->selectedMenu = 0;
+            game_state->started = true;
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                drawDealerCard,
+                NULL,
+                dealer_back_card_animation,
+                game_state->settings.animation_duration);
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                drawPlayerCard,
+                NULL,
+                player_card_animation,
+                game_state->settings.animation_duration);
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                drawDealerCard,
+                NULL,
+                dealer_card_animation,
+                game_state->settings.animation_duration);
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                drawPlayerCard,
+                NULL,
+                player_card_animation,
+                game_state->settings.animation_duration);
+        }
+        if(!queue_ran) player_tick(game_state);
+        break;
+    case GameStateDealer:
+        if(!queue_ran) dealer_tick(game_state);
+        break;
+    case GameStateSettings:
+        settings_tick(game_state);
+        break;
+    default:
+        break;
     }
 
     game_state->selectDirection = None;
-
 }
 
-void start_round(GameState *game_state) {
-    game_state->menu->current_menu=1;
+void start_round(GameState* game_state) {
+    game_state->menu->current_menu = 1;
     game_state->player_card_count = 0;
     game_state->dealer_card_count = 0;
     set_menu_state(game_state->menu, 0, true);
-    game_state->menu->enabled=true;
+    game_state->menu->enabled = true;
     game_state->started = false;
     game_state->doubled = false;
     game_state->queue_state.running = true;
     shuffle_deck(&(game_state->deck));
     game_state->doubled = false;
     game_state->bet = game_state->settings.round_price;
-    if (game_state->player_score < game_state->settings.round_price) {
+    if(game_state->player_score < game_state->settings.round_price) {
         game_state->state = GameStateGameOver;
     } else {
         game_state->player_score -= game_state->settings.round_price;
@@ -411,10 +450,10 @@ void start_round(GameState *game_state) {
     game_state->state = GameStatePlay;
 }
 
-void init(GameState *game_state) {
+void init(GameState* game_state) {
     set_menu_state(game_state->menu, 0, true);
-    game_state->menu->enabled=true;
-    game_state->menu->current_menu=1;
+    game_state->menu->enabled = true;
+    game_state->menu->current_menu = 1;
     game_state->settings = load_settings();
     game_state->last_tick = 0;
     game_state->processing = true;
@@ -424,60 +463,86 @@ void init(GameState *game_state) {
     start_round(game_state);
 }
 
-static void input_callback(InputEvent *input_event, FuriMessageQueue *event_queue) {
+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) {
+static void update_timer_callback(FuriMessageQueue* event_queue) {
     furi_assert(event_queue);
     AppEvent event = {.type = EventTypeTick};
     furi_message_queue_put(event_queue, &event, 0);
 }
 
-void doubleAction(void *state){
-    GameState *game_state = state;
-    if (!game_state->doubled &&  game_state->player_score >= game_state->settings.round_price) {
+void doubleAction(void* state) {
+    GameState* game_state = state;
+    if(!game_state->doubled && game_state->player_score >= game_state->settings.round_price) {
         game_state->player_score -= game_state->settings.round_price;
         game_state->bet += game_state->settings.round_price;
         game_state->doubled = true;
-        enqueue(&(game_state->queue_state), game_state, drawPlayerCard, NULL, player_card_animation,
-                game_state->settings.animation_duration);
-        game_state->player_cards[game_state->player_card_count] = game_state->deck.cards[game_state->deck.index];
+        enqueue(
+            &(game_state->queue_state),
+            game_state,
+            drawPlayerCard,
+            NULL,
+            player_card_animation,
+            game_state->settings.animation_duration);
+        game_state->player_cards[game_state->player_card_count] =
+            game_state->deck.cards[game_state->deck.index];
         uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count + 1);
-        if (score > 21) {
-            enqueue(&(game_state->queue_state), game_state, lose, NULL, to_bust_state,
-                    game_state->settings.message_duration);
+        if(score > 21) {
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                lose,
+                NULL,
+                to_bust_state,
+                game_state->settings.message_duration);
         } else {
-            enqueue(&(game_state->queue_state), game_state, dealerTurn, NULL, to_dealer_turn,
-                    game_state->settings.message_duration);
+            enqueue(
+                &(game_state->queue_state),
+                game_state,
+                dealerTurn,
+                NULL,
+                to_dealer_turn,
+                game_state->settings.message_duration);
         }
         set_menu_state(game_state->menu, 0, false);
     }
 }
 
-void hitAction(void *state){
-    GameState *game_state = state;
-    enqueue(&(game_state->queue_state), game_state, drawPlayerCard, NULL, player_card_animation,
-            game_state->settings.animation_duration);
-}
-void stayAction(void *state){
-    GameState *game_state = state;
-    enqueue(&(game_state->queue_state), game_state, dealerTurn, NULL, to_dealer_turn,
-            game_state->settings.message_duration);
-}
-
-int32_t blackjack_app(void *p) {
+void hitAction(void* state) {
+    GameState* game_state = state;
+    enqueue(
+        &(game_state->queue_state),
+        game_state,
+        drawPlayerCard,
+        NULL,
+        player_card_animation,
+        game_state->settings.animation_duration);
+}
+void stayAction(void* state) {
+    GameState* game_state = state;
+    enqueue(
+        &(game_state->queue_state),
+        game_state,
+        dealerTurn,
+        NULL,
+        to_dealer_turn,
+        game_state->settings.message_duration);
+}
+
+int32_t blackjack_app(void* p) {
     UNUSED(p);
 
     int32_t return_code = 0;
 
-    FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
-    dolphin_deed(DolphinDeedPluginGameStart);
-    GameState *game_state = malloc(sizeof(GameState));
-    game_state->menu= malloc(sizeof(Menu));
-    game_state->menu->menu_width=40;
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
+
+    GameState* game_state = malloc(sizeof(GameState));
+    game_state->menu = malloc(sizeof(Menu));
+    game_state->menu->menu_width = 40;
     init(game_state);
     add_menu(game_state->menu, "Double", doubleAction);
     add_menu(game_state->menu, "Hit", hitAction);
@@ -487,72 +552,70 @@ int32_t blackjack_app(void *p) {
     game_state->state = GameStateStart;
 
     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");
         return_code = 255;
         goto free_and_exit;
     }
 
-    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_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() / 25);
 
-    Gui *gui = furi_record_open("gui");
+    Gui* gui = furi_record_open(RECORD_GUI);
     gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 
     AppEvent event;
 
-    for (bool processing = true; processing;) {
+    // Call dolphin deed on game start
+    dolphin_deed(DolphinDeedPluginGameStart);
+
+    for(bool processing = true; processing;) {
         FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
         furi_mutex_acquire(game_state->mutex, FuriWaitForever);
-        if (event_status == FuriStatusOk) {
-            if (event.type == EventTypeKey) {
-
-                if (event.input.type == InputTypePress) {
-                    switch (event.input.key) {
-                        case InputKeyUp:
-                            game_state->selectDirection = DirectionUp;
-                            break;
-                        case InputKeyDown:
-                            game_state->selectDirection = DirectionDown;
-                            break;
-                        case InputKeyRight:
-                            game_state->selectDirection = DirectionRight;
-                            break;
-                        case InputKeyLeft:
-                            game_state->selectDirection = DirectionLeft;
-                            break;
-                        case InputKeyBack:
-                            if (game_state->state == GameStateSettings) {
-                                game_state->state = GameStateStart;
-                                save_settings(game_state->settings);
-                            } else
-                                processing = false;
-                            break;
-                        case InputKeyOk:
-                            game_state->selectDirection = Select;
-                            break;
-                        default:
-                            break;
+        if(event_status == FuriStatusOk) {
+            if(event.type == EventTypeKey) {
+                if(event.input.type == InputTypePress) {
+                    switch(event.input.key) {
+                    case InputKeyUp:
+                        game_state->selectDirection = DirectionUp;
+                        break;
+                    case InputKeyDown:
+                        game_state->selectDirection = DirectionDown;
+                        break;
+                    case InputKeyRight:
+                        game_state->selectDirection = DirectionRight;
+                        break;
+                    case InputKeyLeft:
+                        game_state->selectDirection = DirectionLeft;
+                        break;
+                    case InputKeyBack:
+                        if(game_state->state == GameStateSettings) {
+                            game_state->state = GameStateStart;
+                            save_settings(game_state->settings);
+                        } else
+                            processing = false;
+                        break;
+                    case InputKeyOk:
+                        game_state->selectDirection = Select;
+                        break;
+                    default:
+                        break;
                     }
                 }
-            } else if (event.type == EventTypeTick) {
+            } else if(event.type == EventTypeTick) {
                 tick(game_state);
                 processing = game_state->processing;
             }
-        } else {
-            FURI_LOG_D(APP_NAME, "osMessageQueue: event timeout");
-            // event timeout
         }
-        view_port_update(view_port);
+
         furi_mutex_release(game_state->mutex);
+        view_port_update(view_port);
     }
 
-
     furi_timer_free(timer);
     view_port_enabled_set(view_port, false);
     gui_remove_view_port(gui, view_port);
@@ -560,7 +623,7 @@ int32_t blackjack_app(void *p) {
     view_port_free(view_port);
     furi_mutex_free(game_state->mutex);
 
-    free_and_exit:
+free_and_exit:
     free(game_state->deck.cards);
     free_menu(game_state->menu);
     queue_clear(&(game_state->queue_state));

+ 0 - 1
common

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

+ 353 - 0
common/card.c

@@ -0,0 +1,353 @@
+#include "card.h"
+#include "dml.h"
+#include "ui.h"
+
+#define CARD_DRAW_X_START 108
+#define CARD_DRAW_Y_START 38
+#define CARD_DRAW_X_SPACE 10
+#define CARD_DRAW_Y_SPACE 8
+#define CARD_DRAW_X_OFFSET 4
+#define CARD_DRAW_FIRST_ROW_LENGTH 7
+
+uint8_t pips[4][3] = {
+    {21, 10, 7}, //spades
+    {7, 10, 7}, //hearts
+    {0, 10, 7}, //diamonds
+    {14, 10, 7}, //clubs
+};
+uint8_t letters[13][3] = {
+    {0, 0, 5},
+    {5, 0, 5},
+    {10, 0, 5},
+    {15, 0, 5},
+    {20, 0, 5},
+    {25, 0, 5},
+    {30, 0, 5},
+    {0, 5, 5},
+    {5, 5, 5},
+    {10, 5, 5},
+    {15, 5, 5},
+    {20, 5, 5},
+    {25, 5, 5},
+};
+
+//region Player card positions
+uint8_t playerCardPositions[22][4] = {
+    //first row
+    {108, 38},
+    {98, 38},
+    {88, 38},
+    {78, 38},
+    {68, 38},
+    {58, 38},
+    {48, 38},
+    {38, 38},
+    //second row
+    {104, 26},
+    {94, 26},
+    {84, 26},
+    {74, 26},
+    {64, 26},
+    {54, 26},
+    {44, 26},
+    //third row
+    {99, 14},
+    {89, 14},
+    {79, 14},
+    {69, 14},
+    {59, 14},
+    {49, 14},
+};
+//endregion
+Icon* card_graphics = NULL;
+
+void set_card_graphics(const Icon* graphics) {
+    card_graphics = (Icon*)graphics;
+}
+
+void draw_card_at_colored(
+    int8_t pos_x,
+    int8_t pos_y,
+    uint8_t pip,
+    uint8_t character,
+    bool inverted,
+    Canvas* const canvas) {
+    DrawMode primary = inverted ? Black : White;
+    DrawMode secondary = inverted ? White : Black;
+    draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, primary);
+    draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
+
+    uint8_t* drawInfo = pips[pip];
+    uint8_t px = drawInfo[0], py = drawInfo[1], s = drawInfo[2];
+
+    uint8_t left = pos_x + 2;
+    uint8_t right = (pos_x + CARD_WIDTH - s - 2);
+    uint8_t top = pos_y + 2;
+    uint8_t bottom = (pos_y + CARD_HEIGHT - s - 2);
+
+    draw_icon_clip(canvas, card_graphics, right, top, px, py, s, s, secondary);
+    draw_icon_clip_flipped(canvas, card_graphics, left, bottom, px, py, s, s, secondary);
+
+    drawInfo = letters[character];
+    px = drawInfo[0], py = drawInfo[1], s = drawInfo[2];
+    left = pos_x + 2;
+    right = (pos_x + CARD_WIDTH - s - 2);
+    top = pos_y + 2;
+    bottom = (pos_y + CARD_HEIGHT - s - 2);
+
+    draw_icon_clip(canvas, card_graphics, left, top + 1, px, py, s, s, secondary);
+    draw_icon_clip_flipped(canvas, card_graphics, right, bottom - 1, px, py, s, s, secondary);
+}
+
+void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas) {
+    draw_card_at_colored(pos_x, pos_y, pip, character, false, canvas);
+}
+
+void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas) {
+    for(int i = count - 1; i >= 0; i--) {
+        draw_card_at(
+            playerCardPositions[i][0],
+            playerCardPositions[i][1],
+            cards[i].pip,
+            cards[i].character,
+            canvas);
+    }
+}
+
+Vector card_pos_at_index(uint8_t index) {
+    return (Vector){playerCardPositions[index][0], playerCardPositions[index][1]};
+}
+
+void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas) {
+    draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, White);
+    draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
+
+    draw_icon_clip(canvas, card_graphics, pos_x + 1, pos_y + 1, 35, 0, 15, 21, Black);
+}
+
+void generate_deck(Deck* deck_ptr, uint8_t deck_count) {
+    uint16_t counter = 0;
+    if(deck_ptr->cards != NULL) {
+        free(deck_ptr->cards);
+    }
+
+    deck_ptr->deck_count = deck_count;
+    deck_ptr->card_count = deck_count * 52;
+    deck_ptr->cards = malloc(sizeof(Card) * deck_ptr->card_count);
+
+    for(uint8_t deck = 0; deck < deck_count; deck++) {
+        for(uint8_t pip = 0; pip < 4; pip++) {
+            for(uint8_t label = 0; label < 13; label++) {
+                deck_ptr->cards[counter] = (Card){pip, label, false, false};
+                counter++;
+            }
+        }
+    }
+}
+
+void shuffle_deck(Deck* deck_ptr) {
+    srand(DWT->CYCCNT);
+    deck_ptr->index = 0;
+    int max = deck_ptr->deck_count * 52;
+    for(int i = 0; i < max; i++) {
+        int r = i + (rand() % (max - i));
+        Card tmp = deck_ptr->cards[i];
+        deck_ptr->cards[i] = deck_ptr->cards[r];
+        deck_ptr->cards[r] = tmp;
+    }
+}
+
+uint8_t hand_count(const Card* cards, uint8_t count) {
+    uint8_t aceCount = 0;
+    uint8_t score = 0;
+
+    for(uint8_t i = 0; i < count; i++) {
+        if(cards[i].character == 12)
+            aceCount++;
+        else {
+            if(cards[i].character > 8)
+                score += 10;
+            else
+                score += cards[i].character + 2;
+        }
+    }
+
+    for(uint8_t i = 0; i < aceCount; i++) {
+        if((score + 11 + (aceCount - 1)) <= 21)
+            score += 11;
+        else
+            score++;
+    }
+
+    return score;
+}
+
+void draw_card_animation(
+    Card animatingCard,
+    Vector from,
+    Vector control,
+    Vector to,
+    float t,
+    bool extra_margin,
+    Canvas* const canvas) {
+    float time = t;
+    if(extra_margin) {
+        time += 0.2;
+    }
+
+    Vector currentPos = quadratic_2d(from, control, to, time);
+    if(t > 1) {
+        draw_card_at(
+            currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas);
+    } else {
+        if(t < 0.5)
+            draw_card_back_at(currentPos.x, currentPos.y, canvas);
+        else
+            draw_card_at(
+                currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas);
+    }
+}
+
+void init_hand(Hand* hand_ptr, uint8_t count) {
+    hand_ptr->cards = malloc(sizeof(Card) * count);
+    hand_ptr->index = 0;
+    hand_ptr->max = count;
+}
+
+void free_hand(Hand* hand_ptr) {
+    FURI_LOG_D("CARD", "Freeing hand");
+    free(hand_ptr->cards);
+}
+
+void add_to_hand(Hand* hand_ptr, Card card) {
+    FURI_LOG_D("CARD", "Adding to hand");
+    if(hand_ptr->index < hand_ptr->max) {
+        hand_ptr->cards[hand_ptr->index] = card;
+        hand_ptr->index++;
+    }
+}
+
+void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas) {
+    if(highlighted) {
+        draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
+        draw_rounded_box_frame(
+            canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White);
+    } else {
+        draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
+        draw_rounded_box_frame(
+            canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White);
+    }
+}
+
+int first_non_flipped_card(Hand hand) {
+    for(int i = 0; i < hand.index; i++) {
+        if(!hand.cards[i].flipped) {
+            return i;
+        }
+    }
+    return hand.index;
+}
+
+void draw_hand_column(
+    Hand hand,
+    int16_t pos_x,
+    int16_t pos_y,
+    int8_t highlight,
+    Canvas* const canvas) {
+    if(hand.index == 0) {
+        draw_card_space(pos_x, pos_y, highlight > 0, canvas);
+        if(highlight == 0)
+            draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Inverse);
+        return;
+    }
+
+    int loopEnd = hand.index;
+    int hStart = max(loopEnd - 4, 0);
+    int pos = 0;
+    int first = first_non_flipped_card(hand);
+    bool wastop = false;
+    if(first >= 0 && first <= hStart && highlight != first) {
+        if(first > 0) {
+            draw_card_back_at(pos_x, pos_y + pos, canvas);
+            pos += 4;
+            hStart++;
+            wastop = true;
+        }
+        draw_card_at_colored(
+            pos_x, pos_y + pos, hand.cards[first].pip, hand.cards[first].character, false, canvas);
+        pos += 8;
+        hStart++;
+    }
+    if(hStart > highlight && highlight >= 0) {
+        if(!wastop && first > 0) {
+            draw_card_back_at(pos_x, pos_y + pos, canvas);
+            pos += 4;
+            hStart++;
+        }
+        draw_card_at_colored(
+            pos_x,
+            pos_y + pos,
+            hand.cards[highlight].pip,
+            hand.cards[highlight].character,
+            true,
+            canvas);
+        pos += 8;
+        hStart++;
+    }
+    for(int i = hStart; i < loopEnd; i++, pos += 4) {
+        if(hand.cards[i].flipped) {
+            draw_card_back_at(pos_x, pos_y + pos, canvas);
+            if(i == highlight)
+                draw_rounded_box(
+                    canvas, pos_x + 1, pos_y + pos + 1, CARD_WIDTH - 2, CARD_HEIGHT - 2, Inverse);
+        } else {
+            draw_card_at_colored(
+                pos_x,
+                pos_y + pos,
+                hand.cards[i].pip,
+                hand.cards[i].character,
+                (i == highlight),
+                canvas);
+            if(i == highlight || i == first) pos += 4;
+        }
+    }
+}
+
+Card remove_from_deck(uint16_t index, Deck* deck) {
+    FURI_LOG_D("CARD", "Removing from deck");
+    Card result = {0, 0, true, false};
+    if(deck->card_count > 0) {
+        deck->card_count--;
+        for(int i = 0, curr_index = 0; i <= deck->card_count; i++) {
+            if(i != index) {
+                deck->cards[curr_index] = deck->cards[i];
+                curr_index++;
+            } else {
+                result = deck->cards[i];
+            }
+        }
+        if(deck->index >= 0) {
+            deck->index--;
+        }
+    }
+    return result;
+}
+
+void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index) {
+    FURI_LOG_D("CARD", "Extracting hand region");
+    if(start_index >= hand->index) return;
+
+    for(uint8_t i = start_index; i < hand->index; i++) {
+        add_to_hand(to, hand->cards[i]);
+    }
+    hand->index = start_index;
+}
+
+void add_hand_region(Hand* to, Hand* from) {
+    FURI_LOG_D("CARD", "Adding hand region");
+    if((to->index + from->index) <= to->max) {
+        for(int i = 0; i < from->index; i++) {
+            add_to_hand(to, from->cards[i]);
+        }
+    }
+}

+ 192 - 0
common/card.h

@@ -0,0 +1,192 @@
+#pragma once
+
+#include <gui/gui.h>
+#include <math.h>
+#include <stdlib.h>
+#include "dml.h"
+
+#define CARD_HEIGHT 23
+#define CARD_HALF_HEIGHT 11
+#define CARD_WIDTH 17
+#define CARD_HALF_WIDTH 8
+
+//region types
+typedef struct {
+    uint8_t pip; //Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
+    uint8_t character; //Card letter [0-12], 0 means 2, 12 is Ace
+    bool disabled;
+    bool flipped;
+} Card;
+
+typedef struct {
+    uint8_t deck_count; //Number of decks used
+    Card* cards; //Cards in the deck
+    int card_count;
+    int index; //Card index (to know where we at in the deck)
+} Deck;
+
+typedef struct {
+    Card* cards; //Cards in the deck
+    uint8_t index; //Current index
+    uint8_t max; //How many cards we want to store
+} Hand;
+//endregion
+
+void set_card_graphics(const Icon* graphics);
+
+/**
+ * Gets card coordinates at the index (range: 0-20).
+ *
+ * @param index Index to check 0-20
+ * @return      Position of the card
+ */
+Vector card_pos_at_index(uint8_t index);
+
+/**
+ * Draws card at a given coordinate (top-left corner)
+ *
+ * @param pos_x         X position
+ * @param pos_y         Y position
+ * @param pip           Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
+ * @param character     Letter [0-12] 0 is 2, 12 is A
+ * @param canvas        Pointer to Flipper's canvas object
+ */
+void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas);
+
+/**
+ * Draws card at a given coordinate (top-left corner)
+ *
+ * @param pos_x         X position
+ * @param pos_y         Y position
+ * @param pip           Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
+ * @param character     Letter [0-12] 0 is 2, 12 is A
+ * @param inverted      Invert colors
+ * @param canvas        Pointer to Flipper's canvas object
+ */
+void draw_card_at_colored(
+    int8_t pos_x,
+    int8_t pos_y,
+    uint8_t pip,
+    uint8_t character,
+    bool inverted,
+    Canvas* const canvas);
+
+/**
+ * Draws 'count' cards at the bottom right corner
+ *
+ * @param cards     List of cards
+ * @param count     Count of cards
+ * @param canvas    Pointer to Flipper's canvas object
+ */
+void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas);
+
+/**
+ * Draws card back at a given coordinate (top-left corner)
+ *
+ * @param pos_x     X coordinate
+ * @param pos_y     Y coordinate
+ * @param canvas    Pointer to Flipper's canvas object
+ */
+void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas);
+
+/**
+ * Generates the deck
+ *
+ * @param deck_ptr      Pointer to the deck
+ * @param deck_count    Number of decks
+ */
+void generate_deck(Deck* deck_ptr, uint8_t deck_count);
+
+/**
+ * Shuffles the deck
+ *
+ * @param deck_ptr Pointer to the deck
+ */
+void shuffle_deck(Deck* deck_ptr);
+
+/**
+ * Calculates the hand count for blackjack
+ *
+ * @param cards     List of cards
+ * @param count     Count of cards
+ * @return          Hand value
+ */
+uint8_t hand_count(const Card* cards, uint8_t count);
+
+/**
+ * Draws card animation
+ *
+ * @param animatingCard Card to animate
+ * @param from          Starting position
+ * @param control       Quadratic lerp control point
+ * @param to            End point
+ * @param t             Current time (0-1)
+ * @param extra_margin  Use extra margin at the end (arrives 0.2 unit before the end so it can stay there a bit)
+ * @param canvas        Pointer to Flipper's canvas object
+ */
+void draw_card_animation(
+    Card animatingCard,
+    Vector from,
+    Vector control,
+    Vector to,
+    float t,
+    bool extra_margin,
+    Canvas* const canvas);
+
+/**
+ * Init hand pointer
+ * @param hand_ptr   Pointer to hand
+ * @param count      Number of cards we want to store
+ */
+void init_hand(Hand* hand_ptr, uint8_t count);
+
+/**
+ * Free hand resources
+ * @param hand_ptr  Pointer to hand
+ */
+void free_hand(Hand* hand_ptr);
+
+/**
+ * Add card to hand
+ * @param hand_ptr  Pointer to hand
+ * @param card      Card to add
+ */
+void add_to_hand(Hand* hand_ptr, Card card);
+
+/**
+ * Draw card placement position at coordinate
+ * @param pos_x     X coordinate
+ * @param pos_y     Y coordinate
+ * @param highlighted   Apply highlight effect
+ * @param canvas    Canvas object
+ */
+void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas);
+
+/**
+ * Draws a column of card, displaying the last [max_cards] cards on the list
+ * @param hand              Hand object
+ * @param pos_x             X coordinate to draw
+ * @param pos_y             Y coordinate to draw
+ * @param highlight         Index to highlight, negative means no highlight
+ * @param canvas            Canvas object
+ */
+void draw_hand_column(
+    Hand hand,
+    int16_t pos_x,
+    int16_t pos_y,
+    int8_t highlight,
+    Canvas* const canvas);
+
+/**
+ * Removes a card from the deck (Be aware, if you remove the first item, the deck index will be at -1 so you have to handle that)
+ * @param index Index to remove
+ * @param deck  Deck reference
+ * @return      The removed card
+ */
+Card remove_from_deck(uint16_t index, Deck* deck);
+
+int first_non_flipped_card(Hand hand);
+
+void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index);
+
+void add_hand_region(Hand* to, Hand* from);

+ 53 - 0
common/dml.c

@@ -0,0 +1,53 @@
+#include "dml.h"
+#include <math.h>
+
+float lerp(float v0, float v1, float t) {
+    if(t > 1) return v1;
+    return (1 - t) * v0 + t * v1;
+}
+
+Vector lerp_2d(Vector start, Vector end, float t) {
+    return (Vector){
+        lerp(start.x, end.x, t),
+        lerp(start.y, end.y, t),
+    };
+}
+
+Vector quadratic_2d(Vector start, Vector control, Vector end, float t) {
+    return lerp_2d(lerp_2d(start, control, t), lerp_2d(control, end, t), t);
+}
+
+Vector vector_add(Vector a, Vector b) {
+    return (Vector){a.x + b.x, a.y + b.y};
+}
+
+Vector vector_sub(Vector a, Vector b) {
+    return (Vector){a.x - b.x, a.y - b.y};
+}
+
+Vector vector_mul_components(Vector a, Vector b) {
+    return (Vector){a.x * b.x, a.y * b.y};
+}
+
+Vector vector_div_components(Vector a, Vector b) {
+    return (Vector){a.x / b.x, a.y / b.y};
+}
+
+Vector vector_normalized(Vector a) {
+    float length = vector_magnitude(a);
+    return (Vector){a.x / length, a.y / length};
+}
+
+float vector_magnitude(Vector a) {
+    return sqrt(a.x * a.x + a.y * a.y);
+}
+
+float vector_distance(Vector a, Vector b) {
+    return vector_magnitude(vector_sub(a, b));
+}
+
+float vector_dot(Vector a, Vector b) {
+    Vector _a = vector_normalized(a);
+    Vector _b = vector_normalized(b);
+    return _a.x * _b.x + _a.y * _b.y;
+}

+ 116 - 0
common/dml.h

@@ -0,0 +1,116 @@
+//
+// Doofy's Math library
+//
+
+#pragma once
+
+typedef struct {
+    float x;
+    float y;
+} Vector;
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#define abs(x) ((x) > 0 ? (x) : -(x))
+
+/**
+ * Lerp function
+ *
+ * @param v0    Start value
+ * @param v1    End value
+ * @param t     Time (0-1 range)
+ * @return      Point between v0-v1 at a given time
+ */
+float lerp(float v0, float v1, float t);
+
+/**
+ * 2D lerp function
+ *
+ * @param start Start vector
+ * @param end   End vector
+ * @param t     Time (0-1 range)
+ * @return      2d Vector between start and end at time
+ */
+Vector lerp_2d(Vector start, Vector end, float t);
+
+/**
+ * Quadratic lerp function
+ *
+ * @param start     Start vector
+ * @param control   Control point
+ * @param end       End vector
+ * @param t         Time (0-1 range)
+ * @return          2d Vector at time
+ */
+Vector quadratic_2d(Vector start, Vector control, Vector end, float t);
+
+/**
+ * Add vector components together
+ *
+ * @param a     First vector
+ * @param b     Second vector
+ * @return      Resulting vector
+ */
+Vector vector_add(Vector a, Vector b);
+
+/**
+ * Subtract vector components together
+ *
+ * @param a First vector
+ * @param b Second vector
+ * @return  Resulting vector
+ */
+Vector vector_sub(Vector a, Vector b);
+
+/**
+ * Multiplying vector components together
+ *
+ * @param a First vector
+ * @param b Second vector
+ * @return  Resulting vector
+ */
+Vector vector_mul_components(Vector a, Vector b);
+
+/**
+ * Dividing vector components
+ *
+ * @param a First vector
+ * @param b Second vector
+ * @return  Resulting vector
+ */
+Vector vector_div_components(Vector a, Vector b);
+
+/**
+ * Calculating Vector length
+ *
+ * @param a Direction vector
+ * @return  Length of the vector
+ */
+float vector_magnitude(Vector a);
+
+/**
+ * Get a normalized vector (length of 1)
+ *
+ * @param a Direction vector
+ * @return  Normalized vector
+ */
+Vector vector_normalized(Vector a);
+
+/**
+ * Calculate two vector's distance
+ *
+ * @param a First vector
+ * @param b Second vector
+ * @return  Distance between vectors
+ */
+float vector_distance(Vector a, Vector b);
+
+/**
+ * Calculate the dot product of the vectors.
+ * No need to normalize, it will do it
+ *
+ * @param a First vector
+ * @param b Second vector
+ * @return  value from -1 to 1
+ */
+float vector_dot(Vector a, Vector b);

+ 103 - 0
common/menu.c

@@ -0,0 +1,103 @@
+#include "menu.h"
+
+void add_menu(Menu* menu, const char* name, void (*callback)(void*)) {
+    MenuItem* items = menu->items;
+
+    menu->items = malloc(sizeof(MenuItem) * (menu->menu_count + 1));
+    for(uint8_t i = 0; i < menu->menu_count; i++) {
+        menu->items[i] = items[i];
+    }
+    free(items);
+
+    menu->items[menu->menu_count] = (MenuItem){name, true, callback};
+    menu->menu_count++;
+}
+
+void free_menu(Menu* menu) {
+    free(menu->items);
+    free(menu);
+}
+
+void set_menu_state(Menu* menu, uint8_t index, bool state) {
+    if(menu->menu_count > index) {
+        menu->items[index].enabled = state;
+    }
+    if(!state && menu->current_menu == index) move_menu(menu, 1);
+}
+
+void move_menu(Menu* menu, int8_t direction) {
+    if(!menu->enabled) return;
+    int max = menu->menu_count;
+    for(int8_t i = 0; i < max; i++) {
+        FURI_LOG_D(
+            "MENU",
+            "Iteration %i, current %i, direction %i, state %i",
+            i,
+            menu->current_menu,
+            direction,
+            menu->items[menu->current_menu].enabled ? 1 : 0);
+        if(direction < 0 && menu->current_menu == 0) {
+            menu->current_menu = menu->menu_count - 1;
+        } else {
+            menu->current_menu = (menu->current_menu + direction) % menu->menu_count;
+        }
+        FURI_LOG_D(
+            "MENU",
+            "After process current %i, direction %i, state %i",
+            menu->current_menu,
+            direction,
+            menu->items[menu->current_menu].enabled ? 1 : 0);
+        if(menu->items[menu->current_menu].enabled) {
+            FURI_LOG_D("MENU", "Next menu %i", menu->current_menu);
+            return;
+        }
+    }
+    FURI_LOG_D("MENU", "Not found, setting false");
+    menu->enabled = false;
+}
+
+void activate_menu(Menu* menu, void* state) {
+    if(!menu->enabled) return;
+    menu->items[menu->current_menu].callback(state);
+}
+
+void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y) {
+    if(!menu->enabled) return;
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2);
+
+    uint8_t w = pos_x + menu->menu_width;
+    uint8_t h = pos_y + 10;
+    uint8_t p1x = pos_x + 2;
+    uint8_t p2x = pos_x + menu->menu_width - 2;
+    uint8_t p1y = pos_y + 2;
+    uint8_t p2y = pos_y + 8;
+
+    canvas_set_color(canvas, ColorBlack);
+    canvas_draw_line(canvas, p1x, pos_y, p2x, pos_y);
+    canvas_draw_line(canvas, p1x, h, p2x, h);
+    canvas_draw_line(canvas, pos_x, p1y, pos_x, p2y);
+    canvas_draw_line(canvas, w, p1y, w, p2y);
+    canvas_draw_dot(canvas, pos_x + 1, pos_y + 1);
+    canvas_draw_dot(canvas, w - 1, pos_y + 1);
+    canvas_draw_dot(canvas, w - 1, h - 1);
+    canvas_draw_dot(canvas, pos_x + 1, h - 1);
+
+    //    canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2);
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(
+        canvas,
+        pos_x + menu->menu_width / 2,
+        pos_y + 6,
+        AlignCenter,
+        AlignCenter,
+        menu->items[menu->current_menu].name);
+    //9*5
+    int center = pos_x + menu->menu_width / 2;
+    for(uint8_t i = 0; i < 4; i++) {
+        for(int8_t j = -i; j <= i; j++) {
+            canvas_draw_dot(canvas, center + j, pos_y - 4 + i);
+            canvas_draw_dot(canvas, center + j, pos_y + 14 - i);
+        }
+    }
+}

+ 77 - 0
common/menu.h

@@ -0,0 +1,77 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/gui.h>
+
+typedef struct {
+    const char* name; //Name of the menu
+    bool enabled; //Is the menu item enabled (it will not render, you cannot select it)
+
+    void (*callback)(
+        void* state); //Callback for when the activate_menu is called while this menu is selected
+} MenuItem;
+
+typedef struct {
+    MenuItem* items; //list of menu items
+    uint8_t menu_count; //count of menu items (do not change)
+    uint8_t current_menu; //currently selected menu item
+    uint8_t menu_width; //width of the menu
+    bool enabled; //is the menu enabled (it will not render and accept events when disabled)
+} Menu;
+
+/**
+ * Cleans up the pointers used by the menu
+ *
+ * @param menu Pointer of the menu to clean up
+ */
+void free_menu(Menu* menu);
+
+/**
+ * Add a new menu item
+ *
+ * @param menu      Pointer of the menu
+ * @param name      Name of the menu item
+ * @param callback  Callback called on activation
+ */
+void add_menu(Menu* menu, const char* name, void (*callback)(void*));
+
+/**
+ * Setting menu item to be enabled/disabled
+ *
+ * @param menu  Pointer of the menu
+ * @param index Menu index to set
+ * @param state Enabled (true), Disabled(false)
+ */
+void set_menu_state(Menu* menu, uint8_t index, bool state);
+
+/**
+ * Moves selection up or down
+ *
+ * @param menu      Pointer of the menu
+ * @param direction Direction to move -1 down, 1 up
+ */
+void move_menu(Menu* menu, int8_t direction);
+
+/**
+ * Triggers the current menu callback
+ *
+ * @param menu  Pointer of the menu
+ * @param state Usually your application state
+ */
+void activate_menu(Menu* menu, void* state);
+
+/**
+ * Renders the menu at a coordinate (call it in your render function).
+ *
+ * Keep in mind that Flipper has a 128x64 pixel screen resolution and the coordinate
+ * you give is the menu's rectangle top-left corner (arrows not included).
+ * The rectangle height is 10 px, the arrows have a 4 pixel height. Space needed is 18px.
+ * The width of the menu can be configured in the menu object.
+ *
+ *
+ * @param menu      Pointer of the menu
+ * @param canvas    Flippers Canvas pointer
+ * @param pos_x     X position to draw
+ * @param pos_y     Y position to draw
+ */
+void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y);

+ 69 - 0
common/queue.c

@@ -0,0 +1,69 @@
+#include "queue.h"
+
+void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas) {
+    if(queue_state->current != NULL && queue_state->current->render != NULL)
+        ((QueueItem*)queue_state->current)->render(app_state, canvas);
+}
+
+bool run_queue(QueueState* queue_state, void* app_state) {
+    if(queue_state->current != NULL) {
+        queue_state->running = true;
+        if((furi_get_tick() - queue_state->start) >= queue_state->current->duration)
+            dequeue(queue_state, app_state);
+
+        return true;
+    }
+    return false;
+}
+
+void dequeue(QueueState* queue_state, void* app_state) {
+    ((QueueItem*)queue_state->current)->callback(app_state);
+    QueueItem* f = queue_state->current;
+    queue_state->current = f->next;
+    free(f);
+    if(queue_state->current != NULL) {
+        if(queue_state->current->start != NULL) queue_state->current->start(app_state);
+        queue_state->start = furi_get_tick();
+    } else {
+        queue_state->running = false;
+    }
+}
+
+void queue_clear(QueueState* queue_state) {
+    queue_state->running = false;
+    QueueItem* curr = queue_state->current;
+    while(curr != NULL) {
+        QueueItem* f = curr;
+        curr = curr->next;
+        free(f);
+    }
+}
+
+void enqueue(
+    QueueState* queue_state,
+    void* app_state,
+    void (*done)(void* state),
+    void (*start)(void* state),
+    void (*render)(const void* state, Canvas* const canvas),
+    uint32_t duration) {
+    QueueItem* next;
+    if(queue_state->current == NULL) {
+        queue_state->start = furi_get_tick();
+        queue_state->current = malloc(sizeof(QueueItem));
+        next = queue_state->current;
+        if(next->start != NULL) next->start(app_state);
+
+    } else {
+        next = queue_state->current;
+        while(next->next != NULL) {
+            next = (QueueItem*)(next->next);
+        }
+        next->next = malloc(sizeof(QueueItem));
+        next = next->next;
+    }
+    next->callback = done;
+    next->render = render;
+    next->start = start;
+    next->duration = duration;
+    next->next = NULL;
+}

+ 70 - 0
common/queue.h

@@ -0,0 +1,70 @@
+#pragma once
+
+#include <gui/gui.h>
+#include <furi.h>
+
+typedef struct {
+    void (*callback)(void* state); //Callback for when the item is dequeued
+    void (*render)(
+        const void* state,
+        Canvas* const canvas); //Callback for the rendering loop while this item is running
+    void (*start)(void* state); //Callback when this item is started running
+    void* next; //Pointer to the next item
+    uint32_t duration; //duration of the item
+} QueueItem;
+
+typedef struct {
+    unsigned int start; //current queue item start time
+    QueueItem* current; //current queue item
+    bool running; //is the queue running
+} QueueState;
+
+/**
+ * Enqueue a new item.
+ *
+ * @param queue_state   The queue state pointer
+ * @param app_state     Your app state
+ * @param done          Callback for dequeue event
+ * @param start         Callback for when the item is activated
+ * @param render        Callback to render loop if needed
+ * @param duration      Length of the item
+ */
+void enqueue(
+    QueueState* queue_state,
+    void* app_state,
+    void (*done)(void* state),
+    void (*start)(void* state),
+    void (*render)(const void* state, Canvas* const canvas),
+    uint32_t duration);
+/**
+ * Clears all queue items
+ *
+ * @param queue_state   The queue state pointer
+ */
+void queue_clear(QueueState* queue_state);
+
+/**
+ * Dequeues the active queue item. Usually you don't need to call it directly.
+ *
+ * @param queue_state   The queue state pointer
+ * @param app_state     Your application state
+ */
+void dequeue(QueueState* queue_state, void* app_state);
+
+/**
+ * Runs the queue logic (place it in your tick function)
+ *
+ * @param queue_state   The queue state pointer
+ * @param app_state     Your application state
+ * @return              FALSE when there is nothing to run, TRUE otherwise
+ */
+bool run_queue(QueueState* queue_state, void* app_state);
+
+/**
+ * Calls the currently active queue items render callback (if there is any)
+ *
+ * @param queue_state   The queue state pointer
+ * @param app_state     Your application state
+ * @param canvas        Pointer to Flipper's canvas object
+ */
+void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas);

+ 257 - 0
common/ui.c

@@ -0,0 +1,257 @@
+#include "ui.h"
+#include <gui/canvas_i.h>
+#include <u8g2_glue.h>
+#include <gui/icon_animation_i.h>
+#include <gui/icon.h>
+#include <gui/icon_i.h>
+#include <furi_hal.h>
+
+TileMap* tileMap;
+uint8_t tileMapCount = 0;
+
+void ui_cleanup() {
+    if(tileMap != NULL) {
+        for(uint8_t i = 0; i < tileMapCount; i++) {
+            if(tileMap[i].data != NULL) free(tileMap[i].data);
+        }
+        free(tileMap);
+    }
+}
+
+void add_new_tilemap(uint8_t* data, unsigned long iconId) {
+    TileMap* old = tileMap;
+    tileMapCount++;
+    tileMap = malloc(sizeof(TileMap) * tileMapCount);
+    if(tileMapCount > 1) {
+        for(uint8_t i = 0; i < tileMapCount; i++) tileMap[i] = old[i];
+    }
+    tileMap[tileMapCount - 1] = (TileMap){data, iconId};
+}
+
+uint8_t* get_tilemap(unsigned long icon_id) {
+    for(uint8_t i = 0; i < tileMapCount; i++) {
+        if(tileMap[i].iconId == icon_id) return tileMap[i].data;
+    }
+
+    return NULL;
+}
+
+uint32_t pixel_index(uint8_t x, uint8_t y) {
+    return y * SCREEN_WIDTH + x;
+}
+
+bool in_screen(int16_t x, int16_t y) {
+    return x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT;
+}
+
+unsigned flipBit(uint8_t x, uint8_t bit) {
+    return x ^ (1 << bit);
+}
+
+unsigned setBit(uint8_t x, uint8_t bit) {
+    return x | (1 << bit);
+}
+
+unsigned unsetBit(uint8_t x, uint8_t bit) {
+    return x & ~(1 << bit);
+}
+
+bool test_pixel(uint8_t* data, uint8_t x, uint8_t y, uint8_t w) {
+    uint8_t current_bit = (y % 8);
+    uint8_t current_row = ((y - current_bit) / 8);
+    uint8_t current_value = data[current_row * w + x];
+    return current_value & (1 << current_bit);
+}
+
+uint8_t* get_buffer(Canvas* const canvas) {
+    return canvas->fb.tile_buf_ptr;
+    //  return canvas_get_buffer(canvas);
+}
+uint8_t* make_buffer() {
+    return malloc(sizeof(uint8_t) * 8 * 128);
+}
+void clone_buffer(uint8_t* canvas, uint8_t* data) {
+    for(int i = 0; i < 1024; i++) {
+        data[i] = canvas[i];
+    }
+}
+
+bool read_pixel(Canvas* const canvas, int16_t x, int16_t y) {
+    if(in_screen(x, y)) {
+        return test_pixel(get_buffer(canvas), x, y, SCREEN_WIDTH);
+    }
+    return false;
+}
+
+void set_pixel(Canvas* const canvas, int16_t x, int16_t y, DrawMode draw_mode) {
+    if(in_screen(x, y)) {
+        uint8_t current_bit = (y % 8);
+        uint8_t current_row = ((y - current_bit) / 8);
+        uint32_t i = pixel_index(x, current_row);
+        uint8_t* buffer = get_buffer(canvas);
+
+        uint8_t current_value = buffer[i];
+        if(draw_mode == Inverse) {
+            buffer[i] = flipBit(current_value, current_bit);
+        } else {
+            if(draw_mode == White) {
+                buffer[i] = unsetBit(current_value, current_bit);
+            } else {
+                buffer[i] = setBit(current_value, current_bit);
+            }
+        }
+    }
+}
+
+void draw_line(
+    Canvas* const canvas,
+    int16_t x1,
+    int16_t y1,
+    int16_t x2,
+    int16_t y2,
+    DrawMode draw_mode) {
+    for(int16_t x = x2; x >= x1; x--) {
+        for(int16_t y = y2; y >= y1; y--) {
+            set_pixel(canvas, x, y, draw_mode);
+        }
+    }
+}
+
+void draw_rounded_box_frame(
+    Canvas* const canvas,
+    int16_t x,
+    int16_t y,
+    uint8_t w,
+    uint8_t h,
+    DrawMode draw_mode) {
+    int16_t xMinCorner = x + 1;
+    int16_t xMax = x + w - 1;
+    int16_t xMaxCorner = x + w - 2;
+    int16_t yMinCorner = y + 1;
+    int16_t yMax = y + h - 1;
+    int16_t yMaxCorner = y + h - 2;
+    draw_line(canvas, xMinCorner, y, xMaxCorner, y, draw_mode);
+    draw_line(canvas, xMinCorner, yMax, xMaxCorner, yMax, draw_mode);
+    draw_line(canvas, x, yMinCorner, x, yMaxCorner, draw_mode);
+    draw_line(canvas, xMax, yMinCorner, xMax, yMaxCorner, draw_mode);
+}
+
+void draw_rounded_box(
+    Canvas* const canvas,
+    int16_t x,
+    int16_t y,
+    uint8_t w,
+    uint8_t h,
+    DrawMode draw_mode) {
+    for(int16_t o = w - 2; o >= 1; o--) {
+        for(int16_t p = h - 2; p >= 1; p--) {
+            set_pixel(canvas, x + o, y + p, draw_mode);
+        }
+    }
+    draw_rounded_box_frame(canvas, x, y, w, h, draw_mode);
+}
+
+void invert_shape(Canvas* const canvas, uint8_t* data, int16_t x, int16_t y, uint8_t w, uint8_t h) {
+    draw_pixels(canvas, data, x, y, w, h, Inverse);
+}
+
+void draw_pixels(
+    Canvas* const canvas,
+    uint8_t* data,
+    int16_t x,
+    int16_t y,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode) {
+    for(int8_t o = 0; o < w; o++) {
+        for(int8_t p = 0; p < h; p++) {
+            if(in_screen(o + x, p + y) && data[p * w + o] == 1)
+                set_pixel(canvas, o + x, p + y, drawMode);
+        }
+    }
+}
+
+void draw_rectangle(
+    Canvas* const canvas,
+    int16_t x,
+    int16_t y,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode) {
+    for(int8_t o = 0; o < w; o++) {
+        for(int8_t p = 0; p < h; p++) {
+            if(in_screen(o + x, p + y)) {
+                set_pixel(canvas, o + x, p + y, drawMode);
+            }
+        }
+    }
+}
+
+void invert_rectangle(Canvas* const canvas, int16_t x, int16_t y, uint8_t w, uint8_t h) {
+    draw_rectangle(canvas, x, y, w, h, Inverse);
+}
+
+uint8_t* image_data(Canvas* const canvas, const Icon* icon) {
+    uint8_t* data = malloc(sizeof(uint8_t) * 8 * 128);
+    uint8_t* screen = canvas->fb.tile_buf_ptr;
+    canvas->fb.tile_buf_ptr = data;
+    canvas_draw_icon(canvas, 0, 0, icon);
+    canvas->fb.tile_buf_ptr = screen;
+    return data;
+}
+
+uint8_t* getOrAddIconData(Canvas* const canvas, const Icon* icon) {
+    uint8_t* icon_data = get_tilemap((unsigned long)icon);
+    if(icon_data == NULL) {
+        icon_data = image_data(canvas, icon);
+        add_new_tilemap(icon_data, (unsigned long)icon);
+    }
+    return icon_data;
+}
+
+void draw_icon_clip(
+    Canvas* const canvas,
+    const Icon* icon,
+    int16_t x,
+    int16_t y,
+    uint8_t left,
+    uint8_t top,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode) {
+    uint8_t* icon_data = getOrAddIconData(canvas, icon);
+
+    for(int i = 0; i < w; i++) {
+        for(int j = 0; j < h; j++) {
+            bool on = test_pixel(icon_data, left + i, top + j, SCREEN_WIDTH);
+            if(drawMode == Filled) {
+                set_pixel(canvas, x + i, y + j, on ? Black : White);
+            } else if(on)
+                set_pixel(canvas, x + i, y + j, drawMode);
+        }
+    }
+}
+
+void draw_icon_clip_flipped(
+    Canvas* const canvas,
+    const Icon* icon,
+    int16_t x,
+    int16_t y,
+    uint8_t left,
+    uint8_t top,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode) {
+    uint8_t* icon_data = getOrAddIconData(canvas, icon);
+
+    for(int i = 0; i < w; i++) {
+        for(int j = 0; j < h; j++) {
+            bool on = test_pixel(icon_data, left + i, top + j, SCREEN_WIDTH);
+
+            if(drawMode == Filled) {
+                set_pixel(canvas, x + w - i - 1, y + h - j - 1, on ? Black : White);
+            } else if(on)
+                set_pixel(canvas, x + w - i - 1, y + h - j - 1, drawMode);
+        }
+    }
+}

+ 105 - 0
common/ui.h

@@ -0,0 +1,105 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/canvas.h>
+
+#define SCREEN_WIDTH 128
+#define SCREEN_HEIGHT 64
+
+typedef enum {
+    Black,
+    White,
+    Inverse,
+    Filled //Currently only for Icon clip drawing
+} DrawMode;
+
+// size is the screen size
+
+typedef struct {
+    uint8_t* data;
+    unsigned long iconId;
+} TileMap;
+
+bool test_pixel(uint8_t* data, uint8_t x, uint8_t y, uint8_t w);
+
+uint8_t* image_data(Canvas* const canvas, const Icon* icon);
+
+uint32_t pixel_index(uint8_t x, uint8_t y);
+
+void draw_icon_clip(
+    Canvas* const canvas,
+    const Icon* icon,
+    int16_t x,
+    int16_t y,
+    uint8_t left,
+    uint8_t top,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode);
+
+void draw_icon_clip_flipped(
+    Canvas* const canvas,
+    const Icon* icon,
+    int16_t x,
+    int16_t y,
+    uint8_t left,
+    uint8_t top,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode);
+
+void draw_rounded_box(
+    Canvas* const canvas,
+    int16_t x,
+    int16_t y,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode);
+
+void draw_rounded_box_frame(
+    Canvas* const canvas,
+    int16_t x,
+    int16_t y,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode);
+
+void draw_rectangle(
+    Canvas* const canvas,
+    int16_t x,
+    int16_t y,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode);
+
+void invert_rectangle(Canvas* const canvas, int16_t x, int16_t y, uint8_t w, uint8_t h);
+
+void invert_shape(Canvas* const canvas, uint8_t* data, int16_t x, int16_t y, uint8_t w, uint8_t h);
+
+void draw_pixels(
+    Canvas* const canvas,
+    uint8_t* data,
+    int16_t x,
+    int16_t y,
+    uint8_t w,
+    uint8_t h,
+    DrawMode drawMode);
+
+bool read_pixel(Canvas* const canvas, int16_t x, int16_t y);
+
+void set_pixel(Canvas* const canvas, int16_t x, int16_t y, DrawMode draw_mode);
+
+void draw_line(
+    Canvas* const canvas,
+    int16_t x1,
+    int16_t y1,
+    int16_t x2,
+    int16_t y2,
+    DrawMode draw_mode);
+
+bool in_screen(int16_t x, int16_t y);
+
+void ui_cleanup();
+uint8_t* get_buffer(Canvas* const canvas);
+uint8_t* make_buffer();
+void clone_buffer(uint8_t* canvas, uint8_t* data);

+ 3 - 4
defines.h

@@ -22,7 +22,7 @@ typedef enum {
     EventTypeKey,
 } EventType;
 
-typedef struct{
+typedef struct {
     uint32_t animation_duration;
     uint32_t message_duration;
     uint32_t starting_money;
@@ -54,6 +54,7 @@ typedef enum {
 } Direction;
 
 typedef struct {
+    FuriMutex* mutex;
     Card player_cards[21];
     Card dealer_cards[21];
     uint8_t player_card_count;
@@ -71,8 +72,6 @@ typedef struct {
     Deck deck;
     PlayState state;
     QueueState queue_state;
-    Menu *menu;
+    Menu* menu;
     unsigned int last_tick;
-    FuriMutex* mutex;
 } GameState;
-

BIN
screenshots/blackjack.gif


BIN
screenshots/play_scene.png


BIN
screenshots/welcome.png


+ 80 - 74
ui.c

@@ -6,32 +6,28 @@
 #define LINE_HEIGHT 16
 #define ITEM_PADDING 4
 
-const char MoneyMul[4] = {
-        'K', 'B', 'T', 'S'
-};
+const char MoneyMul[4] = {'K', 'B', 'T', 'S'};
 
-void draw_player_scene(Canvas *const canvas, const GameState *game_state) {
+void draw_player_scene(Canvas* const canvas, const GameState* game_state) {
     int max_card = game_state->player_card_count;
 
-    if (max_card > 0)
-        draw_deck((game_state->player_cards), max_card, canvas);
+    if(max_card > 0) draw_deck((game_state->player_cards), max_card, canvas);
 
-    if (game_state->dealer_card_count > 0)
-        draw_card_back_at(13, 5, canvas);
+    if(game_state->dealer_card_count > 0) draw_card_back_at(13, 5, canvas);
 
     max_card = game_state->dealer_card_count;
-    if (max_card > 1) {
-        draw_card_at(2, 2, game_state->dealer_cards[1].pip, game_state->dealer_cards[1].character,
-                   canvas);
+    if(max_card > 1) {
+        draw_card_at(
+            2, 2, game_state->dealer_cards[1].pip, game_state->dealer_cards[1].character, canvas);
     }
 }
 
-void draw_dealer_scene(Canvas *const canvas, const GameState *game_state) {
+void draw_dealer_scene(Canvas* const canvas, const GameState* game_state) {
     uint8_t max_card = game_state->dealer_card_count;
     draw_deck((game_state->dealer_cards), max_card, canvas);
 }
 
-void popup_frame(Canvas *const canvas) {
+void popup_frame(Canvas* const canvas) {
     canvas_set_color(canvas, ColorWhite);
     canvas_draw_box(canvas, 32, 15, 66, 13);
     canvas_set_color(canvas, ColorBlack);
@@ -39,15 +35,16 @@ void popup_frame(Canvas *const canvas) {
     canvas_set_font(canvas, FontSecondary);
 }
 
-
-void draw_play_menu(Canvas *const canvas, const GameState *game_state) {
-    const char *menus[3] = {"Double", "Hit", "Stay"};
-    for (uint8_t m = 0; m < 3; m++) {
-        if (m == 0 && (game_state->doubled || game_state->player_score < game_state->settings.round_price)) continue;
+void draw_play_menu(Canvas* const canvas, const GameState* game_state) {
+    const char* menus[3] = {"Double", "Hit", "Stay"};
+    for(uint8_t m = 0; m < 3; m++) {
+        if(m == 0 &&
+           (game_state->doubled || game_state->player_score < game_state->settings.round_price))
+            continue;
         int y = m * 13 + 25;
         canvas_set_color(canvas, ColorBlack);
 
-        if (game_state->selectedMenu == m) {
+        if(game_state->selectedMenu == m) {
             canvas_set_color(canvas, ColorBlack);
             canvas_draw_box(canvas, 1, y, 31, 12);
         } else {
@@ -57,7 +54,7 @@ void draw_play_menu(Canvas *const canvas, const GameState *game_state) {
             canvas_draw_frame(canvas, 1, y, 31, 12);
         }
 
-        if (game_state->selectedMenu == m)
+        if(game_state->selectedMenu == m)
             canvas_set_color(canvas, ColorWhite);
         else
             canvas_set_color(canvas, ColorBlack);
@@ -65,35 +62,34 @@ void draw_play_menu(Canvas *const canvas, const GameState *game_state) {
     }
 }
 
-void draw_screen(Canvas *const canvas, const bool *points) {
-    for (uint8_t x = 0; x < 128; x++) {
-        for (uint8_t y = 0; y < 64; y++) {
-            if (points[y * 128 + x])
-                canvas_draw_dot(canvas, x, y);
+void draw_screen(Canvas* const canvas, const bool* points) {
+    for(uint8_t x = 0; x < 128; x++) {
+        for(uint8_t y = 0; y < 64; y++) {
+            if(points[y * 128 + x]) canvas_draw_dot(canvas, x, y);
         }
     }
 }
 
-void draw_score(Canvas *const canvas, bool top, uint8_t amount) {
+void draw_score(Canvas* const canvas, bool top, uint8_t amount) {
     char drawChar[20];
     snprintf(drawChar, sizeof(drawChar), "Player score: %i", amount);
-    if (top)
+    if(top)
         canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, drawChar);
     else
         canvas_draw_str_aligned(canvas, 64, 62, AlignCenter, AlignBottom, drawChar);
 }
 
-void draw_money(Canvas *const canvas, uint32_t score) {
+void draw_money(Canvas* const canvas, uint32_t score) {
     canvas_set_font(canvas, FontSecondary);
-    char drawChar[10];
+    char drawChar[11];
     uint32_t currAmount = score;
-    if (currAmount < 1000) {
+    if(currAmount < 1000) {
         snprintf(drawChar, sizeof(drawChar), "$%lu", currAmount);
     } else {
         char c = 'K';
-        for (uint8_t i = 0; i < 4; i++) {
+        for(uint8_t i = 0; i < 4; i++) {
             currAmount = currAmount / 1000;
-            if (currAmount < 1000) {
+            if(currAmount < 1000) {
                 c = MoneyMul[i];
                 break;
             }
@@ -104,77 +100,87 @@ void draw_money(Canvas *const canvas, uint32_t score) {
     canvas_draw_str_aligned(canvas, 126, 2, AlignRight, AlignTop, drawChar);
 }
 
-
-void draw_menu(Canvas *const canvas, const char *text, const char *value, int8_t y, bool left_caret, bool right_caret,
-               bool selected) {
+void draw_menu(
+    Canvas* const canvas,
+    const char* text,
+    const char* value,
+    int8_t y,
+    bool left_caret,
+    bool right_caret,
+    bool selected) {
     UNUSED(selected);
-    if (y < 0 || y >= 64) return;
+    if(y < 0 || y >= 64) return;
 
-    if (selected) {
+    if(selected) {
         canvas_set_color(canvas, ColorBlack);
         canvas_draw_box(canvas, 0, y, 122, LINE_HEIGHT);
         canvas_set_color(canvas, ColorWhite);
     }
 
     canvas_draw_str_aligned(canvas, 4, y + ITEM_PADDING, AlignLeft, AlignTop, text);
-    if (left_caret)
-        canvas_draw_str_aligned(canvas, 80, y + ITEM_PADDING, AlignLeft, AlignTop, "<");
+    if(left_caret) canvas_draw_str_aligned(canvas, 80, y + ITEM_PADDING, AlignLeft, AlignTop, "<");
     canvas_draw_str_aligned(canvas, 100, y + ITEM_PADDING, AlignCenter, AlignTop, value);
-    if (right_caret)
+    if(right_caret)
         canvas_draw_str_aligned(canvas, 120, y + ITEM_PADDING, AlignRight, AlignTop, ">");
 
     canvas_set_color(canvas, ColorBlack);
 }
 
-void settings_page(Canvas *const canvas, const GameState *gameState) {
+void settings_page(Canvas* const canvas, const GameState* gameState) {
     char drawChar[10];
     int startY = 0;
-    if (LINE_HEIGHT * (gameState->selectedMenu + 1) >= 64) {
+    if(LINE_HEIGHT * (gameState->selectedMenu + 1) >= 64) {
         startY -= (LINE_HEIGHT * (gameState->selectedMenu + 1)) - 64;
     }
 
     int scrollHeight = round(64 / 6.0) + ITEM_PADDING * 2;
     int scrollPos = 64 / (6.0 / (gameState->selectedMenu + 1)) - ITEM_PADDING * 2;
 
-
     canvas_set_color(canvas, ColorBlack);
     canvas_draw_box(canvas, 123, scrollPos, 4, scrollHeight);
     canvas_draw_box(canvas, 125, 0, 1, 64);
 
     snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.starting_money);
-    draw_menu(canvas, "Start money", drawChar,
-              0 * LINE_HEIGHT + startY,
-              gameState->settings.starting_money > gameState->settings.round_price,
-              gameState->settings.starting_money < 400,
-              gameState->selectedMenu == 0
-    );
+    draw_menu(
+        canvas,
+        "Start money",
+        drawChar,
+        0 * LINE_HEIGHT + startY,
+        gameState->settings.starting_money > gameState->settings.round_price,
+        gameState->settings.starting_money < 400,
+        gameState->selectedMenu == 0);
     snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.round_price);
-    draw_menu(canvas, "Round price", drawChar,
-              1 * LINE_HEIGHT + startY,
-              gameState->settings.round_price > 10,
-              gameState->settings.round_price < gameState->settings.starting_money,
-              gameState->selectedMenu == 1
-    );
+    draw_menu(
+        canvas,
+        "Round price",
+        drawChar,
+        1 * LINE_HEIGHT + startY,
+        gameState->settings.round_price > 10,
+        gameState->settings.round_price < gameState->settings.starting_money,
+        gameState->selectedMenu == 1);
 
     snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.animation_duration);
-    draw_menu(canvas, "Anim. length", drawChar,
-              2 * LINE_HEIGHT + startY,
-              gameState->settings.animation_duration > 0,
-              gameState->settings.animation_duration < 2000,
-              gameState->selectedMenu == 2
-    );
+    draw_menu(
+        canvas,
+        "Anim. length",
+        drawChar,
+        2 * LINE_HEIGHT + startY,
+        gameState->settings.animation_duration > 0,
+        gameState->settings.animation_duration < 2000,
+        gameState->selectedMenu == 2);
     snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.message_duration);
-    draw_menu(canvas, "Popup time", drawChar,
-              3 * LINE_HEIGHT + startY,
-              gameState->settings.message_duration > 0,
-              gameState->settings.message_duration < 2000,
-              gameState->selectedMenu == 3
-    );
-//    draw_menu(canvas, "Sound", gameState->settings.sound_effects ? "Yes" : "No",
-//              5 * LINE_HEIGHT + startY,
-//              true,
-//              true,
-//              gameState->selectedMenu == 5
-//    );
-
+    draw_menu(
+        canvas,
+        "Popup time",
+        drawChar,
+        3 * LINE_HEIGHT + startY,
+        gameState->settings.message_duration > 0,
+        gameState->settings.message_duration < 2000,
+        gameState->selectedMenu == 3);
+    //    draw_menu(canvas, "Sound", gameState->settings.sound_effects ? "Yes" : "No",
+    //              5 * LINE_HEIGHT + startY,
+    //              true,
+    //              true,
+    //              gameState->selectedMenu == 5
+    //    );
 }

+ 8 - 8
ui.h

@@ -3,16 +3,16 @@
 #include "defines.h"
 #include <gui/gui.h>
 
-void draw_player_scene(Canvas *const canvas, const GameState *game_state);
+void draw_player_scene(Canvas* const canvas, const GameState* game_state);
 
-void draw_dealer_scene(Canvas *const canvas, const GameState *game_state);
+void draw_dealer_scene(Canvas* const canvas, const GameState* game_state);
 
-void draw_play_menu(Canvas *const canvas, const GameState *game_state);
+void draw_play_menu(Canvas* const canvas, const GameState* game_state);
 
-void draw_score(Canvas *const canvas, bool top, uint8_t amount);
+void draw_score(Canvas* const canvas, bool top, uint8_t amount);
 
-void draw_money(Canvas *const canvas, uint32_t score);
-void settings_page(Canvas *const canvas, const GameState * gameState);
+void draw_money(Canvas* const canvas, uint32_t score);
+void settings_page(Canvas* const canvas, const GameState* gameState);
 
-void popup_frame(Canvas *const canvas);
-void draw_screen(Canvas *const canvas, const bool* points);
+void popup_frame(Canvas* const canvas);
+void draw_screen(Canvas* const canvas, const bool* points);

+ 26 - 26
util.c

@@ -1,16 +1,17 @@
 #include <storage/storage.h>
 #include "util.h"
 
-const char *CONFIG_FILE_PATH = EXT_PATH(".blackjack.settings");
+const char* CONFIG_FILE_PATH = EXT_PATH(".blackjack.settings");
 
 void save_settings(Settings settings) {
-    Storage *storage = furi_record_open(RECORD_STORAGE);
-    FlipperFormat *file = flipper_format_file_alloc(storage);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* file = flipper_format_file_alloc(storage);
     FURI_LOG_D(APP_NAME, "Saving config");
-    if (flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
-        FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_ANIMATION_DURATION, settings.animation_duration);
-        flipper_format_update_uint32(file, CONF_ANIMATION_DURATION, &(settings.animation_duration), 1);
-
+    if(flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
+        FURI_LOG_D(
+            APP_NAME, "Saving %s: %ld", CONF_ANIMATION_DURATION, settings.animation_duration);
+        flipper_format_update_uint32(
+            file, CONF_ANIMATION_DURATION, &(settings.animation_duration), 1);
 
         FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_MESSAGE_DURATION, settings.message_duration);
         flipper_format_update_uint32(file, CONF_MESSAGE_DURATION, &(settings.message_duration), 1);
@@ -21,10 +22,10 @@ void save_settings(Settings settings) {
         FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_ROUND_PRICE, settings.round_price);
         flipper_format_update_uint32(file, CONF_ROUND_PRICE, &(settings.round_price), 1);
 
-        FURI_LOG_D(APP_NAME, "Saving %s: %i", CONF_SOUND_EFFECTS, settings.sound_effects?1:0);
+        FURI_LOG_D(APP_NAME, "Saving %s: %i", CONF_SOUND_EFFECTS, settings.sound_effects ? 1 : 0);
         flipper_format_update_bool(file, CONF_SOUND_EFFECTS, &(settings.sound_effects), 1);
         FURI_LOG_D(APP_NAME, "Config saved");
-    }else{
+    } else {
         FURI_LOG_E(APP_NAME, "Save error");
     }
     flipper_format_file_close(file);
@@ -32,7 +33,7 @@ void save_settings(Settings settings) {
     furi_record_close(RECORD_STORAGE);
 }
 
-void save_settings_file(FlipperFormat *file, Settings *settings) {
+void save_settings_file(FlipperFormat* file, Settings* settings) {
     flipper_format_write_header_cstr(file, CONFIG_FILE_HEADER, CONFIG_FILE_VERSION);
     flipper_format_write_comment_cstr(file, "Card animation duration in ms");
     flipper_format_write_uint32(file, CONF_ANIMATION_DURATION, &(settings->animation_duration), 1);
@@ -57,58 +58,57 @@ Settings load_settings() {
     settings.sound_effects = true;
 
     FURI_LOG_D(APP_NAME, "Opening storage");
-    Storage *storage = furi_record_open(RECORD_STORAGE);
+    Storage* storage = furi_record_open(RECORD_STORAGE);
     FURI_LOG_D(APP_NAME, "Allocating file");
-    FlipperFormat *file = flipper_format_file_alloc(storage);
+    FlipperFormat* file = flipper_format_file_alloc(storage);
 
     FURI_LOG_D(APP_NAME, "Allocating string");
-    FuriString *string_value;
+    FuriString* string_value;
     string_value = furi_string_alloc();
 
-    if (storage_common_stat(storage, CONFIG_FILE_PATH, NULL) != FSE_OK) {
+    if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) != FSE_OK) {
         FURI_LOG_D(APP_NAME, "Config file %s not found, creating new one...", CONFIG_FILE_PATH);
-        if (!flipper_format_file_open_new(file, CONFIG_FILE_PATH)) {
+        if(!flipper_format_file_open_new(file, CONFIG_FILE_PATH)) {
             FURI_LOG_E(APP_NAME, "Error creating new file %s", CONFIG_FILE_PATH);
             flipper_format_file_close(file);
         } else {
             save_settings_file(file, &settings);
         }
     } else {
-        if (!flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
+        if(!flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
             FURI_LOG_E(APP_NAME, "Error opening existing file %s", CONFIG_FILE_PATH);
             flipper_format_file_close(file);
-        }
-        else {
+        } else {
             uint32_t value;
             bool valueBool;
             FURI_LOG_D(APP_NAME, "Checking version");
-            if (!flipper_format_read_header(file, string_value, &value)) {
+            if(!flipper_format_read_header(file, string_value, &value)) {
                 FURI_LOG_E(APP_NAME, "Config file mismatch");
             } else {
                 FURI_LOG_D(APP_NAME, "Loading %s", CONF_ANIMATION_DURATION);
-                if (flipper_format_read_uint32(file, CONF_ANIMATION_DURATION, &value, 1)) {
+                if(flipper_format_read_uint32(file, CONF_ANIMATION_DURATION, &value, 1)) {
                     settings.animation_duration = value;
                     FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ANIMATION_DURATION, value);
                 }
                 FURI_LOG_D(APP_NAME, "Loading %s", CONF_MESSAGE_DURATION);
-                if (flipper_format_read_uint32(file, CONF_MESSAGE_DURATION, &value, 1)) {
+                if(flipper_format_read_uint32(file, CONF_MESSAGE_DURATION, &value, 1)) {
                     settings.message_duration = value;
                     FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_MESSAGE_DURATION, value);
                 }
                 FURI_LOG_D(APP_NAME, "Loading %s", CONF_STARTING_MONEY);
-                if (flipper_format_read_uint32(file, CONF_STARTING_MONEY, &value, 1)) {
+                if(flipper_format_read_uint32(file, CONF_STARTING_MONEY, &value, 1)) {
                     settings.starting_money = value;
                     FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_STARTING_MONEY, value);
                 }
                 FURI_LOG_D(APP_NAME, "Loading %s", CONF_ROUND_PRICE);
-                if (flipper_format_read_uint32(file, CONF_ROUND_PRICE, &value, 1)) {
+                if(flipper_format_read_uint32(file, CONF_ROUND_PRICE, &value, 1)) {
                     settings.round_price = value;
                     FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ROUND_PRICE, value);
                 }
                 FURI_LOG_D(APP_NAME, "Loading %s", CONF_SOUND_EFFECTS);
-                if (flipper_format_read_bool(file, CONF_SOUND_EFFECTS, &valueBool, 1)) {
+                if(flipper_format_read_bool(file, CONF_SOUND_EFFECTS, &valueBool, 1)) {
                     settings.sound_effects = valueBool;
-                    FURI_LOG_D(APP_NAME, "Loaded %s: %i", CONF_ROUND_PRICE, valueBool?1:0);
+                    FURI_LOG_D(APP_NAME, "Loaded %s: %i", CONF_ROUND_PRICE, valueBool ? 1 : 0);
                 }
             }
             flipper_format_file_close(file);
@@ -116,7 +116,7 @@ Settings load_settings() {
     }
 
     furi_string_free(string_value);
-//        flipper_format_file_close(file);
+    //        flipper_format_file_close(file);
     flipper_format_free(file);
     furi_record_close(RECORD_STORAGE);
     return settings;