Explorar el Código

Add solitaire/common from https://github.com/teeebor/flipper_helpers

git-subtree-dir: solitaire/common
git-subtree-mainline: c588fd8e63e9b27a9daa6303edc22d4c72370252
git-subtree-split: 4ef796c450428521fc576c8e5c993d027061414d
Willy-JL hace 2 años
padre
commit
7ba114fa43

+ 1 - 0
solitaire/common/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/teeebor/flipper_helpers main

+ 21 - 0
solitaire/common/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Tibor Tálosi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 162 - 0
solitaire/common/README.md

@@ -0,0 +1,162 @@
+# Flipper helper functions
+
+A collection of methods my games are using. 
+
+---
+
+## UI
+
+Extra methods for graphics. It can read/write/copy anything from the screen, create a buffer that you can swap with the one inside the canvas object and every flipper draw call should work with it.
+
+Call this at the end of your program
+```
+ui_cleanup();
+```
+
+### How to use it
+
+* To test if a pixel is black or not: ``test_pixel(canvas_buffer, pixel_x, pixel_y, screen_width)``
+* Convert Flippers icon to the same type to u8g2: ``uint8_t * result=image_data(canvas, icon);``
+* Flatten X/Y pixel coordinate to array index ``uint32_t result=pixel_index(x, y)``
+* Draw icon region with overlay/invert support: ``draw_icon_clip(canvas, icon, image_x, image_y, screen_x, screen_y, width, height, draw_mode)``
+* Draw flipped region icon with overlay/invert support: ``draw_icon_clip_flipped(canvas, icon, image_x, image_y, screen_x, screen_y, width, height, draw_mode)``
+* Draw rounded box: ``draw_rounded_box(canvas, x, y, width, heigth, draw_mode)``
+* Draw rounded box frame: ``draw_rounded_box_frame(canvas, x, y, width, heigth, draw_mode)``
+* Draw rectangle: ``draw_rectangle(canvas, x, y, width, heigth, draw_mode)``
+* Invert screen colors in a rectangle: ``invert_rectangle(canvas, x, y, width, height)``
+* Invert screen colors in a shape: ``invert_shape(canvas, image, x, y, width, height)``
+* Read pixel: ``read_pixel(canvas, x, y)``
+* Set pixel: ``set_pixel(canvas, x, y, draw_mode)``
+* Draw line: ``set_pixel(canvas, x1, y1, x2, y1, draw_mode)``
+* Test coordinate if it is in the screen: ``in_screen(x, y)``
+* Test coordinate if it is in the screen: ``in_screen(x, y)``
+* Get screen buffer: ``get_buffer(canvas)``
+* Create a new buffer: ``make_buffer()``
+* Clone buffer: ``clone_buffer(source_buffer, result_buffer)``
+
+---
+## Math (dml)
+
+Some extra math functionality.
+
+### Vector math
+
+* Vector type
+* add 2 vectors together ``Vector result = vector_add(a, b);``
+* divide 2 vectors components from each other ``Vector result = vector_sub(a, b);``
+* Multiplying vector components together ``Vector result = vector_mul_components(a, b);``
+* Dividing vector components ``Vector result = vector_div_components(a, b);``
+* Get the magnitude of a vector ``float result = vector_magnitude(a);``
+* Normalize a vector (components add up to one) ``Vector result = vector_normalized(a);``
+* Distance between vectors ``Vector result = vector_distance(a, b);``
+* Get dot product ``float result = vector_distance(a, b);``
+
+### Linear Interpolation
+
+* Interpolate a single value ``float result = lerp(start_value, end_value, time);``
+* Interpolate Vector ``Vector result = lerp(start_vector, end_vector, time);``
+* Quadratic interpolation ``Vector result = quadratic_2d(start_vector, control_vector, end_vector, time);``
+
+
+### Extra
+
+* min macro ``min(a,b);``
+* max macro ``max(a,b);``
+* abs macro ``abs(value);``
+
+---
+## Queue
+
+A basic queue functionality. Useful to chain your methods in order, so they can follow each other. Mainly used to state switching an animation state handling in my games.
+
+Currently it only supports one queue chain.
+### How to use it
+* Store a queue handler in your application state
+  ```c
+  typedef struct {
+      //...your game states
+      QueueState queue_state;
+  } AppState;
+  ```
+* To add a new queue item
+  ```c
+  //Needed functions
+  void done_callback(void *ctx) {
+     //Called when the item is being removed from the queue
+  }
+  void start_callback(void *ctx) {
+     //Called every tick when it is active
+  }
+  void render_callback(const void *ctx, Canvas *const canvas) {
+     //Called while rendering in case you want to render something
+  }
+  
+  //In your code somewhere
+  //Not needed callbacks can be set to NULL
+  enqueue(&(app_state->queue_state), app_state, done_callback, start_callback, render_callback, queue_run_duration)
+  ```
+* Required to run
+  ```c
+  //Put this in a place that runs every tick
+  bool queue_ran = run_queue(&(app_state->queue_state), app_state);
+  
+  //Put this in your render function
+  render_queue(&(app_state->queue_state), app_state, canvas);
+  ```
+* Clean up at the end of your program
+  ```c
+  queue_clear(&(app_state->queue_state));
+  ```
+
+---
+## Card
+
+### Types
+* Card
+  - Stores data about one card from the deck
+  - Properties
+    - pip: the symbol (0: spades, 1: hearths, 2: diamonds, 3: clubs)
+    - character: the letter for the card (2 is 0, A is 12)
+    - disabled: can be used freely. I used it to hide the card during animation
+    - flipped: if this is set to true, instead of the card graphics the backside will be rendered
+* Deck
+  - Store a whole deck
+  - Properties
+    - deck_count: How many decks are added into this
+    - cards: contains the cards
+    - card_count: how many cards are in the deck
+    - index: useful to track your current index in the deck
+* Hand
+  - Store cards that are given for example to the player
+  - Properties
+    - Cards: cards in the hand
+    - index: to keep track of where we are in the hand
+    - max: how many cards can be in the hand
+
+### Usage
+
+Before you can use it, you have to load the card_graphics.png file in your games assets
+```c
+    set_card_graphics(&I_card_graphics);
+```
+
+- Draw a single card: ``draw_card_at(pos_x, pos_y, pip, character, canvas)``
+- Draw single card with invert support: ``draw_card_at_colored(pos_x, pos_y, pip, character, inverted, canvas)``
+- Draw the blackjack deck in the bottom right corner (it can stack up to 21 cards): ``draw_deck(cards, count, canvas)``
+- Draw the backside of the card: ``draw_card_back_at(pos_x, pos_y, canvas)``
+- Generate a deck: ``generate_deck(deck_pointer, how_many_decks)``
+- Blackjack hand calculation: ``hand_count(cards_array, how_many_cards)``
+- Draw card animation state: ``draw_card_animation(card, from_vector, control_vector, to_vector, current_phase, extra_time_margin, canvas)``
+- Create hand pointer: ``init_hand(hand_pointer, card_count)``
+- Free hand pointer: ``free_hand(hand_pointer)``
+- Add card to the hand: ``add_to_hand(hand_pointer, card)``
+- Draw an empty space (like the one in solitaire): ``draw_card_space(pos_x, pos_y, is_highlighted, canvas)``
+- Draw cards in a column: ``draw_hand_column(hand, pos_x, pos_y, selected_card, canvas)``
+- Remove card from the deck: ``remove_from_deck(index_to_remove, deck_pointer)``
+- Get first non flipped card index in a hand: ``first_non_flipped_card(hand)``
+- Move cards from one hand to another starting at index: ``extract_hand_region(from_hand, to_hand, start_index)``lipped card index in a hand: ``first_non_flipped_card(hand)``
+- Move the whole hand into another hand: ``add_hand_region(from_hand, to_hand)``
+
+## Menu
+
+Do not use it, it barely works and only used in blackjack.

+ 333 - 0
solitaire/common/card.c

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

+ 177 - 0
solitaire/common/card.h

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

BIN
solitaire/common/card_graphics.png


+ 60 - 0
solitaire/common/dml.c

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

+ 116 - 0
solitaire/common/dml.h

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

+ 89 - 0
solitaire/common/menu.c

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

+ 76 - 0
solitaire/common/menu.h

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

+ 66 - 0
solitaire/common/queue.c

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

+ 64 - 0
solitaire/common/queue.h

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

+ 216 - 0
solitaire/common/ui.c

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

+ 58 - 0
solitaire/common/ui.h

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