From c333ce2b9786514ee99cb8ecffb92b4490ceaaab Mon Sep 17 00:00:00 2001 From: Marta Sokolska Date: Sun, 31 Dec 2023 00:35:47 +0100 Subject: [PATCH] Initial commit --- .github/workflows/build.yml | 41 ++++ .gitignore | 5 + application.fam | 15 ++ images/cursor.png | Bin 0 -> 116 bytes images/major_placeholder.png | Bin 0 -> 271 bytes tarot.c | 361 +++++++++++++++++++++++++++++++++++ 6 files changed, 422 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 application.fam create mode 100644 images/cursor.png create mode 100644 images/major_placeholder.png create mode 100644 tarot.c diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..143847c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: "FAP: Build for multiple SDK sources" +# This will build your app for dev and release channels on GitHub. +# It will also build your app every day to make sure it's up to date with the latest SDK changes. +# See https://github.com/marketplace/actions/build-flipper-application-package-fap for more information + +on: + push: + ## put your main branch name under "branches" + #branches: + # - master + pull_request: + schedule: + # do a build every day + - cron: "1 1 * * *" + +jobs: + ufbt-build: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - name: dev channel + sdk-channel: dev + - name: release channel + sdk-channel: release + # You can add unofficial channels here. See ufbt action docs for more info. + name: 'ufbt: Build for ${{ matrix.name }}' + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Build with ufbt + uses: flipperdevices/flipperzero-ufbt-action@v0.1 + id: build-app + with: + sdk-channel: ${{ matrix.sdk-channel }} + - name: Upload app artifacts + uses: actions/upload-artifact@v3 + with: + # See ufbt action docs for other output variables + name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }} + path: ${{ steps.build-app.outputs.fap-artifacts }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..538bc53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +dist/* +.vscode +.clang-format +.editorconfig +.DS_Store \ No newline at end of file diff --git a/application.fam b/application.fam new file mode 100644 index 0000000..da9fc4e --- /dev/null +++ b/application.fam @@ -0,0 +1,15 @@ +App( + appid="tarot", # Must be unique + name="Tarot", # Displayed in menus + apptype=FlipperAppType.EXTERNAL, + entry_point="tarot_app", + stack_size=2 * 1024, + fap_category="Games", + # Optional values + # fap_version=(1, 0), # (major, minor) + # fap_icon="tarot.png", # 10x10 1-bit PNG + fap_description="Tarot card reader", + fap_author="pionaiki", + fap_weburl="https://github.com/pionaiki/flipper-tarot", + fap_icon_assets="images", # Image assets to compile for this application +) \ No newline at end of file diff --git a/images/cursor.png b/images/cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..8746f1919459a40ea116dde4ae58b14f4a6e7e2c GIT binary patch literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^tRT$61SFYwH*Nw_oCO|{#S9GGLLkg|>2BR0prD$k zi(?3fZ1SJ~|LvKXnU$55TMjZTZAjwL$l1U!OCms5IEG`9Aj7dg;_oxo*E#}qFnGH9 KxvXl#z;#j`kYr|s}-{b^XT6`yT)O5*h zc=&5FZ@9>`&MN<%anVcaUZ;N(y8U;t&E=){PkU{jJ}LjDPwL-Si8qgniOTzI%Vs?D z(pF2Z@wd-MMp;wUZ${UA43*;=j4wR7w?H9tuN2pBQ~7RV&*>H+$`8~;R`4A8GG|hg zL{nK~ +#include +#include +#include +#include +#include +#include +#include + +#define TAG "tarot" + +/* generated by fbt from .png files in images folder */ +#include + +/* ids for all scenes used by the app */ +typedef enum { + AppScene_MainMenu, + AppScene_About, + AppScene_Game, + AppScene_count +} AppScene; + +/* ids for the 2 types of view used by the app */ +typedef enum { AppView_Submenu, AppView_Popup, AppView_Widget } AppView; + +/* the app context struct */ +typedef struct { + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + Submenu* submenu; // Submenu for the main menu + Popup* popup; // Popup for about + Widget* widget; // Widget for game +} App; + +/* all custom events */ +typedef enum { AppEvent_ShowGame, AppEvent_ShowAbout } AppEvent; + +/* main menu scene */ + +/* indices for menu items */ +typedef enum { AppMenuSelection_Run, AppMenuSelection_About } AppMenuSelection; + +/* main menu callback - sends a custom event to the scene manager based on the menu selection */ +void tarot_app_menu_callback_main_menu(void* context, uint32_t index) { + FURI_LOG_T(TAG, "tarot_app_menu_callback_main_menu"); + App* app = context; + switch(index) { + case AppMenuSelection_Run: + scene_manager_handle_custom_event(app->scene_manager, AppEvent_ShowGame); + break; + case AppMenuSelection_About: + scene_manager_handle_custom_event(app->scene_manager, AppEvent_ShowAbout); + break; + } +} + +/* resets the submenu, gives it content, callbacks and selection enums */ +void tarot_app_scene_on_enter_main_menu(void* context) { + FURI_LOG_T(TAG, "tarot_app_scene_on_enter_main_menu"); + App* app = context; + submenu_reset(app->submenu); + + submenu_add_item( + app->submenu, + "Run", + AppMenuSelection_Run, + tarot_app_menu_callback_main_menu, + app); + submenu_add_item( + app->submenu, + "About", + AppMenuSelection_About, + tarot_app_menu_callback_main_menu, + app); + view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Submenu); +} + +/* main menu event handler - switches scene based on the event */ +bool tarot_app_scene_on_event_main_menu(void* context, SceneManagerEvent event) { + FURI_LOG_T(TAG, "tarot_app_scene_on_event_main_menu"); + App* app = context; + bool consumed = false; + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + case AppEvent_ShowGame: + scene_manager_next_scene(app->scene_manager, AppScene_Game); + consumed = true; + break; + case AppEvent_ShowAbout: + scene_manager_next_scene(app->scene_manager, AppScene_About); + consumed = true; + break; + } + break; + default: // eg. SceneManagerEventTypeBack, SceneManagerEventTypeTick + consumed = false; + break; + } + return consumed; +} + +void tarot_app_scene_on_exit_main_menu(void* context) { + FURI_LOG_T(TAG, "tarot_app_scene_on_exit_main_menu"); + App* app = context; + submenu_reset(app->submenu); +} + +/* About scene */ + +void tarot_app_scene_on_enter_about(void* context) { + FURI_LOG_T(TAG, "tarot_app_scene_on_enter_about"); + App* app = context; + popup_reset(app->popup); + popup_set_context(app->popup, app); + popup_set_header(app->popup, "About", 64, 1, AlignCenter, AlignTop); + popup_set_text(app->popup, "\nCode: pionaiki\nArt: tihyltew\n\ngithub.com/pionaiki/fz-tarot", 64, 10, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Popup); +} + +bool tarot_app_scene_on_event_about(void* context, SceneManagerEvent event) { + FURI_LOG_T(TAG, "tarot_app_scene_on_event_about"); + UNUSED(context); + UNUSED(event); + return false; // don't handle any events +} + +void tarot_app_scene_on_exit_about(void* context) { + FURI_LOG_T(TAG, "tarot_app_scene_on_exit_about"); + App* app = context; + popup_reset(app->popup); +} + +/* Game scene */ + +/* ###### */ + +const int card_x = 23; +const int card_y = 32; +const int card_number = 22; + +int card_selected = 1; // Cursor position 0-2 + +struct Card { + const char name[20]; + const Icon* icon; +}; + +const struct Card card[] = { + {"The Fool", &I_major_placeholder}, + {"The Magician", &I_major_placeholder}, + {"The High Priestess", &I_major_placeholder}, + {"The Empress", &I_major_placeholder}, + {"The Emperor", &I_major_placeholder}, + {"The Hierophant", &I_major_placeholder}, + {"The Lovers", &I_major_placeholder}, + {"The Chariot", &I_major_placeholder}, + {"Strength", &I_major_placeholder}, + {"The Hermit", &I_major_placeholder}, + {"Wheel of Fortune", &I_major_placeholder}, + {"Justice", &I_major_placeholder}, + {"The Hanged Man", &I_major_placeholder}, + {"Death", &I_major_placeholder}, + {"Temperance", &I_major_placeholder}, + {"The Devil", &I_major_placeholder}, + {"The Tower", &I_major_placeholder}, + {"The Star", &I_major_placeholder}, + {"The Moon", &I_major_placeholder}, + {"The Sun", &I_major_placeholder}, + {"Judgement", &I_major_placeholder}, + {"The World", &I_major_placeholder} +}; + +static uint16_t unbiased_rand (uint16_t max) { + uint16_t remainder = RAND_MAX % max; + uint16_t x; + do { + x = rand(); + } while (x >= RAND_MAX - remainder); + return x % max; +} + +void tarot_app_scene_on_enter_game(void* context) { + FURI_LOG_T(TAG, "tarot_app_scene_on_enter_game"); + App* app = context; + widget_reset(app->widget); + + int one = unbiased_rand(card_number); + int two = unbiased_rand(card_number); + while (two == one) { + two = unbiased_rand(card_number); + }; + int three = unbiased_rand(card_number); + while (three == one || three == two) { + three = unbiased_rand(card_number); + } + + struct Card random_card[] = { + card[one], + card[two], + card[three] + }; + + widget_add_icon_element(app->widget, (128-card_x)/2 - 32, 10, random_card[0].icon); + widget_add_icon_element(app->widget, (128-card_x)/2, 10, random_card[1].icon); + widget_add_icon_element(app->widget, (128-card_x)/2 + 32, 10, random_card[2].icon); + + widget_add_icon_element(app->widget, (128-card_x)/2 - 34 + card_x/2 + card_selected*32, 40, &I_cursor); + + widget_add_string_element(app->widget, 64, 60, AlignCenter, AlignBottom, FontPrimary, random_card[card_selected].name); + + view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Widget); +} + +bool tarot_app_scene_on_event_game(void* context, SceneManagerEvent event) { + FURI_LOG_T(TAG, "tarot_app_scene_on_event_game"); + UNUSED(context); + UNUSED(event); + return false; // don't handle any events +} + +void tarot_app_scene_on_exit_game(void* context) { + FURI_LOG_T(TAG, "tarot_app_scene_on_exit_game"); + App* app = context; + widget_reset(app->widget); +} + +/* ###### */ + +/* collection of all scene on_enter handlers - in the same order as their enum */ +void (*const tarot_app_scene_on_enter_handlers[])(void*) = { + tarot_app_scene_on_enter_main_menu, + tarot_app_scene_on_enter_about, + tarot_app_scene_on_enter_game}; + +/* collection of all scene on event handlers - in the same order as their enum */ +bool (*const tarot_app_scene_on_event_handlers[])(void*, SceneManagerEvent) = { + tarot_app_scene_on_event_main_menu, + tarot_app_scene_on_event_about, + tarot_app_scene_on_event_game}; + +/* collection of all scene on exit handlers - in the same order as their enum */ +void (*const tarot_app_scene_on_exit_handlers[])(void*) = { + tarot_app_scene_on_exit_main_menu, + tarot_app_scene_on_exit_about, + tarot_app_scene_on_exit_game}; + +/* collection of all on_enter, on_event, on_exit handlers */ +const SceneManagerHandlers tarot_app_scene_event_handlers = { + .on_enter_handlers = tarot_app_scene_on_enter_handlers, + .on_event_handlers = tarot_app_scene_on_event_handlers, + .on_exit_handlers = tarot_app_scene_on_exit_handlers, + .scene_num = AppScene_count}; + +/* custom event handler - passes the event to the scene manager */ +bool tarot_app_scene_manager_custom_event_callback(void* context, uint32_t custom_event) { + FURI_LOG_T(TAG, "tarot_app_scene_manager_custom_event_callback"); + furi_assert(context); + App* app = context; + return scene_manager_handle_custom_event(app->scene_manager, custom_event); +} + +/* navigation event handler - passes the event to the scene manager */ +bool tarot_app_scene_manager_navigation_event_callback(void* context) { + FURI_LOG_T(TAG, "tarot_app_scene_manager_navigation_event_callback"); + furi_assert(context); + App* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +/* initialise the scene manager with all handlers */ +void tarot_app_scene_manager_init(App* app) { + FURI_LOG_T(TAG, "tarot_app_scene_manager_init"); + app->scene_manager = scene_manager_alloc(&tarot_app_scene_event_handlers, app); +} + +/* initialise the views, and initialise the view dispatcher with all views */ +void tarot_app_view_dispatcher_init(App* app) { + FURI_LOG_T(TAG, "tarot_app_view_dispatcher_init"); + app->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_enable_queue(app->view_dispatcher); + + // allocate each view + FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init allocating views"); + app->submenu = submenu_alloc(); + app->popup = popup_alloc(); + app->widget = widget_alloc(); + + // assign callback that pass events from views to the scene manager + FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init setting callbacks"); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, tarot_app_scene_manager_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, tarot_app_scene_manager_navigation_event_callback); + + // add views to the dispatcher, indexed by their enum value + FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init adding view menu"); + view_dispatcher_add_view(app->view_dispatcher, AppView_Submenu, submenu_get_view(app->submenu)); + + FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init adding view popup"); + view_dispatcher_add_view(app->view_dispatcher, AppView_Popup, popup_get_view(app->popup)); + + FURI_LOG_D(TAG, "tarot_app_view_dispatcher_init adding view widget"); + view_dispatcher_add_view(app->view_dispatcher, AppView_Widget, widget_get_view(app->widget)); +} + +/* initialise app data, scene manager, and view dispatcher */ +App* tarot_app_init() { + FURI_LOG_T(TAG, "tarot_app_init"); + App* app = malloc(sizeof(App)); + tarot_app_scene_manager_init(app); + tarot_app_view_dispatcher_init(app); + return app; +} + +/* free all app data, scene manager, and view dispatcher */ +void tarot_app_free(App* app) { + FURI_LOG_T(TAG, "tarot_app_free"); + scene_manager_free(app->scene_manager); + view_dispatcher_remove_view(app->view_dispatcher, AppView_Submenu); + view_dispatcher_remove_view(app->view_dispatcher, AppView_Popup); + view_dispatcher_remove_view(app->view_dispatcher, AppView_Widget); + view_dispatcher_free(app->view_dispatcher); + submenu_free(app->submenu); + popup_free(app->popup); + widget_free(app->widget); + free(app); +} + +/* go to trace log level in the dev environment */ +void tarot_app_set_log_level() { +#ifdef FURI_DEBUG + furi_log_set_level(FuriLogLevelTrace); +#else + furi_log_set_level(FuriLogLevelInfo); +#endif +} + +/* entrypoint */ +int32_t tarot_app(void* p) { + UNUSED(p); + tarot_app_set_log_level(); + + // create the app context struct, scene manager, and view dispatcher + FURI_LOG_I(TAG, "Tarot app starting..."); + App* app = tarot_app_init(); + + // set the scene and launch the main loop + Gui* gui = furi_record_open(RECORD_GUI); + view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + scene_manager_next_scene(app->scene_manager, AppScene_MainMenu); + FURI_LOG_D(TAG, "Starting dispatcher..."); + view_dispatcher_run(app->view_dispatcher); + + // free all memory + FURI_LOG_I(TAG, "Tarot app finishing..."); + furi_record_close(RECORD_GUI); + tarot_app_free(app); + return 0; +}