diff --git a/applications/external/slots/application.fam b/applications/external/slots/application.fam new file mode 100644 index 000000000..27337ca53 --- /dev/null +++ b/applications/external/slots/application.fam @@ -0,0 +1,13 @@ +App( + appid="slotmachine", + name="Slot Machine", + apptype=FlipperAppType.EXTERNAL, + entry_point="slotmachine_app", + cdefines=["APP_SLOTMACHINE"], + requires=["gui"], + stack_size=1 * 1024, + fap_icon="ddgame_icon.png", + order=30, + fap_category="Games", + fap_icon_assets="assets", +) diff --git a/applications/external/slots/assets/little_coin.png b/applications/external/slots/assets/little_coin.png new file mode 100644 index 000000000..34ef4f8e7 Binary files /dev/null and b/applications/external/slots/assets/little_coin.png differ diff --git a/applications/external/slots/assets/x2.png b/applications/external/slots/assets/x2.png new file mode 100644 index 000000000..f65ded3ba Binary files /dev/null and b/applications/external/slots/assets/x2.png differ diff --git a/applications/external/slots/assets/x2_2.png b/applications/external/slots/assets/x2_2.png new file mode 100644 index 000000000..6808d7212 Binary files /dev/null and b/applications/external/slots/assets/x2_2.png differ diff --git a/applications/external/slots/assets/x3.png b/applications/external/slots/assets/x3.png new file mode 100644 index 000000000..b54dcf104 Binary files /dev/null and b/applications/external/slots/assets/x3.png differ diff --git a/applications/external/slots/assets/x4.png b/applications/external/slots/assets/x4.png new file mode 100644 index 000000000..48b3cb407 Binary files /dev/null and b/applications/external/slots/assets/x4.png differ diff --git a/applications/external/slots/assets/x5.png b/applications/external/slots/assets/x5.png new file mode 100644 index 000000000..8aaf5401f Binary files /dev/null and b/applications/external/slots/assets/x5.png differ diff --git a/applications/external/slots/ddgame_icon.png b/applications/external/slots/ddgame_icon.png new file mode 100644 index 000000000..aa2aa5b35 Binary files /dev/null and b/applications/external/slots/ddgame_icon.png differ diff --git a/applications/external/slots/slotmachine.c b/applications/external/slots/slotmachine.c new file mode 100644 index 000000000..c8eb270d6 --- /dev/null +++ b/applications/external/slots/slotmachine.c @@ -0,0 +1,268 @@ +#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; +} SlotMachineApp; + +#define START_MONEY 1500; +#define START_BET 300; +#define SSRAND_MAX 5; +#define DEFAULT_SPEED 16; + +uint8_t DEFAULT_SPINNING_TIMES = 10; + +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; + app->winamount = total; + app->winview = 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"); + canvas_draw_icon(canvas, 30, 3, &I_little_coin); + + char moneyStr[15]; + snprintf(moneyStr, sizeof(moneyStr), "$%.0f", slotmachine->money); + + char betStr[7]; + snprintf(betStr, sizeof(betStr), "$%d", slotmachine->bet); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 45, 10, moneyStr); + canvas_draw_str(canvas, 2, canvas_height(canvas) - 3, "Bet:"); + canvas_draw_str(canvas, 20, canvas_height(canvas) - 3, betStr); + + 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; + } + + 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() % SSRAND_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->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; + + // endless input cycle + while(furi_message_queue_get(slotmachine->input_queue, &input, FuriWaitForever) == + FuriStatusOk) { + // if thread idle - take it + furi_check(furi_mutex_acquire(slotmachine->model_mutex, FuriWaitForever) == FuriStatusOk); + + if(!checkIsSpinning(slotmachine)) { + if(input.key == InputKeyBack) { + // exit on back button + furi_mutex_release(slotmachine->model_mutex); + break; + } else if(input.key == InputKeyOk && input.type == InputTypeShort && slotmachine->winview) { + slotmachine->winview = 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; + } + } + } + + // release thread + furi_mutex_release(slotmachine->model_mutex); + // redraw viewport + view_port_update(slotmachine->view_port); + } + + slotmachine_app_free(slotmachine); + + return 0; +}