Bläddra i källkod

code rewrite (again)
new RenderBuffer and way more table code

Tibor Tálosi 1 år sedan
förälder
incheckning
a64a117532
25 ändrade filer med 1773 tillägg och 1162 borttagningar
  1. 0 86
      Deck.cpp
  2. 0 21
      Deck.h
  3. 0 312
      Game.cpp
  4. 0 52
      Game.h
  5. 436 0
      GameLogic.cpp
  6. 55 0
      GameLogic.h
  7. 94 0
      GameLoop.cpp
  8. 36 0
      GameLoop.h
  9. 0 171
      TableauColumn.cpp
  10. 0 28
      TableauColumn.h
  11. 249 54
      assets.h
  12. 4 114
      solitaire.cpp
  13. 106 17
      utils/Buffer.cpp
  14. 12 1
      utils/Buffer.h
  15. 49 26
      utils/Card.cpp
  16. 32 5
      utils/Card.h
  17. 4 4
      utils/Helpers.cpp
  18. 5 2
      utils/Helpers.h
  19. 47 0
      utils/Input.h
  20. 208 216
      utils/List.h
  21. 136 33
      utils/RenderBuffer.cpp
  22. 13 3
      utils/RenderBuffer.h
  23. 3 2
      utils/Sprite.cpp
  24. 7 6
      utils/Sprite.h
  25. 277 9
      utils/Vector.h

+ 0 - 86
Deck.cpp

@@ -1,86 +0,0 @@
-#include "Deck.h"
-#include "utils/Buffer.h"
-#include "utils/RenderBuffer.h"
-
-Deck::Deck(uint8_t count) : deck_count(count) {
-
-}
-
-void Deck::Generate() {
-    waste_pile.clear();
-    stock_pile.clear();
-    //generate and shuffle deck
-    int cards_count = 52 * deck_count;
-    uint8_t cards[cards_count];
-    for (int i = 0; i < cards_count; i++) cards[i] = i % 52;
-    srand(DWT->CYCCNT);
-
-    for (int i = 0; i < cards_count; i++) {
-        int r = i + (rand() % (cards_count - 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));
-    }
-    FURI_LOG_D("Deck count", "%li", stock_pile.count);
-}
-
-void Deck::Cycle() {
-    if (stock_pile.count > 0) {
-        auto *c = stock_pile.pop();
-        check_pointer(c);
-        c->exposed = true;
-        waste_pile.add(c);
-    } else {
-        while (waste_pile.count > 0) {
-            auto *c = waste_pile.pop();
-            check_pointer(c);
-            c->exposed = false;
-            stock_pile.add(c);
-        }
-    }
-}
-
-Card *Deck::GetLastWaste() {
-    return waste_pile.pop();
-}
-
-void Deck::AddToWaste(Card *c) {
-    check_pointer(c);
-    waste_pile.add(c);
-}
-
-Card *Deck::Extract() {
-    return stock_pile.pop();
-}
-
-void Deck::Render(RenderBuffer *buffer, bool stockpileSelect, bool wasteSelect) {
-    if (stock_pile.count == 0) {
-        Card::RenderEmptyCard(1, 1, buffer);
-        if (stockpileSelect) {
-            buffer->draw_rbox(2, 2, 17, 23, Flip);
-        }
-    } else {
-        if (stock_pile.count > 1) {
-            buffer->draw_rbox_frame(1, 1, 17, 23, Black);
-            stock_pile.last()->Render(0, 0, stockpileSelect, buffer, 22);
-        } else {
-            stock_pile.last()->Render(1, 1, stockpileSelect, buffer, 22);
-        }
-    }
-
-    if (waste_pile.count == 0) {
-        Card::RenderEmptyCard(19, 1, buffer);
-        if (wasteSelect) {
-            buffer->draw_rbox(20, 2, 35, 23, Flip);
-        }
-    } else {
-        waste_pile.last()->Render(19, 1, wasteSelect, buffer, 22);
-    }
-}

+ 0 - 21
Deck.h

@@ -1,21 +0,0 @@
-#pragma once
-
-#include <furi_hal_resources.h>
-#include "utils/Card.h"
-#include "utils/List.h"
-#include "TableauColumn.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, bool stockpileSelect, bool wasteSelect);
-    void Cycle();
-    Card* GetLastWaste();
-    void AddToWaste(Card* c);
-    Card* Extract();
-    void Click(InputKey key, bool isDeck, TableauColumn *hand);
-};

+ 0 - 312
Game.cpp

@@ -1,312 +0,0 @@
-#include "Game.h"
-#include "assets.h"
-#include <notification/notification.h>
-#include <notification/notification_messages.h>
-
-static Sprite logo = Sprite(sprite_logo, BlackOnly);
-static Sprite main_image = Sprite(sprite_main_image, BlackOnly);
-static Sprite start = Sprite(sprite_start, BlackOnly);
-
-static 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
-};
-
-Game::~Game() {
-    furi_record_close(RECORD_NOTIFICATION);
-    delete deck;
-    for (int i = 0; i < 4; i++)
-        delete piles[i];
-    delete buffer;
-}
-
-void Game::Render(Canvas *const canvas) {
-    if (!buffer) return;
-    if (had_change) {
-        if (state == GameStateStart) {
-            buffer->clear();
-            buffer->draw(&logo, (Vector) {60, 30}, 0);
-            buffer->draw(&main_image, (Vector) {110, 25}, 0);
-            buffer->draw(&start, (Vector) {64, 55}, 0);
-        } else if (state == GameStatePlay) {
-            buffer->clear();
-            if (deck)
-                deck->Render(buffer, current_row == 0 && current_column == 0, current_row == 0 && current_column == 1);
-            for (int i = 0; i < 7; i++) {
-                bool selected = current_row == 1 && current_column == i;
-                tableau[i].Render(i * 18 + 1, 25, selected, selected ? column_selection : 0, buffer);
-            }
-            for (int i = 0; i < 4; i++) {
-                bool pileSelected = current_row == 0 && (current_column - 3) == i;
-//                check_pointer(piles[i]);
-                if (piles[i]) {
-                    piles[i]->Render(i * 18 + 55, 1, pileSelected, buffer, 22);
-                } else {
-                    Card::RenderEmptyCard(i * 18 + 55, 1, buffer);
-                    if (pileSelected) {
-                        buffer->draw_rbox(i * 18 + 56, 2, i * 18 + 71, 23, Flip);
-                    }
-                }
-            }
-            if (hand.Count() > 0) {
-                hand.Render(current_column * 18 + 10, current_row * 25 + 15, false, 0, buffer);
-            }
-        }
-    }
-    had_change = false;
-    buffer->render(canvas);
-    if (state == GameStatePlay && can_auto_solve) {
-        canvas_set_font(canvas,FontSecondary);
-
-        canvas_set_color(canvas, ColorBlack);
-        canvas_draw_frame(canvas, 8, 52, 112, 12);
-        canvas_set_color(canvas, ColorWhite);
-        canvas_draw_box(canvas, 9, 53, 110, 10);
-
-        canvas_set_color(canvas, ColorBlack);
-        canvas_draw_str_aligned(canvas, 64, 58, AlignCenter, AlignCenter, "Long press > to auto solve!");
-    }
-}
-
-Game::Game() {
-    deck = new Deck(1);
-    buffer = new RenderBuffer(128, 64);
-
-    notifications = static_cast<NotificationApp *>(furi_record_open(RECORD_NOTIFICATION));
-}
-
-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;
-    hand.Clear();
-    picked_from_column = -1;
-    picked_from_row = -1;
-
-    deck->Generate();
-    //Reset foundations
-    for (int i = 0; i < 4; i++) {
-        if (piles[i]) delete piles[i];
-    }
-
-    //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) {
-    if (key == InputKeyOk) {
-        if (hand.Count() == 1) {
-            for (uint8_t i = 0; i < 4; i++) {
-                Card *current = piles[i];
-                if (Card::CanPlace(current, hand.TopCard())) {
-                    check_pointer(piles[i]);
-                    piles[i] = hand.Pop();
-                    had_change = true;
-                    return;
-                }
-            }
-        }
-    } else if (key == InputKeyUp) {
-        current_row = 0;
-        had_change = true;
-        if (current_column == 2) current_column--;
-    }
-}
-
-void Game::Update() {
-}
-
-void Game::Press(InputKey key) {
-//region Navigation
-    if (key == InputKeyOk && state == GameStateStart) {
-        Reset();
-        NewRound();
-        had_change = true;
-        return;
-    }
-
-    if (key == InputKeyBack && state == GameStatePlay) {
-        state = GameStateStart;
-        had_change = true;
-        return;
-    }
-
-    if (key == InputKeyLeft && current_column > 0) {
-        current_column--;
-        had_change = true;
-        column_selection = 0;
-    } else if (key == InputKeyRight && current_column < 6) {
-        current_column++;
-        column_selection = 0;
-        if (current_row == 0 && current_column == 2) current_column++;
-        had_change = true;
-    }
-
-    if (key == InputKeyUp && current_row == 1) {
-        uint8_t count = tableau[current_column].Count();
-        uint8_t first_non_flipped = tableau[current_column].FirstNonFlipped();
-        if (hand.Count() == 0) {
-            if ((count - column_selection - 1) > first_non_flipped && (column_selection) < count) {
-                column_selection++;
-            } else
-                current_row--;
-        } else {
-            current_row--;
-        }
-
-        had_change = true;
-    } else if (key == InputKeyDown && current_row == 0) {
-        current_row++;
-        column_selection = 0;
-        had_change = true;
-    } else if (key == InputKeyDown && current_row == 1 && column_selection > 0) {
-        column_selection--;
-        had_change = true;
-    }
-
-    //disable empty space selection
-    if (current_row == 0 && current_column == 2) {
-        current_column--;
-        column_selection = 0;
-        had_change = true;
-    }
-//endregion
-
-    //Pick/cycle logic
-    if (hand.Count() == 0) {
-        if (key == InputKeyOk) {
-            if (current_row == 0) {
-                //cycle deck
-                if (current_column == 0) {
-                    deck->Cycle();
-                    had_change = true;
-                } else if (current_column == 1) {
-                    //pick from deck
-                    Card *c = deck->GetLastWaste();
-                    if (c) {
-                        hand.AddCard(c);
-                        picked_from_column = current_column;
-                        picked_from_row = current_row;
-                        had_change = true;
-                    }
-                }
-            } else {
-                //Tableau logic
-                auto *column = &tableau[current_column];
-                //Flip last card if it is not exposed
-                if (column->Count() > 0) {
-                    if (!column->LastCard()->exposed) {
-                        column->Reveal();
-                        had_change = true;
-                    } else {
-//                        hand.AddCard(column->Pop());
-                        //Pick selection
-                        auto *data = column->splice(column_selection);
-                        hand.AddRange(data);
-                        data->soft_clear();
-                        delete data;
-                        column_selection = 0;
-                        had_change = true;
-                        picked_from_row = current_row;
-                        picked_from_column = current_column;
-                    }
-                } else {
-                    ErrorMessage();
-                }
-            }
-        }
-    } else {
-        //Place logic
-        if (key == InputKeyOk) {
-            //Place back to where it was picked up
-            if (current_column == picked_from_column && current_row == picked_from_row) {
-                //add back to tableau
-                if (picked_from_row == 0) {
-                    FURI_LOG_D("PLACE", "reinsert top");
-                    had_change = true;
-                    deck->AddToWaste(hand.Pop());
-                } else {
-                    FURI_LOG_D("PLACE", "reinsert tableau %i %i", hand.Count(), current_column);
-                    auto *column = &tableau[current_column];
-                    if (column) {
-                        FURI_LOG_D("PLACE", "BEFORE MERGE %i", hand.Count());
-                        column->Merge(&hand);
-                        had_change = true;
-                        FURI_LOG_D("PLACE", "AFTER MERGE");
-                    } else {
-                        FURI_LOG_E("PLACE", "TABLEAU ERROR");
-                    }
-                }
-                //Place to the top piles
-            } else if (hand.Count() == 1 && current_row == 0 && current_column > 1) {
-                FURI_LOG_D("PLACE", "place top");
-                check_pointer(piles[current_column - 3]);
-                Card *current = piles[current_column - 3];
-                if (Card::CanPlace(current, hand.TopCard())) {
-                    piles[current_column - 3] = hand.Pop();
-                    had_change = true;
-                } else {
-                    ErrorMessage();
-                }
-                //Place to the tableau columns
-            } else if (current_row == 1) {
-                FURI_LOG_D("PLACE", "place bottom");
-                auto *column = &tableau[current_column];
-                if (column) {
-                    if ((current_row == picked_from_row && current_column == picked_from_column) ||
-                        column->CanPlace(&hand)) {
-                        FURI_LOG_D("PLACE", "canplace");
-                        column->Merge(&hand);
-                        had_change = true;
-                    }
-                } else {
-                    FURI_LOG_E("PLACE", "TABLEAU ERROR in bottom place");
-                }
-            } else {
-                ErrorMessage();
-            }
-        }
-    }
-
-    CheckCanAutoSolve();
-}
-
-void Game::ErrorMessage() {
-    notification_message(notifications, &sequence_fail);
-}
-
-void Game::CheckCanAutoSolve() {
-    uint8_t ok = 0;
-    for (uint8_t i = 0; i < 7; i++) {
-        if (tableau[i].Count() == 0 || tableau[i].TopCard()->exposed)
-            ok++;
-    }
-    can_auto_solve = ok == 7;
-    if (can_auto_solve)
-        FURI_LOG_D("Solve", "can auto solve");
-}

+ 0 - 52
Game.h

@@ -1,52 +0,0 @@
-#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, GameStateSolve
-} PlayState;
-
-class Game {
-    Deck *deck;
-
-    Card *piles[4];
-    TableauColumn tableau[7];
-
-    TableauColumn hand;
-
-    PlayState state = GameStateStart;
-    uint8_t current_row;
-    uint8_t current_column;
-
-    uint8_t picked_from_row;
-    uint8_t picked_from_column;
-    NotificationApp* notifications;
-
-    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();
-    void ErrorMessage();
-    void CheckCanAutoSolve();
-
-    ~Game();
-};

+ 436 - 0
GameLogic.cpp

@@ -0,0 +1,436 @@
+#include "GameLogic.h"
+#include "utils/Sprite.h"
+#include "assets.h"
+
+static Sprite logo = Sprite(sprite_logo, BlackOnly);
+static Sprite solve = Sprite(sprite_solve, BlackOnly);
+static Sprite main_image = Sprite(sprite_main_image, BlackOnly);
+static Sprite start = Sprite(sprite_start, BlackOnly);
+
+GameLogic::GameLogic(RenderBuffer *b, InputEventHandler *inputHandler) : buffer(b) {
+    inputHandler->subscribe(this, [](void *ctx, int key, InputType type) {
+        auto *inst = (GameLogic *) ctx;
+        inst->Input(key, type);
+    });
+}
+
+void GameLogic::Input(int key, InputType type) {
+    if (type == InputTypeShort) {
+        if (key == InputKeyBack && state == Play) {
+            state = Logo;
+            target[0] = 0;
+            target[1] = 0;
+            return;
+        } else if (key == InputKeyOk) {
+            if (state == Logo) {
+                state = Intro;
+                return;
+            }
+            if (state == Solve) {
+                end = furi_get_tick();
+                state = Finish;
+                return;
+            }
+            if (state == Finish) {
+                state = Logo;
+                return;
+            } else if (state == Intro) {
+                for (int i = 0; i < 7; i++) {
+                    while ((int) tableau[i].size() < (i + 1)) {
+                        tableau[i].push_back(stock.pop_back());
+                    }
+                    tableau[i].peek_back()->exposed = true;
+                }
+                tempCard = nullptr;
+                state = Play;
+                startTime = furi_get_tick();
+                buffer->clear();
+                DrawPlayScene();
+            } else if (state == Play) {
+                if (selection[0] == 0 && selection[1] == 0) {
+                    //Cycle waste and stock
+                    if (hand.size() == 0) {
+                        if (stock.size() > 0) {
+                            //move from stock to waste
+                            waste.push_back(stock.pop_back());
+                            waste.peek_back()->exposed = true;
+                        } else {
+                            //move back all the card
+                            while (waste.size() > 0) {
+                                waste.peek_back()->exposed = false;
+                                stock.push_back(waste.pop_back());
+                            }
+                        }
+                        return;
+                    }
+                    return;
+                } else if (selection[1] == 1 || selection[0] > 0) {
+                    PickAndPlace();
+                }
+                return;
+            }
+            return;
+        }
+        if (state == Play)
+            HandleNavigation(key);
+    } else if (type == InputTypeLong) {
+        switch (key) {
+            case InputKeyLeft:
+                selection[0] = 0;
+                selectedCard = 1;
+                break;
+            case InputKeyRight:
+                selection[0] = 6;
+                selectedCard = 1;
+                break;
+            case InputKeyUp:
+                selectedCard = 1;
+                selection[1] = 0;
+                break;
+            case InputKeyOk:
+                if (CanSolve()) {
+                    state = Solve;
+                    break;
+                }
+                //quick place
+                if (state == Play && (selection[0] == 1 || selection[1] == 1) && selectedCard == 1) {
+                    Card *c = selection[1] == 0 ? waste.peek_back() : tableau[selection[0]].peek_back();
+                    if (!c->exposed) return;
+                    for (int i = 0; i < 4; i++) {
+                        if (c->CanPlaceFoundation(foundation[i].peek_back())) {
+                            if (selection[1] == 0)
+                                foundation[i].push_back(waste.pop_back());
+                            else
+                                foundation[i].push_back(tableau[selection[0]].pop_back());
+                            break;
+                        }
+                    }
+                }
+                break;
+            default:
+                break;
+        }
+    }
+}
+
+void GameLogic::PickAndPlace() {
+    //pick and place waste
+    if (selection[0] == 1 && selection[1] == 0) {
+        if (waste.size() > 0 && hand.size() == 0) {
+            hand.push_back(waste.pop_back());
+            target[0] = 1;
+            target[1] = 0;
+        } else if (hand.size() == 1 && target[0] == 1 && target[1] == 0) {
+            waste.push_back(hand.pop_back());
+            target[0] = 0;
+            target[1] = 0;
+        }
+    }
+        //place to foundation
+    else if (selection[1] == 0 && selection[0] > 2 && hand.size() == 1) {
+        uint8_t id = selection[0] - 3;
+        if (hand.peek_back()->CanPlaceFoundation(foundation[id].peek_back())) {
+            foundation[id].push_back(hand.pop_back());
+            target[0] = selection[0];
+            target[1] = selection[1];
+        }
+    }
+        //pick and place columns
+    else if (selection[1] == 1) {
+        auto &tbl = tableau[selection[0]];
+        if (hand.size() == 0) {
+            if (tbl.peek_back() && !tbl.peek_back()->exposed) {
+                tbl.peek_back()->exposed = true;
+            } else {
+                uint8_t count = selectedCard;
+                while (count > 0) {
+                    hand.push_front(tbl.pop_back());
+                    count--;
+                }
+                selectedCard = 1;
+                target[0] = selection[0];
+                target[1] = selection[1];
+            }
+        } else if ((target[0] == selection[0] && target[1] == selection[1]) ||
+                   hand.peek_front()->CanPlaceColumn(tbl.peek_back())) {
+            while (hand.size() > 0) {
+                tbl.push_back(hand.pop_front());
+            }
+            target[0] = 0;
+            target[1] = 0;
+        }
+    }
+}
+
+void GameLogic::HandleNavigation(int key) {
+    if (key == InputKeyLeft && selection[0] > 0) {
+        selectedCard = 1;
+        selection[0]--;
+    } else if (key == InputKeyRight && selection[0] < 6) {
+        selectedCard = 1;
+        selection[0]++;
+    } else if (key == InputKeyUp && selection[1] == 1) {
+        auto &tbl = tableau[selection[0]];
+        uint8_t first = FirstNonFlipped(tbl);
+        if (tbl.size() > 0 && first < tbl.size() - selectedCard && hand.size() == 0) {
+            selectedCard++;
+        } else {
+            selectedCard = 1;
+            selection[1]--;
+        }
+    } else if (key == InputKeyDown && selection[1] == 0) {
+        selectedCard = 1;
+        selection[1]++;
+    } else if (selection[1] == 1 && selectedCard > 1) {
+        selectedCard--;
+    }
+    if (selection[1] == 0 && selection[0] == 2) { //skip empty space
+        selection[0] += key == InputKeyRight ? 1 : -1;
+    }
+}
+
+
+void GameLogic::Update(float delta) {
+    if(state != Finish){
+        buffer->clear();
+    }
+    switch (state) {
+        case Logo:
+            Reset();
+            buffer->draw(&logo, (Vector) {60, 30}, 0);
+            buffer->draw(&main_image, (Vector) {115, 25}, 0);
+            buffer->draw(&start, (Vector) {64, 55}, 0);
+            break;
+        case Intro:
+            DoIntro(delta);
+            dirty = true;
+            break;
+        case Play:
+            DrawPlayScene();
+            if (CanSolve()) {
+                buffer->draw_rbox(25, 53, 101, 64, Black);
+                buffer->draw_rbox(26, 54, 100, 63, White);
+                buffer->draw(&solve, (Vector) {64, 58}, 0);
+                end = furi_get_tick();
+            }
+            break;
+        default:
+            break;
+    }
+    buffer->swap();
+    readyToRender = true;
+}
+
+void GameLogic::Reset() {
+    stock.deleteData();
+    stock.clear();
+    waste.deleteData();
+    waste.clear();
+    tempPos = {0, 0};
+    selection[0] = 0;
+    selection[1] = 0;
+    target[0] = 0;
+    selectedCard = 1;
+    target[1] = -1;
+    for (int i = 0; i < 7; i++) {
+        tableau[i].deleteData();
+        tableau[i].clear();
+        if (i < 4) {
+            foundation[i].deleteData();
+            foundation[i].clear();
+        }
+    }
+    GenerateDeck();
+}
+
+bool GameLogic::CanSolve() {
+    for (uint8_t i = 0; i < 7; i++) {
+        if (tableau[i].peek_front() && !tableau[i].peek_front()->exposed)
+            return false;
+    }
+    return state == Play;
+}
+
+void GameLogic::GenerateDeck() {
+    int cards_count = 52;
+    uint8_t cards[cards_count];
+    for (int i = 0; i < cards_count; i++) cards[i] = i % 52;
+    srand(DWT->CYCCNT);
+    //reorder
+    for (int i = 0; i < cards_count; i++) {
+        int r = i + (rand() % (cards_count - i));
+        uint8_t card = cards[i];
+        cards[i] = cards[r];
+        cards[r] = card;
+    }
+
+    //Init deck list
+    for (int i = 0; i < cards_count; i++) {
+        int letter = cards[i] % 13;
+        int suit = cards[i] / 13;
+        stock.push_back(new Card(suit, letter));
+    }
+}
+
+GameLogic::~GameLogic() {
+    stock.deleteData();
+    stock.clear();
+    waste.deleteData();
+    waste.clear();
+    hand.deleteData();
+    hand.clear();
+    for (int i = 0; i < 7; i++) {
+        tableau[i].deleteData();
+        tableau[i].clear();
+        if (i < 4) {
+            foundation[i].deleteData();
+            foundation[i].clear();
+        }
+    }
+}
+
+Vector pos, targetPos;
+
+void GameLogic::DoIntro(float delta) {
+    //render next after finish
+    if (!buffer) return;
+    buffer->clear();
+    DrawPlayScene();
+
+
+    if (tempCard) {
+        targetPos = {
+            2.0f + (float) target[0] * 18,
+            MIN(25.0f + (float) target[1] * 4, 36)
+        };
+        tempTime += delta * 10;
+        pos = Vector::Lerp(tempPos, targetPos, MIN(tempTime, 1));
+        tempCard->Render((int) pos.x, (int) pos.y, false, buffer);
+        float distance = targetPos.distance(pos);
+        if (distance < 0.01) {
+            tempCard = nullptr;
+            tableau[target[0]].push_back(stock.pop_back());
+            tableau[target[0]].peek_back()->exposed = target[0] == target[1];
+            if (target[0] == 6 && target[1] == 6) {
+                startTime = furi_get_tick();
+                state = Play;
+                buffer->clear();
+                DrawPlayScene();
+            }
+        }
+    } else {
+        tempTime = 0;
+        tempCard = stock.peek_back();
+        tempPos.x = 2;
+        tempPos.y = 1;
+        if (target[0] == target[1]) {
+            target[0]++;
+            target[1] = 0;
+        } else {
+            target[1]++;
+        }
+    }
+}
+
+void GameLogic::DrawPlayScene() {
+    if (stock.size() > 0) {
+        if (stock.size() > 1) {
+            stock.peek_back()->Render(2, 1, true, buffer);
+            stock.peek_back()->Render(0, 0, selection[0] == 0 && selection[1] == 0, buffer);
+        } else {
+            stock.peek_back()->Render(2, 1, selection[0] == 0 && selection[1] == 0, buffer);
+        }
+    } else
+        Card::TryRender(nullptr, 2, 1, selection[0] == 1 && selection[1] == 0, buffer);
+
+    Card::TryRender(waste.peek_back(), 20, 1, selection[0] == 1 && selection[1] == 0, buffer);
+
+    int i;
+    for (i = 0; i < 4; i++) {
+        Card::TryRender(foundation[i].peek_back(), 56 + i * 18, 1, selection[0] == i + 3 && selection[1] == 0, buffer);
+    }
+    for (i = 0; i < 7; i++) {
+        DrawColumn(2 + i * 18, 25, (selection[0] == i && selection[1] == 1) ? selectedCard : 0, i);
+    }
+
+    if (hand.size() > 0) {
+        if (selection[1] == 0)
+            DrawColumn(10 + selection[0] * 18, 15 + selection[1] * 25, hand.size(), -1);
+        else {
+            int shift = MIN((int) tableau[selection[0]].size(), 4) * 4;
+            DrawColumn(10 + selection[0] * 18, 30 + shift, hand.size(), -1);
+        }
+    }
+}
+
+void GameLogic::DrawColumn(uint8_t x, uint8_t y, uint8_t selected, int8_t column) {
+    UNUSED(selected);
+    auto &deck = column >= 0 ? tableau[column] : hand;
+    if (deck.size() == 0 && column >= 0) {
+        Card::RenderEmptyCard(x, y, buffer);
+        buffer->draw_rbox(x + 1, y + 1, x + 16, y + 22, Flip);
+        return;
+
+    }
+    uint8_t selection = deck.size() - selected;
+//    if (selected != 0) selection--;
+    uint8_t loop_end = deck.size();
+    uint8_t loop_start = MAX(loop_end - 4, 0);
+    uint8_t position = 0;
+    uint8_t first_non_flipped = FirstNonFlipped(deck);
+    bool had_top = false;
+    bool showDark = column >= 0;
+
+    // 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, false, buffer, 5);
+            // Increment loop start index and position
+            position += 4;
+            loop_start++;
+            had_top = true;
+        }
+        // Draw the front side of the first non-flipped card
+        deck[first_non_flipped]->Render(x, y + position, false, buffer, deck.size() == 1 ? 22 : 9);
+        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, false, buffer, 5);
+            position += 4;
+            loop_start++;
+        }
+        // Draw the front side of the selected card
+        deck[selection]->Render(x, y + position, showDark, buffer, 9);
+        position += 8;
+        loop_start++; // Increment loop start index
+    }
+
+    int height = 5;
+    uint8_t i = 0;
+    for (auto *card: deck) {
+        if (i >= loop_start && i < loop_end) {
+            height = 5;
+            if ((i + 1) == loop_end) height = 22;
+            else if (i == selection || i == first_non_flipped) height = 9;
+            card->Render(x, y + position, i == selection && showDark, buffer, height);
+            if (i == selection || i == first_non_flipped)position += 4;
+            position += 4;
+        }
+        i++;
+    }
+}
+
+int8_t GameLogic::FirstNonFlipped(const List<Card> &deck) {
+    int8_t index = 0;
+    for (auto *card: deck) {
+        if (card->exposed) return index;
+        index++;
+    }
+
+    return -1;
+}

+ 55 - 0
GameLogic.h

@@ -0,0 +1,55 @@
+#pragma once
+
+
+#include "utils/Card.h"
+#include "utils/List.h"
+#include "utils/Input.h"
+#include "utils/Vector.h"
+
+enum GameState {
+    Logo, Intro, Play, Solve, Finish
+};
+
+class GameLogic {
+    GameState state = Logo;
+
+    List<Card> hand = List<Card>();
+    RenderBuffer *buffer;
+
+    List<Card> stock = List<Card>();
+    List<Card> waste = List<Card>();
+    List<Card> foundation[4] = {List<Card>(), List<Card>(), List<Card>(), List<Card>()};
+    List<Card> tableau[7] = {List<Card>(), List<Card>(), List<Card>(), List<Card>(), List<Card>(), List<Card>(),
+                             List<Card>()};
+    int8_t selection[2] = {0, 0};
+    int8_t selectedCard=0;
+    Card *tempCard;
+    Vector tempPos = {0, 0};
+    float tempTime=0;
+    int8_t target[2] = {0, -1};
+    bool readyToRender= false;
+    double startTime;
+    double end;
+public:
+    bool dirty= true;
+
+    GameLogic(RenderBuffer *buffer, InputEventHandler *inputHandler);
+    ~GameLogic();
+    void Update(float delta);
+
+    void Input(int key, InputType type);
+
+    void Reset();
+    void GenerateDeck();
+    bool CanSolve();
+
+    void DoIntro(float delta);
+    void DrawPlayScene();
+    bool isReady(){return readyToRender;}
+    void DrawColumn(uint8_t x, uint8_t y, uint8_t selected, int8_t column);
+
+    void HandleNavigation(int key);
+    void PickAndPlace();
+
+    int8_t FirstNonFlipped(const List<Card> &deck);
+};

+ 94 - 0
GameLoop.cpp

@@ -0,0 +1,94 @@
+#include <dolphin/dolphin.h>
+#include <notification/notification_messages.h>
+#include "GameLoop.h"
+
+GameLoop::GameLoop() {
+    inputHandler = InputEventHandler();
+    dolphin_deed(DolphinDeedPluginGameStart);
+    input = (FuriPubSub *) furi_record_open(RECORD_INPUT_EVENTS);
+    gui = (Gui *) furi_record_open(RECORD_GUI);
+    canvas = gui_direct_draw_acquire(gui);
+    input_subscription = furi_pubsub_subscribe(input, &input_callback, this);
+    buffer = new RenderBuffer(128, 64);
+    render_mutex = (FuriMutex *) furi_mutex_alloc(FuriMutexTypeNormal);
+    logic = new GameLogic(buffer, &inputHandler);
+    if (!render_mutex) {
+        return;
+    }
+    notification_app = (NotificationApp *) furi_record_open(RECORD_NOTIFICATION);
+    notification_message_block(notification_app, &sequence_display_backlight_enforce_on);
+    buffer_thread_ptr = furi_thread_alloc_ex(
+        "BackBufferThread", 3 * 1024, render_thread, this);
+}
+
+void GameLoop::input_callback(const void *value, void *ctx) {
+    auto *inst = (GameLoop *) ctx;
+    const auto *event = (const InputEvent *) value;
+    inst->inputHandler.Set(event->key, event->type);
+    inst->logic->dirty = event->type != InputTypePress;
+    if (event->type == InputTypeLong) {
+        FURI_LOG_I("INPUT", "LONG INPUT %i", event->key);
+    }
+    if (event->type == InputTypeLong && event->key == InputKeyBack) {
+        inst->processing = false;
+    }
+}
+
+int32_t GameLoop::render_thread(void *ctx) {
+    auto *inst = (GameLoop *) ctx;
+    uint32_t last_tick = 0;
+    furi_thread_set_current_priority(FuriThreadPriorityIdle);
+
+    while (inst->renderRunning) {
+        uint32_t tick = furi_get_tick();
+        if (inst->logic->dirty) {
+            if(furi_mutex_acquire(inst->render_mutex, 20) == FuriStatusOk) {
+                inst->logic->dirty = false;
+                inst->logic->Update((tick - last_tick) / 1000.0f);
+                last_tick = tick;
+                furi_mutex_release(inst->render_mutex);
+            }
+        }
+        furi_thread_yield();
+    }
+
+    return 0;
+}
+
+void GameLoop::Start() {
+    dolphin_deed(DolphinDeedPluginGameStart);
+    if (!render_mutex) {
+        return;
+    }
+
+    furi_thread_start(buffer_thread_ptr);
+    furi_thread_set_current_priority(FuriThreadPriorityIdle);
+    while (processing) {
+        if (logic->isReady()) {
+            furi_mutex_acquire(render_mutex, FuriWaitForever);
+            buffer->render(canvas);
+            canvas_commit(canvas);
+            furi_mutex_release(render_mutex);
+        }
+        furi_thread_yield();
+    }
+}
+
+GameLoop::~GameLoop() {
+    notification_message_block(notification_app, &sequence_display_backlight_enforce_auto);
+    furi_pubsub_unsubscribe(input, input_subscription);
+
+    renderRunning = false;
+    furi_thread_join(buffer_thread_ptr);
+    furi_thread_free(buffer_thread_ptr);
+
+    canvas = NULL;
+    gui_direct_draw_release(gui);
+    furi_record_close(RECORD_GUI);
+    furi_record_close(RECORD_INPUT_EVENTS);
+    furi_record_close(RECORD_NOTIFICATION);
+    furi_mutex_free(render_mutex);
+
+    delete logic;
+    delete buffer;
+}

+ 36 - 0
GameLoop.h

@@ -0,0 +1,36 @@
+#pragma once
+
+#include <notification/notification.h>
+#include "utils/Input.h"
+#include "utils/RenderBuffer.h"
+#include "utils/Card.h"
+#include "utils/Vector.h"
+#include "GameLogic.h"
+
+
+class GameLoop {
+    Gui *gui;
+    Canvas *canvas;
+    InputEventHandler inputHandler;
+    FuriMutex *render_mutex;
+    FuriThread *buffer_thread_ptr;
+    RenderBuffer *buffer;
+    bool renderRunning = true;
+    FuriPubSub *input;
+    FuriPubSubSubscription *input_subscription;
+    NotificationApp *notification_app;
+    bool processing = true;
+
+    GameLogic *logic;
+
+    static void input_callback(const void *value, void *ctx);
+
+    static int32_t render_thread(void *ctx);
+
+public:
+    GameLoop();
+
+    ~GameLoop();
+
+    void Start();
+};

+ 0 - 171
TableauColumn.cpp

@@ -1,171 +0,0 @@
-#include "TableauColumn.h"
-#include "utils/RenderBuffer.h"
-
-#define max(a, b) a>b?a:b
-
-void TableauColumn::Reset() {
-    cards->clear();
-}
-
-void TableauColumn::AddCard(Card *c) {
-    check_pointer(c);
-    cards->add(c);
-}
-
-void TableauColumn::AddRange(List<Card> *hand) {
-    check_pointer(hand);
-    for (auto *item: *hand) {
-        check_pointer(item);
-        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
-    if (cards->count == 0) {
-        Card::RenderEmptyCard(x, y, buffer);
-        if (selected)
-            buffer->draw_rbox(x+1, y+1, x + 16, y + 22, Flip);
-        return;
-    }
-    uint8_t selection = cards->count - selection_from_end;
-    if (selected) selection--;
-
-    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;
-    check_pointer(cards);
-
-    // 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, false, buffer, 5);
-            // Increment loop start index and position
-            position += 4;
-            loop_start++;
-            had_top = true;
-        }
-        // Draw the front side of the first non-flipped card
-        check_pointer((*cards)[first_non_flipped]);
-        (*cards)[first_non_flipped]->Render(x, y + position, false, buffer,
-                                            cards->count == 1 ? 22 : 9);
-        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, false, buffer, 5);
-            position += 4;
-            loop_start++;
-        }
-        check_pointer((*cards)[selection]);
-        // Draw the front side of the selected card
-        (*cards)[selection]->Render(x, y + position, true, buffer, 9);
-        position += 8;
-        loop_start++; // Increment loop start index
-    }
-
-    //Draw the rest
-    for (uint8_t i = loop_start; i < loop_end; i++, position += 4) {
-        int height = 5;
-        if((i + 1) == loop_end) height = 22;
-        else if(i == selection || i == first_non_flipped) height = 9;
-        check_pointer((*cards)[i]);
-        if((*cards)[i]) {
-            (*cards)[i]->Render(x, y + position, i == selection, buffer, height);
-            if (i == selection || i == first_non_flipped) position += 4;
-        }
-    }
-}
-
-int8_t TableauColumn::FirstNonFlipped() {
-    int8_t index = -1;
-    if (cards->count > 0) {
-        for (auto *card: *cards) {
-            index++;
-            if (card && card->exposed) {
-                break;
-            }
-        }
-    }
-    return index;
-}
-
-TableauColumn::TableauColumn() {
-    cards = new List<Card>();
-}
-
-TableauColumn::~TableauColumn() {
-    delete cards;
-}
-
-void TableauColumn::Merge(TableauColumn *other) {
-    Card *prev = nullptr;
-    for (auto *item: *(other->cards)) {
-        if(prev == item) {
-            FURI_LOG_E("PLACE", "Possible circular dependency!");
-            break;
-        }
-        FURI_LOG_D("PLACE", "ADDING %i %i", item->suit, item->value);
-        if(!item){
-            FURI_LOG_E("PLACE", "nullptr in merge!");
-            break;
-        }
-        cards->add(item);
-    }
-    FURI_LOG_D("PLACE", "clear");
-    other->cards->soft_clear();
-}
-
-Card *TableauColumn::TopCard() {
-    return (*cards)[0];
-}
-
-uint8_t TableauColumn::Count() {
-    return cards->count;
-}
-
-Card *TableauColumn::Pop() {
-    return cards->pop();
-}
-
-void TableauColumn::Reveal() {
-    (*cards)[cards->count - 1]->exposed = true;
-}
-
-Card *TableauColumn::LastCard() {
-    if (cards->count > 0) {
-        return (*cards)[cards->count - 1];
-    }
-    return nullptr;
-}
-
-void TableauColumn::Clear() {
-    cards->soft_clear();
-}
-
-bool TableauColumn::CanPlace(TableauColumn *other) {
-    if (cards->count == 0) {
-        if(other->TopCard()->value == 11) FURI_LOG_D("TBLC", "placing first");
-        return other->TopCard()->value == 11;
-    }
-    Card *last = LastCard();
-    Card *top = other->TopCard();
-    int current_suit = last->suit / 2;
-    int other_suit = top->suit / 2;
-    if((current_suit + 1) % 2 == other_suit && (last->value + 1) % 13 == (top->value + 2) % 13) FURI_LOG_D("TBLC", "Adding at end");
-    else
-        FURI_LOG_D("TBLC", "CANT ADD, suit check %i, label check %i", (current_suit + 1) % 2 == other_suit, (last->value + 1) % 13 == (top->value + 2) % 13);
-
-    return (current_suit + 1) % 2 == other_suit && (last->value + 1) % 13 == (top->value + 2) % 13;
-}
-
-List<Card> *TableauColumn::splice(uint32_t selection) {
-    FURI_LOG_D("TBLC", "%li,  %li", cards->count-selection, selection);
-    return cards->splice(cards->count-selection-1, selection+1);
-}

+ 0 - 28
TableauColumn.h

@@ -1,28 +0,0 @@
-#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 Clear();
-    void AddRange(List<Card> *hand);
-    void Merge(TableauColumn *other);
-    void Render(uint8_t x, uint8_t y, bool selected, uint8_t selection, RenderBuffer *buffer);
-    Card* TopCard();
-    Card* LastCard();
-
-    int8_t FirstNonFlipped();
-    uint8_t Count();
-    Card* Pop();
-    void Reveal();
-    bool CanPlace(TableauColumn *other);
-    List<Card> *splice(uint32_t selection);
-};

+ 249 - 54
assets.h

@@ -1,5 +1,12 @@
 #pragma once
-#include "utils/Sprite.h"
+#include <furi.h>
+
+struct SpriteData {
+    uint8_t width = 0;
+    uint8_t height = 0;
+    uint8_t *data = NULL;
+};
+
 /*
       ██████   
    ███   ███   
@@ -7,8 +14,12 @@
 ████████████   
          ███   
 */
-const SpriteData sprite_4 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0xc, 0xa, 0x9, 0x1f, 0x0
+const SpriteData sprite_4 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0xc, 
+		0xa, 
+		0x9, 
+		0xf, 
+		0x8
 }};
 /*
 ████████████   
@@ -17,8 +28,12 @@ const SpriteData sprite_4 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
    ███         
    ███         
 */
-const SpriteData sprite_7 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x1, 0x19, 0x5, 0x3, 0x0
+const SpriteData sprite_7 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0xf, 
+		0x8, 
+		0x4, 
+		0x2, 
+		0x2
 }};
 /*
    ███         ███   
@@ -29,8 +44,14 @@ const SpriteData sprite_7 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
       ███   ███      
          ███         
 */
-const SpriteData sprite_hearths = (SpriteData) {.width=7, .height=7, .data=(uint8_t[]) {
-		0xe, 0x11, 0x22, 0x44, 0x22, 0x11, 0xe
+const SpriteData sprite_hearths = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x22, 
+		0x55, 
+		0x49, 
+		0x41, 
+		0x22, 
+		0x14, 
+		0x8
 }};
 /*
    ██████      
@@ -39,8 +60,12 @@ const SpriteData sprite_hearths = (SpriteData) {.width=7, .height=7, .data=(uint
 ███      ███   
 ███      ███   
 */
-const SpriteData sprite_A = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x1e, 0x5, 0x5, 0x1e, 0x0
+const SpriteData sprite_A = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x9, 
+		0xf, 
+		0x9, 
+		0x9
 }};
 /*
                      
@@ -51,8 +76,14 @@ const SpriteData sprite_A = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
       ███   ███   ███
    ███   ███   ███   
 */
-const SpriteData sprite_pattern_small = (SpriteData) {.width=7, .height=7, .data=(uint8_t[]) {
-		0x0, 0x54, 0x2a, 0x54, 0x2a, 0x54, 0x2a
+const SpriteData sprite_pattern_small = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x0, 
+		0x54, 
+		0x2a, 
+		0x54, 
+		0x2a, 
+		0x54, 
+		0x2a
 }};
 /*
                   ███                        
@@ -77,10 +108,28 @@ const SpriteData sprite_pattern_small = (SpriteData) {.width=7, .height=7, .data
       ███            ███   ███      ███      
       ███         ███      ███   ███         
 */
-const SpriteData sprite_pattern_big = (SpriteData) {.width=15, .height=21, .data=(uint8_t[]) {
-		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_pattern_big = (SpriteData) {.width=16, .height=21, .data=(uint8_t[]) {
+		0x40, 0x0, 
+		0xf2, 0x0, 
+		0xfe, 0x9, 
+		0xfe, 0x15, 
+		0xf2, 0x8, 
+		0x0, 0x0, 
+		0x8, 0x1, 
+		0x94, 0x27, 
+		0xc8, 0x3f, 
+		0xc0, 0x3f, 
+		0x80, 0x27, 
+		0x0, 0x0, 
+		0x40, 0x8, 
+		0xf2, 0x14, 
+		0xfe, 0x9, 
+		0xfe, 0x21, 
+		0xf2, 0x10, 
+		0x0, 0x10, 
+		0x88, 0x10, 
+		0x84, 0x12, 
+		0x44, 0xa
 }};
 /*
                                           █████████████████████████████████████████████            
@@ -128,13 +177,51 @@ const SpriteData sprite_pattern_big = (SpriteData) {.width=15, .height=21, .data
                      ███                                             ███                           
                         █████████████████████████████████████████████                              
 */
-const SpriteData sprite_main_image = (SpriteData) {.width=33, .height=44, .data=(uint8_t[]) {
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe0, 0x18, 0xd6, 0x15, 0x95, 0x55, 0x95, 0x15, 0x15, 0x15, 0x95, 0xd5, 0x95, 0x15, 0x15, 0x15, 0xe5, 0xf9, 0xfe, 0x0, 0x0, 0x0, 
-		0x0, 0x0, 0x0, 0xe0, 0x10, 0xd0, 0x10, 0x90, 0x50, 0x90, 0x1e, 0x11, 0x1d, 0x91, 0xd9, 0x95, 0x19, 0x11, 0x11, 0xf1, 0xd9, 0x9d, 0x19, 0x11, 0x11, 0xe1, 0xfe, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 
-		0x0, 0x0, 0x0, 0xff, 0x0, 0xf, 0x0, 0x7, 0x8, 0x7, 0x0, 0x2, 0x7, 0xf, 0x1f, 0xf, 0x7, 0x2, 0x0, 0xff, 0xff, 0xfd, 0xe1, 0x81, 0xfe, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x0, 0x0, 0x0, 
-		0x0, 0x0, 0x0, 0xff, 0x0, 0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x0, 0x78, 0x84, 0x78, 0x0, 0xfc, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 
-		0x0, 0x0, 0x0, 0x1, 0x6, 0x3a, 0xca, 0x4a, 0x4a, 0x4a, 0x5a, 0xca, 0x4a, 0x4a, 0x4a, 0x4a, 0x5a, 0xca, 0x4a, 0x59, 0xc7, 0x3f, 0xff, 0xff, 0x1f, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
-		0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x9, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
+const SpriteData sprite_main_image = (SpriteData) {.width=40, .height=44, .data=(uint8_t[]) {
+		0x0, 0xc0, 0xff, 0x1f, 0x0, 
+		0x0, 0x20, 0x0, 0x20, 0x0, 
+		0x0, 0xe0, 0xff, 0x2f, 0x0, 
+		0x0, 0x10, 0x0, 0x30, 0x0, 
+		0x0, 0xf0, 0xff, 0x37, 0x0, 
+		0x0, 0x8, 0x0, 0x38, 0x0, 
+		0x0, 0x28, 0x41, 0x38, 0x0, 
+		0x0, 0xa8, 0xe2, 0x38, 0x0, 
+		0x0, 0xf8, 0xff, 0x3b, 0x0, 
+		0x0, 0x4, 0x0, 0x3c, 0x0, 
+		0x0, 0x94, 0x20, 0x3c, 0x0, 
+		0x0, 0x54, 0x71, 0x3c, 0x0, 
+		0xf0, 0xff, 0xff, 0x3d, 0x0, 
+		0x8, 0x0, 0x8, 0x3e, 0x0, 
+		0x28, 0x41, 0x18, 0x3e, 0x0, 
+		0xa8, 0xe2, 0x38, 0x3e, 0x0, 
+		0xa8, 0xf2, 0xf9, 0x3e, 0x0, 
+		0xa8, 0xfa, 0x1b, 0x3f, 0x0, 
+		0xa8, 0xf2, 0x39, 0x3f, 0x0, 
+		0x28, 0xe1, 0x38, 0x3f, 0x0, 
+		0x8, 0x40, 0x38, 0x3f, 0x0, 
+		0x8, 0x0, 0x78, 0x1f, 0x0, 
+		0x8, 0x0, 0x78, 0x1f, 0x0, 
+		0x8, 0x0, 0xf8, 0xf, 0x0, 
+		0x8, 0x0, 0xf8, 0xf, 0x0, 
+		0x8, 0x1, 0xf8, 0x7, 0x0, 
+		0x88, 0x43, 0xfa, 0x7, 0x0, 
+		0xc8, 0xa7, 0xfa, 0x7, 0x0, 
+		0xe8, 0xaf, 0xfa, 0x7, 0x0, 
+		0xc8, 0xa7, 0xfa, 0x3, 0x0, 
+		0x88, 0xa3, 0xfa, 0x3, 0x0, 
+		0x8, 0x41, 0xfa, 0x3, 0x0, 
+		0x8, 0x0, 0xf8, 0x3, 0x0, 
+		0xf0, 0xff, 0xf7, 0x1, 0x0, 
+		0x10, 0x0, 0xf0, 0x1, 0x0, 
+		0xe0, 0xff, 0xef, 0x1, 0x0, 
+		0x20, 0x4, 0xe9, 0x1, 0x0, 
+		0x20, 0x0, 0xe0, 0x0, 0x0, 
+		0xc0, 0xff, 0xdf, 0x0, 0x0, 
+		0x40, 0x8, 0xd2, 0x0, 0x0, 
+		0x40, 0x0, 0xc0, 0x0, 0x0, 
+		0x80, 0xff, 0xbf, 0x0, 0x0, 
+		0x80, 0x0, 0x80, 0x0, 0x0, 
+		0x0, 0xff, 0x7f, 0x0, 0x0
 }};
 /*
    ██████      
@@ -143,8 +230,12 @@ const SpriteData sprite_main_image = (SpriteData) {.width=33, .height=44, .data=
 ███   ███      
    ███   ███   
 */
-const SpriteData sprite_Q = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0xe, 0x11, 0x9, 0x16, 0x0
+const SpriteData sprite_Q = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x9, 
+		0x9, 
+		0x5, 
+		0xa
 }};
 /*
    ██████      
@@ -153,8 +244,12 @@ const SpriteData sprite_Q = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
    ███         
 ████████████   
 */
-const SpriteData sprite_2 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x12, 0x19, 0x15, 0x12, 0x0
+const SpriteData sprite_2 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x9, 
+		0x4, 
+		0x2, 
+		0xf
 }};
 /*
 █████████      
@@ -163,8 +258,12 @@ const SpriteData sprite_2 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
          ███   
 █████████      
 */
-const SpriteData sprite_3 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x11, 0x15, 0x15, 0xa, 0x0
+const SpriteData sprite_3 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x7, 
+		0x8, 
+		0x6, 
+		0x8, 
+		0x7
 }};
 /*
 ███      ███   
@@ -173,8 +272,12 @@ const SpriteData sprite_3 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
 ███   ███   ███
 ███      ███   
 */
-const SpriteData sprite_10 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x1f, 0x0, 0xe, 0x11, 0xe
+const SpriteData sprite_10 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x9, 
+		0x15, 
+		0x15, 
+		0x15, 
+		0x9
 }};
 /*
 ███      ███   
@@ -183,8 +286,12 @@ const SpriteData sprite_10 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]
 ███   ███      
 ███      ███   
 */
-const SpriteData sprite_K = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x1f, 0x4, 0xa, 0x11, 0x0
+const SpriteData sprite_K = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x9, 
+		0x5, 
+		0x3, 
+		0x5, 
+		0x9
 }};
 /*
 ████████████   
@@ -193,8 +300,12 @@ const SpriteData sprite_K = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
          ███   
 ████████████   
 */
-const SpriteData sprite_5 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x17, 0x15, 0x15, 0x19, 0x0
+const SpriteData sprite_5 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0xf, 
+		0x1, 
+		0x7, 
+		0x8, 
+		0xf
 }};
 /*
    ██████      
@@ -203,8 +314,12 @@ const SpriteData sprite_5 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
 ███      ███   
    ██████      
 */
-const SpriteData sprite_6 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0xe, 0x15, 0x15, 0x8, 0x0
+const SpriteData sprite_6 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x1, 
+		0x7, 
+		0x9, 
+		0x6
 }};
 /*
    ██████      
@@ -213,8 +328,12 @@ const SpriteData sprite_6 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
          ███   
    ██████      
 */
-const SpriteData sprite_9 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x2, 0x15, 0x15, 0xe, 0x0
+const SpriteData sprite_9 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x9, 
+		0xe, 
+		0x8, 
+		0x6
 }};
 /*
          ███         
@@ -225,8 +344,14 @@ const SpriteData sprite_9 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
       ███   ███      
          ███         
 */
-const SpriteData sprite_diamonds = (SpriteData) {.width=7, .height=7, .data=(uint8_t[]) {
-		0x8, 0x14, 0x22, 0x41, 0x22, 0x14, 0x8
+const SpriteData sprite_diamonds = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x8, 
+		0x14, 
+		0x22, 
+		0x41, 
+		0x22, 
+		0x14, 
+		0x8
 }};
 /*
 ████████████   
@@ -235,8 +360,12 @@ const SpriteData sprite_diamonds = (SpriteData) {.width=7, .height=7, .data=(uin
 ███      ███   
 ████████████   
 */
-const SpriteData sprite_8 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x1f, 0x15, 0x15, 0x1f, 0x0
+const SpriteData sprite_8 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0xf, 
+		0x9, 
+		0xf, 
+		0x9, 
+		0xf
 }};
 /*
          ███   
@@ -245,8 +374,12 @@ const SpriteData sprite_8 = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
 ███      ███   
    ██████      
 */
-const SpriteData sprite_J = (SpriteData) {.width=5, .height=5, .data=(uint8_t[]) {
-		0x8, 0x10, 0x10, 0xf, 0x0
+const SpriteData sprite_J = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x8, 
+		0x8, 
+		0x8, 
+		0x9, 
+		0x6
 }};
 /*
          ███         
@@ -257,8 +390,14 @@ const SpriteData sprite_J = (SpriteData) {.width=5, .height=5, .data=(uint8_t[])
          ███         
       █████████      
 */
-const SpriteData sprite_spades = (SpriteData) {.width=7, .height=7, .data=(uint8_t[]) {
-		0x18, 0x1c, 0x4e, 0x7f, 0x4e, 0x1c, 0x18
+const SpriteData sprite_spades = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x8, 
+		0x1c, 
+		0x3e, 
+		0x7f, 
+		0x6b, 
+		0x8, 
+		0x1c
 }};
 /*
       ████████████               ████████████      ███                                    ███   
@@ -271,7 +410,32 @@ const SpriteData sprite_spades = (SpriteData) {.width=7, .height=7, .data=(uint8
       ████████████               ████████████      ██████   █████████   ███   ███         ██████
 */
 const SpriteData sprite_start = (SpriteData) {.width=32, .height=8, .data=(uint8_t[]) {
-		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
+		0x3c, 0x78, 0x2, 0x40, 
+		0x7e, 0x84, 0x2, 0x40, 
+		0xff, 0x4, 0xe7, 0xf4, 
+		0xff, 0x18, 0x12, 0x4d, 
+		0xff, 0x60, 0xe2, 0x45, 
+		0xff, 0x80, 0x12, 0x45, 
+		0x7e, 0x84, 0x92, 0x45, 
+		0x3c, 0x78, 0x76, 0xc5
+}};
+/*
+███         ███                     ███                  ███            ████████████               ███                                                                  ███                                       
+███         ███                     ███                  ███         ██████████████████            ███                                                                  ███                                       
+███         ███      █████████      ███         ████████████      ████████████████████████      █████████      █████████                  ████████████   █████████      ███      ███            ███   █████████   
+███████████████   ███         ███   ███      ███         ███      ████████████████████████         ███      ███         ███            ███            ███         ███   ███         ███      ███   ███         ███
+███         ███   ███         ███   ███      ███         ███      ████████████████████████         ███      ███         ███               █████████   ███         ███   ███         ███      ███   ███████████████
+███         ███   ███         ███   ███      ███         ███         ██████████████████            ███      ███         ███                        ██████         ███   ███            ██████      ███            
+███         ███      █████████      ███         ████████████            ████████████               ██████      █████████               ████████████      █████████      ███            ██████         ████████████
+*/
+const SpriteData sprite_solve = (SpriteData) {.width=72, .height=7, .data=(uint8_t[]) {
+		0x11, 0x10, 0x8, 0xf, 0x2, 0x0, 0x0, 0x1, 0x0, 
+		0x11, 0x10, 0x88, 0x1f, 0x2, 0x0, 0x0, 0x1, 0x0, 
+		0x91, 0x13, 0xcf, 0x3f, 0xe7, 0xc0, 0x3b, 0x9, 0x1d, 
+		0x5f, 0x94, 0xc8, 0x3f, 0x12, 0x21, 0x44, 0x91, 0x22, 
+		0x51, 0x94, 0xc8, 0x3f, 0x12, 0xc1, 0x45, 0x91, 0x3e, 
+		0x51, 0x94, 0x88, 0x1f, 0x12, 0x1, 0x46, 0x61, 0x2, 
+		0x91, 0x13, 0xf, 0xf, 0xe6, 0xe0, 0x39, 0x61, 0x3c
 }};
 /*
       █████████      
@@ -282,8 +446,14 @@ const SpriteData sprite_start = (SpriteData) {.width=32, .height=8, .data=(uint8
          ███         
       █████████      
 */
-const SpriteData sprite_clubs = (SpriteData) {.width=7, .height=7, .data=(uint8_t[]) {
-		0x1c, 0x1c, 0x4b, 0x7f, 0x4b, 0x1c, 0x1c
+const SpriteData sprite_clubs = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x1c, 
+		0x1c, 
+		0x6b, 
+		0x7f, 
+		0x6b, 
+		0x8, 
+		0x1c
 }};
 /*
 ██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
@@ -316,9 +486,34 @@ const SpriteData sprite_clubs = (SpriteData) {.width=7, .height=7, .data=(uint8_
             ███                     ███                  █████████               █████████            ███                                                                                          ███            
                                                                                                       ████████████████████████████████████████████████████████████████████████████████████████████████            
 */
-const SpriteData sprite_logo = (SpriteData) {.width=70, .height=29, .data=(uint8_t[]) {
-		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, 0xc8, 0x28, 0x48, 0x88, 0x48, 0x29, 0xc9, 0x9, 0x9, 0x89, 0x48, 0x28, 0x48, 0x88, 0x8, 0x9, 0x9, 0x89, 0xc9, 0xe8, 0xc8, 0x88, 0x8, 0x9, 0x89, 0x88, 0x68, 0xe8, 0x69, 0x89, 0x88, 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, 0x1, 0x2, 0x4, 0x8, 0x4, 0x2, 0x1, 0x0, 0x1, 0x2, 0x4, 0x8, 0x4, 0x2, 0x1, 0x0, 0x3, 0x3, 0x9, 0xf, 0x9, 0x3, 0x3, 0x0, 0x3, 0x3, 0x9, 0xf, 0x9, 0x3, 0x3, 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
+const SpriteData sprite_logo = (SpriteData) {.width=72, .height=29, .data=(uint8_t[]) {
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 
+		0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 
+		0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 
+		0xc1, 0x7, 0x0, 0x63, 0x8, 0x0, 0x3, 0x0, 0x20, 
+		0xe1, 0xf, 0x0, 0x63, 0xc, 0x0, 0x3, 0x0, 0x20, 
+		0x71, 0x1c, 0x0, 0x63, 0xc, 0x0, 0x3, 0x0, 0x20, 
+		0x31, 0x18, 0x0, 0x3, 0xc, 0x0, 0x0, 0x0, 0x20, 
+		0x31, 0x0, 0xf, 0x63, 0x3e, 0x1f, 0x63, 0xe3, 0x20, 
+		0x71, 0x80, 0x1f, 0x63, 0x3e, 0x3f, 0xe3, 0xf3, 0x21, 
+		0xe1, 0xc3, 0x39, 0x63, 0x8c, 0x31, 0x63, 0x18, 0x23, 
+		0x81, 0xcf, 0x30, 0x63, 0xc, 0x30, 0x63, 0x18, 0x23, 
+		0x1, 0xdc, 0x30, 0x63, 0xc, 0x3c, 0x63, 0xf8, 0x23, 
+		0x31, 0xd8, 0x30, 0x63, 0xc, 0x3f, 0x63, 0xf8, 0x23, 
+		0x31, 0xd8, 0x30, 0x63, 0x8c, 0x33, 0x63, 0x18, 0x20, 
+		0x71, 0xdc, 0x39, 0x63, 0x8c, 0x31, 0x63, 0x18, 0x23, 
+		0xe1, 0x8f, 0x1f, 0x63, 0xbc, 0x3f, 0x63, 0xf0, 0x21, 
+		0xc1, 0x7, 0xf, 0x63, 0x38, 0x37, 0x63, 0xe0, 0x20, 
+		0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 
+		0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 
+		0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x2, 
+		0x44, 0x10, 0x10, 0x38, 0x4, 0x0, 0x0, 0x8, 0x2, 
+		0xaa, 0x28, 0x38, 0x38, 0x14, 0xe0, 0x0, 0x4, 0x2, 
+		0x92, 0x44, 0x7c, 0xd6, 0x34, 0x25, 0x89, 0xae, 0x2, 
+		0x82, 0x82, 0xfe, 0xfe, 0x54, 0x25, 0x55, 0xa5, 0x2, 
+		0x44, 0x44, 0xd6, 0xd6, 0x54, 0x22, 0x55, 0x45, 0x2, 
+		0x28, 0x28, 0x10, 0x10, 0x34, 0xe2, 0x88, 0x44, 0x2, 
+		0x10, 0x10, 0x38, 0x38, 0x4, 0x0, 0x0, 0x0, 0x2, 
+		0x0, 0x0, 0x0, 0x0, 0xfc, 0xff, 0xff, 0xff, 0x3
 }};

+ 4 - 114
solitaire.cpp

@@ -1,41 +1,5 @@
 #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, 150);
-    if (g == nullptr || status != FuriStatusOk) return;
-    g->Render(canvas);
-    UNUSED(canvas);
-    UNUSED(g);
-    furi_mutex_release(mutex);
-}
+#include "GameLoop.h"
 
 #ifdef __cplusplus
 extern "C"
@@ -43,84 +7,10 @@ extern "C"
 #endif
 int32_t solitaire_app(void *p) {
     UNUSED(p);
-    FURI_LOG_D("MEMORY", "Free %i", memmgr_get_free_heap());
     int32_t return_code = 0;
-    size_t start = memmgr_get_free_heap();
-    Game *game = new Game;
-    FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
-    mutex = furi_mutex_alloc(FuriMutexTypeNormal);
-    if (mutex) {
-        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() / 20);
-
-        auto gui = static_cast<Gui *>(furi_record_open("gui"));
-        gui_add_view_port(gui, view_port, GuiLayerFullscreen);
-
-        AppEvent event;
-        game->Reset();
-
-        while (processing) {
-            FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever);
-            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) {
-                        game->Press(event.input->key);
-                    }
-                } else if (event.type == EventTypeTick) {
-                    game->Update();
-                }
-            }
-
-            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);
-    start = memmgr_get_free_heap()-start;
-    if(start!=0)
-        FURI_LOG_E("MEMORY", "Leak detected %i", start);
+    auto *loop = new GameLoop();
+    loop->Start();
+    delete loop;
     return return_code;
 }
 

+ 106 - 17
utils/Buffer.cpp

@@ -1,63 +1,118 @@
+#include <toolbox/compress.h>
+#include <firmware_api_table.h>
 #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));
+    data = (uint8_t *) malloc(sizeof(uint8_t) * (int)ceil(w / 8.0) * ceil(h));
 }
 
-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);
+Buffer::Buffer(uint8_t *d, uint8_t w, uint8_t h) {
+    _width = w;
+    _height = h;//ceil(i->height / 8.0);
+    data = d;
     remove_buffer = false;
 }
 
 Buffer::~Buffer() {
-    FURI_LOG_D("BUFFER", "Buffer removed");
-
-    if (data && remove_buffer)
+    FURI_LOG_I("BUFFER", "deleting buffer");
+    if (remove_buffer) {
         delete data;
+    }
 }
 
 bool Buffer::test_pixel(uint8_t x, uint8_t y) {
-    return data[(y >> 3) * _width + x] & (1 << (y & 7));
+    return data[pixel(x,y)] & (1 << (x & 7));
 }
 
+/**
+ * @brief Copies the contents of the buffer into another array.
+ *
+ * This function copies the data from the current buffer into the provided array. The size of the array should be equal
+ * to the size of the buffer.
+ *
+ * @param other The array to copy the data into.
+ * @return void
+ */
 void Buffer::copy_into(uint8_t *other) {
-    int size = (int) (_width * ceil(_height / 8.0));
+    int size = (int) (_height * ceil(_width / 8.0));
     for (int i = 0; i < size; i++) {
         other[i] = data[i];
     }
 }
 
+/**
+ * @brief Copies data from another buffer into this buffer.
+ *
+ * This function copies the data from the given buffer into this buffer.
+ * The size of the data being copied is calculated according to the width
+ * and height of this buffer. The data is copied byte by byte.
+ *
+ * @param other A pointer to the data buffer to copy from.
+ */
 void Buffer::copy_from(uint8_t *other) {
-    int size = (int) (_width * ceil(_height / 8.0));
+    int size = (int) (_height * ceil(_width / 8.0));
     for (int i = 0; i < size; i++) {
         data[i] = other[i];
     }
 }
 
+/**
+ * @brief Clears the buffer by setting all data elements to 0.
+ *
+ * The buffer size is calculated based on the width and height attributes.
+ * The width and height are multiplied to get the size in bytes needed to store the buffer data.
+ * If the height is not a multiple of 8, the size is rounded up to the next multiple of 8.
+ * Each element in the data array is set to 0 using a for loop.
+ */
 void Buffer::clear() {
-    int size = (int) (_width * ceil(_height / 8.0));
-    for (int i = 0; i < size; i++) {
+    int size = (int) (_height * ceil(_width / 8.0));
+    memset(data, 0, sizeof(uint8_t)*size);
+/*    for (int i = 0; i < size; i++) {
         data[i] = 0;
-    }
+    }*/
 }
 
 
+/**
+* @brief Checks if the given coordinates are within the bounds of the buffer.
+*
+* @param x The x-coordinate.
+* @param y The y-coordinate.
+* @return True if the coordinates are within the buffer's bounds, false otherwise.
+*/
 bool Buffer::test_coordinate(int x, int y) const {
     return x >= 0 && y >= 0 && x < _width && y < _height;
 }
 
+/**
+ * @brief Sets a pixel in the buffer with a given draw mode, after checking if the pixel is within the buffer dimensions.
+ *
+ * @param x The x-coordinate of the pixel.
+ * @param y The y-coordinate of the pixel.
+ * @param draw_mode The draw mode to use for setting the pixel.
+ *
+ * This function checks if the pixel at the given coordinates is within the buffer dimensions by calling the `test_pixel` function.
+ * If the pixel is within the buffer, it calls the `set_pixel` function to set the pixel with the provided draw mode.
+ *
+ * @see test_pixel
+ * @see set_pixel
+ */
 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);
 }
 
+/**
+ * Sets the pixel at the specified coordinates to the specified draw mode.
+ *
+ * @param x The x-coordinate of the pixel.
+ * @param y The y-coordinate of the pixel.
+ * @param draw_mode The draw mode to apply to the pixel.
+ */
 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;
+    uint8_t bit = 1 << (x & 7);
+    uint8_t *p = &data[pixel(x,y)];
 
     switch (draw_mode) {
         case Black:
@@ -72,9 +127,43 @@ void Buffer::set_pixel(int16_t x, int16_t y, PixelColor draw_mode) {
     }
 }
 
+/**
+ * @brief Swaps the buffer with the provided buffer.
+ *
+ * This function swaps the internal buffer with the buffer passed as a parameter. After the swap,
+ * the caller will have ownership of the current internal buffer.
+ *
+ * @param buffer A pointer to the buffer to swap with.
+ */
 void Buffer::swap(uint8_t *&buffer) {
     uint8_t *back = data;
     data = buffer;
     buffer = back;
 }
 
+
+
+uint16_t Buffer::pixel(uint8_t x, uint8_t y) {
+    return (y*width()+x) / 8;
+}
+
+uint8_t *Buffer::decode(const Icon *icon) {
+    uint8_t* p_icon_data;
+    uint8_t width = icon_get_width(icon);
+    uint8_t height = icon_get_height(icon);
+    uint16_t size = (width/8)*height;
+
+    // Create decoder instance and grab decoded data pointer
+    // - Ideally re-use the CompressIcon and free on app exit
+    CompressIcon* compress_icon = compress_icon_alloc();
+    compress_icon_decode(compress_icon, icon_get_data(icon), &p_icon_data);
+
+    // Copy the data since the decoder only holds on temporarily
+    auto* icon_data = static_cast<uint8_t *>(malloc(size));
+    memcpy(icon_data, p_icon_data, size);
+
+    // Free decoder instance as we're done here
+    compress_icon_free(compress_icon);
+
+    return icon_data;
+}

+ 12 - 1
utils/Buffer.h

@@ -1,5 +1,6 @@
 #pragma once
 #include <furi.h>
+#include <gui/icon.h>
 
 enum PixelColor {
     Black, //or
@@ -16,12 +17,19 @@ enum DrawMode {
 };
 
 struct SpriteData;
+/**
+ * @class Buffer
+ * @brief The Buffer class represents a buffer for storing pixel data.
+ *
+ * The Buffer class provides methods for manipulating the pixel data stored in the buffer.
+ */
 class Buffer {
     uint8_t _width=0, _height=0;
+protected:
     bool remove_buffer=true;
 public:
     uint8_t *data;
-    explicit Buffer(const SpriteData *icon);
+    Buffer(uint8_t *data, uint8_t w, uint8_t h);
     Buffer(uint8_t width, uint8_t height);
     virtual ~Buffer();
 
@@ -38,4 +46,7 @@ public:
     uint8_t width() { return _width; }
     uint8_t height() { return _height; }
     virtual void swap(uint8_t *&buffer);
+    uint16_t pixel(uint8_t x, uint8_t y);
+
+    uint8_t* decode(const Icon *icon);
 };

+ 49 - 26
utils/Card.cpp

@@ -1,43 +1,45 @@
 #include "Card.h"
 #include "RenderBuffer.h"
+#include "Sprite.h"
 #include "../assets.h"
 
 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),
+    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)
+    Sprite(sprite_hearths, BlackOnly),
+    Sprite(sprite_spades, BlackOnly),
+    Sprite(sprite_diamonds, 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, uint8_t size_limit) {
     uint8_t height = y + fmin(size_limit, 22);
+
     if (exposed) {
         buffer->draw_rbox(x, y, x + 16, height, White);
         buffer->draw_rbox_frame(x, y, x + 16, height, Black);
-        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 + 6, (float) y + 5}, 0);
+        buffer->draw(&(suits[suit]), (Vector) {(float) x + 12, (float) y + 5}, 0);
 
         if (size_limit > 8) {
-            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);
+            buffer->draw(&(letters[value]), (Vector) {(float) x + 10, (float) y + 16}, M_PI);
+            buffer->draw(&(suits[suit]), (Vector) {(float) x + 4, (float) y + 16}, M_PI);
         }
         if (selected) {
             buffer->draw_box(x + 1, y + 1, x + 16, height, Flip);
@@ -57,15 +59,36 @@ void Card::RenderBack(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer,
 
     buffer->draw_box(x + 1, y + 1, x + 16, height, White);
     buffer->draw_rbox_frame(x, y, x + 16, height, Black);
-    buffer->draw(&backSide, (Vector) {(float) x + 9, (float) y + 12}, 15, fmin(size_limit, 22), 0);
+    buffer->draw(&backSide, (Vector) {(float) x + 9, (float) y + 11}, 15, fmin(size_limit, 22), 0);
     if (selected) {
         buffer->draw_box(x + 1, y + 1, x + 16, height, Flip);
     }
 }
 
-bool Card::CanPlace(Card *a, Card *b) {
-    if (a == nullptr) {
-        return b->value == 12;
+bool Card::CanPlaceFoundation(Card *where) {
+    if (!where) {
+        return value == ACE;
+    }
+    return where->suit == suit && ((where->value + 1) % 13 == value % 13);
+}
+
+bool Card::CanPlaceColumn(Card *where) {
+    if (!where) {
+        return value == KING;
+    }
+    return where->suit % 2 == (suit + 1) % 2 && (value + 1) == where->value ;
+}
+
+void Card::print() {
+    FURI_LOG_D("Card", "%i %i", suit, value);
+}
+
+void Card::TryRender(Card *c, uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer, uint8_t size_limit) {
+    if (c) {
+        c->Render(x, y, selected, buffer, size_limit);
+    } else {
+        RenderEmptyCard(x, y, buffer);
+        if(selected)
+            buffer->draw_rbox(x+1, y+1, x + 16, y + 22, Flip);
     }
-    return a->suit == b->suit && ((b->value + 1) % 13 == (a->value + 2) % 13);
 }

+ 32 - 5
utils/Card.h

@@ -4,15 +4,42 @@
 
 class RenderBuffer;
 
+enum CardValue {
+    NONE = -1,
+    TWO = 0,
+    THREE = 1,
+    FOUR = 2,
+    FIVE = 3,
+    SIX = 4,
+    SEVEN = 5,
+    EIGHT = 6,
+    NINE = 7,
+    TEN = 8,
+    JACK = 9,
+    QUEEN = 10,
+    KING = 11,
+    ACE = 12,
+};
+
 struct Card {
     uint8_t suit = 0;
-    uint8_t value = 0;
+    CardValue value = NONE;
     bool exposed = false;
 
-    Card(uint8_t s, uint8_t v) : suit(s), value(v) {}
+    Card(uint8_t s, uint8_t v) : suit(s), value((CardValue) v) {}
+
+    void Render(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer, uint8_t size_limit = 22);
 
-    void Render(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer, uint8_t size_limit);
     static void RenderEmptyCard(uint8_t x, uint8_t y, RenderBuffer *buffer);
-    static void RenderBack(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer, uint8_t size_limit);
-    static bool CanPlace(Card *where, Card *what);
+
+    static void RenderBack(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer, uint8_t size_limit= 22);
+
+    static void TryRender(Card *c, uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer, uint8_t size_limit= 22);
+
+
+    bool CanPlaceFoundation(Card *where);
+
+    bool CanPlaceColumn(Card *where);
+
+    void print();
 };

+ 4 - 4
utils/Helpers.cpp

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

+ 5 - 2
utils/Helpers.h

@@ -12,8 +12,11 @@
 #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);
+#define CHECK_HEAP() FURI_LOG_I("Solitaire", "Free/total heap: %zu / %zu", memmgr_get_free_heap(), memmgr_get_total_heap())
+char *get_basename(const char *path);
+#ifndef basename
+#define basename(path) get_basename(path)
+#endif
 
 void check_ptr(void *p, const char *file, int line, const char *func);
 

+ 47 - 0
utils/Input.h

@@ -0,0 +1,47 @@
+#pragma once
+
+#include <input/input.h>
+#include "List.h"
+
+class InputEventHandler {
+    struct Subscription {
+        void *ctx;
+        size_t id;
+
+        void (*callback)(void *ctx, int, InputType);
+    };
+
+    List<Subscription> events;
+    size_t _id = 0;
+public:
+    InputEventHandler() {
+        events = List<Subscription>();
+    }
+
+    ~InputEventHandler() {
+        events.deleteData();
+        events.clear();
+    }
+
+    int subscribe(void *caller, void(*p)(void *, int, InputType)) {
+        size_t currID=_id;
+        _id++;
+        events.push_back(new Subscription{.ctx = caller, .id=currID, .callback=p});
+        return currID;
+    }
+
+    void unsubscribe(size_t id) {
+        for(auto item : events){
+            if (item->id == id) {
+                events.remove(item);
+                return;
+            }
+        }
+    }
+
+    void Set(int key, InputType type) {
+        for (auto *evt: events) {
+            evt->callback(evt->ctx, key, type);
+        }
+    }
+};

+ 208 - 216
utils/List.h

@@ -1,290 +1,282 @@
 #pragma once
 
 #include <furi.h>
-#include <iterator>
+#include <cstdio>
 #include "Helpers.h"
 
 template<typename T>
-struct ListItem {
-    ListItem *next;
-    T *data;
-};
+class List {
+private:
+    struct Node {
+        T *data;
+        Node *next;
+        Node *prev;
+    };
+    size_t count=0;
+    Node *head;
+    Node *tail;
+
+    class Iterator {
+        Node *node;
+
+    public:
+        explicit Iterator(Node *node) : node(node) {}
+
+        T *operator*() {
+            return node->data;
+        }
 
-template<typename T>
-struct ListIterator {
-    using iterator_category = std::forward_iterator_tag;
+        Iterator &operator++() {
+            if (node) {
+                node = node->next;
+            }
+            return *this;
+        }
 
-    ListItem<T> *current;
+        Iterator operator++(int) {
+            Iterator tmp(*this);
+            operator++();
+            return tmp;
+        }
 
-    explicit ListIterator(ListItem<T> *node) : current(node) {}
+        Iterator &operator--() {
+            if (node) {
+                node = node->prev;
+            }
+            return *this;
+        }
 
-    ListIterator &operator++() {
-        current = current->next;
-        return *this;
-    }
+        Iterator operator--(int) {
+            Iterator tmp(*this);
+            operator--();
+            return tmp;
+        }
 
-    ListIterator operator++(int) {
-        ListIterator iterator = *this;
-        ++(*this);
-        return iterator;
-    }
+        bool operator==(const Iterator &rhs) const {
+            return node == rhs.node;
+        }
 
-    bool operator==(const ListIterator &other) const {
-        return current == other.current;
-    }
+        bool operator!=(const Iterator &rhs) const {
+            return node != rhs.node;
+        }
+    };
 
-    bool operator!=(const ListIterator &other) const {
-        return current != other.current;
-    }
+public:
+    explicit List() : head(nullptr), tail(nullptr) {}
 
-    T *operator*() const {
-        return (current->data);
+    Iterator begin() const {
+        return Iterator(head);
     }
 
-    T *operator->() const {
-        return current->data;
+    Iterator end() const {
+        return Iterator(nullptr);
     }
-};
 
-
-template<typename T>
-struct List {
-    uint32_t count;
-    ListItem<T> *start = nullptr;
-
-    List() : count(0) {}
+    Iterator last() const {
+        return Iterator(tail);
+    }
 
     ~List() {
-        FURI_LOG_D("App", "List emptied");
         clear();
     }
 
-    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;
+        while (head) {
+            Node *toDelete = head;
+            head = head->next;
+            delete toDelete;
         }
+        tail = nullptr;
         count = 0;
     }
 
-    void add(T *data) {
-        check_pointer(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 deleteData(){
+        Node *current = head;
+        while (current) {
+            delete current->data;
+            current=current->next;
         }
     }
 
-    void add_front(T *data) {
-        count++;
-        ListItem<T> *c = start;
-        start = new ListItem<T>();
-        start->data = data;
-        start->next = c;
-    }
-
-    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 remove(T *value) {
+        Node *current= head;
+        while (current){
+            if(current->data == value){
+                if (current->prev)
+                    current->prev->next = current->next;
+                else
+                    head = current->next;
+
+                if (current->next)
+                    current->next->prev = current->prev;
+                else
+                    tail = current->prev;
+
+                delete current;
+                --count;
             }
+            current=current->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 push_back(T *value) {
+        Node *newNode = new Node{value, nullptr, tail};
+        if (tail)
+            tail->next = newNode;
+        else
+            head = newNode;
 
-    void remove(uint32_t index, uint32_t amount) {
-        auto *result = splice(index, amount);
-        delete result;
+        tail = newNode;
+        ++count;
     }
 
-    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 currentItem = start;
-            ListItem<T> *prevItem = nullptr;
-            while (curr_id < index) {
-                prevItem = currentItem;
-                currentItem = currentItem->next;
-                if (!currentItem) return removedElements;
-                curr_id++;
-            }
+    void push_front(T *value) {
+        Node *newNode = new Node{value, head, nullptr};
+        if (head)
+            head->prev = newNode;
+        else
+            tail = newNode;
 
-            ListItem<T> *temp;
-            for (uint32_t i = 0; i < m; i++) {
-                temp = currentItem->next;
-                if (currentItem->data) {
-                    removedElements->add(currentItem->data);
-                }
-                delete currentItem;
-                currentItem = temp;
-                count--;
-            }
+        head = newNode;
+        ++count;
+    }
 
-            if (prevItem) {
-                prevItem->next = currentItem;
-            } else {
-                start = currentItem; // Update start if removing from the beginning.
-            }
-        }
+    T *pop_back() {
+        if (count == 0)
+            return nullptr;
 
-        return removedElements;
+        Node *toDelete = tail;
+        tail = tail->prev;
+        if (tail)
+            tail->next = nullptr;
+        else
+            head = nullptr;
+
+        T *data = toDelete->data;
+        delete toDelete;
+        --count;
+        return data;
     }
 
     T *pop_front() {
-        if (!start) {
-            // List is empty, nothing to remove
+        if (count == 0)
             return nullptr;
-        }
 
-        ListItem<T> *front = start;
-        start = start->next; // Update the start pointer to the next element
+        Node *toDelete = head; // Change this to head instead of tail
+        head = head->next;
+        if (head)
+            head->prev = nullptr;
+        else
+            tail = nullptr;  // Setting tail to nullptr if list is now empty
+        T *data = toDelete->data;
+        delete toDelete;  // Deleting the old head of the list
+        --count;
+        return data;  // return the data
+    }
 
-        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
+    void unset(size_t index){
+        Node *current = head;
+        for (size_t i = 0; i != index && current; ++i) {
+            current = current->next;
+        }
+        if(current){
+            if (current->prev)
+                current->prev->next = current->next;
+            else
+                head = current->next;
+
+            if (current->next)
+                current->next->prev = current->prev;
+            else
+                tail = current->prev;
+
+            delete current;
+            --count;
+        }
     }
 
-    T *last() {
-        if (!start) {
+    List<T> *splice(size_t index, size_t numItems) {
+        if (index >= count || numItems == 0 || numItems > (count - index)) {
+            FURI_LOG_E("LIST", "Invalid index or number of items.");
             return nullptr;
         }
 
-        if (!start->next) {
-            return start->data;
-        }
+        List<T> *new_list = new List<T>();
+        Node *current = head;  //It will point to node to remove.
+        Node *prev_node = nullptr; //It will point to node prior to node to remove.
 
-        ListItem<T> *current = start;
-        while (current->next) {
+        //Positioning current and previous node pointers to right nodes.
+        for (size_t i = 0; i < index; ++i) {
+            prev_node = current;
             current = current->next;
         }
 
-        return current->data; // Return the data
-    }
+        int i = 0;
+        Node *last_in_segment = current;
+        while (i < numItems - 1 && last_in_segment->next) {
+            last_in_segment = last_in_segment->next;
+            i++;
+        }
 
-    T *pop() {
-        if (!start) {
-            FURI_LOG_E("LIST", "No start for pop");
-            // List is empty, nothing to remove
-            return nullptr;
+        //Avoiding over-splicing if numItems is larger than available items from index
+        numItems = i + 1;
+
+        //Update original list new head and tail after splicing
+        if (prev_node) {
+            prev_node->next = last_in_segment->next;
+        }
+        else {
+            head = last_in_segment->next;
         }
 
-        if (!start->next) {
-            // Only one element in the list
-            T *data = start->data;
-            delete start;
-            start = nullptr;
-            count--;
-            return data;
+        if (last_in_segment->next) {
+            last_in_segment->next->prev = prev_node;
+        }
+        else {
+            tail = prev_node;
         }
 
-        ListItem<T> *previous = nullptr;
-        ListItem<T> *current = start;
+        //Update new list head and tail
+        new_list->head = current;
+        new_list->tail = last_in_segment;
 
-        while (current->next) {
-            previous = current;
-            current = current->next;
-        }
+        //Update new and original list counts
+        new_list->count = numItems;
+        count -= numItems;
 
-        previous->next = nullptr; // Remove the last element from the list
-        T *data = current->data; // Store the data of the last element
-        count--;
+        last_in_segment->next = nullptr;
+        current->prev = nullptr;
 
-        delete current; // Delete the last element
-        return data; // Return the data of the removed element
+        return new_list;
     }
 
-    ListIterator<T> begin() {
-        return ListIterator<T>(start);
+    T *operator[](size_t index) {
+        Node *current = head;
+        for (size_t i = 0; i != index && current; ++i)
+            current = current->next;
+
+        return current ? current->data : nullptr;
     }
 
-    ListIterator<T> end() {
-        return ListIterator<T>(nullptr);
+    const T *operator[](size_t index) const {
+        Node *current = head;
+        for (size_t i = 0; i != index && current; ++i)
+            current = current->next;
+
+        return current->data;
     }
 
-    T *operator[](int i) {
-        int index = 0;
-        auto *item = start;
-        while (item) {
-            if (index == i) return item->data;
+    T *peek_front() const {
+        if (head) return head->data;
+        else return nullptr;
+    }
 
-            item = item->next;
-            index++;
-        }
+    T *peek_back() const {
+        if (tail) return tail->data;
+        else return nullptr;
+    }
 
-        return nullptr;
+    size_t size() const {
+        return count;
     }
 };

+ 136 - 33
utils/RenderBuffer.cpp

@@ -4,28 +4,37 @@
 #include "Vector.h"
 #include "cmath"
 
-RenderBuffer::RenderBuffer(uint8_t w, uint8_t h) : Buffer(w, h) {
+RenderBuffer::RenderBuffer(uint8_t w, uint8_t h, bool doubleBuffered) : Buffer(w, h) {
     FURI_LOG_D("App", "New renderBuffer");
+    if (doubleBuffered)
+        frontBuffer = new Buffer(w, h);
 }
 
-void RenderBuffer::reset() {
-    int size = width() * (height() / 8);
-    for (int i = 0; i < size; i++) {
-        data[i] = 0;
-    }
-}
-
+/**
+ * Renders the contents of the RenderBuffer onto the given Canvas.
+ * This function iterates over each pixel in the RenderBuffer and checks
+ * if it should be drawn onto the canvas. If a pixel satisfies the test_pixel
+ * condition, it calls the canvas_draw_dot function to draw the pixel onto
+ * the canvas.
+ *
+ * @param canvas A pointer to the Canvas object where the RenderBuffer will be rendered.
+ */
 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);
+    if (!doubleBuffer)
+        canvas_draw_xbm(canvas, 0, 0, width(), height(), data);
+    else
+        canvas_draw_xbm(canvas, 0, 0, width(), height(), frontBuffer->data);
 }
 
+/**
+ * @brief Draws a line from (x0, y0) to (x1, y1) on the render buffer.
+ *
+ * @param x0 The x-coordinate of the starting point of the line.
+ * @param y0 The y-coordinate of the starting point of the line.
+ * @param x1 The x-coordinate of the ending point of the line.
+ * @param y1 The y-coordinate of the ending point of the line.
+ * @param draw_mode The color to draw the line with.
+ */
 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;
@@ -48,6 +57,18 @@ void RenderBuffer::draw_line(int x0, int y0, int x1, int y1, PixelColor draw_mod
     }
 }
 
+/**
+ * @brief Draws a circle on the render buffer.
+ *
+ * This function draws a circle using Bresenham's circle drawing algorithm. The circle is centered at the given x and y coordinates,
+ * with the given radius, and using the specified color. The circle is drawn by plotting symmetrical points in the eight octants
+ * of the circle.
+ *
+ * @param x The x-coordinate of the center of the circle.
+ * @param y The y-coordinate of the center of the circle.
+ * @param r The radius of the circle.
+ * @param color The color of the circle.
+ */
 void RenderBuffer::draw_circle(int x, int y, int r, PixelColor color) {
     int16_t a = r;
     int16_t b = 0;
@@ -73,11 +94,23 @@ void RenderBuffer::draw_circle(int x, int y, int r, PixelColor color) {
     }
 }
 
+/**
+ * @brief RenderBuffer class handles rendering operations on a buffer.
+ */
 void RenderBuffer::draw(Sprite *const sprite, Vector position, float rotation) {
     draw(sprite, position, sprite->width(), sprite->height(), rotation);
 }
 
 
+/**
+ * Draws a sprite on the render buffer.
+ *
+ * @param sprite A pointer to the sprite to be drawn.
+ * @param position The position where the sprite should be drawn.
+ * @param x_cap The horizontal limit for the drawing area.
+ * @param y_cap The vertical limit for the drawing area.
+ * @param rotation The rotation angle for the sprite.
+ */
 void RenderBuffer::draw(Sprite *const sprite, Vector position, uint8_t x_cap, uint8_t y_cap, float rotation) {
     switch (sprite->draw_mode) {
         default:
@@ -102,41 +135,72 @@ void RenderBuffer::draw(Sprite *const sprite, Vector position, uint8_t x_cap, ui
     }
 }
 
+/**
+ * @brief Draws a sprite on the render buffer.
+ *
+ * This function takes a sprite and draws it on the render buffer at the specified position, with optional rotation,
+ * and limited to a maximum width and height cap. The draw color and whether to consider certain pixels as black can
+ * also be specified.
+ *
+ * @warning This function does not implement proper scaling.
+ *
+ * @param sprite The sprite to be drawn.
+ * @param is_black Whether to consider certain pixels in the sprite as black.
+ * @param draw_color The color to use for drawing.
+ * @param position The position at which to draw the sprite.
+ * @param x_cap The maximum width cap for drawing.
+ * @param y_cap The maximum height cap for drawing.
+ * @param rotation The rotation angle, in radians, for the sprite. Positive values rotate the sprite clockwise.
+ */
 //TODO: proper scaling
-void
-RenderBuffer::draw_sprite(Sprite *const sprite, bool is_black, PixelColor draw_color, const Vector &position,
-                          uint8_t x_cap, uint8_t y_cap, 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;
+void RenderBuffer::draw_sprite(Sprite *const sprite, bool is_black, PixelColor draw_color, const Vector &position,
+                               uint8_t x_cap, uint8_t y_cap, float rotation) {
+    Vector center = sprite->get_offset();
+    Vector transform;
     int max_w = fmin(sprite->width(), x_cap);
     int max_h = fmin(sprite->height(), y_cap);
     bool isOn;
     int16_t finalX, finalY;
     for (int y = 0; y < max_h; y++) {
         for (int x = 0; x < max_w; 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);
+            transform = Vector(x, y) - center;
+            transform.rotate(rotation);
+            transform += position;
+            finalX = (int16_t) round(transform.x);
+            finalY = (int16_t) round(transform.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");
+    if (doubleBuffer)
+        delete frontBuffer;
+    delete data;
+    FURI_LOG_I("App", "RenderBuffer end");
 }
 
+/**
+ * @brief Draws a filled rectangle on the render buffer.
+ *
+ * This function draws a filled rectangle on the render buffer defined by the top-left corner (x0, y0) and the
+ * bottom-right corner (x1, y1) coordinates. The draw_mode parameter specifies the color to use for filling the rectangle.
+ *
+ * The rectangle is drawn by iterating through each pixel within the given coordinates and setting its color to the draw_mode.
+ * However, the corners with coordinates (x0, y0) and (x1, y1) are not filled, resulting in an unfilled rectangle shape.
+ *
+ * If a pixel falls on the corners of the rectangle or if it is outside the boundaries of the render buffer, it is skipped.
+ *
+ * @param x0 The x-coordinate of the top-left corner of the rectangle.
+ * @param y0 The y-coordinate of the top-left corner of the rectangle.
+ * @param x1 The x-coordinate of the bottom-right corner of the rectangle.
+ * @param y1 The y-coordinate of the bottom-right corner of the rectangle.
+ * @param draw_mode The color to use for filling the rectangle.
+ */
 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++) {
@@ -146,6 +210,26 @@ void RenderBuffer::draw_rbox(int16_t x0, int16_t y0, int16_t x1, int16_t y1, Pix
     }
 }
 
+/**
+ * @brief Draws a rectangular frame on the RenderBuffer using the given coordinates and draw mode.
+ *
+ * This function draws a rectangular frame on the RenderBuffer defined by the top-left corner (x0, y0)
+ * and the bottom-right corner (x1, y1). The frame consists of four lines that form the outline of the rectangle.
+ * The drawing mode is specified by the draw_mode parameter.
+ *
+ * @param x0 The x-coordinate of the top-left corner of the rectangle.
+ * @param y0 The y-coordinate of the top-left corner of the rectangle.
+ * @param x1 The x-coordinate of the bottom-right corner of the rectangle.
+ * @param y1 The y-coordinate of the bottom-right corner of the rectangle.
+ * @param draw_mode The drawing mode to be used for rendering the lines.
+ *
+ * @see PixelColor
+ * @see RenderBuffer::draw_line
+ *
+ * @note This function assumes that the RenderBuffer instance has been properly initialized and allocated.
+ *       The given coordinates must be within the valid range of the RenderBuffer dimensions.
+ *       The draw_mode should be one of the specified PixelColor enumeration values.
+ */
 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);
@@ -154,10 +238,29 @@ void RenderBuffer::draw_rbox_frame(int16_t x0, int16_t y0, int16_t x1, int16_t y
     draw_line(x1, y0 + 1, x1, y1 - 1, draw_mode);
 }
 
+/**
+ * @brief Draws a filled box on the render buffer.
+ *
+ * This function draws a filled box with the specified coordinates and color on the render buffer.
+ * The box is drawn by setting each pixel within the specified coordinates to the specified color.
+ *
+ * @param x0 The x-coordinate of the top-left corner of the box.
+ * @param y0 The y-coordinate of the top-left corner of the box.
+ * @param x1 The x-coordinate of the bottom-right corner of the box.
+ * @param y1 The y-coordinate of the bottom-right corner of the box.
+ * @param draw_mode The color to be used for drawing the box.
+ *
+ * @note The coordinates (x0, y0) are inclusive, while (x1, y1) are exclusive.
+ */
 void RenderBuffer::draw_box(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++) {
             set_pixel(x, y, draw_mode);
         }
     }
-}
+}
+
+void RenderBuffer::swap() {
+    if (doubleBuffer)
+        frontBuffer->swap(data);
+}

+ 13 - 3
utils/RenderBuffer.h

@@ -5,18 +5,27 @@
 
 struct Vector;
 class Sprite;
+/**
+ * @class RenderBuffer
+ * @brief A class that represents a render buffer for drawing graphics.
+ *
+ * This class inherits from the Buffer class and provides additional methods for drawing various shapes on the buffer.
+ *
+ * @see Buffer
+ */
 class RenderBuffer : public Buffer {
     void draw_sprite(Sprite *const sprite, bool is_black,
                      PixelColor draw_color, const Vector& position, uint8_t x_cap, uint8_t y_cap, float rotation);
-
+    Buffer *frontBuffer;
+    bool doubleBuffer;
 public:
-    explicit RenderBuffer(uint8_t w, uint8_t h);
+    explicit RenderBuffer(uint8_t w, uint8_t h, bool doubleBuffered=false);
 
     virtual ~RenderBuffer();
 
     void render(Canvas *const canvas);
 
-    void reset();
+    void swap();
 
     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);
@@ -27,4 +36,5 @@ public:
 
     void draw(Sprite *const sprite, Vector position, uint8_t x_cap, uint8_t y_cap, float rotation);
     void draw(Sprite *const sprite, Vector position, float rotation);
+
 };

+ 3 - 2
utils/Sprite.cpp

@@ -1,7 +1,8 @@
 #include "Sprite.h"
 #include "Buffer.h"
+#include "../assets.h"
 
-Sprite::Sprite(const SpriteData *spriteData, DrawMode d) : Buffer(spriteData), _icon(spriteData), draw_mode(d) {
+Sprite::Sprite(const SpriteData *spriteData, DrawMode d) : Buffer(spriteData->data, spriteData->width,spriteData->height), _icon(spriteData), draw_mode(d) {
 }
 
 void Sprite::set_anchor(float x, float y) {
@@ -16,5 +17,5 @@ Vector Sprite::get_offset() {
     };
 }
 
-Sprite::Sprite(const SpriteData &spriteData, DrawMode d) : Buffer(&spriteData), _icon(&spriteData), draw_mode(d) {
+Sprite::Sprite(const SpriteData &spriteData, DrawMode d) : Buffer(spriteData.data, spriteData.width,spriteData.height), _icon(&spriteData), draw_mode(d) {
 }

+ 7 - 6
utils/Sprite.h

@@ -3,12 +3,13 @@
 #include "RenderBuffer.h"
 #include "Vector.h"
 
-struct SpriteData {
-    uint8_t width = 0;
-    uint8_t height = 0;
-    uint8_t *data = NULL;
-};
-
+/**
+ * @class Sprite
+ * @brief The Sprite class represents a graphical sprite that can be drawn on a Buffer.
+ *
+ * The Sprite class inherits from the Buffer class and adds functionality specific to sprites.
+ * It contains information about the sprite's icon, anchor point, and draw mode.
+ */
 class Sprite : public Buffer {
     const SpriteData *_icon;
     Vector anchor = {0.5, 0.5};

+ 277 - 9
utils/Vector.h

@@ -3,6 +3,14 @@
 #include <cmath>
 #include "Helpers.h"
 
+/**
+ * @class Vector
+ * @brief Represents a 2D vector and provides various operations on it.
+ *
+ * This class represents a 2D vector with `x` and `y` components. It provides
+ * various operators and functions to perform operations on vectors such as
+ * addition, subtraction, multiplication, division, normalization, rotation, etc.
+ */
 struct Vector {
     float x;
     float y;
@@ -21,84 +29,218 @@ struct Vector {
 
     Vector() : x(0), y(0) {}
 
+    /**
+     * @brief Represents the addition of two vectors.
+     *
+     * @param other The vector to add with.
+     * @return The sum of the two vectors.
+     */
     Vector operator+(Vector const &other) {
         return Vector({x + other.x, y + other.y});
     }
 
+    /**
+     * @brief Represents the addition of a constant float to a vector.
+     *
+     * This function overloads the '+' operator to allow the addition of a constant float value to a vector.
+     *
+     * @param other The constant float value to add.
+     * @return A new vector that is the sum of the original vector and the constant float value.
+     */
     Vector operator+(float const &other) {
         return Vector({x + other, y + other});
     }
 
+    /**
+     * @brief Adds a vector to the current vector.
+     *
+     * This operator overloads the '+=' operator to allow adding another vector to the current vector.
+     *
+     * @param other The vector to be added to the current vector.
+     * @return A reference to the modified current vector.
+     */
     Vector &operator+=(Vector const &other) {
         x += other.x;
         y += other.y;
         return *this;
     }
 
-    Vector operator-(Vector const &other) {
+    /**
+     * @brief Represents the subtraction of two vectors.
+     *
+     * This operator overloads the '-' operator to subtract one vector from another.
+     *
+     * @param other The vector to subtract with.
+     * @return The result of subtracting the two vectors.
+     */
+    Vector operator-(Vector const &other) const{
         return Vector{x - other.x, y - other.y};
     }
 
+    /**
+     * @brief Subtract another vector from the current vector.
+     *
+     * This operator overloads the '-=' operator to subtract another vector from the current vector.
+     *
+     * @param other The vector to subtract.
+     * @return A reference to the modified current vector.
+     */
     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};
-    }
-
+    /**
+     * @brief Represents the multiplication of two vectors.
+     *
+     * This operator overloads the '*' operator to multiply two vectors element-wise.
+     *
+     * @param other The vector to multiply with.
+     * @return The result of multiplying the two vectors element-wise.
+     */
     Vector operator*(Vector const &other) {
         return Vector{x * other.x, y * other.y};
     }
 
 
+    /**
+     * @brief Multiplies the vector with a constant float value.
+     *
+     * This operator overloads the '*' operator to allow the multiplication of the
+     * vector with a constant float value. The result is a new vector with each
+     * component of the original vector multiplied by the constant float value.
+     *
+     * @param other The constant float value to multiply with.
+     * @return A new vector that is the result of multiplying the original vector
+     *         with the constant float value.
+     */
     Vector operator*(float other) {
         return Vector{x * other, y * other};
     }
 
+    /**
+     * @brief Multiplies the vector with another vector element-wise.
+     *
+     * This operator overloads the "*=" operator to multiply the vector with another vector
+     * element-wise. Each component of the current vector is multiplied by the corresponding
+     * component of the other vector.
+     *
+     * @param other The vector to multiply with.
+     * @return A reference to the modified current vector.
+     */
     Vector &operator*=(Vector const &other) {
         x *= other.x;
         y *= other.y;
         return *this;
     }
 
+    /**
+     * @brief Multiplies the vector by a constant float value in place.
+     *
+     * This operator overloads the "*=" operator to multiply each component of the vector by a constant float value.
+     *
+     * @param other The constant float value to multiply the vector by.
+     * @return A reference to the modified vector.
+     */
     Vector &operator*=(float other) {
         x *= other;
         y *= other;
         return *this;
     }
 
+    /**
+     * @brief Represents the division of two vectors.
+     *
+     * This operator overloads the '/' operator to divide one vector by another element-wise.
+     *
+     * @param other The vector to divide by.
+     * @return The result of dividing the two vectors element-wise.
+     */
     Vector operator/(Vector const &other) {
         return Vector{x / other.x, y / other.y};
     }
 
+    /**
+     * @brief Divides the vector by another vector element-wise.
+     *
+     * This operator overloads the '/=' operator to divide the vector by another vector
+     * element-wise. Each component of the current vector is divided by the corresponding
+     * component of the other vector.
+     *
+     * @param other The vector to divide by.
+     * @return A reference to the modified current vector.
+     */
     Vector &operator/=(Vector const &other) {
         x /= other.x;
         y /= other.y;
         return *this;
     }
 
+    /**
+     * @brief Divides the vector by a constant float value.
+     *
+     * This operator overloads the '/' operator to divide each component of the vector
+     * by a constant float value. The result is a new vector with each component of the
+     * original vector divided by the constant float value.
+     *
+     * @param other The constant float value to divide the vector by.
+     * @return A new vector that is the result of dividing the original vector by the
+     *         constant float value.
+     */
     Vector operator/(float other) {
         return Vector{x / other, y / other};
     }
 
+    /**
+     * @brief Divides the vector by a constant float value in place.
+     *
+     * This operator overloads the '/=' operator to divide each component of the vector
+     * by a constant float value. The result is a new vector with each component of the
+     * original vector divided by the constant float value.
+     *
+     * @param other The constant float value to divide the vector by.
+     * @return A reference to the modified vector.
+     */
     Vector &operator/=(float other) {
         x /= other;
         y /= other;
         return *this;
     }
 
+    /**
+     * @brief Calculates the magnitude of the vector.
+     *
+     * The magnitude of a 2D vector is the length of the vector computed using the Euclidean distance formula.
+     * The magnitude is computed as the square root of the sum of the squares of the vector's x and y components.
+     *
+     * @return The magnitude of the vector.
+     */
     float magnitude() {
         return sqrtf(x * x + y * y);
     }
 
+    /**
+     * @brief Calculates the distance between the current vector and another vector.
+     *
+     * This function calculates the distance between the current vector and another vector by subtracting the two vectors and then calculating the magnitude of the resulting vector.
+     *
+     * @param other The vector to calculate the distance from.
+     * @return The distance between the two vectors.
+     */
     float distance(Vector const &other) {
         Vector v = *this - other;
         return v.magnitude();
     }
 
+    /**
+     * @brief Normalizes the vector.
+     *
+     * This function normalizes the vector by dividing its components by its magnitude.
+     * If the magnitude is zero, the vector is set to (0,0).
+     *
+     * @return void
+     */
     void normalize() {
         float m = magnitude();
         if (m == 0) {
@@ -110,6 +252,14 @@ struct Vector {
         }
     }
 
+    /**
+     * @brief Returns a new vector that is the normalized version of the current vector.
+     *
+     * This function normalizes the vector by dividing its components by its magnitude.
+     * If the magnitude is zero, the vector is set to (0,0).
+     *
+     * @return The normalized vector.
+     */
     Vector normalized() {
         float m = magnitude();
         if (m == 0) return {0, 0};
@@ -117,41 +267,134 @@ struct Vector {
         return {x / m, y / m};
     }
 
+    /**
+     * @brief Returns the inverse of the vector.
+     *
+     * The inverse of a vector is a new vector with the negated values of its `x` and `y` components.
+     *
+     * @return The inverse of the vector.
+     */
     Vector inverse() {
         return {-x, -y};
     }
 
+    /**
+     * @brief Calculates the dot product of the current vector and another vector.
+     *
+     * The dot product is a scalar value that represents the projection of one vector onto another.
+     *
+     * The dot product of two vectors A and B can be calculated using the formula:
+     * dot(A, B) = A.x * B.x + A.y * B.y
+     *
+     * @param b The vector to calculate the dot product with.
+     * @return The dot product of the two vectors.
+     */
     float dot(Vector const &b) {
         return x * b.x + y * b.y;
     }
 
+    /**
+     * @brief Rotates the vector by the specified angle in degrees.
+     *
+     * This function rotates the vector by the specified angle in degrees.
+     * The rotation is performed using the cosine and sine functions, and the result is stored in the x and y components of the vector.
+     *
+     * @param deg The angle to rotate the vector by in degrees.
+     * @return void
+     */
     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);
+        x = (float) (cos(deg) * tx - sin(deg) * ty);
+        y = (float) (sin(deg) * tx + cos(deg) * ty);
     }
 
+    /**
+     * @brief Rounds the x and y components of the vector to the nearest integer.
+     *
+     * This function rounds the x and y components of the vector to the nearest integer by using the round function
+     * from the cmath library. The result is stored back into the x and y
+     * components of the vector.
+     *
+     * @return void
+     */
     void rounded() {
         x = (float) round(x);
         y = (float) round(y);
     }
 
+    /**
+     * @brief Calculates and returns a 2D vector that is rotated by the specified angle in degrees.
+     *
+     * This function takes a floating-point value representing an angle in degrees and returns a new 2D vector that is rotated by
+     * that angle. The rotation is performed in the counter-clockwise direction.
+     *
+     * @param deg The angle to rotate the vector by in degrees.
+     * @return The rotated 2D vector.
+     *
+     * @note This function assumes that the input angle is in degrees, not radians. If the input angle is in radians, it needs to be
+     * converted to degrees before being passed to this function.
+     *
+     * @note The returned vector is a new instance and does not modify the original vector.
+     *
+     * @warning This function assumes that the vector is represented using floating-point values. If the vector uses integer values,
+     * the return type and calculations should be adjusted accordingly.
+     *
+     * @see https://en.wikipedia.org/wiki/Rotation_matrix for more information on rotation matrices.
+     *
+     * @sa rotate(float deg) to rotate the vector in-place.
+     *
+     * Example usage:
+     * @code
+     * Vector v1(2, 3);
+     * Vector v2 = v1.rotated(45.0f);
+     * // v2 = { -0.707, 3.536 }
+     * @endcode
+     */
     Vector rotated(float deg) {
         return {
-                (float) (cos(deg) * (double) x - sin(deg) * (double) y),
-                (float) (sin(deg) * (double) x + cos(deg) * (double) y)
+                (float) (cos(deg) * x - sin(deg) * y),
+                (float) (sin(deg) * x + cos(deg) * y)
         };
     }
 
+    /**
+     * @brief Calculates the cross product between two vectors.
+     *
+     * This method calculates the cross product between the current vector and the
+     * specified vector, and returns the result. The cross product is calculated
+     * using the formula: cross product = (x1 * y2) - (y1 * x2).
+     *
+     * @param other The vector to calculate the cross product with.
+     * @return The cross product between the two vectors.
+     */
     float cross(Vector const &other) {
         return x * other.x - y * other.y;
     }
 
+    /**
+     * @brief Calculates the perpendicular vector.
+     *
+     * This function calculates the perpendicular vector of the current vector.
+     *
+     * @return The perpendicular vector.
+     */
     Vector perpendicular() {
         return {-y, x};
     }
 
+    /**
+     * @brief Projects a vector onto a line segment defined by two points.
+     *
+     * Given a line segment defined by two points lineA and lineB, this function calculates the projection of the current vector onto that line segment.
+     * It returns the projected vector point and also sets the success flag to indicate whether the projection was successful or not.
+     *
+     * @param lineA The first point defining the line segment.
+     * @param lineB The second point defining the line segment.
+     * @param success A pointer to a boolean value that will be set to true if the projection is successful, and false otherwise.
+     *
+     * @return The projected vector point if the projection is successful, otherwise an empty vector.
+     */
     Vector project(Vector const &lineA, Vector const &lineB, bool *success) {
         Vector AB = lineB - lineA;
         Vector AC = *this - lineA;
@@ -168,6 +411,17 @@ struct Vector {
         };
     }
 
+    /**
+     * @brief Linearly interpolates between two vectors.
+     *
+     * This function performs linear interpolation between two vectors using the given time value.
+     * It is assumed that the start and end vectors have the same dimension.
+     *
+     * @param start The start vector.
+     * @param end The end vector.
+     * @param time The interpolation time value between 0 and 1.
+     * @return The interpolated vector.
+     */
     static Vector Lerp(Vector const &start, Vector const &end, float time) {
         return {
                 lerp(start.x, end.x, time),
@@ -175,6 +429,20 @@ struct Vector {
         };
     }
 
+    /**
+     * @brief Calculate the quadratic Bezier interpolation between three vectors.
+     *
+     * This function calculates the position of a point along a quadratic Bezier curve, given three control points
+     * and a time value.
+     *
+     * @param start The start point of the curve.
+     * @param control The control point of the curve, which affects the shape of the curve.
+     * @param end The end point of the curve.
+     * @param time The time value parameter that specifies the position along the curve.
+     * @return The interpolated vector position.
+     *
+     * @see Vector::Lerp()
+     */
     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);