MX 1 год назад
Родитель
Сommit
5bb8693529
68 измененных файлов с 3359 добавлено и 1984 удалено
  1. 5 5
      application.fam
  2. 512 0
      assets.c
  3. 282 0
      assets.h
  4. BIN
      assets/10.png
  5. BIN
      assets/2.png
  6. BIN
      assets/3.png
  7. BIN
      assets/4.png
  8. BIN
      assets/5.png
  9. BIN
      assets/6.png
  10. BIN
      assets/7.png
  11. BIN
      assets/8.png
  12. BIN
      assets/9.png
  13. BIN
      assets/A.png
  14. BIN
      assets/J.png
  15. BIN
      assets/K.png
  16. BIN
      assets/Q.png
  17. BIN
      assets/card_graphics.png
  18. BIN
      assets/clubs.png
  19. BIN
      assets/diamonds.png
  20. BIN
      assets/hearths.png
  21. BIN
      assets/joker.png
  22. BIN
      assets/logo.png
  23. BIN
      assets/main_image.png
  24. BIN
      assets/pattern_big.png
  25. BIN
      assets/pattern_small.png
  26. BIN
      assets/solitaire_main.png
  27. BIN
      assets/solve.png
  28. BIN
      assets/spades.png
  29. BIN
      assets/start.png
  30. 0 353
      common/card.c
  31. 0 192
      common/card.h
  32. 0 53
      common/dml.c
  33. 0 116
      common/dml.h
  34. 0 103
      common/menu.c
  35. 0 77
      common/menu.h
  36. 0 69
      common/queue.c
  37. 0 70
      common/queue.h
  38. 0 257
      common/ui.c
  39. 0 105
      common/ui.h
  40. 0 63
      defines.h
  41. 25 0
      docs/CHANGELOG.md
  42. 31 0
      docs/README.md
  43. 44 0
      game_state.h
  44. 139 521
      solitaire.c
  45. 103 0
      src/scene/falling_card.c
  46. 11 0
      src/scene/falling_card.h
  47. 118 0
      src/scene/intro_animation.c
  48. 12 0
      src/scene/intro_animation.h
  49. 130 0
      src/scene/main_screen.c
  50. 16 0
      src/scene/main_screen.h
  51. 341 0
      src/scene/play_screen.c
  52. 14 0
      src/scene/play_screen.h
  53. 74 0
      src/scene/result_screen.c
  54. 12 0
      src/scene/result_screen.h
  55. 61 0
      src/scene/scene_setup.h
  56. 204 0
      src/scene/solve_screen.c
  57. 8 0
      src/scene/solve_screen.h
  58. 258 0
      src/util/buffer.c
  59. 74 0
      src/util/buffer.h
  60. 230 0
      src/util/card.c
  61. 55 0
      src/util/card.h
  62. 6 0
      src/util/game_loop.h
  63. 41 0
      src/util/helpers.c
  64. 34 0
      src/util/helpers.h
  65. 303 0
      src/util/list.c
  66. 48 0
      src/util/list.h
  67. 119 0
      src/util/vector.c
  68. 49 0
      src/util/vector.h

+ 5 - 5
application.fam

@@ -3,13 +3,13 @@ App(
     name="Solitaire",
     apptype=FlipperAppType.EXTERNAL,
     entry_point="solitaire_app",
-    requires=["gui","storage","canvas"],
+    requires=["gui"],
     stack_size=2 * 1024,
     order=30,
     fap_icon="solitaire_10px.png",
     fap_category="Games",
-    fap_icon_assets="assets",
-    fap_author="@teeebor",
-    fap_version="1.1",
-    fap_description="Solitaire game",
+    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
+}};

+ 282 - 0
assets.h

@@ -0,0 +1,282 @@
+#include <furi.h>
+#include "src/util/buffer.h"
+
+/*
+      ██████   
+   ███   ███   
+███      ███   
+████████████   
+         ███   
+*/
+extern const Buffer sprite_4;
+/*
+████████████   
+         ███   
+      ███      
+   ███         
+   ███         
+*/
+extern const Buffer sprite_7;
+/*
+   ███         ███   
+███   ███   ███   ███
+███      ███      ███
+███               ███
+   ███         ███   
+      ███   ███      
+         ███         
+*/
+extern const Buffer sprite_hearths;
+/*
+   ██████      
+███      ███   
+████████████   
+███      ███   
+███      ███   
+*/
+extern const Buffer sprite_A;
+/*
+                     
+      ███   ███   ███
+   ███   ███   ███   
+      ███   ███   ███
+   ███   ███   ███   
+      ███   ███   ███
+   ███   ███   ███   
+*/
+extern const Buffer sprite_pattern_small;
+/*
+                  ███                        
+   ███      ████████████                     
+   ████████████████████████      ███         
+   ████████████████████████   ███   ███      
+   ███      ████████████         ███         
+                                             
+         ███            ███                  
+      ███   ███      ████████████      ███   
+         ███      ████████████████████████   
+                  ████████████████████████   
+                     ████████████      ███   
+                                             
+                  ███            ███         
+   ███      ████████████      ███   ███      
+   ████████████████████████      ███         
+   ████████████████████████            ███   
+   ███      ████████████            ███      
+                                    ███      
+         ███         ███            ███      
+      ███            ███   ███      ███      
+      ███         ███      ███   ███         
+*/
+extern const Buffer sprite_pattern_big;
+/*
+                                          █████████████████████████████████████████████            
+                                       ███                                             ███         
+                                       █████████████████████████████████████████████   ███         
+                                    ███                                             ██████         
+                                    █████████████████████████████████████████████   ██████         
+                                 ███                                             █████████         
+                                 ███   ███      ███               ███            █████████         
+                                 ███   ███   ███   ███         █████████         █████████         
+                                 █████████████████████████████████████████████   █████████         
+                              ███                                             ████████████         
+                              ███   ███      ███               ███            ████████████         
+                              ███   ███   ███   ███         █████████         ████████████         
+            ███████████████████████████████████████████████████████████████   ████████████         
+         ███                                             ███               ███████████████         
+         ███   ███      ███               ███            ██████            ███████████████         
+         ███   ███   ███   ███         █████████         █████████         ███████████████         
+         ███   ███   ███   ███      ███████████████      ███████████████   ███████████████         
+         ███   ███   ███   ███   █████████████████████   ██████         ██████████████████         
+         ███   ███   ███   ███      ███████████████      █████████      ██████████████████         
+         ███   ███      ███            █████████         █████████      ██████████████████         
+         ███                              ███            █████████      ██████████████████         
+         ███                                             ████████████   ███████████████            
+         ███                                             ████████████   ███████████████            
+         ███                                             ███████████████████████████               
+         ███                                             ███████████████████████████               
+         ███            ███                              ████████████████████████                  
+         ███         █████████            ███      ███   ████████████████████████                  
+         ███      ███████████████      ███   ███   ███   ████████████████████████                  
+         ███   █████████████████████   ███   ███   ███   ████████████████████████                  
+         ███      ███████████████      ███   ███   ███   █████████████████████                     
+         ███         █████████         ███   ███   ███   █████████████████████                     
+         ███            ███               ███      ███   █████████████████████                     
+         ███                                             █████████████████████                     
+            █████████████████████████████████████████████   ███████████████                        
+            ███                                             ███████████████                        
+               █████████████████████████████████████████████   ████████████                        
+               ███            ███               ███      ███   ████████████                        
+               ███                                             █████████                           
+                  █████████████████████████████████████████████   ██████                           
+                  ███            ███               ███      ███   ██████                           
+                  ███                                             ██████                           
+                     █████████████████████████████████████████████   ███                           
+                     ███                                             ███                           
+                        █████████████████████████████████████████████                              
+*/
+extern const Buffer sprite_main_image;
+/*
+   ██████      
+███      ███   
+███      ███   
+███   ███      
+   ███   ███   
+*/
+extern const Buffer sprite_Q;
+/*
+   ██████      
+███      ███   
+      ███      
+   ███         
+████████████   
+*/
+extern const Buffer sprite_2;
+/*
+█████████      
+         ███   
+   ██████      
+         ███   
+█████████      
+*/
+extern const Buffer sprite_3;
+/*
+███      ███   
+███   ███   ███
+███   ███   ███
+███   ███   ███
+███      ███   
+*/
+extern const Buffer sprite_10;
+/*
+███      ███   
+███   ███      
+██████         
+███   ███      
+███      ███   
+*/
+extern const Buffer sprite_K;
+/*
+████████████   
+███            
+█████████      
+         ███   
+████████████   
+*/
+extern const Buffer sprite_5;
+/*
+   ██████      
+███            
+█████████      
+███      ███   
+   ██████      
+*/
+extern const Buffer sprite_6;
+/*
+   ██████      
+███      ███   
+   █████████   
+         ███   
+   ██████      
+*/
+extern const Buffer sprite_9;
+/*
+         ███         
+      ███   ███      
+   ███         ███   
+███               ███
+   ███         ███   
+      ███   ███      
+         ███         
+*/
+extern const Buffer sprite_diamonds;
+/*
+████████████   
+███      ███   
+████████████   
+███      ███   
+████████████   
+*/
+extern const Buffer sprite_8;
+/*
+         ███   
+         ███   
+         ███   
+███      ███   
+   ██████      
+*/
+extern const Buffer sprite_J;
+/*
+         ███         
+      █████████      
+   ███████████████   
+█████████████████████
+██████   ███   ██████
+         ███         
+      █████████      
+*/
+extern const Buffer sprite_spades;
+/*
+      ████████████               ████████████      ███                                    ███   
+   ██████████████████         ███            ███   ███                                    ███   
+████████████████████████      ███               █████████      █████████      ███   ████████████
+████████████████████████         ██████            ███      ███         ███   ██████      ███   
+████████████████████████               ██████      ███         ████████████   ███         ███   
+████████████████████████                     ███   ███      ███         ███   ███         ███   
+   ██████████████████         ███            ███   ███      ███      ██████   ███         ███   
+      ████████████               ████████████      ██████   █████████   ███   ███         ██████
+*/
+extern const Buffer sprite_start;
+/*
+███         ███                     ███                  ███            ████████████               ███                                                                  ███                                       
+███         ███                     ███                  ███         ██████████████████            ███                                                                  ███                                       
+███         ███      █████████      ███         ████████████      ████████████████████████      █████████      █████████                  ████████████   █████████      ███      ███            ███   █████████   
+███████████████   ███         ███   ███      ███         ███      ████████████████████████         ███      ███         ███            ███            ███         ███   ███         ███      ███   ███         ███
+███         ███   ███         ███   ███      ███         ███      ████████████████████████         ███      ███         ███               █████████   ███         ███   ███         ███      ███   ███████████████
+███         ███   ███         ███   ███      ███         ███         ██████████████████            ███      ███         ███                        ██████         ███   ███            ██████      ███            
+███         ███      █████████      ███         ████████████            ████████████               ██████      █████████               ████████████      █████████      ███            ██████         ████████████
+*/
+extern const Buffer sprite_solve;
+/*
+      █████████      
+      █████████      
+██████   ███   ██████
+█████████████████████
+██████   ███   ██████
+         ███         
+      █████████      
+*/
+extern const Buffer sprite_clubs;
+/*
+██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+███                                                                                                                                                                                                            ███
+███                                                                                                                                                                                                            ███
+███               ███████████████                                       ██████         ██████            ███                                    ██████                                                         ███
+███            █████████████████████                                    ██████         ██████         ██████                                    ██████                                                         ███
+███         █████████         █████████                                 ██████         ██████         ██████                                    ██████                                                         ███
+███         ██████               ██████                                 ██████                        ██████                                                                                                   ███
+███         ██████                              ████████████            ██████         ██████      ███████████████      ███████████████         ██████         ██████   ██████         █████████               ███
+███         █████████                        ██████████████████         ██████         ██████      ███████████████      ██████████████████      ██████         ███████████████      ███████████████            ███
+███            ███████████████            █████████      █████████      ██████         ██████         ██████         ██████         ██████      ██████         ██████            ██████         ██████         ███
+███                  ███████████████      ██████            ██████      ██████         ██████         ██████                        ██████      ██████         ██████            ██████         ██████         ███
+███                           █████████   ██████            ██████      ██████         ██████         ██████                  ████████████      ██████         ██████            █████████████████████         ███
+███         ██████               ██████   ██████            ██████      ██████         ██████         ██████            ██████████████████      ██████         ██████            █████████████████████         ███
+███         ██████               ██████   ██████            ██████      ██████         ██████         ██████         █████████      ██████      ██████         ██████            ██████                        ███
+███         █████████         █████████   █████████      █████████      ██████         ██████         ██████         ██████         ██████      ██████         ██████            ██████         ██████         ███
+███            █████████████████████         ██████████████████         ██████         ██████         ████████████   █████████████████████      ██████         ██████               ███████████████            ███
+███               ███████████████               ████████████            ██████         ██████            █████████      █████████   ██████      ██████         ██████                  █████████               ███
+███                                                                                                                                                                                                            ███
+███                                                                                                                                                                                                            ███
+██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
+                                                                                                      ███                                                                                          ███            
+      ███         ███               ███                     ███                  █████████            ███                                                                        ███               ███            
+   ███   ███   ███   ███         ███   ███               █████████               █████████            ███   ███                        █████████                              ███                  ███            
+   ███      ███      ███      ███         ███         ███████████████      ██████   ███   ██████      ███   ██████      ███   ███      ███      ███      ███         ███   █████████   ███   ███   ███            
+   ███               ███   ███               ███   █████████████████████   █████████████████████      ███   ███   ███   ███   ███      ███      ███   ███   ███   ███   ███   ███      ███   ███   ███            
+      ███         ███         ███         ███      ██████   ███   ██████   ██████   ███   ██████      ███   ███   ███      ███         ███      ███   ███   ███   ███   ███   ███         ███      ███            
+         ███   ███               ███   ███                  ███                     ███               ███   ██████         ███         █████████         ███         ███      ███         ███      ███            
+            ███                     ███                  █████████               █████████            ███                                                                                          ███            
+                                                                                                      ████████████████████████████████████████████████████████████████████████████████████████████████            
+*/
+extern const Buffer sprite_logo;
+














BIN
assets/card_graphics.png


BIN
assets/clubs.png


BIN
assets/diamonds.png


BIN
assets/hearths.png


BIN
assets/joker.png


BIN
assets/logo.png


BIN
assets/main_image.png


BIN
assets/pattern_big.png


BIN
assets/pattern_small.png


BIN
assets/solitaire_main.png


BIN
assets/solve.png


BIN
assets/spades.png


BIN
assets/start.png


+ 0 - 353
common/card.c

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

+ 0 - 192
common/card.h

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

+ 0 - 53
common/dml.c

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

+ 0 - 116
common/dml.h

@@ -1,116 +0,0 @@
-//
-// 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);

+ 0 - 103
common/menu.c

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

+ 0 - 77
common/menu.h

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

+ 0 - 69
common/queue.c

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

+ 0 - 70
common/queue.h

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

+ 0 - 257
common/ui.c

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

+ 0 - 105
common/ui.h

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

+ 0 - 63
defines.h

@@ -1,63 +0,0 @@
-#pragma once
-#include <furi.h>
-#include <input/input.h>
-#include <gui/elements.h>
-#include <flipper_format/flipper_format.h>
-#include <flipper_format/flipper_format_i.h>
-#include "common/card.h"
-#include "common/queue.h"
-
-#define APP_NAME "Solitaire"
-
-typedef enum {
-    EventTypeTick,
-    EventTypeKey,
-} EventType;
-
-typedef struct {
-    EventType type;
-    InputEvent input;
-} AppEvent;
-
-typedef enum {
-    GameStateGameOver,
-    GameStateStart,
-    GameStatePlay,
-    GameStateAnimate
-} PlayState;
-
-typedef struct {
-    uint8_t *buffer;
-    Card card;
-    int8_t deck;
-    int indexes[4];
-    float x;
-    float y;
-    float vx;
-    float vy;
-    bool started;
-} CardAnimation;
-
-typedef struct {
-    Deck deck;
-    Hand bottom_columns[7];
-    Card top_cards[4];
-    bool dragging_deck;
-    uint8_t dragging_column;
-    Hand dragging_hand;
-
-    InputKey input;
-
-    bool started;
-    bool had_change;
-    bool processing;
-    bool longPress;
-    PlayState state;
-    unsigned int last_tick;
-    uint8_t selectRow;
-    uint8_t selectColumn;
-    int8_t selected_card;
-    CardAnimation animation;
-    uint8_t *buffer;
-    FuriMutex* mutex;
-} GameState;

+ 25 - 0
docs/CHANGELOG.md

@@ -0,0 +1,25 @@
+## 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 

+ 31 - 0
docs/README.md

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

+ 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;
+

+ 139 - 521
solitaire.c

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

+ 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);

+ 118 - 0
src/scene/intro_animation.c

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

+ 41 - 0
src/util/helpers.c

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

+ 34 - 0
src/util/helpers.h

@@ -0,0 +1,34 @@
+#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__)
+#else
+#define check_pointer(X) _test_ptr(X)
+#define trace(X) while(0)
+#endif
+
+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);
+
+
+float inverse_tanh(double x);
+
+float lerp_number(float a, float b, float t);
+
+size_t curr_time();

+ 303 - 0
src/util/list.c

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