#include #include #include #include #include #include #include #include #include #include #include "notifications.h" #include #define WIN_SCORES 100 #define EGGS_2_SCORES 10 #define EGGS_3_SCORES 20 #define EGGS_4_SCORES 50 typedef struct NuPogodiModel NuPogodiModel; typedef struct { Gui* gui; NotificationApp* notification; ViewDispatcher* view_dispatcher; View* view; FuriThread* worker_thread; FuriTimer* timer; NuPogodiModel* model; } NuPogodiApp; typedef enum { Logo, Ready, Play, Pause, Fail, Over, } NuPogodiMode; struct NuPogodiModel { bool sound; NuPogodiMode mode; uint8_t tick; bool top; bool left; uint8_t missed; uint16_t scores; uint8_t eggs[4]; }; typedef enum { WorkerEventReserved = (1 << 0), WorkerEventStop = (1 << 1), WorkerEventTick = (1 << 2), } WorkerEventFlags; #define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventTick) static void nupogodi_view_draw_wolf(Canvas* canvas, NuPogodiModel* model) { if(model->mode == Play) { if(model->left) { if(model->top) { canvas_draw_icon(canvas, 41, 16, &I_WolfLeftUp); } else { canvas_draw_icon(canvas, 41, 16, &I_WolfLeftDown); } } else { if(model->top) { canvas_draw_icon(canvas, 41, 16, &I_WolfRightUp); } else { canvas_draw_icon(canvas, 41, 16, &I_WolfRightDown); } } } else if(model->mode == Fail) { if(model->left) { canvas_draw_icon(canvas, 46, 0, &I_WolfLeftFail); } else { canvas_draw_icon(canvas, 36, 0, &I_WolfRightFail); } } } static void nupogodi_view_draw_eggs(Canvas* canvas, NuPogodiModel* model) { // Top Right if(model->eggs[0] == 1) { canvas_draw_icon(canvas, 108, 12, &I_Egg1); } else if(model->eggs[0] == 2) { canvas_draw_icon(canvas, 103, 13, &I_Egg2); } else if(model->eggs[0] == 3) { canvas_draw_icon(canvas, 98, 14, &I_Egg3); } else if(model->eggs[0] == 4) { canvas_draw_icon(canvas, 93, 16, &I_Egg4); } else if(model->eggs[0] == 5) { canvas_draw_icon(canvas, 88, 17, &I_Egg5); } // Bottom Right if(model->eggs[1] == 1) { canvas_draw_icon_ex(canvas, 108, 36, &I_Egg1, IconRotation90); } else if(model->eggs[1] == 2) { canvas_draw_icon_ex(canvas, 103, 37, &I_Egg2, IconRotation90); } else if(model->eggs[1] == 3) { canvas_draw_icon_ex(canvas, 98, 38, &I_Egg3, IconRotation90); } else if(model->eggs[1] == 4) { canvas_draw_icon_ex(canvas, 93, 39, &I_Egg4, IconRotation90); } else if(model->eggs[1] == 5) { canvas_draw_icon_ex(canvas, 88, 41, &I_Egg5, IconRotation90); } // Top Left if(model->eggs[2] == 1) { canvas_draw_icon_ex(canvas, 10, 13, &I_Egg1, IconRotation180); } else if(model->eggs[2] == 2) { canvas_draw_icon_ex(canvas, 15, 13, &I_Egg2, IconRotation180); } else if(model->eggs[2] == 3) { canvas_draw_icon_ex(canvas, 20, 15, &I_Egg3, IconRotation180); } else if(model->eggs[2] == 4) { canvas_draw_icon_ex(canvas, 25, 17, &I_Egg4, IconRotation180); } else if(model->eggs[2] == 5) { canvas_draw_icon_ex(canvas, 30, 18, &I_Egg5, IconRotation180); } // Bottom Left if(model->eggs[3] == 1) { canvas_draw_icon_ex(canvas, 10, 35, &I_Egg1, IconRotation270); } else if(model->eggs[3] == 2) { canvas_draw_icon_ex(canvas, 15, 38, &I_Egg2, IconRotation270); } else if(model->eggs[3] == 3) { canvas_draw_icon_ex(canvas, 20, 39, &I_Egg3, IconRotation270); } else if(model->eggs[3] == 4) { canvas_draw_icon_ex(canvas, 25, 40, &I_Egg4, IconRotation270); } else if(model->eggs[3] == 5) { canvas_draw_icon_ex(canvas, 30, 42, &I_Egg5, IconRotation270); } } static void nupogodi_view_draw_scores(Canvas* canvas, NuPogodiModel* model) { if(model->mode == Play) { if(model->missed >= 1) { canvas_draw_icon(canvas, 78, 0, &I_Chick); } if(model->missed >= 2) { canvas_draw_icon(canvas, 92, 0, &I_Chick); } if(model->missed >= 3) { canvas_draw_icon(canvas, 106, 0, &I_Chick); } canvas_draw_icon(canvas, 22, 0, &I_Egg3); FuriString* scores_str = model->scores < WIN_SCORES ? furi_string_alloc_printf("%1d/%1d", model->scores, WIN_SCORES) : furi_string_alloc_printf("%1d", model->scores); canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 38, 10, furi_string_get_cstr(scores_str)); furi_string_free(scores_str); } } static void nupogodi_view_draw_scene(Canvas* canvas) { canvas_draw_icon(canvas, 0, 0, &I_ChickenL); canvas_draw_icon(canvas, 0, 24, &I_ChickenL); canvas_draw_icon(canvas, 120, 0, &I_ChickenR); canvas_draw_icon(canvas, 120, 24, &I_ChickenR); canvas_draw_line(canvas, 0, 21, 8, 21); canvas_draw_line(canvas, 0, 22, 8, 22); canvas_draw_line(canvas, 0, 23, 8, 23); canvas_draw_line(canvas, 8, 21, 36, 29); canvas_draw_line(canvas, 8, 22, 36, 30); canvas_draw_line(canvas, 8, 23, 36, 31); canvas_draw_line(canvas, 0, 45, 10, 45); canvas_draw_line(canvas, 0, 46, 10, 46); canvas_draw_line(canvas, 0, 47, 10, 47); canvas_draw_line(canvas, 10, 45, 36, 53); canvas_draw_line(canvas, 10, 46, 36, 54); canvas_draw_line(canvas, 10, 47, 36, 55); canvas_draw_line(canvas, 120, 21, 127, 21); canvas_draw_line(canvas, 120, 22, 127, 22); canvas_draw_line(canvas, 120, 23, 127, 23); canvas_draw_line(canvas, 92, 29, 120, 21); canvas_draw_line(canvas, 92, 30, 120, 22); canvas_draw_line(canvas, 92, 31, 120, 23); canvas_draw_line(canvas, 120, 45, 127, 45); canvas_draw_line(canvas, 120, 46, 127, 46); canvas_draw_line(canvas, 120, 47, 127, 47); canvas_draw_line(canvas, 92, 53, 120, 45); canvas_draw_line(canvas, 92, 54, 120, 46); canvas_draw_line(canvas, 92, 55, 120, 47); } static void nupogodi_view_draw_logo(Canvas* canvas) { canvas_draw_icon(canvas, 0, 0, &I_NuPogodi); } static void nupogodi_view_draw_over(Canvas* canvas, NuPogodiModel* model) { canvas_draw_icon(canvas, 30, 0, &I_Egg3); FuriString* scores_str = furi_string_alloc_printf("%1d", model->scores); canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 46, 10, furi_string_get_cstr(scores_str)); furi_string_free(scores_str); if(model->scores >= WIN_SCORES) { canvas_draw_icon(canvas, 22, 12, &I_OverWin); } else { canvas_draw_icon(canvas, 22, 12, &I_OverLose); } } static void nupogodi_view_draw_callback(Canvas* canvas, void* _model) { NuPogodiModel* model = _model; furi_assert(model); canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); switch(model->mode) { case Logo: nupogodi_view_draw_logo(canvas); break; case Play: case Fail: nupogodi_view_draw_eggs(canvas, model); nupogodi_view_draw_scene(canvas); nupogodi_view_draw_scores(canvas, model); nupogodi_view_draw_wolf(canvas, model); break; case Pause: nupogodi_view_draw_scene(canvas); nupogodi_view_draw_scores(canvas, model); break; case Over: nupogodi_view_draw_over(canvas, model); break; default: break; } } static bool nupogodi_view_input_callback(InputEvent* event, void* context) { NuPogodiApp* app = context; furi_assert(app); bool consumed = false; if(app->model->mode == Fail) { return false; } if(event->type == InputTypeShort) { switch(event->key) { case InputKeyUp: consumed = true; app->model->top = true; break; case InputKeyDown: consumed = true; app->model->top = false; break; case InputKeyLeft: consumed = true; app->model->left = true; break; case InputKeyRight: consumed = true; app->model->left = false; break; case InputKeyOk: consumed = true; if(app->model->mode == Play) { app->model->mode = Pause; } else if(app->model->mode == Pause) { app->model->mode = Play; } else if(app->model->mode == Over) { app->model->mode = Ready; } break; default: break; } } else if(event->type == InputTypeLong) { switch(event->key) { case InputKeyOk: consumed = true; app->model->sound = !app->model->sound; break; default: break; } } return consumed; } static uint32_t nupogodi_exit(void* context) { UNUSED(context); return VIEW_NONE; //return VIEW_IGNORE; } static int32_t nupogodi_worker(void* context) { furi_assert(context); NuPogodiApp* app = context; UNUSED(app); while(1) { uint32_t events = furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever); furi_check((events & FuriFlagError) == 0); if(events & WorkerEventStop) break; if(events & WorkerEventTick) { with_view_model( app->view, NuPogodiModel * model, { uint8_t sound = model->sound ? 0 : 1; switch(model->mode) { case Logo: if(model->tick > 0) { model->tick--; } else { model->mode = Play; dolphin_deed(DolphinDeedPluginGameStart); } break; case Ready: model->eggs[0] = 0; model->eggs[1] = 0; model->eggs[2] = 0; model->eggs[3] = 0; model->scores = 0; model->missed = 0; model->mode = Play; dolphin_deed(DolphinDeedPluginGameStart); break; case Play: // Прокатываем все яйца на одно деление вперед for(uint8_t i = 0; i < 4; i++) { if(model->eggs[i] > 0) { model->eggs[i]++; if(model->eggs[i] < 6) { notification_message( app->notification, notification_eggs[i][sound]); } } } // Считаем текущее количество яиц uint8_t eggs_num = 0; for(uint8_t i = 0; i < 4; i++) { if(model->eggs[i] > 0) { eggs_num++; } } // В зависимости от количества собраных яиц добавляем новые if((eggs_num == 0) || (model->scores > EGGS_2_SCORES && eggs_num == 1) || (model->scores > EGGS_3_SCORES && eggs_num == 2) || (model->scores > EGGS_4_SCORES && eggs_num == 3)) { uint32_t rnd = furi_hal_random_get() % 4; if(model->eggs[rnd] == 0) { model->eggs[rnd] = 1; notification_message( app->notification, notification_eggs[rnd][sound]); } } // Проверяем те яйца, которые должны попасть в корзину if((model->eggs[0] == 6) && (model->top) && (!model->left)) { model->eggs[0] = 0; model->scores++; notification_message(app->notification, notification_done[sound]); } else if((model->eggs[1] == 6) && (!model->top) && (!model->left)) { model->eggs[1] = 0; model->scores++; notification_message(app->notification, notification_done[sound]); } else if((model->eggs[2] == 6) && (model->top) && (model->left)) { model->eggs[2] = 0; model->scores++; notification_message(app->notification, notification_done[sound]); } else if((model->eggs[3] == 6) && (!model->top) && (model->left)) { model->eggs[3] = 0; model->scores++; notification_message(app->notification, notification_done[sound]); } else { // Если яйцо было не поймано - зануляем все, запускаем анимацию разбития for(uint8_t i = 0; i < 4; i++) { if(model->eggs[i] == 6) { model->eggs[0] = 0; model->eggs[1] = 0; model->eggs[2] = 0; model->eggs[3] = 0; model->left = i >= 2; model->missed++; if(model->missed < 4) { model->mode = Fail; model->tick = 3; } else { model->mode = Over; if(model->scores >= WIN_SCORES) { dolphin_deed(DolphinDeedPluginGameWin); } } notification_message( app->notification, notification_fail[sound]); } } } break; case Pause: // nop break; case Fail: if(model->tick > 0) { model->tick--; } else { model->mode = Play; } break; case Over: break; } }, true); } } return 0; } static void nupogodi_timer_callback(void* context) { furi_assert(context); NuPogodiApp* app = context; furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventTick); } static NuPogodiApp* nupogodi_app_alloc() { NuPogodiApp* app = malloc(sizeof(NuPogodiApp)); // Gui app->gui = furi_record_open(RECORD_GUI); app->notification = furi_record_open(RECORD_NOTIFICATION); // View dispatcher app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); // Views app->view = view_alloc(); view_set_context(app->view, app); view_set_draw_callback(app->view, nupogodi_view_draw_callback); view_set_input_callback(app->view, nupogodi_view_input_callback); view_allocate_model(app->view, ViewModelTypeLocking, sizeof(NuPogodiModel)); with_view_model( app->view, NuPogodiModel * model, { app->model = model; model->mode = Logo; model->tick = 1; model->top = false; model->left = true; model->missed = 0; model->scores = 0; model->eggs[0] = 0; model->eggs[1] = 0; model->eggs[2] = 0; model->eggs[3] = 0; }, true); view_set_previous_callback(app->view, nupogodi_exit); view_dispatcher_add_view(app->view_dispatcher, 0, app->view); view_dispatcher_switch_to_view(app->view_dispatcher, 0); app->worker_thread = furi_thread_alloc_ex("NuPogodiWorker", 1024, nupogodi_worker, app); furi_thread_start(app->worker_thread); app->timer = furi_timer_alloc(nupogodi_timer_callback, FuriTimerTypePeriodic, app); furi_timer_start(app->timer, furi_ms_to_ticks(1000)); return app; } static void nupogodi_app_free(NuPogodiApp* app) { furi_assert(app); furi_timer_stop(app->timer); furi_timer_free(app->timer); furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop); furi_thread_join(app->worker_thread); furi_thread_free(app->worker_thread); // Free views view_dispatcher_remove_view(app->view_dispatcher, 0); view_free(app->view); view_dispatcher_free(app->view_dispatcher); // Close gui record furi_record_close(RECORD_GUI); app->gui = NULL; furi_record_close(RECORD_NOTIFICATION); app->notification = NULL; // Free rest free(app); } int32_t nupogodi_app(void* p) { UNUSED(p); NuPogodiApp* app = nupogodi_app_alloc(); view_dispatcher_run(app->view_dispatcher); nupogodi_app_free(app); return 0; }