#include #include #include #include #include #include #include #include #include const Icon* slot_frames[] = {&I_x2, &I_x3, &I_x4, &I_x2_2, &I_x5}; const uint8_t slot_coef[] = {2, 3, 4, 2, 5}; typedef struct { uint8_t x, y, value, times, speed; bool spining; } SlotColumn; int COLUMNS_COUNT = 4; int MAX_COLUMNS_COUNT = 4; typedef struct { Gui* gui; // container gui ViewPort* view_port; // current viewport FuriMessageQueue* input_queue; // Input Events queue FuriMutex* model_mutex; // mutex for safe threads uint16_t bet; double money, winamount; SlotColumn* columns[4]; bool winview, loseview; } SlotMachineApp; typedef struct { int highscore; } SlotsHighscore; #define START_MONEY 1500; #define START_BET 300; #define SLOTS_RAND_MAX 5; #define DEFAULT_SPEED 16; #define HIGHSCORES_FILENAME APP_DATA_PATH("slotmachine.save") uint8_t DEFAULT_SPINNING_TIMES = 10; static SlotsHighscore highscore; static bool highscores_load() { Storage* storage = furi_record_open(RECORD_STORAGE); storage_common_migrate(storage, EXT_PATH("apps/Games/slotmachine.save"), HIGHSCORES_FILENAME); File* file = storage_file_alloc(storage); uint16_t bytes_readed = 0; if(storage_file_open(file, HIGHSCORES_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) { bytes_readed = storage_file_read(file, &highscore, sizeof(SlotsHighscore)); } storage_file_close(file); storage_file_free(file); furi_record_close(RECORD_STORAGE); return bytes_readed == sizeof(SlotsHighscore); } static void highscores_save() { Storage* storage = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(storage); if(storage_file_open(file, HIGHSCORES_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { storage_file_write(file, &highscore, sizeof(SlotsHighscore)); } storage_file_close(file); storage_file_free(file); furi_record_close(RECORD_STORAGE); } void game_results(SlotMachineApp* app) { int matches[] = {0, 0, 0, 0, 0}; double total = 0; for(int i = 0; i < COLUMNS_COUNT; i++) { matches[app->columns[i]->value]++; } for(int i = 0; i < 5; i++) { if(matches[i] >= 2) { total += app->bet * (slot_coef[i] / (double)(MAX_COLUMNS_COUNT + 1 - matches[i])); } } if(total > 0) { app->money += total; // Add winnings to the player's money app->winamount = total; app->winview = true; // Add the bet amount back to the player's money app->money += app->bet; // Reset the bet amount, uncomment me if you want to do this //app->bet = 0; if(total > highscore.highscore) { highscore.highscore = total; highscores_save(); } } if(app->money < 10) { app->loseview = true; } } void draw_container(Canvas* canvas) { canvas_draw_rframe(canvas, 2, 12, 120, 34, 3); canvas_draw_rframe(canvas, 2, 13, 120, 34, 3); canvas_draw_rframe(canvas, 2, 14, 120, 34, 3); canvas_draw_rframe(canvas, 2, 15, 120, 34, 3); canvas_draw_rframe(canvas, 2, 16, 120, 34, 3); canvas_draw_rframe(canvas, 2, 17, 120, 34, 3); canvas_draw_line(canvas, 31, 16, 31, 48); canvas_draw_line(canvas, 61, 16, 61, 48); canvas_draw_line(canvas, 91, 16, 91, 48); } bool checkIsSpinning(SlotMachineApp* slotmachine) { for(int i = 0; i < COLUMNS_COUNT; i++) { if(slotmachine->columns[i]->spining) return true; } return false; } void drawButton(Canvas* canvas, uint8_t x, uint8_t y, char* str, bool invert) { const uint8_t string_width = canvas_string_width(canvas, str); canvas_set_font(canvas, FontSecondary); if(invert) { canvas_draw_rbox(canvas, x, y, string_width + 15, 11, 3); canvas_invert_color(canvas); } else { canvas_draw_rframe(canvas, x, y, string_width + 15, 11, 3); } canvas_draw_circle(canvas, x + 5, y + 5, 3); canvas_draw_circle(canvas, x + 5, y + 5, 1); canvas_draw_str(canvas, x + 13, y + 9, str); canvas_invert_color(canvas); } // viewport callback void slotmachine_draw_callback(Canvas* canvas, void* ctx) { SlotMachineApp* slotmachine = (SlotMachineApp*)ctx; furi_check(furi_mutex_acquire(slotmachine->model_mutex, FuriWaitForever) == FuriStatusOk); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 2, 10, "Slots"); const Icon* litl_icon = &I_little_coin; canvas_draw_icon(canvas, 30, 3, litl_icon); char moneyStr[15]; snprintf(moneyStr, sizeof(moneyStr), "$%.0f", slotmachine->money); char highscoresStr[25]; snprintf(highscoresStr, sizeof(highscoresStr), "HS:$%d", highscore.highscore); char betStr[7]; snprintf(betStr, sizeof(betStr), "$%d", slotmachine->bet); canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 40, 10, moneyStr); canvas_draw_str(canvas, 2, canvas_height(canvas) - 3, "Bet:"); canvas_draw_str(canvas, 20, canvas_height(canvas) - 3, betStr); canvas_draw_str(canvas, 75, 10, highscoresStr); if(slotmachine->winview) { char winamountStr[30]; snprintf(winamountStr, sizeof(winamountStr), "You win: $%.2f!", slotmachine->winamount); canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 2, 35, winamountStr); drawButton(canvas, 95, 52, "Ok", false); furi_mutex_release(slotmachine->model_mutex); return; } else if(slotmachine->loseview) { canvas_set_font(canvas, FontPrimary); canvas_draw_str(canvas, 2, 35, "You lose ;("); drawButton(canvas, 95, 52, "Ok", false); furi_mutex_release(slotmachine->model_mutex); return; } for(int i = 0; i < COLUMNS_COUNT; i++) { if(slotmachine->columns[i]->spining) { slotmachine->columns[i]->y += slotmachine->columns[i]->speed; if(slotmachine->columns[i]->y > 31) { slotmachine->columns[i]->y = 13; slotmachine->columns[i]->times--; slotmachine->columns[i]->speed--; slotmachine->columns[i]->value = rand() % SLOTS_RAND_MAX; if(slotmachine->columns[i]->times == 0) { slotmachine->columns[i]->y = 23; slotmachine->columns[i]->spining = false; if(i == COLUMNS_COUNT - 1) { game_results(slotmachine); } } if(i < COLUMNS_COUNT - 1 && slotmachine->columns[i]->times == (DEFAULT_SPINNING_TIMES - (int)(DEFAULT_SPINNING_TIMES / 3))) { slotmachine->columns[i + 1]->spining = true; } } } canvas_draw_icon( canvas, slotmachine->columns[i]->x, slotmachine->columns[i]->y, slot_frames[slotmachine->columns[i]->value]); } draw_container(canvas); drawButton(canvas, 90, 52, "Spin", checkIsSpinning(slotmachine)); furi_mutex_release(slotmachine->model_mutex); } // callback for viewport input events static void slotmachine_input_callback(InputEvent* input_event, void* ctx) { SlotMachineApp* slotmachine = ctx; furi_message_queue_put(slotmachine->input_queue, input_event, FuriWaitForever); } // allocation memory and initialization SlotMachineApp* slotmachine_app_alloc() { SlotMachineApp* app = malloc(sizeof(SlotMachineApp)); app->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal); app->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); app->view_port = view_port_alloc(); view_port_draw_callback_set( app->view_port, slotmachine_draw_callback, app); // viewport callback register view_port_input_callback_set(app->view_port, slotmachine_input_callback, app); app->money = START_MONEY; app->bet = START_BET; app->winview = false; app->loseview = false; app->winamount = 0; int x = 7; for(int i = 0; i < COLUMNS_COUNT; i++) { app->columns[i] = malloc(sizeof(SlotColumn)); app->columns[i]->x = x; app->columns[i]->y = 25; app->columns[i]->value = 0; app->columns[i]->spining = false; x += 30; } app->gui = furi_record_open("gui"); // start gui and adding viewport gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); return app; } void slotmachine_app_free(SlotMachineApp* app) { gui_remove_view_port(app->gui, app->view_port); view_port_free(app->view_port); furi_record_close("gui"); // free memory furi_mutex_free(app->model_mutex); for(int i = 0; i < COLUMNS_COUNT; i++) { free(app->columns[i]); } free(app); } // entry point int32_t slotmachine_app(void* p) { UNUSED(p); SlotMachineApp* slotmachine = slotmachine_app_alloc(); InputEvent input; if(!highscores_load()) { memset(&highscore, 0, sizeof(highscore)); } // endless input cycle while(1) { if(furi_message_queue_get(slotmachine->input_queue, &input, 100) == FuriStatusOk) { // if thread idle - take it furi_check( furi_mutex_acquire(slotmachine->model_mutex, FuriWaitForever) == FuriStatusOk); if(!checkIsSpinning(slotmachine)) { if(input.key == InputKeyBack) { highscores_save(); // exit on back button furi_mutex_release(slotmachine->model_mutex); break; } else if( input.key == InputKeyOk && input.type == InputTypeShort && (slotmachine->winview || slotmachine->loseview)) { slotmachine->winview = false; slotmachine->loseview = false; } else if( input.key == InputKeyOk && input.type == InputTypeShort && slotmachine->bet <= slotmachine->money) { COLUMNS_COUNT = rand() % 3 + 2; slotmachine->money -= slotmachine->bet; slotmachine->columns[0]->spining = true; for(int i = 0; i < COLUMNS_COUNT; i++) { slotmachine->columns[i]->times = DEFAULT_SPINNING_TIMES; slotmachine->columns[i]->speed = DEFAULT_SPEED; } } else if(input.key == InputKeyUp) { if(slotmachine->bet + 10 <= slotmachine->money) { slotmachine->bet += 10; } } else if(input.key == InputKeyDown) { if(slotmachine->bet - 10 > 0) { slotmachine->bet -= 10; } } else if(input.key == InputKeyLeft && input.type == InputTypeLong) { memset(&highscore, 0, sizeof(highscore)); highscores_save(); } } // release thread furi_mutex_release(slotmachine->model_mutex); } // redraw viewport view_port_update(slotmachine->view_port); } slotmachine_app_free(slotmachine); return 0; }