Procházet zdrojové kódy

v2 release
replaced C++ codebase with C

Tibor Tálosi před 1 rokem
rodič
revize
d87de9d9cb
59 změnil soubory, kde provedl 3188 přidání a 2843 odebrání
  1. 0 722
      GameLogic.cpp
  2. 0 74
      GameLogic.h
  3. 0 105
      GameLoop.cpp
  4. 0 36
      GameLoop.h
  5. 33 16
      README.md
  6. 8 4
      application.fam
  7. 512 0
      assets.c
  8. 25 262
      assets.h
  9. binární
      assets/joker.png
  10. 20 0
      docs/CHANGELOG.md
  11. 30 0
      docs/README.md
  12. 0 0
      docs/changelog.md
  13. 44 0
      game_state.h
  14. binární
      screenshots/catalog_1.png
  15. binární
      screenshots/catalog_2.png
  16. binární
      screenshots/catalog_3.png
  17. binární
      screenshots/catalog_4.png
  18. binární
      screenshots/catalog_5.png
  19. binární
      screenshots/catalog_6.png
  20. binární
      screenshots/solitaire.png
  21. 184 0
      solitaire.c
  22. 0 19
      solitaire.cpp
  23. 103 0
      src/scene/falling_card.c
  24. 11 0
      src/scene/falling_card.h
  25. 119 0
      src/scene/intro_animation.c
  26. 12 0
      src/scene/intro_animation.h
  27. 130 0
      src/scene/main_screen.c
  28. 16 0
      src/scene/main_screen.h
  29. 341 0
      src/scene/play_screen.c
  30. 14 0
      src/scene/play_screen.h
  31. 74 0
      src/scene/result_screen.c
  32. 12 0
      src/scene/result_screen.h
  33. 61 0
      src/scene/scene_setup.h
  34. 204 0
      src/scene/solve_screen.c
  35. 8 0
      src/scene/solve_screen.h
  36. 258 0
      src/util/buffer.c
  37. 74 0
      src/util/buffer.h
  38. 230 0
      src/util/card.c
  39. 55 0
      src/util/card.h
  40. 6 0
      src/util/game_loop.h
  41. 45 0
      src/util/helpers.c
  42. 39 0
      src/util/helpers.h
  43. 304 0
      src/util/list.c
  44. 48 0
      src/util/list.h
  45. 119 0
      src/util/vector.c
  46. 49 0
      src/util/vector.h
  47. 0 169
      utils/Buffer.cpp
  48. 0 52
      utils/Buffer.h
  49. 0 94
      utils/Card.cpp
  50. 0 45
      utils/Card.h
  51. 0 37
      utils/Helpers.cpp
  52. 0 47
      utils/Helpers.h
  53. 0 47
      utils/Input.h
  54. 0 305
      utils/List.h
  55. 0 266
      utils/RenderBuffer.cpp
  56. 0 40
      utils/RenderBuffer.h
  57. 0 21
      utils/Sprite.cpp
  58. 0 31
      utils/Sprite.h
  59. 0 451
      utils/Vector.h

+ 0 - 722
GameLogic.cpp

@@ -1,722 +0,0 @@
-#include <dolphin/helpers/dolphin_deed.h>
-#include <dolphin/dolphin.h>
-#include <notification/notification_messages.h>
-#include "GameLogic.h"
-#include "utils/Sprite.h"
-#include "assets.h"
-
-//#define SOLVE_TEST
-
-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, NotificationApp *notification_app) : buffer(b),
-                                                                                                            notification(
-                                                                                                                notification_app) {
-    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;
-                dolphin_deed(DolphinDeedPluginGameStart);
-                return;
-            }
-            if (state == Solve) {
-                end = furi_get_tick();
-                QuickSolve();
-                return;
-            }
-            if (state == Finish) {
-                dolphin_deed(DolphinDeedPluginGameWin);
-                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());
-                            return;
-                        }
-                    }
-                    PlayError();
-                }
-                break;
-            default:
-                break;
-        }
-    }
-
-    if (selection[1] == 0 && selection[0] == 2) { //skip empty space
-        selection[0] += key == InputKeyRight ? 1 : -1;
-    }
-}
-
-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;
-        } else {
-            PlayError();
-        }
-    }
-        //place to foundation
-    else if (selection[1] == 0 && selection[0] > 2) {
-        if (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];
-            } else {
-                PlayError();
-            }
-        } else {
-            PlayError();
-        }
-    }
-        //pick and place columns
-    else if (selection[1] == 1) {
-        auto &tbl = tableau[selection[0]];
-        if (hand.size() == 0) {
-            if(tbl.peek_back()) {
-                if (!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 {
-                PlayError();
-            }
-        } 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;
-        } else {
-            PlayError();
-        }
-    }
-
-    for (uint8_t i = 0; i < 4; i++) {
-        if (foundation[i].size() == 0 || foundation[i].peek_back()->value != KING)
-            return;
-    }
-
-    buffer->clear();
-    DrawPlayScene();
-    selectedCard = 0;
-    state = Finish;
-}
-
-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--;
-    }
-}
-
-
-void GameLogic::Update(float delta) {
-    //keep the buffer for the falling animation to achieve the trailing effect
-    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;
-            end = 0;
-            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;
-        case Solve:
-            DrawPlayScene();
-            HandleSolve(delta);
-            dirty = true;
-            break;
-        case Finish:
-            dirty = true;
-            FallingCard(delta);
-            break;
-        default:
-            break;
-    }
-    buffer->swap();
-    readyToRender = true;
-}
-
-void GameLogic::Reset() {
-    dolphin_deed(DolphinDeedPluginGameStart);
-    delete tempCard;
-    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);
-#ifndef SOLVE_TEST
-    //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;
-    }
-#endif
-    //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();
-    delete tempCard;
-    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
-#ifdef SOLVE_TEST
-    startTime = furi_get_tick();
-    state = Play;
-    buffer->clear();
-    DrawPlayScene();
-    return;
-#endif
-
-    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] == 0 && 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);
-        if (selected == 1)
-            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;
-}
-
-
-void GameLogic::HandleSolve(float delta) {
-    if (tempCard) {
-
-        tempTime += delta * 8;
-        Vector finalPos{56 + (float) target[0] * 18, 2};
-        if (tempTime > 1) tempTime = 1;
-        Vector localpos = Vector::Lerp(tempPos, finalPos, tempTime);
-        tempCard->Render((uint8_t) localpos.x, (uint8_t) localpos.y, false, buffer);
-        if (finalPos.distance(localpos) < 0.01) {
-            foundation[target[0]].push_back(tempCard);
-            tempCard = nullptr;
-        }
-
-        //check finish
-        uint8_t size = 0;
-        for (uint8_t i = 0; i < 4; i++) {
-            if (foundation[i].size() > 0 && foundation[i].peek_back()->value == KING)
-                size++;
-        }
-
-        if (size == 4) {
-            buffer->clear();
-            selectedCard = 0;
-            DrawPlayScene();
-            state = Finish;
-            return;
-        }
-    } else {
-        tempTime = 0;
-        //search the lowest card
-        int lowestSuit = -2, lowestValue = 13;
-        for (int i = 0; i < 4; i++) {
-            auto &fnd = foundation[i];
-            if (foundation[i].size() == 0) {
-                //find the missing suit
-                int foundations[4] = {0, 0, 0, 0};
-                for (int j = 0; j < 4; j++) {
-                    if (foundation[j].size() > 0) {
-                        foundations[foundation[j].peek_front()->suit] = 1;
-                    }
-                }
-                for (int j = 0; j < 4; j++) {
-                    if (foundations[j] == 0) {
-                        lowestSuit = j;
-                        lowestValue = -1;
-                        target[0] = (float) j;
-                        break;
-                    }
-                }
-                break;
-            }
-            if (i == 0 || (lowestValue + 1) % 13 > (fnd.peek_back()->value + 1) % 13) {
-                lowestSuit = fnd.peek_back()->suit;
-                lowestValue = fnd.peek_back()->value;
-                target[0] = (float) i;
-            }
-        }
-
-        Card lowest(lowestSuit, lowestValue);
-
-        //try to find it in tableau
-        for (int i = 0; i < 7; i++) {
-            auto &tbl = tableau[i];
-            if (tbl.peek_back() && tbl.peek_back()->CanPlaceFoundation(&lowest)) {
-                tempCard = tbl.pop_back();
-                int y = MIN((int) tableau[i].size(), 4) * 4 + 25;
-                tempPos.x = 2 + (float) i * 18;
-                tempPos.y = (float) y;
-                return;
-            }
-        }
-
-        //try to find in waste
-        int index = -1;
-        for (auto *w: waste) {
-            index++;
-            if (w->CanPlaceFoundation(&lowest)) {
-                tempCard = waste.extract(index);
-                tempCard->exposed = true;
-                tempPos.x = 20;
-                tempPos.y = 1;
-                return;
-            }
-        }
-
-        index = -1;
-        //try to find in stock
-        for (auto *w: stock) {
-            index++;
-            if (w->CanPlaceFoundation(&lowest)) {
-                tempCard = stock.extract(index);
-                tempCard->exposed = true;
-                tempPos.x = 2;
-                tempPos.y = 1;
-                return;
-            }
-        }
-    }
-}
-
-void GameLogic::QuickSolve() {
-    if (tempCard) {
-        delete tempCard;
-        tempCard = nullptr;
-    }
-
-    waste.deleteData();
-    waste.clear();
-    stock.deleteData();
-    stock.clear();
-    for (uint8_t i = 0; i < 7; i++) {
-        tableau[i].deleteData();
-        tableau[i].clear();
-    }
-
-    int foundations[4] = {0, 0, 0, 0};
-    for (int j = 0; j < 4; j++) {
-        if (foundation[j].size() > 0) {
-            foundations[foundation[j].peek_front()->suit] = 1;
-        }
-    }
-
-    for (int j = 0; j < 4; j++) {
-        if (foundations[j] == 0) {
-            //seed the foundation
-            foundation[j].push_back(new Card(j, ACE));
-            foundation[j].peek_back()->exposed = true;
-        }
-    }
-
-    for (uint8_t i = 0; i < 4; i++) {
-        auto &fnd = foundation[i];
-        if (fnd.peek_back()->value == ACE) {
-            fnd.push_back(new Card(fnd.peek_back()->suit, TWO));
-            fnd.peek_back()->exposed = true;
-        }
-
-        while (fnd.peek_back()->value != KING) {
-            fnd.push_back(new Card(fnd.peek_back()->suit, fnd.peek_back()->value + 1));
-            fnd.peek_back()->exposed = true;
-        }
-    }
-    buffer->clear();
-    DrawPlayScene();
-    selectedCard = 0;
-    state = Finish;
-}
-
-void GameLogic::FallingCard(float delta) {
-    UNUSED(delta);
-    if (tempCard) {
-        if ((furi_get_tick() - tempTime) > 12) {
-            tempTime = furi_get_tick();
-            tempPos.x += velocity.x;
-            tempPos.y -= velocity.y;
-
-            if (tempPos.y > 41) {
-                velocity.y *= -0.8;
-                tempPos.y = 41;
-                PlayBounce();
-            } else {
-                velocity.y -= 1;
-                if (velocity.y < -10) velocity.y = -10;
-            }
-            tempCard->Render((int8_t) tempPos.x, (int8_t) tempPos.y, false, buffer);
-            if (tempPos.x < -18 || tempPos.x > 128) {
-                delete tempCard;
-                tempCard = nullptr;
-            }
-        }
-    } else {
-        float r1 = 2.0 * (float) (rand() % 2) - 1.0; // random number in range -1 to 1
-        if (r1 == 0) r1 = 0.1;
-        float r2 = inverse_tanh(r1);
-
-        velocity.x = (float) (tanh(r2)) * (rand() % 3 + 1);
-
-        if (velocity.x == 0) velocity.x = 1;
-        velocity.y = (rand() % 3 + 1);
-        if (foundation[selectedCard].size() > 0) {
-            tempCard = foundation[selectedCard].pop_back();
-            tempCard->exposed = true;
-            tempPos.x = 56 + selectedCard * 18;
-            tempPos.y = 2;
-            selectedCard = (uint8_t) (selectedCard + 1) % 4;
-        } else {
-            state = Logo;
-            return;
-        }
-    }
-}
-
-
-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,
-};
-
-static const NotificationSequence sequence_bounce = {
-    &message_vibro_on,
-    &message_note_c4,
-    &message_delay_10,
-    &message_vibro_off,
-    &message_sound_off,
-    NULL,
-};
-
-void GameLogic::PlayError() {
-    notification_message(notification, (const NotificationSequence *) &sequence_fail);
-}
-
-void GameLogic::PlayBounce() {
-    notification_message(notification, (const NotificationSequence *) &sequence_bounce);
-}

+ 0 - 74
GameLogic.h

@@ -1,74 +0,0 @@
-#pragma once
-
-
-#include <notification/notification.h>
-#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 {
-
-    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;
-    Vector velocity;
-    NotificationApp *notification;
-public:
-    GameState state = Logo;
-    bool dirty = true;
-    double startTime;
-    double end;
-
-    GameLogic(RenderBuffer *buffer, InputEventHandler *inputHandler, NotificationApp *notification_app);
-
-    ~GameLogic();
-
-    void Update(float delta);
-
-    void Input(int key, InputType type);
-
-    void Reset();
-
-    void GenerateDeck();
-
-    bool CanSolve();
-
-    void DoIntro(float delta);
-
-    void DrawPlayScene();
-
-    void HandleSolve(float delta);
-
-    void QuickSolve();
-
-    void FallingCard(float delta);
-
-    bool isReady() const { return readyToRender; }
-
-    void DrawColumn(uint8_t x, uint8_t y, uint8_t selected, int8_t column);
-
-    void HandleNavigation(int key);
-
-    void PickAndPlace();
-    void PlayError();
-    void PlayBounce();
-
-    int8_t FirstNonFlipped(const List<Card> &deck);
-};

+ 0 - 105
GameLoop.cpp

@@ -1,105 +0,0 @@
-#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);
-    notification_app = (NotificationApp *) furi_record_open(RECORD_NOTIFICATION);
-    notification_message_block(notification_app, &sequence_display_backlight_enforce_on);
-    logic = new GameLogic(buffer, &inputHandler, notification_app);
-    if (!render_mutex) {
-        return;
-    }
-    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 && 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;
-}
-char timeString[24];
-
-void GameLoop::Start() {
-    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);
-
-            if(logic->end>0 &&logic->state == Logo){
-                int diff = logic->end - logic->startTime;
-                if(diff>0) {
-                    int hours = diff / 3600000;
-                    int minutes = (diff % 3600000) / 60000;
-                    int seconds = (diff % 60000) / 1000;
-                    snprintf(timeString, sizeof(timeString), "Completed: %02d:%02d:%02d", hours, minutes, seconds);
-                    canvas_set_font(canvas, FontSecondary);
-                    canvas_draw_str_aligned(canvas, 60, 5, AlignCenter, AlignTop, timeString);
-                }
-            }
-
-
-            furi_mutex_release(render_mutex);
-            canvas_commit(canvas);
-        }
-        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;
-}

+ 0 - 36
GameLoop.h

@@ -1,36 +0,0 @@
-#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();
-};

+ 33 - 16
README.md

@@ -3,28 +3,45 @@
 [![issues - flipper-zero_authenticator](https://img.shields.io/github/issues/teeebor/flipper_solitaire)](https://github.com/teeebor/flipper_solitaire/issues)
 [![issues - flipper-zero_authenticator](https://img.shields.io/github/issues/teeebor/flipper_solitaire)](https://github.com/teeebor/flipper_solitaire/issues)
 ![maintained - yes](https://img.shields.io/badge/maintained-yes-blue)
 ![maintained - yes](https://img.shields.io/badge/maintained-yes-blue)
 ![contributions - welcome](https://img.shields.io/badge/contributions-welcome-blue)
 ![contributions - welcome](https://img.shields.io/badge/contributions-welcome-blue)
-# Solitaire game for Flipper Zero
+# Solitaire - Klondike for Flipper Zero
+
 
 
 ![Play buffer](screenshots/solitaire.png)
 ![Play buffer](screenshots/solitaire.png)
 
 
 ![Play buffer](screenshots/solitaire.gif)
 ![Play buffer](screenshots/solitaire.gif)
 
 
-### Shortcuts
-* Long press up skips the navigation inside the bottom column
-* Long press center to automatically place the card to the top rigth section
+## Features
+
+* **Auto-Solve:** Ability to automatically solve the game when all cards are flipped.
+* **Animated Card Movements:** Animated transitions during solve and deal.
+* **Time Tracking:** Displays the time it took to solve at the end of each game.
+* **Falling Cards:** Enjoy a visually satisfying cascade of cards when you win.
+
+## Shortcuts
+
+* **Long Press Any Arrow:** Jump to the furthest point in that direction.
+* **Long Press Center:** Automatically place the card in the top right section.
+* **Long Press Back:** Close the application instantly.
+
+## Rules
+- **Empty Tableau Spots:** Only a King can be placed in an empty tableau spot.
+- **Tableau Arrangement:** Cards must be arranged in descending order and alternating colors (e.g., red ten on black nine) within the tableau.
+- **Empty Foundation Piles:** Only an Ace can be placed in an empty foundation pile.
+- **Foundation Pile Arrangement:** Cards must be in ascending order and in the same suit.
+
+## How to Play
+
+- Use the directional arrows to navigate and move cards.
+- Pick up cards with the center button.
+- Aim to build up four foundation piles in ascending order, separated by suit.
+- Flip and move cards within the tableau to reveal hidden cards.
+- You can place back cards in the same spot you picked them up.
+
 
 
 ## Building
 ## Building
 > The app should be compatible with the official and custom flipper firmwares. If not, follow these steps to build it
 > The app should be compatible with the official and custom flipper firmwares. If not, follow these steps to build it
 > yourself
 > yourself
-* Download your firmware's source code
-* Clone the repository recursively `git clone REPO_URL --recursive` into the firmware's applications_user folder
-* Navigate into the firmwares root folder
-* Make sure you can use
-  the [Fipper build tool](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/fbt.md)
-* To build the project, type this into your console:
-  #### Linux
-  > ./fbt fap_{APP_NAME}
-  #### Windows
-  > fbt.cmd fap_{APP_NAME}
-* the finished build will be in the following location, copy this into your SD card:
-  > build\f7-firmware-D\.extapps\blackjack.fap
+* Set up [uFBT](https://pypi.org/project/ufbt/) if you haven't already
+* Navigate into the folder of the game
+* Run `ufbt`
+* the finished build will be in the dist folder, copy this the fap file into your SD card

+ 8 - 4
application.fam

@@ -1,12 +1,16 @@
 App(
 App(
     appid="solitaire",
     appid="solitaire",
-    name="Solitaire 2",
+    name="Solitaire",
     apptype=FlipperAppType.EXTERNAL,
     apptype=FlipperAppType.EXTERNAL,
     entry_point="solitaire_app",
     entry_point="solitaire_app",
     cdefines=["APP_SOLITAIRE"],
     cdefines=["APP_SOLITAIRE"],
-    requires=["gui","storage","canvas"],
-    stack_size=10 * 1024,
+    requires=["gui"],
+    stack_size=2*1024,
     order=30,
     order=30,
     fap_icon="solitaire_10px.png",
     fap_icon="solitaire_10px.png",
-    fap_category="Games"
+    fap_category="Games",
+    fap_author="@doofy_dev",
+    fap_weburl="https://github.com/doofy-dev/flipper_solitaire",
+    fap_version="2.0",
+    fap_description="Klondike Solitaire card game",
 )
 )

+ 512 - 0
assets.c

@@ -0,0 +1,512 @@
+#include "assets.h"
+
+/*
+      ██████   
+   ███   ███   
+███      ███   
+████████████   
+         ███   
+*/
+const Buffer sprite_4 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0xc, 
+		0xa, 
+		0x9, 
+		0xf, 
+		0x8
+}};
+/*
+████████████   
+         ███   
+      ███      
+   ███         
+   ███         
+*/
+const Buffer sprite_7 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0xf, 
+		0x8, 
+		0x4, 
+		0x2, 
+		0x2
+}};
+/*
+   ███         ███   
+███   ███   ███   ███
+███      ███      ███
+███               ███
+   ███         ███   
+      ███   ███      
+         ███         
+*/
+const Buffer sprite_hearths = (Buffer) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x22, 
+		0x55, 
+		0x49, 
+		0x41, 
+		0x22, 
+		0x14, 
+		0x8
+}};
+/*
+   ██████      
+███      ███   
+████████████   
+███      ███   
+███      ███   
+*/
+const Buffer sprite_A = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x9, 
+		0xf, 
+		0x9, 
+		0x9
+}};
+/*
+                     
+      ███   ███   ███
+   ███   ███   ███   
+      ███   ███   ███
+   ███   ███   ███   
+      ███   ███   ███
+   ███   ███   ███   
+*/
+const Buffer sprite_pattern_small = (Buffer) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x0, 
+		0x54, 
+		0x2a, 
+		0x54, 
+		0x2a, 
+		0x54, 
+		0x2a
+}};
+/*
+                  ███                        
+   ███      ████████████                     
+   ████████████████████████      ███         
+   ████████████████████████   ███   ███      
+   ███      ████████████         ███         
+                                             
+         ███            ███                  
+      ███   ███      ████████████      ███   
+         ███      ████████████████████████   
+                  ████████████████████████   
+                     ████████████      ███   
+                                             
+                  ███            ███         
+   ███      ████████████      ███   ███      
+   ████████████████████████      ███         
+   ████████████████████████            ███   
+   ███      ████████████            ███      
+                                    ███      
+         ███         ███            ███      
+      ███            ███   ███      ███      
+      ███         ███      ███   ███         
+*/
+const Buffer sprite_pattern_big = (Buffer) {.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
+}};
+/*
+                                          █████████████████████████████████████████████            
+                                       ███                                             ███         
+                                       █████████████████████████████████████████████   ███         
+                                    ███                                             ██████         
+                                    █████████████████████████████████████████████   ██████         
+                                 ███                                             █████████         
+                                 ███   ███      ███               ███            █████████         
+                                 ███   ███   ███   ███         █████████         █████████         
+                                 █████████████████████████████████████████████   █████████         
+                              ███                                             ████████████         
+                              ███   ███      ███               ███            ████████████         
+                              ███   ███   ███   ███         █████████         ████████████         
+            ███████████████████████████████████████████████████████████████   ████████████         
+         ███                                             ███               ███████████████         
+         ███   ███      ███               ███            ██████            ███████████████         
+         ███   ███   ███   ███         █████████         █████████         ███████████████         
+         ███   ███   ███   ███      ███████████████      ███████████████   ███████████████         
+         ███   ███   ███   ███   █████████████████████   ██████         ██████████████████         
+         ███   ███   ███   ███      ███████████████      █████████      ██████████████████         
+         ███   ███      ███            █████████         █████████      ██████████████████         
+         ███                              ███            █████████      ██████████████████         
+         ███                                             ████████████   ███████████████            
+         ███                                             ████████████   ███████████████            
+         ███                                             ███████████████████████████               
+         ███                                             ███████████████████████████               
+         ███            ███                              ████████████████████████                  
+         ███         █████████            ███      ███   ████████████████████████                  
+         ███      ███████████████      ███   ███   ███   ████████████████████████                  
+         ███   █████████████████████   ███   ███   ███   ████████████████████████                  
+         ███      ███████████████      ███   ███   ███   █████████████████████                     
+         ███         █████████         ███   ███   ███   █████████████████████                     
+         ███            ███               ███      ███   █████████████████████                     
+         ███                                             █████████████████████                     
+            █████████████████████████████████████████████   ███████████████                        
+            ███                                             ███████████████                        
+               █████████████████████████████████████████████   ████████████                        
+               ███            ███               ███      ███   ████████████                        
+               ███                                             █████████                           
+                  █████████████████████████████████████████████   ██████                           
+                  ███            ███               ███      ███   ██████                           
+                  ███                                             ██████                           
+                     █████████████████████████████████████████████   ███                           
+                     ███                                             ███                           
+                        █████████████████████████████████████████████                              
+*/
+const Buffer sprite_main_image = (Buffer) {.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
+}};
+/*
+   ██████      
+███      ███   
+███      ███   
+███   ███      
+   ███   ███   
+*/
+const Buffer sprite_Q = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x9, 
+		0x9, 
+		0x5, 
+		0xa
+}};
+/*
+   ██████      
+███      ███   
+      ███      
+   ███         
+████████████   
+*/
+const Buffer sprite_2 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x9, 
+		0x4, 
+		0x2, 
+		0xf
+}};
+/*
+█████████      
+         ███   
+   ██████      
+         ███   
+█████████      
+*/
+const Buffer sprite_3 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x7, 
+		0x8, 
+		0x6, 
+		0x8, 
+		0x7
+}};
+/*
+███      ███   
+███   ███   ███
+███   ███   ███
+███   ███   ███
+███      ███   
+*/
+const Buffer sprite_10 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x9, 
+		0x15, 
+		0x15, 
+		0x15, 
+		0x9
+}};
+/*
+███      ███   
+███   ███      
+██████         
+███   ███      
+███      ███   
+*/
+const Buffer sprite_K = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x9, 
+		0x5, 
+		0x3, 
+		0x5, 
+		0x9
+}};
+/*
+████████████   
+███            
+█████████      
+         ███   
+████████████   
+*/
+const Buffer sprite_5 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0xf, 
+		0x1, 
+		0x7, 
+		0x8, 
+		0xf
+}};
+/*
+   ██████      
+███            
+█████████      
+███      ███   
+   ██████      
+*/
+const Buffer sprite_6 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x1, 
+		0x7, 
+		0x9, 
+		0x6
+}};
+/*
+   ██████      
+███      ███   
+   █████████   
+         ███   
+   ██████      
+*/
+const Buffer sprite_9 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x6, 
+		0x9, 
+		0xe, 
+		0x8, 
+		0x6
+}};
+/*
+         ███         
+      ███   ███      
+   ███         ███   
+███               ███
+   ███         ███   
+      ███   ███      
+         ███         
+*/
+const Buffer sprite_diamonds = (Buffer) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x8, 
+		0x14, 
+		0x22, 
+		0x41, 
+		0x22, 
+		0x14, 
+		0x8
+}};
+/*
+████████████   
+███      ███   
+████████████   
+███      ███   
+████████████   
+*/
+const Buffer sprite_8 = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0xf, 
+		0x9, 
+		0xf, 
+		0x9, 
+		0xf
+}};
+/*
+         ███   
+         ███   
+         ███   
+███      ███   
+   ██████      
+*/
+const Buffer sprite_J = (Buffer) {.width=8, .height=5, .data=(uint8_t[]) {
+		0x8, 
+		0x8, 
+		0x8, 
+		0x9, 
+		0x6
+}};
+/*
+         ███         
+      █████████      
+   ███████████████   
+█████████████████████
+██████   ███   ██████
+         ███         
+      █████████      
+*/
+const Buffer sprite_spades = (Buffer) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x8, 
+		0x1c, 
+		0x3e, 
+		0x7f, 
+		0x6b, 
+		0x8, 
+		0x1c
+}};
+/*
+      ████████████               ████████████      ███                                    ███   
+   ██████████████████         ███            ███   ███                                    ███   
+████████████████████████      ███               █████████      █████████      ███   ████████████
+████████████████████████         ██████            ███      ███         ███   ██████      ███   
+████████████████████████               ██████      ███         ████████████   ███         ███   
+████████████████████████                     ███   ███      ███         ███   ███         ███   
+   ██████████████████         ███            ███   ███      ███      ██████   ███         ███   
+      ████████████               ████████████      ██████   █████████   ███   ███         ██████
+*/
+const Buffer sprite_start = (Buffer) {.width=32, .height=8, .data=(uint8_t[]) {
+		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 Buffer sprite_solve = (Buffer) {.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
+}};
+/*
+      █████████      
+      █████████      
+██████   ███   ██████
+█████████████████████
+██████   ███   ██████
+         ███         
+      █████████      
+*/
+const Buffer sprite_clubs = (Buffer) {.width=8, .height=7, .data=(uint8_t[]) {
+		0x1c, 
+		0x1c, 
+		0x6b, 
+		0x7f, 
+		0x6b, 
+		0x8, 
+		0x1c
+}};
+/*
+██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+███                                                                                                                                                                                                            ███
+███                                                                                                                                                                                                            ███
+███               ███████████████                                       ██████         ██████            ███                                    ██████                                                         ███
+███            █████████████████████                                    ██████         ██████         ██████                                    ██████                                                         ███
+███         █████████         █████████                                 ██████         ██████         ██████                                    ██████                                                         ███
+███         ██████               ██████                                 ██████                        ██████                                                                                                   ███
+███         ██████                              ████████████            ██████         ██████      ███████████████      ███████████████         ██████         ██████   ██████         █████████               ███
+███         █████████                        ██████████████████         ██████         ██████      ███████████████      ██████████████████      ██████         ███████████████      ███████████████            ███
+███            ███████████████            █████████      █████████      ██████         ██████         ██████         ██████         ██████      ██████         ██████            ██████         ██████         ███
+███                  ███████████████      ██████            ██████      ██████         ██████         ██████                        ██████      ██████         ██████            ██████         ██████         ███
+███                           █████████   ██████            ██████      ██████         ██████         ██████                  ████████████      ██████         ██████            █████████████████████         ███
+███         ██████               ██████   ██████            ██████      ██████         ██████         ██████            ██████████████████      ██████         ██████            █████████████████████         ███
+███         ██████               ██████   ██████            ██████      ██████         ██████         ██████         █████████      ██████      ██████         ██████            ██████                        ███
+███         █████████         █████████   █████████      █████████      ██████         ██████         ██████         ██████         ██████      ██████         ██████            ██████         ██████         ███
+███            █████████████████████         ██████████████████         ██████         ██████         ████████████   █████████████████████      ██████         ██████               ███████████████            ███
+███               ███████████████               ████████████            ██████         ██████            █████████      █████████   ██████      ██████         ██████                  █████████               ███
+███                                                                                                                                                                                                            ███
+███                                                                                                                                                                                                            ███
+██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+                                                                                                      ███                                                                                          ███            
+      ███         ███               ███                     ███                  █████████            ███                                                                        ███               ███            
+   ███   ███   ███   ███         ███   ███               █████████               █████████            ███   ███                        █████████                              ███                  ███            
+   ███      ███      ███      ███         ███         ███████████████      ██████   ███   ██████      ███   ██████      ███   ███      ███      ███      ███         ███   █████████   ███   ███   ███            
+   ███               ███   ███               ███   █████████████████████   █████████████████████      ███   ███   ███   ███   ███      ███      ███   ███   ███   ███   ███   ███      ███   ███   ███            
+      ███         ███         ███         ███      ██████   ███   ██████   ██████   ███   ██████      ███   ███   ███      ███         ███      ███   ███   ███   ███   ███   ███         ███      ███            
+         ███   ███               ███   ███                  ███                     ███               ███   ██████         ███         █████████         ███         ███      ███         ███      ███            
+            ███                     ███                  █████████               █████████            ███                                                                                          ███            
+                                                                                                      ████████████████████████████████████████████████████████████████████████████████████████████████            
+*/
+const Buffer sprite_logo = (Buffer) {.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
+}};

+ 25 - 262
assets.h

@@ -1,11 +1,5 @@
-#pragma once
 #include <furi.h>
 #include <furi.h>
-
-struct SpriteData {
-    uint8_t width = 0;
-    uint8_t height = 0;
-    uint8_t *data = NULL;
-};
+#include "src/util/buffer.h"
 
 
 /*
 /*
       ██████   
       ██████   
@@ -14,13 +8,7 @@ struct SpriteData {
 ████████████   
 ████████████   
          ███   
          ███   
 */
 */
-const SpriteData sprite_4 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0xc, 
-		0xa, 
-		0x9, 
-		0xf, 
-		0x8
-}};
+extern const Buffer sprite_4;
 /*
 /*
 ████████████   
 ████████████   
          ███   
          ███   
@@ -28,13 +16,7 @@ const SpriteData sprite_4 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
    ███         
    ███         
    ███         
    ███         
 */
 */
-const SpriteData sprite_7 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0xf, 
-		0x8, 
-		0x4, 
-		0x2, 
-		0x2
-}};
+extern const Buffer sprite_7;
 /*
 /*
    ███         ███   
    ███         ███   
 ███   ███   ███   ███
 ███   ███   ███   ███
@@ -44,15 +26,7 @@ const SpriteData sprite_7 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
       ███   ███      
       ███   ███      
          ███         
          ███         
 */
 */
-const SpriteData sprite_hearths = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
-		0x22, 
-		0x55, 
-		0x49, 
-		0x41, 
-		0x22, 
-		0x14, 
-		0x8
-}};
+extern const Buffer sprite_hearths;
 /*
 /*
    ██████      
    ██████      
 ███      ███   
 ███      ███   
@@ -60,13 +34,7 @@ const SpriteData sprite_hearths = (SpriteData) {.width=8, .height=7, .data=(uint
 ███      ███   
 ███      ███   
 ███      ███   
 ███      ███   
 */
 */
-const SpriteData sprite_A = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x6, 
-		0x9, 
-		0xf, 
-		0x9, 
-		0x9
-}};
+extern const Buffer sprite_A;
 /*
 /*
                      
                      
       ███   ███   ███
       ███   ███   ███
@@ -76,15 +44,7 @@ const SpriteData sprite_A = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
       ███   ███   ███
       ███   ███   ███
    ███   ███   ███   
    ███   ███   ███   
 */
 */
-const SpriteData sprite_pattern_small = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
-		0x0, 
-		0x54, 
-		0x2a, 
-		0x54, 
-		0x2a, 
-		0x54, 
-		0x2a
-}};
+extern const Buffer sprite_pattern_small;
 /*
 /*
                   ███                        
                   ███                        
    ███      ████████████                     
    ███      ████████████                     
@@ -108,29 +68,7 @@ const SpriteData sprite_pattern_small = (SpriteData) {.width=8, .height=7, .data
       ███            ███   ███      ███      
       ███            ███   ███      ███      
       ███         ███      ███   ███         
       ███         ███      ███   ███         
 */
 */
-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
-}};
+extern const Buffer sprite_pattern_big;
 /*
 /*
                                           █████████████████████████████████████████████            
                                           █████████████████████████████████████████████            
                                        ███                                             ███         
                                        ███                                             ███         
@@ -177,52 +115,7 @@ const SpriteData sprite_pattern_big = (SpriteData) {.width=16, .height=21, .data
                      ███                                             ███                           
                      ███                                             ███                           
                         █████████████████████████████████████████████                              
                         █████████████████████████████████████████████                              
 */
 */
-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
-}};
+extern const Buffer sprite_main_image;
 /*
 /*
    ██████      
    ██████      
 ███      ███   
 ███      ███   
@@ -230,13 +123,7 @@ const SpriteData sprite_main_image = (SpriteData) {.width=40, .height=44, .data=
 ███   ███      
 ███   ███      
    ███   ███   
    ███   ███   
 */
 */
-const SpriteData sprite_Q = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x6, 
-		0x9, 
-		0x9, 
-		0x5, 
-		0xa
-}};
+extern const Buffer sprite_Q;
 /*
 /*
    ██████      
    ██████      
 ███      ███   
 ███      ███   
@@ -244,13 +131,7 @@ const SpriteData sprite_Q = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
    ███         
    ███         
 ████████████   
 ████████████   
 */
 */
-const SpriteData sprite_2 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x6, 
-		0x9, 
-		0x4, 
-		0x2, 
-		0xf
-}};
+extern const Buffer sprite_2;
 /*
 /*
 █████████      
 █████████      
          ███   
          ███   
@@ -258,13 +139,7 @@ const SpriteData sprite_2 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
          ███   
          ███   
 █████████      
 █████████      
 */
 */
-const SpriteData sprite_3 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x7, 
-		0x8, 
-		0x6, 
-		0x8, 
-		0x7
-}};
+extern const Buffer sprite_3;
 /*
 /*
 ███      ███   
 ███      ███   
 ███   ███   ███
 ███   ███   ███
@@ -272,13 +147,7 @@ const SpriteData sprite_3 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
 ███   ███   ███
 ███   ███   ███
 ███      ███   
 ███      ███   
 */
 */
-const SpriteData sprite_10 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x9, 
-		0x15, 
-		0x15, 
-		0x15, 
-		0x9
-}};
+extern const Buffer sprite_10;
 /*
 /*
 ███      ███   
 ███      ███   
 ███   ███      
 ███   ███      
@@ -286,13 +155,7 @@ const SpriteData sprite_10 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]
 ███   ███      
 ███   ███      
 ███      ███   
 ███      ███   
 */
 */
-const SpriteData sprite_K = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x9, 
-		0x5, 
-		0x3, 
-		0x5, 
-		0x9
-}};
+extern const Buffer sprite_K;
 /*
 /*
 ████████████   
 ████████████   
 ███            
 ███            
@@ -300,13 +163,7 @@ const SpriteData sprite_K = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
          ███   
          ███   
 ████████████   
 ████████████   
 */
 */
-const SpriteData sprite_5 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0xf, 
-		0x1, 
-		0x7, 
-		0x8, 
-		0xf
-}};
+extern const Buffer sprite_5;
 /*
 /*
    ██████      
    ██████      
 ███            
 ███            
@@ -314,13 +171,7 @@ const SpriteData sprite_5 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
 ███      ███   
 ███      ███   
    ██████      
    ██████      
 */
 */
-const SpriteData sprite_6 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x6, 
-		0x1, 
-		0x7, 
-		0x9, 
-		0x6
-}};
+extern const Buffer sprite_6;
 /*
 /*
    ██████      
    ██████      
 ███      ███   
 ███      ███   
@@ -328,13 +179,7 @@ const SpriteData sprite_6 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
          ███   
          ███   
    ██████      
    ██████      
 */
 */
-const SpriteData sprite_9 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x6, 
-		0x9, 
-		0xe, 
-		0x8, 
-		0x6
-}};
+extern const Buffer sprite_9;
 /*
 /*
          ███         
          ███         
       ███   ███      
       ███   ███      
@@ -344,15 +189,7 @@ const SpriteData sprite_9 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
       ███   ███      
       ███   ███      
          ███         
          ███         
 */
 */
-const SpriteData sprite_diamonds = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
-		0x8, 
-		0x14, 
-		0x22, 
-		0x41, 
-		0x22, 
-		0x14, 
-		0x8
-}};
+extern const Buffer sprite_diamonds;
 /*
 /*
 ████████████   
 ████████████   
 ███      ███   
 ███      ███   
@@ -360,13 +197,7 @@ const SpriteData sprite_diamonds = (SpriteData) {.width=8, .height=7, .data=(uin
 ███      ███   
 ███      ███   
 ████████████   
 ████████████   
 */
 */
-const SpriteData sprite_8 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0xf, 
-		0x9, 
-		0xf, 
-		0x9, 
-		0xf
-}};
+extern const Buffer sprite_8;
 /*
 /*
          ███   
          ███   
          ███   
          ███   
@@ -374,13 +205,7 @@ const SpriteData sprite_8 = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
 ███      ███   
 ███      ███   
    ██████      
    ██████      
 */
 */
-const SpriteData sprite_J = (SpriteData) {.width=8, .height=5, .data=(uint8_t[]) {
-		0x8, 
-		0x8, 
-		0x8, 
-		0x9, 
-		0x6
-}};
+extern const Buffer sprite_J;
 /*
 /*
          ███         
          ███         
       █████████      
       █████████      
@@ -390,15 +215,7 @@ const SpriteData sprite_J = (SpriteData) {.width=8, .height=5, .data=(uint8_t[])
          ███         
          ███         
       █████████      
       █████████      
 */
 */
-const SpriteData sprite_spades = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
-		0x8, 
-		0x1c, 
-		0x3e, 
-		0x7f, 
-		0x6b, 
-		0x8, 
-		0x1c
-}};
+extern const Buffer sprite_spades;
 /*
 /*
       ████████████               ████████████      ███                                    ███   
       ████████████               ████████████      ███                                    ███   
    ██████████████████         ███            ███   ███                                    ███   
    ██████████████████         ███            ███   ███                                    ███   
@@ -409,16 +226,7 @@ const SpriteData sprite_spades = (SpriteData) {.width=8, .height=7, .data=(uint8
    ██████████████████         ███            ███   ███      ███      ██████   ███         ███   
    ██████████████████         ███            ███   ███      ███      ██████   ███         ███   
       ████████████               ████████████      ██████   █████████   ███   ███         ██████
       ████████████               ████████████      ██████   █████████   ███   ███         ██████
 */
 */
-const SpriteData sprite_start = (SpriteData) {.width=32, .height=8, .data=(uint8_t[]) {
-		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
-}};
+extern const Buffer sprite_start;
 /*
 /*
 ███         ███                     ███                  ███            ████████████               ███                                                                  ███                                       
 ███         ███                     ███                  ███            ████████████               ███                                                                  ███                                       
 ███         ███                     ███                  ███         ██████████████████            ███                                                                  ███                                       
 ███         ███                     ███                  ███         ██████████████████            ███                                                                  ███                                       
@@ -428,15 +236,7 @@ const SpriteData sprite_start = (SpriteData) {.width=32, .height=8, .data=(uint8
 ███         ███   ███         ███   ███      ███         ███         ██████████████████            ███      ███         ███                        ██████         ███   ███            ██████      ███            
 ███         ███   ███         ███   ███      ███         ███         ██████████████████            ███      ███         ███                        ██████         ███   ███            ██████      ███            
 ███         ███      █████████      ███         ████████████            ████████████               ██████      █████████               ████████████      █████████      ███            ██████         ████████████
 ███         ███      █████████      ███         ████████████            ████████████               ██████      █████████               ████████████      █████████      ███            ██████         ████████████
 */
 */
-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
-}};
+extern const Buffer sprite_solve;
 /*
 /*
       █████████      
       █████████      
       █████████      
       █████████      
@@ -446,15 +246,7 @@ const SpriteData sprite_solve = (SpriteData) {.width=72, .height=7, .data=(uint8
          ███         
          ███         
       █████████      
       █████████      
 */
 */
-const SpriteData sprite_clubs = (SpriteData) {.width=8, .height=7, .data=(uint8_t[]) {
-		0x1c, 
-		0x1c, 
-		0x6b, 
-		0x7f, 
-		0x6b, 
-		0x8, 
-		0x1c
-}};
+extern const Buffer sprite_clubs;
 /*
 /*
 ██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
 ██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
 ███                                                                                                                                                                                                            ███
 ███                                                                                                                                                                                                            ███
@@ -486,34 +278,5 @@ const SpriteData sprite_clubs = (SpriteData) {.width=8, .height=7, .data=(uint8_
             ███                     ███                  █████████               █████████            ███                                                                                          ███            
             ███                     ███                  █████████               █████████            ███                                                                                          ███            
                                                                                                       ████████████████████████████████████████████████████████████████████████████████████████████████            
                                                                                                       ████████████████████████████████████████████████████████████████████████████████████████████████            
 */
 */
-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
-}};
+extern const Buffer sprite_logo;
+

binární
assets/joker.png


+ 20 - 0
docs/CHANGELOG.md

@@ -0,0 +1,20 @@
+## v2.0.0
+- App rewrite
+- Added quick solve
+- New effects and sounds
+- Removed hacky canvas manipulation
+
+## v1.1.3
+- Aligned with the latest mutex updates
+ 
+## v1.1.0
+- Fixed ability to place ♠3 on empty foundations
+- Implemented Dolphin Deeds
+- Aligned with the latest mutex updates
+
+## v1.0.1
+- Falling cards now working as intended
+- Fixed game state reset issues
+
+## v1.0
+- Initial release 

+ 30 - 0
docs/README.md

@@ -0,0 +1,30 @@
+# Solitaire - Klondike for Flipper Zero
+
+Solitaire, the classic Klondike version, now available on your Flipper Zero. 
+
+## Features
+
+* **Auto-Solve:** Ability to automatically solve the game when all cards are flipped.
+* **Animated Card Movements:** Animated transitions during solve and deal.
+* **Time Tracking:** Displays the time it took to solve at the end of each game.
+* **Falling Cards:** Enjoy a visually satisfying cascade of cards when you win.
+
+## Shortcuts
+
+* **Long Press Any Arrow:** Jump to the furthest point in that direction.
+* **Long Press Center:** Automatically place the card in the top right section.
+* **Long Press Back:** Close the application instantly.
+
+## Rules
+- **Empty Tableau Spots:** Only a King can be placed in an empty tableau spot.
+- **Tableau Arrangement:** Cards must be arranged in descending order and alternating colors (e.g., red ten on black nine) within the tableau.
+- **Empty Foundation Piles:** Only an Ace can be placed in an empty foundation pile.
+- **Foundation Pile Arrangement:** Cards must be in ascending order and in the same suit.
+
+## How to Play
+
+- Use the directional arrows to navigate and move cards.
+- Pick up cards with the center button.
+- Aim to build up four foundation piles in ascending order, separated by suit.
+- Flip and move cards within the tableau to reveal hidden cards.
+- You can place back cards in the same spot you picked them up.

+ 0 - 0
docs/changelog.md


+ 44 - 0
game_state.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/canvas.h>
+#include <gui/gui.h>
+#include "src/util/buffer.h"
+#include "src/util/list.h"
+#include "src/util/card.h"
+#include <notification/notification.h>
+
+typedef struct {
+    Card *card;
+    Vector position;
+    Vector velocity;
+} AnimatedCard;
+
+typedef struct {
+    Canvas *canvas;
+    Gui *gui;
+    FuriPubSub *input;
+    FuriPubSubSubscription *input_subscription;
+    bool exit;
+    bool isDirty;
+    bool clearBuffer;
+    bool lateRender;
+    uint8_t scene_switch;
+    Buffer *buffer;
+    NotificationApp *notification_app;
+    uint8_t selected[2];
+    uint8_t selected_card;
+
+    List *deck;
+    List *waste;
+    List *hand;
+    List *foundation[4];
+    List *tableau[7];
+
+    AnimatedCard animated_card;
+    double delta_time;
+    size_t game_start;
+    size_t game_end;
+
+} GameState;
+

binární
screenshots/catalog_1.png


binární
screenshots/catalog_2.png


binární
screenshots/catalog_3.png


binární
screenshots/catalog_4.png


binární
screenshots/catalog_5.png


binární
screenshots/catalog_6.png


binární
screenshots/solitaire.png


+ 184 - 0
solitaire.c

@@ -0,0 +1,184 @@
+#include <furi.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <notification/notification_messages.h>
+
+#include "game_state.h"
+#include "src/util/list.h"
+#include "src/scene/scene_setup.h"
+#include "src/util/helpers.h"
+
+static List *game_logic;
+static ListItem *current_state;
+static FuriMutex *update_mutex;
+
+static void gui_input_events_callback(const void *value, void *ctx) {
+    furi_mutex_acquire(update_mutex, FuriWaitForever);
+    GameState *instance = ctx;
+    const InputEvent *event = value;
+
+    if (event->key == InputKeyBack && event->type == InputTypeLong) {
+        FURI_LOG_W("INPUT", "EXIT");
+        instance->exit = true;
+    }
+
+    if (current_state) {
+        ((GameLogic *) current_state->data)->input(instance, event->key, event->type);
+    }
+    furi_mutex_release(update_mutex);
+}
+
+
+static GameState *prepare() {
+    game_logic = list_make();
+    update_mutex = (FuriMutex *) furi_mutex_alloc(FuriMutexTypeNormal);
+
+    //Add scenes to the logic list
+    list_push_back(&main_screen, game_logic);
+    list_push_back(&intro_screen, game_logic);
+    list_push_back(&play_screen, game_logic);
+    list_push_back(&solve_screen, game_logic);
+    list_push_back(&falling_screen, game_logic);
+    list_push_back(&result_screen, game_logic);
+
+    current_state = game_logic->head;
+
+    GameState *instance = malloc(sizeof(GameState));
+    ((GameLogic *) current_state->data)->start(instance);
+
+    instance->hand = list_make();
+    instance->deck = list_make();
+    instance->waste = list_make();
+    for (int i = 0; i < 7; i++) {
+        if (i < 4) {
+            instance->foundation[i] = list_make();
+        }
+        instance->tableau[i] = list_make();
+    }
+
+    instance->animated_card.position = VECTOR_ZERO;
+    instance->animated_card.velocity = VECTOR_ZERO;
+
+
+    instance->buffer = buffer_create(SCREEN_WIDTH, SCREEN_HEIGHT, false);
+    instance->input = furi_record_open(RECORD_INPUT_EVENTS);
+    instance->gui = furi_record_open(RECORD_GUI);
+    instance->canvas = gui_direct_draw_acquire(instance->gui);
+    instance->notification_app = (NotificationApp *) furi_record_open(RECORD_NOTIFICATION);
+    notification_message_block(instance->notification_app, &sequence_display_backlight_enforce_on);
+
+
+    instance->input_subscription =
+        furi_pubsub_subscribe(instance->input, gui_input_events_callback, instance);
+
+    return instance;
+}
+
+static void cleanup(GameState *instance) {
+    furi_pubsub_unsubscribe(instance->input, instance->input_subscription);
+    notification_message_block(instance->notification_app, &sequence_display_backlight_enforce_auto);
+
+    list_free(instance->hand);
+    list_free(instance->deck);
+    list_free(instance->waste);
+    for (int i = 0; i < 7; i++) {
+        if (i < 4) {
+            list_free(instance->foundation[i]);
+        }
+        list_free(instance->tableau[i]);
+    }
+
+    furi_mutex_free(update_mutex);
+    instance->canvas = NULL;
+    gui_direct_draw_release(instance->gui);
+    furi_record_close(RECORD_GUI);
+    furi_record_close(RECORD_INPUT_EVENTS);
+    list_clear(game_logic);
+    buffer_release(instance->buffer);
+    free(instance);
+}
+
+static void next_scene(GameState *instance) {
+    FURI_LOG_W("SCENE", "Next scene");
+    current_state = current_state->next;
+    if (current_state == NULL) {
+        current_state = game_logic->head;
+    }
+    ((GameLogic *) current_state->data)->start(instance);
+}
+
+static void prev_scene(GameState *instance) {
+    FURI_LOG_W("SCENE", "Prev scene");
+    current_state = game_logic->head;
+    if (current_state->prev == NULL) {
+        instance->exit = true;
+        return;
+    }
+    ((GameLogic *) current_state->data)->start(instance);
+}
+
+static void direct_draw_run(GameState *instance) {
+    if(!check_pointer(instance)) return;
+
+    size_t currFrameTime;
+    size_t lastFrameTime = curr_time();
+    instance->lateRender = false;
+
+    furi_thread_set_current_priority(FuriThreadPriorityIdle);
+    do {
+        FuriStatus status = furi_mutex_acquire(update_mutex, 20);
+        if (!status) continue;
+
+        GameLogic *curr_state = (GameLogic *) current_state->data;
+        currFrameTime = curr_time();
+        instance->delta_time = (currFrameTime - lastFrameTime) / 64000000.0f;
+        lastFrameTime = currFrameTime;
+
+        check_pointer(curr_state);
+        curr_state->update(instance);
+        if (instance->scene_switch == 1) {
+            next_scene(instance);
+        } else if (instance->scene_switch == 2) {
+            prev_scene(instance);
+        }
+        check_pointer(curr_state);
+        check_pointer(instance);
+        check_pointer(instance->canvas);
+        check_pointer(instance->buffer);
+        instance->scene_switch = 0;
+        if (curr_state && instance->isDirty && instance->canvas && instance->buffer) {
+            canvas_reset(instance->canvas);
+
+            if(instance->lateRender){
+                buffer_swap_back(instance->buffer);
+                buffer_render(instance->buffer, instance->canvas);
+                curr_state->render(instance);
+            }else{
+                curr_state->render(instance);
+                buffer_swap_back(instance->buffer);
+                buffer_render(instance->buffer, instance->canvas);
+            }
+            canvas_commit(instance->canvas);
+
+            if (instance->clearBuffer)
+                buffer_clear(instance->buffer);
+
+            instance->clearBuffer = true;
+            instance->lateRender = false;
+            instance->isDirty = false;
+        }
+        furi_mutex_release(update_mutex);
+        furi_thread_yield();
+    } while (!instance->exit);
+}
+
+int32_t solitaire_app(void *p) {
+    UNUSED(p);
+    int32_t return_code = 0;
+    GameState *instance = prepare();
+
+    direct_draw_run(instance);
+
+    cleanup(instance);
+    return return_code;
+}

+ 0 - 19
solitaire.cpp

@@ -1,19 +0,0 @@
-#include <furi.h>
-#include "GameLoop.h"
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-int32_t solitaire_app(void *p) {
-    UNUSED(p);
-    int32_t return_code = 0;
-    auto *loop = new GameLoop();
-    loop->Start();
-    delete loop;
-    return return_code;
-}
-
-#ifdef __cplusplus
-}
-#endif

+ 103 - 0
src/scene/falling_card.c

@@ -0,0 +1,103 @@
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include "falling_card.h"
+#include "../../game_state.h"
+#include "play_screen.h"
+#include "../util/helpers.h"
+
+static const NotificationSequence sequence_bounce = {
+    &message_vibro_on,
+//    &message_note_c4,
+    &message_delay_10,
+    &message_vibro_off,
+//    &message_sound_off,
+    NULL,
+};
+static uint8_t start_index = 0;
+static size_t tempTime = 0;
+
+void start_falling_screen(void *data) {
+    //draw the play screen once (without any other thing to have the initial state)
+    GameState *state = (GameState *) data;
+    buffer_clear(state->buffer);
+    render_play_screen(data);
+    state->clearBuffer = false;
+    state->isDirty = true;
+    start_index = 0;
+    tempTime = 0;
+    state->game_end = furi_get_tick();
+}
+
+void render_falling_screen(void *data) {
+    GameState *state = (GameState *) data;
+    if (state->animated_card.card != NULL) {
+        card_render_front(state->animated_card.card, state->animated_card.position.x, state->animated_card.position.y,
+                          false, state->buffer, 22);
+    }
+}
+
+
+void update_falling_screen(void *data) {
+    GameState *state = (GameState *) data;
+    state->clearBuffer = false;
+    state->isDirty = true;
+
+    if (state->animated_card.card) {
+
+        if ((curr_time() - tempTime) > 12) {
+            tempTime = curr_time();
+            state->animated_card.position.x += state->animated_card.velocity.x;
+            state->animated_card.position.y -= state->animated_card.velocity.y;
+
+            //bounce on the bottom
+            if (state->animated_card.position.y > 41) {
+                state->animated_card.velocity.y *= -0.8f;
+                state->animated_card.position.y = 41;
+                notification_message(state->notification_app, (const NotificationSequence *) &sequence_bounce);
+            } else {
+                state->animated_card.velocity.y--;
+                if (state->animated_card.velocity.y < -10) state->animated_card.velocity.y = -10;
+            }
+
+            //delete the card if it is outside the screen
+            if (state->animated_card.position.x < -18 || state->animated_card.position.x > 128) {
+                free(state->animated_card.card);
+                state->animated_card.card = NULL;
+            }
+        }
+
+    } else {
+        //When we find a foundation without any card means that we finished the animation
+        if (state->foundation[start_index]->count == 0) {
+            state->scene_switch = 1;
+            return;
+        }
+
+        //start with the next card
+        state->animated_card.card = list_pop_back(state->foundation[start_index]);
+        state->animated_card.position = (Vector) {56 + start_index * 18, 1};
+
+        float r1 = 2.0 * (float) (rand() % 2) - 1.0; // random number in range -1 to 1
+        if (r1 == 0) r1 = 0.1;
+        float r2 = inverse_tanh(r1);
+        float vx = (float) (tanh(r2)) * (rand() % 3 + 1);
+        state->animated_card.velocity.x = vx == 0 ? 1 : vx;
+        state->animated_card.velocity.y = (rand() % 3 + 1);
+
+
+        start_index = (start_index + 1) % 4;
+    }
+
+}
+
+void input_falling_screen(void *data, InputKey key, InputType type) {
+    GameState *state = (GameState *) data;
+    if (key == InputKeyOk && type == InputTypePress) {
+        //remove card that is currently animated
+        if (state->animated_card.card != NULL) {
+            free(state->animated_card.card);
+            state->animated_card.card = NULL;
+        }
+        state->scene_switch = 1;
+    }
+}

+ 11 - 0
src/scene/falling_card.h

@@ -0,0 +1,11 @@
+#pragma once
+#include <furi.h>
+#include <input/input.h>
+
+void start_falling_screen(void *data);
+
+void render_falling_screen(void *data);
+
+void update_falling_screen(void *data);
+
+void input_falling_screen(void *data, InputKey key, InputType type);

+ 119 - 0
src/scene/intro_animation.c

@@ -0,0 +1,119 @@
+#include "./intro_animation.h"
+
+#include <dolphin/dolphin.h>
+
+#include "../../game_state.h"
+#include "../util/helpers.h"
+#include "play_screen.h"
+
+static bool animation_running = true;
+static int8_t curr_tableau = 0;
+static Vector animation_target = VECTOR_ZERO;
+static Vector animation_from = VECTOR_ZERO;
+static double accumulated_delta = 0;
+
+void start_animation(GameState *state) {
+    accumulated_delta = 0;
+    check_pointer(state->deck->tail);
+    state->animated_card.card = list_peek_back(state->deck);
+    check_pointer(state->animated_card.card);
+    animation_from = (Vector) {2, 1};
+    animation_target.x = 2.0f + (float) curr_tableau * 18;
+    check_pointer(state->tableau[curr_tableau]);
+    animation_target.y = MIN(25.0f + (float) state->tableau[curr_tableau]->count * 4, 36);
+}
+
+void start_intro_screen(void *data) {
+    curr_tableau = 0;
+    animation_running = true;
+    dolphin_deed(DolphinDeedPluginGameStart);
+    GameState *state = (GameState *) data;
+    check_pointer(state->deck);
+    list_free(state->deck);
+    check_pointer(state->hand);
+    list_free_data(state->hand);
+    check_pointer(state->waste);
+    list_free_data(state->waste);
+    for (int i = 0; i < 7; i++) {
+        if (i < 4) {
+            check_pointer(state->foundation[i]);
+            list_free_data(state->foundation[i]);
+        }
+        check_pointer(state->tableau[i]);
+        list_free_data(state->tableau[i]);
+    }
+
+    state->deck = deck_generate(1);
+    check_pointer(state->deck->tail);
+    start_animation(state);
+}
+
+void render_intro_screen(void *data) {
+
+    GameState *state = (GameState *) data;
+    render_play_screen(data);
+
+    if (state->animated_card.card) {
+        card_render_back(state->animated_card.position.x, state->animated_card.position.y, false, state->buffer, 22);
+    }
+}
+
+static bool animation_done(GameState *state) {
+    FURI_LOG_W("DELTA", "%f", (double)state->delta_time);
+    accumulated_delta += state->delta_time * 4;
+    vector_lerp(&(animation_from), &animation_target, accumulated_delta,
+                &(state->animated_card.position));
+    double dist = vector_distance(&(state->animated_card.position), &animation_target);
+    return dist < 1;
+}
+
+void update_intro_screen(void *data) {
+    GameState *state = (GameState *) data;
+    state->isDirty = true;
+    if (curr_tableau < 7 && animation_running) {
+        if (animation_done(state)) {
+            if (curr_tableau < 7) {
+                check_pointer(state->deck);
+                check_pointer(state->deck->tail);
+                check_pointer(state->tableau);
+                check_pointer(state->tableau[curr_tableau]);
+                list_push_back(list_pop_back(state->deck), state->tableau[curr_tableau]);
+                if ((curr_tableau + 1) == (uint8_t) state->tableau[curr_tableau]->count) {
+                    Card *c = ((Card *) list_peek_back(state->tableau[curr_tableau]));
+                    check_pointer(c);
+                    c->exposed = true;
+                    curr_tableau++;
+                }
+                start_animation(state);
+            }
+        }
+    } else {
+        state->animated_card.card = NULL;
+        state->scene_switch = 1;
+        return;
+    }
+}
+
+static void quick_finish(GameState *state) {
+    if (state->animated_card.card) {
+        list_push_back(list_pop_back(state->deck), state->tableau[curr_tableau]);
+    }
+
+    while (curr_tableau < 7) {
+        while ((uint8_t) (state->tableau[curr_tableau]->count) < (curr_tableau + 1)) {
+            list_push_back(list_pop_back(state->deck), state->tableau[curr_tableau]);
+        }
+        ((Card *) list_peek_back(state->tableau[curr_tableau]))->exposed = true;
+        curr_tableau++;
+    }
+    animation_running = false;
+}
+
+void input_intro_screen(void *data, InputKey key, InputType type) {
+    GameState *state = (GameState *) data;
+    if (key == InputKeyOk && type == InputTypePress) {
+        quick_finish(state);
+        animation_running = false;
+    }
+
+}

+ 12 - 0
src/scene/intro_animation.h

@@ -0,0 +1,12 @@
+#pragma once
+#include <furi.h>
+#include <input/input.h>
+
+
+void start_intro_screen(void *data);
+
+void render_intro_screen(void *data);
+
+void update_intro_screen(void *data);
+
+void input_intro_screen(void *data, InputKey key, InputType type);

+ 130 - 0
src/scene/main_screen.c

@@ -0,0 +1,130 @@
+#include <notification/notification_messages.h>
+#include "main_screen.h"
+
+static bool is_dirty = false;
+static size_t last_start = 0;
+static int8_t note = 0;
+static const float VOLUME = 0.25f;
+
+static const NotificationMessage note_e4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 329.63f,
+    .data.sound.volume = VOLUME,
+};
+
+static const NotificationMessage note_g4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 392.0f,
+    .data.sound.volume = VOLUME,
+};
+
+static const NotificationMessage note_c5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 523.25f,
+    .data.sound.volume = VOLUME,
+};
+
+static const NotificationMessage note_d4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 293.66f,
+    .data.sound.volume = VOLUME,
+};
+
+static const NotificationMessage note_f4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 349.23f,
+    .data.sound.volume = VOLUME,
+};
+
+static const NotificationMessage note_b4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 493.88f,
+    .data.sound.volume = VOLUME,
+};
+
+static const NotificationMessage *music_notes[] = {
+    &note_e4,
+    &note_g4,
+    &note_c5,
+    &message_sound_off,
+    &note_g4,
+    &message_sound_off,
+
+    &note_e4,
+    &note_g4,
+    &note_c5,
+    &message_sound_off,
+    &note_g4,
+    &message_sound_off,
+
+    &note_d4,
+    &note_f4,
+    &note_b4,
+    &message_sound_off,
+    &note_f4,
+    &message_sound_off,
+
+    &note_d4,
+    &note_f4,
+    &note_b4,
+    &message_sound_off,
+    &note_f4,
+    &message_sound_off,
+};
+
+static NotificationSequence music = {
+    NULL,
+    &message_delay_250,
+    &message_sound_off,
+    NULL
+};
+
+
+void start_main_screen(void *data) {
+    is_dirty = true;
+    GameState *state = (GameState *) data;
+    last_start=0;
+    note = -1;
+    state->lateRender = false;
+    state->isDirty = true;
+    state->clearBuffer = true;
+}
+
+void render_main_screen(void *data) {
+    GameState *state = (GameState *) data;
+    Vector logo_pos = (Vector) {60, 30};
+    Vector main_img_pos = (Vector) {115, 25};
+    Vector start_text_pos = (Vector) {64, 55};
+    buffer_draw_all(state->buffer, (Buffer *) &sprite_logo, &logo_pos, 0);
+    buffer_draw_all(state->buffer, (Buffer *) &sprite_main_image, &main_img_pos, 0);
+    buffer_draw_all(state->buffer, (Buffer *) &sprite_start, &start_text_pos, 0);
+}
+
+void update_main_screen(void *data) {
+    GameState *state = (GameState *) data;
+    UNUSED(state);
+    size_t t = curr_time();
+    //Play the menu music one note at a time to not block the app
+    if ((last_start - t) > 250) {
+        if(note>=0) {
+            music[0] = music_notes[note];
+
+            notification_message_block(state->notification_app, (const NotificationSequence *) &music);
+
+            last_start = t;
+        }
+        note = (note+1)%24;
+    }
+
+    state->isDirty = is_dirty;
+    is_dirty = false;
+
+}
+
+void input_main_screen(void *data, InputKey key, InputType type) {
+    GameState *state = (GameState *) data;
+
+    if (key == InputKeyOk && type == InputTypePress) {
+        state->scene_switch = 1;
+    }
+}

+ 16 - 0
src/scene/main_screen.h

@@ -0,0 +1,16 @@
+#pragma once
+
+#include <furi.h>
+#include <input/input.h>
+
+#include "../../game_state.h"
+#include "../../assets.h"
+#include "../util/helpers.h"
+
+void start_main_screen(void *data);
+
+void render_main_screen(void *data);
+
+void update_main_screen(void *data);
+
+void input_main_screen(void *data, InputKey key, InputType type);

+ 341 - 0
src/scene/play_screen.c

@@ -0,0 +1,341 @@
+#include <notification/notification_messages.h>
+#include "./play_screen.h"
+#include "../../game_state.h"
+#include "../util/helpers.h"
+#include "../../assets.h"
+
+static bool can_quick_solve = false;
+static bool solved = false;
+static bool started = false;
+int8_t picked_from[2] = {-1, -1};
+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,
+};
+
+
+void end_play_screen(GameState *state) {
+
+    //put back the card from the hand to allow quick solve
+    if (state->hand->count) {
+        if (picked_from[1] == 1) {
+            while (state->hand->tail) {
+                list_push_back(list_pop_front(state->hand), state->tableau[picked_from[1]]);
+            }
+        }
+        if (picked_from[1] == 0) {
+            list_push_back(list_pop_front(state->hand), state->waste);
+        }
+    }
+
+    state->selected[0] = 0;
+    state->selected[1] = 0;
+    started = false;
+    solved = false;
+    state->scene_switch = 1;
+}
+
+void check_quick_solve(GameState *state) {
+    uint8_t c = 0;
+    for (uint8_t i = 0; i < 7; i++) {
+        Card *front = list_peek_front(state->tableau[i]);
+        if (!front || front->exposed) c++;
+    }
+    can_quick_solve = c == 7;
+}
+
+void start_play_screen(void *data) {
+    GameState *state = (GameState *) data;
+    picked_from[0] = -1;
+    picked_from[1] = -1;
+    state->selected[0] = 0;
+    state->selected[1] = 0;
+    state->selected_card = 0;
+    state->isDirty = true;
+    can_quick_solve = false;
+    solved = false;
+    started = true;
+    state->game_start = furi_get_tick();
+}
+
+bool check_finish(void *data) {
+    GameState *state = (GameState *) data;
+    if (state->waste->count > 0 || state->deck->count > 0) return false;
+
+    for (uint8_t i = 0; i < 7; i++) {
+        Card *front = list_peek_front(state->tableau[i]);
+        if (front) return false;
+    }
+
+    return true;
+}
+
+void reset_picked() {
+    picked_from[0] = -1;
+    picked_from[1] = -1;
+}
+
+bool is_picked_from(uint8_t x, uint8_t y) {
+    return picked_from[0] == x && picked_from[1] == y;
+}
+
+void set_picked_from(int8_t x, int8_t y) {
+    picked_from[0] = x;
+    picked_from[1] = y;
+}
+
+void render_play_screen(void *data) {
+
+    GameState *state = (GameState *) data;
+
+    check_pointer(state->deck);
+    check_pointer(state->waste);
+
+    //Render deck, if there is more than one card left, simulate a bit of depth
+    if (state->deck->count > 1) {
+        card_render_slot(2, 1, false, state->buffer);
+        deck_render(state->deck, Normal, 1, 0, state->selected[0] == 0 && state->selected[1] == 0, true, state->buffer);
+    } else {
+        deck_render(state->deck, Normal, 2, 1, state->selected[0] == 0 && state->selected[1] == 0, true, state->buffer);
+    }
+
+    //Render waste pile
+    deck_render(state->waste, Normal, 20, 1, state->selected[0] == 1 && state->selected[1] == 0, true, state->buffer);
+
+    //Render tableau and foundation
+    for (uint8_t x = 0; x < 7; x++) {
+        if (x < 4) {
+            check_pointer(state->foundation[x]);
+            deck_render(state->foundation[x], Normal, 56 + x * 18, 1,
+                        state->selected[0] == x + 3 && state->selected[1] == 0, true, state->buffer);
+        }
+        check_pointer(state->tableau[x]);
+        deck_render(state->tableau[x], Vertical, 2 + x * 18, 25,
+                    (state->selected[0] == x && state->selected[1] == 1) ? state->selected_card : 0, true,
+                    state->buffer);
+    }
+
+    uint8_t h = state->selected[1] == 1 ? (MIN((uint8_t) state->tableau[state->selected[0]]->count, 4) * 4 + 15) : 0;
+
+    //render cards in hand
+    deck_render(state->hand, Vertical, 10 + state->selected[0] * 18, h + 10, false, false,
+                state->buffer);
+
+    if (started && can_quick_solve) {
+        buffer_draw_rbox(state->buffer, 26, 53, 100, 64, White);
+        buffer_draw_rbox_frame(state->buffer, 25, 52, 101, 65, Black);
+        Vector pos = (Vector) {64, 58};
+        buffer_draw_all(state->buffer, (Buffer *) &sprite_solve, &pos, 0);
+    }
+
+}
+
+void update_play_screen(void *data) {
+    GameState *state = (GameState *) data;
+    if (solved) {
+        end_play_screen(state);
+    }
+}
+
+void input_play_screen(void *data, InputKey key, InputType type) {
+    GameState *state = (GameState *) data;
+    state->isDirty = true;
+
+    if (type == InputTypePress) {
+        switch (key) {
+            case InputKeyLeft:
+                if (state->selected[0] > 0) state->selected[0]--;
+                if (state->selected[0] == 2 && state->selected[1] == 0) state->selected[0]--;
+                state->selected_card = 1;
+                return;
+                break;
+            case InputKeyRight:
+                if (state->selected[0] < 6) state->selected[0]++;
+                if (state->selected[0] == 2 && state->selected[1] == 0) state->selected[0]++;
+                state->selected_card = 1;
+                return;
+                break;
+            case InputKeyUp:
+                //try to move selection inside the tableau
+                if (state->selected[1] == 1) {
+                    //check if highlight can move up in the tableau
+                    int8_t id, id_flipped;
+                    deck_first_non_flipped(state->tableau[state->selected[0]], &id);
+                    id_flipped = state->tableau[state->selected[0]]->count - id;
+                    //move up until it reaches the last exposed card, disable when there is something in hand or no card is exposed
+                    if (state->selected_card < id_flipped && id >= 0 && state->hand->count == 0) {
+                        state->selected_card++;
+                    }
+                        //move to the top row
+                    else {
+                        state->selected[1] = 0;
+                        state->selected_card = 1;
+                        if (state->selected[0] == 2) state->selected[0]--;
+                    }
+                }
+                return;
+                break;
+            case InputKeyDown:
+                if (state->selected[1] == 0) {
+                    state->selected_card = 1;
+                    state->selected[1] = 1;
+                } else if (state->selected_card > 1) {
+                    state->selected_card--;
+                }
+                return;
+                break;
+            case InputKeyOk:
+
+                //cycle deck
+                if (state->selected[0] == 0 && state->selected[1] == 0) {
+                    if(state->deck->count > 0 || state->waste->count > 0) {
+                        if (state->deck->count > 0) {
+                            Card *c = list_pop_back(state->deck);
+                            c->exposed = true;
+                            list_push_back(c, state->waste);
+                            return;
+                        } else {
+                            while (state->waste->count) {
+                                Card *c = list_pop_back(state->waste);
+                                c->exposed = false;
+                                list_push_back(c, state->deck);
+                            }
+                            return;
+                        }
+                    }
+                    if(can_quick_solve) return;
+                } else if (state->selected[0] == 1 && state->selected[1] == 0) {
+                    //pick from waste
+                    if (state->hand->count == 0 && state->waste->count > 0) {
+                        list_push_back(list_pop_back(state->waste), state->hand);
+                        set_picked_from(0, 1);
+                        return;
+                    } else if (is_picked_from(0, 1)) { //put back to waste
+                        list_push_back(list_pop_back(state->hand), state->waste);
+                        reset_picked();
+                        return;
+                    }
+
+                }
+                    //test if it can be put to the foundation (only if 1 card is in hand)
+                else if (state->hand->count == 1 && state->selected[1] == 0 && state->selected[0] > 2) {
+                    List *foundation = state->foundation[state->selected[0] - 3];
+                    check_pointer(foundation);
+                    if (card_test_foundation(list_peek_front(state->hand), list_peek_back(foundation))) {
+                        list_push_back(list_pop_front(state->hand), foundation);
+                        reset_picked();
+                        solved = check_finish(state);
+                        return;
+                    }
+                } else if (state->selected[1] == 1) { //Pick from tableau or flip card
+                    //store a reference to the tableau, doesn't matter if we are over them or not, it can be indexed
+                    List *tbl = state->tableau[state->selected[0]];
+                    //pick cards
+                    if (state->hand->count == 0) {
+                        Card *last = list_peek_back(tbl);
+                        if(last) {
+                            //Flip card if not exposed
+                            if (!last->exposed) {
+                                last->exposed = true;
+                                check_quick_solve(state);
+                                return;
+                            }
+                                //Pick cards
+                            else {
+                                for (uint8_t i = 0; i < state->selected_card && tbl->count > 0; i++) {
+                                    list_push_front(list_pop_back(tbl), state->hand);
+                                }
+                                set_picked_from(state->selected[0], 1);
+                                state->selected_card = 1;
+                                return;
+                            }
+                        }
+                        if(can_quick_solve) return;
+                    }
+                        //try to place hand
+                    else {
+                        Card *last = list_peek_back(tbl);
+                        //place back from where you picked up
+                        if (is_picked_from(state->selected[0], 1)) {
+                            while (state->hand->count) {
+                                list_push_back(list_pop_front(state->hand), tbl);
+                            }
+                            reset_picked();
+                            return;
+                        }
+                            //test if the hand can be placed at one of the tableau columns
+                        else if ((!last || last->exposed) && card_test_column(list_peek_front(state->hand), last)) {
+                            while (state->hand->count) {
+                                list_push_back(list_pop_front(state->hand), tbl);
+                            }
+                            reset_picked();
+                            return;
+                        }
+                    }
+                }
+                break;
+            default:
+                return;
+        }
+        notification_message(state->notification_app, &sequence_fail);
+    } else if (type == InputTypeLong) {
+        switch (key) {
+            case InputKeyLeft:
+                state->selected_card = 1;
+                state->selected[0] = 0;
+                return;
+                break;
+            case InputKeyRight:
+                state->selected_card = 1;
+                state->selected[0] = 6;
+                return;
+                break;
+            case InputKeyUp:
+                state->selected[1] = 0;
+                state->selected_card = 1;
+                if (state->selected[0] == 2) state->selected[0]--;
+                return;
+                break;
+            case InputKeyDown:
+                state->selected_card = 1;
+                state->selected[1] = 1;
+                return;
+                break;
+            case InputKeyOk:
+                if (can_quick_solve) {
+                    end_play_screen(state);
+                    return;
+                }
+
+                //try to quick place to the foundation
+                if (state->hand->count == 1) {
+                    Card *c = list_peek_back(state->hand);
+                    for (int8_t i = 0; i < 4; i++) {
+                        if (card_test_foundation(c, list_peek_back(state->foundation[i]))) {
+                            list_push_back(list_pop_back(state->hand), state->foundation[i]);
+                            state->selected_card = 1;
+                            solved = check_finish(state);
+                            return;
+                        }
+                    }
+                }
+                break;
+            case InputKeyBack:
+                return;
+            default:
+                break;
+        }
+        notification_message(state->notification_app, &sequence_fail);
+    }
+}

+ 14 - 0
src/scene/play_screen.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <input/input.h>
+#include <furi.h>
+
+
+void start_play_screen(void *data);
+
+void render_play_screen(void *data);
+
+void update_play_screen(void *data);
+
+void input_play_screen(void *data, InputKey key, InputType type);
+bool check_finish(void *data);

+ 74 - 0
src/scene/result_screen.c

@@ -0,0 +1,74 @@
+#include "result_screen.h"
+#include "../../game_state.h"
+#include "../util/helpers.h"
+#include <dolphin/dolphin.h>
+#include <notification/notification_messages.h>
+
+static int hours, minutes, seconds;
+static bool isStarted = false;
+static char timeString[24];
+static const NotificationSequence sequence_cheer = {
+    &message_note_c4,
+    &message_delay_100,
+    &message_note_e4,
+    &message_delay_100,
+    &message_note_g4,
+    &message_delay_100,
+    &message_note_a4,
+    &message_delay_100,
+    &message_sound_off,
+    NULL,
+};
+
+void start_result_screen(void *data) {
+    GameState *state = (GameState *) data;
+    dolphin_deed(DolphinDeedPluginGameWin);
+    size_t diff = (state->game_end - state->game_start) / furi_kernel_get_tick_frequency();
+    hours = (int) (diff / 3600);
+    minutes = (int) (diff % 3600) / 60;
+    seconds = (int) (diff % 60);
+    state->lateRender = true;
+    state->isDirty = true;
+    state->clearBuffer = false;
+    isStarted = false;
+    notification_message(state->notification_app, (const NotificationSequence *) &sequence_cheer);
+}
+
+void render_result_screen(void *data) {
+    GameState *state = (GameState *) data;
+
+    canvas_set_color(state->canvas, ColorWhite);
+    canvas_draw_box(state->canvas, 22, 14, 85, 30);
+    canvas_set_color(state->canvas, ColorBlack);
+    canvas_draw_frame(state->canvas, 21, 13, 87, 32);
+
+    canvas_set_font(state->canvas, FontPrimary);
+    canvas_draw_str_aligned(state->canvas, 64, 15, AlignCenter, AlignTop, "Congratulations!");
+    canvas_set_font(state->canvas, FontSecondary);
+    canvas_draw_str_aligned(state->canvas, 64, 26, AlignCenter, AlignTop, "Solve time:");
+
+    if(hours>0)
+        snprintf(timeString, sizeof(timeString), "%02d:%02d:%02d", hours, minutes, seconds);
+    else
+        snprintf(timeString, sizeof(timeString), "%02d:%02d", minutes, seconds);
+    canvas_set_font(state->canvas, FontSecondary);
+    canvas_draw_str_aligned(state->canvas, 64, 35, AlignCenter, AlignTop, timeString);
+}
+
+void update_result_screen(void *data) {
+    GameState *state = (GameState *) data;
+    state->clearBuffer = false;
+    state->lateRender = true;
+    if (!isStarted) {
+        state->isDirty = true;
+        isStarted = true;
+    }
+}
+
+
+void input_result_screen(void *data, InputKey key, InputType type) {
+    GameState *state = (GameState *) data;
+    if (key == InputKeyOk && type == InputTypePress) {
+        state->scene_switch = 1;
+    }
+}

+ 12 - 0
src/scene/result_screen.h

@@ -0,0 +1,12 @@
+#pragma once
+#include <furi.h>
+#include <input/input.h>
+
+void start_result_screen(void *data);
+
+void render_result_screen(void *data);
+
+void update_result_screen(void *data);
+
+
+void input_result_screen(void *data, InputKey key, InputType type);

+ 61 - 0
src/scene/scene_setup.h

@@ -0,0 +1,61 @@
+#pragma once
+
+#include <furi.h>
+#include "../util/list.h"
+#include "main_screen.h"
+#include "intro_animation.h"
+#include "play_screen.h"
+#include "solve_screen.h"
+#include "result_screen.h"
+#include "falling_card.h"
+
+typedef struct {
+    void (*start)(void *data);
+    void (*render)(void *data);
+
+    void (*update)(void *data);
+
+    void (*input)(void *data, InputKey key, InputType type);
+} GameLogic;
+
+GameLogic main_screen = (GameLogic) {
+    .start=start_main_screen,
+    .render=render_main_screen,
+    .update=update_main_screen,
+    .input= input_main_screen
+};
+
+GameLogic intro_screen = (GameLogic) {
+    .start=start_intro_screen,
+    .render=render_intro_screen,
+    .update=update_intro_screen,
+    .input=input_intro_screen
+};
+
+GameLogic play_screen = (GameLogic) {
+    .start=start_play_screen,
+    .render=render_play_screen,
+    .update=update_play_screen,
+    .input=input_play_screen
+};
+
+GameLogic solve_screen = (GameLogic) {
+    .start=start_solve_screen,
+    .render=render_solve_screen,
+    .update=update_solve_screen,
+    .input=input_solve_screen
+};
+
+GameLogic falling_screen = (GameLogic) {
+    .start=start_falling_screen,
+    .render=render_falling_screen,
+    .update=update_falling_screen,
+    .input=input_falling_screen
+};
+
+GameLogic result_screen = (GameLogic) {
+    .start=start_result_screen,
+    .render=render_result_screen,
+    .update=update_result_screen,
+    .input=input_result_screen
+};

+ 204 - 0
src/scene/solve_screen.c

@@ -0,0 +1,204 @@
+#include "solve_screen.h"
+#include "../../game_state.h"
+#include "play_screen.h"
+
+uint8_t target_foundation;
+static double accumulated_delta = 0;
+static Vector animation_target = VECTOR_ZERO;
+static Vector animation_from = VECTOR_ZERO;
+
+void start_solve_screen(void *data) {
+    UNUSED(data);
+    accumulated_delta = 0;
+    target_foundation = 0;
+}
+
+void render_solve_screen(void *data) {
+    GameState *state = (GameState *) data;
+
+    render_play_screen(state);
+
+    if (state->animated_card.card) {
+        card_render_front(
+            state->animated_card.card,
+            (uint8_t) state->animated_card.position.x,
+            (uint8_t) state->animated_card.position.y,
+            false,
+            state->buffer,
+            22
+        );
+    }
+}
+
+bool end_solve_screen(GameState *state) {
+    for (uint8_t i = 0; i < 4; i++) {
+        Card *c = list_peek_back(state->foundation[i]);
+        if (!c || c->value != KING) return false;
+    }
+
+    return true;
+}
+
+static int8_t missing_suit(GameState *state) {
+    bool found[4] = {false, false, false, false};
+    for (uint8_t i = 0; i < 4; i++) {
+        if (state->foundation[i]->count) {
+            Card *c = list_peek_back(state->foundation[i]);
+            found[c->suit] = true;
+        }
+    }
+    for (int8_t i = 0; i < 4; i++) {
+        if (!found[i]) return i;
+    }
+
+    return -1;
+}
+
+static void quick_solve(GameState *state) {
+    //remove all cards that are not placed to the foundation yet
+    list_free_data(state->deck);
+    list_free_data(state->waste);
+    for (uint8_t i = 0; i < 7; i++) {
+        list_free_data(state->tableau[i]);
+    }
+    list_free_data(state->hand);
+
+    state->animated_card.card = NULL;
+
+    //fill up the foundations with cards
+    for (uint8_t i = 0; i < 4; i++) {
+        //add ace to the start
+        if (state->foundation[i]->count == 0) {
+            Card *c = malloc(sizeof(Card));
+            c->value = ACE;
+            c->suit = missing_suit(state);
+            c->exposed = true;
+            list_push_back(c, state->foundation[i]);
+        }
+
+        //fill up the rest
+        for (uint8_t v = state->foundation[i]->count; v < 13; v++) {
+            Card *c = malloc(sizeof(Card));
+            c->value = v - 1;
+            c->suit = ((Card *) list_peek_back(state->foundation[i]))->suit;
+            c->exposed = true;
+            list_push_back(c, state->foundation[i]);
+        }
+    }
+
+    end_solve_screen(state);
+}
+
+static bool animation_done(GameState *state) {
+    if (!state->animated_card.card) return true;
+
+    accumulated_delta += state->delta_time * 4;
+    vector_lerp(&(animation_from), &animation_target, accumulated_delta,
+                &(state->animated_card.position));
+
+    double dist = vector_distance(&(state->animated_card.position), &animation_target);
+    return dist < 1;
+}
+
+static Card *find_and_remove(List *list, uint8_t suit, uint8_t value) {
+    //go reversed order because tableau will always have at the end
+    ListItem *current = list->tail;
+    while (current) {
+        if (((Card *) current->data)->value == value && ((Card *) current->data)->suit == suit) {
+            Card *c = current->data;
+            list_remove_item(c, list);
+            return c;
+        }
+        current = current->prev;
+    }
+
+    return NULL;
+}
+
+static uint8_t positions[9] = {
+    2, 20,
+    2, 20, 38, 56, 74, 92, 110
+};
+
+static void find_next_card(GameState *state) {
+    List *order[9] = {state->deck, state->waste, state->tableau[0], state->tableau[1], state->tableau[2],
+                      state->tableau[3], state->tableau[4], state->tableau[5], state->tableau[6]};
+
+    int8_t missing = missing_suit(state);
+    if (missing >= 0) {
+        //find the missing ACE
+        for (uint8_t id = 0; id < 9; id++) {
+            Card *ace = find_and_remove(order[id], missing, ACE);
+            if (ace) {
+                ace->exposed = true;
+                for (uint8_t i = 0; i < 4; i++) {
+                    if (state->foundation[i]->count == 0) {
+                        state->animated_card.card = ace;
+                        target_foundation = i;
+                        animation_from.x = positions[id];
+                        animation_from.y =
+                            id < 2 ? 1 : ((float) MIN((uint8_t) state->tableau[state->selected[0]]->count, 4) * 4 + 15);
+                        state->animated_card.position = animation_from;
+
+                        animation_target.x = (float) (56 + target_foundation * 18);
+                        animation_target.y = 1;
+                    }
+                }
+            }
+        }
+    } else {
+        uint8_t lowestValue = 14, lowestSuit = 0;
+        //get the lowest value
+        for (uint8_t i = 0; i < 4; i++) {
+            Card *c = list_peek_back(state->foundation[i]);
+            if (lowestValue > ((c->value + 1) % 13)) {
+                lowestValue = ((c->value + 1) % 13);
+                target_foundation = i;
+                lowestSuit = c->suit;
+            }
+        }
+        //store that card to animate
+        for (uint8_t id = 0; id < 9; id++) {
+            Card *c = find_and_remove(order[id], lowestSuit, lowestValue);
+            if (c) {
+                state->animated_card.card = c;
+
+                animation_from.x = positions[id];
+                animation_from.y =
+                    id < 2 ? 1 : ((float) MIN((uint8_t) state->tableau[state->selected[0]]->count, 4) * 4 + 15);
+                state->animated_card.position = animation_from;
+
+                animation_target.x = (float) (56 + target_foundation * 18);
+                animation_target.y = 1;
+                break;
+            }
+        }
+    }
+}
+
+
+void update_solve_screen(void *data) {
+    GameState *state = (GameState *) data;
+    state->isDirty = true;
+
+    if (!end_solve_screen(state)) {
+        if (animation_done(state)) {
+            if (state->animated_card.card) {
+                state->animated_card.card->exposed=true;
+                list_push_back(state->animated_card.card, state->foundation[target_foundation]);
+                state->animated_card.card = NULL;
+                accumulated_delta=0;
+            }
+            find_next_card(state);
+        }
+    } else {
+        state->scene_switch = 1;
+    }
+}
+
+
+void input_solve_screen(void *data, InputKey key, InputType type) {
+    if (key == InputKeyOk && type == InputTypePress) {
+        quick_solve(data);
+    }
+}

+ 8 - 0
src/scene/solve_screen.h

@@ -0,0 +1,8 @@
+#pragma once
+#include <furi.h>
+#include <input/input.h>
+
+void start_solve_screen(void *data);
+void render_solve_screen(void *data);
+void update_solve_screen(void *data);
+void input_solve_screen(void *data, InputKey key, InputType type);

+ 258 - 0
src/util/buffer.c

@@ -0,0 +1,258 @@
+#include "buffer.h"
+#include "helpers.h"
+#include <memory.h>
+#include <furi.h>
+
+static RenderSettings default_render = DEFAULT_RENDER;
+
+uint16_t pixel(uint8_t x, uint8_t y, uint8_t w) {
+    return (y * w + x) / 8;
+}
+
+unsigned long buffer_size(uint8_t width, uint8_t height) {
+    return sizeof(uint8_t) * (int) ceil(width / 8.0) * ceil(height);
+}
+
+uint8_t *malloc_buffer(uint8_t width, uint8_t height) {
+    return (uint8_t *) malloc(buffer_size(width, height));
+}
+
+Buffer *buffer_create(uint8_t width, uint8_t height, bool double_buffered) {
+    Buffer *b = (Buffer *) malloc(sizeof(Buffer));
+    b->double_buffered = double_buffered;
+    b->width = width;
+    b->height = height;
+    b->data = malloc_buffer(width, height);
+    if (double_buffered)
+        b->back_buffer = malloc_buffer(width, height);
+    else
+        b->back_buffer = NULL;
+    return b;
+}
+
+void buffer_release(Buffer *buffer) {
+    free(buffer->data);
+    if (buffer->double_buffered)
+        free(buffer->back_buffer);
+    free(buffer);
+}
+
+bool buffer_test_coordinate(Buffer *const buffer, int x, int y) {
+    return (x >= 0 && x < buffer->width && y >= 0 && y < buffer->height);
+}
+
+bool buffer_get_pixel(Buffer *const buffer, int x, int y) {
+    return buffer->data[pixel(x, y, buffer->width)] & (1 << (x & 7));
+}
+
+void buffer_set_pixel(Buffer *buffer, int16_t x, int16_t y, enum PixelColor draw_mode) {
+    uint8_t bit = 1 << (x & 7);
+    uint8_t *p = &(buffer->data[pixel(x, y, buffer->width)]);
+
+    switch (draw_mode) {
+        case Black:
+            *p |= bit;
+            break;
+        case White:
+            *p &= ~bit;
+            break;
+        case Flip:
+            *p ^= bit;
+            break;
+    }
+}
+
+Buffer *buffer_copy(Buffer *buffer) {
+    Buffer *new_buffer = (Buffer *) malloc(sizeof(Buffer));
+    new_buffer->double_buffered = buffer->double_buffered;
+    new_buffer->width = buffer->width;
+    new_buffer->height = buffer->height;
+    new_buffer->data = malloc_buffer(buffer->width, buffer->height);
+    memcpy(new_buffer->data, buffer->data, buffer_size(buffer->width, buffer->height));
+    if (buffer->double_buffered) {
+        new_buffer->back_buffer = malloc_buffer(buffer->width, buffer->height);
+        memcpy(new_buffer->back_buffer, buffer->back_buffer, buffer_size(buffer->width, buffer->height));
+    } else {
+        new_buffer->back_buffer = NULL;
+    }
+    return new_buffer;
+}
+
+void buffer_swap_back(Buffer *buffer) {
+    check_pointer(buffer);
+    check_pointer(buffer->data);
+    if (buffer->double_buffered) {
+        check_pointer(buffer->back_buffer);
+        uint8_t *temp = buffer->data;
+        buffer->data = buffer->back_buffer;
+        buffer->back_buffer = temp;
+    }
+}
+
+void buffer_clear(Buffer *buffer){
+    check_pointer(buffer);
+    check_pointer(buffer->data);
+    buffer->data=(uint8_t *)memset(buffer->data,0,buffer_size(buffer->width, buffer->height));
+}
+
+void buffer_swap_with(Buffer *buffer_a, Buffer *buffer_b) {
+    check_pointer(buffer_a);
+    check_pointer(buffer_b);
+    uint8_t *temp = buffer_a->data;
+    buffer_a->data = buffer_b->data;
+    buffer_b->data = temp;
+}
+
+void
+buffer_draw_internal(Buffer *target, Buffer *const sprite, bool is_black, enum PixelColor color, Vector *const position,
+                     uint8_t x_cap, uint8_t y_cap, float rotation, Vector anchor) {
+
+    Vector center = {
+        .x=anchor.x * sprite->width,
+        .y=anchor.y * sprite->height,
+    };
+    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++) {
+            Vector curr = {x, y};
+            vector_sub(&curr, &center, &transform);
+            vector_rotate(&transform, rotation, &transform);
+            vector_add(&transform, position, &transform);
+
+            finalX = (int16_t) roundf(transform.x);
+            finalY = (int16_t) roundf(transform.y);
+            if (buffer_test_coordinate(target, finalX, finalY)) {
+                isOn = buffer_get_pixel(sprite, x, y) == is_black;
+                if (isOn)
+                    buffer_set_pixel(target, finalX, finalY, color);
+            }
+        }
+    }
+
+}
+
+void buffer_draw_all(Buffer *target, Buffer *const sprite, Vector *position, float rotation) {
+    check_pointer(target);
+    check_pointer(sprite);
+    check_pointer(position);
+    buffer_draw(target, sprite, position, sprite->width, sprite->height, rotation, &default_render);
+}
+
+void
+buffer_draw(Buffer *target, Buffer *const sprite, Vector *position, uint8_t x_cap, uint8_t y_cap, float rotation, RenderSettings *settings) {
+    check_pointer(target);
+    check_pointer(sprite);
+    check_pointer(position);
+    switch (settings->drawMode) {
+        default:
+        case BlackOnly:
+            buffer_draw_internal(target, sprite, true, Black, position, x_cap, y_cap, rotation, settings->anchor);
+            break;
+        case WhiteOnly:
+            buffer_draw_internal(target, sprite, false, White, position, x_cap, y_cap, rotation, settings->anchor);
+            break;
+        case WhiteAsBlack:
+            buffer_draw_internal(target, sprite, false, Black, position, x_cap, y_cap, rotation, settings->anchor);
+            break;
+        case BlackAsWhite:
+            buffer_draw_internal(target, sprite, true, White, position, x_cap, y_cap, rotation, settings->anchor);
+            break;
+        case WhiteAsInverted:
+            buffer_draw_internal(target, sprite, false, Flip, position, x_cap, y_cap, rotation, settings->anchor);
+            break;
+        case BlackAsInverted:
+            buffer_draw_internal(target, sprite, true, Flip, position, x_cap, y_cap, rotation, settings->anchor);
+            break;
+    }
+}
+
+void buffer_render(Buffer *buffer, Canvas *const canvas) {
+    check_pointer(buffer);
+    canvas_draw_xbm(canvas, 0, 0, buffer->width, buffer->height, buffer->data);
+}
+
+void buffer_draw_line(Buffer *buffer, int x0, int y0, int x1, int y1, enum PixelColor draw_mode) {
+    check_pointer(buffer);
+    int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
+    int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
+    int err = (dx > dy ? dx : -dy) / 2;
+
+    while (true) {
+        if (buffer_test_coordinate(buffer, x0, y0)) {
+            buffer_set_pixel(buffer, x0, y0, draw_mode);
+        }
+        if (x0 == x1 && y0 == y1) break;
+        int e2 = err;
+        if (e2 > -dx) {
+            err -= dy;
+            x0 += sx;
+        }
+        if (e2 < dy) {
+            err += dx;
+            y0 += sy;
+        }
+    }
+}
+
+void buffer_draw_rbox(Buffer *buffer, int16_t x0, int16_t y0, int16_t x1, int16_t y1, enum PixelColor draw_mode) {
+    for (int16_t x = x0; x < x1; x++) {
+        for (int16_t y = y0; y < y1; y++) {
+            if (((x == x0 || x == x1 - 1) && (y == y0 || y == y1 - 1)) ||
+                !buffer_test_coordinate(buffer, x, y))
+                continue;
+            buffer_set_pixel(buffer, x, y, draw_mode);
+        }
+    }
+}
+
+void buffer_draw_rbox_frame(Buffer *buffer, int16_t x0, int16_t y0, int16_t x1, int16_t y1, enum PixelColor draw_mode) {
+    buffer_draw_line(buffer, x0 + 1, y0, x1 - 1, y0, draw_mode);
+    buffer_draw_line(buffer, x0 + 1, y1, x1 - 1, y1, draw_mode);
+
+    buffer_draw_line(buffer, x0, y0 + 1, x0, y1 - 1, draw_mode);
+    buffer_draw_line(buffer, x1, y0 + 1, x1, y1 - 1, draw_mode);
+}
+
+void buffer_draw_box(Buffer *buffer, int16_t x0, int16_t y0, int16_t x1, int16_t y1, enum PixelColor draw_mode) {
+    for (int16_t x = x0 + 1; x < x1 - 1; x++) {
+        for (int16_t y = y0 + 1; y < y1 - 1; y++) {
+            if (!buffer_test_coordinate(buffer, x, y))
+                continue;
+            buffer_set_pixel(buffer, x, y, draw_mode);
+        }
+    }
+}
+
+void buffer_set_pixel_with_check(Buffer *buffer, int16_t x, int16_t y, enum PixelColor draw_mode) {
+    if (buffer_test_coordinate(buffer, x, y))
+        buffer_set_pixel(buffer, x, y, draw_mode);
+}
+
+void buffer_draw_circle(Buffer *buffer, int x, int y, int r, enum PixelColor color) {
+    int16_t a = r;
+    int16_t b = 0;
+    int16_t decision = 1 - a;
+
+    while (b <= a) {
+        buffer_set_pixel_with_check(buffer, a + x, b + y, color);
+        buffer_set_pixel_with_check(buffer, b + x, a + y, color);
+        buffer_set_pixel_with_check(buffer, -a + x, b + y, color);
+        buffer_set_pixel_with_check(buffer, -b + x, a + y, color);
+        buffer_set_pixel_with_check(buffer, -a + x, -b + y, color);
+        buffer_set_pixel_with_check(buffer, -b + x, -a + y, color);
+        buffer_set_pixel_with_check(buffer, a + x, -b + y, color);
+        buffer_set_pixel_with_check(buffer, b + x, -a + y, color);
+
+        b++;
+        if (decision <= 0) {
+            decision += 2 * b + 1;
+        } else {
+            a--;
+            decision += 2 * (b - a) + 1;
+        }
+    }
+}

+ 74 - 0
src/util/buffer.h

@@ -0,0 +1,74 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/canvas.h>
+#include "vector.h"
+
+enum DrawMode {
+    WhiteOnly,
+    BlackOnly,
+    WhiteAsBlack,
+    BlackAsWhite,
+    WhiteAsInverted,
+    BlackAsInverted,
+};
+
+typedef struct {
+    uint8_t *data;
+    uint8_t *back_buffer;
+    uint8_t width;
+    uint8_t height;
+    bool double_buffered;
+} Buffer;
+
+enum PixelColor {
+    Black, //or
+    White, //
+    Flip   //not
+};
+
+typedef struct {
+    const Vector anchor;
+    enum DrawMode drawMode;
+} RenderSettings;
+
+#define SCREEN_WIDTH 128
+#define SCREEN_HEIGHT 64
+
+#define DEFAULT_RENDER (RenderSettings){.anchor={.x=0.5f, .y=0.5f}, .drawMode=BlackOnly}
+
+Buffer *buffer_create(uint8_t width, uint8_t height, bool double_buffered);
+
+void buffer_release(Buffer *buffer);
+
+bool buffer_test_coordinate(Buffer *const buffer, int x, int y);
+
+bool buffer_get_pixel(Buffer *const buffer, int x, int y);
+
+void buffer_set_pixel(Buffer *buffer, int16_t x, int16_t y, enum PixelColor draw_mode);
+
+Buffer *buffer_copy(Buffer *buffer);
+
+void buffer_swap_back(Buffer *buffer);
+
+void buffer_swap_with(Buffer *buffer_a, Buffer *buffer_b);
+
+void buffer_draw_all(Buffer *target, Buffer *const sprite, Vector *position, float rotation);
+
+void buffer_draw(Buffer *target, Buffer *const sprite, Vector *position, uint8_t x_cap, uint8_t y_cap, float rotation, RenderSettings *settings);
+
+//void buffer_draw_internal(Buffer *target, Buffer* sprite, bool is_black, enum PixelColor color, Vector *const position, uint8_t x_cap, uint8_t y_cap, float rotation);
+void buffer_render(Buffer *buffer, Canvas *const canvas);
+void buffer_clear(Buffer *buffer);
+
+void buffer_draw_line(Buffer *buffer, int x0, int y0, int x1, int y1, enum PixelColor draw_mode);
+
+void buffer_draw_rbox(Buffer *buffer, int16_t x0, int16_t y0, int16_t x1, int16_t y1, enum PixelColor draw_mode);
+
+void buffer_draw_rbox_frame(Buffer *buffer, int16_t x0, int16_t y0, int16_t x1, int16_t y1, enum PixelColor draw_mode);
+
+void buffer_draw_box(Buffer *buffer, int16_t x0, int16_t y0, int16_t x1, int16_t y1, enum PixelColor draw_mode);
+
+void buffer_draw_circle(Buffer *buffer, int x, int y, int r, enum PixelColor draw_mode);
+
+

+ 230 - 0
src/util/card.c

@@ -0,0 +1,230 @@
+#include "card.h"
+#include "../../assets.h"
+#include "helpers.h"
+
+static RenderSettings default_render = DEFAULT_RENDER;
+
+static Buffer *letters[] = {
+    (Buffer *) &sprite_2,
+    (Buffer *) &sprite_3,
+    (Buffer *) &sprite_4,
+    (Buffer *) &sprite_5,
+    (Buffer *) &sprite_6,
+    (Buffer *) &sprite_7,
+    (Buffer *) &sprite_8,
+    (Buffer *) &sprite_9,
+    (Buffer *) &sprite_10,
+    (Buffer *) &sprite_J,
+    (Buffer *) &sprite_Q,
+    (Buffer *) &sprite_K,
+    (Buffer *) &sprite_A,
+};
+
+static Buffer *suits[] = {
+    (Buffer *) &sprite_hearths,
+    (Buffer *) &sprite_spades,
+    (Buffer *) &sprite_diamonds,
+    (Buffer *) &sprite_clubs
+};
+
+static Buffer *backSide = (Buffer *) &sprite_pattern_big;
+
+void card_render_front(Card *c, int16_t x, int16_t y, bool selected, Buffer *buffer, uint8_t size_limit) {
+    uint8_t height = y + fmin(size_limit, 22);
+
+    buffer_draw_rbox(buffer, x, y, x + 16, height, White);
+    buffer_draw_rbox_frame(buffer, x, y, x + 16, height, Black);
+
+    Vector p = (Vector) {(float) x + 6, (float) y + 5};
+    buffer_draw_all(buffer, letters[c->value], &p, 0);
+
+    p = (Vector) {(float) x + 12, (float) y + 5};
+    buffer_draw_all(buffer, suits[c->suit], &p, 0);
+
+
+    if (size_limit > 8) {
+        p = (Vector) {(float) x + 10, (float) y + 16};
+        buffer_draw_all(buffer, letters[c->value], &p, M_PI);
+        p = (Vector) {(float) x + 4, (float) y + 16};
+        buffer_draw_all(buffer, suits[c->suit], &p, M_PI);
+    }
+    if (selected) {
+        buffer_draw_box(buffer, x , y , x + 17, height+1, Flip);
+    }
+}
+
+void card_render_slot(int16_t x, int16_t y, bool selected, Buffer *buffer) {
+
+    buffer_draw_rbox(buffer, x, y, x + 17, y + 23, Black);
+    buffer_draw_rbox_frame(buffer, x + 2, y + 2, x + 14, y + 20, White);
+    if (selected)
+        buffer_draw_rbox(buffer, x + 1, y + 1, x + 16, y + 22, Flip);
+}
+
+void card_render_back(int16_t x, int16_t y, bool selected, Buffer *buffer, uint8_t size_limit) {
+    uint8_t height = y + fmin(size_limit, 22);
+
+    buffer_draw_rbox(buffer, x + 1, y + 1, x + 16, height, White);
+    buffer_draw_rbox_frame(buffer, x, y, x + 16, height, Black);
+    Vector pos = (Vector) {(float) x + 9, (float) y + 11};
+    check_pointer(buffer);
+    check_pointer(backSide);
+    check_pointer(&pos);
+    check_pointer(&default_render);
+    buffer_draw(buffer, backSide, &pos, 15, (int) fmin(size_limit, 22), 0, &default_render);
+    if (selected) {
+        buffer_draw_box(buffer, x , y , x + 17, height+1, Flip);
+    }
+}
+
+void card_try_render(Card *c, int16_t x, int16_t y, bool selected, Buffer *buffer, uint8_t size_limit) {
+    if (c) {
+        if (c->exposed)
+            card_render_front(c, x, y, selected, buffer, size_limit);
+        else
+            card_render_back(x, y, selected, buffer, size_limit);
+    } else {
+        card_render_slot(x, y, selected, buffer);
+    }
+}
+
+bool card_test_foundation(Card *data, Card *target) {
+    if (!target || (target->value == -1 && target->suit == data->suit)) {
+        return data->value == ACE;
+    }
+    return target->suit == data->suit && ((target->value + 1) % 13 == data->value % 13);
+}
+
+bool card_test_column(Card *data, Card *target) {
+    if (!target) return data->value == KING;
+    return target->suit % 2 == (data->suit + 1) % 2 && (data->value + 1) == target->value;
+}
+
+List *deck_generate(uint8_t deck_count) {
+    List *deck = list_make();
+    int cards_count = 52 * deck_count;
+    uint8_t cards[cards_count];
+    for (int i = 0; i < cards_count; i++) cards[i] = i % 52;
+    srand(curr_time());
+
+    //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++) {
+        Card *c = malloc(sizeof(Card));
+        c->value = cards[i] % 13;
+        c->suit = cards[i] / 13;
+        c->exposed = false;
+        list_push_back(c, deck);
+    }
+
+    return deck;
+}
+
+void deck_free(List *deck) {
+    list_free(deck);
+}
+
+void deck_render_vertical(List *deck, uint8_t x, uint8_t y, int8_t selected, Buffer *buffer) {
+
+    check_pointer(deck);
+    check_pointer(buffer);
+    uint8_t loop_end = deck->count;
+    int8_t selection = loop_end - selected;
+    uint8_t loop_start = MAX(loop_end - 4, 0);
+    uint8_t position = 0;
+    int8_t first_non_flipped;
+    Card *first_non_flipped_card = deck_first_non_flipped(deck, &first_non_flipped);
+
+    bool had_top = false;
+    bool showDark = selection >= 0;
+
+    if (first_non_flipped <= loop_start && selection != first_non_flipped && first_non_flipped_card) {
+        // Draw a card back if it is not the first card
+        if (first_non_flipped > 0) {
+            card_render_back(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
+        card_try_render(first_non_flipped_card, x, y + position, false, buffer, deck->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_render_back(x, y + position, false, buffer, 5);
+            position += 4;
+            loop_start++;
+        }
+
+        Card *selected_card = (Card *) list_peek_index(deck, selection);
+        check_pointer(selected_card);
+        // Draw the front side of the selected card
+        card_try_render(selected_card, x, y + position, showDark, buffer, 9);
+        position += 8;
+        loop_start++; // Increment loop start index
+    }
+
+    int height = 5;
+    ListItem *curr = list_get_index(deck, loop_start);
+    for (uint8_t i = loop_start; i < loop_end; i++) {
+        check_pointer(curr);
+        if (!curr) break;
+
+        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 *c = (Card *) curr->data;
+            check_pointer(c);
+            card_try_render(c, x, y + position, i == selection && showDark, buffer, height);
+            if (i == selection || i == first_non_flipped)position += 4;
+            position += 4;
+        }
+        curr = curr->next;
+    }
+}
+
+void deck_render(List *deck, DeckType type, int16_t x, int16_t y, int8_t selected, bool draw_empty, Buffer *buffer) {
+    switch (type) {
+        case Normal:
+            card_try_render(list_peek_back(deck), x, y, selected == 1, buffer, 22);
+            break;
+        case Vertical:
+            if (deck && deck->count > 0)
+                deck_render_vertical(deck, x, y, selected, buffer);
+            else if (draw_empty)
+                card_render_slot(x, y, selected == 1, buffer);
+            break;
+        case Pile:
+            break;
+    }
+}
+
+Card *deck_first_non_flipped(List *deck, int8_t *index) {
+    ListItem *curr = deck->head;
+    for (int8_t i = 0; i < (int8_t) deck->count; i++) {
+        if (!curr->data) break;
+        Card *card = (Card *) curr->data;
+        if (card->exposed) {
+            (*index) = i;
+            return card;
+        }
+        curr = curr->next;
+    }
+    (*index) = -1;
+    return NULL;
+}

+ 55 - 0
src/util/card.h

@@ -0,0 +1,55 @@
+#pragma once
+
+#include <furi.h>
+#include "buffer.h"
+#include "list.h"
+
+typedef enum {
+    NONE = -1,
+    TWO = 0,        //1
+    THREE = 1,      //2
+    FOUR = 2,       //3
+    FIVE = 3,       //4
+    SIX = 4,        //5
+    SEVEN = 5,      //6
+    EIGHT = 6,      //7
+    NINE = 7,       //8
+    TEN = 8,        //9
+    JACK = 9,       //10
+    QUEEN = 10,     //11
+    KING = 11,      //12
+    ACE = 12,       //13
+} CardValue;
+
+typedef struct {
+    uint8_t suit;
+    CardValue value;
+    bool exposed;
+} Card;
+
+typedef enum {
+    Normal,
+    Vertical,
+    Pile
+} DeckType;
+
+
+void card_render_front(Card *c, int16_t x, int16_t y, bool selected, Buffer *buffer, uint8_t size_limit);
+
+void card_render_slot(int16_t x, int16_t y, bool selected, Buffer *buffer);
+
+void card_render_back(int16_t x, int16_t y, bool selected, Buffer *buffer, uint8_t size_limit);
+
+void card_try_render(Card *c, int16_t x, int16_t y, bool selected, Buffer *buffer, uint8_t size_limit);
+
+bool card_test_foundation(Card *data, Card *target);
+
+bool card_test_column(Card *data, Card *target);
+
+List *deck_generate(uint8_t deck_count);
+
+void deck_free(List *deck);
+
+Card* deck_first_non_flipped(List *deck, int8_t *index);
+
+void deck_render(List *deck, DeckType type, int16_t x, int16_t y, int8_t selected, bool draw_empty, Buffer *buffer);

+ 6 - 0
src/util/game_loop.h

@@ -0,0 +1,6 @@
+#pragma once
+#include <furi.h>
+
+struct GameState{
+    bool (*update)(void *inputState);
+};

+ 45 - 0
src/util/helpers.c

@@ -0,0 +1,45 @@
+#include "helpers.h"
+#include <furi.h>
+#include <math.h>
+
+float inverse_tanh(double x) {
+    return 0.5f * (float) log((1 + x) / (1 - x));
+}
+
+float lerp_number(float a, float b, float t) {
+    if (t >= 1) return b;
+    if (t <= 0) return a;
+    return (1 - t) * a + t * b;
+}
+
+void _log_location(const char *p, const char *file, int line, const char *func) {
+    FURI_LOG_W("App", "[TRACE][%s] %s:%s():%i", p, get_basename((char *) file), func, line);
+}
+
+bool _test_ptr(void *p) {
+    return p != NULL;
+}
+
+bool _check_ptr(void *p, const char *file, int line, const char *func) {
+    UNUSED(file);
+    UNUSED(line);
+    UNUSED(func);
+    if (p == NULL) {
+        FURI_LOG_W("App", "[NULLPTR] %s:%s():%i", get_basename((char *) file), func, line);
+    }
+
+    return _test_ptr(p);
+}
+
+char *get_basename(const char *path) {
+    const char *base = path;
+    while (*path) {
+        if (*path++ == '/') {
+            base = path;
+        }
+    }
+    return (char *) base;
+}
+
+size_t curr_time() { return DWT->CYCCNT; }
+

+ 39 - 0
src/util/helpers.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#include <furi.h>
+//#define DEBUG_BUILD
+
+#define M_PIX2        6.28318530717958647692    /* 2 pi */
+#define l_abs(x) ((x) < 0 ? -(x) : (x))
+#define DEG_2_RAD  0.01745329251994329576f
+#define RAD_2_DEG  565.48667764616278292327f
+
+
+#ifdef DEBUG_BUILD
+#define check_pointer(X) _check_ptr( X, __FILE__, __LINE__, __FUNCTION__)
+#define log_location(X) _log_location( X, __FILE__, __LINE__, __FUNCTION__)
+#else
+#define check_pointer(X) _test_ptr(X)
+#define trace(X) while(0)
+#endif
+
+#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
+
+bool _test_ptr(void *p);
+
+bool _check_ptr(void *p, const char *file, int line, const char *func);
+
+void _log_location(const char *p, const char *file, int line, const char *func);
+
+float inverse_tanh(double x);
+
+float lerp_number(float a, float b, float t);
+
+size_t curr_time();

+ 304 - 0
src/util/list.c

@@ -0,0 +1,304 @@
+#include "list.h"
+#include "helpers.h"
+
+List *list_make() {
+    List *list = malloc(sizeof(List));
+    if (list != NULL) {
+        list->head = NULL;
+        list->tail = NULL;
+        list->count = 0;
+    } else {
+        FURI_LOG_W("LIST", "Failed to create list");
+    }
+    return list;
+}
+
+void list_free(List *list) {
+    if (list == NULL) return;
+
+    ListItem *start = list->head;
+    while (start) {
+        ListItem *next = start->next;
+        free(start->data);
+        free(start);
+        start = next;
+    }
+    free(list);
+}
+
+void list_free_data(List *list) {
+    if (list == NULL) return;
+
+    ListItem *start = list->head;
+    while (start) {
+        ListItem *next = start->next;
+        free(start->data);
+        free(start);
+        start = next;
+    }
+
+    list->head = NULL;
+    list->tail = NULL;
+    list->count = 0;
+}
+
+void list_clear(List *list) {
+    if (list == NULL) return;
+    ListItem *start = list->head;
+    FURI_LOG_W("LIST", "list_clear");
+    while (start) {
+        ListItem *next = start->next;
+        free(start);
+        start = next;
+    }
+    list->head = NULL;
+    list->tail = NULL;
+    list->count = 0;
+}
+
+void list_push_back(void *data, List *list) {
+    if (list == NULL) {
+        FURI_LOG_W("LIST", "List not initialized, cannot push data");
+        return;
+    }
+    ListItem *newItem = malloc(sizeof(ListItem));
+    if (newItem != NULL) {
+        newItem->data = data;
+        newItem->next = NULL;
+        newItem->prev = list->tail;
+
+        if (list->tail == NULL) {
+            list->head = newItem;
+            list->tail = newItem;
+        } else {
+            list->tail->next = newItem;
+            list->tail = newItem;
+        }
+        list->count++;
+    }
+}
+
+void list_push_front(void *data, List *list) {
+    if (list == NULL) {
+        FURI_LOG_W("LIST", "List not initialized, cannot push data");
+        return;
+    }
+    ListItem *newItem = malloc(sizeof(ListItem));
+    if (newItem != NULL) {
+        newItem->data = data;
+        newItem->next = list->head;
+        if (list->head == NULL) {
+            list->head = newItem;
+            list->tail = newItem;
+        } else {
+            list->head->prev = newItem;
+            list->head = newItem;
+        }
+        list->count++;
+    }
+}
+
+void *list_pop_back(List *list) {
+    if (!check_pointer(list)) {
+        FURI_LOG_W("LIST", "List not initialized, cannot pop data");
+        return NULL;
+    }
+
+    if (!check_pointer(list->tail)) {
+        FURI_LOG_W("LIST", "List empty, cannot pop");
+        return NULL;
+    }
+    void *data = list->tail->data;
+    check_pointer(data);
+    ListItem *prev = list->tail->prev;
+    check_pointer(prev);
+    if (prev) {
+        prev->next = NULL;
+    } else {
+        list->head = NULL;
+    }
+    free(list->tail);
+    list->tail = prev;
+    list->count--;
+    return data;
+}
+
+void *list_pop_front(List *list) {
+    if (!check_pointer(list)) {
+        FURI_LOG_W("LIST", "List not initialized, cannot pop data");
+        return NULL;
+    }
+    if (!check_pointer(list->head)) {
+        FURI_LOG_W("LIST", "List empty, cannot pop");
+        return NULL;
+    }
+    void *data = list->head->data;
+    check_pointer(data);
+    ListItem *next = list->head->next;
+    if (next) {
+        next->prev = NULL;
+    } else {
+        list->tail = NULL;
+    }
+    free(list->head);
+    list->head = next;
+    list->count--;
+    return data;
+}
+
+void *list_pop_at(size_t index, List *list) {
+    if (!check_pointer(list)) {
+        FURI_LOG_W("LIST", "List not initialized, cannot pop data");
+        return NULL;
+    }
+    if (index >= list->count) {
+        FURI_LOG_W("LIST", "Out of range, cannot pop");
+        return NULL;
+    }
+    if (index == 0) {
+        return list_pop_front(list);
+    }
+    if (index == list->count - 1) {
+        return list_pop_back(list);
+    }
+    ListItem *current = list->head;
+    check_pointer(current);
+    for (size_t i = 0; i < index; i++) {
+        current = current->next;
+    }
+    check_pointer(current);
+    void *data = current->data;
+    check_pointer(data);
+    current->prev->next = current->next;
+    current->next->prev = current->prev;
+    free(current);
+    list->count--;
+    return data;
+}
+
+void list_remove_item(void *data, List *list) {
+    if (list == NULL) {
+        return;
+    }
+    ListItem *current = list->head;
+    while (current != NULL) {
+        if (current->data == data) {
+            if (current->prev != NULL) {
+                current->prev->next = current->next;
+            } else {
+                list->head = current->next;
+            }
+            if (current->next != NULL) {
+                current->next->prev = current->prev;
+            } else {
+                list->tail = current->prev;
+            }
+            free(current);
+            list->count--;
+            break;
+        }
+        current = current->next;
+    }
+}
+
+void list_remove_at(size_t index, List *list) {
+    if (list == NULL) {
+        return;
+    }
+    void *d = list_pop_at(index, list);
+    free(d);
+}
+
+
+List *list_splice(size_t index, size_t count, List *list) {
+    if (list == NULL) {
+        FURI_LOG_W("LIST", "List not initialized, cannot splice");
+        return NULL;
+    }
+    List *newList = list_make();
+    if (index >= list->count || count == 0) {
+        return newList;
+    }
+    if (index == 0 && count >= list->count) {
+        newList->head = list->head;
+        newList->tail = list->tail;
+        newList->count = list->count;
+        list->head = NULL;
+        list->tail = NULL;
+        list->count = 0;
+        return newList;
+    }
+
+    ListItem *start = list->head;
+    for (size_t i = 0; i < index; i++) {
+        start = start->next;
+    }
+
+    size_t c = count;
+    if (c > list->count) c = list->count - index;
+
+    ListItem *end = start;
+    for (size_t i = 1; i < c && end->next; i++) {
+        end = end->next;
+        if (end->next == NULL) c = i;
+    }
+
+    newList->head = start;
+    newList->tail = end;
+    newList->count = c;
+
+    if (end->next != NULL) {
+        end->next->prev = start->prev;
+    } else {
+        list->tail = start->prev;
+    }
+
+    if (start->prev != NULL) {
+        start->prev->next = end->next;
+    } else {
+        list->head = end->next;
+    }
+
+    start->prev = NULL;
+    end->next = NULL;
+    list->count -= c;
+    return newList;
+}
+
+void *list_peek_front(List *list) {
+    if (list == NULL || list->head == NULL) {
+        return NULL;
+    }
+    return list->head->data;
+}
+
+ListItem *list_get_index(List *list, size_t index){
+    check_pointer(list);
+    ListItem *curr = list->head;
+    check_pointer(curr);
+    if(index > list->count || !curr) return NULL;
+    for (size_t i = 0; i < index; i++) {
+        if (!curr) return NULL;
+        curr = curr->next;
+    }
+    return curr;
+}
+
+
+void *list_peek_index(List *list, size_t index) {
+    ListItem *curr = list_get_index(list,index);
+    check_pointer(curr);
+    if(curr) {
+        check_pointer(curr->data);
+        return curr->data;
+    }
+    return NULL;
+}
+
+void *list_peek_back(List *list) {
+    if (!list || !list->tail) {
+        return NULL;
+    }
+    check_pointer(list->tail->data);
+    return list->tail->data;
+}

+ 48 - 0
src/util/list.h

@@ -0,0 +1,48 @@
+#pragma once
+
+#include <furi.h>
+
+struct ListItem;
+
+typedef struct ListItem {
+    void *data;
+    struct ListItem *next;
+    struct ListItem *prev;
+} ListItem;
+
+typedef struct {
+    ListItem *head;
+    ListItem *tail;
+    size_t count;
+} List;
+
+List *list_make();
+//frees everything
+void list_free(List *list);
+//clears the list, but not data
+void list_clear(List *list);
+
+//clears the list, and data
+void list_free_data(List *list);
+
+void list_push_back(void *data, List *list);
+
+void list_push_front(void *data, List *list);
+
+void *list_pop_back(List *list);
+
+void *list_pop_front(List *list);
+
+void *list_pop_at(size_t index, List *list);
+
+void list_remove_item(void *data, List *list);
+
+void list_remove_at(size_t index, List *list);
+
+List *list_splice(size_t index, size_t count, List *list);
+
+void *list_peek_front(List *list);
+void *list_peek_index(List *list, size_t index);
+ListItem *list_get_index(List *list, size_t index);
+
+void *list_peek_back(List *list);

+ 119 - 0
src/util/vector.c

@@ -0,0 +1,119 @@
+#include "vector.h"
+
+#include <math.h>
+#include "helpers.h"
+
+
+Vector vector_copy(Vector *const other) {
+    return (Vector) {.x=other->x, .y=other->y};
+}
+
+//basic math
+
+void vector_add(Vector *const a, Vector *const b, Vector *target) {
+    target->x = a->x + b->x;
+    target->y = a->y + b->y;
+}
+
+void vector_sub(Vector *const a, Vector *const b, Vector *target) {
+    target->x = a->x - b->x;
+    target->y = a->y - b->y;
+}
+
+void vector_mul(Vector *const a, Vector *const b, Vector *target) {
+    target->x = a->x * b->x;
+    target->y = a->y * b->y;
+}
+
+void vector_div(Vector *const a, Vector *const b, Vector *target) {
+    target->x = a->x / b->x;
+    target->y = a->y / b->y;
+}
+
+// computations
+
+float vector_length_sqrt(Vector *const a) {
+    return a->x * a->x + a->y * a->y;
+}
+
+float vector_length(Vector *const v) {
+    return sqrtf(vector_length_sqrt(v));
+}
+
+float vector_distance(Vector *const a, Vector *const b) {
+    Vector v;
+    vector_sub(a, b, &v);
+    return vector_length(&v);
+}
+
+void vector_normalized(Vector *const v, Vector *target) {
+    float length = vector_length(v);
+    if (length == 0) {
+        target->x = 0;
+        target->y = 0;
+    } else {
+        target->x = v->x / length;
+        target->y = v->y / length;
+    }
+}
+
+void vector_inverse(Vector *const v, Vector *target) {
+    target->x = -v->x;
+    target->y = -v->y;
+}
+
+float vector_dot(Vector *const a, Vector *const b) {
+    return a->x * b->x + a->y * b->y;
+}
+
+
+void vector_rotate(Vector *const v, float deg, Vector *target) {
+    float tx = v->x;
+    float ty = v->y;
+    target->x = (float) (cosf(deg) * tx - sinf(deg) * ty);
+    target->y = (float) (sinf(deg) * tx + cosf(deg) * ty);
+}
+
+void vector_rounded(Vector *const source, Vector *target) {
+    target->x = roundf(source->x);
+    target->y = roundf(source->y);
+}
+
+float vector_cross(Vector *const a, Vector *const b) {
+    return a->x * b->y - a->y * b->x;
+}
+
+void vector_perpendicular(Vector *const v, Vector *target) {
+    target->x = -v->y;
+    target->y = v->x;
+}
+
+void vector_project(Vector *point, Vector *const line_a, Vector *const line_b, Vector *result, bool *success) {
+    Vector ab;
+    vector_sub(line_b, line_a, &ab);
+    Vector ap;
+    vector_sub(point, line_a, &ap);
+    float dot = vector_dot(&ap, &ab);
+    float length_squared = vector_length_sqrt(&ab);
+    float t = dot / length_squared;
+    if (t < 0 || t > 1) {
+        *success = false;
+    } else {
+        result->x = line_a->x + ab.x * t;
+        result->y = line_a->y + ab.y * t;
+        *success = true;
+    }
+}
+
+void vector_lerp(Vector *const start, Vector *const end, float time, Vector *result) {
+    result->x = lerp_number(start->x, end->x, time);
+    result->y = lerp_number(start->y, end->y, time);
+}
+
+void vector_quadratic(Vector *const start, Vector *const control, Vector *const end, float time, Vector *result) {
+    Vector a;
+    vector_lerp(start, control, time, &a);
+    Vector b;
+    vector_lerp(control, end, time, &b);
+    vector_lerp(&a, &b, time, result);
+}

+ 49 - 0
src/util/vector.h

@@ -0,0 +1,49 @@
+#pragma once
+#include <furi.h>
+
+typedef struct {
+    float x, y;
+} Vector;
+
+#define VECTOR_ZERO (Vector){.x=0,.y=0}
+
+Vector vector_copy(Vector *const other);
+
+//basic math
+
+void vector_add(Vector *const a, Vector *const b, Vector *target);
+
+void vector_sub(Vector *const a, Vector *const b, Vector *target);
+
+void vector_mul(Vector *const a, Vector *const b, Vector *target);
+
+void vector_div(Vector *const a, Vector *const b, Vector *target);
+
+// computations
+
+float vector_length_sqrt(Vector *const a);
+
+float vector_length(Vector *const v);
+
+float vector_distance(Vector *const a, Vector *const b);
+
+void vector_normalized(Vector *const v, Vector *target);
+
+void vector_inverse(Vector *const v, Vector *target);
+
+float vector_dot(Vector *const a, Vector *const b) ;
+
+
+void vector_rotate(Vector *const v, float deg, Vector *target) ;
+
+void vector_rounded(Vector *const source, Vector *target);
+
+float vector_cross(Vector *const a, Vector *const b) ;
+
+void vector_perpendicular(Vector *const v, Vector *target);
+
+void vector_project(Vector *point, Vector *const line_a, Vector *const line_b, Vector *result, bool *success);
+
+void vector_lerp(Vector *const start, Vector *const end, float time, Vector *result);
+
+void vector_quadratic(Vector *const start, Vector *const control, Vector *const end, float time, Vector *result);

+ 0 - 169
utils/Buffer.cpp

@@ -1,169 +0,0 @@
-#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) * (int)ceil(w / 8.0) * ceil(h));
-}
-
-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_I("BUFFER", "deleting buffer");
-    if (remove_buffer) {
-        delete data;
-    }
-}
-
-bool Buffer::test_pixel(uint8_t x, uint8_t y) {
-    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) (_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) (_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) (_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 << (x & 7);
-    uint8_t *p = &data[pixel(x,y)];
-
-    switch (draw_mode) {
-        case Black:
-            *p |= bit;
-            break;
-        case White:
-            *p &= ~bit;
-            break;
-        case Flip:
-            *p ^= bit;
-            break;
-    }
-}
-
-/**
- * @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;
-}

+ 0 - 52
utils/Buffer.h

@@ -1,52 +0,0 @@
-#pragma once
-#include <furi.h>
-#include <gui/icon.h>
-
-enum PixelColor {
-    Black, //or
-    White, //
-    Flip   //not
-};
-enum DrawMode {
-    WhiteOnly,
-    BlackOnly,
-    WhiteAsBlack,
-    BlackAsWhite,
-    WhiteAsInverted,
-    BlackAsInverted,
-};
-
-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;
-    Buffer(uint8_t *data, uint8_t w, uint8_t h);
-    Buffer(uint8_t width, uint8_t height);
-    virtual ~Buffer();
-
-    bool test_pixel(uint8_t x, uint8_t y);
-
-    void copy_into(uint8_t *other);
-
-    void clear();
-
-    bool test_coordinate(int x, int y) const;
-    void set_pixel(int16_t x, int16_t y, PixelColor draw_mode);
-    void set_pixel_with_check(int16_t x, int16_t y, PixelColor draw_mode);
-    void copy_from(uint8_t *other);
-    uint8_t width() { return _width; }
-    uint8_t height() { return _height; }
-    virtual void swap(uint8_t *&buffer);
-    uint16_t pixel(uint8_t x, uint8_t y);
-
-    uint8_t* decode(const Icon *icon);
-};

+ 0 - 94
utils/Card.cpp

@@ -1,94 +0,0 @@
-#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),
-};
-
-static Sprite suits[] = {
-    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(int8_t x, int8_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 + 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 + 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);
-        }
-    } else {
-        RenderBack(x, y, selected, buffer, size_limit);
-    }
-}
-
-void Card::RenderEmptyCard(uint8_t x, uint8_t y, RenderBuffer *buffer) {
-    buffer->draw_rbox(x, y, x + 17, y + 23, Black);
-    buffer->draw_rbox_frame(x + 2, y + 2, x + 14, y + 20, White);
-}
-
-void Card::RenderBack(uint8_t x, uint8_t y, bool selected, RenderBuffer *buffer, uint8_t size_limit) {
-    uint8_t height = y + fmin(size_limit, 22);
-
-    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 + 11}, 15, fmin(size_limit, 22), 0);
-    if (selected) {
-        buffer->draw_box(x + 1, y + 1, x + 16, height, Flip);
-    }
-}
-
-bool Card::CanPlaceFoundation(Card *where) {
-    if (!where || (where->value==-1 && where->suit==suit)) {
-        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);
-    }
-}

+ 0 - 45
utils/Card.h

@@ -1,45 +0,0 @@
-#pragma once
-
-#include <furi.h>
-
-class RenderBuffer;
-
-enum CardValue {
-    NONE = -1,
-    TWO = 0,        //1
-    THREE = 1,      //2
-    FOUR = 2,       //3
-    FIVE = 3,       //4
-    SIX = 4,        //5
-    SEVEN = 5,      //6
-    EIGHT = 6,      //7
-    NINE = 7,       //8
-    TEN = 8,        //9
-    JACK = 9,       //10
-    QUEEN = 10,     //11
-    KING = 11,      //12
-    ACE = 12,       //13
-};
-
-struct Card {
-    uint8_t suit = 0;
-    CardValue value = NONE;
-    bool exposed = false;
-
-    Card(uint8_t s, uint8_t v) : suit(s), value((CardValue) v) {}
-
-    void Render(int8_t x, int8_t y, bool selected, RenderBuffer *buffer, uint8_t size_limit = 22);
-
-    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= 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();
-};

+ 0 - 37
utils/Helpers.cpp

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

+ 0 - 47
utils/Helpers.h

@@ -1,47 +0,0 @@
-#pragma once
-#define M_PIX2		6.28318530717958647692	/* 2 pi */
-#include "furi.h"
-#define l_abs(x) ((x) < 0 ? -(x) : (x))
-#define DEG_2_RAD  0.01745329251994329576f
-#define RAD_2_DEG  565.48667764616278292327f
-
-#define DEBUG_BUILD
-#ifdef DEBUG_BUILD
-#define check_pointer(X) check_ptr( X, __FILE__, __LINE__, __FUNCTION__)
-#else
-#define check_pointer(X) while(0)
-#endif
-
-#define CHECK_HEAP() FURI_LOG_I("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);
-float inverse_tanh(double x);
-
-float lerp(float a, float b, float t);
-
-class LogTimer{
-    double start;
-    const char* name;
-public:
-    explicit LogTimer(const char* name);
-    ~LogTimer();
-};
-
-class TypeRegistry {
-public:
-    template <typename T>
-    static int getTypeID() {
-        static int typeID = getNextTypeID();
-        return typeID;
-    }
-
-private:
-    static int getNextTypeID() {
-        static int currentTypeID = 0;
-        return ++currentTypeID;
-    }
-};

+ 0 - 47
utils/Input.h

@@ -1,47 +0,0 @@
-#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);
-        }
-    }
-};

+ 0 - 305
utils/List.h

@@ -1,305 +0,0 @@
-#pragma once
-
-#include <furi.h>
-#include <cstdio>
-#include "Helpers.h"
-
-template<typename T>
-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;
-        }
-
-        Iterator &operator++() {
-            if (node) {
-                node = node->next;
-            }
-            return *this;
-        }
-
-        Iterator operator++(int) {
-            Iterator tmp(*this);
-            operator++();
-            return tmp;
-        }
-
-        Iterator &operator--() {
-            if (node) {
-                node = node->prev;
-            }
-            return *this;
-        }
-
-        Iterator operator--(int) {
-            Iterator tmp(*this);
-            operator--();
-            return tmp;
-        }
-
-        bool operator==(const Iterator &rhs) const {
-            return node == rhs.node;
-        }
-
-        bool operator!=(const Iterator &rhs) const {
-            return node != rhs.node;
-        }
-    };
-
-public:
-    explicit List() : head(nullptr), tail(nullptr) {}
-
-    Iterator begin() const {
-        return Iterator(head);
-    }
-
-    Iterator end() const {
-        return Iterator(nullptr);
-    }
-
-    Iterator last() const {
-        return Iterator(tail);
-    }
-
-    ~List() {
-        clear();
-    }
-
-    void clear() {
-        while (head) {
-            Node *toDelete = head;
-            head = head->next;
-            delete toDelete;
-        }
-        tail = nullptr;
-        count = 0;
-    }
-
-    void deleteData() {
-        Node *current = head;
-        while (current) {
-            delete current->data;
-            current = current->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 push_back(T *value) {
-        Node *newNode = new Node{value, nullptr, tail};
-        if (tail)
-            tail->next = newNode;
-        else
-            head = newNode;
-
-        tail = newNode;
-        ++count;
-    }
-
-    void push_front(T *value) {
-        Node *newNode = new Node{value, head, nullptr};
-        if (head)
-            head->prev = newNode;
-        else
-            tail = newNode;
-
-        head = newNode;
-        ++count;
-    }
-
-    T *pop_back() {
-        if (count == 0)
-            return nullptr;
-
-        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 (count == 0)
-            return nullptr;
-
-        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
-    }
-
-    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;
-        }
-    }
-
-    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;
-        }
-
-        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.
-
-        //Positioning current and previous node pointers to right nodes.
-        for (size_t i = 0; i < index; ++i) {
-            prev_node = current;
-            current = current->next;
-        }
-
-        int i = 0;
-        Node *last_in_segment = current;
-        while (i < numItems - 1 && last_in_segment->next) {
-            last_in_segment = last_in_segment->next;
-            i++;
-        }
-
-        //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 (last_in_segment->next) {
-            last_in_segment->next->prev = prev_node;
-        } else {
-            tail = prev_node;
-        }
-
-        //Update new list head and tail
-        new_list->head = current;
-        new_list->tail = last_in_segment;
-
-        //Update new and original list counts
-        new_list->count = numItems;
-        count -= numItems;
-
-        last_in_segment->next = nullptr;
-        current->prev = nullptr;
-
-        return new_list;
-    }
-
-    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;
-    }
-
-    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 *extract(size_t index) {
-        if (index > size()) {
-            FURI_LOG_E("List", "Out of range");
-            return nullptr;
-        }
-
-        Node *current = head;
-        for (size_t i = 0; i != index && current; ++i)
-            current = current->next;
-
-        if (current->prev)
-            current->prev->next = current->next;
-        else
-            head = current->next;
-        if (current->next)
-            current->next->prev = current->prev;
-        else
-            tail = current->prev;
-
-        T *data = current->data;
-        delete current;
-        count--;
-        return data;
-    }
-
-    T *peek_front() const {
-        if (head) return head->data;
-        else return nullptr;
-    }
-
-    T *peek_back() const {
-        if (tail) return tail->data;
-        else return nullptr;
-    }
-
-    size_t size() const {
-        return count;
-    }
-};

+ 0 - 266
utils/RenderBuffer.cpp

@@ -1,266 +0,0 @@
-#include "RenderBuffer.h"
-#include "Sprite.h"
-#include "Helpers.h"
-#include "Vector.h"
-#include "cmath"
-
-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);
-}
-
-/**
- * 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) {
-    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;
-    int err = (dx > dy ? dx : -dy) / 2;
-
-    while (true) {
-        if (test_coordinate(x0, y0)) {
-            set_pixel(x0, y0, draw_mode);
-        }
-        if (x0 == x1 && y0 == y1) break;
-        int e2 = err;
-        if (e2 > -dx) {
-            err -= dy;
-            x0 += sx;
-        }
-        if (e2 < dy) {
-            err += dx;
-            y0 += sy;
-        }
-    }
-}
-
-/**
- * @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;
-    int16_t decision = 1 - a;
-
-    while (b <= a) {
-        set_pixel_with_check(a + x, b + y, color);
-        set_pixel_with_check(b + x, a + y, color);
-        set_pixel_with_check(-a + x, b + y, color);
-        set_pixel_with_check(-b + x, a + y, color);
-        set_pixel_with_check(-a + x, -b + y, color);
-        set_pixel_with_check(-b + x, -a + y, color);
-        set_pixel_with_check(a + x, -b + y, color);
-        set_pixel_with_check(b + x, -a + y, color);
-
-        b++;
-        if (decision <= 0) {
-            decision += 2 * b + 1;
-        } else {
-            a--;
-            decision += 2 * (b - a) + 1;
-        }
-    }
-}
-
-/**
- * @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:
-        case BlackOnly:
-            draw_sprite(sprite, true, Black, position, x_cap, y_cap, rotation);
-            break;
-        case WhiteOnly:
-            draw_sprite(sprite, false, White, position, x_cap, y_cap, rotation);
-            break;
-        case WhiteAsBlack:
-            draw_sprite(sprite, false, Black, position, x_cap, y_cap, rotation);
-            break;
-        case BlackAsWhite:
-            draw_sprite(sprite, true, White, position, x_cap, y_cap, rotation);
-            break;
-        case WhiteAsInverted:
-            draw_sprite(sprite, false, Flip, position, x_cap, y_cap, rotation);
-            break;
-        case BlackAsInverted:
-            draw_sprite(sprite, true, Flip, position, x_cap, y_cap, rotation);
-            break;
-    }
-}
-
-/**
- * @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 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++) {
-            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() {
-    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++) {
-            if (((x == x0 || x == x1 - 1) && (y == y0 || y == y1 - 1)) || !test_coordinate(x, y)) continue;
-            set_pixel(x, y, draw_mode);
-        }
-    }
-}
-
-/**
- * @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);
-
-    draw_line(x0, y0 + 1, x0, y1 - 1, draw_mode);
-    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);
-}

+ 0 - 40
utils/RenderBuffer.h

@@ -1,40 +0,0 @@
-#pragma once
-
-#include "Buffer.h"
-#include <gui/gui.h>
-
-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, bool doubleBuffered=false);
-
-    virtual ~RenderBuffer();
-
-    void render(Canvas *const canvas);
-
-    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);
-    void draw_box(int16_t x0, int16_t y0, int16_t x1, int16_t y1, PixelColor draw_mode);
-    void draw_rbox_frame(int16_t x0, int16_t y0, int16_t x1, int16_t y1, PixelColor draw_mode);
-
-    void draw_circle(int x, int y, int r, PixelColor draw_mode);
-
-    void draw(Sprite *const sprite, Vector position, uint8_t x_cap, uint8_t y_cap, float rotation);
-    void draw(Sprite *const sprite, Vector position, float rotation);
-
-};

+ 0 - 21
utils/Sprite.cpp

@@ -1,21 +0,0 @@
-#include "Sprite.h"
-#include "Buffer.h"
-#include "../assets.h"
-
-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) {
-    anchor.x = x;
-    anchor.y = y;
-}
-
-Vector Sprite::get_offset() {
-    return {
-            anchor.x * (float) width(),
-            anchor.y * (float) height()
-    };
-}
-
-Sprite::Sprite(const SpriteData &spriteData, DrawMode d) : Buffer(spriteData.data, spriteData.width,spriteData.height), _icon(&spriteData), draw_mode(d) {
-}

+ 0 - 31
utils/Sprite.h

@@ -1,31 +0,0 @@
-#pragma once
-
-#include "RenderBuffer.h"
-#include "Vector.h"
-
-/**
- * @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};
-public:
-    DrawMode draw_mode;
-
-    Sprite(const SpriteData *icon, DrawMode draw_mode);
-    Sprite(const SpriteData &icon, DrawMode draw_mode);
-
-    virtual ~Sprite() {
-        FURI_LOG_D("App", "Sprite cleared");
-    }
-
-    const SpriteData *get_data() { return _icon; }
-
-    void set_anchor(float x, float y);
-
-    Vector get_offset();
-};

+ 0 - 451
utils/Vector.h

@@ -1,451 +0,0 @@
-#pragma once
-
-#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;
-
-    Vector &operator=(const Vector &other) {
-        if (this != &other) {
-            x = other.x;
-            y = other.y;
-        }
-        return *this;
-    }
-
-    Vector(const Vector &other) : x(other.x), y(other.y) {}
-
-    Vector(float _x, float _y) : x(_x), y(_y) {}
-
-    Vector() : x(0), y(0) {}
-
-    /**
-     * @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;
-    }
-
-    /**
-     * @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;
-    }
-
-    /**
-     * @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) {
-            x = 0;
-            y = 0;
-        }else{
-            x = x / m;
-            y = y / m;
-        }
-    }
-
-    /**
-     * @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};
-
-        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) * 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) * 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;
-
-        float k = AC.dot(AB) / AB.dot(AB);
-        if (k < 0 || k > 1) {
-            *success = false;
-            return {};
-        }
-        *success = true;
-        return {
-            k * AB.x + lineA.x,
-            k * AB.y + lineA.y
-        };
-    }
-
-    /**
-     * @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),
-                lerp(start.y, end.y, time)
-        };
-    }
-
-    /**
-     * @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);
-        return Vector::Lerp(a, b, time);
-    }
-};