Browse Source

upd nupogodi

MX 2 năm trước cách đây
mục cha
commit
8148d341e1

+ 1 - 1
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",
 )

+ 175 - 85
apps_source_code/nupogodi_game/nupogodi.c

@@ -4,6 +4,7 @@
 #include <gui/elements.h>
 #include <gui/view_dispatcher.h>
 #include <gui/modules/dialog_ex.h>
+#include <gui/modules/widget.h>
 #include <storage/storage.h>
 #include <stdlib.h>
 #include <power/power_service/power.h>
@@ -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