slotmachine.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. #include <furi.h>
  2. #include <gui/gui.h>
  3. #include <gui/elements.h>
  4. #include <stdlib.h>
  5. #include <storage/storage.h>
  6. #include <stdio.h>
  7. #include <input/input.h>
  8. #include <furi_hal.h>
  9. #include <slotmachine_icons.h>
  10. const Icon* slot_frames[] = {&I_x2, &I_x3, &I_x4, &I_x2_2, &I_x5};
  11. const uint8_t slot_coef[] = {2, 3, 4, 2, 5};
  12. typedef struct {
  13. uint8_t x, y, value, times, speed;
  14. bool spining;
  15. } SlotColumn;
  16. int COLUMNS_COUNT = 4;
  17. int MAX_COLUMNS_COUNT = 4;
  18. typedef struct {
  19. Gui* gui; // container gui
  20. ViewPort* view_port; // current viewport
  21. FuriMessageQueue* input_queue; // Input Events queue
  22. FuriMutex** model_mutex; // mutex for safe threads
  23. uint16_t bet;
  24. double money, winamount;
  25. SlotColumn* columns[4];
  26. bool winview, loseview;
  27. } SlotMachineApp;
  28. typedef struct {
  29. int highscore;
  30. } SlotsHighscore;
  31. #define START_MONEY 1500;
  32. #define START_BET 300;
  33. #define SLOTS_RAND_MAX 5;
  34. #define DEFAULT_SPEED 16;
  35. #define HIGHSCORES_FILENAME APP_DATA_PATH("slotmachine.save")
  36. uint8_t DEFAULT_SPINNING_TIMES = 10;
  37. static SlotsHighscore highscore;
  38. static bool highscores_load() {
  39. Storage* storage = furi_record_open(RECORD_STORAGE);
  40. File* file = storage_file_alloc(storage);
  41. uint16_t bytes_readed = 0;
  42. if(storage_file_open(file, HIGHSCORES_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
  43. bytes_readed = storage_file_read(file, &highscore, sizeof(SlotsHighscore));
  44. }
  45. storage_file_close(file);
  46. storage_file_free(file);
  47. furi_record_close(RECORD_STORAGE);
  48. return bytes_readed == sizeof(SlotsHighscore);
  49. }
  50. static void highscores_save() {
  51. Storage* storage = furi_record_open(RECORD_STORAGE);
  52. File* file = storage_file_alloc(storage);
  53. if(storage_file_open(file, HIGHSCORES_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
  54. storage_file_write(file, &highscore, sizeof(SlotsHighscore));
  55. }
  56. storage_file_close(file);
  57. storage_file_free(file);
  58. furi_record_close(RECORD_STORAGE);
  59. }
  60. void game_results(SlotMachineApp* app) {
  61. int matches[] = {0, 0, 0, 0, 0};
  62. double total = 0;
  63. for(int i = 0; i < COLUMNS_COUNT; i++) {
  64. matches[app->columns[i]->value]++;
  65. }
  66. for(int i = 0; i < 5; i++) {
  67. if(matches[i] >= 2) {
  68. total += app->bet * (slot_coef[i] / (double)(MAX_COLUMNS_COUNT + 1 - matches[i]));
  69. }
  70. }
  71. if(total > 0) {
  72. app->money += total;
  73. app->winamount = total;
  74. app->winview = true;
  75. if(total > highscore.highscore) {
  76. highscore.highscore = total;
  77. highscores_save();
  78. }
  79. }
  80. if(app->money < 10) {
  81. app->loseview = true;
  82. }
  83. }
  84. void draw_container(Canvas* canvas) {
  85. canvas_draw_rframe(canvas, 2, 12, 120, 34, 3);
  86. canvas_draw_rframe(canvas, 2, 13, 120, 34, 3);
  87. canvas_draw_rframe(canvas, 2, 14, 120, 34, 3);
  88. canvas_draw_rframe(canvas, 2, 15, 120, 34, 3);
  89. canvas_draw_rframe(canvas, 2, 16, 120, 34, 3);
  90. canvas_draw_rframe(canvas, 2, 17, 120, 34, 3);
  91. canvas_draw_line(canvas, 31, 16, 31, 48);
  92. canvas_draw_line(canvas, 61, 16, 61, 48);
  93. canvas_draw_line(canvas, 91, 16, 91, 48);
  94. }
  95. bool checkIsSpinning(SlotMachineApp* slotmachine) {
  96. for(int i = 0; i < COLUMNS_COUNT; i++) {
  97. if(slotmachine->columns[i]->spining) return true;
  98. }
  99. return false;
  100. }
  101. void drawButton(Canvas* canvas, uint8_t x, uint8_t y, char* str, bool invert) {
  102. const uint8_t string_width = canvas_string_width(canvas, str);
  103. canvas_set_font(canvas, FontSecondary);
  104. if(invert) {
  105. canvas_draw_rbox(canvas, x, y, string_width + 15, 11, 3);
  106. canvas_invert_color(canvas);
  107. } else {
  108. canvas_draw_rframe(canvas, x, y, string_width + 15, 11, 3);
  109. }
  110. canvas_draw_circle(canvas, x + 5, y + 5, 3);
  111. canvas_draw_circle(canvas, x + 5, y + 5, 1);
  112. canvas_draw_str(canvas, x + 13, y + 9, str);
  113. canvas_invert_color(canvas);
  114. }
  115. // viewport callback
  116. void slotmachine_draw_callback(Canvas* canvas, void* ctx) {
  117. SlotMachineApp* slotmachine = (SlotMachineApp*)ctx;
  118. furi_check(furi_mutex_acquire(slotmachine->model_mutex, FuriWaitForever) == FuriStatusOk);
  119. canvas_set_font(canvas, FontPrimary);
  120. canvas_draw_str(canvas, 2, 10, "Slots");
  121. const Icon* litl_icon = &I_little_coin;
  122. canvas_draw_icon(canvas, 30, 3, litl_icon);
  123. char moneyStr[15];
  124. snprintf(moneyStr, sizeof(moneyStr), "$%.0f", slotmachine->money);
  125. char highscoresStr[25];
  126. snprintf(highscoresStr, sizeof(highscoresStr), "HS:$%d", highscore.highscore);
  127. char betStr[7];
  128. snprintf(betStr, sizeof(betStr), "$%d", slotmachine->bet);
  129. canvas_set_font(canvas, FontSecondary);
  130. canvas_draw_str(canvas, 40, 10, moneyStr);
  131. canvas_draw_str(canvas, 2, canvas_height(canvas) - 3, "Bet:");
  132. canvas_draw_str(canvas, 20, canvas_height(canvas) - 3, betStr);
  133. canvas_draw_str(canvas, 75, 10, highscoresStr);
  134. if(slotmachine->winview) {
  135. char winamountStr[30];
  136. snprintf(winamountStr, sizeof(winamountStr), "You win: $%.2f!", slotmachine->winamount);
  137. canvas_set_font(canvas, FontPrimary);
  138. canvas_draw_str(canvas, 2, 35, winamountStr);
  139. drawButton(canvas, 95, 52, "Ok", false);
  140. furi_mutex_release(slotmachine->model_mutex);
  141. return;
  142. } else if(slotmachine->loseview) {
  143. canvas_set_font(canvas, FontPrimary);
  144. canvas_draw_str(canvas, 2, 35, "You lose ;(");
  145. drawButton(canvas, 95, 52, "Ok", false);
  146. furi_mutex_release(slotmachine->model_mutex);
  147. return;
  148. }
  149. for(int i = 0; i < COLUMNS_COUNT; i++) {
  150. if(slotmachine->columns[i]->spining) {
  151. slotmachine->columns[i]->y += slotmachine->columns[i]->speed;
  152. if(slotmachine->columns[i]->y > 31) {
  153. slotmachine->columns[i]->y = 13;
  154. slotmachine->columns[i]->times--;
  155. slotmachine->columns[i]->speed--;
  156. slotmachine->columns[i]->value = rand() % SLOTS_RAND_MAX;
  157. if(slotmachine->columns[i]->times == 0) {
  158. slotmachine->columns[i]->y = 23;
  159. slotmachine->columns[i]->spining = false;
  160. if(i == COLUMNS_COUNT - 1) {
  161. game_results(slotmachine);
  162. }
  163. }
  164. if(i < COLUMNS_COUNT - 1 &&
  165. slotmachine->columns[i]->times ==
  166. (DEFAULT_SPINNING_TIMES - (int)(DEFAULT_SPINNING_TIMES / 3))) {
  167. slotmachine->columns[i + 1]->spining = true;
  168. }
  169. }
  170. }
  171. canvas_draw_icon(
  172. canvas,
  173. slotmachine->columns[i]->x,
  174. slotmachine->columns[i]->y,
  175. slot_frames[slotmachine->columns[i]->value]);
  176. }
  177. draw_container(canvas);
  178. drawButton(canvas, 90, 52, "Spin", checkIsSpinning(slotmachine));
  179. furi_mutex_release(slotmachine->model_mutex);
  180. }
  181. // callback for viewport input events
  182. static void slotmachine_input_callback(InputEvent* input_event, void* ctx) {
  183. SlotMachineApp* slotmachine = ctx;
  184. furi_message_queue_put(slotmachine->input_queue, input_event, FuriWaitForever);
  185. }
  186. // allocation memory and initialization
  187. SlotMachineApp* slotmachine_app_alloc() {
  188. SlotMachineApp* app = malloc(sizeof(SlotMachineApp));
  189. app->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  190. app->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  191. app->view_port = view_port_alloc();
  192. view_port_draw_callback_set(
  193. app->view_port, slotmachine_draw_callback, app); // viewport callback register
  194. view_port_input_callback_set(app->view_port, slotmachine_input_callback, app);
  195. app->money = START_MONEY;
  196. app->bet = START_BET;
  197. app->winview = false;
  198. app->loseview = false;
  199. app->winamount = 0;
  200. int x = 7;
  201. for(int i = 0; i < COLUMNS_COUNT; i++) {
  202. app->columns[i] = malloc(sizeof(SlotColumn));
  203. app->columns[i]->x = x;
  204. app->columns[i]->y = 25;
  205. app->columns[i]->value = 0;
  206. app->columns[i]->spining = false;
  207. x += 30;
  208. }
  209. app->gui = furi_record_open("gui"); // start gui and adding viewport
  210. gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
  211. return app;
  212. }
  213. void slotmachine_app_free(SlotMachineApp* app) {
  214. gui_remove_view_port(app->gui, app->view_port);
  215. view_port_free(app->view_port);
  216. furi_record_close("gui"); // free memory
  217. furi_mutex_free(app->model_mutex);
  218. for(int i = 0; i < COLUMNS_COUNT; i++) {
  219. free(app->columns[i]);
  220. }
  221. free(app);
  222. }
  223. // entry point
  224. int32_t slotmachine_app(void* p) {
  225. UNUSED(p);
  226. SlotMachineApp* slotmachine = slotmachine_app_alloc();
  227. InputEvent input;
  228. if(!highscores_load()) {
  229. memset(&highscore, 0, sizeof(highscore));
  230. }
  231. // endless input cycle
  232. while(1) {
  233. if(furi_message_queue_get(slotmachine->input_queue, &input, 100) == FuriStatusOk) {
  234. // if thread idle - take it
  235. furi_check(
  236. furi_mutex_acquire(slotmachine->model_mutex, FuriWaitForever) == FuriStatusOk);
  237. if(!checkIsSpinning(slotmachine)) {
  238. if(input.key == InputKeyBack) {
  239. highscores_save();
  240. // exit on back button
  241. furi_mutex_release(slotmachine->model_mutex);
  242. break;
  243. } else if(
  244. input.key == InputKeyOk && input.type == InputTypeShort &&
  245. (slotmachine->winview || slotmachine->loseview)) {
  246. slotmachine->winview = false;
  247. slotmachine->loseview = false;
  248. } else if(
  249. input.key == InputKeyOk && input.type == InputTypeShort &&
  250. slotmachine->bet <= slotmachine->money) {
  251. COLUMNS_COUNT = rand() % 3 + 2;
  252. slotmachine->money -= slotmachine->bet;
  253. slotmachine->columns[0]->spining = true;
  254. for(int i = 0; i < COLUMNS_COUNT; i++) {
  255. slotmachine->columns[i]->times = DEFAULT_SPINNING_TIMES;
  256. slotmachine->columns[i]->speed = DEFAULT_SPEED;
  257. }
  258. } else if(input.key == InputKeyUp) {
  259. if(slotmachine->bet + 10 <= slotmachine->money) {
  260. slotmachine->bet += 10;
  261. }
  262. } else if(input.key == InputKeyDown) {
  263. if(slotmachine->bet - 10 > 0) {
  264. slotmachine->bet -= 10;
  265. }
  266. } else if(input.key == InputKeyLeft && input.type == InputTypeLong) {
  267. memset(&highscore, 0, sizeof(highscore));
  268. highscores_save();
  269. }
  270. }
  271. // release thread
  272. furi_mutex_release(slotmachine->model_mutex);
  273. }
  274. // redraw viewport
  275. view_port_update(slotmachine->view_port);
  276. }
  277. slotmachine_app_free(slotmachine);
  278. return 0;
  279. }