more updates

This commit is contained in:
OrionW06 2025-06-20 06:56:20 -05:00 committed by Marta Sokolska
parent fc4762b3f6
commit 1b1c65d5cc
5 changed files with 340 additions and 136 deletions

36
CARDS.md Normal file
View file

@ -0,0 +1,36 @@
# Card Development Guide for fz-tarot
This document explains how to add new cards (major or minor arcana) to the fz-tarot app for Flipper Zero.
## 1. Card Types
- **Major Arcana:** 22 cards (The Fool to The World), each with upright and reversed images.
- **Minor Arcana:** Four suits (Cups, Swords, Wands, Pentacles), each with Ace10, Page, Knight, Queen, King. Only upright images are currently used.
## 2. Adding Card Images
- Place 1-bit PNG images in the `images/` directory.
- Major arcana: Name images as `major_N.png` (upright) and `major_N_.png` (reversed), where N is 021.
- Minor arcana: Name images as `cups_1.png`, `swords_queen.png`, etc. (see below for naming convention).
- Images should be the same size as existing cards (see `images/major_0.png` for reference).
### Minor Arcana Naming Example
- Ace of Cups: `cups_1.png`
- 2 of Swords: `swords_2.png`
- Queen of Wands: `wands_queen.png`
- King of Pentacles: `pentacles_king.png`
## 3. Updating the Code
- Card definitions are in `cards.c` (see the `card` array).
- Add a new entry for each card, specifying its name and icon pointer (e.g., `&I_cups_1`).
- For new images, run the build system to regenerate the icon header (`tarot_icons.h`).
- If you add new cards, update any logic that uses the card array size or indices.
## 4. Adding Card Meanings (Optional)
- You can add a `const char* meaning` field to the `Card` struct for upright and reversed meanings.
- Update the deck browser and game scenes to display these meanings.
## 5. Testing
- Rebuild the app and use the deck browser to verify new cards and images appear correctly.
---
For questions or contributions, see the main [README.md](./README.md) or open an issue on GitHub.

View file

@ -23,3 +23,7 @@ Tarot spread for Flipper Zero. This app can give you a linear spread of the majo
## Credits
- Code: [pionaiki](https://github.com/pionaiki)
- Art: [tihyltew](https://github.com/tihyltew)
## Developer Guide
See [CARDS.md](./CARDS.md) for instructions on how to add new cards, images, and update the deck.

148
cards.c Normal file
View file

@ -0,0 +1,148 @@
#include <gui/icon_i.h>
#include "cards.h"
#include <tarot_icons.h>
#include <stdlib.h>
#include <stddef.h>
#include <furi_hal.h>
#include <furi_hal_subghz.h>
// Major Arcana count (upright only)
const int card_number = 22;
const Card card[] = {
// Major Arcana (upright)
{"The Fool", &I_major_0},
{"The Magician", &I_major_1},
{"The High Priestess", &I_major_2},
{"The Empress", &I_major_3},
{"The Emperor", &I_major_4},
{"The Hierophant", &I_major_5},
{"The Lovers", &I_major_6},
{"The Chariot", &I_major_7},
{"Strength", &I_major_8},
{"The Hermit", &I_major_9},
{"Wheel of Fortune", &I_major_10},
{"Justice", &I_major_11},
{"The Hanged Man", &I_major_12},
{"Death", &I_major_13},
{"Temperance", &I_major_14},
{"The Devil", &I_major_15},
{"The Tower", &I_major_16},
{"The Star", &I_major_17},
{"The Moon", &I_major_18},
{"The Sun", &I_major_19},
{"Judgement", &I_major_20},
{"The World", &I_major_21},
// Major Arcana (reversed)
{"The Fool", &I_major_0_},
{"The Magician", &I_major_1_},
{"The High Priestess", &I_major_2_},
{"The Empress", &I_major_3_},
{"The Emperor", &I_major_4_},
{"The Hierophant", &I_major_5_},
{"The Lovers", &I_major_6_},
{"The Chariot", &I_major_7_},
{"Strength", &I_major_8_},
{"The Hermit", &I_major_9_},
{"Wheel of Fortune", &I_major_10_},
{"Justice", &I_major_11_},
{"The Hanged Man", &I_major_12_},
{"Death", &I_major_13_},
{"Temperance", &I_major_14_},
{"The Devil", &I_major_15_},
{"The Tower", &I_major_16_},
{"The Star", &I_major_17_},
{"The Moon", &I_major_18_},
{"The Sun", &I_major_19_},
{"Judgement", &I_major_20_},
{"The World", &I_major_21_},
// Minor Arcana Placeholders (upright only, not used in spread)
// Cups
{"Ace of Cups", NULL},
{"2 of Cups", NULL},
{"3 of Cups", NULL},
{"4 of Cups", NULL},
{"5 of Cups", NULL},
{"6 of Cups", NULL},
{"7 of Cups", NULL},
{"8 of Cups", NULL},
{"9 of Cups", NULL},
{"10 of Cups", NULL},
{"Page of Cups", NULL},
{"Knight of Cups", NULL},
{"Queen of Cups", NULL},
{"King of Cups", NULL},
// Swords
{"Ace of Swords", NULL},
{"2 of Swords", NULL},
{"3 of Swords", NULL},
{"4 of Swords", NULL},
{"5 of Swords", NULL},
{"6 of Swords", NULL},
{"7 of Swords", NULL},
{"8 of Swords", NULL},
{"9 of Swords", NULL},
{"10 of Swords", NULL},
{"Page of Swords", NULL},
{"Knight of Swords", NULL},
{"Queen of Swords", NULL},
{"King of Swords", NULL},
// Wands
{"Ace of Wands", NULL},
{"2 of Wands", NULL},
{"3 of Wands", NULL},
{"4 of Wands", NULL},
{"5 of Wands", NULL},
{"6 of Wands", NULL},
{"7 of Wands", NULL},
{"8 of Wands", NULL},
{"9 of Wands", NULL},
{"10 of Wands", NULL},
{"Page of Wands", NULL},
{"Knight of Wands", NULL},
{"Queen of Wands", NULL},
{"King of Wands", NULL},
// Pentacles
{"Ace of Pentacles", NULL},
{"2 of Pentacles", NULL},
{"3 of Pentacles", NULL},
{"4 of Pentacles", NULL},
{"5 of Pentacles", NULL},
{"6 of Pentacles", NULL},
{"7 of Pentacles", NULL},
{"8 of Pentacles", NULL},
{"9 of Pentacles", NULL},
{"10 of Pentacles", NULL},
{"Page of Pentacles", NULL},
{"Knight of Pentacles", NULL},
{"Queen of Pentacles", NULL},
{"King of Pentacles", NULL}};
static uint32_t radio_noise_entropy() {
furi_hal_subghz_reset();
furi_hal_subghz_set_frequency(433920000); // ISM band
furi_hal_subghz_rx();
uint32_t entropy = 0;
for(int i = 0; i < 32; ++i) {
furi_delay_ms(2);
float rssi = furi_hal_subghz_get_rssi();
// Use the lowest bit of the mantissa for entropy
uint32_t bit = ((uint32_t)(rssi * 1000)) & 0x01;
entropy = (entropy << 1) ^ bit;
}
furi_hal_subghz_idle();
return entropy;
}
uint16_t unbiased_rand(uint16_t max) {
if(max == 0) return 0;
uint32_t x, remainder = UINT32_MAX % max;
do {
x = furi_hal_random_get() ^ radio_noise_entropy();
} while(x >= UINT32_MAX - remainder);
return x % max;
}
int get_total_card_count(void) {
return sizeof(card) / sizeof(card[0]);
}

20
cards.h Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
// Forward declaration for Icon type
struct Icon;
// Card struct
typedef struct {
const char name[32];
const struct Icon* icon;
} Card;
extern const Card card[];
extern const int card_number;
uint16_t unbiased_rand(uint16_t max);
// Returns the total number of cards in the deck (major + minor)
int get_total_card_count(void);

268
tarot.c
View file

@ -6,6 +6,7 @@
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include <gui/modules/widget.h>
#include "cards.h"
#define TAG "tarot"
@ -18,6 +19,7 @@ typedef enum {
AppScene_About,
AppScene_Game,
AppScene_Settings,
AppScene_DeckBrowser,
AppScene_count
} AppScene;
@ -42,7 +44,8 @@ typedef struct {
typedef enum {
AppEvent_ShowGame,
AppEvent_ShowAbout,
AppEvent_ShowSettings
AppEvent_ShowSettings,
AppEvent_ShowDeckBrowser
} AppEvent;
/* main menu scene */
@ -51,7 +54,8 @@ typedef enum {
typedef enum {
AppMenuSelection_Run,
AppMenuSelection_About,
AppMenuSelection_Settings
AppMenuSelection_Settings,
AppMenuSelection_BrowseDeck
} AppMenuSelection;
/* main menu callback - sends a custom event to the scene manager based on the menu selection */
@ -68,6 +72,9 @@ void tarot_app_menu_callback_main_menu(void* context, uint32_t index) {
case AppMenuSelection_Settings:
scene_manager_handle_custom_event(app->scene_manager, AppEvent_ShowSettings);
break;
case AppMenuSelection_BrowseDeck:
scene_manager_handle_custom_event(app->scene_manager, AppEvent_ShowDeckBrowser);
break;
}
}
@ -87,6 +94,12 @@ void tarot_app_scene_on_enter_main_menu(void* context) {
AppMenuSelection_Settings,
tarot_app_menu_callback_main_menu,
app);
submenu_add_item(
app->submenu,
"Browse Deck",
AppMenuSelection_BrowseDeck,
tarot_app_menu_callback_main_menu,
app);
view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Submenu);
}
@ -110,6 +123,10 @@ bool tarot_app_scene_on_event_main_menu(void* context, SceneManagerEvent event)
scene_manager_next_scene(app->scene_manager, AppScene_Settings);
consumed = true;
break;
case AppEvent_ShowDeckBrowser:
scene_manager_next_scene(app->scene_manager, AppScene_DeckBrowser);
consumed = true;
break;
}
break;
default: // eg. SceneManagerEventTypeBack, SceneManagerEventTypeTick
@ -159,135 +176,6 @@ void tarot_app_scene_on_exit_about(void* context) {
/* Game scene */
const int card_x = 23;
const int card_y = 32;
const int card_number = 22;
int card_selected = 0;
struct Card {
const char name[20];
const Icon* icon;
};
const struct Card card[] = {
// Major Arcana (upright)
{"The Fool", &I_major_0},
{"The Magician", &I_major_1},
{"The High Priestess", &I_major_2},
{"The Empress", &I_major_3},
{"The Emperor", &I_major_4},
{"The Hierophant", &I_major_5},
{"The Lovers", &I_major_6},
{"The Chariot", &I_major_7},
{"Strength", &I_major_8},
{"The Hermit", &I_major_9},
{"Wheel of Fortune", &I_major_10},
{"Justice", &I_major_11},
{"The Hanged Man", &I_major_12},
{"Death", &I_major_13},
{"Temperance", &I_major_14},
{"The Devil", &I_major_15},
{"The Tower", &I_major_16},
{"The Star", &I_major_17},
{"The Moon", &I_major_18},
{"The Sun", &I_major_19},
{"Judgement", &I_major_20},
{"The World", &I_major_21},
// Major Arcana (reversed)
{"The Fool", &I_major_0_},
{"The Magician", &I_major_1_},
{"The High Priestess", &I_major_2_},
{"The Empress", &I_major_3_},
{"The Emperor", &I_major_4_},
{"The Hierophant", &I_major_5_},
{"The Lovers", &I_major_6_},
{"The Chariot", &I_major_7_},
{"Strength", &I_major_8_},
{"The Hermit", &I_major_9_},
{"Wheel of Fortune", &I_major_10_},
{"Justice", &I_major_11_},
{"The Hanged Man", &I_major_12_},
{"Death", &I_major_13_},
{"Temperance", &I_major_14_},
{"The Devil", &I_major_15_},
{"The Tower", &I_major_16_},
{"The Star", &I_major_17_},
{"The Moon", &I_major_18_},
{"The Sun", &I_major_19_},
{"Judgement", &I_major_20_},
{"The World", &I_major_21_},
// Minor Arcana Placeholders (upright only, not used in spread)
// Cups
{"Ace of Cups", NULL},
{"2 of Cups", NULL},
{"3 of Cups", NULL},
{"4 of Cups", NULL},
{"5 of Cups", NULL},
{"6 of Cups", NULL},
{"7 of Cups", NULL},
{"8 of Cups", NULL},
{"9 of Cups", NULL},
{"10 of Cups", NULL},
{"Page of Cups", NULL},
{"Knight of Cups", NULL},
{"Queen of Cups", NULL},
{"King of Cups", NULL},
// Swords
{"Ace of Swords", NULL},
{"2 of Swords", NULL},
{"3 of Swords", NULL},
{"4 of Swords", NULL},
{"5 of Swords", NULL},
{"6 of Swords", NULL},
{"7 of Swords", NULL},
{"8 of Swords", NULL},
{"9 of Swords", NULL},
{"10 of Swords", NULL},
{"Page of Swords", NULL},
{"Knight of Swords", NULL},
{"Queen of Swords", NULL},
{"King of Swords", NULL},
// Wands
{"Ace of Wands", NULL},
{"2 of Wands", NULL},
{"3 of Wands", NULL},
{"4 of Wands", NULL},
{"5 of Wands", NULL},
{"6 of Wands", NULL},
{"7 of Wands", NULL},
{"8 of Wands", NULL},
{"9 of Wands", NULL},
{"10 of Wands", NULL},
{"Page of Wands", NULL},
{"Knight of Wands", NULL},
{"Queen of Wands", NULL},
{"King of Wands", NULL},
// Pentacles
{"Ace of Pentacles", NULL},
{"2 of Pentacles", NULL},
{"3 of Pentacles", NULL},
{"4 of Pentacles", NULL},
{"5 of Pentacles", NULL},
{"6 of Pentacles", NULL},
{"7 of Pentacles", NULL},
{"8 of Pentacles", NULL},
{"9 of Pentacles", NULL},
{"10 of Pentacles", NULL},
{"Page of Pentacles", NULL},
{"Knight of Pentacles", NULL},
{"Queen of Pentacles", NULL},
{"King of Pentacles", NULL}};
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;
}
struct Spread {
int card[3];
bool selected[3];
@ -295,6 +183,11 @@ struct Spread {
struct Spread spread;
// Card selection and card size globals
int card_selected = 0;
const int card_x = 23;
const int card_y = 32;
void draw_tarot(void* context) {
App* app = context;
widget_reset(app->widget);
@ -324,8 +217,7 @@ void draw_tarot(void* context) {
}
// Adjusted cursor position (moved 8px to the right)
widget_add_icon_element(
app->widget, x_offsets[card_selected] - 2 + card_x / 2, 41, &I_cursor);
widget_add_icon_element(app->widget, x_offsets[card_selected] - 2 + card_x / 2, 41, &I_cursor);
widget_add_string_element(
app->widget,
@ -418,26 +310,34 @@ void tarot_app_scene_on_enter_settings(void* context);
bool tarot_app_scene_on_event_settings(void* context, SceneManagerEvent event);
void tarot_app_scene_on_exit_settings(void* context);
// Forward declarations for deck browser scene
void tarot_app_scene_on_enter_deck_browser(void* context);
bool tarot_app_scene_on_event_deck_browser(void* context, SceneManagerEvent event);
void tarot_app_scene_on_exit_deck_browser(void* context);
/* 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,
tarot_app_scene_on_enter_settings};
tarot_app_scene_on_enter_settings,
tarot_app_scene_on_enter_deck_browser};
/* 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,
tarot_app_scene_on_event_settings};
tarot_app_scene_on_event_settings,
tarot_app_scene_on_event_deck_browser};
/* 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,
tarot_app_scene_on_exit_settings};
tarot_app_scene_on_exit_settings,
tarot_app_scene_on_exit_deck_browser};
/* collection of all on_enter, on_event, on_exit handlers */
const SceneManagerHandlers tarot_app_scene_event_handlers = {
@ -595,3 +495,99 @@ void tarot_app_scene_on_exit_settings(void* context) {
App* app = context;
submenu_reset(app->submenu);
}
// Deck browser state
static int deck_browser_index = 0;
static void draw_deck_browser(void* context) {
App* app = context;
widget_reset(app->widget);
// 22 majors (upright+reversed shown together) + 56 minors = 78 cards
int total = 78;
if(deck_browser_index < 0) deck_browser_index = 0;
if(deck_browser_index >= total) deck_browser_index = total - 1;
if(deck_browser_index < 22) {
// Major arcana: show upright and reversed side by side
int major_idx = deck_browser_index;
const Card* upright = &card[major_idx];
const Card* reversed = &card[major_idx + 22];
// Upright image
if(upright->icon) {
widget_add_icon_element(app->widget, 16, 10, upright->icon);
} else {
widget_add_string_element(
app->widget, 32, 24, AlignCenter, AlignTop, FontPrimary, "(no image)");
}
// Reversed image
if(reversed->icon) {
widget_add_icon_element(app->widget, 72, 10, reversed->icon);
widget_add_string_element(
app->widget, 72 + card_x / 2, 1, AlignCenter, AlignTop, FontPrimary, "R");
} else {
widget_add_string_element(
app->widget, 88, 24, AlignCenter, AlignTop, FontPrimary, "(no image)");
}
// Name (centered below both images)
widget_add_string_element(
app->widget, 64, 60, AlignCenter, AlignBottom, FontPrimary, upright->name);
} else {
// Minor arcana: upright only
const Card* c = &card[44 + (deck_browser_index - 22)];
if(c->icon) {
widget_add_icon_element(app->widget, (128 - card_x) / 2, 10, c->icon);
} else {
widget_add_string_element(
app->widget, 64, 24, AlignCenter, AlignTop, FontPrimary, "(no image)");
}
widget_add_string_element(
app->widget, 64, 60, AlignCenter, AlignBottom, FontPrimary, c->name);
}
// Card number
char idxbuf[16];
snprintf(idxbuf, sizeof(idxbuf), "%d/%d", deck_browser_index + 1, total);
widget_add_string_element(app->widget, 124, 2, AlignRight, AlignTop, FontSecondary, idxbuf);
}
static bool deck_browser_input_callback(InputEvent* input_event, void* context) {
App* app = context;
bool consumed = false;
int total = 78;
if(input_event->type == InputTypeShort) {
switch(input_event->key) {
case InputKeyRight:
deck_browser_index++;
if(deck_browser_index >= total) deck_browser_index = 0;
consumed = true;
break;
case InputKeyLeft:
deck_browser_index--;
if(deck_browser_index < 0) deck_browser_index = total - 1;
consumed = true;
break;
default:
break;
}
}
if(consumed) draw_deck_browser(app);
return consumed;
}
void tarot_app_scene_on_enter_deck_browser(void* context) {
App* app = context;
deck_browser_index = 0;
draw_deck_browser(app);
view_set_context(widget_get_view(app->widget), app);
view_set_input_callback(widget_get_view(app->widget), deck_browser_input_callback);
view_dispatcher_switch_to_view(app->view_dispatcher, AppView_Widget);
}
bool tarot_app_scene_on_event_deck_browser(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void tarot_app_scene_on_exit_deck_browser(void* context) {
App* app = context;
widget_reset(app->widget);
}