Initial commit

This commit is contained in:
Marta Sokolska 2023-12-31 00:35:47 +01:00
parent aa23d0cbbb
commit c333ce2b97
6 changed files with 422 additions and 0 deletions

41
.github/workflows/build.yml vendored Normal file
View 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
View file

@ -0,0 +1,5 @@
dist/*
.vscode
.clang-format
.editorconfig
.DS_Store

15
application.fam Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

361
tarot.c Normal file
View 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;
}