mirror of
https://github.com/pionaiki/fz-tarot.git
synced 2026-02-13 10:24:36 +01:00
Initial commit
This commit is contained in:
parent
aa23d0cbbb
commit
c333ce2b97
6 changed files with 422 additions and 0 deletions
41
.github/workflows/build.yml
vendored
Normal file
41
.github/workflows/build.yml
vendored
Normal file
|
|
@ -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 }}
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
dist/*
|
||||
.vscode
|
||||
.clang-format
|
||||
.editorconfig
|
||||
.DS_Store
|
||||
15
application.fam
Normal file
15
application.fam
Normal file
|
|
@ -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
|
||||
)
|
||||
BIN
images/cursor.png
Normal file
BIN
images/cursor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 116 B |
BIN
images/major_placeholder.png
Normal file
BIN
images/major_placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 271 B |
361
tarot.c
Normal file
361
tarot.c
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/icon_i.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/widget.h>
|
||||
|
||||
#define TAG "tarot"
|
||||
|
||||
/* generated by fbt from .png files in images folder */
|
||||
#include <tarot_icons.h>
|
||||
|
||||
/* 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue