diff --git a/apps_source_code/nupogodi_game/application.fam b/apps_source_code/nupogodi_game/application.fam index 9d2912c0a39..7e6ec032b65 100644 --- a/apps_source_code/nupogodi_game/application.fam +++ b/apps_source_code/nupogodi_game/application.fam @@ -13,6 +13,6 @@ App( fap_icon_assets="icons", fap_author="@sionyx", fap_weburl="https://github.com/sionyx/flipper_nupogodi", - fap_version="1.1", + fap_version="1.3", fap_description="Elektronika IM-02 - Nu, Pogodi! / (Game & Watch - Egg) - Soviet retro game", ) diff --git a/apps_source_code/nupogodi_game/nupogodi.c b/apps_source_code/nupogodi_game/nupogodi.c index f7a7f00cf7a..85230a37d46 100644 --- a/apps_source_code/nupogodi_game/nupogodi.c +++ b/apps_source_code/nupogodi_game/nupogodi.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ typedef struct { NotificationApp* notification; ViewDispatcher* view_dispatcher; View* view; + Widget* widget; FuriThread* worker_thread; FuriTimer* timer; NuPogodiModel* model; @@ -40,6 +42,7 @@ typedef enum { struct NuPogodiModel { bool sound; NuPogodiMode mode; + uint8_t fail_pause; uint8_t tick; bool top; bool left; @@ -54,6 +57,11 @@ typedef enum { WorkerEventTick = (1 << 2), } WorkerEventFlags; +typedef enum { + NuPogodiAppViewGame, + NuPogodiAppViewPause, +} NuPogodiAppView; + #define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventTick) static void nupogodi_view_draw_wolf(Canvas* canvas, NuPogodiModel* model) { @@ -81,55 +89,63 @@ static void nupogodi_view_draw_wolf(Canvas* canvas, NuPogodiModel* model) { } 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); + if(model->eggs[0] > 0) { + if((model->eggs[0] - 1) / 4 == 0) { + canvas_draw_icon(canvas, 108, 12, &I_Egg1); + } else if((model->eggs[0] - 1) / 4 == 1) { + canvas_draw_icon(canvas, 103, 13, &I_Egg2); + } else if((model->eggs[0] - 1) / 4 == 2) { + canvas_draw_icon(canvas, 98, 14, &I_Egg3); + } else if((model->eggs[0] - 1) / 4 == 3) { + canvas_draw_icon(canvas, 93, 16, &I_Egg4); + } else if((model->eggs[0] - 1) / 4 == 4) { + 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); + if(model->eggs[1] > 0) { + if((model->eggs[1] - 1) / 4 == 0) { + canvas_draw_icon_ex(canvas, 108, 36, &I_Egg1, IconRotation90); + } else if((model->eggs[1] - 1) / 4 == 1) { + canvas_draw_icon_ex(canvas, 103, 37, &I_Egg2, IconRotation90); + } else if((model->eggs[1] - 1) / 4 == 2) { + canvas_draw_icon_ex(canvas, 98, 38, &I_Egg3, IconRotation90); + } else if((model->eggs[1] - 1) / 4 == 3) { + canvas_draw_icon_ex(canvas, 93, 39, &I_Egg4, IconRotation90); + } else if((model->eggs[1] - 1) / 4 == 4) { + 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); + if(model->eggs[2] > 0) { + if((model->eggs[2] - 1) / 4 == 0) { + canvas_draw_icon_ex(canvas, 10, 13, &I_Egg1, IconRotation180); + } else if((model->eggs[2] - 1) / 4 == 1) { + canvas_draw_icon_ex(canvas, 15, 13, &I_Egg2, IconRotation180); + } else if((model->eggs[2] - 1) / 4 == 2) { + canvas_draw_icon_ex(canvas, 20, 15, &I_Egg3, IconRotation180); + } else if((model->eggs[2] - 1) / 4 == 3) { + canvas_draw_icon_ex(canvas, 25, 17, &I_Egg4, IconRotation180); + } else if((model->eggs[2] - 1) / 4 == 4) { + 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); + if(model->eggs[3] > 0) { + if((model->eggs[3] - 1) / 4 == 0) { + canvas_draw_icon_ex(canvas, 10, 35, &I_Egg1, IconRotation270); + } else if((model->eggs[3] - 1) / 4 == 1) { + canvas_draw_icon_ex(canvas, 15, 38, &I_Egg2, IconRotation270); + } else if((model->eggs[3] - 1) / 4 == 2) { + canvas_draw_icon_ex(canvas, 20, 39, &I_Egg3, IconRotation270); + } else if((model->eggs[3] - 1) / 4 == 3) { + canvas_draw_icon_ex(canvas, 25, 40, &I_Egg4, IconRotation270); + } else if((model->eggs[3] - 1) / 4 == 4) { + canvas_draw_icon_ex(canvas, 30, 42, &I_Egg5, IconRotation270); + } } } @@ -272,22 +288,17 @@ static bool nupogodi_view_input_callback(InputEvent* event, void* context) { case InputKeyOk: consumed = true; if(app->model->mode == Play) { - app->model->mode = Pause; - } else if(app->model->mode == Pause) { - app->model->mode = Play; + app->model->sound = !app->model->sound; } 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; + case InputKeyBack: + if(app->model->mode == Play) { + consumed = true; + app->model->mode = Pause; + view_dispatcher_switch_to_view(app->view_dispatcher, NuPogodiAppViewPause); + } default: break; } @@ -297,10 +308,33 @@ static bool nupogodi_view_input_callback(InputEvent* event, void* context) { } static uint32_t nupogodi_exit(void* context) { - UNUSED(context); - return VIEW_NONE; + NuPogodiApp* app = context; + furi_assert(app); - //return VIEW_IGNORE; + if(app->model->mode == Over) { + return VIEW_NONE; + } else { + return VIEW_IGNORE; + } +} + +static void nupogodi_pause_exit(GuiButtonType result, InputType type, void* context) { + UNUSED(result); + furi_assert(context); + NuPogodiApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_stop(app->view_dispatcher); + } +} + +static void nupogodi_pause_go(GuiButtonType result, InputType type, void* context) { + UNUSED(result); + furi_assert(context); + NuPogodiApp* app = context; + if(type == InputTypeShort) { + view_dispatcher_switch_to_view(app->view_dispatcher, NuPogodiAppViewGame); + app->model->mode = Play; + } } static int32_t nupogodi_worker(void* context) { @@ -322,8 +356,8 @@ static int32_t nupogodi_worker(void* context) { uint8_t sound = model->sound ? 0 : 1; switch(model->mode) { case Logo: - if(model->tick > 0) { - model->tick--; + if(model->fail_pause > 0) { + model->fail_pause--; } else { model->mode = Play; dolphin_deed(DolphinDeedPluginGameStart); @@ -336,62 +370,94 @@ static int32_t nupogodi_worker(void* context) { model->eggs[3] = 0; model->scores = 0; model->missed = 0; + model->tick = 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) { + if((model->eggs[i] > 0) && ((model->eggs[i] - 1) % 4 == model->tick)) { + model->eggs[i] += 4; + if((model->eggs[i] - 1) / 4 < 5) { notification_message( app->notification, notification_eggs[i][sound]); } } } - // Считаем текущее количество яиц - uint8_t eggs_num = 0; + // Есть ли на текущем такте яйцо + uint8_t tick_egg = 0; for(uint8_t i = 0; i < 4; i++) { - if(model->eggs[i] > 0) { - eggs_num++; + if((model->eggs[i] > 0) && ((model->eggs[i] - 1) % 4 == model->tick)) { + tick_egg = 1; + } + } + + // Определяем положение яйца нулевого такта + uint8_t first_egg_pos = 0; + for(uint8_t i = 0; i < 4; i++) { + if((model->eggs[i] > 0) && ((model->eggs[i] - 1) % 4 == 0)) { + first_egg_pos = (model->eggs[i] - 1) / 4; } } // В зависимости от количества собраных яиц добавляем новые - 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(tick_egg == 0) { + if((model->tick == 0) || + ((model->tick == 2) && (model->scores > EGGS_2_SCORES)) || + ((model->tick == 1) && (model->scores > EGGS_3_SCORES)) || + ((model->tick == 3) && (model->scores > EGGS_4_SCORES))) { + // Добавляем яйцо второго только тогда, когда яйцо нулевого такта уже немного прокатилось, для равномерности + // Яйца первого и третьего тактов так же добавляем в зависимости от положения нулевого. + // Это дает ровную последовательность появления яиц + if((model->tick == 0) || (model->tick + 1 == first_egg_pos)) { + uint32_t rnd; + // Ищем случайное, гарантированно свободное место + do { + rnd = furi_hal_random_get() % 4; + } while(model->eggs[rnd] != 0); + // Добавляем яйцо + // Положение яйца это значение в ячейке деленное на 4 + // Такт яйца - его остаток от деления ни 4 + // Т.к. 0 это значит, что в яцейке нет яйца, поэтому всегда добавляется/отнимается единичка + model->eggs[rnd] = model->tick + 1; + notification_message( + app->notification, notification_eggs[rnd][sound]); + } } } // Проверяем те яйца, которые должны попасть в корзину - if((model->eggs[0] == 6) && (model->top) && (!model->left)) { + if((model->eggs[0] > 0) && ((model->eggs[0] - 1) % 4 == model->tick) && + ((model->eggs[0] - 1) / 4 == 5) && (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)) { + } else if( + (model->eggs[1] > 0) && ((model->eggs[1] - 1) % 4 == model->tick) && + ((model->eggs[1] - 1) / 4 == 5) && (!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)) { + } else if( + (model->eggs[2] > 0) && ((model->eggs[2] - 1) % 4 == model->tick) && + ((model->eggs[2] - 1) / 4 == 5) && (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)) { + } else if( + (model->eggs[3] > 0) && ((model->eggs[3] - 1) % 4 == model->tick) && + ((model->eggs[3] - 1) / 4 == 5) && (!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) { + if((model->eggs[i] > 0) && + ((model->eggs[i] - 1) % 4 == model->tick) && + ((model->eggs[i] - 1) / 4 == 5)) { model->eggs[0] = 0; model->eggs[1] = 0; model->eggs[2] = 0; @@ -400,7 +466,7 @@ static int32_t nupogodi_worker(void* context) { model->missed++; if(model->missed < 4) { model->mode = Fail; - model->tick = 3; + model->fail_pause = 3 * 4; } else { model->mode = Over; if(model->scores >= WIN_SCORES) { @@ -418,8 +484,8 @@ static int32_t nupogodi_worker(void* context) { // nop break; case Fail: - if(model->tick > 0) { - model->tick--; + if(model->fail_pause > 0) { + model->fail_pause--; } else { model->mode = Play; } @@ -427,6 +493,10 @@ static int32_t nupogodi_worker(void* context) { case Over: break; } + model->tick++; + if(model->tick > 3) { + model->tick = 0; + } }, true); } @@ -441,6 +511,15 @@ static void nupogodi_timer_callback(void* context) { furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventTick); } +static Widget* nupogodi_widget_alloc(NuPogodiApp* app) { + Widget* widget = widget_alloc(); + widget_add_string_multiline_element( + widget, 64, 20, AlignCenter, AlignTop, FontSecondary, "Game Paused"); + widget_add_button_element(widget, GuiButtonTypeLeft, "Exit", nupogodi_pause_exit, app); + widget_add_button_element(widget, GuiButtonTypeRight, "Go", nupogodi_pause_go, app); + return widget; +} + static NuPogodiApp* nupogodi_app_alloc() { NuPogodiApp* app = malloc(sizeof(NuPogodiApp)); @@ -465,7 +544,8 @@ static NuPogodiApp* nupogodi_app_alloc() { { app->model = model; model->mode = Logo; - model->tick = 1; + model->tick = 0; + model->fail_pause = 4; // Пропускаем 4 такта перед началом игры - показываем лого model->top = false; model->left = true; model->missed = 0; @@ -479,13 +559,19 @@ static NuPogodiApp* nupogodi_app_alloc() { 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); + view_dispatcher_switch_to_view(app->view_dispatcher, NuPogodiAppViewGame); + + app->widget = nupogodi_widget_alloc(app); + view_dispatcher_add_view( + app->view_dispatcher, NuPogodiAppViewPause, widget_get_view(app->widget)); 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)); + furi_timer_start(app->timer, furi_ms_to_ticks(250)); + + notification_message(app->notification, &sequence_display_backlight_enforce_on); return app; } @@ -493,6 +579,8 @@ static NuPogodiApp* nupogodi_app_alloc() { static void nupogodi_app_free(NuPogodiApp* app) { furi_assert(app); + notification_message(app->notification, &sequence_display_backlight_enforce_auto); + furi_timer_stop(app->timer); furi_timer_free(app->timer); @@ -501,8 +589,10 @@ static void nupogodi_app_free(NuPogodiApp* app) { furi_thread_free(app->worker_thread); // Free views - view_dispatcher_remove_view(app->view_dispatcher, 0); + view_dispatcher_remove_view(app->view_dispatcher, NuPogodiAppViewGame); + view_dispatcher_remove_view(app->view_dispatcher, NuPogodiAppViewPause); view_free(app->view); + widget_free(app->widget); view_dispatcher_free(app->view_dispatcher); // Close gui record