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

C++ rewrite started

* Removed tilemap, assets are now one per image
* New renderer that should be supported by ufbt
* New and improved utility classes extracted from Flipper Game Engine
* Removed old common folder
* Better class, method and variable names
* Hopefully more readable code
Tibor Tálosi 2 лет назад
Родитель
Сommit
3face7e2ef
50 измененных файлов с 1783 добавлено и 575 удалено
  1. 0 3
      .gitmodules
  2. 76 0
      Deck.cpp
  3. 18 0
      Deck.h
  4. 80 0
      Game.cpp
  5. 45 0
      Game.h
  6. 2 2
      README.md
  7. 101 0
      TableauColumn.cpp
  8. 19 0
      TableauColumn.h
  9. 3 4
      application.fam
  10. 324 0
      assets.h
  11. BIN
      assets/10.png
  12. BIN
      assets/2.png
  13. BIN
      assets/3.png
  14. BIN
      assets/4.png
  15. BIN
      assets/5.png
  16. BIN
      assets/6.png
  17. BIN
      assets/7.png
  18. BIN
      assets/8.png
  19. BIN
      assets/9.png
  20. BIN
      assets/A.png
  21. BIN
      assets/J.png
  22. BIN
      assets/K.png
  23. BIN
      assets/Q.png
  24. BIN
      assets/card_graphics.png
  25. BIN
      assets/clubs.png
  26. BIN
      assets/diamonds.png
  27. BIN
      assets/hearths.png
  28. BIN
      assets/logo.png
  29. BIN
      assets/main_image.png
  30. BIN
      assets/pattern_big.png
  31. BIN
      assets/pattern_small.png
  32. BIN
      assets/solitaire_main.png
  33. BIN
      assets/spades.png
  34. BIN
      assets/start.png
  35. 0 1
      common
  36. 4 10
      defines.h
  37. 0 555
      solitaire.c
  38. 139 0
      solitaire.cpp
  39. 80 0
      utils/Buffer.cpp
  40. 41 0
      utils/Buffer.h
  41. 69 0
      utils/Card.cpp
  42. 17 0
      utils/Card.h
  43. 33 0
      utils/Helpers.cpp
  44. 43 0
      utils/Helpers.h
  45. 284 0
      utils/List.h
  46. 143 0
      utils/RenderBuffer.cpp
  47. 29 0
      utils/RenderBuffer.h
  48. 20 0
      utils/Sprite.cpp
  49. 30 0
      utils/Sprite.h
  50. 183 0
      utils/Vector.h

+ 0 - 3
.gitmodules

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

+ 76 - 0
Deck.cpp

@@ -0,0 +1,76 @@
+#include "Deck.h"
+#include "utils/Buffer.h"
+#include "utils/RenderBuffer.h"
+
+Deck::Deck(uint8_t count) : deck_count(count) {
+
+}
+
+void Deck::Generate() {
+    waste_pile.empty();
+    stock_pile.empty();
+    //generate and shuffle deck
+    uint8_t cards[52];
+    for (int i = 0; i < 52; i++) cards[i] = i;
+    srand(DWT->CYCCNT);
+    for (int i = 0; i < 52; i++) {
+        int r = i + (rand() % (52 - i));
+        uint8_t card = cards[i];
+        cards[i] = cards[r];
+        cards[r] = card;
+    }
+
+    //Init deck list
+    for (int i = 0; i < 52; i++) {
+        int letter = cards[i] % 13;
+        int suit = cards[i] / 13;
+        stock_pile.add(new Card(suit, letter));
+    }
+}
+
+void Deck::Cycle() {
+    if (stock_pile.count > 0) {
+        auto *c = stock_pile.pop();
+        c->exposed= true;
+        waste_pile.add(c);
+    }
+    else {
+        while (waste_pile.count > 0) {
+            auto *c = waste_pile.pop();
+            c->exposed= false;
+            stock_pile.add(c);
+        }
+    }
+}
+
+Card *Deck::GetLastWaste() {
+    return waste_pile.pop();
+}
+
+void Deck::AddToWaste(Card *c) {
+    waste_pile.add(c);
+}
+
+Card *Deck::Extract() {
+    return stock_pile.pop();
+}
+
+void Deck::Render(RenderBuffer *buffer) {
+    if (stock_pile.count == 0)
+        Card::RenderEmptyCard(1, 1, buffer);
+    else {
+        if(stock_pile.count>1) {
+            buffer->draw_rbox_frame(1, 1, 17, 23, Black);
+            stock_pile.last()->Render(0, 0, false, buffer);
+        }else{
+            stock_pile.last()->Render(1, 1, false, buffer);
+        }
+    }
+
+    if (waste_pile.count == 0) {
+        FURI_LOG_D("DECK", "Rendering empty card");
+        Card::RenderEmptyCard(19, 1, buffer);
+    }
+    else
+        waste_pile.last()->Render(19, 1, false, buffer);
+}

+ 18 - 0
Deck.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include "utils/Card.h"
+#include "utils/List.h"
+
+class Deck {
+    List<Card> stock_pile;
+    List<Card> waste_pile;
+    uint8_t deck_count;
+public:
+    explicit Deck(uint8_t count);
+    void Generate();
+    void Render(RenderBuffer *buffer);
+    void Cycle();
+    Card* GetLastWaste();
+    Card* Extract();
+    void AddToWaste(Card* c);
+};

+ 80 - 0
Game.cpp

@@ -0,0 +1,80 @@
+#include "Game.h"
+
+Game::~Game() {
+    delete deck;
+}
+
+void Game::Render(Canvas *const canvas) {
+    if (!buffer) return;
+    if (had_change) {
+        deck->Render(buffer);
+        for (int i = 0; i < 7; i++) {
+            FURI_LOG_D("Game", "%i", i);
+            tableau[i].Render(i * 18 + 1, 25, false, 0, buffer);
+        }
+        for (int i = 0; i < 4; i++) {
+            if (piles[i])
+                piles[i]->Render(i * 18 + 55, 1, false, buffer);
+            else
+                Card::RenderEmptyCard(i * 18 + 55, 1, buffer);
+        }
+
+    }
+    had_change = false;
+    buffer->render(canvas);
+}
+
+void Game::Press(InputKey key) {
+    if (key == InputKeyOk && state == GameStateStart) {
+        Reset();
+        NewRound();
+        return;
+    }
+    had_change = true;
+
+}
+
+Game::Game() {
+    deck = new Deck(1);
+    buffer = new RenderBuffer(128, 64);
+}
+
+void Game::NewRound() {
+    round_start = furi_get_tick();
+    state = GameStatePlay;
+    had_change = true;
+}
+
+void Game::Reset() {
+    can_auto_solve = false;
+    current_column = 0;
+    current_row = 0;
+    column_selection = -1;
+
+    deck->Generate();
+    deck->Cycle();
+
+    //Reset foundations
+    for (int i = 0; i < 4; i++) {
+        if (piles[i]) delete piles[i];
+        piles[i] = nullptr;
+    }
+
+    //Populate columns
+    for (int i = 0; i < 7; i++) {
+        tableau[i].Reset();
+        for (int j = 0; j <= i; j++) {
+            Card *card = deck->Extract();
+            if (j >= i) card->exposed = true;
+            tableau[i].AddCard(card);
+        }
+    }
+}
+
+void Game::LongPress(InputKey key) {
+    UNUSED(key);
+}
+
+void Game::Update(NotificationApp *app) {
+    UNUSED(app);
+}

+ 45 - 0
Game.h

@@ -0,0 +1,45 @@
+#pragma once
+
+
+#include <gui/canvas.h>
+#include <notification/notification.h>
+#include "utils/List.h"
+#include "utils/Card.h"
+#include "utils/RenderBuffer.h"
+#include "Deck.h"
+#include "TableauColumn.h"
+
+typedef enum {
+    GameStateGameOver, GameStateStart, GameStatePlay, GameStateAnimate
+} PlayState;
+
+class Game {
+    Deck *deck;
+
+    Card *piles[4];
+    TableauColumn tableau[7];
+
+    List<Card> hand;
+
+    PlayState state = GameStateStart;
+    uint8_t current_row;
+    uint8_t current_column;
+    int8_t column_selection;
+    RenderBuffer *buffer;
+    uint32_t round_start;
+    bool can_auto_solve;
+    bool had_change = true;
+
+public:
+    Game();
+    void Render(Canvas* const canvas);
+
+    void Reset();
+    void NewRound();
+
+    void LongPress(InputKey key);
+    void Press(InputKey key);
+    void Update(NotificationApp *app);
+
+    ~Game();
+};

+ 2 - 2
README.md

@@ -5,9 +5,9 @@
 ![contributions - welcome](https://img.shields.io/badge/contributions-welcome-blue)
 # Solitaire game for Flipper Zero
 
-![Play screen](screenshots/solitaire.png)
+![Play buffer](screenshots/solitaire.png)
 
-![Play screen](screenshots/solitaire.gif)
+![Play buffer](screenshots/solitaire.gif)
 
 ### Shortcuts
 * Long press up skips the navigation inside the bottom column

+ 101 - 0
TableauColumn.cpp

@@ -0,0 +1,101 @@
+#include "TableauColumn.h"
+#include "utils/RenderBuffer.h"
+
+#define max(a, b) a>b?a:b
+
+void TableauColumn::Reset() {
+    cards->empty();
+}
+
+void TableauColumn::AddCard(Card *c) {
+    cards->add(c);
+}
+
+void TableauColumn::AddRange(List<Card> *hand) {
+    for (auto *item: *hand) {
+        cards->add(item);
+    }
+}
+
+void TableauColumn::Render(uint8_t x, uint8_t y, bool selected, uint8_t selection_from_end, RenderBuffer *buffer) {
+    // If the hand is empty
+    FURI_LOG_D("Tableau", "%i %i, count %li", x, y, cards->count);
+    if (cards->count == 0) {
+        Card::RenderEmptyCard(x, y, buffer);
+        if (selected)
+            buffer->draw_rbox(x, y, x + 16, y + 22, Flip);
+        return;
+    }
+    uint8_t selection = cards->count - selection_from_end;
+
+    uint8_t loop_end = cards->count;
+    uint8_t loop_start = max(loop_end - 4, 0);
+    uint8_t position = 0;
+    uint8_t first_non_flipped = FirstNonFlipped();
+    bool had_top = false;
+
+    // Draw the first flipped and non-flipped card with adjusted visibility
+    if (first_non_flipped <= loop_start && selection != first_non_flipped) {
+        // Draw a card back if it is not the first card
+        if (first_non_flipped > 0) {
+            Card::RenderBack(x,y+position, selection == first_non_flipped, buffer);
+            // Increment loop start index and position
+            position += 4;
+            loop_start++;
+            had_top=true;
+        }
+        // Draw the front side of the first non-flipped card
+        (*cards)[first_non_flipped]->Render(x, y + position, selection == first_non_flipped, buffer);
+        position += 8;
+        loop_start++; // Increment loop start index
+    }
+
+    // Draw the selected card with adjusted visibility
+    if (loop_start > selection) {
+        if(!had_top && first_non_flipped>0){
+            Card::RenderBack(x,y+position, selection == first_non_flipped, buffer);
+            position+=2;
+            loop_start++;
+        }
+        // Draw the front side of the selected card
+        (*cards)[selection]->Render(x, y + position, true, buffer);
+        position += 8;
+        loop_start++; // Increment loop start index
+    }
+
+    //Draw the rest
+    for (uint8_t i = loop_start; i < loop_end; i++, position += 4) {
+        (*cards)[i]->Render(x, y + position, i == selection, buffer);
+
+        if (i == selection || i == first_non_flipped) position += 4;
+    }
+}
+
+int8_t TableauColumn::FirstNonFlipped() {
+    int8_t index = -1;
+    if (cards->count > 0) {
+/*        ListItem<Card> *c = cards->start;
+        while (c){
+            index++;
+            if (c->data->exposed) {
+                break;
+            }
+            c=c->next;
+        }*/
+        for (auto *card: *cards) {
+            index++;
+            if (card && card->exposed) {
+                break;
+            }
+        }
+    }
+    return index;
+}
+
+TableauColumn::TableauColumn() {
+    cards = new List<Card>();
+}
+
+TableauColumn::~TableauColumn() {
+    delete cards;
+}

+ 19 - 0
TableauColumn.h

@@ -0,0 +1,19 @@
+#pragma once
+
+
+#include "utils/Card.h"
+#include "utils/List.h"
+
+class TableauColumn {
+    List<Card> *cards;
+
+public:
+    TableauColumn();
+    ~TableauColumn();
+    void Reset();
+    void AddCard(Card *c);
+    void AddRange(List<Card> *hand);
+    void Render(uint8_t x, uint8_t y, bool selected, uint8_t selection, RenderBuffer *buffer);
+
+    int8_t FirstNonFlipped();
+};

+ 3 - 4
application.fam

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

+ 324 - 0
assets.h

@@ -0,0 +1,324 @@
+#pragma once
+#include "utils/Sprite.h"
+/*
+      ██████   
+   ███   ███   
+███      ███   
+████████████   
+         ███   
+*/
+const SpriteData *sprite_4 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0xc, 0xa, 0x9, 0x1f, 0x0
+}};
+/*
+████████████   
+         ███   
+      ███      
+   ███         
+   ███         
+*/
+const SpriteData *sprite_7 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x1, 0x19, 0x5, 0x3, 0x0
+}};
+/*
+   ███         ███   
+███   ███   ███   ███
+███      ███      ███
+███               ███
+   ███         ███   
+      ███   ███      
+         ███         
+*/
+const SpriteData *sprite_hearths = new SpriteData {.width=7, .height=7, .data=new uint8_t[7] {
+		0xe, 0x11, 0x22, 0x44, 0x22, 0x11, 0xe
+}};
+/*
+   ██████      
+███      ███   
+████████████   
+███      ███   
+███      ███   
+*/
+const SpriteData *sprite_A = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x1e, 0x5, 0x5, 0x1e, 0x0
+}};
+/*
+                     
+      ███   ███   ███
+   ███   ███   ███   
+      ███   ███   ███
+   ███   ███   ███   
+      ███   ███   ███
+   ███   ███   ███   
+*/
+const SpriteData *sprite_pattern_small = new SpriteData {.width=7, .height=7, .data=new uint8_t[7] {
+		0x0, 0x54, 0x2a, 0x54, 0x2a, 0x54, 0x2a
+}};
+/*
+                  ███                        
+   ███      ████████████                     
+   ████████████████████████      ███         
+   ████████████████████████   ███   ███      
+   ███      ████████████         ███         
+                                             
+         ███            ███                  
+      ███   ███      ████████████      ███   
+         ███      ████████████████████████   
+                  ████████████████████████   
+                     ████████████      ███   
+                                             
+                  ███            ███         
+   ███      ████████████      ███   ███      
+   ████████████████████████      ███         
+   ████████████████████████            ███   
+   ███      ████████████            ███      
+                                    ███      
+         ███         ███            ███      
+      ███            ███   ███      ███      
+      ███         ███      ███   ███         
+*/
+const SpriteData *sprite_pattern_big = new SpriteData {.width=15, .height=21, .data=new uint8_t[45] {
+		0x0, 0x1e, 0x8c, 0x4c, 0x9e, 0x1e, 0x1f, 0x9e, 0xcc, 0x80, 0x88, 0x14, 0x8, 0x80, 0x0, 
+		0x0, 0xe0, 0xc0, 0xc1, 0xe0, 0xe0, 0xf3, 0xe7, 0xc7, 0x7, 0x27, 0x53, 0x23, 0x87, 0x0, 
+		0x0, 0x1, 0x18, 0x4, 0x1, 0x1, 0x11, 0xd, 0x0, 0x18, 0x0, 0x10, 0xf, 0x0, 0x0
+}};
+/*
+                                                   █████████████████████████████████████████████   
+                                                ███                                             ███
+                                             █████████████████████████████████████████████      ███
+                                          ███                                             ███   ███
+                                       █████████████████████████████████████████████      ███   ███
+                                    ███                                             ███   ███   ███
+                                    █████████████████████████████████████████████   ███   ███   ███
+                                 ███                                             ██████   ███   ███
+                                 █████████████████████████████████████████████   ██████   ███   ███
+                              ███                                             █████████   ███   ███
+                              ███   ███      ███               ███            █████████   ███   ███
+                              █████████████████████████████████████████████   █████████   ███   ███
+   █████████████████████████████████████████████                           ████████████   ███   ███
+███                                             ███         ███            ████████████   ███   ███
+███   ███      ███               ███            ███████████████████████████   █████████   ███   ███
+███   ███   ███   ███         █████████         ███                        ████████████   ███   ███
+███   ███   ███   ███      ███████████████      ███         ███            ████████████   ███   ███
+███   ███   ███   ███   █████████████████████   ████████████████████████   ████████████   ███   ███
+███   ███   ███   ███      ███████████████      ██████                  ███████████████   ███   ███
+███   ███      ███            █████████         ████████████            ███████████████   ███   ███
+███                              ███            ██████      ████████████   ████████████   ███   ███
+███                                             ██████      ███         ███████████████   ██████   
+███                                             ██████      ██████      ███████████████   ███      
+███                                             ██████      ███   ███   ██████████████████         
+███                                             █████████   ███   ███   ███████████████            
+███            ███                              ██████      ███   ███   ████████████               
+███         █████████            ███      ███   ██████      ███   ███   ████████████               
+███      ███████████████      ███   ███   ███   ██████      ███   ███   █████████                  
+███   █████████████████████   ███   ███   ███   ██████      ███   ███   █████████                  
+███      ███████████████      ███   ███   ███   ██████      ███   ███   ██████                     
+███         █████████         ███   ███   ███   ██████      ███   ███   ██████                     
+███            ███               ███      ███   ██████      ███   ███   ██████                     
+███                                             ██████      ███   ███   ██████                     
+   █████████████████████████████████████████████   ██████   ███   ███   ██████                     
+   ███         █████████         ███   ███   ███   ██████   ███   ███   ██████                     
+   ███            ███               ███      ███   ██████   ███   ███   ███                        
+   ███                                             ██████   ███   ███   ███                        
+      █████████████████████████████████████████████   ███   ███   ███   ███                        
+            ███            ███               ███      ███   ███   ███   ███                        
+            ███                                             ███   ███   ███                        
+               █████████████████████████████████████████████      ███   ███                        
+                  ███            ███               ███      ███   ██████                           
+                  ███                                             ███                              
+                     █████████████████████████████████████████████                                 
+*/
+const SpriteData *sprite_main_image = new SpriteData {.width=33, .height=44, .data=new uint8_t[198] {
+		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x60, 0x50, 0x58, 0x54, 0x56, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x95, 0xe5, 0x5, 0xf9, 0x1, 0xfe, 
+		0xe0, 0x10, 0xd0, 0x10, 0x90, 0x50, 0x90, 0x10, 0x10, 0x10, 0x9e, 0xd9, 0x9d, 0x19, 0x19, 0x1d, 0xe9, 0x49, 0x49, 0x49, 0x69, 0x4d, 0x49, 0x49, 0x49, 0xb1, 0xfe, 0xff, 0xff, 0x0, 0xff, 0x0, 0xff, 
+		0xff, 0x0, 0xf, 0x0, 0x7, 0x8, 0x7, 0x0, 0x2, 0x7, 0xf, 0x1f, 0xf, 0x7, 0x2, 0x0, 0xff, 0xfe, 0xa, 0xa, 0xf3, 0x52, 0x92, 0x12, 0xec, 0xff, 0xff, 0xff, 0xff, 0x80, 0x7f, 0x20, 0x1f, 
+		0xff, 0x0, 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x0, 0x78, 0x84, 0x78, 0x0, 0xfc, 0x0, 0xff, 0xff, 0x1, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0xff, 0x1f, 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 
+		0x1, 0x1e, 0x22, 0x22, 0xe2, 0x26, 0x2e, 0x26, 0x22, 0x62, 0x22, 0x26, 0x2a, 0x26, 0x22, 0x6e, 0x21, 0x1f, 0x7e, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
+		0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7, 0x9, 0x9, 0x9, 0x9, 0xb, 0x9, 0x9, 0x9, 0x9, 0x9, 0xb, 0x9, 0x9, 0xa, 0x8, 0x7, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
+}};
+/*
+   ██████      
+███      ███   
+███      ███   
+███   ███      
+   ███   ███   
+*/
+const SpriteData *sprite_Q = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0xe, 0x11, 0x9, 0x16, 0x0
+}};
+/*
+   ██████      
+███      ███   
+      ███      
+   ███         
+████████████   
+*/
+const SpriteData *sprite_2 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x12, 0x19, 0x15, 0x12, 0x0
+}};
+/*
+█████████      
+         ███   
+   ██████      
+         ███   
+█████████      
+*/
+const SpriteData *sprite_3 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x11, 0x15, 0x15, 0xa, 0x0
+}};
+/*
+███      ███   
+███   ███   ███
+███   ███   ███
+███   ███   ███
+███      ███   
+*/
+const SpriteData *sprite_10 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x1f, 0x0, 0xe, 0x11, 0xe
+}};
+/*
+███      ███   
+███   ███      
+██████         
+███   ███      
+███      ███   
+*/
+const SpriteData *sprite_K = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x1f, 0x4, 0xa, 0x11, 0x0
+}};
+/*
+████████████   
+███            
+█████████      
+         ███   
+████████████   
+*/
+const SpriteData *sprite_5 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x17, 0x15, 0x15, 0x19, 0x0
+}};
+/*
+   ██████      
+███            
+█████████      
+███      ███   
+   ██████      
+*/
+const SpriteData *sprite_6 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0xe, 0x15, 0x15, 0x8, 0x0
+}};
+/*
+   ██████      
+███      ███   
+   █████████   
+         ███   
+   ██████      
+*/
+const SpriteData *sprite_9 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x2, 0x15, 0x15, 0xe, 0x0
+}};
+/*
+         ███         
+      ███   ███      
+   ███         ███   
+███               ███
+   ███         ███   
+      ███   ███      
+         ███         
+*/
+const SpriteData *sprite_diamonds = new SpriteData {.width=7, .height=7, .data=new uint8_t[7] {
+		0x8, 0x14, 0x22, 0x41, 0x22, 0x14, 0x8
+}};
+/*
+████████████   
+███      ███   
+████████████   
+███      ███   
+████████████   
+*/
+const SpriteData *sprite_8 = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x1f, 0x15, 0x15, 0x1f, 0x0
+}};
+/*
+         ███   
+         ███   
+         ███   
+███      ███   
+   ██████      
+*/
+const SpriteData *sprite_J = new SpriteData {.width=5, .height=5, .data=new uint8_t[5] {
+		0x8, 0x10, 0x10, 0xf, 0x0
+}};
+/*
+         ███         
+      █████████      
+   ███████████████   
+█████████████████████
+██████   ███   ██████
+         ███         
+      █████████      
+*/
+const SpriteData *sprite_spades = new SpriteData {.width=7, .height=7, .data=new uint8_t[7] {
+		0x18, 0x1c, 0x4e, 0x7f, 0x4e, 0x1c, 0x18
+}};
+/*
+      ████████████               ████████████      ███                                    ███   
+   ██████████████████         ███            ███   ███                                    ███   
+████████████████████████      ███               █████████      █████████      ███   ████████████
+████████████████████████         ██████            ███      ███         ███   ██████      ███   
+████████████████████████               ██████      ███         ████████████   ███         ███   
+████████████████████████                     ███   ███      ███         ███   ███         ███   
+   ██████████████████         ███            ███   ███      ███      ██████   ███         ███   
+      ████████████               ████████████      ██████   █████████   ███   ███         ██████
+*/
+const SpriteData *sprite_start = new SpriteData {.width=32, .height=8, .data=new uint8_t[32] {
+		0x3c, 0x7e, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x3c, 0x0, 0x0, 0x46, 0x89, 0x89, 0x91, 0x91, 0x62, 0x4, 0xff, 0x84, 0x0, 0xe8, 0x94, 0x94, 0x54, 0xf8, 0x0, 0xfc, 0x8, 0x4, 0x4, 0xff, 0x84
+}};
+/*
+      █████████      
+      █████████      
+██████   ███   ██████
+█████████████████████
+██████   ███   ██████
+         ███         
+      █████████      
+*/
+const SpriteData *sprite_clubs = new SpriteData {.width=7, .height=7, .data=new uint8_t[7] {
+		0x1c, 0x1c, 0x4b, 0x7f, 0x4b, 0x1c, 0x1c
+}};
+/*
+██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+███                                                                                                                                                                                                            ███
+███                                                                                                                                                                                                            ███
+███               ███████████████                                       ██████         ██████            ███                                    ██████                                                         ███
+███            █████████████████████                                    ██████         ██████         ██████                                    ██████                                                         ███
+███         █████████         █████████                                 ██████         ██████         ██████                                    ██████                                                         ███
+███         ██████               ██████                                 ██████                        ██████                                                                                                   ███
+███         ██████                              ████████████            ██████         ██████      ███████████████      ███████████████         ██████         ██████   ██████         █████████               ███
+███         █████████                        ██████████████████         ██████         ██████      ███████████████      ██████████████████      ██████         ███████████████      ███████████████            ███
+███            ███████████████            █████████      █████████      ██████         ██████         ██████         ██████         ██████      ██████         ██████            ██████         ██████         ███
+███                  ███████████████      ██████            ██████      ██████         ██████         ██████                        ██████      ██████         ██████            ██████         ██████         ███
+███                           █████████   ██████            ██████      ██████         ██████         ██████                  ████████████      ██████         ██████            █████████████████████         ███
+███         ██████               ██████   ██████            ██████      ██████         ██████         ██████            ██████████████████      ██████         ██████            █████████████████████         ███
+███         ██████               ██████   ██████            ██████      ██████         ██████         ██████         █████████      ██████      ██████         ██████            ██████                        ███
+███         █████████         █████████   █████████      █████████      ██████         ██████         ██████         ██████         ██████      ██████         ██████            ██████         ██████         ███
+███            █████████████████████         ██████████████████         ██████         ██████         ████████████   █████████████████████      ██████         ██████               ███████████████            ███
+███               ███████████████               ████████████            ██████         ██████            █████████      █████████   ██████      ██████         ██████                  █████████               ███
+███                                                                                                                                                                                                            ███
+███                                                                                                                                                                                                            ███
+██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+                                                                                                      ███                                                                                          ███            
+                                                                                                      ███                                                                        ███               ███            
+                                                                                                      ███   ███                        █████████                              ███                  ███            
+                                                                                                      ███   ██████      ███   ███      ███      ███      ███         ███   █████████   ███   ███   ███            
+                                                                                                      ███   ███   ███   ███   ███      ███      ███   ███   ███   ███   ███   ███      ███   ███   ███            
+                                                                                                      ███   ███   ███      ███         ███      ███   ███   ███   ███   ███   ███         ███      ███            
+                                                                                                      ███   ██████         ███         █████████         ███         ███      ███         ███      ███            
+                                                                                                      ███                                                                                          ███            
+                                                                                                      ████████████████████████████████████████████████████████████████████████████████████████████████            
+*/
+const SpriteData *sprite_logo = new SpriteData {.width=70, .height=29, .data=new uint8_t[280] {
+		0xff, 0x1, 0x1, 0x1, 0xe1, 0xf1, 0x39, 0x19, 0x19, 0x19, 0x39, 0x71, 0x61, 0x1, 0x1, 0x1, 0x81, 0x81, 0x81, 0x81, 0x1, 0x1, 0x1, 0x1, 0xf9, 0xf9, 0x1, 0x1, 0x1, 0xb9, 0xb9, 0x1, 0x1, 0x81, 0xf1, 0xf9, 0x81, 0x81, 0x1, 0x1, 0x81, 0x81, 0x81, 0x81, 0x81, 0x1, 0x1, 0x1, 0xb9, 0xb9, 0x1, 0x1, 0x1, 0x81, 0x81, 0x1, 0x81, 0x81, 0x1, 0x1, 0x1, 0x81, 0x81, 0x81, 0x1, 0x1, 0x1, 0x1, 0x1, 0xff, 
+		0xff, 0x0, 0x0, 0x0, 0x71, 0xf3, 0xc3, 0x86, 0x86, 0x86, 0xcc, 0xfc, 0x78, 0x0, 0x7e, 0xff, 0xc3, 0x81, 0x81, 0xc3, 0xff, 0x7e, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x1, 0xff, 0xff, 0x81, 0x81, 0x0, 0xe2, 0xf3, 0xb1, 0x99, 0x99, 0xff, 0xff, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff, 0xff, 0x1, 0x1, 0x1, 0x0, 0x7e, 0xff, 0x99, 0x99, 0x99, 0xdf, 0x5e, 0x0, 0x0, 0x0, 0xff, 
+		0xf, 0x8, 0x8, 0x8, 0x8, 0x8, 0x9, 0x9, 0x9, 0x9, 0x9, 0x8, 0x8, 0x8, 0x8, 0x8, 0x9, 0x9, 0x9, 0x9, 0x8, 0x8, 0x8, 0x8, 0x9, 0x9, 0x8, 0x8, 0x8, 0x9, 0x9, 0x8, 0x8, 0x8, 0xf8, 0x9, 0xc9, 0x89, 0x8, 0x8, 0x89, 0x9, 0x89, 0x8, 0x9, 0xc9, 0x48, 0x48, 0x89, 0x9, 0x8, 0x88, 0x8, 0x9, 0x9, 0x88, 0x8, 0x88, 0xc8, 0xa8, 0x8, 0x89, 0x9, 0x89, 0x8, 0xf8, 0x8, 0x8, 0x8, 0xf, 
+		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0x10, 0x17, 0x14, 0x13, 0x10, 0x11, 0x16, 0x11, 0x10, 0x10, 0x17, 0x14, 0x14, 0x13, 0x10, 0x13, 0x14, 0x13, 0x10, 0x13, 0x14, 0x13, 0x10, 0x17, 0x10, 0x10, 0x11, 0x16, 0x11, 0x10, 0x1f, 0x0, 0x0, 0x0, 0x0
+}};














BIN
assets/card_graphics.png


BIN
assets/clubs.png


BIN
assets/diamonds.png


BIN
assets/hearths.png


BIN
assets/logo.png


BIN
assets/main_image.png


BIN
assets/pattern_big.png


BIN
assets/pattern_small.png


BIN
assets/solitaire_main.png


BIN
assets/spades.png


BIN
assets/start.png


+ 0 - 1
common

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

+ 4 - 10
defines.h

@@ -19,15 +19,9 @@ typedef struct {
     InputEvent input;
 } AppEvent;
 
-typedef enum {
-    GameStateGameOver,
-    GameStateStart,
-    GameStatePlay,
-    GameStateAnimate
-} PlayState;
-
+typedef enum { GameStateGameOver, GameStateStart, GameStatePlay, GameStateAnimate } PlayState;
 typedef struct {
-    uint8_t *buffer;
+    uint8_t* buffer;
     Card card;
     int8_t deck;
     int indexes[4];
@@ -57,6 +51,6 @@ typedef struct {
     uint8_t selectColumn;
     int8_t selected_card;
     CardAnimation animation;
-    uint8_t *buffer;
+    uint8_t* buffer;
     FuriMutex* mutex;
-} GameState;
+} GameState;

+ 0 - 555
solitaire.c

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

+ 139 - 0
solitaire.cpp

@@ -0,0 +1,139 @@
+#include <furi.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include "Game.h"
+
+typedef enum {
+    EventTypeTick,
+    EventTypeKey,
+} EventType;
+
+typedef struct {
+    EventType type;
+    InputEvent *input;
+} AppEvent;
+
+static FuriMutex *mutex;
+
+static void input_callback(InputEvent *input_event, FuriMessageQueue *event_queue) {
+    furi_assert(event_queue);
+    AppEvent event = {.type = EventTypeKey, .input = input_event};
+    furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void update_timer_callback(FuriMessageQueue *event_queue) {
+    furi_assert(event_queue);
+    AppEvent event = {.type = EventTypeTick, .input=nullptr};
+    furi_message_queue_put(event_queue, &event, 0);
+}
+
+static void render_callback(Canvas *const canvas, void *ctx) {
+    Game *g = (Game *) ctx;
+    auto status = furi_mutex_acquire(mutex, 25);
+    if (g == nullptr || status != FuriStatusOk) return;
+    g->Render(canvas);
+    UNUSED(canvas);
+    UNUSED(g);
+    furi_mutex_release(mutex);
+}
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+int32_t solitaire_app(void *p) {
+    UNUSED(p);
+    int32_t return_code = 0;
+    Game *game = new Game;
+    FURI_LOG_D("LOAD", "START");
+    FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
+    FURI_LOG_D("LOAD", "QUEUE");
+    FURI_LOG_D("LOAD", "GAME");
+    mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if (mutex) {
+        FURI_LOG_D("LOAD", "ALLOC");
+        bool processing = true;
+        auto *notification = static_cast<NotificationApp *>(furi_record_open(RECORD_NOTIFICATION));
+
+        notification_message_block(notification, &sequence_display_backlight_enforce_on);
+
+        ViewPort *view_port = view_port_alloc();
+        view_port_draw_callback_set(view_port, render_callback, game);
+        view_port_input_callback_set(view_port, input_callback, event_queue);
+
+        FuriTimer *timer = furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
+        furi_timer_start(timer, furi_kernel_get_tick_frequency() / 30);
+
+        auto gui = static_cast<Gui *>(furi_record_open("gui"));
+        gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+        AppEvent event;
+        game->Reset();
+//        game->NewRound();
+
+        while (processing) {
+            FuriStatus event_status = furi_message_queue_get(event_queue, &event, 150);
+            furi_mutex_acquire(mutex, FuriWaitForever);
+
+            if (event_status == FuriStatusOk) {
+                if (event.type == EventTypeKey) {
+                    if (event.input->type == InputTypeLong) {
+                        switch (event.input->key) {
+                            case InputKeyUp:
+                            case InputKeyDown:
+                            case InputKeyRight:
+                            case InputKeyLeft:
+                            case InputKeyOk:
+                                game->LongPress(event.input->key);
+                                break;
+                            case InputKeyBack:
+                                processing = false;
+                                return_code = 1;
+                            default:
+                                break;
+                        }
+                    } else if (event.input->type == InputTypePress) {
+                        switch (event.input->key) {
+                            case InputKeyUp:
+                            case InputKeyDown:
+                            case InputKeyRight:
+                            case InputKeyLeft:
+                            case InputKeyOk:
+                                game->Press(event.input->key);
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                } else if (event.type == EventTypeTick) {
+                    game->Update(notification);
+                }
+            }
+
+            view_port_update(view_port);
+            furi_mutex_release(mutex);
+        }
+
+
+        notification_message_block(notification, &sequence_display_backlight_enforce_auto);
+        furi_timer_free(timer);
+        view_port_enabled_set(view_port, false);
+        gui_remove_view_port(gui, view_port);
+        furi_record_close(RECORD_GUI);
+        furi_record_close(RECORD_NOTIFICATION);
+        view_port_free(view_port);
+    } else {
+        FURI_LOG_E("APP", "cannot create mutex\r\n");
+        return_code = 255;
+    }
+
+
+    delete game;
+    furi_mutex_free(mutex);
+    furi_message_queue_free(event_queue);
+    return return_code;
+}
+
+#ifdef __cplusplus
+}
+#endif

+ 80 - 0
utils/Buffer.cpp

@@ -0,0 +1,80 @@
+#include "Buffer.h"
+#include "Sprite.h"
+
+Buffer::Buffer(uint8_t w, uint8_t h) : _width(w), _height(h) {
+    data = (uint8_t *) malloc(sizeof(uint8_t) * w * ceil(h / 8.0));
+}
+
+Buffer::Buffer(const SpriteData *i) {
+    FURI_LOG_D("BUFFER","Buffer init");
+    _width = i->width;
+    _height = i->height;//ceil(i->height / 8.0);
+    data = (i->data);
+    remove_buffer = false;
+}
+
+Buffer::~Buffer() {
+    FURI_LOG_D("BUFFER", "Buffer removed");
+
+    if (data && remove_buffer)
+        delete data;
+}
+
+bool Buffer::test_pixel(uint8_t x, uint8_t y) {
+    return data[(y >> 3) * _width + x] & (1 << (y & 7));
+}
+
+void Buffer::copy_into(uint8_t *other) {
+    int size = (int) (_width * ceil(_height / 8.0));
+    for (int i = 0; i < size; i++) {
+        other[i] = data[i];
+    }
+}
+
+void Buffer::copy_from(uint8_t *other) {
+    int size = (int) (_width * ceil(_height / 8.0));
+    for (int i = 0; i < size; i++) {
+        data[i] = other[i];
+    }
+}
+
+void Buffer::clear() {
+    int size = (int) (_width * ceil(_height / 8.0));
+    for (int i = 0; i < size; i++) {
+        data[i] = 0;
+    }
+}
+
+
+bool Buffer::test_coordinate(int x, int y) const {
+    return x >= 0 && y >= 0 && x < _width && y < _height;
+}
+
+void Buffer::set_pixel_with_check(int16_t x, int16_t y, PixelColor draw_mode) {
+    if (test_pixel(x, y))
+        set_pixel(x, y, draw_mode);
+}
+
+void Buffer::set_pixel(int16_t x, int16_t y, PixelColor draw_mode) {
+    uint8_t bit = 1 << (y & 7);
+    uint8_t *p = data + (y >> 3) * width() + x;
+
+    switch (draw_mode) {
+        case Black:
+            *p |= bit;
+            break;
+        case White:
+            *p &= ~bit;
+            break;
+        case Flip:
+            *p ^= bit;
+            break;
+    }
+}
+
+void Buffer::swap(uint8_t *&buffer) {
+    uint8_t *back = data;
+    data = buffer;
+    buffer = back;
+}
+

+ 41 - 0
utils/Buffer.h

@@ -0,0 +1,41 @@
+#pragma once
+#include <furi.h>
+
+enum PixelColor {
+    Black, //or
+    White, //
+    Flip   //not
+};
+enum DrawMode {
+    WhiteOnly,
+    BlackOnly,
+    WhiteAsBlack,
+    BlackAsWhite,
+    WhiteAsInverted,
+    BlackAsInverted,
+};
+
+struct SpriteData;
+class Buffer {
+    uint8_t _width=0, _height=0;
+    bool remove_buffer=true;
+public:
+    uint8_t *data;
+    explicit Buffer(const SpriteData *icon);
+    Buffer(uint8_t width, uint8_t height);
+    virtual ~Buffer();
+
+    bool test_pixel(uint8_t x, uint8_t y);
+
+    void copy_into(uint8_t *other);
+
+    void clear();
+
+    bool test_coordinate(int x, int y) const;
+    void set_pixel(int16_t x, int16_t y, PixelColor draw_mode);
+    void set_pixel_with_check(int16_t x, int16_t y, PixelColor draw_mode);
+    void copy_from(uint8_t *other);
+    uint8_t width() { return _width; }
+    uint8_t height() { return _height; }
+    virtual void swap(uint8_t *&buffer);
+};

+ 69 - 0
utils/Card.cpp

@@ -0,0 +1,69 @@
+#include "Card.h"
+#include "RenderBuffer.h"
+#include "../assets.h"
+
+#define CARD_HEIGHT 23
+#define CARD_HALF_HEIGHT 11
+#define CARD_WIDTH 17
+#define CARD_HALF_WIDTH 8
+
+static Sprite letters[] = {
+        Sprite(sprite_2, BlackOnly),
+        Sprite(sprite_3, BlackOnly),
+        Sprite(sprite_4, BlackOnly),
+        Sprite(sprite_5, BlackOnly),
+        Sprite(sprite_6, BlackOnly),
+        Sprite(sprite_7, BlackOnly),
+        Sprite(sprite_8, BlackOnly),
+        Sprite(sprite_9, BlackOnly),
+        Sprite(sprite_10, BlackOnly),
+        Sprite(sprite_J, BlackOnly),
+        Sprite(sprite_Q, BlackOnly),
+        Sprite(sprite_K, BlackOnly),
+        Sprite(sprite_A, BlackOnly),
+};
+
+static Sprite suits[] = {
+        Sprite(sprite_hearths, BlackOnly),
+        Sprite(sprite_diamonds, BlackOnly),
+        Sprite(sprite_spades, BlackOnly),
+        Sprite(sprite_clubs, BlackOnly)
+};
+
+static Sprite backSide = Sprite(sprite_pattern_big, BlackOnly);
+
+void Card::Render(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer) {
+
+    if (exposed) {
+        buffer->draw_rbox(x, y, x + 16, y + 22, White);
+        buffer->draw_rbox_frame(x, y, x + 16, y + 22, Black);
+//        buffer->draw_line(x+15, y, x+15, y+22, White);
+
+            buffer->draw(&(letters[value]), (Vector) {(float) x + 5, (float) y + 6}, 0);
+           buffer->draw(&(suits[suit]), (Vector) {(float) x + 12, (float) y + 6}, 0);
+
+          buffer->draw(&(letters[value]), (Vector) {(float) x + 12, (float) y + 17}, M_PI);
+           buffer->draw(&(suits[suit]), (Vector) {(float) x + 5, (float) y + 17}, M_PI);
+        if (selected) {
+            buffer->draw_rbox(x, y, x + 17, y + 23, Flip);
+        }
+    } else {
+        RenderBack(x, y, selected, buffer);
+//        buffer->draw(&backSide, (Vector) {(float) x + 8, (float) y + 11}, 0);
+    }
+
+}
+
+void Card::RenderEmptyCard(uint8_t x, uint8_t y, RenderBuffer *buffer) {
+    buffer->draw_rbox(x, y, x + 17, y + 23, Flip);
+    buffer->draw_rbox_frame(x + 2, y + 2, x + 14, y + 20, Flip);
+}
+
+void Card::RenderBack(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer) {
+    buffer->draw_rbox(x + 1, y + 1, x + 16, y + 22, White);
+    buffer->draw_rbox_frame(x, y, x + 16, y + 22, Black);
+    buffer->draw(&backSide, (Vector) {(float) x + 9, (float) y + 12}, 0);
+    if (selected) {
+        buffer->draw_rbox(x, y, x + 17, y + 23, Flip);
+    }
+}

+ 17 - 0
utils/Card.h

@@ -0,0 +1,17 @@
+#pragma once
+
+#include <furi.h>
+
+class RenderBuffer;
+
+struct Card {
+    uint8_t suit = 0;
+    uint8_t value = 0;
+    bool exposed = false;
+
+    Card(uint8_t s, uint8_t v) : suit(s), value(v) {}
+
+    void Render(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer);
+    static void RenderEmptyCard(uint8_t x, uint8_t y, RenderBuffer *buffer);
+    static void RenderBack(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer);
+};

+ 33 - 0
utils/Helpers.cpp

@@ -0,0 +1,33 @@
+#include "Helpers.h"
+
+char *basename(const char *path) {
+    const char *base = path;
+    while (*path) {
+        if (*path++ == '/') {
+            base = path;
+        }
+    }
+    return (char *) base;
+}
+
+void check_ptr(void *p, const char *file, int line, const char *func) {
+    UNUSED(file);
+    UNUSED(line);
+    UNUSED(func);
+    if (p == NULL) {
+        FURI_LOG_W("App", "[NULLPTR] %s:%s():%i", basename((char *) file), func, line);
+    }
+}
+
+float lerp(float a, float b, float t) {
+    if (t > 1) return b;
+    return (1 - t) * a + t * b;
+}
+
+LogTimer::LogTimer(const char *n):name(n) {
+    start = furi_get_tick();
+}
+
+LogTimer::~LogTimer() {
+    FURI_LOG_D("App", "%s took %fms", name, furi_get_tick()-start);
+}

+ 43 - 0
utils/Helpers.h

@@ -0,0 +1,43 @@
+#pragma once
+#define M_PIX2		6.28318530717958647692	/* 2 pi */
+#include "furi.h"
+#define l_abs(x) ((x) < 0 ? -(x) : (x))
+#define DEG_2_RAD  0.01745329251994329576f
+#define RAD_2_DEG  565.48667764616278292327f
+
+#define DEBUG_BUILD
+#ifdef DEBUG_BUILD
+#define check_pointer(X) check_ptr( X, __FILE__, __LINE__, __FUNCTION__)
+#else
+#define check_pointer(X) while(0)
+#endif
+
+#define CHECK_HEAP() FURI_LOG_I("FlipperGameEngine", "Free/total heap: %zu / %zu", memmgr_get_free_heap(), memmgr_get_total_heap())
+char *basename(const char *path);
+
+void check_ptr(void *p, const char *file, int line, const char *func);
+
+float lerp(float a, float b, float t);
+
+class LogTimer{
+    double start;
+    const char* name;
+public:
+    explicit LogTimer(const char* name);
+    ~LogTimer();
+};
+
+class TypeRegistry {
+public:
+    template <typename T>
+    static int getTypeID() {
+        static int typeID = getNextTypeID();
+        return typeID;
+    }
+
+private:
+    static int getNextTypeID() {
+        static int currentTypeID = 0;
+        return ++currentTypeID;
+    }
+};

+ 284 - 0
utils/List.h

@@ -0,0 +1,284 @@
+#pragma once
+
+#include <furi.h>
+#include <iterator>
+#include "Helpers.h"
+
+template<typename T>
+struct ListItem {
+    ListItem *next;
+    T *data;
+};
+
+template<typename T>
+struct ListIterator {
+    using iterator_category = std::forward_iterator_tag;
+
+    ListItem<T> *current;
+
+    explicit ListIterator(ListItem<T> *node) : current(node) {}
+
+    ListIterator &operator++() {
+        current = current->next;
+        return *this;
+    }
+
+    ListIterator operator++(int) {
+        ListIterator iterator = *this;
+        ++(*this);
+        return iterator;
+    }
+
+    bool operator==(const ListIterator &other) const {
+        return current == other.current;
+    }
+
+    bool operator!=(const ListIterator &other) const {
+        return current != other.current;
+    }
+
+    T *operator*() const {
+        return (current->data);
+    }
+
+    T *operator->() const {
+        return current->data;
+    }
+};
+
+
+template<typename T>
+struct List {
+    uint32_t count;
+    ListItem<T> *start = nullptr;
+
+    List() : count(0) {}
+
+    ~List() {
+        FURI_LOG_D("App", "List emptied");
+
+        empty();
+    }
+
+    void soft_clear() {
+        auto *item = start;
+        ListItem<T> *t;
+        while (item) {
+            t = item;
+            item = item->next;
+            delete t;
+        }
+        count = 0;
+    }
+
+    void clear() {
+        auto *item = start;
+        ListItem<T> *t;
+        while (item) {
+            t = item;
+            item = item->next;
+            if (t->data) {
+                check_pointer(t->data);
+                delete t->data;
+            }
+            check_pointer(t);
+            delete t;
+        }
+        count = 0;
+    }
+
+    void empty() {
+        clear();
+        if (start) {
+            check_pointer(start);
+            delete start;
+        }
+    }
+
+    void add(T *data) {
+        count++;
+        if (count > 1) {
+            ListItem<T> *c = start;
+            while (c->next) {
+                c = c->next;
+            }
+            c->next = new ListItem<T>();
+            c->next->data = data;
+        } else {
+            start = new ListItem<T>();
+            start->data = data;
+        }
+    }
+
+    void remove(T *data) {
+        if (!start || !data) return;
+
+        ListItem<T> *s = start;
+        if (s->data == data) {
+            check_pointer(s->data);
+            delete s->data;
+            start = start->next;
+            count--;
+        } else {
+            while (s) {
+                if (s->next && s->next->data == data) {
+                    auto n = s->next->next;
+                    check_pointer(s->next->data);
+                    check_pointer(s->next);
+                    delete s->next->data;
+                    delete s->next;
+                    s->next = n;
+                    count--;
+                    return;
+                }
+
+                s = s->next;
+            }
+        }
+    }
+
+    void soft_remove(T *data) {
+        if (!start || !data) return;
+
+        ListItem<T> *s = start;
+        if (s->data == data) {
+            auto tmp = start;
+            start = start->next;
+            delete tmp;
+            count--;
+        } else {
+            while (s) {
+                if (s->next && s->next->data == data) {
+                    auto n = s->next->next;
+                    check_pointer(s->next);
+                    delete s->next;
+                    s->next = n;
+                    count--;
+                    return;
+                }
+
+                s = s->next;
+            }
+        }
+    }
+
+    void remove(uint32_t index, uint32_t amount) {
+        auto *result = splice(index, amount);
+        delete result;
+    }
+
+    List<T> *splice(uint32_t index, uint32_t amount) {
+        auto *removedElements = new List<T>();
+
+        if (index < count) {
+            uint32_t m = (index + amount) > count ? count - index : amount;
+            uint32_t curr_id = 0;
+            auto s = start;
+            while (curr_id < index) {
+                s = s->next;
+                if (!s) return removedElements;
+                curr_id++;
+            }
+
+            ListItem<T> *t;
+            for (uint32_t i = 0; i < m; i++) {
+                t = s->next;
+                if (s->data) {
+                    removedElements->add(s->data);
+                }
+                delete s;
+                s = t->next;
+                count--;
+            }
+            if (index == 0) {
+                start = s;
+            }
+        }
+
+        return removedElements;
+    }
+
+    T *pop_front() {
+        if (!start) {
+            // List is empty, nothing to remove
+            return nullptr;
+        }
+
+        ListItem<T> *front = start;
+        start = start->next; // Update the start pointer to the next element
+
+        T *data = front->data; // Store the data of the front element
+        delete front; // Delete the front element
+        count--;
+        return data; // Return the data of the removed element
+    }
+
+    T *last() {
+        if (!start) {
+            return nullptr;
+        }
+
+        if (!start->next) {
+            return start->data;
+        }
+
+        ListItem<T> *current = start;
+        while (current->next) {
+            current = current->next;
+        }
+
+        return current->data; // Return the data
+    }
+
+    T *pop() {
+        if (!start) {
+            // List is empty, nothing to remove
+            return nullptr;
+        }
+
+        if (!start->next) {
+            // Only one element in the list
+            T *data = start->data;
+            delete start;
+            start = nullptr;
+            count--;
+            return data;
+        }
+
+        ListItem<T> *previous = nullptr;
+        ListItem<T> *current = start;
+
+        while (current->next) {
+            previous = current;
+            current = current->next;
+        }
+
+        previous->next = nullptr; // Remove the last element from the list
+        T *data = current->data; // Store the data of the last element
+        count--;
+
+        delete current; // Delete the last element
+        return data; // Return the data of the removed element
+    }
+
+    ListIterator<T> begin() {
+        return ListIterator<T>(start);
+    }
+
+    ListIterator<T> end() {
+        return ListIterator<T>(nullptr);
+    }
+
+    T *operator[](int i) {
+        int index = 0;
+        auto *item = start;
+        while (item) {
+            if (index == i) return item->data;
+
+            item = item->next;
+            index++;
+        }
+
+        return nullptr;
+    }
+};

+ 143 - 0
utils/RenderBuffer.cpp

@@ -0,0 +1,143 @@
+#include "RenderBuffer.h"
+#include "Sprite.h"
+#include "Helpers.h"
+#include "Vector.h"
+#include "cmath"
+
+RenderBuffer::RenderBuffer(uint8_t w, uint8_t h) : Buffer(w, h) {
+    FURI_LOG_D("App", "New renderBuffer");
+}
+
+void RenderBuffer::reset() {
+}
+
+void RenderBuffer::render(Canvas *const canvas) {
+    canvas_clear(canvas);
+    for (uint8_t x = 0; x < width(); x++) {
+        for (uint8_t y = 0; y < height(); y++) {
+            if (test_pixel(x, y))
+                canvas_draw_dot(canvas, x, y);
+        }
+    }
+    canvas_commit(canvas);
+}
+
+void RenderBuffer::draw_line(int x0, int y0, int x1, int y1, PixelColor draw_mode) {
+    int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
+    int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
+    int err = (dx > dy ? dx : -dy) / 2;
+
+    while (true) {
+        if (test_coordinate(x0, y0)) {
+            set_pixel(x0, y0, draw_mode);
+        }
+        if (x0 == x1 && y0 == y1) break;
+        int e2 = err;
+        if (e2 > -dx) {
+            err -= dy;
+            x0 += sx;
+        }
+        if (e2 < dy) {
+            err += dx;
+            y0 += sy;
+        }
+    }
+}
+
+void RenderBuffer::draw_circle(int x, int y, int r, PixelColor color) {
+    int16_t a = r;
+    int16_t b = 0;
+    int16_t decision = 1 - a;
+
+    while (b <= a) {
+        set_pixel_with_check(a + x, b + y, color);
+        set_pixel_with_check(b + x, a + y, color);
+        set_pixel_with_check(-a + x, b + y, color);
+        set_pixel_with_check(-b + x, a + y, color);
+        set_pixel_with_check(-a + x, -b + y, color);
+        set_pixel_with_check(-b + x, -a + y, color);
+        set_pixel_with_check(a + x, -b + y, color);
+        set_pixel_with_check(b + x, -a + y, color);
+
+        b++;
+        if (decision <= 0) {
+            decision += 2 * b + 1;
+        } else {
+            a--;
+            decision += 2 * (b - a) + 1;
+        }
+    }
+}
+
+void RenderBuffer::draw(Sprite *const sprite, Vector position, float rotation) {
+    switch (sprite->draw_mode) {
+        default:
+        case BlackOnly:
+            draw_sprite(sprite, true, Black, position, rotation);
+            break;
+        case WhiteOnly:
+            draw_sprite(sprite, false, White, position, rotation);
+            break;
+        case WhiteAsBlack:
+            draw_sprite(sprite, false, Black, position, rotation);
+            break;
+        case BlackAsWhite:
+            draw_sprite(sprite, true, White, position, rotation);
+            break;
+        case WhiteAsInverted:
+            draw_sprite(sprite, false, Flip, position, rotation);
+            break;
+        case BlackAsInverted:
+            draw_sprite(sprite, true, Flip, position, rotation);
+            break;
+    }
+}
+
+//TODO: proper scaling
+void
+RenderBuffer::draw_sprite(Sprite *const sprite, bool is_black, PixelColor draw_color, const Vector& position, float rotation) {
+    Vector anchor = sprite->get_offset();
+    float cosTheta = cos(rotation/* + M_PI_2*/);
+    float sinTheta = sin(rotation/* + M_PI_2*/);
+    float transformedX, transformedY, rotatedX, rotatedY;
+    bool isOn;
+    int16_t finalX, finalY;
+    for (int y = 0; y < sprite->height(); y++) {
+        for (int x = 0; x < sprite->width(); x++) {
+            transformedX = (x - anchor.x);
+            transformedY = (y - anchor.y);
+            rotatedX = transformedX * cosTheta - transformedY * sinTheta;
+            rotatedY = transformedX * sinTheta + transformedY * cosTheta;
+
+            finalX = (int16_t) floor(rotatedX + position.x);
+            finalY = (int16_t) floor(rotatedY + position.y);
+            if (test_coordinate(finalX, finalY)) {
+                isOn = sprite->test_pixel(x, y) == is_black;
+                if (isOn)
+                    set_pixel(finalX, finalY, draw_color);
+            }
+
+        }
+    }
+}
+
+RenderBuffer::~RenderBuffer() {
+    FURI_LOG_D("App", "RenderBuffer end");
+}
+
+void RenderBuffer::draw_rbox(int16_t x0, int16_t y0, int16_t x1, int16_t y1, PixelColor draw_mode) {
+    for (int16_t x = x0; x < x1; x++) {
+        for (int16_t y = y0; y < y1; y++) {
+            if (((x == x0 || x == x1-1) && (y == y0 || y == y1-1)) || !test_coordinate(x,y)) continue;
+            set_pixel(x, y, draw_mode);
+        }
+    }
+}
+
+void RenderBuffer::draw_rbox_frame(int16_t x0, int16_t y0, int16_t x1, int16_t y1, PixelColor draw_mode) {
+    draw_line(x0 + 1, y0, x1 - 1, y0, draw_mode);
+    draw_line(x0 + 1, y1, x1 - 1, y1, draw_mode);
+
+    draw_line(x0, y0 + 1, x0, y1 - 1, draw_mode);
+    draw_line(x1, y0 + 1, x1, y1 - 1, draw_mode);
+}

+ 29 - 0
utils/RenderBuffer.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include "Buffer.h"
+#include <gui/gui.h>
+
+struct Vector;
+class Sprite;
+class RenderBuffer : public Buffer {
+    void draw_sprite(Sprite *const sprite, bool is_black,
+                     PixelColor draw_color, const Vector& position, float rotation);
+
+public:
+    explicit RenderBuffer(uint8_t w, uint8_t h);
+
+    virtual ~RenderBuffer();
+
+    void render(Canvas *const canvas);
+
+    void reset();
+
+    void draw_line(int x0, int y0, int x1, int y1, PixelColor draw_mode);
+    void draw_rbox(int16_t x0, int16_t y0, int16_t x1, int16_t y1, PixelColor draw_mode);
+    void draw_rbox_frame(int16_t x0, int16_t y0, int16_t x1, int16_t y1, PixelColor draw_mode);
+
+    void draw_circle(int x, int y, int r, PixelColor draw_mode);
+
+    void draw(Sprite *const sprite, Vector position, float rotation);
+
+};

+ 20 - 0
utils/Sprite.cpp

@@ -0,0 +1,20 @@
+#include "Sprite.h"
+#include "Buffer.h"
+
+Sprite::Sprite(const SpriteData *spriteData, DrawMode d) : Buffer(spriteData), _icon(spriteData), draw_mode(d) {
+}
+
+void Sprite::set_anchor(float x, float y) {
+    anchor.x = x;
+    anchor.y = y;
+}
+
+Vector Sprite::get_offset() {
+    return {
+            anchor.x * (float) width(),
+            anchor.y * (float) height()
+    };
+}
+
+Sprite::Sprite(const SpriteData &spriteData, DrawMode d) : Buffer(&spriteData), _icon(&spriteData), draw_mode(d) {
+}

+ 30 - 0
utils/Sprite.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include "RenderBuffer.h"
+#include "Vector.h"
+
+struct SpriteData {
+    uint8_t width = 0;
+    uint8_t height = 0;
+    uint8_t *data = NULL;
+};
+
+class Sprite : public Buffer {
+    const SpriteData *_icon;
+    Vector anchor = {0.5, 0.5};
+public:
+    DrawMode draw_mode;
+
+    Sprite(const SpriteData *icon, DrawMode draw_mode);
+    Sprite(const SpriteData &icon, DrawMode draw_mode);
+
+    virtual ~Sprite() {
+        FURI_LOG_D("App", "Sprite cleared");
+    }
+
+    const SpriteData *get_data() { return _icon; }
+
+    void set_anchor(float x, float y);
+
+    Vector get_offset();
+};

+ 183 - 0
utils/Vector.h

@@ -0,0 +1,183 @@
+#pragma once
+
+#include <cmath>
+#include "Helpers.h"
+
+struct Vector {
+    float x;
+    float y;
+
+    Vector &operator=(const Vector &other) {
+        if (this != &other) {
+            x = other.x;
+            y = other.y;
+        }
+        return *this;
+    }
+
+    Vector(const Vector &other) : x(other.x), y(other.y) {}
+
+    Vector(float _x, float _y) : x(_x), y(_y) {}
+
+    Vector() : x(0), y(0) {}
+
+    Vector operator+(Vector const &other) {
+        return Vector({x + other.x, y + other.y});
+    }
+
+    Vector operator+(float const &other) {
+        return Vector({x + other, y + other});
+    }
+
+    Vector &operator+=(Vector const &other) {
+        x += other.x;
+        y += other.y;
+        return *this;
+    }
+
+    Vector operator-(Vector const &other) {
+        return Vector{x - other.x, y - other.y};
+    }
+
+    Vector &operator-=(Vector const &other) {
+        x -= other.x;
+        y -= other.y;
+        return *this;
+    }
+
+    Vector operator-(Vector const &other) const {
+        return Vector{x - other.x, y - other.y};
+    }
+
+    Vector operator*(Vector const &other) {
+        return Vector{x * other.x, y * other.y};
+    }
+
+
+    Vector operator*(float other) {
+        return Vector{x * other, y * other};
+    }
+
+    Vector &operator*=(Vector const &other) {
+        x *= other.x;
+        y *= other.y;
+        return *this;
+    }
+
+    Vector &operator*=(float other) {
+        x *= other;
+        y *= other;
+        return *this;
+    }
+
+    Vector operator/(Vector const &other) {
+        return Vector{x / other.x, y / other.y};
+    }
+
+    Vector &operator/=(Vector const &other) {
+        x /= other.x;
+        y /= other.y;
+        return *this;
+    }
+
+    Vector operator/(float other) {
+        return Vector{x / other, y / other};
+    }
+
+    Vector &operator/=(float other) {
+        x /= other;
+        y /= other;
+        return *this;
+    }
+
+    float magnitude() {
+        return sqrtf(x * x + y * y);
+    }
+
+    float distance(Vector const &other) {
+        Vector v = *this - other;
+        return v.magnitude();
+    }
+
+    void normalize() {
+        float m = magnitude();
+        if (m == 0) {
+            x = 0;
+            y = 0;
+        }else{
+            x = x / m;
+            y = y / m;
+        }
+    }
+
+    Vector normalized() {
+        float m = magnitude();
+        if (m == 0) return {0, 0};
+
+        return {x / m, y / m};
+    }
+
+    Vector inverse() {
+        return {-x, -y};
+    }
+
+    float dot(Vector const &b) {
+        return x * b.x + y * b.y;
+    }
+
+    void rotate(float deg) {
+        float tx = x;
+        float ty = y;
+        x = (float) (cos(deg) * (double) tx - sin(deg) * (double) ty);
+        y = (float) (sin(deg) * (double) tx + cos(deg) * (double) ty);
+    }
+
+    void rounded() {
+        x = (float) round(x);
+        y = (float) round(y);
+    }
+
+    Vector rotated(float deg) {
+        return {
+                (float) (cos(deg) * (double) x - sin(deg) * (double) y),
+                (float) (sin(deg) * (double) x + cos(deg) * (double) y)
+        };
+    }
+
+    float cross(Vector const &other) {
+        return x * other.x - y * other.y;
+    }
+
+    Vector perpendicular() {
+        return {-y, x};
+    }
+
+    Vector project(Vector const &lineA, Vector const &lineB, bool *success) {
+        Vector AB = lineB - lineA;
+        Vector AC = *this - lineA;
+
+        float k = AC.dot(AB) / AB.dot(AB);
+        if (k < 0 || k > 1) {
+            *success = false;
+            return {};
+        }
+        *success = true;
+        return {
+            k * AB.x + lineA.x,
+            k * AB.y + lineA.y
+        };
+    }
+
+    static Vector Lerp(Vector const &start, Vector const &end, float time) {
+        return {
+                lerp(start.x, end.x, time),
+                lerp(start.y, end.y, time)
+        };
+    }
+
+    static Vector Quadratic(Vector const &start, Vector const &control, Vector const &end, float time) {
+        Vector a = Vector::Lerp(start, control, time);
+        Vector b = Vector::Lerp(control, end, time);
+        return Vector::Lerp(a, b, time);
+    }
+};