13 more apps done, 31 to go

This commit is contained in:
Willy-JL
2023-11-05 01:12:28 +00:00
parent 7087e5f2a7
commit bdf4a39530
180 changed files with 0 additions and 28311 deletions

View File

@@ -1,14 +0,0 @@
App(
appid="blackjack",
name="BlackJack",
apptype=FlipperAppType.EXTERNAL,
entry_point="blackjack_app",
requires=["gui", "storage", "canvas"],
stack_size=2 * 1024,
fap_icon="blackjack_10px.png",
fap_category="Games",
fap_icon_assets="assets",
fap_author="@teeebor",
fap_version="1.0",
fap_description="Blackjack Game",
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,634 +0,0 @@
#include <gui/gui.h>
#include <stdlib.h>
#include <dolphin/dolphin.h>
#include <dialogs/dialogs.h>
#include <gui/canvas_i.h>
#include <math.h>
#include "util.h"
#include "defines.h"
#include "common/card.h"
#include "common/dml.h"
#include "common/queue.h"
#include "util.h"
#include "ui.h"
#include "blackjack_icons.h"
#include <assets_icons.h>
#define DEALER_MAX 17
void start_round(GameState* game_state);
void init(GameState* game_state);
static void draw_ui(Canvas* const canvas, const GameState* game_state) {
draw_money(canvas, game_state->player_score);
draw_score(canvas, true, hand_count(game_state->player_cards, game_state->player_card_count));
if(!game_state->queue_state.running && game_state->state == GameStatePlay) {
render_menu(game_state->menu, canvas, 2, 47);
}
}
static void render_callback(Canvas* const canvas, void* ctx) {
furi_assert(ctx);
const GameState* game_state = ctx;
furi_mutex_acquire(game_state->mutex, FuriWaitForever);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 0, 0, 128, 64);
if(game_state->state == GameStateStart) {
canvas_draw_icon(canvas, 0, 0, &I_blackjack);
}
if(game_state->state == GameStateGameOver) {
canvas_draw_icon(canvas, 0, 0, &I_endscreen);
}
if(game_state->state == GameStatePlay || game_state->state == GameStateDealer) {
if(game_state->state == GameStatePlay)
draw_player_scene(canvas, game_state);
else
draw_dealer_scene(canvas, game_state);
render_queue(&(game_state->queue_state), game_state, canvas);
draw_ui(canvas, game_state);
} else if(game_state->state == GameStateSettings) {
settings_page(canvas, game_state);
}
furi_mutex_release(game_state->mutex);
}
//region card draw
Card draw_card(GameState* game_state) {
Card c = game_state->deck.cards[game_state->deck.index];
game_state->deck.index++;
return c;
}
void drawPlayerCard(void* ctx) {
GameState* game_state = ctx;
Card c = draw_card(game_state);
game_state->player_cards[game_state->player_card_count] = c;
game_state->player_card_count++;
if(game_state->player_score < game_state->settings.round_price || game_state->doubled) {
set_menu_state(game_state->menu, 0, false);
}
}
void drawDealerCard(void* ctx) {
GameState* game_state = ctx;
Card c = draw_card(game_state);
game_state->dealer_cards[game_state->dealer_card_count] = c;
game_state->dealer_card_count++;
}
//endregion
//region queue callbacks
void to_lose_state(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
if(game_state->settings.message_duration == 0) return;
popup_frame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost");
}
void to_bust_state(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
if(game_state->settings.message_duration == 0) return;
popup_frame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!");
}
void to_draw_state(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
if(game_state->settings.message_duration == 0) return;
popup_frame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw");
}
void to_dealer_turn(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
if(game_state->settings.message_duration == 0) return;
popup_frame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn");
}
void to_win_state(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
if(game_state->settings.message_duration == 0) return;
popup_frame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win");
}
void to_start(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
if(game_state->settings.message_duration == 0) return;
popup_frame(canvas);
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started");
}
void before_start(void* ctx) {
GameState* game_state = ctx;
game_state->dealer_card_count = 0;
game_state->player_card_count = 0;
}
void start(void* ctx) {
GameState* game_state = ctx;
start_round(game_state);
}
void draw(void* ctx) {
GameState* game_state = ctx;
game_state->player_score += game_state->bet;
game_state->bet = 0;
enqueue(
&(game_state->queue_state),
game_state,
start,
before_start,
to_start,
game_state->settings.message_duration);
}
void game_over(void* ctx) {
GameState* game_state = ctx;
game_state->state = GameStateGameOver;
}
void lose(void* ctx) {
GameState* game_state = ctx;
game_state->state = GameStatePlay;
game_state->bet = 0;
if(game_state->player_score >= game_state->settings.round_price) {
enqueue(
&(game_state->queue_state),
game_state,
start,
before_start,
to_start,
game_state->settings.message_duration);
} else {
enqueue(&(game_state->queue_state), game_state, game_over, NULL, NULL, 0);
}
}
void win(void* ctx) {
GameState* game_state = ctx;
game_state->state = GameStatePlay;
game_state->player_score += game_state->bet * 2;
game_state->bet = 0;
enqueue(
&(game_state->queue_state),
game_state,
start,
before_start,
to_start,
game_state->settings.message_duration);
}
void dealerTurn(void* ctx) {
GameState* game_state = ctx;
game_state->state = GameStateDealer;
}
float animationTime(const GameState* game_state) {
return (float)(furi_get_tick() - game_state->queue_state.start) /
(float)(game_state->settings.animation_duration);
}
void dealer_card_animation(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
float t = animationTime(game_state);
Card animatingCard = game_state->deck.cards[game_state->deck.index];
if(game_state->dealer_card_count > 1) {
Vector end = card_pos_at_index(game_state->dealer_card_count);
draw_card_animation(animatingCard, (Vector){0, 64}, (Vector){0, 32}, end, t, true, canvas);
} else {
draw_card_animation(
animatingCard,
(Vector){32, -CARD_HEIGHT},
(Vector){64, 32},
(Vector){2, 2},
t,
false,
canvas);
}
}
void dealer_back_card_animation(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
float t = animationTime(game_state);
Vector currentPos =
quadratic_2d((Vector){32, -CARD_HEIGHT}, (Vector){64, 32}, (Vector){13, 5}, t);
draw_card_back_at(currentPos.x, currentPos.y, canvas);
}
void player_card_animation(const void* ctx, Canvas* const canvas) {
const GameState* game_state = ctx;
float t = animationTime(game_state);
Card animatingCard = game_state->deck.cards[game_state->deck.index];
Vector end = card_pos_at_index(game_state->player_card_count);
draw_card_animation(
animatingCard, (Vector){32, -CARD_HEIGHT}, (Vector){0, 32}, end, t, true, canvas);
}
//endregion
void player_tick(GameState* game_state) {
uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count);
if((game_state->doubled && score <= 21) || score == 21) {
enqueue(
&(game_state->queue_state),
game_state,
dealerTurn,
NULL,
to_dealer_turn,
game_state->settings.message_duration);
} else if(score > 21) {
enqueue(
&(game_state->queue_state),
game_state,
lose,
NULL,
to_bust_state,
game_state->settings.message_duration);
} else {
if(game_state->selectDirection == DirectionUp ||
game_state->selectDirection == DirectionDown) {
move_menu(game_state->menu, game_state->selectDirection == DirectionUp ? -1 : 1);
}
if(game_state->selectDirection == Select) {
activate_menu(game_state->menu, game_state);
}
}
}
void dealer_tick(GameState* game_state) {
uint8_t dealer_score = hand_count(game_state->dealer_cards, game_state->dealer_card_count);
uint8_t player_score = hand_count(game_state->player_cards, game_state->player_card_count);
if(dealer_score >= DEALER_MAX) {
if(dealer_score > 21 || dealer_score < player_score) {
dolphin_deed(DolphinDeedPluginGameWin);
enqueue(
&(game_state->queue_state),
game_state,
win,
NULL,
to_win_state,
game_state->settings.message_duration);
} else if(dealer_score > player_score) {
enqueue(
&(game_state->queue_state),
game_state,
lose,
NULL,
to_lose_state,
game_state->settings.message_duration);
} else if(dealer_score == player_score) {
enqueue(
&(game_state->queue_state),
game_state,
draw,
NULL,
to_draw_state,
game_state->settings.message_duration);
}
} else {
enqueue(
&(game_state->queue_state),
game_state,
drawDealerCard,
NULL,
dealer_card_animation,
game_state->settings.animation_duration);
}
}
void settings_tick(GameState* game_state) {
if(game_state->selectDirection == DirectionDown && game_state->selectedMenu < 4) {
game_state->selectedMenu++;
}
if(game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) {
game_state->selectedMenu--;
}
if(game_state->selectDirection == DirectionLeft ||
game_state->selectDirection == DirectionRight) {
int nextScore = 0;
switch(game_state->selectedMenu) {
case 0:
nextScore = game_state->settings.starting_money;
if(game_state->selectDirection == DirectionLeft)
nextScore -= 10;
else
nextScore += 10;
if(nextScore >= (int)game_state->settings.round_price && nextScore < 400)
game_state->settings.starting_money = nextScore;
break;
case 1:
nextScore = game_state->settings.round_price;
if(game_state->selectDirection == DirectionLeft)
nextScore -= 10;
else
nextScore += 10;
if(nextScore >= 5 && nextScore <= (int)game_state->settings.starting_money)
game_state->settings.round_price = nextScore;
break;
case 2:
nextScore = game_state->settings.animation_duration;
if(game_state->selectDirection == DirectionLeft)
nextScore -= 100;
else
nextScore += 100;
if(nextScore >= 0 && nextScore < 2000)
game_state->settings.animation_duration = nextScore;
break;
case 3:
nextScore = game_state->settings.message_duration;
if(game_state->selectDirection == DirectionLeft)
nextScore -= 100;
else
nextScore += 100;
if(nextScore >= 0 && nextScore < 2000)
game_state->settings.message_duration = nextScore;
break;
case 4:
game_state->settings.sound_effects = !game_state->settings.sound_effects;
default:
break;
}
}
}
void tick(GameState* game_state) {
game_state->last_tick = furi_get_tick();
bool queue_ran = run_queue(&(game_state->queue_state), game_state);
switch(game_state->state) {
case GameStateGameOver:
case GameStateStart:
if(game_state->selectDirection == Select)
init(game_state);
else if(game_state->selectDirection == DirectionRight) {
game_state->selectedMenu = 0;
game_state->state = GameStateSettings;
}
break;
case GameStatePlay:
if(!game_state->started) {
game_state->selectedMenu = 0;
game_state->started = true;
enqueue(
&(game_state->queue_state),
game_state,
drawDealerCard,
NULL,
dealer_back_card_animation,
game_state->settings.animation_duration);
enqueue(
&(game_state->queue_state),
game_state,
drawPlayerCard,
NULL,
player_card_animation,
game_state->settings.animation_duration);
enqueue(
&(game_state->queue_state),
game_state,
drawDealerCard,
NULL,
dealer_card_animation,
game_state->settings.animation_duration);
enqueue(
&(game_state->queue_state),
game_state,
drawPlayerCard,
NULL,
player_card_animation,
game_state->settings.animation_duration);
}
if(!queue_ran) player_tick(game_state);
break;
case GameStateDealer:
if(!queue_ran) dealer_tick(game_state);
break;
case GameStateSettings:
settings_tick(game_state);
break;
default:
break;
}
game_state->selectDirection = None;
}
void start_round(GameState* game_state) {
game_state->menu->current_menu = 1;
game_state->player_card_count = 0;
game_state->dealer_card_count = 0;
set_menu_state(game_state->menu, 0, true);
game_state->menu->enabled = true;
game_state->started = false;
game_state->doubled = false;
game_state->queue_state.running = true;
shuffle_deck(&(game_state->deck));
game_state->doubled = false;
game_state->bet = game_state->settings.round_price;
if(game_state->player_score < game_state->settings.round_price) {
game_state->state = GameStateGameOver;
} else {
game_state->player_score -= game_state->settings.round_price;
}
game_state->state = GameStatePlay;
}
void init(GameState* game_state) {
set_menu_state(game_state->menu, 0, true);
game_state->menu->enabled = true;
game_state->menu->current_menu = 1;
game_state->settings = load_settings();
game_state->last_tick = 0;
game_state->processing = true;
game_state->selectedMenu = 0;
game_state->player_score = game_state->settings.starting_money;
generate_deck(&(game_state->deck), 6);
start_round(game_state);
}
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
AppEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void update_timer_callback(FuriMessageQueue* event_queue) {
furi_assert(event_queue);
AppEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
void doubleAction(void* state) {
GameState* game_state = state;
if(!game_state->doubled && game_state->player_score >= game_state->settings.round_price) {
game_state->player_score -= game_state->settings.round_price;
game_state->bet += game_state->settings.round_price;
game_state->doubled = true;
enqueue(
&(game_state->queue_state),
game_state,
drawPlayerCard,
NULL,
player_card_animation,
game_state->settings.animation_duration);
game_state->player_cards[game_state->player_card_count] =
game_state->deck.cards[game_state->deck.index];
uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count + 1);
if(score > 21) {
enqueue(
&(game_state->queue_state),
game_state,
lose,
NULL,
to_bust_state,
game_state->settings.message_duration);
} else {
enqueue(
&(game_state->queue_state),
game_state,
dealerTurn,
NULL,
to_dealer_turn,
game_state->settings.message_duration);
}
set_menu_state(game_state->menu, 0, false);
}
}
void hitAction(void* state) {
GameState* game_state = state;
enqueue(
&(game_state->queue_state),
game_state,
drawPlayerCard,
NULL,
player_card_animation,
game_state->settings.animation_duration);
}
void stayAction(void* state) {
GameState* game_state = state;
enqueue(
&(game_state->queue_state),
game_state,
dealerTurn,
NULL,
to_dealer_turn,
game_state->settings.message_duration);
}
int32_t blackjack_app(void* p) {
UNUSED(p);
int32_t return_code = 0;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
GameState* game_state = malloc(sizeof(GameState));
game_state->menu = malloc(sizeof(Menu));
game_state->menu->menu_width = 40;
init(game_state);
add_menu(game_state->menu, "Double", doubleAction);
add_menu(game_state->menu, "Hit", hitAction);
add_menu(game_state->menu, "Stay", stayAction);
set_card_graphics(&I_card_graphics);
game_state->state = GameStateStart;
game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!game_state->mutex) {
FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
return_code = 255;
goto free_and_exit;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, render_callback, game_state);
view_port_input_callback_set(view_port, input_callback, event_queue);
FuriTimer* timer = furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
AppEvent event;
// Call dolphin deed on game start
dolphin_deed(DolphinDeedPluginGameStart);
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
furi_mutex_acquire(game_state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
if(event.type == EventTypeKey) {
if(event.input.type == InputTypePress) {
switch(event.input.key) {
case InputKeyUp:
game_state->selectDirection = DirectionUp;
break;
case InputKeyDown:
game_state->selectDirection = DirectionDown;
break;
case InputKeyRight:
game_state->selectDirection = DirectionRight;
break;
case InputKeyLeft:
game_state->selectDirection = DirectionLeft;
break;
case InputKeyBack:
if(game_state->state == GameStateSettings) {
game_state->state = GameStateStart;
save_settings(game_state->settings);
} else
processing = false;
break;
case InputKeyOk:
game_state->selectDirection = Select;
break;
default:
break;
}
}
} else if(event.type == EventTypeTick) {
tick(game_state);
processing = game_state->processing;
}
}
view_port_update(view_port);
furi_mutex_release(game_state->mutex);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_mutex_free(game_state->mutex);
free_and_exit:
free(game_state->deck.cards);
free_menu(game_state->menu);
queue_clear(&(game_state->queue_state));
free(game_state);
furi_message_queue_free(event_queue);
return return_code;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

View File

@@ -1,353 +0,0 @@
#include "card.h"
#include "dml.h"
#include "ui.h"
#define CARD_DRAW_X_START 108
#define CARD_DRAW_Y_START 38
#define CARD_DRAW_X_SPACE 10
#define CARD_DRAW_Y_SPACE 8
#define CARD_DRAW_X_OFFSET 4
#define CARD_DRAW_FIRST_ROW_LENGTH 7
uint8_t pips[4][3] = {
{21, 10, 7}, //spades
{7, 10, 7}, //hearts
{0, 10, 7}, //diamonds
{14, 10, 7}, //clubs
};
uint8_t letters[13][3] = {
{0, 0, 5},
{5, 0, 5},
{10, 0, 5},
{15, 0, 5},
{20, 0, 5},
{25, 0, 5},
{30, 0, 5},
{0, 5, 5},
{5, 5, 5},
{10, 5, 5},
{15, 5, 5},
{20, 5, 5},
{25, 5, 5},
};
//region Player card positions
uint8_t playerCardPositions[22][4] = {
//first row
{108, 38},
{98, 38},
{88, 38},
{78, 38},
{68, 38},
{58, 38},
{48, 38},
{38, 38},
//second row
{104, 26},
{94, 26},
{84, 26},
{74, 26},
{64, 26},
{54, 26},
{44, 26},
//third row
{99, 14},
{89, 14},
{79, 14},
{69, 14},
{59, 14},
{49, 14},
};
//endregion
Icon* card_graphics = NULL;
void set_card_graphics(const Icon* graphics) {
card_graphics = (Icon*)graphics;
}
void draw_card_at_colored(
int8_t pos_x,
int8_t pos_y,
uint8_t pip,
uint8_t character,
bool inverted,
Canvas* const canvas) {
DrawMode primary = inverted ? Black : White;
DrawMode secondary = inverted ? White : Black;
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, primary);
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
uint8_t* drawInfo = pips[pip];
uint8_t px = drawInfo[0], py = drawInfo[1], s = drawInfo[2];
uint8_t left = pos_x + 2;
uint8_t right = (pos_x + CARD_WIDTH - s - 2);
uint8_t top = pos_y + 2;
uint8_t bottom = (pos_y + CARD_HEIGHT - s - 2);
draw_icon_clip(canvas, card_graphics, right, top, px, py, s, s, secondary);
draw_icon_clip_flipped(canvas, card_graphics, left, bottom, px, py, s, s, secondary);
drawInfo = letters[character];
px = drawInfo[0], py = drawInfo[1], s = drawInfo[2];
left = pos_x + 2;
right = (pos_x + CARD_WIDTH - s - 2);
top = pos_y + 2;
bottom = (pos_y + CARD_HEIGHT - s - 2);
draw_icon_clip(canvas, card_graphics, left, top + 1, px, py, s, s, secondary);
draw_icon_clip_flipped(canvas, card_graphics, right, bottom - 1, px, py, s, s, secondary);
}
void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas) {
draw_card_at_colored(pos_x, pos_y, pip, character, false, canvas);
}
void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas) {
for(int i = count - 1; i >= 0; i--) {
draw_card_at(
playerCardPositions[i][0],
playerCardPositions[i][1],
cards[i].pip,
cards[i].character,
canvas);
}
}
Vector card_pos_at_index(uint8_t index) {
return (Vector){playerCardPositions[index][0], playerCardPositions[index][1]};
}
void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas) {
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, White);
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
draw_icon_clip(canvas, card_graphics, pos_x + 1, pos_y + 1, 35, 0, 15, 21, Black);
}
void generate_deck(Deck* deck_ptr, uint8_t deck_count) {
uint16_t counter = 0;
if(deck_ptr->cards != NULL) {
free(deck_ptr->cards);
}
deck_ptr->deck_count = deck_count;
deck_ptr->card_count = deck_count * 52;
deck_ptr->cards = malloc(sizeof(Card) * deck_ptr->card_count);
for(uint8_t deck = 0; deck < deck_count; deck++) {
for(uint8_t pip = 0; pip < 4; pip++) {
for(uint8_t label = 0; label < 13; label++) {
deck_ptr->cards[counter] = (Card){pip, label, false, false};
counter++;
}
}
}
}
void shuffle_deck(Deck* deck_ptr) {
srand(DWT->CYCCNT);
deck_ptr->index = 0;
int max = deck_ptr->deck_count * 52;
for(int i = 0; i < max; i++) {
int r = i + (rand() % (max - i));
Card tmp = deck_ptr->cards[i];
deck_ptr->cards[i] = deck_ptr->cards[r];
deck_ptr->cards[r] = tmp;
}
}
uint8_t hand_count(const Card* cards, uint8_t count) {
uint8_t aceCount = 0;
uint8_t score = 0;
for(uint8_t i = 0; i < count; i++) {
if(cards[i].character == 12)
aceCount++;
else {
if(cards[i].character > 8)
score += 10;
else
score += cards[i].character + 2;
}
}
for(uint8_t i = 0; i < aceCount; i++) {
if((score + 11 + (aceCount - 1)) <= 21)
score += 11;
else
score++;
}
return score;
}
void draw_card_animation(
Card animatingCard,
Vector from,
Vector control,
Vector to,
float t,
bool extra_margin,
Canvas* const canvas) {
float time = t;
if(extra_margin) {
time += 0.2;
}
Vector currentPos = quadratic_2d(from, control, to, time);
if(t > 1) {
draw_card_at(
currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas);
} else {
if(t < 0.5)
draw_card_back_at(currentPos.x, currentPos.y, canvas);
else
draw_card_at(
currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas);
}
}
void init_hand(Hand* hand_ptr, uint8_t count) {
hand_ptr->cards = malloc(sizeof(Card) * count);
hand_ptr->index = 0;
hand_ptr->max = count;
}
void free_hand(Hand* hand_ptr) {
FURI_LOG_D("CARD", "Freeing hand");
free(hand_ptr->cards);
}
void add_to_hand(Hand* hand_ptr, Card card) {
FURI_LOG_D("CARD", "Adding to hand");
if(hand_ptr->index < hand_ptr->max) {
hand_ptr->cards[hand_ptr->index] = card;
hand_ptr->index++;
}
}
void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas) {
if(highlighted) {
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
draw_rounded_box_frame(
canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White);
} else {
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
draw_rounded_box_frame(
canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White);
}
}
int first_non_flipped_card(Hand hand) {
for(int i = 0; i < hand.index; i++) {
if(!hand.cards[i].flipped) {
return i;
}
}
return hand.index;
}
void draw_hand_column(
Hand hand,
int16_t pos_x,
int16_t pos_y,
int8_t highlight,
Canvas* const canvas) {
if(hand.index == 0) {
draw_card_space(pos_x, pos_y, highlight > 0, canvas);
if(highlight == 0)
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Inverse);
return;
}
int loopEnd = hand.index;
int hStart = max(loopEnd - 4, 0);
int pos = 0;
int first = first_non_flipped_card(hand);
bool wastop = false;
if(first >= 0 && first <= hStart && highlight != first) {
if(first > 0) {
draw_card_back_at(pos_x, pos_y + pos, canvas);
pos += 4;
hStart++;
wastop = true;
}
draw_card_at_colored(
pos_x, pos_y + pos, hand.cards[first].pip, hand.cards[first].character, false, canvas);
pos += 8;
hStart++;
}
if(hStart > highlight && highlight >= 0) {
if(!wastop && first > 0) {
draw_card_back_at(pos_x, pos_y + pos, canvas);
pos += 4;
hStart++;
}
draw_card_at_colored(
pos_x,
pos_y + pos,
hand.cards[highlight].pip,
hand.cards[highlight].character,
true,
canvas);
pos += 8;
hStart++;
}
for(int i = hStart; i < loopEnd; i++, pos += 4) {
if(hand.cards[i].flipped) {
draw_card_back_at(pos_x, pos_y + pos, canvas);
if(i == highlight)
draw_rounded_box(
canvas, pos_x + 1, pos_y + pos + 1, CARD_WIDTH - 2, CARD_HEIGHT - 2, Inverse);
} else {
draw_card_at_colored(
pos_x,
pos_y + pos,
hand.cards[i].pip,
hand.cards[i].character,
(i == highlight),
canvas);
if(i == highlight || i == first) pos += 4;
}
}
}
Card remove_from_deck(uint16_t index, Deck* deck) {
FURI_LOG_D("CARD", "Removing from deck");
Card result = {0, 0, true, false};
if(deck->card_count > 0) {
deck->card_count--;
for(int i = 0, curr_index = 0; i <= deck->card_count; i++) {
if(i != index) {
deck->cards[curr_index] = deck->cards[i];
curr_index++;
} else {
result = deck->cards[i];
}
}
if(deck->index >= 0) {
deck->index--;
}
}
return result;
}
void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index) {
FURI_LOG_D("CARD", "Extracting hand region");
if(start_index >= hand->index) return;
for(uint8_t i = start_index; i < hand->index; i++) {
add_to_hand(to, hand->cards[i]);
}
hand->index = start_index;
}
void add_hand_region(Hand* to, Hand* from) {
FURI_LOG_D("CARD", "Adding hand region");
if((to->index + from->index) <= to->max) {
for(int i = 0; i < from->index; i++) {
add_to_hand(to, from->cards[i]);
}
}
}

View File

@@ -1,192 +0,0 @@
#pragma once
#include <gui/gui.h>
#include <math.h>
#include <stdlib.h>
#include "dml.h"
#define CARD_HEIGHT 23
#define CARD_HALF_HEIGHT 11
#define CARD_WIDTH 17
#define CARD_HALF_WIDTH 8
//region types
typedef struct {
uint8_t pip; //Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
uint8_t character; //Card letter [0-12], 0 means 2, 12 is Ace
bool disabled;
bool flipped;
} Card;
typedef struct {
uint8_t deck_count; //Number of decks used
Card* cards; //Cards in the deck
int card_count;
int index; //Card index (to know where we at in the deck)
} Deck;
typedef struct {
Card* cards; //Cards in the deck
uint8_t index; //Current index
uint8_t max; //How many cards we want to store
} Hand;
//endregion
void set_card_graphics(const Icon* graphics);
/**
* Gets card coordinates at the index (range: 0-20).
*
* @param index Index to check 0-20
* @return Position of the card
*/
Vector card_pos_at_index(uint8_t index);
/**
* Draws card at a given coordinate (top-left corner)
*
* @param pos_x X position
* @param pos_y Y position
* @param pip Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
* @param character Letter [0-12] 0 is 2, 12 is A
* @param canvas Pointer to Flipper's canvas object
*/
void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas);
/**
* Draws card at a given coordinate (top-left corner)
*
* @param pos_x X position
* @param pos_y Y position
* @param pip Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
* @param character Letter [0-12] 0 is 2, 12 is A
* @param inverted Invert colors
* @param canvas Pointer to Flipper's canvas object
*/
void draw_card_at_colored(
int8_t pos_x,
int8_t pos_y,
uint8_t pip,
uint8_t character,
bool inverted,
Canvas* const canvas);
/**
* Draws 'count' cards at the bottom right corner
*
* @param cards List of cards
* @param count Count of cards
* @param canvas Pointer to Flipper's canvas object
*/
void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas);
/**
* Draws card back at a given coordinate (top-left corner)
*
* @param pos_x X coordinate
* @param pos_y Y coordinate
* @param canvas Pointer to Flipper's canvas object
*/
void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas);
/**
* Generates the deck
*
* @param deck_ptr Pointer to the deck
* @param deck_count Number of decks
*/
void generate_deck(Deck* deck_ptr, uint8_t deck_count);
/**
* Shuffles the deck
*
* @param deck_ptr Pointer to the deck
*/
void shuffle_deck(Deck* deck_ptr);
/**
* Calculates the hand count for blackjack
*
* @param cards List of cards
* @param count Count of cards
* @return Hand value
*/
uint8_t hand_count(const Card* cards, uint8_t count);
/**
* Draws card animation
*
* @param animatingCard Card to animate
* @param from Starting position
* @param control Quadratic lerp control point
* @param to End point
* @param t Current time (0-1)
* @param extra_margin Use extra margin at the end (arrives 0.2 unit before the end so it can stay there a bit)
* @param canvas Pointer to Flipper's canvas object
*/
void draw_card_animation(
Card animatingCard,
Vector from,
Vector control,
Vector to,
float t,
bool extra_margin,
Canvas* const canvas);
/**
* Init hand pointer
* @param hand_ptr Pointer to hand
* @param count Number of cards we want to store
*/
void init_hand(Hand* hand_ptr, uint8_t count);
/**
* Free hand resources
* @param hand_ptr Pointer to hand
*/
void free_hand(Hand* hand_ptr);
/**
* Add card to hand
* @param hand_ptr Pointer to hand
* @param card Card to add
*/
void add_to_hand(Hand* hand_ptr, Card card);
/**
* Draw card placement position at coordinate
* @param pos_x X coordinate
* @param pos_y Y coordinate
* @param highlighted Apply highlight effect
* @param canvas Canvas object
*/
void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas);
/**
* Draws a column of card, displaying the last [max_cards] cards on the list
* @param hand Hand object
* @param pos_x X coordinate to draw
* @param pos_y Y coordinate to draw
* @param highlight Index to highlight, negative means no highlight
* @param canvas Canvas object
*/
void draw_hand_column(
Hand hand,
int16_t pos_x,
int16_t pos_y,
int8_t highlight,
Canvas* const canvas);
/**
* Removes a card from the deck (Be aware, if you remove the first item, the deck index will be at -1 so you have to handle that)
* @param index Index to remove
* @param deck Deck reference
* @return The removed card
*/
Card remove_from_deck(uint16_t index, Deck* deck);
int first_non_flipped_card(Hand hand);
void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index);
void add_hand_region(Hand* to, Hand* from);

View File

@@ -1,53 +0,0 @@
#include "dml.h"
#include <math.h>
float lerp(float v0, float v1, float t) {
if(t > 1) return v1;
return (1 - t) * v0 + t * v1;
}
Vector lerp_2d(Vector start, Vector end, float t) {
return (Vector){
lerp(start.x, end.x, t),
lerp(start.y, end.y, t),
};
}
Vector quadratic_2d(Vector start, Vector control, Vector end, float t) {
return lerp_2d(lerp_2d(start, control, t), lerp_2d(control, end, t), t);
}
Vector vector_add(Vector a, Vector b) {
return (Vector){a.x + b.x, a.y + b.y};
}
Vector vector_sub(Vector a, Vector b) {
return (Vector){a.x - b.x, a.y - b.y};
}
Vector vector_mul_components(Vector a, Vector b) {
return (Vector){a.x * b.x, a.y * b.y};
}
Vector vector_div_components(Vector a, Vector b) {
return (Vector){a.x / b.x, a.y / b.y};
}
Vector vector_normalized(Vector a) {
float length = vector_magnitude(a);
return (Vector){a.x / length, a.y / length};
}
float vector_magnitude(Vector a) {
return sqrt(a.x * a.x + a.y * a.y);
}
float vector_distance(Vector a, Vector b) {
return vector_magnitude(vector_sub(a, b));
}
float vector_dot(Vector a, Vector b) {
Vector _a = vector_normalized(a);
Vector _b = vector_normalized(b);
return _a.x * _b.x + _a.y * _b.y;
}

View File

@@ -1,116 +0,0 @@
//
// Doofy's Math library
//
#pragma once
typedef struct {
float x;
float y;
} Vector;
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define abs(x) ((x) > 0 ? (x) : -(x))
/**
* Lerp function
*
* @param v0 Start value
* @param v1 End value
* @param t Time (0-1 range)
* @return Point between v0-v1 at a given time
*/
float lerp(float v0, float v1, float t);
/**
* 2D lerp function
*
* @param start Start vector
* @param end End vector
* @param t Time (0-1 range)
* @return 2d Vector between start and end at time
*/
Vector lerp_2d(Vector start, Vector end, float t);
/**
* Quadratic lerp function
*
* @param start Start vector
* @param control Control point
* @param end End vector
* @param t Time (0-1 range)
* @return 2d Vector at time
*/
Vector quadratic_2d(Vector start, Vector control, Vector end, float t);
/**
* Add vector components together
*
* @param a First vector
* @param b Second vector
* @return Resulting vector
*/
Vector vector_add(Vector a, Vector b);
/**
* Subtract vector components together
*
* @param a First vector
* @param b Second vector
* @return Resulting vector
*/
Vector vector_sub(Vector a, Vector b);
/**
* Multiplying vector components together
*
* @param a First vector
* @param b Second vector
* @return Resulting vector
*/
Vector vector_mul_components(Vector a, Vector b);
/**
* Dividing vector components
*
* @param a First vector
* @param b Second vector
* @return Resulting vector
*/
Vector vector_div_components(Vector a, Vector b);
/**
* Calculating Vector length
*
* @param a Direction vector
* @return Length of the vector
*/
float vector_magnitude(Vector a);
/**
* Get a normalized vector (length of 1)
*
* @param a Direction vector
* @return Normalized vector
*/
Vector vector_normalized(Vector a);
/**
* Calculate two vector's distance
*
* @param a First vector
* @param b Second vector
* @return Distance between vectors
*/
float vector_distance(Vector a, Vector b);
/**
* Calculate the dot product of the vectors.
* No need to normalize, it will do it
*
* @param a First vector
* @param b Second vector
* @return value from -1 to 1
*/
float vector_dot(Vector a, Vector b);

View File

@@ -1,103 +0,0 @@
#include "menu.h"
void add_menu(Menu* menu, const char* name, void (*callback)(void*)) {
MenuItem* items = menu->items;
menu->items = malloc(sizeof(MenuItem) * (menu->menu_count + 1));
for(uint8_t i = 0; i < menu->menu_count; i++) {
menu->items[i] = items[i];
}
free(items);
menu->items[menu->menu_count] = (MenuItem){name, true, callback};
menu->menu_count++;
}
void free_menu(Menu* menu) {
free(menu->items);
free(menu);
}
void set_menu_state(Menu* menu, uint8_t index, bool state) {
if(menu->menu_count > index) {
menu->items[index].enabled = state;
}
if(!state && menu->current_menu == index) move_menu(menu, 1);
}
void move_menu(Menu* menu, int8_t direction) {
if(!menu->enabled) return;
int max = menu->menu_count;
for(int8_t i = 0; i < max; i++) {
FURI_LOG_D(
"MENU",
"Iteration %i, current %i, direction %i, state %i",
i,
menu->current_menu,
direction,
menu->items[menu->current_menu].enabled ? 1 : 0);
if(direction < 0 && menu->current_menu == 0) {
menu->current_menu = menu->menu_count - 1;
} else {
menu->current_menu = (menu->current_menu + direction) % menu->menu_count;
}
FURI_LOG_D(
"MENU",
"After process current %i, direction %i, state %i",
menu->current_menu,
direction,
menu->items[menu->current_menu].enabled ? 1 : 0);
if(menu->items[menu->current_menu].enabled) {
FURI_LOG_D("MENU", "Next menu %i", menu->current_menu);
return;
}
}
FURI_LOG_D("MENU", "Not found, setting false");
menu->enabled = false;
}
void activate_menu(Menu* menu, void* state) {
if(!menu->enabled) return;
menu->items[menu->current_menu].callback(state);
}
void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y) {
if(!menu->enabled) return;
canvas_set_color(canvas, ColorWhite);
canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2);
uint8_t w = pos_x + menu->menu_width;
uint8_t h = pos_y + 10;
uint8_t p1x = pos_x + 2;
uint8_t p2x = pos_x + menu->menu_width - 2;
uint8_t p1y = pos_y + 2;
uint8_t p2y = pos_y + 8;
canvas_set_color(canvas, ColorBlack);
canvas_draw_line(canvas, p1x, pos_y, p2x, pos_y);
canvas_draw_line(canvas, p1x, h, p2x, h);
canvas_draw_line(canvas, pos_x, p1y, pos_x, p2y);
canvas_draw_line(canvas, w, p1y, w, p2y);
canvas_draw_dot(canvas, pos_x + 1, pos_y + 1);
canvas_draw_dot(canvas, w - 1, pos_y + 1);
canvas_draw_dot(canvas, w - 1, h - 1);
canvas_draw_dot(canvas, pos_x + 1, h - 1);
// canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(
canvas,
pos_x + menu->menu_width / 2,
pos_y + 6,
AlignCenter,
AlignCenter,
menu->items[menu->current_menu].name);
//9*5
int center = pos_x + menu->menu_width / 2;
for(uint8_t i = 0; i < 4; i++) {
for(int8_t j = -i; j <= i; j++) {
canvas_draw_dot(canvas, center + j, pos_y - 4 + i);
canvas_draw_dot(canvas, center + j, pos_y + 14 - i);
}
}
}

View File

@@ -1,77 +0,0 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
typedef struct {
const char* name; //Name of the menu
bool enabled; //Is the menu item enabled (it will not render, you cannot select it)
void (*callback)(
void* state); //Callback for when the activate_menu is called while this menu is selected
} MenuItem;
typedef struct {
MenuItem* items; //list of menu items
uint8_t menu_count; //count of menu items (do not change)
uint8_t current_menu; //currently selected menu item
uint8_t menu_width; //width of the menu
bool enabled; //is the menu enabled (it will not render and accept events when disabled)
} Menu;
/**
* Cleans up the pointers used by the menu
*
* @param menu Pointer of the menu to clean up
*/
void free_menu(Menu* menu);
/**
* Add a new menu item
*
* @param menu Pointer of the menu
* @param name Name of the menu item
* @param callback Callback called on activation
*/
void add_menu(Menu* menu, const char* name, void (*callback)(void*));
/**
* Setting menu item to be enabled/disabled
*
* @param menu Pointer of the menu
* @param index Menu index to set
* @param state Enabled (true), Disabled(false)
*/
void set_menu_state(Menu* menu, uint8_t index, bool state);
/**
* Moves selection up or down
*
* @param menu Pointer of the menu
* @param direction Direction to move -1 down, 1 up
*/
void move_menu(Menu* menu, int8_t direction);
/**
* Triggers the current menu callback
*
* @param menu Pointer of the menu
* @param state Usually your application state
*/
void activate_menu(Menu* menu, void* state);
/**
* Renders the menu at a coordinate (call it in your render function).
*
* Keep in mind that Flipper has a 128x64 pixel screen resolution and the coordinate
* you give is the menu's rectangle top-left corner (arrows not included).
* The rectangle height is 10 px, the arrows have a 4 pixel height. Space needed is 18px.
* The width of the menu can be configured in the menu object.
*
*
* @param menu Pointer of the menu
* @param canvas Flippers Canvas pointer
* @param pos_x X position to draw
* @param pos_y Y position to draw
*/
void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y);

View File

@@ -1,69 +0,0 @@
#include "queue.h"
void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas) {
if(queue_state->current != NULL && queue_state->current->render != NULL)
((QueueItem*)queue_state->current)->render(app_state, canvas);
}
bool run_queue(QueueState* queue_state, void* app_state) {
if(queue_state->current != NULL) {
queue_state->running = true;
if((furi_get_tick() - queue_state->start) >= queue_state->current->duration)
dequeue(queue_state, app_state);
return true;
}
return false;
}
void dequeue(QueueState* queue_state, void* app_state) {
((QueueItem*)queue_state->current)->callback(app_state);
QueueItem* f = queue_state->current;
queue_state->current = f->next;
free(f);
if(queue_state->current != NULL) {
if(queue_state->current->start != NULL) queue_state->current->start(app_state);
queue_state->start = furi_get_tick();
} else {
queue_state->running = false;
}
}
void queue_clear(QueueState* queue_state) {
queue_state->running = false;
QueueItem* curr = queue_state->current;
while(curr != NULL) {
QueueItem* f = curr;
curr = curr->next;
free(f);
}
}
void enqueue(
QueueState* queue_state,
void* app_state,
void (*done)(void* state),
void (*start)(void* state),
void (*render)(const void* state, Canvas* const canvas),
uint32_t duration) {
QueueItem* next;
if(queue_state->current == NULL) {
queue_state->start = furi_get_tick();
queue_state->current = malloc(sizeof(QueueItem));
next = queue_state->current;
if(next->start != NULL) next->start(app_state);
} else {
next = queue_state->current;
while(next->next != NULL) {
next = (QueueItem*)(next->next);
}
next->next = malloc(sizeof(QueueItem));
next = next->next;
}
next->callback = done;
next->render = render;
next->start = start;
next->duration = duration;
next->next = NULL;
}

View File

@@ -1,70 +0,0 @@
#pragma once
#include <gui/gui.h>
#include <furi.h>
typedef struct {
void (*callback)(void* state); //Callback for when the item is dequeued
void (*render)(
const void* state,
Canvas* const canvas); //Callback for the rendering loop while this item is running
void (*start)(void* state); //Callback when this item is started running
void* next; //Pointer to the next item
uint32_t duration; //duration of the item
} QueueItem;
typedef struct {
unsigned int start; //current queue item start time
QueueItem* current; //current queue item
bool running; //is the queue running
} QueueState;
/**
* Enqueue a new item.
*
* @param queue_state The queue state pointer
* @param app_state Your app state
* @param done Callback for dequeue event
* @param start Callback for when the item is activated
* @param render Callback to render loop if needed
* @param duration Length of the item
*/
void enqueue(
QueueState* queue_state,
void* app_state,
void (*done)(void* state),
void (*start)(void* state),
void (*render)(const void* state, Canvas* const canvas),
uint32_t duration);
/**
* Clears all queue items
*
* @param queue_state The queue state pointer
*/
void queue_clear(QueueState* queue_state);
/**
* Dequeues the active queue item. Usually you don't need to call it directly.
*
* @param queue_state The queue state pointer
* @param app_state Your application state
*/
void dequeue(QueueState* queue_state, void* app_state);
/**
* Runs the queue logic (place it in your tick function)
*
* @param queue_state The queue state pointer
* @param app_state Your application state
* @return FALSE when there is nothing to run, TRUE otherwise
*/
bool run_queue(QueueState* queue_state, void* app_state);
/**
* Calls the currently active queue items render callback (if there is any)
*
* @param queue_state The queue state pointer
* @param app_state Your application state
* @param canvas Pointer to Flipper's canvas object
*/
void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas);

View File

@@ -1,257 +0,0 @@
#include "ui.h"
#include <gui/canvas_i.h>
#include <u8g2_glue.h>
#include <gui/icon_animation_i.h>
#include <gui/icon.h>
#include <gui/icon_i.h>
#include <furi_hal.h>
TileMap* tileMap;
uint8_t tileMapCount = 0;
void ui_cleanup() {
if(tileMap != NULL) {
for(uint8_t i = 0; i < tileMapCount; i++) {
if(tileMap[i].data != NULL) free(tileMap[i].data);
}
free(tileMap);
}
}
void add_new_tilemap(uint8_t* data, unsigned long iconId) {
TileMap* old = tileMap;
tileMapCount++;
tileMap = malloc(sizeof(TileMap) * tileMapCount);
if(tileMapCount > 1) {
for(uint8_t i = 0; i < tileMapCount; i++) tileMap[i] = old[i];
}
tileMap[tileMapCount - 1] = (TileMap){data, iconId};
}
uint8_t* get_tilemap(unsigned long icon_id) {
for(uint8_t i = 0; i < tileMapCount; i++) {
if(tileMap[i].iconId == icon_id) return tileMap[i].data;
}
return NULL;
}
uint32_t pixel_index(uint8_t x, uint8_t y) {
return y * SCREEN_WIDTH + x;
}
bool in_screen(int16_t x, int16_t y) {
return x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT;
}
unsigned flipBit(uint8_t x, uint8_t bit) {
return x ^ (1 << bit);
}
unsigned setBit(uint8_t x, uint8_t bit) {
return x | (1 << bit);
}
unsigned unsetBit(uint8_t x, uint8_t bit) {
return x & ~(1 << bit);
}
bool test_pixel(uint8_t* data, uint8_t x, uint8_t y, uint8_t w) {
uint8_t current_bit = (y % 8);
uint8_t current_row = ((y - current_bit) / 8);
uint8_t current_value = data[current_row * w + x];
return current_value & (1 << current_bit);
}
uint8_t* get_buffer(Canvas* const canvas) {
return canvas->fb.tile_buf_ptr;
// return canvas_get_buffer(canvas);
}
uint8_t* make_buffer() {
return malloc(sizeof(uint8_t) * 8 * 128);
}
void clone_buffer(uint8_t* canvas, uint8_t* data) {
for(int i = 0; i < 1024; i++) {
data[i] = canvas[i];
}
}
bool read_pixel(Canvas* const canvas, int16_t x, int16_t y) {
if(in_screen(x, y)) {
return test_pixel(get_buffer(canvas), x, y, SCREEN_WIDTH);
}
return false;
}
void set_pixel(Canvas* const canvas, int16_t x, int16_t y, DrawMode draw_mode) {
if(in_screen(x, y)) {
uint8_t current_bit = (y % 8);
uint8_t current_row = ((y - current_bit) / 8);
uint32_t i = pixel_index(x, current_row);
uint8_t* buffer = get_buffer(canvas);
uint8_t current_value = buffer[i];
if(draw_mode == Inverse) {
buffer[i] = flipBit(current_value, current_bit);
} else {
if(draw_mode == White) {
buffer[i] = unsetBit(current_value, current_bit);
} else {
buffer[i] = setBit(current_value, current_bit);
}
}
}
}
void draw_line(
Canvas* const canvas,
int16_t x1,
int16_t y1,
int16_t x2,
int16_t y2,
DrawMode draw_mode) {
for(int16_t x = x2; x >= x1; x--) {
for(int16_t y = y2; y >= y1; y--) {
set_pixel(canvas, x, y, draw_mode);
}
}
}
void draw_rounded_box_frame(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t w,
uint8_t h,
DrawMode draw_mode) {
int16_t xMinCorner = x + 1;
int16_t xMax = x + w - 1;
int16_t xMaxCorner = x + w - 2;
int16_t yMinCorner = y + 1;
int16_t yMax = y + h - 1;
int16_t yMaxCorner = y + h - 2;
draw_line(canvas, xMinCorner, y, xMaxCorner, y, draw_mode);
draw_line(canvas, xMinCorner, yMax, xMaxCorner, yMax, draw_mode);
draw_line(canvas, x, yMinCorner, x, yMaxCorner, draw_mode);
draw_line(canvas, xMax, yMinCorner, xMax, yMaxCorner, draw_mode);
}
void draw_rounded_box(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t w,
uint8_t h,
DrawMode draw_mode) {
for(int16_t o = w - 2; o >= 1; o--) {
for(int16_t p = h - 2; p >= 1; p--) {
set_pixel(canvas, x + o, y + p, draw_mode);
}
}
draw_rounded_box_frame(canvas, x, y, w, h, draw_mode);
}
void invert_shape(Canvas* const canvas, uint8_t* data, int16_t x, int16_t y, uint8_t w, uint8_t h) {
draw_pixels(canvas, data, x, y, w, h, Inverse);
}
void draw_pixels(
Canvas* const canvas,
uint8_t* data,
int16_t x,
int16_t y,
uint8_t w,
uint8_t h,
DrawMode drawMode) {
for(int8_t o = 0; o < w; o++) {
for(int8_t p = 0; p < h; p++) {
if(in_screen(o + x, p + y) && data[p * w + o] == 1)
set_pixel(canvas, o + x, p + y, drawMode);
}
}
}
void draw_rectangle(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t w,
uint8_t h,
DrawMode drawMode) {
for(int8_t o = 0; o < w; o++) {
for(int8_t p = 0; p < h; p++) {
if(in_screen(o + x, p + y)) {
set_pixel(canvas, o + x, p + y, drawMode);
}
}
}
}
void invert_rectangle(Canvas* const canvas, int16_t x, int16_t y, uint8_t w, uint8_t h) {
draw_rectangle(canvas, x, y, w, h, Inverse);
}
uint8_t* image_data(Canvas* const canvas, const Icon* icon) {
uint8_t* data = malloc(sizeof(uint8_t) * 8 * 128);
uint8_t* screen = canvas->fb.tile_buf_ptr;
canvas->fb.tile_buf_ptr = data;
canvas_draw_icon(canvas, 0, 0, icon);
canvas->fb.tile_buf_ptr = screen;
return data;
}
uint8_t* getOrAddIconData(Canvas* const canvas, const Icon* icon) {
uint8_t* icon_data = get_tilemap((unsigned long)icon);
if(icon_data == NULL) {
icon_data = image_data(canvas, icon);
add_new_tilemap(icon_data, (unsigned long)icon);
}
return icon_data;
}
void draw_icon_clip(
Canvas* const canvas,
const Icon* icon,
int16_t x,
int16_t y,
uint8_t left,
uint8_t top,
uint8_t w,
uint8_t h,
DrawMode drawMode) {
uint8_t* icon_data = getOrAddIconData(canvas, icon);
for(int i = 0; i < w; i++) {
for(int j = 0; j < h; j++) {
bool on = test_pixel(icon_data, left + i, top + j, SCREEN_WIDTH);
if(drawMode == Filled) {
set_pixel(canvas, x + i, y + j, on ? Black : White);
} else if(on)
set_pixel(canvas, x + i, y + j, drawMode);
}
}
}
void draw_icon_clip_flipped(
Canvas* const canvas,
const Icon* icon,
int16_t x,
int16_t y,
uint8_t left,
uint8_t top,
uint8_t w,
uint8_t h,
DrawMode drawMode) {
uint8_t* icon_data = getOrAddIconData(canvas, icon);
for(int i = 0; i < w; i++) {
for(int j = 0; j < h; j++) {
bool on = test_pixel(icon_data, left + i, top + j, SCREEN_WIDTH);
if(drawMode == Filled) {
set_pixel(canvas, x + w - i - 1, y + h - j - 1, on ? Black : White);
} else if(on)
set_pixel(canvas, x + w - i - 1, y + h - j - 1, drawMode);
}
}
}

View File

@@ -1,105 +0,0 @@
#pragma once
#include <furi.h>
#include <gui/canvas.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
typedef enum {
Black,
White,
Inverse,
Filled //Currently only for Icon clip drawing
} DrawMode;
// size is the screen size
typedef struct {
uint8_t* data;
unsigned long iconId;
} TileMap;
bool test_pixel(uint8_t* data, uint8_t x, uint8_t y, uint8_t w);
uint8_t* image_data(Canvas* const canvas, const Icon* icon);
uint32_t pixel_index(uint8_t x, uint8_t y);
void draw_icon_clip(
Canvas* const canvas,
const Icon* icon,
int16_t x,
int16_t y,
uint8_t left,
uint8_t top,
uint8_t w,
uint8_t h,
DrawMode drawMode);
void draw_icon_clip_flipped(
Canvas* const canvas,
const Icon* icon,
int16_t x,
int16_t y,
uint8_t left,
uint8_t top,
uint8_t w,
uint8_t h,
DrawMode drawMode);
void draw_rounded_box(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t w,
uint8_t h,
DrawMode drawMode);
void draw_rounded_box_frame(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t w,
uint8_t h,
DrawMode drawMode);
void draw_rectangle(
Canvas* const canvas,
int16_t x,
int16_t y,
uint8_t w,
uint8_t h,
DrawMode drawMode);
void invert_rectangle(Canvas* const canvas, int16_t x, int16_t y, uint8_t w, uint8_t h);
void invert_shape(Canvas* const canvas, uint8_t* data, int16_t x, int16_t y, uint8_t w, uint8_t h);
void draw_pixels(
Canvas* const canvas,
uint8_t* data,
int16_t x,
int16_t y,
uint8_t w,
uint8_t h,
DrawMode drawMode);
bool read_pixel(Canvas* const canvas, int16_t x, int16_t y);
void set_pixel(Canvas* const canvas, int16_t x, int16_t y, DrawMode draw_mode);
void draw_line(
Canvas* const canvas,
int16_t x1,
int16_t y1,
int16_t x2,
int16_t y2,
DrawMode draw_mode);
bool in_screen(int16_t x, int16_t y);
void ui_cleanup();
uint8_t* get_buffer(Canvas* const canvas);
uint8_t* make_buffer();
void clone_buffer(uint8_t* canvas, uint8_t* data);

View File

@@ -1,77 +0,0 @@
#pragma once
#include <furi.h>
#include <input/input.h>
#include <gui/elements.h>
#include <flipper_format/flipper_format.h>
#include <flipper_format/flipper_format_i.h>
#include "common/card.h"
#include "common/queue.h"
#include "common/menu.h"
#define APP_NAME "Blackjack"
#define CONF_ANIMATION_DURATION "AnimationDuration"
#define CONF_MESSAGE_DURATION "MessageDuration"
#define CONF_STARTING_MONEY "StartingMoney"
#define CONF_ROUND_PRICE "RoundPrice"
#define CONF_SOUND_EFFECTS "SoundEffects"
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
uint32_t animation_duration;
uint32_t message_duration;
uint32_t starting_money;
uint32_t round_price;
bool sound_effects;
} Settings;
typedef struct {
EventType type;
InputEvent input;
} AppEvent;
typedef enum {
GameStateGameOver,
GameStateStart,
GameStatePlay,
GameStateSettings,
GameStateDealer,
} PlayState;
typedef enum {
DirectionUp,
DirectionDown,
DirectionRight,
DirectionLeft,
Select,
Back,
None
} Direction;
typedef struct {
FuriMutex* mutex;
Card player_cards[21];
Card dealer_cards[21];
uint8_t player_card_count;
uint8_t dealer_card_count;
Direction selectDirection;
Settings settings;
uint32_t player_score;
uint32_t bet;
uint8_t selectedMenu;
bool doubled;
bool started;
bool processing;
Deck deck;
PlayState state;
QueueState queue_state;
Menu* menu;
unsigned int last_tick;
} GameState;

View File

@@ -1,186 +0,0 @@
#include <math.h>
#include <notification/notification_messages.h>
#include "ui.h"
#define LINE_HEIGHT 16
#define ITEM_PADDING 4
const char MoneyMul[4] = {'K', 'B', 'T', 'S'};
void draw_player_scene(Canvas* const canvas, const GameState* game_state) {
int max_card = game_state->player_card_count;
if(max_card > 0) draw_deck((game_state->player_cards), max_card, canvas);
if(game_state->dealer_card_count > 0) draw_card_back_at(13, 5, canvas);
max_card = game_state->dealer_card_count;
if(max_card > 1) {
draw_card_at(
2, 2, game_state->dealer_cards[1].pip, game_state->dealer_cards[1].character, canvas);
}
}
void draw_dealer_scene(Canvas* const canvas, const GameState* game_state) {
uint8_t max_card = game_state->dealer_card_count;
draw_deck((game_state->dealer_cards), max_card, canvas);
}
void popup_frame(Canvas* const canvas) {
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 32, 15, 66, 13);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 32, 15, 66, 13);
canvas_set_font(canvas, FontSecondary);
}
void draw_play_menu(Canvas* const canvas, const GameState* game_state) {
const char* menus[3] = {"Double", "Hit", "Stay"};
for(uint8_t m = 0; m < 3; m++) {
if(m == 0 &&
(game_state->doubled || game_state->player_score < game_state->settings.round_price))
continue;
int y = m * 13 + 25;
canvas_set_color(canvas, ColorBlack);
if(game_state->selectedMenu == m) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, 1, y, 31, 12);
} else {
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 1, y, 31, 12);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 1, y, 31, 12);
}
if(game_state->selectedMenu == m)
canvas_set_color(canvas, ColorWhite);
else
canvas_set_color(canvas, ColorBlack);
canvas_draw_str_aligned(canvas, 16, y + 6, AlignCenter, AlignCenter, menus[m]);
}
}
void draw_screen(Canvas* const canvas, const bool* points) {
for(uint8_t x = 0; x < 128; x++) {
for(uint8_t y = 0; y < 64; y++) {
if(points[y * 128 + x]) canvas_draw_dot(canvas, x, y);
}
}
}
void draw_score(Canvas* const canvas, bool top, uint8_t amount) {
char drawChar[20];
snprintf(drawChar, sizeof(drawChar), "Player score: %i", amount);
if(top)
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, drawChar);
else
canvas_draw_str_aligned(canvas, 64, 62, AlignCenter, AlignBottom, drawChar);
}
void draw_money(Canvas* const canvas, uint32_t score) {
canvas_set_font(canvas, FontSecondary);
char drawChar[11];
uint32_t currAmount = score;
if(currAmount < 1000) {
snprintf(drawChar, sizeof(drawChar), "$%lu", currAmount);
} else {
char c = 'K';
for(uint8_t i = 0; i < 4; i++) {
currAmount = currAmount / 1000;
if(currAmount < 1000) {
c = MoneyMul[i];
break;
}
}
snprintf(drawChar, sizeof(drawChar), "$%lu %c", currAmount, c);
}
canvas_draw_str_aligned(canvas, 126, 2, AlignRight, AlignTop, drawChar);
}
void draw_menu(
Canvas* const canvas,
const char* text,
const char* value,
int8_t y,
bool left_caret,
bool right_caret,
bool selected) {
UNUSED(selected);
if(y < 0 || y >= 64) return;
if(selected) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, 0, y, 122, LINE_HEIGHT);
canvas_set_color(canvas, ColorWhite);
}
canvas_draw_str_aligned(canvas, 4, y + ITEM_PADDING, AlignLeft, AlignTop, text);
if(left_caret) canvas_draw_str_aligned(canvas, 80, y + ITEM_PADDING, AlignLeft, AlignTop, "<");
canvas_draw_str_aligned(canvas, 100, y + ITEM_PADDING, AlignCenter, AlignTop, value);
if(right_caret)
canvas_draw_str_aligned(canvas, 120, y + ITEM_PADDING, AlignRight, AlignTop, ">");
canvas_set_color(canvas, ColorBlack);
}
void settings_page(Canvas* const canvas, const GameState* gameState) {
char drawChar[10];
int startY = 0;
if(LINE_HEIGHT * (gameState->selectedMenu + 1) >= 64) {
startY -= (LINE_HEIGHT * (gameState->selectedMenu + 1)) - 64;
}
int scrollHeight = round(64 / 6.0) + ITEM_PADDING * 2;
int scrollPos = 64 / (6.0 / (gameState->selectedMenu + 1)) - ITEM_PADDING * 2;
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(canvas, 123, scrollPos, 4, scrollHeight);
canvas_draw_box(canvas, 125, 0, 1, 64);
snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.starting_money);
draw_menu(
canvas,
"Start money",
drawChar,
0 * LINE_HEIGHT + startY,
gameState->settings.starting_money > gameState->settings.round_price,
gameState->settings.starting_money < 400,
gameState->selectedMenu == 0);
snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.round_price);
draw_menu(
canvas,
"Round price",
drawChar,
1 * LINE_HEIGHT + startY,
gameState->settings.round_price > 10,
gameState->settings.round_price < gameState->settings.starting_money,
gameState->selectedMenu == 1);
snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.animation_duration);
draw_menu(
canvas,
"Anim. length",
drawChar,
2 * LINE_HEIGHT + startY,
gameState->settings.animation_duration > 0,
gameState->settings.animation_duration < 2000,
gameState->selectedMenu == 2);
snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.message_duration);
draw_menu(
canvas,
"Popup time",
drawChar,
3 * LINE_HEIGHT + startY,
gameState->settings.message_duration > 0,
gameState->settings.message_duration < 2000,
gameState->selectedMenu == 3);
// draw_menu(canvas, "Sound", gameState->settings.sound_effects ? "Yes" : "No",
// 5 * LINE_HEIGHT + startY,
// true,
// true,
// gameState->selectedMenu == 5
// );
}

View File

@@ -1,18 +0,0 @@
#pragma once
#include "defines.h"
#include <gui/gui.h>
void draw_player_scene(Canvas* const canvas, const GameState* game_state);
void draw_dealer_scene(Canvas* const canvas, const GameState* game_state);
void draw_play_menu(Canvas* const canvas, const GameState* game_state);
void draw_score(Canvas* const canvas, bool top, uint8_t amount);
void draw_money(Canvas* const canvas, uint32_t score);
void settings_page(Canvas* const canvas, const GameState* gameState);
void popup_frame(Canvas* const canvas);
void draw_screen(Canvas* const canvas, const bool* points);

View File

@@ -1,124 +0,0 @@
#include <storage/storage.h>
#include "util.h"
const char* CONFIG_FILE_PATH = APP_DATA_PATH("blackjack.settings");
void save_settings(Settings settings) {
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file = flipper_format_file_alloc(storage);
FURI_LOG_D(APP_NAME, "Saving config");
if(flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
FURI_LOG_D(
APP_NAME, "Saving %s: %ld", CONF_ANIMATION_DURATION, settings.animation_duration);
flipper_format_update_uint32(
file, CONF_ANIMATION_DURATION, &(settings.animation_duration), 1);
FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_MESSAGE_DURATION, settings.message_duration);
flipper_format_update_uint32(file, CONF_MESSAGE_DURATION, &(settings.message_duration), 1);
FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_STARTING_MONEY, settings.starting_money);
flipper_format_update_uint32(file, CONF_STARTING_MONEY, &(settings.starting_money), 1);
FURI_LOG_D(APP_NAME, "Saving %s: %ld", CONF_ROUND_PRICE, settings.round_price);
flipper_format_update_uint32(file, CONF_ROUND_PRICE, &(settings.round_price), 1);
FURI_LOG_D(APP_NAME, "Saving %s: %i", CONF_SOUND_EFFECTS, settings.sound_effects ? 1 : 0);
flipper_format_update_bool(file, CONF_SOUND_EFFECTS, &(settings.sound_effects), 1);
FURI_LOG_D(APP_NAME, "Config saved");
} else {
FURI_LOG_E(APP_NAME, "Save error");
}
flipper_format_file_close(file);
flipper_format_free(file);
furi_record_close(RECORD_STORAGE);
}
void save_settings_file(FlipperFormat* file, Settings* settings) {
flipper_format_write_header_cstr(file, CONFIG_FILE_HEADER, CONFIG_FILE_VERSION);
flipper_format_write_comment_cstr(file, "Card animation duration in ms");
flipper_format_write_uint32(file, CONF_ANIMATION_DURATION, &(settings->animation_duration), 1);
flipper_format_write_comment_cstr(file, "Popup message duration in ms");
flipper_format_write_uint32(file, CONF_MESSAGE_DURATION, &(settings->message_duration), 1);
flipper_format_write_comment_cstr(file, "Player's starting money");
flipper_format_write_uint32(file, CONF_STARTING_MONEY, &(settings->starting_money), 1);
flipper_format_write_comment_cstr(file, "Round price");
flipper_format_write_uint32(file, CONF_ROUND_PRICE, &(settings->round_price), 1);
flipper_format_write_comment_cstr(file, "Enable sound effects");
flipper_format_write_bool(file, CONF_SOUND_EFFECTS, &(settings->sound_effects), 1);
}
Settings load_settings() {
Settings settings;
FURI_LOG_D(APP_NAME, "Loading default settings");
settings.animation_duration = 800;
settings.message_duration = 1500;
settings.starting_money = 200;
settings.round_price = 10;
settings.sound_effects = true;
FURI_LOG_D(APP_NAME, "Opening storage");
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(storage, EXT_PATH(".blackjack.settings"), CONFIG_FILE_PATH);
FURI_LOG_D(APP_NAME, "Allocating file");
FlipperFormat* file = flipper_format_file_alloc(storage);
FURI_LOG_D(APP_NAME, "Allocating string");
FuriString* string_value;
string_value = furi_string_alloc();
if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) != FSE_OK) {
FURI_LOG_D(APP_NAME, "Config file %s not found, creating new one...", CONFIG_FILE_PATH);
if(!flipper_format_file_open_new(file, CONFIG_FILE_PATH)) {
FURI_LOG_E(APP_NAME, "Error creating new file %s", CONFIG_FILE_PATH);
flipper_format_file_close(file);
} else {
save_settings_file(file, &settings);
}
} else {
if(!flipper_format_file_open_existing(file, CONFIG_FILE_PATH)) {
FURI_LOG_E(APP_NAME, "Error opening existing file %s", CONFIG_FILE_PATH);
flipper_format_file_close(file);
} else {
uint32_t value;
bool valueBool;
FURI_LOG_D(APP_NAME, "Checking version");
if(!flipper_format_read_header(file, string_value, &value)) {
FURI_LOG_E(APP_NAME, "Config file mismatch");
} else {
FURI_LOG_D(APP_NAME, "Loading %s", CONF_ANIMATION_DURATION);
if(flipper_format_read_uint32(file, CONF_ANIMATION_DURATION, &value, 1)) {
settings.animation_duration = value;
FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ANIMATION_DURATION, value);
}
FURI_LOG_D(APP_NAME, "Loading %s", CONF_MESSAGE_DURATION);
if(flipper_format_read_uint32(file, CONF_MESSAGE_DURATION, &value, 1)) {
settings.message_duration = value;
FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_MESSAGE_DURATION, value);
}
FURI_LOG_D(APP_NAME, "Loading %s", CONF_STARTING_MONEY);
if(flipper_format_read_uint32(file, CONF_STARTING_MONEY, &value, 1)) {
settings.starting_money = value;
FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_STARTING_MONEY, value);
}
FURI_LOG_D(APP_NAME, "Loading %s", CONF_ROUND_PRICE);
if(flipper_format_read_uint32(file, CONF_ROUND_PRICE, &value, 1)) {
settings.round_price = value;
FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ROUND_PRICE, value);
}
FURI_LOG_D(APP_NAME, "Loading %s", CONF_SOUND_EFFECTS);
if(flipper_format_read_bool(file, CONF_SOUND_EFFECTS, &valueBool, 1)) {
settings.sound_effects = valueBool;
FURI_LOG_D(APP_NAME, "Loaded %s: %i", CONF_ROUND_PRICE, valueBool ? 1 : 0);
}
}
flipper_format_file_close(file);
}
}
furi_string_free(string_value);
// flipper_format_file_close(file);
flipper_format_free(file);
furi_record_close(RECORD_STORAGE);
return settings;
}

View File

@@ -1,7 +0,0 @@
#pragma once
#include "defines.h"
#define CONFIG_FILE_HEADER "Blackjack config file"
#define CONFIG_FILE_VERSION 1
void save_settings(Settings settings);
Settings load_settings();

View File

@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -1,19 +0,0 @@
App(
appid="nrf24batch",
name="[NRF24] Batch",
apptype=FlipperAppType.EXTERNAL,
entry_point="nrf24batch_app",
cdefines=["APP_NRF24BATCH"],
requires=["gui"],
stack_size=2 * 1024,
fap_icon="nrf24batch_10px.png",
fap_category="GPIO",
fap_private_libs=[
Lib(
name="nrf24",
sources=[
"nrf24.c",
],
),
],
)

View File

@@ -1,384 +0,0 @@
// Modified by vad7, 24.02.2023
//
#include "nrf24.h"
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_resources.h>
#include <assert.h>
#include <string.h>
void nrf24_init() {
// this is needed if multiple SPI devices are connected to the same bus but with different CS pins
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull);
furi_hal_gpio_write(&gpio_ext_pc3, true);
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
furi_hal_gpio_write(&gpio_ext_pa4, true);
}
furi_hal_spi_bus_handle_init(nrf24_HANDLE);
furi_hal_spi_acquire(nrf24_HANDLE);
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_write(nrf24_CE_PIN, false);
}
void nrf24_deinit() {
furi_hal_spi_release(nrf24_HANDLE);
furi_hal_spi_bus_handle_deinit(nrf24_HANDLE);
furi_hal_gpio_write(nrf24_CE_PIN, false);
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
// resetting the CS pins to floating
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog);
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog);
}
}
void nrf24_spi_trx(FuriHalSpiBusHandle* handle, uint8_t* tx, uint8_t* rx, uint8_t size) {
furi_hal_gpio_write(handle->cs, false);
furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT);
furi_hal_gpio_write(handle->cs, true);
}
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
uint8_t buf[] = {W_REGISTER | (REGISTER_MASK & reg), data};
nrf24_spi_trx(handle, buf, buf, 2);
//FURI_LOG_D("NRF_WR", " #%02X=%02X", reg, data);
return buf[0];
}
uint8_t
nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
uint8_t buf[size + 1];
buf[0] = W_REGISTER | (REGISTER_MASK & reg);
memcpy(&buf[1], data, size);
nrf24_spi_trx(handle, buf, buf, size + 1);
//FURI_LOG_D("NRF_WR", " #%02X(%02X)=0x%02X%02X%02X%02X%02X", reg, size, data[0], data[1], data[2], data[3], data[4] );
return buf[0];
}
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
uint8_t buf[size + 1];
memset(buf, 0, size + 1);
buf[0] = R_REGISTER | (REGISTER_MASK & reg);
nrf24_spi_trx(handle, buf, buf, size + 1);
memcpy(data, &buf[1], size);
return buf[0];
}
uint8_t nrf24_read_register(FuriHalSpiBusHandle* handle, uint8_t reg) {
uint8_t buf[] = {R_REGISTER | (REGISTER_MASK & reg), 0};
nrf24_spi_trx(handle, buf, buf, 2);
return buf[1];
}
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) {
uint8_t tx[] = {FLUSH_RX};
uint8_t rx[] = {0};
nrf24_spi_trx(handle, tx, rx, 1);
return rx[0];
}
uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle) {
uint8_t tx[] = {FLUSH_TX};
uint8_t rx[] = {0};
nrf24_spi_trx(handle, tx, rx, 1);
return rx[0];
}
uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle) {
uint8_t maclen;
nrf24_read_reg(handle, REG_SETUP_AW, &maclen, 1);
maclen &= 3;
return maclen + 2;
}
uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen) {
assert(maclen > 1 && maclen < 6);
uint8_t status = 0;
status = nrf24_write_reg(handle, REG_SETUP_AW, maclen - 2);
return status;
}
uint8_t nrf24_status(FuriHalSpiBusHandle* handle) {
uint8_t tx = RF24_NOP;
nrf24_spi_trx(handle, &tx, &tx, 1);
return tx;
}
uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle) {
uint8_t setup = 0;
uint32_t rate = 0;
nrf24_read_reg(handle, REG_RF_SETUP, &setup, 1);
setup &= 0x28;
if(setup == 0x20)
rate = 250000; // 250kbps
else if(setup == 0x08)
rate = 2000000; // 2Mbps
else if(setup == 0x00)
rate = 1000000; // 1Mbps
return rate;
}
uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate) {
uint8_t r6 = 0;
uint8_t status = 0;
if(!rate) rate = 2000000;
nrf24_read_reg(handle, REG_RF_SETUP, &r6, 1); // RF_SETUP register
r6 = r6 & (~0x28); // Clear rate fields.
if(rate == 2000000)
r6 = r6 | 0x08;
else if(rate == 1000000)
r6 = r6;
else if(rate == 250000)
r6 = r6 | 0x20;
status = nrf24_write_reg(handle, REG_RF_SETUP, r6); // Write new rate.
return status;
}
uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle) {
uint8_t channel = 0;
nrf24_read_reg(handle, REG_RF_CH, &channel, 1);
return channel;
}
uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan) {
uint8_t status;
status = nrf24_write_reg(handle, REG_RF_CH, chan);
return status;
}
uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
uint8_t size = 0;
uint8_t status = 0;
size = nrf24_get_maclen(handle);
status = nrf24_read_reg(handle, REG_RX_ADDR_P0, mac, size);
return status;
}
uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
uint8_t status = 0;
uint8_t clearmac[] = {0, 0, 0, 0, 0};
nrf24_set_maclen(handle, size);
nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, clearmac, 5);
status = nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, mac, size);
return status;
}
uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
uint8_t size = 0;
uint8_t status = 0;
size = nrf24_get_maclen(handle);
status = nrf24_read_reg(handle, REG_TX_ADDR, mac, size);
return status;
}
uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
uint8_t status = 0;
uint8_t clearmac[] = {0, 0, 0, 0, 0};
nrf24_set_maclen(handle, size);
nrf24_write_buf_reg(handle, REG_TX_ADDR, clearmac, 5);
status = nrf24_write_buf_reg(handle, REG_TX_ADDR, mac, size);
return status;
}
uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe) {
uint8_t len = 0;
if(pipe > 5) pipe = 0;
nrf24_read_reg(handle, RX_PW_P0 + pipe, &len, 1);
return len;
}
uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len) {
uint8_t status = 0;
status = nrf24_write_reg(handle, RX_PW_P0, len);
return status;
}
// packet_size: 0 - dyn payload (read from PL_WID), 1 - read from pipe size, >1 - override
// Return STATUS reg + additional: RX_DR - new data available, 0x80 - NRF24 hardware error
uint8_t nrf24_rxpacket(
FuriHalSpiBusHandle* handle,
uint8_t* packet,
uint8_t* ret_packetsize,
uint8_t packet_size) {
uint8_t status = 0;
uint8_t buf[33]; // 32 max payload size + 1 for command
status = nrf24_status(handle);
if(!(status & RX_DR)) {
uint8_t st = nrf24_read_register(handle, REG_FIFO_STATUS);
if(st == 0xFF || st == 0) return 0x80; // hardware error
if((st & 1) == 0) {
FURI_LOG_D("NRF", "FIFO PKT");
status |= RX_DR; // packet in FIFO buffer
}
}
if(status & RX_DR) {
if(status & 0x80) return 0x80; // hardware error
if(packet_size == 1)
packet_size = nrf24_get_packetlen(handle, (status >> 1) & 7);
else if(packet_size == 0) {
buf[0] = R_RX_PL_WID;
buf[1] = 0xFF;
nrf24_spi_trx(handle, buf, buf, 2);
packet_size = buf[1];
}
if(packet_size > 32 || packet_size == 0) packet_size = 32;
memset(buf, 0, packet_size + 1);
buf[0] = R_RX_PAYLOAD;
nrf24_spi_trx(handle, buf, buf, packet_size + 1);
memcpy(packet, &buf[1], packet_size);
nrf24_write_reg(handle, REG_STATUS, RX_DR); // clear RX_DR
}
if(status & (MAX_RT)) { // MAX_RT
nrf24_write_reg(handle, REG_STATUS, (MAX_RT)); // clear MAX_RT.
}
*ret_packetsize = packet_size;
return status;
}
// Return 0 when error
uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack) {
uint8_t status = 0;
uint8_t buf[size + 1];
buf[0] = ack ? W_TX_PAYLOAD : W_TX_PAYLOAD_NOACK;
memcpy(&buf[1], payload, size);
nrf24_set_tx_mode(handle);
nrf24_spi_trx(handle, buf, buf, size + 1);
uint32_t start_time = furi_get_tick();
do {
furi_delay_us(100);
status = nrf24_status(handle);
} while(!(status & (TX_DS | MAX_RT)) && furi_get_tick() - start_time < 100UL);
if(status & MAX_RT) {
if(furi_log_get_level() == FuriLogLevelDebug)
FURI_LOG_D(
"NRF", "MAX RT: %X (%X)", nrf24_read_register(handle, REG_OBSERVE_TX), status);
nrf24_flush_tx(handle);
}
furi_hal_gpio_write(nrf24_CE_PIN, false);
//nrf24_set_idle(handle);
if(status & (TX_DS | MAX_RT)) nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT);
return status & TX_DS;
}
uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle) {
uint8_t status = 0;
uint8_t cfg = 0;
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
cfg = cfg | 2;
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
//furi_delay_ms(1000);
return status;
}
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
uint8_t status = 0;
uint8_t cfg = 0;
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
cfg &= 0xfc; // clear bottom two bits to power down the radio
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
furi_hal_gpio_write(nrf24_CE_PIN, false);
return status;
}
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle) {
uint8_t cfg = 0;
cfg = nrf24_read_register(handle, REG_CONFIG);
cfg |= 0x03; // PWR_UP, and PRIM_RX
cfg = nrf24_write_reg(handle, REG_CONFIG, cfg);
furi_hal_gpio_write(nrf24_CE_PIN, true);
return cfg;
}
uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle) {
uint8_t reg;
furi_hal_gpio_write(nrf24_CE_PIN, false);
//nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT);
reg = nrf24_read_register(handle, REG_CONFIG);
reg &= ~0x01; // disable PRIM_RX
reg |= 0x02; // PWR_UP
reg = nrf24_write_reg(handle, REG_CONFIG, reg);
furi_hal_gpio_write(nrf24_CE_PIN, true);
return reg;
}
void hexlify(uint8_t* in, uint8_t size, char* out) {
memset(out, 0, size * 2);
for(int i = 0; i < size; i++)
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
}
uint64_t bytes_to_int64(uint8_t* bytes, uint8_t size, bool bigendian) {
uint64_t ret = 0;
for(int i = 0; i < size; i++)
if(bigendian)
ret |= bytes[i] << ((size - 1 - i) * 8);
else
ret |= bytes[i] << (i * 8);
return ret;
}
void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian) {
for(int i = 0; i < 8; i++) {
if(bigendian)
out[i] = (val >> ((7 - i) * 8)) & 0xff;
else
out[i] = (val >> (i * 8)) & 0xff;
}
}
uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian) {
uint32_t ret = 0;
for(int i = 0; i < 4; i++)
if(bigendian)
ret |= bytes[i] << ((3 - i) * 8);
else
ret |= bytes[i] << (i * 8);
return ret;
}
void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian) {
for(int i = 0; i < 4; i++) {
if(bigendian)
out[i] = (val >> ((3 - i) * 8)) & 0xff;
else
out[i] = (val >> (i * 8)) & 0xff;
}
}
uint64_t bytes_to_int16(uint8_t* bytes, bool bigendian) {
uint16_t ret = 0;
for(int i = 0; i < 2; i++)
if(bigendian)
ret |= bytes[i] << ((1 - i) * 8);
else
ret |= bytes[i] << (i * 8);
return ret;
}
void int16_to_bytes(uint16_t val, uint8_t* out, bool bigendian) {
for(int i = 0; i < 2; i++) {
if(bigendian)
out[i] = (val >> ((1 - i) * 8)) & 0xff;
else
out[i] = (val >> (i * 8)) & 0xff;
}
}
uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t* mac, uint8_t mlen) {
uint8_t addr[5];
for(int i = 0; i < mlen; i++) addr[i] = mac[mlen - i - 1];
return nrf24_write_buf_reg(nrf24_HANDLE, mac_addr, addr, mlen);
}

View File

@@ -1,392 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <furi_hal_spi.h>
#include <xtreme.h>
#ifdef __cplusplus
extern "C" {
#endif
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define REGISTER_MASK 0x1F
#define ACTIVATE 0x50
#define R_RX_PL_WID 0x60
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define W_TX_PAYLOAD_NOACK 0xB0
#define W_ACK_PAYLOAD 0xA8
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define RF24_NOP 0xFF
#define REG_CONFIG 0x00
#define REG_EN_AA 0x01
#define REG_EN_RXADDR 0x02
#define REG_SETUP_AW 0x03
#define REG_SETUP_RETR 0x04
#define REG_DYNPD 0x1C
#define REG_FEATURE 0x1D
#define REG_RF_SETUP 0x06
#define REG_STATUS 0x07
#define REG_RX_ADDR_P0 0x0A
#define REG_RX_ADDR_P1 0x0B
#define REG_RX_ADDR_P2 0x0C
#define REG_RX_ADDR_P3 0x0D
#define REG_RX_ADDR_P4 0x0E
#define REG_RX_ADDR_P5 0x0F
#define REG_RF_CH 0x05
#define REG_TX_ADDR 0x10
#define REG_FIFO_STATUS 0x17
#define REG_OBSERVE_TX 0x08
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
#define RX_DR 0x40
#define TX_DS 0x20
#define MAX_RT 0x10
#define NRF24_EN_DYN_ACK 0x01
#define nrf24_TIMEOUT 500
#define nrf24_CE_PIN &gpio_ext_pb2
#define nrf24_HANDLE \
(xtreme_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
&furi_hal_spi_bus_handle_external_extra)
/* Low level API */
/** Write device register
*
* @param handle - pointer to FuriHalSpiHandle
* @param reg - register
* @param data - data to write
*
* @return device status
*/
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
/** Write buffer to device register
*
* @param handle - pointer to FuriHalSpiHandle
* @param reg - register
* @param data - data to write
* @param size - size of data to write
*
* @return device status
*/
uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
/** Read device register
*
* @param handle - pointer to FuriHalSpiHandle
* @param reg - register
* @param[out] data - pointer to data
*
* @return device status
*/
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
// Read single register (1 byte)
uint8_t nrf24_read_register(FuriHalSpiBusHandle* handle, uint8_t reg);
/** Power up the radio for operation
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle);
/** Power down the radio
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
/** Sets the radio to RX mode
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle);
/** Sets the radio to TX mode
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle);
/*=============================================================================================================*/
/* High level API */
/** Must call this before using any other nrf24 API
*
*/
void nrf24_init();
/** Must call this when we end using nrf24 device
*
*/
void nrf24_deinit();
/** Send flush rx command
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
/** Send flush tx command
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle);
/** Gets the RX packet length in data pipe 0
*
* @param handle - pointer to FuriHalSpiHandle
* pipe - pipe index (0..5)
* @return packet length in data pipe 0
*/
uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe);
/** Sets the RX packet length in data pipe 0
*
* @param handle - pointer to FuriHalSpiHandle
* @param len - length to set
*
* @return device status
*/
uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len);
/** Gets configured length of MAC address
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return MAC address length
*/
uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle);
/** Sets configured length of MAC address
*
* @param handle - pointer to FuriHalSpiHandle
* @param maclen - length to set MAC address to, must be greater than 1 and less than 6
*
* @return MAC address length
*/
uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen);
/** Gets the current status flags from the STATUS register
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return status flags
*/
uint8_t nrf24_status(FuriHalSpiBusHandle* handle);
/** Gets the current transfer rate
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return transfer rate in bps
*/
uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle);
/** Sets the transfer rate
*
* @param handle - pointer to FuriHalSpiHandle
* @param rate - the transfer rate in bps
*
* @return device status
*/
uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate);
/** Gets the current channel
* In nrf24, the channel number is multiplied times 1MHz and added to 2400MHz to get the frequency
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return channel
*/
uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle);
/** Sets the channel
*
* @param handle - pointer to FuriHalSpiHandle
* @param frequency - the frequency in hertz
*
* @return device status
*/
uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan);
/** Gets the source mac address
*
* @param handle - pointer to FuriHalSpiHandle
* @param[out] mac - the source mac address
*
* @return device status
*/
uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
/** Sets the source mac address
*
* @param handle - pointer to FuriHalSpiHandle
* @param mac - the mac address to set
* @param size - the size of the mac address (2 to 5)
*
* @return device status
*/
uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
/** Gets the dest mac address
*
* @param handle - pointer to FuriHalSpiHandle
* @param[out] mac - the source mac address
*
* @return device status
*/
uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
/** Sets the dest mac address
*
* @param handle - pointer to FuriHalSpiHandle
* @param mac - the mac address to set
* @param size - the size of the mac address (2 to 5)
*
* @return device status
*/
uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
/** Reads RX packet
*
* @param handle - pointer to FuriHalSpiHandle
* @param[out] packet - the packet contents
* @param[out] ret_packetsize - size of the received packet
* @param packet_size: >1 - size, 1 - packet length is determined by RX_PW_P0 register, 0 - it is determined by dynamic payload length command
*
* @return device status
*/
uint8_t nrf24_rxpacket(
FuriHalSpiBusHandle* handle,
uint8_t* packet,
uint8_t* ret_packetsize,
uint8_t packet_size_flag);
/** Sends TX packet
*
* @param handle - pointer to FuriHalSpiHandle
* @param packet - the packet contents
* @param size - packet size
* @param ack - boolean to determine whether an ACK is required for the packet or not
*
* @return device status
*/
uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack);
/** Configure the radio
* This is not comprehensive, but covers a lot of the common configuration options that may be changed
* @param handle - pointer to FuriHalSpiHandle
* @param rate - transfer rate in Mbps (1 or 2)
* @param srcmac - source mac address
* @param dstmac - destination mac address
* @param maclen - length of mac address
* @param channel - channel to tune to
* @param noack - if true, disable auto-acknowledge
* @param disable_aa - if true, disable ShockBurst
*
*/
void nrf24_configure(
FuriHalSpiBusHandle* handle,
uint8_t rate,
uint8_t* srcmac,
uint8_t* dstmac,
uint8_t maclen,
uint8_t channel,
bool noack,
bool disable_aa);
// Set mac address (MSB first), Return: Status
uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t* mac, uint8_t mlen);
/** Configures the radio for "promiscuous mode" and primes it for rx
* This is not an actual mode of the nrf24, but this function exploits a few bugs in the chip that allows it to act as if it were.
* See http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html for details.
* @param handle - pointer to FuriHalSpiHandle
* @param channel - channel to tune to
* @param rate - transfer rate in Mbps (1 or 2)
*/
void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate);
/** Listens for a packet and returns first possible address sniffed
* Call this only after calling nrf24_init_promisc_mode
* @param handle - pointer to FuriHalSpiHandle
* @param maclen - length of target mac address
* @param[out] addresses - sniffed address
*
* @return success
*/
bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address);
/** Sends ping packet on each channel for designated tx mac looking for ack
*
* @param handle - pointer to FuriHalSpiHandle
* @param srcmac - source address
* @param dstmac - destination address
* @param maclen - length of address
* @param rate - transfer rate in Mbps (1 or 2)
* @param min_channel - channel to start with
* @param max_channel - channel to end at
* @param autoinit - if true, automatically configure radio for this channel
*
* @return channel that the address is listening on, if this value is above the max_channel param, it failed
*/
uint8_t nrf24_find_channel(
FuriHalSpiBusHandle* handle,
uint8_t* srcmac,
uint8_t* dstmac,
uint8_t maclen,
uint8_t rate,
uint8_t min_channel,
uint8_t max_channel,
bool autoinit);
/** Converts 64 bit value into uint8_t array
* @param val - 64-bit integer
* @param[out] out - bytes out
* @param bigendian - if true, convert as big endian, otherwise little endian
*/
void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian);
/** Converts 32 bit value into uint8_t array
* @param val - 32-bit integer
* @param[out] out - bytes out
* @param bigendian - if true, convert as big endian, otherwise little endian
*/
void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian);
/** Converts uint8_t array into 32 bit value
* @param bytes - uint8_t array
* @param bigendian - if true, convert as big endian, otherwise little endian
*
* @return 32-bit value
*/
uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +0,0 @@
#pragma once
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <toolbox/stream/file_stream.h>
#include <notification/notification_messages.h>
#include <power/power_service/power.h>
#include <power/power_service/power_i.h>
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef struct {
FuriMutex* mutex;
} PluginState;
typedef struct {
Gui* gui;
Storage* storage;
NotificationApp* notification;
PluginState* plugin_state;
} nRF24Batch;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,23 +0,0 @@
App(
appid="nrf24channelscanner",
name="[NRF24] Channel Scan",
apptype=FlipperAppType.EXTERNAL,
entry_point="nrf24channelscanner_main",
fap_author="HTotoo",
fap_weburl="https://github.com/htotoo/NRF24ChannelScanner",
stack_size=2 * 1024,
requires=["gui"],
fap_category="GPIO",
fap_version=(1, 3),
fap_icon_assets="images",
fap_icon="fapicon.png",
fap_description="Scans 2.4Ghz frequency for usage data.",
fap_private_libs=[
Lib(
name="nrf24",
sources=[
"nrf24.c",
],
),
],
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

View File

@@ -1,117 +0,0 @@
#include "nrf24.h"
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_resources.h>
#include <assert.h>
#include <string.h>
void nrf24_init() {
// this is needed if multiple SPI devices are connected to the same bus but with different CS pins
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull);
furi_hal_gpio_write(&gpio_ext_pc3, true);
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
furi_hal_gpio_write(&gpio_ext_pa4, true);
}
furi_hal_spi_bus_handle_init(nrf24_HANDLE);
furi_hal_spi_acquire(nrf24_HANDLE);
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_write(nrf24_CE_PIN, false);
}
void nrf24_deinit() {
furi_hal_spi_release(nrf24_HANDLE);
furi_hal_spi_bus_handle_deinit(nrf24_HANDLE);
furi_hal_gpio_write(nrf24_CE_PIN, false);
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
// resetting the CS pins to floating
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog);
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog);
}
}
void nrf24_spi_trx(
FuriHalSpiBusHandle* handle,
uint8_t* tx,
uint8_t* rx,
uint8_t size,
uint32_t timeout) {
UNUSED(timeout);
furi_hal_gpio_write(handle->cs, false);
furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT);
furi_hal_gpio_write(handle->cs, true);
}
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data};
uint8_t rx[2] = {0};
nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT);
return rx[0];
}
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
uint8_t tx[size + 1];
uint8_t rx[size + 1];
memset(rx, 0, size + 1);
tx[0] = R_REGISTER | (REGISTER_MASK & reg);
memset(&tx[1], 0, size);
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
memcpy(data, &rx[1], size);
return rx[0];
}
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) {
uint8_t tx[] = {FLUSH_RX};
uint8_t rx[] = {0};
nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
return rx[0];
}
uint8_t nrf24_get_rdp(FuriHalSpiBusHandle* handle) {
uint8_t rdp;
nrf24_read_reg(handle, REG_RDP, &rdp, 1);
return rdp;
}
uint8_t nrf24_status(FuriHalSpiBusHandle* handle) {
uint8_t status;
uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)};
nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT);
return status;
}
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
uint8_t status = 0;
uint8_t cfg = 0;
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
cfg &= 0xfc; // clear bottom two bits to power down the radio
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
furi_hal_gpio_write(nrf24_CE_PIN, false);
return status;
}
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle, bool nodelay) {
uint8_t status = 0;
uint8_t cfg = 0;
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
cfg |= 0x03; // PWR_UP, and PRIM_RX
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
furi_hal_gpio_write(nrf24_CE_PIN, true);
if(!nodelay) furi_delay_ms(2000);
return status;
}
bool nrf24_check_connected(FuriHalSpiBusHandle* handle) {
uint8_t status = nrf24_status(handle);
if(status != 0x00) {
return true;
} else {
return false;
}
}

View File

@@ -1,129 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <furi_hal_spi.h>
#include <xtreme.h>
#ifdef __cplusplus
extern "C" {
#endif
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define REGISTER_MASK 0x1F
#define ACTIVATE 0x50
#define R_RX_PL_WID 0x60
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define W_TX_PAYLOAD_NOACK 0xB0
#define W_ACK_PAYLOAD 0xA8
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define RF24_NOP 0xFF
#define REG_CONFIG 0x00
#define REG_EN_AA 0x01
#define REG_EN_RXADDR 0x02
#define REG_SETUP_AW 0x03
#define REG_SETUP_RETR 0x04
#define REG_RDP 0x09
#define REG_DYNPD 0x1C
#define REG_FEATURE 0x1D
#define REG_RF_SETUP 0x06
#define REG_STATUS 0x07
#define REG_RX_ADDR_P0 0x0A
#define REG_RF_CH 0x05
#define REG_TX_ADDR 0x10
#define RX_PW_P0 0x11
#define TX_DS 0x20
#define MAX_RT 0x10
#define nrf24_TIMEOUT 500
#define nrf24_CE_PIN &gpio_ext_pb2
#define nrf24_HANDLE \
(xtreme_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
&furi_hal_spi_bus_handle_external_extra)
/* Low level API */
/** Write device register
*
* @param handle - pointer to FuriHalSpiHandle
* @param reg - register
* @param data - data to write
*
* @return device status
*/
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
/** Read device register
*
* @param handle - pointer to FuriHalSpiHandle
* @param reg - register
* @param[out] data - pointer to data
*
* @return device status
*/
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
/** Power down the radio
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
/** Sets the radio to RX mode
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle, bool nodelay);
/*=============================================================================================================*/
/* High level API */
/** Must call this before using any other nrf24 API
*
*/
void nrf24_init();
/** Must call this when we end using nrf24 device
*
*/
void nrf24_deinit();
/** Send flush rx command
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
/** Gets RDP from register 0x09
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return RDP from register 0x09
*/
uint8_t nrf24_get_rdp(FuriHalSpiBusHandle* handle);
/** Gets the current status flags from the STATUS register
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return status flags
*/
uint8_t nrf24_status(FuriHalSpiBusHandle* handle);
bool nrf24_check_connected(FuriHalSpiBusHandle* handle);
#ifdef __cplusplus
}
#endif

View File

@@ -1,269 +0,0 @@
#include <stdio.h>
#include <furi.h>
#include <furi_hal_power.h>
#include <gui/gui.h>
#include <input/input.h>
#include <gui/elements.h>
#include <notification/notification_messages.h>
#include <nrf24.h>
#include "nrf24channelscanner_icons.h"
#include <assets_icons.h>
const uint8_t num_channels = 128;
static uint8_t nrf24values[128] = {0}; //to store channel data
bool ifNotFoundNrf = false; //to show error message
bool szuz = true; //to show welcome screen
static bool isScanning = false; //to track the progress
static bool stopNrfScan = false; //to exit thread
static bool isInfiniteScan = false; //to prevent stop scan when OK long pressed
static bool threadStoppedsoFree = false; //indicate if I can free the thread from ram.
static uint8_t currCh = 0; //for the progress bar or the channel selector
static int delayPerChan = 150; //can set via up / down.
bool showFreq = true;
FuriThread* thread;
typedef enum {
EventTypeKey,
EventTypeTick,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} Event;
static void draw_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
canvas_set_bitmap_mode(canvas, 1);
canvas_draw_icon(canvas, 100, 0, &I_Pin_back_arrow_10x8);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 112, 8, "Exit");
canvas_draw_icon(canvas, 1, 0, &I_Ok_btn_9x9);
canvas_set_font(canvas, FontSecondary);
if(isScanning) {
canvas_draw_str(canvas, 12, 8, "Stop");
} else {
canvas_draw_str(canvas, 12, 8, "Scan");
}
canvas_draw_line(canvas, 0, 11, 127, 11);
if(ifNotFoundNrf) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 23, 35, "NRF24 not found!");
return;
}
canvas_draw_line(canvas, currCh, 12, currCh, 13); //draw the current channel
//draw hello mesage
if(szuz) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 1, 22, "OK: scan / stop. Long: infinite.");
canvas_draw_str(canvas, 1, 33, "Up / Down to change channel time.");
canvas_draw_str(canvas, 1, 44, "Left / Right to select channel");
canvas_draw_str(canvas, 1, 56, " to get it's frequency");
}
//draw freq ir the progress
canvas_set_font(canvas, FontSecondary);
if(isScanning) {
if(isInfiniteScan)
canvas_draw_str(canvas, 37, 8, "scanning...");
else
canvas_draw_str(canvas, 37, 8, "scanning");
} else {
if(showFreq) {
int freq = 2400 + currCh;
char strfreq[10] = {0};
snprintf(strfreq, sizeof(strfreq), "%d MHZ", freq);
canvas_draw_str(canvas, 40, 8, strfreq);
} else {
//show delay
int dly = delayPerChan;
char strdel[10] = {0};
snprintf(strdel, sizeof(strdel), "%d us", dly);
canvas_draw_str(canvas, 40, 8, strdel);
}
}
//draw the chart
for(int i = 0; i < num_channels; ++i) {
int h = 64 - nrf24values[i];
if(h < 11) h = 12;
canvas_draw_line(canvas, i, h, i, 64);
}
}
static void input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
Event event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static int32_t scanner(void* context) {
UNUSED(context);
isScanning = true;
stopNrfScan = false;
threadStoppedsoFree = false;
uint8_t tmp = 0;
currCh = 0;
nrf24_set_rx_mode(nrf24_HANDLE, false);
nrf24_write_reg(nrf24_HANDLE, REG_EN_AA, 0x0);
nrf24_write_reg(nrf24_HANDLE, REG_RF_SETUP, 0x0f);
while(true) { //scan until stopped somehow
if(stopNrfScan) break;
for(uint8_t i = 0; i < num_channels; i++) {
if(stopNrfScan) break;
currCh = i;
nrf24_write_reg(nrf24_HANDLE, REG_RF_CH, i);
nrf24_set_rx_mode(nrf24_HANDLE, true);
for(uint8_t ii = 0; ii < 3; ++ii) {
nrf24_flush_rx(nrf24_HANDLE);
furi_delay_us(delayPerChan);
tmp = nrf24_get_rdp(nrf24_HANDLE);
if(tmp > 0 && nrf24values[i] < 65) {
nrf24values[i]++; //don't overrun it
}
if(nrf24values[i] > 50 && !isInfiniteScan) {
stopNrfScan = true; //stop, bc maxed, but only when not infinite
}
}
}
furi_delay_ms(1);
//for screen refresh.
}
//cleanup
nrf24_set_idle(nrf24_HANDLE);
isScanning = false;
threadStoppedsoFree = true;
currCh = 0;
return 0;
}
void ChangeFreq(int delta) {
currCh += delta;
if(currCh > num_channels) currCh = 0;
showFreq = true;
}
void ChangeDelay(int delta) {
delayPerChan += delta;
if(delayPerChan > 4000) delayPerChan = 4000;
if(delayPerChan < 120) delayPerChan = 120;
if(delayPerChan == 170) delayPerChan = 150; //rounding for the next
showFreq = false;
}
// Main entry of the application
int32_t nrf24channelscanner_main(void* p) {
UNUSED(p);
Event event;
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(Event));
//turn on 5v for some modules
uint8_t attempts = 0;
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
furi_hal_power_enable_otg();
furi_delay_ms(10);
}
nrf24_init();
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, draw_callback, NULL);
view_port_input_callback_set(view_port, input_callback, event_queue);
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
while(true) {
furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk);
if(event.type == EventTypeKey) {
szuz = false; //hit any button, so hide welcome screen
if((event.input.type == InputTypeShort || event.input.type == InputTypeLong) &&
event.input.key == InputKeyBack) {
if(isScanning) {
stopNrfScan = true; //if running, stop it.
notification_message(notification, &sequence_blink_yellow_100);
furi_thread_join(thread);
furi_thread_free(thread);
}
break;
}
//isInfiniteScan
if((event.input.type == InputTypeShort || event.input.type == InputTypeLong) &&
event.input.key == InputKeyOk) {
if(isScanning) {
notification_message(notification, &sequence_blink_yellow_100);
stopNrfScan = true;
furi_thread_join(thread);
furi_thread_free(thread);
threadStoppedsoFree = false; //to prevent double free
continue;
}
memset(nrf24values, 0, sizeof(nrf24values));
if(nrf24_check_connected(nrf24_HANDLE)) {
threadStoppedsoFree = false;
ifNotFoundNrf = false;
notification_message(notification, &sequence_blink_green_100);
isInfiniteScan = (event.input.type == InputTypeLong);
thread = furi_thread_alloc();
furi_thread_set_name(thread, "nrfscannerth");
furi_thread_set_stack_size(thread, 1024);
furi_thread_set_callback(thread, scanner);
furi_thread_start(thread);
} else {
ifNotFoundNrf = true;
notification_message(notification, &sequence_error);
}
}
//change the delay
if(event.input.type == InputTypeShort && event.input.key == InputKeyUp) {
ChangeDelay(50);
}
if(event.input.type == InputTypeShort && event.input.key == InputKeyDown) {
ChangeDelay(-50);
}
if(!isScanning) {
if(event.input.type == InputTypeLong && event.input.key == InputKeyLeft)
ChangeFreq(-10);
if(event.input.type == InputTypeShort && event.input.key == InputKeyLeft)
ChangeFreq(-1);
if(event.input.type == InputTypeLong && event.input.key == InputKeyRight)
ChangeFreq(10);
if(event.input.type == InputTypeShort && event.input.key == InputKeyRight)
ChangeFreq(1);
}
}
if(threadStoppedsoFree) {
threadStoppedsoFree = false;
furi_thread_join(thread);
furi_thread_free(thread);
}
}
nrf24_deinit();
furi_message_queue_free(event_queue);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_record_close(RECORD_GUI);
//turn off 5v
if(furi_hal_power_is_otg_enabled()) {
furi_hal_power_disable_otg();
}
return 0;
}

View File

@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -1,19 +0,0 @@
App(
appid="nrf24scan",
name="[NRF24] Scanner",
apptype=FlipperAppType.EXTERNAL,
entry_point="nrf24scan_app",
cdefines=["APP_NRF24SCAN"],
requires=["gui"],
stack_size=2 * 1024,
fap_icon="nrf24scan_10px.png",
fap_category="GPIO",
fap_private_libs=[
Lib(
name="nrf24",
sources=[
"nrf24.c",
],
),
],
)

View File

@@ -1,556 +0,0 @@
// Modified by vad7, 25.11.2022
//
#include "nrf24.h"
#include <furi.h>
#include <furi_hal.h>
#include <furi_hal_resources.h>
#include <assert.h>
#include <string.h>
void nrf24_init() {
// this is needed if multiple SPI devices are connected to the same bus but with different CS pins
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeOutputPushPull);
furi_hal_gpio_write(&gpio_ext_pc3, true);
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull);
furi_hal_gpio_write(&gpio_ext_pa4, true);
}
furi_hal_spi_bus_handle_init(nrf24_HANDLE);
furi_hal_spi_acquire(nrf24_HANDLE);
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_write(nrf24_CE_PIN, false);
}
void nrf24_deinit() {
furi_hal_spi_release(nrf24_HANDLE);
furi_hal_spi_bus_handle_deinit(nrf24_HANDLE);
furi_hal_gpio_write(nrf24_CE_PIN, false);
furi_hal_gpio_init(nrf24_CE_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
// resetting the CS pins to floating
if(xtreme_settings.spi_nrf24_handle == SpiDefault) {
furi_hal_gpio_init_simple(&gpio_ext_pc3, GpioModeAnalog);
} else if(xtreme_settings.spi_nrf24_handle == SpiExtra) {
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog);
}
}
void nrf24_spi_trx(
FuriHalSpiBusHandle* handle,
uint8_t* tx,
uint8_t* rx,
uint8_t size,
uint32_t timeout) {
UNUSED(timeout);
furi_hal_gpio_write(handle->cs, false);
furi_hal_spi_bus_trx(handle, tx, rx, size, nrf24_TIMEOUT);
furi_hal_gpio_write(handle->cs, true);
}
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data) {
uint8_t tx[2] = {W_REGISTER | (REGISTER_MASK & reg), data};
uint8_t rx[2] = {0};
nrf24_spi_trx(handle, tx, rx, 2, nrf24_TIMEOUT);
//FURI_LOG_D("NRF_WR", " #%02X=%02X", reg, data);
return rx[0];
}
uint8_t
nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
uint8_t tx[size + 1];
uint8_t rx[size + 1];
memset(rx, 0, size + 1);
tx[0] = W_REGISTER | (REGISTER_MASK & reg);
memcpy(&tx[1], data, size);
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
//FURI_LOG_D("NRF_WR", " #%02X(%02X)=0x%02X%02X%02X%02X%02X", reg, size, data[0], data[1], data[2], data[3], data[4] );
return rx[0];
}
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size) {
uint8_t tx[size + 1];
uint8_t rx[size + 1];
memset(rx, 0, size + 1);
tx[0] = R_REGISTER | (REGISTER_MASK & reg);
memset(&tx[1], 0, size);
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
memcpy(data, &rx[1], size);
return rx[0];
}
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle) {
uint8_t tx[] = {FLUSH_RX};
uint8_t rx[] = {0};
nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
return rx[0];
}
uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle) {
uint8_t tx[] = {FLUSH_TX};
uint8_t rx[] = {0};
nrf24_spi_trx(handle, tx, rx, 1, nrf24_TIMEOUT);
return rx[0];
}
uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle) {
uint8_t maclen;
nrf24_read_reg(handle, REG_SETUP_AW, &maclen, 1);
maclen &= 3;
return maclen + 2;
}
uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen) {
assert(maclen > 1 && maclen < 6);
uint8_t status = 0;
status = nrf24_write_reg(handle, REG_SETUP_AW, maclen - 2);
return status;
}
uint8_t nrf24_status(FuriHalSpiBusHandle* handle) {
uint8_t status;
uint8_t tx[] = {R_REGISTER | (REGISTER_MASK & REG_STATUS)};
nrf24_spi_trx(handle, tx, &status, 1, nrf24_TIMEOUT);
return status;
}
uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle) {
uint8_t setup = 0;
uint32_t rate = 0;
nrf24_read_reg(handle, REG_RF_SETUP, &setup, 1);
setup &= 0x28;
if(setup == 0x20)
rate = 250000; // 250kbps
else if(setup == 0x08)
rate = 2000000; // 2Mbps
else if(setup == 0x00)
rate = 1000000; // 1Mbps
return rate;
}
uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate) {
uint8_t r6 = 0;
uint8_t status = 0;
if(!rate) rate = 2000000;
nrf24_read_reg(handle, REG_RF_SETUP, &r6, 1); // RF_SETUP register
r6 = r6 & (~0x28); // Clear rate fields.
if(rate == 2000000)
r6 = r6 | 0x08;
else if(rate == 1000000)
r6 = r6;
else if(rate == 250000)
r6 = r6 | 0x20;
status = nrf24_write_reg(handle, REG_RF_SETUP, r6); // Write new rate.
return status;
}
uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle) {
uint8_t channel = 0;
nrf24_read_reg(handle, REG_RF_CH, &channel, 1);
return channel;
}
uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan) {
uint8_t status;
status = nrf24_write_reg(handle, REG_RF_CH, chan);
return status;
}
uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
uint8_t size = 0;
uint8_t status = 0;
size = nrf24_get_maclen(handle);
status = nrf24_read_reg(handle, REG_RX_ADDR_P0, mac, size);
return status;
}
uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
uint8_t status = 0;
uint8_t clearmac[] = {0, 0, 0, 0, 0};
nrf24_set_maclen(handle, size);
nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, clearmac, 5);
status = nrf24_write_buf_reg(handle, REG_RX_ADDR_P0, mac, size);
return status;
}
uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac) {
uint8_t size = 0;
uint8_t status = 0;
size = nrf24_get_maclen(handle);
status = nrf24_read_reg(handle, REG_TX_ADDR, mac, size);
return status;
}
uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size) {
uint8_t status = 0;
uint8_t clearmac[] = {0, 0, 0, 0, 0};
nrf24_set_maclen(handle, size);
nrf24_write_buf_reg(handle, REG_TX_ADDR, clearmac, 5);
status = nrf24_write_buf_reg(handle, REG_TX_ADDR, mac, size);
return status;
}
uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe) {
uint8_t len = 0;
if(pipe > 5) pipe = 0;
nrf24_read_reg(handle, RX_PW_P0 + pipe, &len, 1);
return len;
}
uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len) {
uint8_t status = 0;
status = nrf24_write_reg(handle, RX_PW_P0, len);
return status;
}
uint8_t nrf24_rxpacket(
FuriHalSpiBusHandle* handle,
uint8_t* packet,
uint8_t* ret_packetsize,
uint8_t packet_size) {
uint8_t status = 0;
uint8_t tx_cmd[33] = {0}; // 32 max payload size + 1 for command
uint8_t tmp_packet[33] = {0};
status = nrf24_status(handle);
if(!(status & RX_DR)) {
tx_cmd[0] = R_REGISTER | (REGISTER_MASK & REG_FIFO_STATUS);
nrf24_spi_trx(handle, tx_cmd, tmp_packet, 2, nrf24_TIMEOUT);
if((tmp_packet[1] & 1) == 0) status |= RX_DR; // packet in FIFO buffer
}
if(status & RX_DR) {
if(packet_size == 1)
packet_size = nrf24_get_packetlen(handle, (status >> 1) & 7);
else if(packet_size == 0) {
tx_cmd[0] = R_RX_PL_WID;
tx_cmd[1] = 0;
nrf24_spi_trx(handle, tx_cmd, tmp_packet, 2, nrf24_TIMEOUT);
packet_size = tmp_packet[1];
}
if(packet_size > 32 || packet_size == 0) packet_size = 32;
tx_cmd[0] = R_RX_PAYLOAD;
tx_cmd[1] = 0;
nrf24_spi_trx(handle, tx_cmd, tmp_packet, packet_size + 1, nrf24_TIMEOUT);
memcpy(packet, &tmp_packet[1], packet_size);
nrf24_write_reg(handle, REG_STATUS, RX_DR); // clear RX_DR
} else if(status & (TX_DS | MAX_RT)) { // MAX_RT, TX_DS
nrf24_write_reg(handle, REG_STATUS, (TX_DS | MAX_RT)); // clear RX_DR, MAX_RT.
}
*ret_packetsize = packet_size;
return status;
}
// Return 0 when error
uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack) {
uint8_t status = 0;
uint8_t tx[size + 1];
uint8_t rx[size + 1];
memset(tx, 0, size + 1);
memset(rx, 0, size + 1);
if(!ack)
tx[0] = W_TX_PAYLOAD_NOACK;
else
tx[0] = W_TX_PAYLOAD;
memcpy(&tx[1], payload, size);
nrf24_spi_trx(handle, tx, rx, size + 1, nrf24_TIMEOUT);
nrf24_set_tx_mode(handle);
uint32_t start_time = furi_get_tick();
while(!(status & (TX_DS | MAX_RT)) && furi_get_tick() - start_time < 2000UL)
status = nrf24_status(handle);
if(status & MAX_RT) nrf24_flush_tx(handle);
nrf24_set_idle(handle);
nrf24_write_reg(handle, REG_STATUS, TX_DS | MAX_RT);
return status & TX_DS;
}
uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle) {
uint8_t status = 0;
uint8_t cfg = 0;
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
cfg = cfg | 2;
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
furi_delay_ms(1000);
return status;
}
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle) {
uint8_t status = 0;
uint8_t cfg = 0;
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
cfg &= 0xfc; // clear bottom two bits to power down the radio
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
//nr204_write_reg(handle, REG_EN_RXADDR, 0x0);
furi_hal_gpio_write(nrf24_CE_PIN, false);
return status;
}
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle) {
uint8_t status = 0;
uint8_t cfg = 0;
//status = nrf24_write_reg(handle, REG_CONFIG, 0x0F); // enable 2-byte CRC, PWR_UP, and PRIM_RX
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
cfg |= 0x03; // PWR_UP, and PRIM_RX
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
//nr204_write_reg(REG_EN_RXADDR, 0x03) // Set RX Pipe 0 and 1
furi_hal_gpio_write(nrf24_CE_PIN, true);
furi_delay_ms(2);
return status;
}
uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle) {
uint8_t status = 0;
uint8_t cfg = 0;
furi_hal_gpio_write(nrf24_CE_PIN, false);
nrf24_write_reg(handle, REG_STATUS, 0x30);
//status = nrf24_write_reg(handle, REG_CONFIG, 0x0E); // enable 2-byte CRC, PWR_UP
nrf24_read_reg(handle, REG_CONFIG, &cfg, 1);
cfg &= 0xfe; // disable PRIM_RX
cfg |= 0x02; // PWR_UP
status = nrf24_write_reg(handle, REG_CONFIG, cfg);
furi_hal_gpio_write(nrf24_CE_PIN, true);
furi_delay_ms(2);
return status;
}
void nrf24_configure(
FuriHalSpiBusHandle* handle,
uint8_t rate,
uint8_t* srcmac,
uint8_t* dstmac,
uint8_t maclen,
uint8_t channel,
bool noack,
bool disable_aa) {
assert(channel <= 125);
assert(rate == 1 || rate == 2);
if(rate == 2)
rate = 8; // 2Mbps
else
rate = 0; // 1Mbps
nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF
nrf24_set_idle(handle);
nrf24_write_reg(handle, REG_STATUS, 0x70); // clear interrupts
if(disable_aa)
nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst
else
nrf24_write_reg(handle, REG_EN_AA, 0x1F); // Enable Shockburst
nrf24_write_reg(handle, REG_DYNPD, 0x3F); // enable dynamic payload length on all pipes
if(noack)
nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack
else {
nrf24_write_reg(handle, REG_CONFIG, 0x0C); // 2 byte CRC
nrf24_write_reg(handle, REG_FEATURE, 0x07); // enable dyn payload and ack
nrf24_write_reg(
handle, REG_SETUP_RETR, 0x1f); // 15 retries for AA, 500us auto retransmit delay
}
nrf24_set_idle(handle);
nrf24_flush_rx(handle);
nrf24_flush_tx(handle);
if(maclen) nrf24_set_maclen(handle, maclen);
if(srcmac) nrf24_set_src_mac(handle, srcmac, maclen);
if(dstmac) nrf24_set_dst_mac(handle, dstmac, maclen);
nrf24_write_reg(handle, REG_RF_CH, channel);
nrf24_write_reg(handle, REG_RF_SETUP, rate);
furi_delay_ms(200);
}
void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate) {
//uint8_t preamble[] = {0x55, 0x00}; // little endian
uint8_t preamble[] = {0xAA, 0x00}; // little endian
//uint8_t preamble[] = {0x00, 0x55}; // little endian
//uint8_t preamble[] = {0x00, 0xAA}; // little endian
nrf24_write_reg(handle, REG_CONFIG, 0x00); // Stop nRF
nrf24_write_reg(handle, REG_STATUS, 0x70); // clear interrupts
nrf24_write_reg(handle, REG_DYNPD, 0x0); // disable shockburst
nrf24_write_reg(handle, REG_EN_AA, 0x00); // Disable Shockburst
nrf24_write_reg(handle, REG_FEATURE, 0x05); // disable payload-with-ack, enable noack
nrf24_set_maclen(handle, 2); // shortest address
nrf24_set_src_mac(handle, preamble, 2); // set src mac to preamble bits to catch everything
nrf24_set_packetlen(handle, 32); // set max packet length
nrf24_set_idle(handle);
nrf24_flush_rx(handle);
nrf24_flush_tx(handle);
nrf24_write_reg(handle, REG_RF_CH, channel);
nrf24_write_reg(handle, REG_RF_SETUP, rate);
// prime for RX, no checksum
nrf24_write_reg(handle, REG_CONFIG, 0x03); // PWR_UP and PRIM_RX, disable AA and CRC
furi_hal_gpio_write(nrf24_CE_PIN, true);
furi_delay_ms(100);
}
void hexlify(uint8_t* in, uint8_t size, char* out) {
memset(out, 0, size * 2);
for(int i = 0; i < size; i++)
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
}
uint64_t bytes_to_int64(uint8_t* bytes, uint8_t size, bool bigendian) {
uint64_t ret = 0;
for(int i = 0; i < size; i++)
if(bigendian)
ret |= bytes[i] << ((size - 1 - i) * 8);
else
ret |= bytes[i] << (i * 8);
return ret;
}
void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian) {
for(int i = 0; i < 8; i++) {
if(bigendian)
out[i] = (val >> ((7 - i) * 8)) & 0xff;
else
out[i] = (val >> (i * 8)) & 0xff;
}
}
uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian) {
uint32_t ret = 0;
for(int i = 0; i < 4; i++)
if(bigendian)
ret |= bytes[i] << ((3 - i) * 8);
else
ret |= bytes[i] << (i * 8);
return ret;
}
void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian) {
for(int i = 0; i < 4; i++) {
if(bigendian)
out[i] = (val >> ((3 - i) * 8)) & 0xff;
else
out[i] = (val >> (i * 8)) & 0xff;
}
}
uint64_t bytes_to_int16(uint8_t* bytes, bool bigendian) {
uint16_t ret = 0;
for(int i = 0; i < 2; i++)
if(bigendian)
ret |= bytes[i] << ((1 - i) * 8);
else
ret |= bytes[i] << (i * 8);
return ret;
}
void int16_to_bytes(uint16_t val, uint8_t* out, bool bigendian) {
for(int i = 0; i < 2; i++) {
if(bigendian)
out[i] = (val >> ((1 - i) * 8)) & 0xff;
else
out[i] = (val >> (i * 8)) & 0xff;
}
}
// handle iffyness with preamble processing sometimes being a bit (literally) off
void alt_address_old(uint8_t* packet, uint8_t* altaddr) {
uint8_t macmess_hi_b[4];
uint8_t macmess_lo_b[2];
uint32_t macmess_hi;
uint16_t macmess_lo;
uint8_t preserved;
// get first 6 bytes into 32-bit and 16-bit variables
memcpy(macmess_hi_b, packet, 4);
memcpy(macmess_lo_b, packet + 4, 2);
macmess_hi = bytes_to_int32(macmess_hi_b, true);
//preserve least 7 bits from hi that will be shifted down to lo
preserved = macmess_hi & 0x7f;
macmess_hi >>= 7;
macmess_lo = bytes_to_int16(macmess_lo_b, true);
macmess_lo >>= 7;
macmess_lo = (preserved << 9) | macmess_lo;
int32_to_bytes(macmess_hi, macmess_hi_b, true);
int16_to_bytes(macmess_lo, macmess_lo_b, true);
memcpy(altaddr, &macmess_hi_b[1], 3);
memcpy(altaddr + 3, macmess_lo_b, 2);
}
bool validate_address(uint8_t* addr) {
uint8_t bad[][3] = {{0x55, 0x55}, {0xAA, 0xAA}, {0x00, 0x00}, {0xFF, 0xFF}};
for(int i = 0; i < 4; i++)
for(int j = 0; j < 2; j++)
if(!memcmp(addr + j * 2, bad[i], 2)) return false;
return true;
}
bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address) {
bool found = false;
uint8_t packet[32] = {0};
uint8_t packetsize;
//char printit[65];
uint8_t status = 0;
status = nrf24_rxpacket(handle, packet, &packetsize, true);
if(status & 0x40) {
if(validate_address(packet)) {
for(int i = 0; i < maclen; i++) address[i] = packet[maclen - 1 - i];
/*
alt_address(packet, packet);
for(i = 0; i < maclen; i++)
address[i + 5] = packet[maclen - 1 - i];
*/
//memcpy(address, packet, maclen);
//hexlify(packet, packetsize, printit);
found = true;
}
}
return found;
}
uint8_t nrf24_find_channel(
FuriHalSpiBusHandle* handle,
uint8_t* srcmac,
uint8_t* dstmac,
uint8_t maclen,
uint8_t rate,
uint8_t min_channel,
uint8_t max_channel,
bool autoinit) {
uint8_t ping_packet[] = {0x0f, 0x0f, 0x0f, 0x0f}; // this can be anything, we just need an ack
uint8_t ch = max_channel + 1; // means fail
nrf24_configure(handle, rate, srcmac, dstmac, maclen, 2, false, false);
for(ch = min_channel; ch <= max_channel + 1; ch++) {
nrf24_write_reg(handle, REG_RF_CH, ch);
if(nrf24_txpacket(handle, ping_packet, 4, true)) break;
}
if(autoinit) {
FURI_LOG_D("nrf24", "initializing radio for channel %d", ch);
nrf24_configure(handle, rate, srcmac, dstmac, maclen, ch, false, false);
return ch;
}
return ch;
}
uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t* mac, uint8_t mlen) {
uint8_t addr[5];
for(int i = 0; i < mlen; i++) addr[i] = mac[mlen - i - 1];
return nrf24_write_buf_reg(nrf24_HANDLE, mac_addr, addr, mlen);
}

View File

@@ -1,387 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <furi_hal_spi.h>
#include <xtreme.h>
#ifdef __cplusplus
extern "C" {
#endif
#define R_REGISTER 0x00
#define W_REGISTER 0x20
#define REGISTER_MASK 0x1F
#define ACTIVATE 0x50
#define R_RX_PL_WID 0x60
#define R_RX_PAYLOAD 0x61
#define W_TX_PAYLOAD 0xA0
#define W_TX_PAYLOAD_NOACK 0xB0
#define W_ACK_PAYLOAD 0xA8
#define FLUSH_TX 0xE1
#define FLUSH_RX 0xE2
#define REUSE_TX_PL 0xE3
#define RF24_NOP 0xFF
#define REG_CONFIG 0x00
#define REG_EN_AA 0x01
#define REG_EN_RXADDR 0x02
#define REG_SETUP_AW 0x03
#define REG_SETUP_RETR 0x04
#define REG_DYNPD 0x1C
#define REG_FEATURE 0x1D
#define REG_RF_SETUP 0x06
#define REG_STATUS 0x07
#define REG_RX_ADDR_P0 0x0A
#define REG_RX_ADDR_P1 0x0B
#define REG_RX_ADDR_P2 0x0C
#define REG_RX_ADDR_P3 0x0D
#define REG_RX_ADDR_P4 0x0E
#define REG_RX_ADDR_P5 0x0F
#define REG_RF_CH 0x05
#define REG_TX_ADDR 0x10
#define REG_FIFO_STATUS 0x17
#define RX_PW_P0 0x11
#define RX_PW_P1 0x12
#define RX_PW_P2 0x13
#define RX_PW_P3 0x14
#define RX_PW_P4 0x15
#define RX_PW_P5 0x16
#define RX_DR 0x40
#define TX_DS 0x20
#define MAX_RT 0x10
#define nrf24_TIMEOUT 500
#define nrf24_CE_PIN &gpio_ext_pb2
#define nrf24_HANDLE \
(xtreme_settings.spi_nrf24_handle == SpiDefault ? &furi_hal_spi_bus_handle_external : \
&furi_hal_spi_bus_handle_external_extra)
/* Low level API */
/** Write device register
*
* @param handle - pointer to FuriHalSpiHandle
* @param reg - register
* @param data - data to write
*
* @return device status
*/
uint8_t nrf24_write_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t data);
/** Write buffer to device register
*
* @param handle - pointer to FuriHalSpiHandle
* @param reg - register
* @param data - data to write
* @param size - size of data to write
*
* @return device status
*/
uint8_t nrf24_write_buf_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
/** Read device register
*
* @param handle - pointer to FuriHalSpiHandle
* @param reg - register
* @param[out] data - pointer to data
*
* @return device status
*/
uint8_t nrf24_read_reg(FuriHalSpiBusHandle* handle, uint8_t reg, uint8_t* data, uint8_t size);
/** Power up the radio for operation
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_power_up(FuriHalSpiBusHandle* handle);
/** Power down the radio
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_set_idle(FuriHalSpiBusHandle* handle);
/** Sets the radio to RX mode
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_set_rx_mode(FuriHalSpiBusHandle* handle);
/** Sets the radio to TX mode
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_set_tx_mode(FuriHalSpiBusHandle* handle);
/*=============================================================================================================*/
/* High level API */
/** Must call this before using any other nrf24 API
*
*/
void nrf24_init();
/** Must call this when we end using nrf24 device
*
*/
void nrf24_deinit();
/** Send flush rx command
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_flush_rx(FuriHalSpiBusHandle* handle);
/** Send flush tx command
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return device status
*/
uint8_t nrf24_flush_tx(FuriHalSpiBusHandle* handle);
/** Gets the RX packet length in data pipe 0
*
* @param handle - pointer to FuriHalSpiHandle
* pipe - pipe index (0..5)
* @return packet length in data pipe 0
*/
uint8_t nrf24_get_packetlen(FuriHalSpiBusHandle* handle, uint8_t pipe);
/** Sets the RX packet length in data pipe 0
*
* @param handle - pointer to FuriHalSpiHandle
* @param len - length to set
*
* @return device status
*/
uint8_t nrf24_set_packetlen(FuriHalSpiBusHandle* handle, uint8_t len);
/** Gets configured length of MAC address
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return MAC address length
*/
uint8_t nrf24_get_maclen(FuriHalSpiBusHandle* handle);
/** Sets configured length of MAC address
*
* @param handle - pointer to FuriHalSpiHandle
* @param maclen - length to set MAC address to, must be greater than 1 and less than 6
*
* @return MAC address length
*/
uint8_t nrf24_set_maclen(FuriHalSpiBusHandle* handle, uint8_t maclen);
/** Gets the current status flags from the STATUS register
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return status flags
*/
uint8_t nrf24_status(FuriHalSpiBusHandle* handle);
/** Gets the current transfer rate
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return transfer rate in bps
*/
uint32_t nrf24_get_rate(FuriHalSpiBusHandle* handle);
/** Sets the transfer rate
*
* @param handle - pointer to FuriHalSpiHandle
* @param rate - the transfer rate in bps
*
* @return device status
*/
uint8_t nrf24_set_rate(FuriHalSpiBusHandle* handle, uint32_t rate);
/** Gets the current channel
* In nrf24, the channel number is multiplied times 1MHz and added to 2400MHz to get the frequency
*
* @param handle - pointer to FuriHalSpiHandle
*
* @return channel
*/
uint8_t nrf24_get_chan(FuriHalSpiBusHandle* handle);
/** Sets the channel
*
* @param handle - pointer to FuriHalSpiHandle
* @param frequency - the frequency in hertz
*
* @return device status
*/
uint8_t nrf24_set_chan(FuriHalSpiBusHandle* handle, uint8_t chan);
/** Gets the source mac address
*
* @param handle - pointer to FuriHalSpiHandle
* @param[out] mac - the source mac address
*
* @return device status
*/
uint8_t nrf24_get_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
/** Sets the source mac address
*
* @param handle - pointer to FuriHalSpiHandle
* @param mac - the mac address to set
* @param size - the size of the mac address (2 to 5)
*
* @return device status
*/
uint8_t nrf24_set_src_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
/** Gets the dest mac address
*
* @param handle - pointer to FuriHalSpiHandle
* @param[out] mac - the source mac address
*
* @return device status
*/
uint8_t nrf24_get_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac);
/** Sets the dest mac address
*
* @param handle - pointer to FuriHalSpiHandle
* @param mac - the mac address to set
* @param size - the size of the mac address (2 to 5)
*
* @return device status
*/
uint8_t nrf24_set_dst_mac(FuriHalSpiBusHandle* handle, uint8_t* mac, uint8_t size);
/** Reads RX packet
*
* @param handle - pointer to FuriHalSpiHandle
* @param[out] packet - the packet contents
* @param[out] ret_packetsize - size of the received packet
* @param packet_size: >1 - size, 1 - packet length is determined by RX_PW_P0 register, 0 - it is determined by dynamic payload length command
*
* @return device status
*/
uint8_t nrf24_rxpacket(
FuriHalSpiBusHandle* handle,
uint8_t* packet,
uint8_t* ret_packetsize,
uint8_t packet_size_flag);
/** Sends TX packet
*
* @param handle - pointer to FuriHalSpiHandle
* @param packet - the packet contents
* @param size - packet size
* @param ack - boolean to determine whether an ACK is required for the packet or not
*
* @return device status
*/
uint8_t nrf24_txpacket(FuriHalSpiBusHandle* handle, uint8_t* payload, uint8_t size, bool ack);
/** Configure the radio
* This is not comprehensive, but covers a lot of the common configuration options that may be changed
* @param handle - pointer to FuriHalSpiHandle
* @param rate - transfer rate in Mbps (1 or 2)
* @param srcmac - source mac address
* @param dstmac - destination mac address
* @param maclen - length of mac address
* @param channel - channel to tune to
* @param noack - if true, disable auto-acknowledge
* @param disable_aa - if true, disable ShockBurst
*
*/
void nrf24_configure(
FuriHalSpiBusHandle* handle,
uint8_t rate,
uint8_t* srcmac,
uint8_t* dstmac,
uint8_t maclen,
uint8_t channel,
bool noack,
bool disable_aa);
// Set mac address (MSB first), Return: Status
uint8_t nrf24_set_mac(uint8_t mac_addr, uint8_t* mac, uint8_t mlen);
/** Configures the radio for "promiscuous mode" and primes it for rx
* This is not an actual mode of the nrf24, but this function exploits a few bugs in the chip that allows it to act as if it were.
* See http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html for details.
* @param handle - pointer to FuriHalSpiHandle
* @param channel - channel to tune to
* @param rate - transfer rate in Mbps (1 or 2)
*/
void nrf24_init_promisc_mode(FuriHalSpiBusHandle* handle, uint8_t channel, uint8_t rate);
/** Listens for a packet and returns first possible address sniffed
* Call this only after calling nrf24_init_promisc_mode
* @param handle - pointer to FuriHalSpiHandle
* @param maclen - length of target mac address
* @param[out] addresses - sniffed address
*
* @return success
*/
bool nrf24_sniff_address(FuriHalSpiBusHandle* handle, uint8_t maclen, uint8_t* address);
/** Sends ping packet on each channel for designated tx mac looking for ack
*
* @param handle - pointer to FuriHalSpiHandle
* @param srcmac - source address
* @param dstmac - destination address
* @param maclen - length of address
* @param rate - transfer rate in Mbps (1 or 2)
* @param min_channel - channel to start with
* @param max_channel - channel to end at
* @param autoinit - if true, automatically configure radio for this channel
*
* @return channel that the address is listening on, if this value is above the max_channel param, it failed
*/
uint8_t nrf24_find_channel(
FuriHalSpiBusHandle* handle,
uint8_t* srcmac,
uint8_t* dstmac,
uint8_t maclen,
uint8_t rate,
uint8_t min_channel,
uint8_t max_channel,
bool autoinit);
/** Converts 64 bit value into uint8_t array
* @param val - 64-bit integer
* @param[out] out - bytes out
* @param bigendian - if true, convert as big endian, otherwise little endian
*/
void int64_to_bytes(uint64_t val, uint8_t* out, bool bigendian);
/** Converts 32 bit value into uint8_t array
* @param val - 32-bit integer
* @param[out] out - bytes out
* @param bigendian - if true, convert as big endian, otherwise little endian
*/
void int32_to_bytes(uint32_t val, uint8_t* out, bool bigendian);
/** Converts uint8_t array into 32 bit value
* @param bytes - uint8_t array
* @param bigendian - if true, convert as big endian, otherwise little endian
*
* @return 32-bit value
*/
uint32_t bytes_to_int32(uint8_t* bytes, bool bigendian);
#ifdef __cplusplus
}
#endif

View File

@@ -1,131 +0,0 @@
#
# NRF24L01+ Enhanced ShockBurst packets decoder
#
payload_len_default = 4
packets = \
(
'10101010 11101110 00000011 00001000 00001011 01000111 000100 10 0 10101010 10101010 10101010 10101010 00011101',
'10101010 11001000 11001000 11000011 110011 10 0 00001011 00000011 00000101 00000000 0010001100100000',
'10101010 11001000 11001000 11000100 000100 11 1 00001011 00000011 00000101 00000000 0010010011100010',
'10101010 11001000 11001000 11000100 00001011 00000011 00000101 00000010 1000010101000010',
'10101010 11001000 11001000 11000000 110011 10 0 11110101 00000010 00000011 00000000 0000111001000000',
'01010101 01000000 01101000 00010101 000000 00 0 0100100000100000',
# '01010101 01000010 11100100 10100110 01010101 01000100 110011 00 0 10010101 10110011 01100100 10101100 10101011 01010010 01111100 01001010 1100110100110001',
)
def bin2hex(x):
def bin2hex_helper(r):
while r:
yield r[0:2].upper()
r = r[2:]
if len(x) == 0: return
fmt = "{0:0" + str(int(len(x) / 8 * 2)) + "X}"
hex_data = fmt.format(int(x, 2))
return list(bin2hex_helper(hex_data))
def bin2hexlong(b):
b = b.replace(" ", "")
out = "";
n = 8
for i in range(0, len(b), n):
b2 = b[i:i+n]
out = out + "{0:02X}".format(int(b2.ljust(8, '0'),2))
return out
def split_packet(packet, parts):
"""Split a string of 1s and 0s into multiple substrings as specified by parts.
Example: "111000011000", (3, 4, 2) -> ["111", "0000", "11", "000"]
:param packet: String of 1s and 0s
:param parts: Tuple of length of substrings
:return: list of substrings
"""
out = []
packet = packet.replace(' ', '')
for part_length in parts:
out.append(packet[0:part_length])
packet = packet[part_length:]
out.append(packet)
return out
def parse_packet(packet, address_length, ESB):
"""Split a packet into its fields and return them as tuple."""
if ESB:
preamble, address, payload_length, pid, no_ack, rest = split_packet(packet=packet, parts=(8, 8 * address_length, 6, 2, 1))
payload, crc = split_packet(packet=rest, parts=((payload_len_default if int(payload_length, 2) > 32 else int(payload_length, 2)) * 8,))
else:
preamble, address, rest = split_packet(packet=packet, parts=(8, 8 * address_length))
crc = packet.rsplit(' ', 1)[1]
payload = rest[0:len(rest) - len(crc)]
payload_length = pid = no_ack = ''
assert preamble in ('10101010', '01010101')
assert len(crc) in (8, 16)
return preamble, address, payload_length, pid, no_ack, payload, crc
def crc(bits, size=8):
"""Calculate the crc value for the polynomial initialized with 0xFF/0xFFFF)
:param size: 8 or 16 bit crc
:param bits: String of 1s and 0s
:return:
:polynomial: 1 byte CRC - standard is 0x107 = 0b100000111 = x^8+x^2+x^1+1, result the same for 0x07
:polynomial: 2 byte CRC - standard is 0x11021 = X^16+X^12+X^5+1, result the same for 0x1021
"""
if size == 8:
polynomial = 0x107
crc = 0xFF
else:
polynomial = 0x11021
crc = 0xFFFF
max_crc_value = (1 << size) - 1 # e.g. 0xFF for mode 8bit-crc
for bit in bits:
bit = int(bit, 2)
crc <<= 1
if (crc >> size) ^ bit: # top most lfsr bit xor current data bit
crc ^= polynomial
crc &= max_crc_value # trim the crc to reject carry over bits
# print('{:X}'.format(crc))
return crc
if __name__ == '__main__':
for packet in packets:
fld = packet.split(' ');
address_length = -1
ESB = True
for f in fld:
if len(f) == 6 : break
if len(f) == 0 :
ESB = False
break
address_length += 1
preamble, address, payload_length, pid, no_ack, payload, crc_received = \
parse_packet(packet=packet, address_length=address_length, ESB=ESB)
crc_size = len(crc_received)
crc_received = '0x' + '{:X}'.format(int(crc_received, 2))
print(f"Packet: {packet}")
print('\n'.join((
f'Hex: {bin2hexlong(packet)}',
'Preamble: 0x%X' % int(preamble,2),
f'Address: {address_length} bytes - {bin2hex(address)}')))
if ESB:
print('\n'.join((
f'Payload length in packet: {int(payload_length, 2)}, used: {(payload_len_default if int(payload_length, 2) > 32 else int(payload_length, 2))}',
f'Payload: {bin2hex(payload)}',
f'Pid: {int(pid, 2)}',
f'No_ack: {int(no_ack, 2) == 1}')))
else:
print(f'Not Enhanced ShockBurst packet, payload length: {int(len(payload) / 8)}')
print(f'Payload: {bin2hex(payload)}')
print(f'CRC{crc_size}: {crc_received}')
crc_calculated = '0x' + '{:X}'.format(crc(address + payload_length + pid + no_ack + payload, size=crc_size))
if crc_received == crc_calculated:
print('CRC is valid!')
else:
print(f'CRC mismatch! Calculated CRC is {crc_calculated}.')
print('-------------')

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +0,0 @@
#pragma once
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <toolbox/stream/file_stream.h>
#include <notification/notification_messages.h>
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} PluginEvent;
typedef struct {
FuriMutex* mutex;
} PluginState;
struct FOUND {
uint8_t addr_size;
uint8_t addr[5];
uint16_t total;
};
typedef struct {
Gui* gui;
FuriMessageQueue* event_queue;
ViewPort* view_port;
Storage* storage;
NotificationApp* notification;
uint8_t* log_arr;
struct FOUND* found;
} Nrf24Scan;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Kirill Korepanov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,17 +0,0 @@
App(
appid="roots_of_life",
name="Roots of Life",
apptype=FlipperAppType.EXTERNAL,
entry_point="roots_of_life_game_app",
cdefines=["APP_ROOTS_OF_LIFE_GAME"],
requires=["gui"],
stack_size=1 * 1024,
fap_icon="roots_of_life_10px.png",
fap_category="Games",
fap_icon_assets="images",
fap_icon_assets_symbol="roots_of_life_game",
fap_author="@Xorboo",
fap_weburl="https://github.com/Xorboo/root-of-life",
fap_version="1.0",
fap_description="A zen-puzzle game for FlipperZero, puzzle made on GlobalGameJam23 (theme: Roots)",
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,751 +0,0 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <gui/view.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include "roots_of_life_game_icons.h"
#include <assets_icons.h>
#define TAG "RootsOfLife"
// Flipper
#define FLIPPER_LCD_WIDTH 128
#define FLIPPER_LCD_HEIGHT 64
// General
#define GROUND_HEIGHT 10
#define CELL_SIZE 3
#define FIELD_START_X 0
#define FIELD_START_Y (GROUND_HEIGHT + 1)
#define CELLS_X (FLIPPER_LCD_WIDTH / CELL_SIZE)
#define CELLS_Y ((FLIPPER_LCD_HEIGHT - GROUND_HEIGHT) / CELL_SIZE)
#define CELLS_TOTAL (CELLS_Y * CELLS_X)
#define CELL(Y, X) (Y * CELLS_X + X)
// Root Spawn
#define ROOT_SIZE_X 7
#define ROOT_SIZE_Y 7
#define ROOT(Y, X) ((Y)*ROOT_SIZE_X + (X))
#define SPAWN_DIRECTIONS 2
#define GROW_STEPS 4
#define GROW_SAME_DIRECTION_CHANCE 70
#define RANDOM_GROW_ATTEMPTS 4
#define RANDOM_GROW_CHANCE 50
// UI
#define BLINK_PERIOD 12
#define BLINK_HIDE_FRAMES 5
#define TREE_HEIGHT 10
#define PICKUP_FREQUENCY 10
// Game
#define REROLLS_MAX 5
#define SCORE_FACTOR 10
#define PICKUPS_MIN 1
#define PICKUPS_MAX 5
#define PICKUPS_POINTS_FACTOR 10
typedef enum { EventTypeTick, EventTypeKey } EventType;
typedef enum {
R_NONE = 0,
R_UP = 0b1000,
R_DOWN = 0b0100,
R_LEFT = 0b0010,
R_RIGHT = 0b0001
} Direction;
typedef enum { StageStart, StageRun, StageOver } GameStage;
typedef struct {
bool initialDraw;
GameStage stage;
int tick;
bool* filledCells;
char* cells;
bool* pickups;
int collectedPickups;
bool* filledRootBase;
char* rootBase;
int rootSizeX;
int rootSizeY;
bool* filledRoot;
char* root;
int pX, pY;
int rerolls;
int score;
FuriMutex* mutex;
} GameState;
typedef struct {
EventType type;
InputEvent input;
} GameEvent;
static Direction rand_dir() {
int r = rand() % 4;
return 1 << r;
}
static Direction reverse_dir(Direction dir) {
switch(dir) {
case R_UP:
return R_DOWN;
case R_DOWN:
return R_UP;
case R_LEFT:
return R_RIGHT;
case R_RIGHT:
return R_LEFT;
default:
return R_NONE;
}
}
static int rand_range(int min, int max) {
return min + rand() % (max - min);
}
static bool rand_chance(int chance) {
return (rand() % 100) < chance;
}
static bool has_intersection(char cellA, char cellB) {
return cellA & cellB;
}
static int root_index(GameState* state, int y, int x) {
return y * state->rootSizeX + x;
}
static void set_cell(GameState* state, int y, int x, char cellRoot) {
int c = CELL(y, x);
state->filledCells[c] = true;
state->cells[c] = cellRoot;
}
static void game_state_init(GameState* state) {
state->initialDraw = false;
state->tick = 0;
// Init field arrays
state->filledCells = (bool*)malloc(CELLS_TOTAL * sizeof(bool));
state->cells = (char*)malloc(CELLS_TOTAL * sizeof(char));
state->pickups = (bool*)malloc(CELLS_TOTAL * sizeof(char));
state->rootBase = (char*)malloc(ROOT_SIZE_X * ROOT_SIZE_Y * sizeof(char));
state->filledRootBase = (bool*)malloc(ROOT_SIZE_X * ROOT_SIZE_Y * sizeof(bool));
state->root = NULL;
state->filledRoot = NULL;
for(int i = 0; i < CELLS_TOTAL; i++) {
state->filledCells[i] = false;
state->cells[i] = R_NONE;
state->pickups[i] = false;
}
}
static void free_root(GameState* state) {
if(state->root) free(state->root);
if(state->filledRoot) free(state->filledRoot);
}
static void game_state_free(GameState* state) {
free(state->filledCells);
free(state->cells);
free(state->pickups);
free(state->rootBase);
free(state->filledRootBase);
free_root(state);
}
/*static bool has_root(GameState* state, int x, int y) {
return x >= 0 && x < ROOT_SIZE_X && y >= 0 && y < ROOT_SIZE_Y &&
state->filledRootBase[ROOT(y, x)];
}*/
static void generate_new_root(GameState* state) {
for(int i = 0; i < ROOT_SIZE_X * ROOT_SIZE_Y; i++) {
state->filledRootBase[i] = false;
state->rootBase[i] = R_NONE;
}
int cX = ROOT_SIZE_X / 2;
int cY = ROOT_SIZE_Y / 2;
int c = ROOT(cY, cX);
state->filledRootBase[c] = true;
for(int i = 0; i < SPAWN_DIRECTIONS; i++) {
int pX = cX, pY = cY;
Direction oldDir = rand_dir();
for(int g = 0; g < GROW_STEPS; g++) {
Direction dir = rand_chance(GROW_SAME_DIRECTION_CHANCE) ? oldDir : rand_dir();
oldDir = dir;
int nX = pX - (dir & R_LEFT ? 1 : 0) + (dir & R_RIGHT ? 1 : 0);
int nY = pY - (dir & R_UP ? 1 : 0) + (dir & R_DOWN ? 1 : 0);
if(nX < 0 || nY < 0 || nX >= ROOT_SIZE_X || nY >= ROOT_SIZE_Y) continue;
int n = ROOT(nY, nX);
state->filledRootBase[n] = true;
// Connect points
int p = ROOT(pY, pX);
state->rootBase[p] |= dir;
state->rootBase[n] |= reverse_dir(dir);
// Grow from new point
pX = nX;
pY = nY;
}
}
for(int y = 0; y < ROOT_SIZE_Y; y++) {
for(int x = 0; x < ROOT_SIZE_X; x++) {
int c = ROOT(y, x);
if(!state->filledRootBase[c]) continue;
/*
if(has_root(state, x - 1, y)) state->rootBase[c] |= R_LEFT;
if(has_root(state, x + 1, y)) state->rootBase[c] |= R_RIGHT;
if(has_root(state, x, y - 1)) state->rootBase[c] |= R_UP;
if(has_root(state, x, y + 1)) state->rootBase[c] |= R_DOWN;
*/
for(int r = 0; r < RANDOM_GROW_ATTEMPTS; r++) {
if(!rand_chance(RANDOM_GROW_CHANCE)) continue;
state->rootBase[c] |= rand_dir();
}
}
}
// Copy root to real root
int minX = cX, maxX = cX, minY = cY, maxY = cY;
for(int y = 0; y < ROOT_SIZE_Y; y++) {
for(int x = 0; x < ROOT_SIZE_X; x++) {
int r = ROOT(y, x);
if(!state->filledRootBase[r]) continue;
minX = MIN(minX, x);
maxX = MAX(maxX, x);
minY = MIN(minY, y);
maxY = MAX(maxY, y);
}
}
// Clone to real root
state->rootSizeX = maxX - minX + 1;
state->rootSizeY = maxY - minY + 1;
free_root(state);
state->root = (char*)malloc(state->rootSizeX * state->rootSizeY * sizeof(char));
state->filledRoot = (bool*)malloc(state->rootSizeX * state->rootSizeY * sizeof(bool));
for(int y = 0; y < state->rootSizeY; y++) {
for(int x = 0; x < state->rootSizeX; x++) {
int c = root_index(state, y, x);
int r = ROOT(y + minY, x + minX);
state->filledRoot[c] = state->filledRootBase[r];
state->root[c] = state->rootBase[r];
}
}
}
static bool in_borders(int x, int y) {
return x >= 0 && y >= 0 && x < CELLS_X && y < CELLS_Y;
}
static char get_cell(GameState* state, int x, int y) {
if(!in_borders(x, y)) return R_NONE;
return state->cells[CELL(y, x)];
}
static bool get_filled_cell(GameState* state, int x, int y) {
if(!in_borders(x, y)) return false;
return state->filledCells[CELL(y, x)];
}
static bool can_place_root(GameState* state) {
bool hasConnection = false;
for(int y = 0; y < state->rootSizeY; y++) {
for(int x = 0; x < state->rootSizeX; x++) {
int r = root_index(state, y, x);
if(!state->filledRoot[r]) {
continue;
}
char root = state->root[r];
int rY = y + state->pY;
int rX = x + state->pX;
// Check if colliding
if(get_filled_cell(state, rX, rY)) {
char cell = get_cell(state, rX, rY);
if(has_intersection(cell, root)) {
return false;
}
hasConnection = true;
}
// Check neighbours
hasConnection |= (root & R_RIGHT) && (get_cell(state, rX + 1, rY) & R_LEFT);
hasConnection |= (root & R_LEFT) && (get_cell(state, rX - 1, rY) & R_RIGHT);
hasConnection |= (root & R_UP) && (get_cell(state, rX, rY - 1) & R_DOWN);
hasConnection |= (root & R_DOWN) && (get_cell(state, rX, rY + 1) & R_UP);
}
}
return hasConnection;
}
static bool try_place_root(GameState* state) {
if(!can_place_root(state)) return false;
for(int y = 0; y < state->rootSizeY; y++) {
for(int x = 0; x < state->rootSizeX; x++) {
int r = root_index(state, y, x);
if(!state->filledRoot[r]) continue;
int rY = y + state->pY;
int rX = x + state->pX;
// Root may be out of borders in rare cases (after new cpawn changed its size), just ignore that part
if(in_borders(rX, rY)) {
int c = CELL(rY, rX);
state->filledCells[c] = true;
state->cells[c] |= state->root[r];
}
}
}
return true;
}
static void reset_level(GameState* state) {
state->stage = StageStart;
state->tick = 0;
for(int i = 0; i < CELLS_TOTAL; i++) {
state->filledCells[i] = false;
state->cells[i] = R_NONE;
}
generate_new_root(state);
// Starting cells
int midX = CELLS_X / 2;
set_cell(state, 0, midX, R_UP | R_DOWN);
set_cell(state, 1, midX, R_UP | R_DOWN | R_LEFT | R_RIGHT);
set_cell(state, 1, midX - 1, R_RIGHT | R_DOWN);
set_cell(state, 1, midX + 1, R_LEFT | R_DOWN);
set_cell(state, 2, midX, R_UP);
state->pX = midX;
state->pY = 4;
state->rerolls = REROLLS_MAX;
state->score = 0;
state->collectedPickups = 0;
for(int i = 0, n = rand_range(PICKUPS_MIN, PICKUPS_MAX); i < n; i++) {
int x = rand_range(0, CELLS_X);
int y = rand_range(0, CELLS_Y);
state->pickups[CELL(y, x)] = true;
}
}
static void recalculate_score(GameState* state) {
int score = 0;
for(int i = 0; i < CELLS_TOTAL; i++) {
if(state->filledCells[i]) score++;
}
for(int i = 0; i < CELLS_TOTAL; i++) {
if(!state->pickups[i] || !state->filledCells[i]) continue;
state->pickups[i] = false;
state->collectedPickups++;
state->rerolls++;
}
state->score = (score + state->collectedPickups * PICKUPS_POINTS_FACTOR) * SCORE_FACTOR;
}
static void draw_root_cell(Canvas* canvas, char root, int y, int x, bool isHidden) {
int posX = FIELD_START_X + x * CELL_SIZE + 1, posY = FIELD_START_Y + y * CELL_SIZE + 1;
canvas_draw_dot(canvas, posX, posY);
if(isHidden) {
canvas_set_color(canvas, ColorXOR);
}
if(root & R_UP) canvas_draw_dot(canvas, posX, posY - 1);
if(root & R_DOWN) canvas_draw_dot(canvas, posX, posY + 1);
if(root & R_LEFT) canvas_draw_dot(canvas, posX - 1, posY);
if(root & R_RIGHT) canvas_draw_dot(canvas, posX + 1, posY);
if(isHidden) {
canvas_set_color(canvas, ColorBlack);
}
}
static void draw_placed_roots(Canvas* canvas, GameState* state) {
for(int y = 0; y < CELLS_Y; y++) {
for(int x = 0; x < CELLS_X; x++) {
int c = CELL(y, x);
if(!state->filledCells[c]) continue;
draw_root_cell(canvas, state->cells[c], y, x, false);
}
}
}
static void draw_pickup(Canvas* canvas, GameState* state, int y, int x) {
int posX = FIELD_START_X + x * CELL_SIZE + 1, posY = FIELD_START_Y + y * CELL_SIZE + 1;
int stage = state->tick / PICKUP_FREQUENCY;
if(stage++ % 4 < 3) canvas_draw_dot(canvas, posX + 1, posY);
if(stage++ % 4 < 3) canvas_draw_dot(canvas, posX, posY + 1);
if(stage++ % 4 < 3) canvas_draw_dot(canvas, posX - 1, posY);
if(stage++ % 4 < 3) canvas_draw_dot(canvas, posX, posY - 1);
}
static void draw_pickups(Canvas* canvas, GameState* state) {
for(int y = 0; y < CELLS_Y; y++) {
for(int x = 0; x < CELLS_X; x++) {
int c = CELL(y, x);
if(!state->pickups[c]) continue;
draw_pickup(canvas, state, y, x);
}
}
}
static void draw_active_root(Canvas* canvas, GameState* state) {
bool isHidden = (state->tick % BLINK_PERIOD) < BLINK_HIDE_FRAMES;
for(int y = 0; y < state->rootSizeY; y++) {
for(int x = 0; x < state->rootSizeX; x++) {
int c = root_index(state, y, x);
if(!state->filledRoot[c]) continue;
int realX = x + state->pX;
int realY = y + state->pY;
draw_root_cell(canvas, state->root[c], realY, realX, isHidden);
}
}
}
#if DRAW_DEBUG
static void draw_generated_root(Canvas* canvas, GameState* state) {
bool isHidden = (state->tick % BLINK_PERIOD) < BLINK_HIDE_FRAMES;
for(int y = 0; y < ROOT_SIZE_Y; y++) {
for(int x = 0; x < ROOT_SIZE_X; x++) {
int c = ROOT(y, x);
if(!state->filledRootBase[c]) continue;
int realX = x + 1;
int realY = y + 1;
draw_root_cell(canvas, state->rootBase[c], realY, realX, isHidden);
}
}
}
#endif
static void draw_ground(Canvas* canvas, GameState* state) {
canvas_draw_line(canvas, 0, GROUND_HEIGHT, FLIPPER_LCD_WIDTH, GROUND_HEIGHT);
UNUSED(state);
}
static void draw_tree(Canvas* canvas, GameState* state) {
canvas_draw_icon(canvas, FLIPPER_LCD_WIDTH / 2 - 5, GROUND_HEIGHT - TREE_HEIGHT, &I_tree);
UNUSED(state);
}
static void draw_placement(Canvas* canvas, GameState* state) {
bool canPlace = can_place_root(state);
canvas_draw_icon(canvas, FLIPPER_LCD_WIDTH - 10, 0, canPlace ? &I_place_ok : &I_place_error);
}
static void draw_rerolls(Canvas* canvas, GameState* state) {
UNUSED(canvas);
UNUSED(state);
canvas_draw_icon(canvas, 0, 0, &I_root_reroll);
// Ugh
FuriString* tmp_string = furi_string_alloc();
furi_string_printf(tmp_string, "%d", MAX(0, state->rerolls));
canvas_draw_str(canvas, 11, 9, furi_string_get_cstr(tmp_string));
furi_string_free(tmp_string);
}
static void draw_score(Canvas* canvas, GameState* state) {
UNUSED(canvas);
UNUSED(state);
int x = FLIPPER_LCD_WIDTH / 2 + 15;
canvas_draw_icon(canvas, x, 0, &I_score);
// Ugh
FuriString* tmp_string = furi_string_alloc();
furi_string_printf(tmp_string, "%d", MAX(0, state->score));
canvas_draw_str(canvas, x + 11, 9, furi_string_get_cstr(tmp_string));
furi_string_free(tmp_string);
}
static void draw_gui(Canvas* canvas, GameState* state) {
draw_ground(canvas, state);
draw_tree(canvas, state);
draw_placement(canvas, state);
draw_rerolls(canvas, state);
draw_score(canvas, state);
}
static void draw_center_box(Canvas* canvas, int w2, int h2, int margin) {
int x = FLIPPER_LCD_WIDTH / 2 - w2;
int y = FLIPPER_LCD_HEIGHT / 2 - h2;
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(
canvas, x - margin - 1, y - margin - 1, (w2 + margin + 1) * 2, (h2 + margin + 1) * 2);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, x - margin, y - margin, (w2 + margin) * 2, (h2 + margin) * 2);
}
static void draw_start_ui(Canvas* canvas, GameState* state) {
int w2 = 40;
int margin = 3;
int h2 = 10;
draw_center_box(canvas, w2, h2, margin);
int x = FLIPPER_LCD_WIDTH / 2 - w2;
int y = FLIPPER_LCD_HEIGHT / 2 - h2;
canvas_draw_str(canvas, x + 1, y + 9, " Grow your roots ");
canvas_draw_str(canvas, x + 1, y + 18, "Press [OK] to start");
UNUSED(state);
}
static void draw_end_ui(Canvas* canvas, GameState* state) {
int w2 = 46;
int margin = 3;
int h2 = 15;
draw_center_box(canvas, w2, h2, margin);
int x = FLIPPER_LCD_WIDTH / 2 - w2;
int y = FLIPPER_LCD_HEIGHT / 2 - h2;
canvas_draw_str(canvas, x + 1, y + 9, " Game Over ");
FuriString* tmp_string = furi_string_alloc();
furi_string_printf(tmp_string, "You've got %d points", MAX(0, state->score));
canvas_draw_str(canvas, x + 1, y + 19, furi_string_get_cstr(tmp_string));
furi_string_free(tmp_string);
canvas_draw_str(canvas, x + 2, y + 29, "Press [OK] to restart");
int h = 13, w = 54;
canvas_set_color(canvas, ColorWhite);
canvas_draw_box(canvas, 0, FLIPPER_LCD_HEIGHT - h, w + 1, h + 1);
canvas_set_color(canvas, ColorBlack);
canvas_draw_frame(canvas, 0, FLIPPER_LCD_HEIGHT - h, w, h);
canvas_draw_str(canvas, 2, FLIPPER_LCD_HEIGHT - 3, "by @Xorboo");
UNUSED(state);
}
static void roots_draw_callback(Canvas* const canvas, void* ctx) {
furi_assert(ctx);
GameState* state = ctx;
furi_mutex_acquire(state->mutex, FuriWaitForever);
if(!state->initialDraw) {
state->initialDraw = true;
canvas_set_font(canvas, FontSecondary);
reset_level(state);
}
state->tick++;
draw_gui(canvas, state);
draw_placed_roots(canvas, state);
draw_pickups(canvas, state);
switch(state->stage) {
case StageStart:
draw_start_ui(canvas, state);
break;
case StageRun:
draw_active_root(canvas, state);
#if DRAW_DEBUG
draw_generated_root(canvas, state);
#endif
break;
case StageOver:
draw_end_ui(canvas, state);
break;
}
furi_mutex_release(state->mutex);
}
static void roots_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
GameEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void roots_update_timer_callback(FuriMessageQueue* event_queue) {
furi_assert(event_queue);
GameEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
static void ProcessStartInput(GameState* state, InputKey key) {
if(key == InputKeyOk) {
state->stage = StageRun;
}
}
static void ProcessRunInput(GameState* state, InputKey key) {
switch(key) {
case InputKeyRight:
state->pX = MIN(state->pX + 1, CELLS_X - state->rootSizeX);
break;
case InputKeyLeft:
state->pX = MAX(state->pX - 1, 0);
break;
case InputKeyUp:
state->pY = MAX(state->pY - 1, 0);
break;
case InputKeyDown:
state->pY = MIN(state->pY + 1, CELLS_Y - state->rootSizeY);
break;
case InputKeyOk: {
bool rootPlaced = try_place_root(state);
if(rootPlaced) {
recalculate_score(state);
generate_new_root(state);
} else {
state->rerolls--;
if(state->rerolls >= 0) {
generate_new_root(state);
} else {
state->stage = StageOver;
}
}
break;
}
default:
break;
}
}
static void ProcessOverInput(GameState* state, InputKey key) {
if(key == InputKeyOk) {
state->stage = StageStart;
reset_level(state);
}
}
int32_t roots_of_life_game_app(void* p) {
FURI_LOG_D(TAG, "Starting game...");
UNUSED(p);
int32_t return_code = 0;
// Set random seed from interrR_UPts
srand(DWT->CYCCNT);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
GameState* state = malloc(sizeof(GameState));
game_state_init(state);
state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!state->mutex) {
FURI_LOG_E(TAG, "Cannot create mutex\r\n");
return_code = 255;
goto free_and_exit;
}
// Set system callbacks
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, roots_draw_callback, state);
view_port_input_callback_set(view_port, roots_input_callback, event_queue);
FuriTimer* timer =
furi_timer_alloc(roots_update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
FURI_LOG_D(TAG, "Entering game loop...");
GameEvent event;
for(bool processing = true; processing;) {
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
furi_mutex_acquire(state->mutex, FuriWaitForever);
if(event_status == FuriStatusOk) {
// Key events
if(event.type == EventTypeKey) {
//FURI_LOG_D(TAG, "Got key: %d", event.input.key);
if(event.input.type == InputTypePress || event.input.type == InputTypeLong ||
event.input.type == InputTypeRepeat) {
if(event.input.key == InputKeyBack) {
processing = false;
}
switch(state->stage) {
case StageStart:
ProcessStartInput(state, event.input.key);
break;
case StageRun:
ProcessRunInput(state, event.input.key);
break;
case StageOver:
ProcessOverInput(state, event.input.key);
break;
}
}
}
}
view_port_update(view_port);
furi_mutex_release(state->mutex);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
view_port_free(view_port);
furi_mutex_free(state->mutex);
free_and_exit:
furi_message_queue_free(event_queue);
//FURI_LOG_D(TAG, "Quitting game...");
game_state_free(state);
free(state);
return return_code;
}

View File

@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -1,15 +0,0 @@
App(
appid="scorched_tanks",
name="Scorched Tanks",
apptype=FlipperAppType.EXTERNAL,
entry_point="scorched_tanks_game_app",
cdefines=["APP_SCORCHED_TANKS_GAME"],
requires=["gui"],
stack_size=1 * 1024,
fap_icon="scorchedTanks_10px.png",
fap_category="Games",
fap_author="@jasniec",
fap_weburl="https://github.com/jasniec/flipper-scorched-tanks-game",
fap_version="1.1",
fap_description="A Flipper Zero game inspired by scorched earth",
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 536 B

View File

@@ -1,546 +0,0 @@
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <stdlib.h>
#include <math.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define PLAYER_INIT_LOCATION_X 20
#define PLAYER_INIT_AIM 45
#define PLAYER_INIT_POWER 50
#define ENEMY_INIT_LOCATION_X 108
#define TANK_BARREL_LENGTH 8
#define GRAVITY_FORCE (double)0.5
#define MIN_GROUND_HEIGHT 35
#define MAX_GROUND_HEIGHT 55
#define MAX_FIRE_POWER 100
#define MIN_FIRE_POWER 0
#define TANK_COLLIDER_SIZE 3
#define MAX_WIND 10
#define MAX_PLAYER_DIFF_X 20
#define MAX_ENEMY_DIFF_X 20
// That's a filthy workaround but sin(player.aimAngle) breaks it all... If you're able to fix it, please do create a PR!
double scorched_tanks_sin[91] = {
0.000, -0.017, -0.035, -0.052, -0.070, -0.087, -0.105, -0.122, -0.139, -0.156, -0.174, -0.191,
-0.208, -0.225, -0.242, -0.259, -0.276, -0.292, -0.309, -0.326, -0.342, -0.358, -0.375, -0.391,
-0.407, -0.423, -0.438, -0.454, -0.469, -0.485, -0.500, -0.515, -0.530, -0.545, -0.559, -0.574,
-0.588, -0.602, -0.616, -0.629, -0.643, -0.656, -0.669, -0.682, -0.695, -0.707, -0.719, -0.731,
-0.743, -0.755, -0.766, -0.777, -0.788, -0.799, -0.809, -0.819, -0.829, -0.839, -0.848, -0.857,
-0.866, -0.875, -0.883, -0.891, -0.899, -0.906, -0.914, -0.921, -0.927, -0.934, -0.940, -0.946,
-0.951, -0.956, -0.961, -0.966, -0.970, -0.974, -0.978, -0.982, -0.985, -0.988, -0.990, -0.993,
-0.995, -0.996, -0.998, -0.999, -0.999, -1.000, -1.000};
double scorched_tanks_cos[91] = {
1.000, 1.000, 0.999, 0.999, 0.998, 0.996, 0.995, 0.993, 0.990, 0.988, 0.985, 0.982, 0.978,
0.974, 0.970, 0.966, 0.961, 0.956, 0.951, 0.946, 0.940, 0.934, 0.927, 0.921, 0.914, 0.906,
0.899, 0.891, 0.883, 0.875, 0.866, 0.857, 0.848, 0.839, 0.829, 0.819, 0.809, 0.799, 0.788,
0.777, 0.766, 0.755, 0.743, 0.731, 0.719, 0.707, 0.695, 0.682, 0.669, 0.656, 0.643, 0.629,
0.616, 0.602, 0.588, 0.574, 0.559, 0.545, 0.530, 0.515, 0.500, 0.485, 0.469, 0.454, 0.438,
0.423, 0.407, 0.391, 0.375, 0.358, 0.342, 0.326, 0.309, 0.292, 0.276, 0.259, 0.242, 0.225,
0.208, 0.191, 0.174, 0.156, 0.139, 0.122, 0.105, 0.087, 0.070, 0.052, 0.035, 0.017, 0.000};
double scorched_tanks_tan[91] = {
0.000, -0.017, -0.035, -0.052, -0.070, -0.087, -0.105, -0.123, -0.141, -0.158, -0.176,
-0.194, -0.213, -0.231, -0.249, -0.268, -0.287, -0.306, -0.325, -0.344, -0.364, -0.384,
-0.404, -0.424, -0.445, -0.466, -0.488, -0.510, -0.532, -0.554, -0.577, -0.601, -0.625,
-0.649, -0.674, -0.700, -0.727, -0.754, -0.781, -0.810, -0.839, -0.869, -0.900, -0.932,
-0.966, -1.000, -1.036, -1.072, -1.111, -1.150, -1.192, -1.235, -1.280, -1.327, -1.376,
-1.428, -1.483, -1.540, -1.600, -1.664, -1.732, -1.804, -1.881, -1.963, -2.050, -2.144,
-2.246, -2.356, -2.475, -2.605, -2.747, -2.904, -3.078, -3.271, -3.487, -3.732, -4.011,
-4.331, -4.704, -5.144, -5.671, -6.313, -7.115, -8.144, -9.513, -11.429, -14.298, -19.077,
-28.627, -57.254, -90747.269};
uint8_t scorched_tanks_ground_modifiers[SCREEN_WIDTH] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 28, 26, 24, 22, 20,
18, 16, 14, 12, 10, 8, 6, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
typedef struct {
// +-----x
// |
// |
// y
uint8_t x;
uint8_t y;
} Point;
typedef struct {
// +-----x
// |
// |
// y
double x;
double y;
} PointDetailed;
typedef struct {
uint8_t locationX;
uint8_t hp;
int aimAngle;
uint8_t firePower;
} Tank;
typedef struct {
Point ground[SCREEN_WIDTH];
Tank player;
Tank enemy;
bool isPlayerTurn;
bool isShooting;
int windSpeed;
Point trajectory[SCREEN_WIDTH];
uint8_t trajectoryAnimationStep;
PointDetailed bulletPosition;
PointDetailed bulletVector;
FuriMutex* mutex;
} Game;
typedef enum {
EventTypeTick,
EventTypeKey,
} EventType;
typedef struct {
EventType type;
InputEvent input;
} ScorchedTanksEvent;
int scorched_tanks_random(int min, int max) {
return min + rand() % ((max + 1) - min);
}
void scorched_tanks_generate_ground(Game* game_state) {
int lastHeight = 45;
for(uint8_t a = 0; a < SCREEN_WIDTH; a++) {
int diffHeight = scorched_tanks_random(-2, 3);
int changeLength = scorched_tanks_random(1, 6);
if(diffHeight == 0) {
changeLength = 1;
}
for(int b = 0; b < changeLength; b++) {
if(a + b < SCREEN_WIDTH) {
int index = a + b;
int newPoint = lastHeight + diffHeight;
newPoint = newPoint < MIN_GROUND_HEIGHT ? MIN_GROUND_HEIGHT : newPoint;
newPoint = newPoint > MAX_GROUND_HEIGHT ? MAX_GROUND_HEIGHT : newPoint;
game_state->ground[index].x = index;
game_state->ground[index].y = newPoint - scorched_tanks_ground_modifiers[a];
lastHeight = newPoint;
} else {
a += b;
break;
}
}
a += changeLength - 1;
}
}
void scorched_tanks_init_game(Game* game_state) {
game_state->player.locationX = PLAYER_INIT_LOCATION_X +
scorched_tanks_random(0, MAX_PLAYER_DIFF_X) -
MAX_PLAYER_DIFF_X / 2;
game_state->player.aimAngle = PLAYER_INIT_AIM;
game_state->player.firePower = PLAYER_INIT_POWER;
game_state->enemy.aimAngle = PLAYER_INIT_AIM;
game_state->enemy.firePower = PLAYER_INIT_POWER;
game_state->enemy.locationX =
ENEMY_INIT_LOCATION_X + scorched_tanks_random(0, MAX_ENEMY_DIFF_X) - MAX_ENEMY_DIFF_X / 2;
game_state->isPlayerTurn = true;
game_state->windSpeed = scorched_tanks_random(0, MAX_WIND);
for(int x = 0; x < SCREEN_WIDTH; x++) {
game_state->trajectory[x].x = 0;
game_state->trajectory[x].y = 0;
}
scorched_tanks_generate_ground(game_state);
}
void scorched_tanks_calculate_trajectory(Game* game_state) {
if(game_state->isShooting) {
game_state->bulletVector.x += ((double)game_state->windSpeed - MAX_WIND / 2) / 40;
game_state->bulletVector.y += GRAVITY_FORCE;
game_state->bulletPosition.x += game_state->bulletVector.x;
game_state->bulletPosition.y += game_state->bulletVector.y;
int totalDistanceToEnemy = 100;
if(game_state->isPlayerTurn) {
double distanceToEnemyX = game_state->enemy.locationX - game_state->bulletPosition.x;
double distanceToEnemyY = game_state->ground[game_state->enemy.locationX].y -
TANK_COLLIDER_SIZE - game_state->bulletPosition.y;
totalDistanceToEnemy =
sqrt(distanceToEnemyX * distanceToEnemyX + distanceToEnemyY * distanceToEnemyY);
} else {
double distanceToEnemyX = game_state->player.locationX - game_state->bulletPosition.x;
double distanceToEnemyY = game_state->ground[game_state->player.locationX].y -
TANK_COLLIDER_SIZE - game_state->bulletPosition.y;
totalDistanceToEnemy =
sqrt(distanceToEnemyX * distanceToEnemyX + distanceToEnemyY * distanceToEnemyY);
}
if(totalDistanceToEnemy <= TANK_COLLIDER_SIZE) {
game_state->isShooting = false;
scorched_tanks_init_game(game_state);
game_state->isPlayerTurn = !game_state->isPlayerTurn;
return;
}
if(game_state->bulletPosition.x > SCREEN_WIDTH ||
game_state->bulletPosition.y >
game_state->ground[(int)round(game_state->bulletPosition.x)].y) {
game_state->isShooting = false;
game_state->bulletPosition.x = 0;
game_state->bulletPosition.y = 0;
game_state->windSpeed = scorched_tanks_random(0, MAX_WIND);
game_state->isPlayerTurn = !game_state->isPlayerTurn;
return;
}
if(game_state->bulletPosition.y > 0) {
game_state->trajectory[game_state->trajectoryAnimationStep].x =
round(game_state->bulletPosition.x);
game_state->trajectory[game_state->trajectoryAnimationStep].y =
round(game_state->bulletPosition.y);
game_state->trajectoryAnimationStep++;
}
}
}
static void scorched_tanks_draw_tank(Canvas* const canvas, uint8_t x, uint8_t y, bool isPlayer) {
uint8_t lineIndex = 0;
if(isPlayer) {
// Draw tank base
canvas_draw_line(canvas, x - 3, y - lineIndex, x + 3, y - lineIndex);
lineIndex++;
canvas_draw_line(canvas, x - 4, y - lineIndex, x + 4, y - lineIndex);
lineIndex++;
canvas_draw_line(canvas, x - 4, y - lineIndex, x + 4, y - lineIndex);
lineIndex++;
// draw turret
canvas_draw_line(canvas, x - 2, y - lineIndex, x + 1, y - lineIndex);
lineIndex++;
canvas_draw_line(canvas, x - 2, y - lineIndex, x, y - lineIndex);
lineIndex++;
} else {
// Draw tank base
canvas_draw_line(canvas, x - 3, y - lineIndex, x + 3, y - lineIndex);
lineIndex++;
canvas_draw_line(canvas, x - 4, y - lineIndex, x + 4, y - lineIndex);
lineIndex++;
canvas_draw_line(canvas, x - 4, y - lineIndex, x + 4, y - lineIndex);
lineIndex++;
// draw turret
canvas_draw_line(canvas, x - 1, y - lineIndex, x + 2, y - lineIndex);
lineIndex++;
canvas_draw_line(canvas, x, y - lineIndex, x + 2, y - lineIndex);
lineIndex++;
}
}
static void scorched_tanks_render_callback(Canvas* const canvas, void* ctx) {
furi_assert(ctx);
const Game* game_state = ctx;
furi_mutex_acquire(game_state->mutex, FuriWaitForever);
canvas_draw_frame(canvas, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
canvas_set_color(canvas, ColorBlack);
if(game_state->isShooting) {
canvas_draw_dot(canvas, game_state->bulletPosition.x, game_state->bulletPosition.y);
}
for(int a = 1; a < SCREEN_WIDTH; a++) {
canvas_draw_line(
canvas,
game_state->ground[a - 1].x,
game_state->ground[a - 1].y,
game_state->ground[a].x,
game_state->ground[a].y);
if(game_state->trajectory[a].y != 0) {
canvas_draw_dot(canvas, game_state->trajectory[a].x, game_state->trajectory[a].y);
}
}
scorched_tanks_draw_tank(
canvas,
game_state->enemy.locationX,
game_state->ground[game_state->enemy.locationX].y - TANK_COLLIDER_SIZE,
true);
scorched_tanks_draw_tank(
canvas,
game_state->player.locationX,
game_state->ground[game_state->player.locationX].y - TANK_COLLIDER_SIZE,
false);
int aimX1 = 0;
int aimY1 = 0;
int aimX2 = 0;
int aimY2 = 0;
if(game_state->isPlayerTurn) {
aimX1 = game_state->player.locationX;
aimY1 = game_state->ground[game_state->player.locationX].y - TANK_COLLIDER_SIZE;
double sinFromAngle = scorched_tanks_sin[game_state->player.aimAngle];
double cosFromAngle = scorched_tanks_cos[game_state->player.aimAngle];
aimX2 = aimX1 + TANK_BARREL_LENGTH * cosFromAngle;
aimY2 = aimY1 + TANK_BARREL_LENGTH * sinFromAngle;
aimX1 += 1;
aimX2 += 1;
} else {
aimX1 = game_state->enemy.locationX;
aimY1 = game_state->ground[game_state->enemy.locationX].y - TANK_COLLIDER_SIZE;
double sinFromAngle = scorched_tanks_sin[game_state->enemy.aimAngle];
double cosFromAngle = scorched_tanks_cos[game_state->enemy.aimAngle];
aimX2 = aimX1 + TANK_BARREL_LENGTH * cosFromAngle;
aimY2 = aimY1 + TANK_BARREL_LENGTH * sinFromAngle;
aimX2 = aimX1 - (aimX2 - aimX1);
aimX1 -= 1;
aimX2 -= 1;
}
canvas_draw_line(canvas, aimX1, aimY1 - 3, aimX2, aimY2 - 3);
canvas_set_font(canvas, FontSecondary);
char buffer2[18];
snprintf(buffer2, sizeof(buffer2), "wind: %i", game_state->windSpeed - MAX_WIND / 2);
canvas_draw_str(canvas, 55, 10, buffer2);
if(game_state->isPlayerTurn) {
canvas_draw_str(canvas, 93, 10, "player1");
char buffer[12];
snprintf(buffer, sizeof(buffer), "a: %u", game_state->player.aimAngle);
canvas_draw_str(canvas, 2, 10, buffer);
snprintf(buffer, sizeof(buffer), "p: %u", game_state->player.firePower);
canvas_draw_str(canvas, 27, 10, buffer);
} else {
canvas_draw_str(canvas, 93, 10, "player2");
char buffer[12];
snprintf(buffer, sizeof(buffer), "a: %u", game_state->enemy.aimAngle);
canvas_draw_str(canvas, 2, 10, buffer);
snprintf(buffer, sizeof(buffer), "p: %u", game_state->enemy.firePower);
canvas_draw_str(canvas, 27, 10, buffer);
}
furi_mutex_release(game_state->mutex);
}
static void scorched_tanks_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
furi_assert(event_queue);
ScorchedTanksEvent event = {.type = EventTypeKey, .input = *input_event};
furi_message_queue_put(event_queue, &event, FuriWaitForever);
}
static void scorched_tanks_update_timer_callback(FuriMessageQueue* event_queue) {
furi_assert(event_queue);
ScorchedTanksEvent event = {.type = EventTypeTick};
furi_message_queue_put(event_queue, &event, 0);
}
static void scorched_tanks_increase_power(Game* game_state) {
if(game_state->player.firePower < MAX_FIRE_POWER && !game_state->isShooting) {
if(game_state->isPlayerTurn && game_state->player.firePower < MAX_FIRE_POWER) {
game_state->player.firePower++;
}
if(!game_state->isPlayerTurn && game_state->enemy.firePower < MAX_FIRE_POWER) {
game_state->enemy.firePower++;
}
}
}
static void scorched_tanks_decrease_power(Game* game_state) {
if(game_state->player.firePower > MIN_FIRE_POWER && !game_state->isShooting) {
if(game_state->isPlayerTurn && game_state->player.firePower > MIN_FIRE_POWER) {
game_state->player.firePower--;
}
if(!game_state->isPlayerTurn && game_state->enemy.firePower > MIN_FIRE_POWER) {
game_state->enemy.firePower--;
}
}
}
static void scorched_tanks_aim_up(Game* game_state) {
if(!game_state->isShooting) {
if(game_state->isPlayerTurn && game_state->player.aimAngle < 90) {
game_state->player.aimAngle++;
}
if(!game_state->isPlayerTurn && game_state->enemy.aimAngle < 90) {
game_state->enemy.aimAngle++;
}
}
}
static void scorched_tanks_aim_down(Game* game_state) {
if(game_state->player.aimAngle > 0 && !game_state->isShooting) {
if(game_state->isPlayerTurn) {
game_state->player.aimAngle--;
} else {
game_state->enemy.aimAngle--;
}
}
}
const NotificationSequence sequence_long_vibro = {
&message_vibro_on,
&message_delay_500,
&message_vibro_off,
NULL,
};
static void scorched_tanks_fire(Game* game_state) {
if(!game_state->isShooting) {
if(game_state->isPlayerTurn) {
double sinFromAngle = scorched_tanks_sin[game_state->player.aimAngle];
double cosFromAngle = scorched_tanks_cos[game_state->player.aimAngle];
uint8_t aimX1 = game_state->player.locationX;
uint8_t aimY1 =
game_state->ground[game_state->player.locationX].y - TANK_COLLIDER_SIZE;
int aimX2 = aimX1 + TANK_BARREL_LENGTH * cosFromAngle;
int aimY2 = aimY1 + TANK_BARREL_LENGTH * sinFromAngle;
game_state->bulletPosition.x = aimX2;
game_state->bulletPosition.y = aimY2;
game_state->bulletVector.x = scorched_tanks_cos[game_state->player.aimAngle] *
((double)game_state->player.firePower / 10);
game_state->bulletVector.y = scorched_tanks_sin[game_state->player.aimAngle] *
((double)game_state->player.firePower / 10);
} else {
double sinFromAngle = scorched_tanks_sin[game_state->enemy.aimAngle];
double cosFromAngle = scorched_tanks_cos[game_state->enemy.aimAngle];
uint8_t aimX1 = game_state->enemy.locationX;
uint8_t aimY1 = game_state->ground[game_state->enemy.locationX].y - TANK_COLLIDER_SIZE;
int aimX2 = aimX1 + TANK_BARREL_LENGTH * cosFromAngle;
int aimY2 = aimY1 + TANK_BARREL_LENGTH * sinFromAngle;
aimX2 = aimX1 - (aimX2 - aimX1);
game_state->bulletPosition.x = aimX2;
game_state->bulletPosition.y = aimY2;
game_state->bulletVector.x = -scorched_tanks_cos[game_state->enemy.aimAngle] *
((double)game_state->enemy.firePower / 10);
game_state->bulletVector.y = scorched_tanks_sin[game_state->enemy.aimAngle] *
((double)game_state->enemy.firePower / 10);
}
game_state->trajectoryAnimationStep = 0;
for(int x = 0; x < SCREEN_WIDTH; x++) {
game_state->trajectory[x].x = 0;
game_state->trajectory[x].y = 0;
}
game_state->isShooting = true;
NotificationApp* notification = furi_record_open("notification");
notification_message(notification, &sequence_long_vibro);
notification_message(notification, &sequence_blink_white_100);
furi_record_close("notification");
}
}
int32_t scorched_tanks_game_app(void* p) {
UNUSED(p);
srand(DWT->CYCCNT);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(ScorchedTanksEvent));
Game* game_state = malloc(sizeof(Game));
scorched_tanks_init_game(game_state);
game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!game_state->mutex) {
FURI_LOG_E("ScorchedTanks", "cannot create mutex\r\n");
furi_message_queue_free(event_queue);
free(game_state);
return 255;
}
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, scorched_tanks_render_callback, game_state);
view_port_input_callback_set(view_port, scorched_tanks_input_callback, event_queue);
FuriTimer* timer =
furi_timer_alloc(scorched_tanks_update_timer_callback, FuriTimerTypePeriodic, event_queue);
furi_timer_start(timer, 2000);
// Open GUI and register view_port
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
ScorchedTanksEvent event;
for(bool processing = true; processing;) {
furi_message_queue_get(event_queue, &event, 50);
furi_mutex_acquire(game_state->mutex, FuriWaitForever);
if(event.type == EventTypeKey) { // && game->isPlayerTurn
if(event.input.type == InputTypeRepeat || event.input.type == InputTypeShort) {
switch(event.input.key) {
case InputKeyUp:
scorched_tanks_aim_up(game_state);
break;
case InputKeyDown:
scorched_tanks_aim_down(game_state);
break;
case InputKeyRight:
scorched_tanks_increase_power(game_state);
break;
case InputKeyLeft:
scorched_tanks_decrease_power(game_state);
break;
case InputKeyOk:
scorched_tanks_fire(game_state);
break;
case InputKeyBack:
processing = false;
break;
default:
break;
}
}
} else if(event.type == EventTypeTick) {
scorched_tanks_calculate_trajectory(game_state);
}
view_port_update(view_port);
furi_mutex_release(game_state->mutex);
}
furi_timer_free(timer);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
furi_record_close(RECORD_GUI);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_mutex_free(game_state->mutex);
free(game_state);
return 0;
}

View File

@@ -1,13 +0,0 @@
App(
appid="signal_generator",
name="[GPIO] Signal Generator",
apptype=FlipperAppType.EXTERNAL,
entry_point="signal_gen_app",
requires=["gui"],
stack_size=1 * 1024,
fap_description="Control GPIO pins to generate digital signals",
fap_version="1.0",
fap_icon="signal_gen_10px.png",
fap_category="GPIO",
fap_icon_assets="icons",
)

View File

@@ -1,30 +0,0 @@
#include "../signal_gen_app_i.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const signal_gen_scene_on_enter_handlers[])(void*) = {
#include "signal_gen_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const signal_gen_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "signal_gen_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const signal_gen_scene_on_exit_handlers[])(void* context) = {
#include "signal_gen_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers signal_gen_scene_handlers = {
.on_enter_handlers = signal_gen_scene_on_enter_handlers,
.on_event_handlers = signal_gen_scene_on_event_handlers,
.on_exit_handlers = signal_gen_scene_on_exit_handlers,
.scene_num = SignalGenSceneNum,
};

View File

@@ -1,29 +0,0 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) SignalGenScene##id,
typedef enum {
#include "signal_gen_scene_config.h"
SignalGenSceneNum,
} SignalGenScene;
#undef ADD_SCENE
extern const SceneManagerHandlers signal_gen_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "signal_gen_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "signal_gen_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "signal_gen_scene_config.h"
#undef ADD_SCENE

View File

@@ -1,3 +0,0 @@
ADD_SCENE(signal_gen, start, Start)
ADD_SCENE(signal_gen, pwm, Pwm)
ADD_SCENE(signal_gen, mco, Mco)

View File

@@ -1,145 +0,0 @@
#include "../signal_gen_app_i.h"
typedef enum {
LineIndexPin,
LineIndexSource,
LineIndexDivision,
} LineIndex;
static const char* const mco_pin_names[] = {
"13(Tx)",
};
static const char* const mco_source_names[] = {
"32768Hz",
"64MHz",
"~100K",
"~200K",
"~400K",
"~800K",
"~1MHz",
"~2MHz",
"~4MHz",
"~8MHz",
"~16MHz",
"~24MHz",
"~32MHz",
"~48MHz",
};
static const FuriHalClockMcoSourceId mco_sources[] = {
FuriHalClockMcoLse,
FuriHalClockMcoSysclk,
FuriHalClockMcoMsi100k,
FuriHalClockMcoMsi200k,
FuriHalClockMcoMsi400k,
FuriHalClockMcoMsi800k,
FuriHalClockMcoMsi1m,
FuriHalClockMcoMsi2m,
FuriHalClockMcoMsi4m,
FuriHalClockMcoMsi8m,
FuriHalClockMcoMsi16m,
FuriHalClockMcoMsi24m,
FuriHalClockMcoMsi32m,
FuriHalClockMcoMsi48m,
};
static const char* const mco_divisor_names[] = {
"1",
"2",
"4",
"8",
"16",
};
static const FuriHalClockMcoDivisorId mco_divisors[] = {
FuriHalClockMcoDiv1,
FuriHalClockMcoDiv2,
FuriHalClockMcoDiv4,
FuriHalClockMcoDiv8,
FuriHalClockMcoDiv16,
};
static void mco_source_list_change_callback(VariableItem* item) {
SignalGenApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, mco_source_names[index]);
app->mco_src = mco_sources[index];
view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate);
}
static void mco_divisor_list_change_callback(VariableItem* item) {
SignalGenApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, mco_divisor_names[index]);
app->mco_div = mco_divisors[index];
view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate);
}
void signal_gen_scene_mco_on_enter(void* context) {
SignalGenApp* app = context;
VariableItemList* var_item_list = app->var_item_list;
VariableItem* item;
item = variable_item_list_add(var_item_list, "GPIO Pin", COUNT_OF(mco_pin_names), NULL, NULL);
variable_item_set_current_value_index(item, 0);
variable_item_set_current_value_text(item, mco_pin_names[0]);
item = variable_item_list_add(
var_item_list,
"Frequency",
COUNT_OF(mco_source_names),
mco_source_list_change_callback,
app);
variable_item_set_current_value_index(item, 0);
variable_item_set_current_value_text(item, mco_source_names[0]);
item = variable_item_list_add(
var_item_list,
"Freq. divider",
COUNT_OF(mco_divisor_names),
mco_divisor_list_change_callback,
app);
variable_item_set_current_value_index(item, 0);
variable_item_set_current_value_text(item, mco_divisor_names[0]);
variable_item_list_set_selected_item(var_item_list, LineIndexSource);
view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewVarItemList);
app->mco_src = FuriHalClockMcoLse;
app->mco_div = FuriHalClockMcoDiv1;
furi_hal_clock_mco_enable(app->mco_src, app->mco_div);
furi_hal_gpio_init_ex(
&gpio_usart_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn0MCO);
}
bool signal_gen_scene_mco_on_event(void* context, SceneManagerEvent event) {
SignalGenApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SignalGenMcoEventUpdate) {
consumed = true;
furi_hal_clock_mco_enable(app->mco_src, app->mco_div);
}
}
return consumed;
}
void signal_gen_scene_mco_on_exit(void* context) {
SignalGenApp* app = context;
variable_item_list_reset(app->var_item_list);
furi_hal_gpio_init_ex(
&gpio_usart_tx,
GpioModeAltFunctionPushPull,
GpioPullUp,
GpioSpeedVeryHigh,
GpioAltFn7USART1);
furi_hal_clock_mco_disable();
}

View File

@@ -1,79 +0,0 @@
#include "../signal_gen_app_i.h"
static const FuriHalPwmOutputId pwm_ch_id[] = {
FuriHalPwmOutputIdTim1PA7,
FuriHalPwmOutputIdLptim2PA4,
};
#define DEFAULT_FREQ 1000
#define DEFAULT_DUTY 50
static void
signal_gen_pwm_callback(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context) {
SignalGenApp* app = context;
app->pwm_freq = freq;
app->pwm_duty = duty;
if(app->pwm_ch != pwm_ch_id[channel_id]) { //-V1051
app->pwm_ch_prev = app->pwm_ch;
app->pwm_ch = pwm_ch_id[channel_id];
view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventChannelChange);
} else {
app->pwm_ch = pwm_ch_id[channel_id]; //-V1048
view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventUpdate);
}
}
void signal_gen_scene_pwm_on_enter(void* context) {
SignalGenApp* app = context;
view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewPwm);
signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app);
signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY);
if(!furi_hal_pwm_is_running(pwm_ch_id[0])) {
furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY);
} else {
furi_hal_pwm_stop(pwm_ch_id[0]);
furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY);
}
}
bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) {
SignalGenApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SignalGenPwmEventUpdate) {
consumed = true;
furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty);
} else if(event.event == SignalGenPwmEventChannelChange) {
consumed = true;
// Stop previous channel PWM
if(furi_hal_pwm_is_running(app->pwm_ch_prev)) {
furi_hal_pwm_stop(app->pwm_ch_prev);
}
// Start PWM and restart if it was starter already
if(furi_hal_pwm_is_running(app->pwm_ch)) {
furi_hal_pwm_stop(app->pwm_ch);
furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty);
} else {
furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty);
}
}
}
return consumed;
}
void signal_gen_scene_pwm_on_exit(void* context) {
SignalGenApp* app = context;
variable_item_list_reset(app->var_item_list);
if(furi_hal_pwm_is_running(app->pwm_ch)) {
furi_hal_pwm_stop(app->pwm_ch);
}
}

View File

@@ -1,55 +0,0 @@
#include "../signal_gen_app_i.h"
typedef enum {
SubmenuIndexPwm,
SubmenuIndexClockOutput,
} SubmenuIndex;
void signal_gen_scene_start_submenu_callback(void* context, uint32_t index) {
SignalGenApp* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, index);
}
void signal_gen_scene_start_on_enter(void* context) {
SignalGenApp* app = context;
Submenu* submenu = app->submenu;
submenu_add_item(
submenu, "PWM Generator", SubmenuIndexPwm, signal_gen_scene_start_submenu_callback, app);
submenu_add_item(
submenu,
"Clock Generator",
SubmenuIndexClockOutput,
signal_gen_scene_start_submenu_callback,
app);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(app->scene_manager, SignalGenSceneStart));
view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewSubmenu);
}
bool signal_gen_scene_start_on_event(void* context, SceneManagerEvent event) {
SignalGenApp* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == SubmenuIndexPwm) {
scene_manager_next_scene(app->scene_manager, SignalGenScenePwm);
consumed = true;
} else if(event.event == SubmenuIndexClockOutput) {
scene_manager_next_scene(app->scene_manager, SignalGenSceneMco);
consumed = true;
}
scene_manager_set_scene_state(app->scene_manager, SignalGenSceneStart, event.event);
}
return consumed;
}
void signal_gen_scene_start_on_exit(void* context) {
SignalGenApp* app = context;
submenu_reset(app->submenu);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,93 +0,0 @@
#include "signal_gen_app_i.h"
#include <furi.h>
#include <furi_hal.h>
static bool signal_gen_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
SignalGenApp* app = context;
return scene_manager_handle_custom_event(app->scene_manager, event);
}
static bool signal_gen_app_back_event_callback(void* context) {
furi_assert(context);
SignalGenApp* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
static void signal_gen_app_tick_event_callback(void* context) {
furi_assert(context);
SignalGenApp* app = context;
scene_manager_handle_tick_event(app->scene_manager);
}
SignalGenApp* signal_gen_app_alloc() {
SignalGenApp* app = malloc(sizeof(SignalGenApp));
app->gui = furi_record_open(RECORD_GUI);
app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&signal_gen_scene_handlers, app);
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(
app->view_dispatcher, signal_gen_app_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, signal_gen_app_back_event_callback);
view_dispatcher_set_tick_event_callback(
app->view_dispatcher, signal_gen_app_tick_event_callback, 100);
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
app->var_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
app->view_dispatcher,
SignalGenViewVarItemList,
variable_item_list_get_view(app->var_item_list));
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, SignalGenViewSubmenu, submenu_get_view(app->submenu));
app->pwm_view = signal_gen_pwm_alloc();
view_dispatcher_add_view(
app->view_dispatcher, SignalGenViewPwm, signal_gen_pwm_get_view(app->pwm_view));
scene_manager_next_scene(app->scene_manager, SignalGenSceneStart);
return app;
}
void signal_gen_app_free(SignalGenApp* app) {
furi_assert(app);
// Views
view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewVarItemList);
view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewSubmenu);
view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewPwm);
submenu_free(app->submenu);
variable_item_list_free(app->var_item_list);
signal_gen_pwm_free(app->pwm_view);
// View dispatcher
view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager);
// Close records
furi_record_close(RECORD_GUI);
free(app);
}
int32_t signal_gen_app(void* p) {
UNUSED(p);
SignalGenApp* signal_gen_app = signal_gen_app_alloc();
view_dispatcher_run(signal_gen_app->view_dispatcher);
signal_gen_app_free(signal_gen_app);
return 0;
}

View File

@@ -1,46 +0,0 @@
#pragma once
#include "scenes/signal_gen_scene.h"
#include <furi_hal_clock.h>
#include <furi_hal_pwm.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h>
#include <gui/modules/submenu.h>
#include "views/signal_gen_pwm.h"
typedef struct SignalGenApp SignalGenApp;
struct SignalGenApp {
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
VariableItemList* var_item_list;
Submenu* submenu;
SignalGenPwm* pwm_view;
FuriHalClockMcoSourceId mco_src;
FuriHalClockMcoDivisorId mco_div;
FuriHalPwmOutputId pwm_ch_prev;
FuriHalPwmOutputId pwm_ch;
uint32_t pwm_freq;
uint8_t pwm_duty;
};
typedef enum {
SignalGenViewVarItemList,
SignalGenViewSubmenu,
SignalGenViewPwm,
} SignalGenAppView;
typedef enum {
SignalGenMcoEventUpdate,
SignalGenPwmEventUpdate,
SignalGenPwmEventChannelChange,
} SignalGenCustomEvent;

View File

@@ -1,311 +0,0 @@
#include "../signal_gen_app_i.h"
#include <furi_hal.h>
#include <gui/elements.h>
#include "signal_generator_icons.h"
#include <assets_icons.h>
typedef enum {
LineIndexChannel,
LineIndexFrequency,
LineIndexDuty,
LineIndexTotalCount
} LineIndex;
static const char* const pwm_ch_names[] = {"2(A7)", "4(A4)"};
struct SignalGenPwm {
View* view;
SignalGenPwmViewCallback callback;
void* context;
};
typedef struct {
LineIndex line_sel;
bool edit_mode;
uint8_t edit_digit;
uint8_t channel_id;
uint32_t freq;
uint8_t duty;
} SignalGenPwmViewModel;
#define ITEM_H 64 / 3
#define ITEM_W 128
#define VALUE_X 100
#define VALUE_W 45
#define FREQ_VALUE_X 62
#define FREQ_MAX 1000000UL
#define FREQ_DIGITS_NB 7
static void pwm_set_config(SignalGenPwm* pwm) {
FuriHalPwmOutputId channel;
uint32_t freq;
uint8_t duty;
with_view_model(
pwm->view,
SignalGenPwmViewModel * model,
{
channel = model->channel_id;
freq = model->freq;
duty = model->duty;
},
false);
furi_assert(pwm->callback);
pwm->callback(channel, freq, duty, pwm->context);
}
static void pwm_channel_change(SignalGenPwmViewModel* model, InputEvent* event) {
if(event->key == InputKeyLeft) {
if(model->channel_id > 0) {
model->channel_id--;
}
} else if(event->key == InputKeyRight) {
if(model->channel_id < (COUNT_OF(pwm_ch_names) - 1)) {
model->channel_id++;
}
}
}
static void pwm_duty_change(SignalGenPwmViewModel* model, InputEvent* event) {
if(event->key == InputKeyLeft) {
if(model->duty > 0) {
model->duty--;
}
} else if(event->key == InputKeyRight) {
if(model->duty < 100) {
model->duty++;
}
}
}
static bool pwm_freq_edit(SignalGenPwmViewModel* model, InputEvent* event) {
bool consumed = false;
if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
if(event->key == InputKeyRight) {
if(model->edit_digit > 0) {
model->edit_digit--;
}
consumed = true;
} else if(event->key == InputKeyLeft) {
if(model->edit_digit < (FREQ_DIGITS_NB - 1)) {
model->edit_digit++;
}
consumed = true;
} else if(event->key == InputKeyUp) {
uint32_t step = 1;
for(uint8_t i = 0; i < model->edit_digit; i++) {
step *= 10;
}
if((model->freq + step) < FREQ_MAX) {
model->freq += step;
} else {
model->freq = FREQ_MAX;
}
consumed = true;
} else if(event->key == InputKeyDown) {
uint32_t step = 1;
for(uint8_t i = 0; i < model->edit_digit; i++) {
step *= 10;
}
if(model->freq > (step + 1)) {
model->freq -= step;
} else {
model->freq = 1;
}
consumed = true;
}
}
return consumed;
}
static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) {
SignalGenPwmViewModel* model = _model;
char* line_label = NULL;
char val_text[16];
for(size_t line = 0; line < LineIndexTotalCount; line++) {
if(line == LineIndexChannel) {
line_label = "GPIO Pin";
} else if(line == LineIndexFrequency) {
line_label = "Frequency";
} else if(line == LineIndexDuty) { //-V547
line_label = "Pulse width";
}
canvas_set_color(canvas, ColorBlack);
if(line == model->line_sel) {
elements_slightly_rounded_box(canvas, 0, ITEM_H * line + 1, ITEM_W, ITEM_H - 1);
canvas_set_color(canvas, ColorWhite);
}
uint8_t text_y = ITEM_H * line + ITEM_H / 2 + 2;
canvas_draw_str_aligned(canvas, 6, text_y, AlignLeft, AlignCenter, line_label);
if(line == LineIndexChannel) {
snprintf(val_text, sizeof(val_text), "%s", pwm_ch_names[model->channel_id]);
canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text);
if(model->channel_id != 0) {
canvas_draw_str_aligned(
canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<");
}
if(model->channel_id != (COUNT_OF(pwm_ch_names) - 1)) {
canvas_draw_str_aligned(
canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">");
}
} else if(line == LineIndexFrequency) {
snprintf(val_text, sizeof(val_text), "%7lu Hz", model->freq);
canvas_set_font(canvas, FontKeyboard);
canvas_draw_str_aligned(
canvas, FREQ_VALUE_X, text_y, AlignLeft, AlignCenter, val_text);
canvas_set_font(canvas, FontSecondary);
if(model->edit_mode) {
uint8_t icon_x = (FREQ_VALUE_X) + (FREQ_DIGITS_NB - model->edit_digit - 1) * 6;
canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_3x5);
canvas_draw_icon(canvas, icon_x, text_y + 5, &I_SmallArrowDown_3x5);
}
} else if(line == LineIndexDuty) { //-V547
snprintf(val_text, sizeof(val_text), "%d%%", model->duty);
canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text);
if(model->duty != 0) {
canvas_draw_str_aligned(
canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<");
}
if(model->duty != 100) {
canvas_draw_str_aligned(
canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">");
}
}
}
}
static bool signal_gen_pwm_input_callback(InputEvent* event, void* context) {
furi_assert(context);
SignalGenPwm* pwm = context;
bool consumed = false;
bool need_update = false;
with_view_model(
pwm->view,
SignalGenPwmViewModel * model,
{
if(model->edit_mode == false) {
if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
if(event->key == InputKeyUp) {
if(model->line_sel == 0) {
model->line_sel = LineIndexTotalCount - 1;
} else {
model->line_sel =
CLAMP(model->line_sel - 1, LineIndexTotalCount - 1, 0);
}
consumed = true;
} else if(event->key == InputKeyDown) {
if(model->line_sel == LineIndexTotalCount - 1) {
model->line_sel = 0;
} else {
model->line_sel =
CLAMP(model->line_sel + 1, LineIndexTotalCount - 1, 0);
}
consumed = true;
} else if((event->key == InputKeyLeft) || (event->key == InputKeyRight)) {
if(model->line_sel == LineIndexChannel) {
pwm_channel_change(model, event);
need_update = true;
} else if(model->line_sel == LineIndexDuty) {
pwm_duty_change(model, event);
need_update = true;
} else if(model->line_sel == LineIndexFrequency) {
model->edit_mode = true;
}
consumed = true;
} else if(event->key == InputKeyOk) {
if(model->line_sel == LineIndexFrequency) {
model->edit_mode = true;
}
consumed = true;
}
}
} else {
if((event->key == InputKeyOk) || (event->key == InputKeyBack)) {
if(event->type == InputTypeShort) {
model->edit_mode = false;
consumed = true;
}
} else {
if(model->line_sel == LineIndexFrequency) {
consumed = pwm_freq_edit(model, event);
need_update = consumed;
}
}
}
},
true);
if(need_update) {
pwm_set_config(pwm);
}
return consumed;
}
SignalGenPwm* signal_gen_pwm_alloc() {
SignalGenPwm* pwm = malloc(sizeof(SignalGenPwm));
pwm->view = view_alloc();
view_allocate_model(pwm->view, ViewModelTypeLocking, sizeof(SignalGenPwmViewModel));
view_set_context(pwm->view, pwm);
view_set_draw_callback(pwm->view, signal_gen_pwm_draw_callback);
view_set_input_callback(pwm->view, signal_gen_pwm_input_callback);
return pwm;
}
void signal_gen_pwm_free(SignalGenPwm* pwm) {
furi_assert(pwm);
view_free(pwm->view);
free(pwm);
}
View* signal_gen_pwm_get_view(SignalGenPwm* pwm) {
furi_assert(pwm);
return pwm->view;
}
void signal_gen_pwm_set_callback(
SignalGenPwm* pwm,
SignalGenPwmViewCallback callback,
void* context) {
furi_assert(pwm);
furi_assert(callback);
with_view_model(
pwm->view,
SignalGenPwmViewModel * model,
{
UNUSED(model);
pwm->callback = callback;
pwm->context = context;
},
false);
}
void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty) {
with_view_model(
pwm->view,
SignalGenPwmViewModel * model,
{
model->channel_id = channel_id;
model->freq = freq;
model->duty = duty;
},
true);
furi_assert(pwm->callback);
pwm->callback(channel_id, freq, duty, pwm->context);
}

View File

@@ -1,21 +0,0 @@
#pragma once
#include <gui/view.h>
#include "../signal_gen_app_i.h"
typedef struct SignalGenPwm SignalGenPwm;
typedef void (
*SignalGenPwmViewCallback)(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context);
SignalGenPwm* signal_gen_pwm_alloc();
void signal_gen_pwm_free(SignalGenPwm* pwm);
View* signal_gen_pwm_get_view(SignalGenPwm* pwm);
void signal_gen_pwm_set_callback(
SignalGenPwm* pwm,
SignalGenPwmViewCallback callback,
void* context);
void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty);

View File

@@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -1,15 +0,0 @@
App(
appid="simon_says", # Must be unique
name="Simon Says", # Displayed in UI
apptype=FlipperAppType.EXTERNAL,
entry_point="simon_says_app_entry",
stack_size=2 * 1024,
fap_category="Games",
# Optional values
# fap_version=(0, 1), # (major, minor)
fap_icon="simon_says.png", # 10x10 1-bit PNG
fap_description="A Simon Says Game",
fap_author="@SimplyMinimal & @ShehabAttia96",
fap_weburl="https://github.com/SimplyMinimal/FlipperZero-SimonSays",
fap_icon_assets="images", # Image assets to compile for this application
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 B

View File

@@ -1,661 +0,0 @@
#include <furi.h>
#include <furi_hal.h>
#include <storage/storage.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <gui/icon.h>
#include <input/input.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <stdbool.h> // Header-file for boolean data-type.
#include <stdio.h>
#include <string.h>
/* generated by fbt from .png files in images folder */
#include "simon_says_icons.h"
#include <assets_icons.h>
#define TAG "Simon" // Used for logging
#define DEBUG_MSG 1
#define SCREEN_XRES 128
#define SCREEN_YRES 64
#define BOARD_X 72 // Used for board placement
#define BOARD_Y 8
#define GAME_START_LIVES 3
#define SAVING_FILENAME APP_DATA_PATH("game_simon_says.save")
// Define Notes
// Shamelessly stolen from Ocarina application
// https://github.com/invalidna-me/flipperzero-ocarina
#define NOTE_UP 587.33f
#define NOTE_LEFT 493.88f
#define NOTE_RIGHT 440.00f
#define NOTE_DOWN 349.23
#define NOTE_OK 293.66f
/* ============================ Data structures ============================= */
typedef enum game_state { preloading, mainMenu, inGame, gameOver, gameVictory } game_state;
typedef enum difficulty_mode { normal, hard } difficulty_mode;
typedef enum shape_names { up, down, left, right, number_of_shapes } Direction;
typedef enum currently_playing { simon, player } currently_playing;
typedef struct {
/* Game state. */
enum game_state gameState; // This is the current game state
bool gameover; /* if true then switch to the game over state */
bool is_wrong_direction; /* Is the last direction wrong? */
enum currently_playing activePlayer; // This is used to track who is playing at the moment
uint32_t lives; /* Number of lives in the current game. */
enum difficulty_mode difficultyMode; // This is the difficulty mode for the current game
bool sound_enabled; // This is the sound enabled flag for the current game
float volume; // This is the volume for the current game
/* Handle Score */
int currentScore; // This is the score for the current
int highScore; /* Highscore. Shown on Game Over Screen */
bool is_new_highscore; /* Is the last score a new highscore? */
/* Handle Shape Display */
uint32_t numberOfMillisecondsBeforeShapeDisappears; // This defines the speed of the game
enum shape_names simonMoves[1000]; // Store the sequence of shapes that Simon plays
enum shape_names selectedShape; // This is used to track the shape that the player has selected
bool set_board_neutral; // This is used to track if the board should be neutral or not
int moveIndex; // This is used to track the current move in the sequence
uint32_t last_button_press_tick;
NotificationApp* notification;
FuriMutex* mutex;
} SimonData;
/* ============================== Sequences ============================== */
const NotificationSequence sequence_wrong_move = {
&message_red_255,
&message_vibro_on,
// &message_note_g5, // Play sound but currently disabled
&message_delay_25,
// &message_note_e5,
&message_vibro_off,
&message_sound_off,
NULL,
};
const NotificationSequence sequence_player_submit_move = {
&message_vibro_on,
// &message_note_g5, // Play sound but currently disabled. Need On/Off menu setting
&message_delay_10,
&message_delay_1,
&message_delay_1,
&message_delay_1,
&message_delay_1,
&message_delay_1,
// &message_note_e5,
&message_vibro_off,
&message_sound_off,
NULL,
};
const NotificationSequence sequence_up = {
// &message_vibro_on,
&message_note_g4,
&message_delay_100,
// &message_vibro_off,
&message_sound_off,
NULL,
};
const NotificationSequence sequence_down = {
// &message_vibro_on,
&message_note_c3,
&message_delay_100,
// &message_vibro_off,
&message_sound_off,
NULL,
};
const NotificationSequence sequence_left = {
// &message_vibro_on,
&message_note_e3,
&message_delay_100,
// &message_vibro_off,
&message_sound_off,
NULL,
};
const NotificationSequence sequence_right = {
// &message_vibro_on,
&message_note_g3,
&message_delay_100,
// &message_vibro_off,
&message_sound_off,
NULL,
};
// Indicate that it's Simon's turn
const NotificationSequence sequence_simon_is_playing = {
&message_red_255,
&message_do_not_reset,
NULL,
};
// Indicate that it's the Player's turn
const NotificationSequence sequence_player_is_playing = {
&message_red_0,
&message_do_not_reset,
NULL,
};
const NotificationSequence sequence_cleanup = {
&message_red_0,
&message_green_0,
&message_blue_0,
&message_sound_off,
&message_vibro_off,
NULL,
};
/* ============================ 2D drawing ================================== */
/* Display remaining lives in the center of the board */
void draw_remaining_lives(Canvas* canvas, const SimonData* simon_state) {
// Convert score to string
// int length = snprintf(NULL, 0, "%lu", simon_state->lives);
// char* str_lives_remaining = malloc(length + 1);
// snprintf(str_lives_remaining, length + 1, "%lu", simon_state->lives);
// TODO: Make it a Simon Says icon on top right
canvas_set_color(canvas, ColorBlack);
canvas_set_font(canvas, FontSecondary);
int x = SCREEN_XRES - 6;
int lives = simon_state->lives;
while(lives--) {
canvas_draw_str(canvas, x, 8, "*");
x -= 7;
}
}
void draw_current_score(Canvas* canvas, const SimonData* simon_data) {
/* Draw Game Score. */
canvas_set_color(canvas, ColorXOR);
canvas_set_font(canvas, FontSecondary);
char str_score[32];
snprintf(str_score, sizeof(str_score), "%i", simon_data->currentScore);
canvas_draw_str_aligned(canvas, SCREEN_XRES / 2 + 4, 2, AlignCenter, AlignTop, str_score);
}
void play_sound_up(const SimonData* app) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
furi_hal_speaker_start(NOTE_UP, app->volume);
}
}
void play_sound_down(const SimonData* app) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
furi_hal_speaker_start(NOTE_DOWN, app->volume);
}
}
void play_sound_left(const SimonData* app) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
furi_hal_speaker_start(NOTE_LEFT, app->volume);
}
}
void play_sound_right(const SimonData* app) {
if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
furi_hal_speaker_start(NOTE_RIGHT, app->volume);
}
}
void stop_sound() {
if(furi_hal_speaker_is_mine()) {
furi_hal_speaker_stop();
furi_hal_speaker_release();
}
}
/* Main Render Function */
void simon_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
const SimonData* simon_state = ctx;
furi_mutex_acquire(simon_state->mutex, FuriWaitForever);
canvas_clear(canvas);
// ######################### Main Menu #########################
// Show Main Menu
if(simon_state->gameState == mainMenu) {
// Draw border frame
canvas_draw_frame(canvas, 1, 1, SCREEN_XRES - 1, SCREEN_YRES - 1); // Border
// Draw Simon text banner
canvas_set_font(canvas, FontSecondary);
canvas_set_color(canvas, ColorBlack);
canvas_draw_str_aligned(
canvas,
SCREEN_XRES / 2,
SCREEN_YRES / 2 - 4,
AlignCenter,
AlignCenter,
"Welcome to Simon Says");
// Display Press OK to start below title
canvas_set_color(canvas, ColorXOR);
canvas_draw_str_aligned(
canvas,
SCREEN_XRES / 2,
SCREEN_YRES / 2 + 10,
AlignCenter,
AlignCenter,
"Press OK to start");
}
// ######################### in Game #########################
//@todo Render Callback
// We're in an active game
if(simon_state->gameState == inGame) {
// Draw Current Score
draw_current_score(canvas, simon_state);
// Draw Lives
draw_remaining_lives(canvas, simon_state);
// Draw Simon Pose
if(simon_state->activePlayer == player) {
// Player's turn
canvas_draw_icon(canvas, 0, 4, &I_DolphinWait_61x59);
} else {
// Simon's turn
canvas_draw_icon(canvas, 0, 4, &I_DolphinTalking_59x63);
}
if(simon_state->set_board_neutral) {
// Draw Neutral Board
canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_board); // Draw Board
// Stop Sound TODO: Move this to a better place
//@todo Sound
stop_sound();
} else {
switch(simon_state->selectedShape) {
case up:
canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_up); // Draw Up
play_sound_up(simon_state);
break;
case down:
canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_down); // Draw Down
play_sound_down(simon_state);
break;
case left:
canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_left); // Draw Left
play_sound_left(simon_state);
break;
case right:
canvas_draw_icon(canvas, BOARD_X, BOARD_Y, &I_right); // Draw Right
play_sound_right(simon_state);
break;
default:
if(DEBUG_MSG)
FURI_LOG_E(
TAG, "Invalid shape: %d", simon_state->simonMoves[simon_state->moveIndex]);
break;
}
}
}
// ######################### Game Over #########################
if(simon_state->gameState == gameOver) {
stop_sound(); //TODO: Make a game over sequence
canvas_set_color(canvas, ColorXOR);
canvas_set_font(canvas, FontPrimary);
// TODO: if new highscore, display blinking "New High Score"
// Display High Score Text
if(simon_state->is_new_highscore) {
canvas_draw_str_aligned(
canvas, SCREEN_XRES / 2, 6, AlignCenter, AlignTop, "New High Score!");
} else {
canvas_draw_str_aligned(
canvas, SCREEN_XRES / 2, 6, AlignCenter, AlignTop, "High Score");
}
// Convert highscore to string
int length = snprintf(NULL, 0, "%i", simon_state->highScore);
char* str_high_score = malloc(length + 1);
snprintf(str_high_score, length + 1, "%i", simon_state->highScore);
// Display High Score
canvas_draw_str_aligned(
canvas, SCREEN_XRES / 2, 22, AlignCenter, AlignCenter, str_high_score);
free(str_high_score);
// Display Game Over
canvas_draw_str_aligned(
canvas, SCREEN_XRES / 2, SCREEN_YRES / 2 + 2, AlignCenter, AlignCenter, "GAME OVER");
// Display Press OK to restart below title
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(
canvas,
SCREEN_XRES / 2,
SCREEN_YRES / 2 + 15,
AlignCenter,
AlignCenter,
"Press OK to restart");
}
// ######################### Victory #########################
//Player Beat Simon beyond limit! A word record holder here!
//TODO
//release the mutex
furi_mutex_release(simon_state->mutex);
}
/* ======================== Input Handling ============================== */
void simon_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
/* ======================== Simon Game Engine ======================== */
bool load_game(SimonData* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_migrate(storage, EXT_PATH("apps/Games/game_simon_says.save"), SAVING_FILENAME);
File* file = storage_file_alloc(storage);
uint16_t bytes_readed = 0;
if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING)) {
if(storage_file_size(file) > sizeof(SimonData)) {
storage_simply_remove(storage, SAVING_FILENAME);
FURI_LOG_E(
TAG, "Error: file is larger than the data structure! The file has been deleted.");
} else {
bytes_readed = storage_file_read(file, app, sizeof(SimonData));
}
storage_file_close(file);
storage_file_free(file);
}
furi_record_close(RECORD_STORAGE);
return bytes_readed == sizeof(SimonData);
}
void save_game(SimonData* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* file = storage_file_alloc(storage);
if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
storage_file_write(file, app, sizeof(SimonData));
}
storage_file_close(file);
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
}
int getRandomIntInRange(int lower, int upper) {
return (rand() % (upper - lower + 1)) + lower;
}
void play_sound_sequence_correct() {
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_success);
}
void play_sound_wrong_move() {
//TODO: play wrong sound: Try sequence_audiovisual_alert
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_error);
}
/* Restart game and give player a chance to try again on same sequence */
// @todo restartGame
void resetGame(SimonData* app) {
app->moveIndex = 0;
app->numberOfMillisecondsBeforeShapeDisappears = 500;
app->activePlayer = simon;
app->is_wrong_direction = false;
app->last_button_press_tick = 0;
app->set_board_neutral = true;
app->activePlayer = simon;
}
/* Set gameover state */
void game_over(SimonData* app) {
if(app->is_new_highscore) save_game(app); // Save highscore but only on change
app->gameover = true;
app->lives = GAME_START_LIVES; // Show 3 lives in game over screen to match new game start
app->gameState = gameOver;
}
/* Called after gameover to restart the game. This function
* also calls restart_game(). */
void restart_game_after_gameover(SimonData* app) {
app->volume = 1.0f; //TODO: make this a setting
app->gameState = inGame;
app->gameover = false;
app->currentScore = 0;
app->is_new_highscore = false;
app->lives = GAME_START_LIVES;
app->simonMoves[0] = rand() % number_of_shapes;
resetGame(app);
}
void addNewSimonMove(int addAtIndex, SimonData* app) {
app->simonMoves[addAtIndex] = getRandomIntInRange(0, 3);
}
void startNewRound(SimonData* app) {
addNewSimonMove(app->currentScore, app);
app->moveIndex = 0;
app->activePlayer = simon;
}
void onPlayerAnsweredCorrect(SimonData* app) {
app->moveIndex++;
}
void onPlayerAnsweredWrong(SimonData* app) {
if(app->lives > 0) {
app->lives--;
// Play the wrong sound
if(app->sound_enabled) {
play_sound_wrong_move();
}
resetGame(app);
} else {
// The player has no lives left
// Game over
game_over(app);
//TODO: Play unique game over sound
}
}
bool isRoundComplete(SimonData* app) {
return app->moveIndex == app->currentScore;
}
enum shape_names getCurrentSimonMove(SimonData* app) {
return app->simonMoves[app->moveIndex];
}
void onPlayerSelectedShapeCallback(enum shape_names shape, SimonData* app) {
if(shape == getCurrentSimonMove(app)) {
onPlayerAnsweredCorrect(app);
} else {
onPlayerAnsweredWrong(app);
}
}
//@todo gametick
void game_tick(SimonData* simon_state) {
if(simon_state->gameState == inGame) {
if(simon_state->activePlayer == simon) {
// ############### Simon Turn ###############
notification_message(simon_state->notification, &sequence_simon_is_playing);
//@todo Gameplay
if(simon_state->set_board_neutral) {
if(simon_state->moveIndex < simon_state->currentScore) {
simon_state->selectedShape = getCurrentSimonMove(simon_state);
simon_state->set_board_neutral = false;
simon_state->moveIndex++;
} else {
simon_state->activePlayer = player;
simon_state->set_board_neutral = true;
simon_state->moveIndex = 0;
}
} else {
simon_state->set_board_neutral = true;
}
} else {
// ############### Player Turn ###############
notification_message(simon_state->notification, &sequence_player_is_playing);
// It's Player's Turn
if(isRoundComplete(simon_state)) {
simon_state->activePlayer = simon;
simon_state->currentScore++;
// app->numberOfMillisecondsBeforeShapeDisappears -= 50;
//TODO: Hacky way of handling highscore by subtracting 1 to account for the first move
if(simon_state->currentScore - 1 > simon_state->highScore) {
simon_state->highScore = simon_state->currentScore - 1;
simon_state->is_new_highscore = true;
}
if(simon_state->sound_enabled) {
play_sound_sequence_correct();
}
startNewRound(simon_state);
}
}
}
}
/* ======================== Main Entry Point ============================== */
int32_t simon_says_app_entry(void* p) {
UNUSED(p);
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
SimonData* simon_state = malloc(sizeof(SimonData));
simon_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(!simon_state->mutex) {
FURI_LOG_E(TAG, "cannot create mutex\r\n");
free(simon_state);
return -1;
}
// Configure view port
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, simon_draw_callback, simon_state);
view_port_input_callback_set(view_port, simon_input_callback, event_queue);
// Register view port in GUI
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
simon_state->notification = notification;
InputEvent input;
// Show Main Menu Screen
//load_game(simon_state);
restart_game_after_gameover(simon_state);
simon_state->gameState = mainMenu;
while(true) {
game_tick(simon_state);
FuriStatus q_status = furi_message_queue_get(
event_queue, &input, simon_state->numberOfMillisecondsBeforeShapeDisappears);
furi_mutex_acquire(simon_state->mutex, FuriWaitForever);
if(q_status == FuriStatusOk) {
//FURI_LOG_D(TAG, "Got input event: %d", input.key);
//break out of the loop if the back key is pressed
if(input.key == InputKeyBack && input.type == InputTypeLong) {
// Save high score before quitting
//if(simon_state->is_new_highscore) {
// save_game(simon_state);
//}
break;
}
//@todo Set Game States
if(input.key == InputKeyOk && simon_state->gameState != inGame) {
restart_game_after_gameover(simon_state);
// Set Simon Board state
startNewRound(simon_state);
view_port_update(view_port);
}
// Keep LED on if it is Simon's turn
if(simon_state->activePlayer == player) {
notification_message(notification, &sequence_player_is_playing);
if(input.type == InputTypePress) {
simon_state->set_board_neutral = false;
switch(input.key) {
case InputKeyUp:
simon_state->selectedShape = up;
onPlayerSelectedShapeCallback(up, simon_state);
break;
case InputKeyDown:
simon_state->selectedShape = down;
onPlayerSelectedShapeCallback(down, simon_state);
break;
case InputKeyLeft:
simon_state->selectedShape = left;
onPlayerSelectedShapeCallback(left, simon_state);
break;
case InputKeyRight:
simon_state->selectedShape = right;
onPlayerSelectedShapeCallback(right, simon_state);
break;
default:
simon_state->set_board_neutral = true;
break;
}
} else {
//FURI_LOG_D(TAG, "Input type is not short");
simon_state->set_board_neutral = true;
}
}
}
// @todo Animation Loop for debug
// if(simon_state->gameState == inGame && simon_state->activePlayer == simon) {
// simon_state->currentScore++;
// simon_state->set_board_neutral = !simon_state->set_board_neutral;
// }
view_port_update(view_port);
furi_mutex_release(simon_state->mutex);
}
stop_sound();
notification_message(notification, &sequence_cleanup);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_mutex_free(simon_state->mutex);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_GUI);
free(simon_state);
return 0;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 B

View File

@@ -1,16 +0,0 @@
App(
appid="slotmachine",
name="Slot Machine",
apptype=FlipperAppType.EXTERNAL,
entry_point="slotmachine_app",
cdefines=["APP_SLOTMACHINE"],
requires=["gui"],
stack_size=1 * 1024,
fap_icon="ddgame_icon.png",
fap_category="Games",
fap_icon_assets="assets",
fap_author="@Daniel-dev-s",
fap_weburl="https://github.com/Daniel-dev-s/flipperzero-slots",
fap_version="1.1",
fap_description="Simple Slots simulator game",
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 B

View File

@@ -1,273 +0,0 @@
#include <furi.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <stdlib.h>
#include <stdio.h>
#include <input/input.h>
#include <furi_hal.h>
#include "slotmachine_icons.h"
#include <assets_icons.h>
const Icon* slot_frames[] = {&I_x2, &I_x3, &I_x4, &I_x2_2, &I_x5};
const uint8_t slot_coef[] = {2, 3, 4, 2, 5};
typedef struct {
uint8_t x, y, value, times, speed;
bool spining;
} SlotColumn;
int COLUMNS_COUNT = 4;
int MAX_COLUMNS_COUNT = 4;
typedef struct {
Gui* gui; // container gui
ViewPort* view_port; // current viewport
FuriMessageQueue* input_queue; // Input Events queue
FuriMutex** model_mutex; // mutex for safe threads
uint16_t bet;
double money, winamount;
SlotColumn* columns[4];
bool winview;
} SlotMachineApp;
#define START_MONEY 1500;
#define START_BET 300;
#define SSRAND_MAX 5;
#define DEFAULT_SPEED 16;
uint8_t DEFAULT_SPINNING_TIMES = 10;
void game_results(SlotMachineApp* app) {
int matches[] = {0, 0, 0, 0, 0};
double total = 0;
for(int i = 0; i < COLUMNS_COUNT; i++) {
matches[app->columns[i]->value]++;
}
for(int i = 0; i < 5; i++) {
if(matches[i] >= 2) {
total += app->bet * (slot_coef[i] / (double)(MAX_COLUMNS_COUNT + 1 - matches[i]));
}
}
if(total > 0) {
app->money += total;
app->winamount = total;
app->winview = true;
}
}
void draw_container(Canvas* canvas) {
canvas_draw_rframe(canvas, 2, 12, 120, 34, 3);
canvas_draw_rframe(canvas, 2, 13, 120, 34, 3);
canvas_draw_rframe(canvas, 2, 14, 120, 34, 3);
canvas_draw_rframe(canvas, 2, 15, 120, 34, 3);
canvas_draw_rframe(canvas, 2, 16, 120, 34, 3);
canvas_draw_rframe(canvas, 2, 17, 120, 34, 3);
canvas_draw_line(canvas, 31, 16, 31, 48);
canvas_draw_line(canvas, 61, 16, 61, 48);
canvas_draw_line(canvas, 91, 16, 91, 48);
}
bool checkIsSpinning(SlotMachineApp* slotmachine) {
for(int i = 0; i < COLUMNS_COUNT; i++) {
if(slotmachine->columns[i]->spining) return true;
}
return false;
}
void drawButton(Canvas* canvas, uint8_t x, uint8_t y, char* str, bool invert) {
const uint8_t string_width = canvas_string_width(canvas, str);
canvas_set_font(canvas, FontSecondary);
if(invert) {
canvas_draw_rbox(canvas, x, y, string_width + 15, 11, 3);
canvas_invert_color(canvas);
} else {
canvas_draw_rframe(canvas, x, y, string_width + 15, 11, 3);
}
canvas_draw_circle(canvas, x + 5, y + 5, 3);
canvas_draw_circle(canvas, x + 5, y + 5, 1);
canvas_draw_str(canvas, x + 13, y + 9, str);
canvas_invert_color(canvas);
}
// viewport callback
void slotmachine_draw_callback(Canvas* canvas, void* ctx) {
SlotMachineApp* slotmachine = (SlotMachineApp*)ctx;
furi_check(furi_mutex_acquire(slotmachine->model_mutex, FuriWaitForever) == FuriStatusOk);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 10, "Slots");
canvas_draw_icon(canvas, 30, 3, &I_little_coin);
char moneyStr[15];
snprintf(moneyStr, sizeof(moneyStr), "$%.0f", slotmachine->money);
char betStr[7];
snprintf(betStr, sizeof(betStr), "$%d", slotmachine->bet);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 45, 10, moneyStr);
canvas_draw_str(canvas, 2, canvas_height(canvas) - 3, "Bet:");
canvas_draw_str(canvas, 20, canvas_height(canvas) - 3, betStr);
if(slotmachine->winview) {
char winamountStr[30];
snprintf(winamountStr, sizeof(winamountStr), "You win: $%.2f!", slotmachine->winamount);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 35, winamountStr);
drawButton(canvas, 95, 52, "Ok", false);
furi_mutex_release(slotmachine->model_mutex);
return;
}
for(int i = 0; i < COLUMNS_COUNT; i++) {
if(slotmachine->columns[i]->spining) {
slotmachine->columns[i]->y += slotmachine->columns[i]->speed;
if(slotmachine->columns[i]->y > 31) {
slotmachine->columns[i]->y = 13;
slotmachine->columns[i]->times--;
slotmachine->columns[i]->speed--;
slotmachine->columns[i]->value = rand() % SSRAND_MAX;
if(slotmachine->columns[i]->times == 0) {
slotmachine->columns[i]->y = 23;
slotmachine->columns[i]->spining = false;
if(i == COLUMNS_COUNT - 1) {
game_results(slotmachine);
}
}
if(i < COLUMNS_COUNT - 1 &&
slotmachine->columns[i]->times ==
(DEFAULT_SPINNING_TIMES - (int)(DEFAULT_SPINNING_TIMES / 3))) {
slotmachine->columns[i + 1]->spining = true;
}
}
}
canvas_draw_icon(
canvas,
slotmachine->columns[i]->x,
slotmachine->columns[i]->y,
slot_frames[slotmachine->columns[i]->value]);
}
draw_container(canvas);
drawButton(canvas, 90, 52, "Spin", checkIsSpinning(slotmachine));
furi_mutex_release(slotmachine->model_mutex);
}
// callback for viewport input events
static void slotmachine_input_callback(InputEvent* input_event, void* ctx) {
SlotMachineApp* slotmachine = ctx;
furi_message_queue_put(slotmachine->input_queue, input_event, FuriWaitForever);
}
// allocation memory and initialization
SlotMachineApp* slotmachine_app_alloc() {
SlotMachineApp* app = malloc(sizeof(SlotMachineApp));
app->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
app->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
app->view_port = view_port_alloc();
view_port_draw_callback_set(
app->view_port, slotmachine_draw_callback, app); // viewport callback register
view_port_input_callback_set(app->view_port, slotmachine_input_callback, app);
app->money = START_MONEY;
app->bet = START_BET;
app->winview = false;
app->winamount = 0;
int x = 7;
for(int i = 0; i < COLUMNS_COUNT; i++) {
app->columns[i] = malloc(sizeof(SlotColumn));
app->columns[i]->x = x;
app->columns[i]->y = 25;
app->columns[i]->value = 0;
app->columns[i]->spining = false;
x += 30;
}
app->gui = furi_record_open("gui"); // start gui and adding viewport
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
return app;
}
void slotmachine_app_free(SlotMachineApp* app) {
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close("gui"); // free memory
furi_mutex_free(app->model_mutex);
for(int i = 0; i < COLUMNS_COUNT; i++) {
free(app->columns[i]);
}
free(app);
}
// entry point
int32_t slotmachine_app(void* p) {
UNUSED(p);
SlotMachineApp* slotmachine = slotmachine_app_alloc();
InputEvent input;
// endless input cycle
while(1) {
if(furi_message_queue_get(slotmachine->input_queue, &input, 100) == FuriStatusOk) {
// if thread idle - take it
furi_check(
furi_mutex_acquire(slotmachine->model_mutex, FuriWaitForever) == FuriStatusOk);
if(!checkIsSpinning(slotmachine)) {
if(input.key == InputKeyBack) {
// exit on back button
furi_mutex_release(slotmachine->model_mutex);
break;
} else if(
input.key == InputKeyOk && input.type == InputTypeShort &&
slotmachine->winview) {
slotmachine->winview = false;
} else if(
input.key == InputKeyOk && input.type == InputTypeShort &&
slotmachine->bet <= slotmachine->money) {
COLUMNS_COUNT = rand() % 3 + 2;
slotmachine->money -= slotmachine->bet;
slotmachine->columns[0]->spining = true;
for(int i = 0; i < COLUMNS_COUNT; i++) {
slotmachine->columns[i]->times = DEFAULT_SPINNING_TIMES;
slotmachine->columns[i]->speed = DEFAULT_SPEED;
}
} else if(input.key == InputKeyUp) {
if(slotmachine->bet + 10 < slotmachine->money) {
slotmachine->bet += 10;
}
} else if(input.key == InputKeyDown) {
if(slotmachine->bet - 10 > 0) {
slotmachine->bet -= 10;
}
}
}
// release thread
furi_mutex_release(slotmachine->model_mutex);
}
// redraw viewport
view_port_update(slotmachine->view_port);
}
slotmachine_app_free(slotmachine);
return 0;
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Tibor Tálosi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,14 +0,0 @@
App(
appid="solitaire",
name="Solitaire",
apptype=FlipperAppType.EXTERNAL,
entry_point="solitaire_app",
requires=["gui", "storage", "canvas"],
stack_size=2 * 1024,
fap_icon="solitaire_10px.png",
fap_category="Games",
fap_icon_assets="assets",
fap_author="@teeebor",
fap_version="1.0",
fap_description="Solitaire game",
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,353 +0,0 @@
#include "card.h"
#include "dml.h"
#include "ui.h"
#define CARD_DRAW_X_START 108
#define CARD_DRAW_Y_START 38
#define CARD_DRAW_X_SPACE 10
#define CARD_DRAW_Y_SPACE 8
#define CARD_DRAW_X_OFFSET 4
#define CARD_DRAW_FIRST_ROW_LENGTH 7
uint8_t pips[4][3] = {
{21, 10, 7}, //spades
{7, 10, 7}, //hearts
{0, 10, 7}, //diamonds
{14, 10, 7}, //clubs
};
uint8_t letters[13][3] = {
{0, 0, 5},
{5, 0, 5},
{10, 0, 5},
{15, 0, 5},
{20, 0, 5},
{25, 0, 5},
{30, 0, 5},
{0, 5, 5},
{5, 5, 5},
{10, 5, 5},
{15, 5, 5},
{20, 5, 5},
{25, 5, 5},
};
//region Player card positions
uint8_t playerCardPositions[22][4] = {
//first row
{108, 38},
{98, 38},
{88, 38},
{78, 38},
{68, 38},
{58, 38},
{48, 38},
{38, 38},
//second row
{104, 26},
{94, 26},
{84, 26},
{74, 26},
{64, 26},
{54, 26},
{44, 26},
//third row
{99, 14},
{89, 14},
{79, 14},
{69, 14},
{59, 14},
{49, 14},
};
//endregion
Icon* card_graphics = NULL;
void set_card_graphics(const Icon* graphics) {
card_graphics = (Icon*)graphics;
}
void draw_card_at_colored(
int8_t pos_x,
int8_t pos_y,
uint8_t pip,
uint8_t character,
bool inverted,
Canvas* const canvas) {
DrawMode primary = inverted ? Black : White;
DrawMode secondary = inverted ? White : Black;
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, primary);
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
uint8_t* drawInfo = pips[pip];
uint8_t px = drawInfo[0], py = drawInfo[1], s = drawInfo[2];
uint8_t left = pos_x + 2;
uint8_t right = (pos_x + CARD_WIDTH - s - 2);
uint8_t top = pos_y + 2;
uint8_t bottom = (pos_y + CARD_HEIGHT - s - 2);
draw_icon_clip(canvas, card_graphics, right, top, px, py, s, s, secondary);
draw_icon_clip_flipped(canvas, card_graphics, left, bottom, px, py, s, s, secondary);
drawInfo = letters[character];
px = drawInfo[0], py = drawInfo[1], s = drawInfo[2];
left = pos_x + 2;
right = (pos_x + CARD_WIDTH - s - 2);
top = pos_y + 2;
bottom = (pos_y + CARD_HEIGHT - s - 2);
draw_icon_clip(canvas, card_graphics, left, top + 1, px, py, s, s, secondary);
draw_icon_clip_flipped(canvas, card_graphics, right, bottom - 1, px, py, s, s, secondary);
}
void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas) {
draw_card_at_colored(pos_x, pos_y, pip, character, false, canvas);
}
void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas) {
for(int i = count - 1; i >= 0; i--) {
draw_card_at(
playerCardPositions[i][0],
playerCardPositions[i][1],
cards[i].pip,
cards[i].character,
canvas);
}
}
Vector card_pos_at_index(uint8_t index) {
return (Vector){playerCardPositions[index][0], playerCardPositions[index][1]};
}
void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas) {
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, White);
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
draw_icon_clip(canvas, card_graphics, pos_x + 1, pos_y + 1, 35, 0, 15, 21, Black);
}
void generate_deck(Deck* deck_ptr, uint8_t deck_count) {
uint16_t counter = 0;
if(deck_ptr->cards != NULL) {
free(deck_ptr->cards);
}
deck_ptr->deck_count = deck_count;
deck_ptr->card_count = deck_count * 52;
deck_ptr->cards = malloc(sizeof(Card) * deck_ptr->card_count);
for(uint8_t deck = 0; deck < deck_count; deck++) {
for(uint8_t pip = 0; pip < 4; pip++) {
for(uint8_t label = 0; label < 13; label++) {
deck_ptr->cards[counter] = (Card){pip, label, false, false};
counter++;
}
}
}
}
void shuffle_deck(Deck* deck_ptr) {
srand(DWT->CYCCNT);
deck_ptr->index = 0;
int max = deck_ptr->deck_count * 52;
for(int i = 0; i < max; i++) {
int r = i + (rand() % (max - i));
Card tmp = deck_ptr->cards[i];
deck_ptr->cards[i] = deck_ptr->cards[r];
deck_ptr->cards[r] = tmp;
}
}
uint8_t hand_count(const Card* cards, uint8_t count) {
uint8_t aceCount = 0;
uint8_t score = 0;
for(uint8_t i = 0; i < count; i++) {
if(cards[i].character == 12)
aceCount++;
else {
if(cards[i].character > 8)
score += 10;
else
score += cards[i].character + 2;
}
}
for(uint8_t i = 0; i < aceCount; i++) {
if((score + 11) <= 21)
score += 11;
else
score++;
}
return score;
}
void draw_card_animation(
Card animatingCard,
Vector from,
Vector control,
Vector to,
float t,
bool extra_margin,
Canvas* const canvas) {
float time = t;
if(extra_margin) {
time += 0.2;
}
Vector currentPos = quadratic_2d(from, control, to, time);
if(t > 1) {
draw_card_at(
currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas);
} else {
if(t < 0.5)
draw_card_back_at(currentPos.x, currentPos.y, canvas);
else
draw_card_at(
currentPos.x, currentPos.y, animatingCard.pip, animatingCard.character, canvas);
}
}
void init_hand(Hand* hand_ptr, uint8_t count) {
hand_ptr->cards = malloc(sizeof(Card) * count);
hand_ptr->index = 0;
hand_ptr->max = count;
}
void free_hand(Hand* hand_ptr) {
FURI_LOG_D("CARD", "Freeing hand");
free(hand_ptr->cards);
}
void add_to_hand(Hand* hand_ptr, Card card) {
FURI_LOG_D("CARD", "Adding to hand");
if(hand_ptr->index < hand_ptr->max) {
hand_ptr->cards[hand_ptr->index] = card;
hand_ptr->index++;
}
}
void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas) {
if(highlighted) {
draw_rounded_box_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
draw_rounded_box_frame(
canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White);
} else {
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Black);
draw_rounded_box_frame(
canvas, pos_x + 2, pos_y + 2, CARD_WIDTH - 4, CARD_HEIGHT - 4, White);
}
}
int first_non_flipped_card(Hand hand) {
for(int i = 0; i < hand.index; i++) {
if(!hand.cards[i].flipped) {
return i;
}
}
return hand.index;
}
void draw_hand_column(
Hand hand,
int16_t pos_x,
int16_t pos_y,
int8_t highlight,
Canvas* const canvas) {
if(hand.index == 0) {
draw_card_space(pos_x, pos_y, highlight > 0, canvas);
if(highlight == 0)
draw_rounded_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT, Inverse);
return;
}
int loopEnd = hand.index;
int hStart = max(loopEnd - 4, 0);
int pos = 0;
int first = first_non_flipped_card(hand);
bool wastop = false;
if(first >= 0 && first <= hStart && highlight != first) {
if(first > 0) {
draw_card_back_at(pos_x, pos_y + pos, canvas);
pos += 4;
hStart++;
wastop = true;
}
draw_card_at_colored(
pos_x, pos_y + pos, hand.cards[first].pip, hand.cards[first].character, false, canvas);
pos += 8;
hStart++;
}
if(hStart > highlight && highlight >= 0) {
if(!wastop && first > 0) {
draw_card_back_at(pos_x, pos_y + pos, canvas);
pos += 4;
hStart++;
}
draw_card_at_colored(
pos_x,
pos_y + pos,
hand.cards[highlight].pip,
hand.cards[highlight].character,
true,
canvas);
pos += 8;
hStart++;
}
for(int i = hStart; i < loopEnd; i++, pos += 4) {
if(hand.cards[i].flipped) {
draw_card_back_at(pos_x, pos_y + pos, canvas);
if(i == highlight)
draw_rounded_box(
canvas, pos_x + 1, pos_y + pos + 1, CARD_WIDTH - 2, CARD_HEIGHT - 2, Inverse);
} else {
draw_card_at_colored(
pos_x,
pos_y + pos,
hand.cards[i].pip,
hand.cards[i].character,
(i == highlight),
canvas);
if(i == highlight || i == first) pos += 4;
}
}
}
Card remove_from_deck(uint16_t index, Deck* deck) {
FURI_LOG_D("CARD", "Removing from deck");
Card result = {0, 0, true, false};
if(deck->card_count > 0) {
deck->card_count--;
for(int i = 0, curr_index = 0; i <= deck->card_count; i++) {
if(i != index) {
deck->cards[curr_index] = deck->cards[i];
curr_index++;
} else {
result = deck->cards[i];
}
}
if(deck->index >= 0) {
deck->index--;
}
}
return result;
}
void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index) {
FURI_LOG_D("CARD", "Extracting hand region");
if(start_index >= hand->index) return;
for(uint8_t i = start_index; i < hand->index; i++) {
add_to_hand(to, hand->cards[i]);
}
hand->index = start_index;
}
void add_hand_region(Hand* to, Hand* from) {
FURI_LOG_D("CARD", "Adding hand region");
if((to->index + from->index) <= to->max) {
for(int i = 0; i < from->index; i++) {
add_to_hand(to, from->cards[i]);
}
}
}

View File

@@ -1,192 +0,0 @@
#pragma once
#include <gui/gui.h>
#include <math.h>
#include <stdlib.h>
#include "dml.h"
#define CARD_HEIGHT 23
#define CARD_HALF_HEIGHT 11
#define CARD_WIDTH 17
#define CARD_HALF_WIDTH 8
//region types
typedef struct {
uint8_t pip; //Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
uint8_t character; //Card letter [0-12], 0 means 2, 12 is Ace
bool disabled;
bool flipped;
} Card;
typedef struct {
uint8_t deck_count; //Number of decks used
Card* cards; //Cards in the deck
int card_count;
int index; //Card index (to know where we at in the deck)
} Deck;
typedef struct {
Card* cards; //Cards in the deck
uint8_t index; //Current index
uint8_t max; //How many cards we want to store
} Hand;
//endregion
void set_card_graphics(const Icon* graphics);
/**
* Gets card coordinates at the index (range: 0-20).
*
* @param index Index to check 0-20
* @return Position of the card
*/
Vector card_pos_at_index(uint8_t index);
/**
* Draws card at a given coordinate (top-left corner)
*
* @param pos_x X position
* @param pos_y Y position
* @param pip Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
* @param character Letter [0-12] 0 is 2, 12 is A
* @param canvas Pointer to Flipper's canvas object
*/
void draw_card_at(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, Canvas* const canvas);
/**
* Draws card at a given coordinate (top-left corner)
*
* @param pos_x X position
* @param pos_y Y position
* @param pip Pip index 0:spades, 1:hearths, 2:diamonds, 3:clubs
* @param character Letter [0-12] 0 is 2, 12 is A
* @param inverted Invert colors
* @param canvas Pointer to Flipper's canvas object
*/
void draw_card_at_colored(
int8_t pos_x,
int8_t pos_y,
uint8_t pip,
uint8_t character,
bool inverted,
Canvas* const canvas);
/**
* Draws 'count' cards at the bottom right corner
*
* @param cards List of cards
* @param count Count of cards
* @param canvas Pointer to Flipper's canvas object
*/
void draw_deck(const Card* cards, uint8_t count, Canvas* const canvas);
/**
* Draws card back at a given coordinate (top-left corner)
*
* @param pos_x X coordinate
* @param pos_y Y coordinate
* @param canvas Pointer to Flipper's canvas object
*/
void draw_card_back_at(int8_t pos_x, int8_t pos_y, Canvas* const canvas);
/**
* Generates the deck
*
* @param deck_ptr Pointer to the deck
* @param deck_count Number of decks
*/
void generate_deck(Deck* deck_ptr, uint8_t deck_count);
/**
* Shuffles the deck
*
* @param deck_ptr Pointer to the deck
*/
void shuffle_deck(Deck* deck_ptr);
/**
* Calculates the hand count for blackjack
*
* @param cards List of cards
* @param count Count of cards
* @return Hand value
*/
uint8_t hand_count(const Card* cards, uint8_t count);
/**
* Draws card animation
*
* @param animatingCard Card to animate
* @param from Starting position
* @param control Quadratic lerp control point
* @param to End point
* @param t Current time (0-1)
* @param extra_margin Use extra margin at the end (arrives 0.2 unit before the end so it can stay there a bit)
* @param canvas Pointer to Flipper's canvas object
*/
void draw_card_animation(
Card animatingCard,
Vector from,
Vector control,
Vector to,
float t,
bool extra_margin,
Canvas* const canvas);
/**
* Init hand pointer
* @param hand_ptr Pointer to hand
* @param count Number of cards we want to store
*/
void init_hand(Hand* hand_ptr, uint8_t count);
/**
* Free hand resources
* @param hand_ptr Pointer to hand
*/
void free_hand(Hand* hand_ptr);
/**
* Add card to hand
* @param hand_ptr Pointer to hand
* @param card Card to add
*/
void add_to_hand(Hand* hand_ptr, Card card);
/**
* Draw card placement position at coordinate
* @param pos_x X coordinate
* @param pos_y Y coordinate
* @param highlighted Apply highlight effect
* @param canvas Canvas object
*/
void draw_card_space(int16_t pos_x, int16_t pos_y, bool highlighted, Canvas* const canvas);
/**
* Draws a column of card, displaying the last [max_cards] cards on the list
* @param hand Hand object
* @param pos_x X coordinate to draw
* @param pos_y Y coordinate to draw
* @param highlight Index to highlight, negative means no highlight
* @param canvas Canvas object
*/
void draw_hand_column(
Hand hand,
int16_t pos_x,
int16_t pos_y,
int8_t highlight,
Canvas* const canvas);
/**
* Removes a card from the deck (Be aware, if you remove the first item, the deck index will be at -1 so you have to handle that)
* @param index Index to remove
* @param deck Deck reference
* @return The removed card
*/
Card remove_from_deck(uint16_t index, Deck* deck);
int first_non_flipped_card(Hand hand);
void extract_hand_region(Hand* hand, Hand* to, uint8_t start_index);
void add_hand_region(Hand* to, Hand* from);

View File

@@ -1,53 +0,0 @@
#include "dml.h"
#include <math.h>
float lerp(float v0, float v1, float t) {
if(t > 1) return v1;
return (1 - t) * v0 + t * v1;
}
Vector lerp_2d(Vector start, Vector end, float t) {
return (Vector){
lerp(start.x, end.x, t),
lerp(start.y, end.y, t),
};
}
Vector quadratic_2d(Vector start, Vector control, Vector end, float t) {
return lerp_2d(lerp_2d(start, control, t), lerp_2d(control, end, t), t);
}
Vector vector_add(Vector a, Vector b) {
return (Vector){a.x + b.x, a.y + b.y};
}
Vector vector_sub(Vector a, Vector b) {
return (Vector){a.x - b.x, a.y - b.y};
}
Vector vector_mul_components(Vector a, Vector b) {
return (Vector){a.x * b.x, a.y * b.y};
}
Vector vector_div_components(Vector a, Vector b) {
return (Vector){a.x / b.x, a.y / b.y};
}
Vector vector_normalized(Vector a) {
float length = vector_magnitude(a);
return (Vector){a.x / length, a.y / length};
}
float vector_magnitude(Vector a) {
return sqrt(a.x * a.x + a.y * a.y);
}
float vector_distance(Vector a, Vector b) {
return vector_magnitude(vector_sub(a, b));
}
float vector_dot(Vector a, Vector b) {
Vector _a = vector_normalized(a);
Vector _b = vector_normalized(b);
return _a.x * _b.x + _a.y * _b.y;
}

View File

@@ -1,116 +0,0 @@
//
// Doofy's Math library
//
#pragma once
typedef struct {
float x;
float y;
} Vector;
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define abs(x) ((x) > 0 ? (x) : -(x))
/**
* Lerp function
*
* @param v0 Start value
* @param v1 End value
* @param t Time (0-1 range)
* @return Point between v0-v1 at a given time
*/
float lerp(float v0, float v1, float t);
/**
* 2D lerp function
*
* @param start Start vector
* @param end End vector
* @param t Time (0-1 range)
* @return 2d Vector between start and end at time
*/
Vector lerp_2d(Vector start, Vector end, float t);
/**
* Quadratic lerp function
*
* @param start Start vector
* @param control Control point
* @param end End vector
* @param t Time (0-1 range)
* @return 2d Vector at time
*/
Vector quadratic_2d(Vector start, Vector control, Vector end, float t);
/**
* Add vector components together
*
* @param a First vector
* @param b Second vector
* @return Resulting vector
*/
Vector vector_add(Vector a, Vector b);
/**
* Subtract vector components together
*
* @param a First vector
* @param b Second vector
* @return Resulting vector
*/
Vector vector_sub(Vector a, Vector b);
/**
* Multiplying vector components together
*
* @param a First vector
* @param b Second vector
* @return Resulting vector
*/
Vector vector_mul_components(Vector a, Vector b);
/**
* Dividing vector components
*
* @param a First vector
* @param b Second vector
* @return Resulting vector
*/
Vector vector_div_components(Vector a, Vector b);
/**
* Calculating Vector length
*
* @param a Direction vector
* @return Length of the vector
*/
float vector_magnitude(Vector a);
/**
* Get a normalized vector (length of 1)
*
* @param a Direction vector
* @return Normalized vector
*/
Vector vector_normalized(Vector a);
/**
* Calculate two vector's distance
*
* @param a First vector
* @param b Second vector
* @return Distance between vectors
*/
float vector_distance(Vector a, Vector b);
/**
* Calculate the dot product of the vectors.
* No need to normalize, it will do it
*
* @param a First vector
* @param b Second vector
* @return value from -1 to 1
*/
float vector_dot(Vector a, Vector b);

View File

@@ -1,103 +0,0 @@
#include "menu.h"
void add_menu(Menu* menu, const char* name, void (*callback)(void*)) {
MenuItem* items = menu->items;
menu->items = malloc(sizeof(MenuItem) * (menu->menu_count + 1));
for(uint8_t i = 0; i < menu->menu_count; i++) {
menu->items[i] = items[i];
}
free(items);
menu->items[menu->menu_count] = (MenuItem){name, true, callback};
menu->menu_count++;
}
void free_menu(Menu* menu) {
free(menu->items);
free(menu);
}
void set_menu_state(Menu* menu, uint8_t index, bool state) {
if(menu->menu_count > index) {
menu->items[index].enabled = state;
}
if(!state && menu->current_menu == index) move_menu(menu, 1);
}
void move_menu(Menu* menu, int8_t direction) {
if(!menu->enabled) return;
int max = menu->menu_count;
for(int8_t i = 0; i < max; i++) {
FURI_LOG_D(
"MENU",
"Iteration %i, current %i, direction %i, state %i",
i,
menu->current_menu,
direction,
menu->items[menu->current_menu].enabled ? 1 : 0);
if(direction < 0 && menu->current_menu == 0) {
menu->current_menu = menu->menu_count - 1;
} else {
menu->current_menu = (menu->current_menu + direction) % menu->menu_count;
}
FURI_LOG_D(
"MENU",
"After process current %i, direction %i, state %i",
menu->current_menu,
direction,
menu->items[menu->current_menu].enabled ? 1 : 0);
if(menu->items[menu->current_menu].enabled) {
FURI_LOG_D("MENU", "Next menu %i", menu->current_menu);
return;
}
}
FURI_LOG_D("MENU", "Not found, setting false");
menu->enabled = false;
}
void activate_menu(Menu* menu, void* state) {
if(!menu->enabled) return;
menu->items[menu->current_menu].callback(state);
}
void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y) {
if(!menu->enabled) return;
canvas_set_color(canvas, ColorWhite);
canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2);
uint8_t w = pos_x + menu->menu_width;
uint8_t h = pos_y + 10;
uint8_t p1x = pos_x + 2;
uint8_t p2x = pos_x + menu->menu_width - 2;
uint8_t p1y = pos_y + 2;
uint8_t p2y = pos_y + 8;
canvas_set_color(canvas, ColorBlack);
canvas_draw_line(canvas, p1x, pos_y, p2x, pos_y);
canvas_draw_line(canvas, p1x, h, p2x, h);
canvas_draw_line(canvas, pos_x, p1y, pos_x, p2y);
canvas_draw_line(canvas, w, p1y, w, p2y);
canvas_draw_dot(canvas, pos_x + 1, pos_y + 1);
canvas_draw_dot(canvas, w - 1, pos_y + 1);
canvas_draw_dot(canvas, w - 1, h - 1);
canvas_draw_dot(canvas, pos_x + 1, h - 1);
// canvas_draw_rbox(canvas, pos_x, pos_y, menu->menu_width + 2, 10, 2);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(
canvas,
pos_x + menu->menu_width / 2,
pos_y + 6,
AlignCenter,
AlignCenter,
menu->items[menu->current_menu].name);
//9*5
int center = pos_x + menu->menu_width / 2;
for(uint8_t i = 0; i < 4; i++) {
for(int8_t j = -i; j <= i; j++) {
canvas_draw_dot(canvas, center + j, pos_y - 4 + i);
canvas_draw_dot(canvas, center + j, pos_y + 14 - i);
}
}
}

View File

@@ -1,77 +0,0 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
typedef struct {
const char* name; //Name of the menu
bool enabled; //Is the menu item enabled (it will not render, you cannot select it)
void (*callback)(
void* state); //Callback for when the activate_menu is called while this menu is selected
} MenuItem;
typedef struct {
MenuItem* items; //list of menu items
uint8_t menu_count; //count of menu items (do not change)
uint8_t current_menu; //currently selected menu item
uint8_t menu_width; //width of the menu
bool enabled; //is the menu enabled (it will not render and accept events when disabled)
} Menu;
/**
* Cleans up the pointers used by the menu
*
* @param menu Pointer of the menu to clean up
*/
void free_menu(Menu* menu);
/**
* Add a new menu item
*
* @param menu Pointer of the menu
* @param name Name of the menu item
* @param callback Callback called on activation
*/
void add_menu(Menu* menu, const char* name, void (*callback)(void*));
/**
* Setting menu item to be enabled/disabled
*
* @param menu Pointer of the menu
* @param index Menu index to set
* @param state Enabled (true), Disabled(false)
*/
void set_menu_state(Menu* menu, uint8_t index, bool state);
/**
* Moves selection up or down
*
* @param menu Pointer of the menu
* @param direction Direction to move -1 down, 1 up
*/
void move_menu(Menu* menu, int8_t direction);
/**
* Triggers the current menu callback
*
* @param menu Pointer of the menu
* @param state Usually your application state
*/
void activate_menu(Menu* menu, void* state);
/**
* Renders the menu at a coordinate (call it in your render function).
*
* Keep in mind that Flipper has a 128x64 pixel screen resolution and the coordinate
* you give is the menu's rectangle top-left corner (arrows not included).
* The rectangle height is 10 px, the arrows have a 4 pixel height. Space needed is 18px.
* The width of the menu can be configured in the menu object.
*
*
* @param menu Pointer of the menu
* @param canvas Flippers Canvas pointer
* @param pos_x X position to draw
* @param pos_y Y position to draw
*/
void render_menu(Menu* menu, Canvas* canvas, uint8_t pos_x, uint8_t pos_y);

View File

@@ -1,69 +0,0 @@
#include "queue.h"
void render_queue(const QueueState* queue_state, const void* app_state, Canvas* const canvas) {
if(queue_state->current != NULL && queue_state->current->render != NULL)
((QueueItem*)queue_state->current)->render(app_state, canvas);
}
bool run_queue(QueueState* queue_state, void* app_state) {
if(queue_state->current != NULL) {
queue_state->running = true;
if((furi_get_tick() - queue_state->start) >= queue_state->current->duration)
dequeue(queue_state, app_state);
return true;
}
return false;
}
void dequeue(QueueState* queue_state, void* app_state) {
((QueueItem*)queue_state->current)->callback(app_state);
QueueItem* f = queue_state->current;
queue_state->current = f->next;
free(f);
if(queue_state->current != NULL) {
if(queue_state->current->start != NULL) queue_state->current->start(app_state);
queue_state->start = furi_get_tick();
} else {
queue_state->running = false;
}
}
void queue_clear(QueueState* queue_state) {
queue_state->running = false;
QueueItem* curr = queue_state->current;
while(curr != NULL) {
QueueItem* f = curr;
curr = curr->next;
free(f);
}
}
void enqueue(
QueueState* queue_state,
void* app_state,
void (*done)(void* state),
void (*start)(void* state),
void (*render)(const void* state, Canvas* const canvas),
uint32_t duration) {
QueueItem* next;
if(queue_state->current == NULL) {
queue_state->start = furi_get_tick();
queue_state->current = malloc(sizeof(QueueItem));
next = queue_state->current;
if(next->start != NULL) next->start(app_state);
} else {
next = queue_state->current;
while(next->next != NULL) {
next = (QueueItem*)(next->next);
}
next->next = malloc(sizeof(QueueItem));
next = next->next;
}
next->callback = done;
next->render = render;
next->start = start;
next->duration = duration;
next->next = NULL;
}

Some files were not shown because too many files have changed in this diff Show More