Add 3 plugins to be included in base package
Solitaire, Blackjack, HEX Viewer
13
applications/plugins/blackjack/application.fam
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
App(
|
||||||
|
appid="Blackjack",
|
||||||
|
name="Blackjack",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="blackjack_app",
|
||||||
|
cdefines=["APP_BLACKJACK"],
|
||||||
|
requires=["gui","storage","canvas"],
|
||||||
|
stack_size=2 * 1024,
|
||||||
|
order=30,
|
||||||
|
fap_icon="blackjack_10px.png",
|
||||||
|
fap_category="Games",
|
||||||
|
fap_icon_assets="assets"
|
||||||
|
)
|
BIN
applications/plugins/blackjack/assets/blackjack.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
applications/plugins/blackjack/assets/card_graphics.png
Normal file
After Width: | Height: | Size: 409 B |
BIN
applications/plugins/blackjack/assets/endscreen.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
634
applications/plugins/blackjack/blackjack.c
Normal file
@ -0,0 +1,634 @@
|
|||||||
|
|
||||||
|
#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"
|
||||||
|
|
||||||
|
#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) {
|
||||||
|
const GameState* game_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||||
|
|
||||||
|
if(game_state == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
release_mutex((ValueMutex*)ctx, game_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
//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) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
ValueMutex state_mutex;
|
||||||
|
if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
|
||||||
|
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, &state_mutex);
|
||||||
|
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("gui");
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
AppEvent event;
|
||||||
|
|
||||||
|
for(bool processing = true; processing;) {
|
||||||
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||||||
|
GameState* localstate = (GameState*)acquire_mutex_block(&state_mutex);
|
||||||
|
if(event_status == FuriStatusOk) {
|
||||||
|
if(event.type == EventTypeKey) {
|
||||||
|
if(event.input.type == InputTypePress) {
|
||||||
|
switch(event.input.key) {
|
||||||
|
case InputKeyUp:
|
||||||
|
localstate->selectDirection = DirectionUp;
|
||||||
|
break;
|
||||||
|
case InputKeyDown:
|
||||||
|
localstate->selectDirection = DirectionDown;
|
||||||
|
break;
|
||||||
|
case InputKeyRight:
|
||||||
|
localstate->selectDirection = DirectionRight;
|
||||||
|
break;
|
||||||
|
case InputKeyLeft:
|
||||||
|
localstate->selectDirection = DirectionLeft;
|
||||||
|
break;
|
||||||
|
case InputKeyBack:
|
||||||
|
if(localstate->state == GameStateSettings) {
|
||||||
|
localstate->state = GameStateStart;
|
||||||
|
save_settings(localstate->settings);
|
||||||
|
} else
|
||||||
|
processing = false;
|
||||||
|
break;
|
||||||
|
case InputKeyOk:
|
||||||
|
localstate->selectDirection = Select;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(event.type == EventTypeTick) {
|
||||||
|
tick(localstate);
|
||||||
|
processing = localstate->processing;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//FURI_LOG_D(APP_NAME, "osMessageQueue: event timeout");
|
||||||
|
// event timeout
|
||||||
|
}
|
||||||
|
view_port_update(view_port);
|
||||||
|
release_mutex(&state_mutex, localstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
delete_mutex(&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;
|
||||||
|
}
|
BIN
applications/plugins/blackjack/blackjack_10px.png
Normal file
After Width: | Height: | Size: 119 B |
353
applications/plugins/blackjack/common/card.c
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
#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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
192
applications/plugins/blackjack/common/card.h
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
#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);
|
53
applications/plugins/blackjack/common/dml.c
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#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;
|
||||||
|
}
|
116
applications/plugins/blackjack/common/dml.h
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
//
|
||||||
|
// 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);
|
103
applications/plugins/blackjack/common/menu.c
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
applications/plugins/blackjack/common/menu.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#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);
|
69
applications/plugins/blackjack/common/queue.c
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#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;
|
||||||
|
}
|
70
applications/plugins/blackjack/common/queue.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#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);
|
257
applications/plugins/blackjack/common/ui.c
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
applications/plugins/blackjack/common/ui.h
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#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);
|
76
applications/plugins/blackjack/defines.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
#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 {
|
||||||
|
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;
|
186
applications/plugins/blackjack/ui.c
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
#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
|
||||||
|
// );
|
||||||
|
}
|
18
applications/plugins/blackjack/ui.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#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);
|
123
applications/plugins/blackjack/util.c
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include <storage/storage.h>
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
const char* CONFIG_FILE_PATH = EXT_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);
|
||||||
|
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;
|
||||||
|
}
|
7
applications/plugins/blackjack/util.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#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();
|
16
applications/plugins/hex_viewer/application.fam
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
App(
|
||||||
|
appid="hex_viewer",
|
||||||
|
name="HEX Viewer",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="hex_viewer_app",
|
||||||
|
cdefines=["APP_HEX_VIEWER"],
|
||||||
|
requires=[
|
||||||
|
"gui",
|
||||||
|
"dialogs",
|
||||||
|
],
|
||||||
|
stack_size=2 * 1024,
|
||||||
|
order=20,
|
||||||
|
fap_icon="icons/hex_10px.png",
|
||||||
|
fap_category="Misc",
|
||||||
|
fap_icon_assets="icons",
|
||||||
|
)
|
285
applications/plugins/hex_viewer/hex_viewer.c
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
|
||||||
|
#include <hex_viewer_icons.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <dialogs/dialogs.h>
|
||||||
|
|
||||||
|
#include <storage/storage.h>
|
||||||
|
#include <stream/stream.h>
|
||||||
|
#include <stream/buffered_file_stream.h>
|
||||||
|
#include <toolbox/stream/file_stream.h>
|
||||||
|
|
||||||
|
#define TAG "HexViewer"
|
||||||
|
|
||||||
|
#define HEX_VIEWER_APP_PATH_FOLDER "/any"
|
||||||
|
#define HEX_VIEWER_APP_EXTENSION "*"
|
||||||
|
|
||||||
|
#define HEX_VIEWER_BYTES_PER_ROW 4
|
||||||
|
#define HEX_VIEWER_ROW_COUNT 4
|
||||||
|
#define HEX_VIEWER_BUF_SIZE (HEX_VIEWER_BYTES_PER_ROW * HEX_VIEWER_ROW_COUNT)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t file_bytes[HEX_VIEWER_ROW_COUNT][HEX_VIEWER_ROW_COUNT];
|
||||||
|
uint32_t line;
|
||||||
|
uint32_t read_bytes;
|
||||||
|
uint32_t file_size;
|
||||||
|
Stream* stream;
|
||||||
|
bool mode; // Print address or content
|
||||||
|
} HexViewerModel;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HexViewerModel* model;
|
||||||
|
FuriMutex** mutex;
|
||||||
|
|
||||||
|
FuriMessageQueue* input_queue;
|
||||||
|
|
||||||
|
ViewPort* view_port;
|
||||||
|
Gui* gui;
|
||||||
|
Storage* storage;
|
||||||
|
} HexViewer;
|
||||||
|
|
||||||
|
static void render_callback(Canvas* canvas, void* ctx) {
|
||||||
|
HexViewer* hex_viewer = ctx;
|
||||||
|
furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
|
||||||
|
canvas_clear(canvas);
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
elements_button_left(canvas, hex_viewer->model->mode ? "Addr" : "Text");
|
||||||
|
elements_button_right(canvas, "Info");
|
||||||
|
|
||||||
|
int ROW_HEIGHT = 12;
|
||||||
|
int TOP_OFFSET = 10;
|
||||||
|
int LEFT_OFFSET = 3;
|
||||||
|
|
||||||
|
uint32_t line_count = hex_viewer->model->file_size / HEX_VIEWER_BYTES_PER_ROW;
|
||||||
|
if(hex_viewer->model->file_size % HEX_VIEWER_BYTES_PER_ROW != 0) line_count += 1;
|
||||||
|
if(line_count > HEX_VIEWER_ROW_COUNT) {
|
||||||
|
uint8_t width = canvas_width(canvas);
|
||||||
|
elements_scrollbar_pos(
|
||||||
|
canvas,
|
||||||
|
width,
|
||||||
|
0,
|
||||||
|
ROW_HEIGHT * HEX_VIEWER_ROW_COUNT,
|
||||||
|
hex_viewer->model->line,
|
||||||
|
line_count - (HEX_VIEWER_ROW_COUNT - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
char temp_buf[32];
|
||||||
|
uint32_t row_iters = hex_viewer->model->read_bytes / HEX_VIEWER_BYTES_PER_ROW;
|
||||||
|
if(hex_viewer->model->read_bytes % HEX_VIEWER_BYTES_PER_ROW != 0) row_iters += 1;
|
||||||
|
|
||||||
|
for(uint32_t i = 0; i < row_iters; ++i) {
|
||||||
|
uint32_t bytes_left_per_row = hex_viewer->model->read_bytes - i * HEX_VIEWER_BYTES_PER_ROW;
|
||||||
|
if(bytes_left_per_row > HEX_VIEWER_BYTES_PER_ROW)
|
||||||
|
bytes_left_per_row = HEX_VIEWER_BYTES_PER_ROW;
|
||||||
|
|
||||||
|
if(hex_viewer->model->mode) {
|
||||||
|
memcpy(temp_buf, hex_viewer->model->file_bytes[i], bytes_left_per_row);
|
||||||
|
temp_buf[bytes_left_per_row] = '\0';
|
||||||
|
for(uint32_t j = 0; j < bytes_left_per_row; ++j)
|
||||||
|
if(!isprint((int)temp_buf[j])) temp_buf[j] = '.';
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontKeyboard);
|
||||||
|
canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||||
|
} else {
|
||||||
|
int addr = (i + hex_viewer->model->line) * HEX_VIEWER_BYTES_PER_ROW;
|
||||||
|
snprintf(temp_buf, 32, "%04X", addr);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontKeyboard);
|
||||||
|
canvas_draw_str(canvas, LEFT_OFFSET, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* p = temp_buf;
|
||||||
|
for(uint32_t j = 0; j < bytes_left_per_row; ++j)
|
||||||
|
p += snprintf(p, 32, "%02X ", hex_viewer->model->file_bytes[i][j]);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontKeyboard);
|
||||||
|
canvas_draw_str(canvas, LEFT_OFFSET + 41, TOP_OFFSET + i * ROW_HEIGHT, temp_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_mutex_release(hex_viewer->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void input_callback(InputEvent* input_event, void* ctx) {
|
||||||
|
HexViewer* hex_viewer = ctx;
|
||||||
|
if(input_event->type == InputTypeShort) {
|
||||||
|
furi_message_queue_put(hex_viewer->input_queue, input_event, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static HexViewer* hex_viewer_alloc() {
|
||||||
|
HexViewer* instance = malloc(sizeof(HexViewer));
|
||||||
|
|
||||||
|
instance->model = malloc(sizeof(HexViewerModel));
|
||||||
|
memset(instance->model, 0x0, sizeof(HexViewerModel));
|
||||||
|
|
||||||
|
instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||||
|
|
||||||
|
instance->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||||
|
|
||||||
|
instance->view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(instance->view_port, render_callback, instance);
|
||||||
|
view_port_input_callback_set(instance->view_port, input_callback, instance);
|
||||||
|
|
||||||
|
instance->gui = furi_record_open(RECORD_GUI);
|
||||||
|
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
instance->storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hex_viewer_free(HexViewer* instance) {
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
|
||||||
|
gui_remove_view_port(instance->gui, instance->view_port);
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
view_port_free(instance->view_port);
|
||||||
|
|
||||||
|
furi_message_queue_free(instance->input_queue);
|
||||||
|
|
||||||
|
furi_mutex_free(instance->mutex);
|
||||||
|
|
||||||
|
if(instance->model->stream) buffered_file_stream_close(instance->model->stream);
|
||||||
|
|
||||||
|
free(instance->model);
|
||||||
|
free(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hex_viewer_open_file(HexViewer* hex_viewer, const char* file_path) {
|
||||||
|
furi_assert(hex_viewer);
|
||||||
|
furi_assert(file_path);
|
||||||
|
|
||||||
|
hex_viewer->model->stream = buffered_file_stream_alloc(hex_viewer->storage);
|
||||||
|
bool isOk = true;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(!buffered_file_stream_open(
|
||||||
|
hex_viewer->model->stream, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
|
FURI_LOG_E(TAG, "Unable to open stream: %s", file_path);
|
||||||
|
isOk = false;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
hex_viewer->model->file_size = stream_size(hex_viewer->model->stream);
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return isOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hex_viewer_read_file(HexViewer* hex_viewer) {
|
||||||
|
furi_assert(hex_viewer);
|
||||||
|
furi_assert(hex_viewer->model->stream);
|
||||||
|
|
||||||
|
memset(hex_viewer->model->file_bytes, 0x0, HEX_VIEWER_BUF_SIZE);
|
||||||
|
bool isOk = true;
|
||||||
|
|
||||||
|
do {
|
||||||
|
uint32_t offset = hex_viewer->model->line * HEX_VIEWER_BYTES_PER_ROW;
|
||||||
|
if(!stream_seek(hex_viewer->model->stream, offset, true)) {
|
||||||
|
FURI_LOG_E(TAG, "Unable to seek stream");
|
||||||
|
isOk = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
hex_viewer->model->read_bytes = stream_read(
|
||||||
|
hex_viewer->model->stream,
|
||||||
|
(uint8_t*)hex_viewer->model->file_bytes,
|
||||||
|
HEX_VIEWER_BUF_SIZE);
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return isOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t hex_viewer_app(void* p) {
|
||||||
|
HexViewer* hex_viewer = hex_viewer_alloc();
|
||||||
|
|
||||||
|
FuriString* file_path;
|
||||||
|
file_path = furi_string_alloc();
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(p && strlen(p)) {
|
||||||
|
furi_string_set(file_path, (const char*)p);
|
||||||
|
} else {
|
||||||
|
furi_string_set(file_path, HEX_VIEWER_APP_PATH_FOLDER);
|
||||||
|
|
||||||
|
DialogsFileBrowserOptions browser_options;
|
||||||
|
dialog_file_browser_set_basic_options(
|
||||||
|
&browser_options, HEX_VIEWER_APP_EXTENSION, &I_hex_10px);
|
||||||
|
browser_options.hide_ext = false;
|
||||||
|
|
||||||
|
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
|
bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
|
||||||
|
|
||||||
|
furi_record_close(RECORD_DIALOGS);
|
||||||
|
if(!res) {
|
||||||
|
FURI_LOG_I(TAG, "No file selected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!hex_viewer_open_file(hex_viewer, furi_string_get_cstr(file_path))) break;
|
||||||
|
hex_viewer_read_file(hex_viewer);
|
||||||
|
|
||||||
|
InputEvent input;
|
||||||
|
while(furi_message_queue_get(hex_viewer->input_queue, &input, FuriWaitForever) ==
|
||||||
|
FuriStatusOk) {
|
||||||
|
if(input.key == InputKeyBack) {
|
||||||
|
break;
|
||||||
|
} else if(input.key == InputKeyUp) {
|
||||||
|
furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
if(hex_viewer->model->line > 0) {
|
||||||
|
hex_viewer->model->line--;
|
||||||
|
|
||||||
|
if(!hex_viewer_read_file(hex_viewer)) break;
|
||||||
|
}
|
||||||
|
furi_mutex_release(hex_viewer->mutex);
|
||||||
|
} else if(input.key == InputKeyDown) {
|
||||||
|
furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
uint32_t cur_pos = hex_viewer->model->line * HEX_VIEWER_BYTES_PER_ROW +
|
||||||
|
hex_viewer->model->read_bytes;
|
||||||
|
|
||||||
|
if(hex_viewer->model->file_size > cur_pos) {
|
||||||
|
hex_viewer->model->line++;
|
||||||
|
if(!hex_viewer_read_file(hex_viewer)) break;
|
||||||
|
}
|
||||||
|
furi_mutex_release(hex_viewer->mutex);
|
||||||
|
} else if(input.key == InputKeyLeft) {
|
||||||
|
furi_check(furi_mutex_acquire(hex_viewer->mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
hex_viewer->model->mode = !hex_viewer->model->mode;
|
||||||
|
furi_mutex_release(hex_viewer->mutex);
|
||||||
|
} else if(input.key == InputKeyRight) {
|
||||||
|
FuriString* buffer;
|
||||||
|
buffer = furi_string_alloc();
|
||||||
|
furi_string_printf(
|
||||||
|
buffer,
|
||||||
|
"File path: %s\nFile size: %lu bytes",
|
||||||
|
furi_string_get_cstr(file_path),
|
||||||
|
hex_viewer->model->file_size);
|
||||||
|
|
||||||
|
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||||
|
DialogMessage* message = dialog_message_alloc();
|
||||||
|
dialog_message_set_header(message, "Hex Viewer", 16, 2, AlignLeft, AlignTop);
|
||||||
|
dialog_message_set_icon(message, &I_hex_10px, 3, 2);
|
||||||
|
dialog_message_set_text(
|
||||||
|
message, furi_string_get_cstr(buffer), 3, 16, AlignLeft, AlignTop);
|
||||||
|
dialog_message_set_buttons(message, NULL, NULL, "Back");
|
||||||
|
dialog_message_show(dialogs, message);
|
||||||
|
|
||||||
|
furi_string_free(buffer);
|
||||||
|
dialog_message_free(message);
|
||||||
|
furi_record_close(RECORD_DIALOGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
view_port_update(hex_viewer->view_port);
|
||||||
|
}
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
furi_string_free(file_path);
|
||||||
|
hex_viewer_free(hex_viewer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
BIN
applications/plugins/hex_viewer/icons/hex_10px.png
Normal file
After Width: | Height: | Size: 171 B |
13
applications/plugins/solitaire/application.fam
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
App(
|
||||||
|
appid="Solitaire",
|
||||||
|
name="Solitaire",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="solitaire_app",
|
||||||
|
cdefines=["APP_SOLITAIRE"],
|
||||||
|
requires=["gui","storage","canvas"],
|
||||||
|
stack_size=2 * 1024,
|
||||||
|
order=30,
|
||||||
|
fap_icon="solitaire_10px.png",
|
||||||
|
fap_category="Games",
|
||||||
|
fap_icon_assets="assets"
|
||||||
|
)
|
BIN
applications/plugins/solitaire/assets/card_graphics.png
Normal file
After Width: | Height: | Size: 409 B |
BIN
applications/plugins/solitaire/assets/solitaire_main.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
353
applications/plugins/solitaire/common/card.c
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
#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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
192
applications/plugins/solitaire/common/card.h
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
#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);
|
53
applications/plugins/solitaire/common/dml.c
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#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;
|
||||||
|
}
|
116
applications/plugins/solitaire/common/dml.h
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
//
|
||||||
|
// 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);
|
103
applications/plugins/solitaire/common/menu.c
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
applications/plugins/solitaire/common/menu.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#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);
|
69
applications/plugins/solitaire/common/queue.c
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#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;
|
||||||
|
}
|
70
applications/plugins/solitaire/common/queue.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#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);
|
257
applications/plugins/solitaire/common/ui.c
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
applications/plugins/solitaire/common/ui.h
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#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);
|
56
applications/plugins/solitaire/defines.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#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"
|
||||||
|
|
||||||
|
#define APP_NAME "Solitaire"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EventTypeTick,
|
||||||
|
EventTypeKey,
|
||||||
|
} EventType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
EventType type;
|
||||||
|
InputEvent input;
|
||||||
|
} AppEvent;
|
||||||
|
|
||||||
|
typedef enum { GameStateGameOver, GameStateStart, GameStatePlay, GameStateAnimate } PlayState;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t* buffer;
|
||||||
|
Card card;
|
||||||
|
int8_t deck;
|
||||||
|
int indexes[4];
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float vx;
|
||||||
|
float vy;
|
||||||
|
bool started;
|
||||||
|
} CardAnimation;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Deck deck;
|
||||||
|
Hand bottom_columns[7];
|
||||||
|
Card top_cards[4];
|
||||||
|
bool dragging_deck;
|
||||||
|
uint8_t dragging_column;
|
||||||
|
Hand dragging_hand;
|
||||||
|
|
||||||
|
InputKey input;
|
||||||
|
|
||||||
|
bool started;
|
||||||
|
bool processing;
|
||||||
|
bool longPress;
|
||||||
|
PlayState state;
|
||||||
|
unsigned int last_tick;
|
||||||
|
uint8_t selectRow;
|
||||||
|
uint8_t selectColumn;
|
||||||
|
int8_t selected_card;
|
||||||
|
CardAnimation animation;
|
||||||
|
uint8_t* buffer;
|
||||||
|
} GameState;
|
568
applications/plugins/solitaire/solitaire.c
Normal file
@ -0,0 +1,568 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <dolphin/dolphin.h>
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/canvas_i.h>
|
||||||
|
#include "defines.h"
|
||||||
|
#include "common/ui.h"
|
||||||
|
#include "Solitaire_icons.h"
|
||||||
|
#include <notification/notification.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
void init(GameState* game_state);
|
||||||
|
const NotificationSequence sequence_fail = {
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_c4,
|
||||||
|
&message_delay_10,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_sound_off,
|
||||||
|
&message_delay_10,
|
||||||
|
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_a3,
|
||||||
|
&message_delay_10,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_sound_off,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
int8_t columns[7][3] = {
|
||||||
|
{1, 1, 25},
|
||||||
|
{19, 1, 25},
|
||||||
|
{37, 1, 25},
|
||||||
|
{55, 1, 25},
|
||||||
|
{73, 1, 25},
|
||||||
|
{91, 1, 25},
|
||||||
|
{109, 1, 25},
|
||||||
|
};
|
||||||
|
|
||||||
|
bool can_place_card(Card where, Card what) {
|
||||||
|
FURI_LOG_D(
|
||||||
|
APP_NAME,
|
||||||
|
"TESTING pip %i, letter %i with pip %i, letter %i",
|
||||||
|
where.pip,
|
||||||
|
where.character,
|
||||||
|
what.pip,
|
||||||
|
what.character);
|
||||||
|
bool a_black = where.pip == 0 || where.pip == 3;
|
||||||
|
bool b_black = what.pip == 0 || what.pip == 3;
|
||||||
|
if(a_black == b_black) return false;
|
||||||
|
|
||||||
|
int8_t a_letter = (int8_t)where.character;
|
||||||
|
int8_t b_letter = (int8_t)what.character;
|
||||||
|
if(a_letter == 12) a_letter = -1;
|
||||||
|
if(b_letter == 12) b_letter = -1;
|
||||||
|
|
||||||
|
return (a_letter - 1) == b_letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_scene(Canvas* const canvas, const GameState* game_state) {
|
||||||
|
int deckIndex = game_state->deck.index;
|
||||||
|
if(game_state->dragging_deck) deckIndex--;
|
||||||
|
|
||||||
|
if((game_state->deck.index < (game_state->deck.card_count - 1) ||
|
||||||
|
game_state->deck.index == -1) &&
|
||||||
|
game_state->deck.card_count > 0) {
|
||||||
|
draw_card_back_at(columns[0][0], columns[0][1], canvas);
|
||||||
|
if(game_state->selectRow == 0 && game_state->selectColumn == 0) {
|
||||||
|
draw_rounded_box(
|
||||||
|
canvas,
|
||||||
|
columns[0][0] + 1,
|
||||||
|
columns[0][1] + 1,
|
||||||
|
CARD_WIDTH - 2,
|
||||||
|
CARD_HEIGHT - 2,
|
||||||
|
Inverse);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
draw_card_space(
|
||||||
|
columns[0][0],
|
||||||
|
columns[0][1],
|
||||||
|
game_state->selectRow == 0 && game_state->selectColumn == 0,
|
||||||
|
canvas);
|
||||||
|
//deck side
|
||||||
|
if(deckIndex >= 0) {
|
||||||
|
Card c = game_state->deck.cards[deckIndex];
|
||||||
|
draw_card_at_colored(
|
||||||
|
columns[1][0],
|
||||||
|
columns[1][1],
|
||||||
|
c.pip,
|
||||||
|
c.character,
|
||||||
|
game_state->selectRow == 0 && game_state->selectColumn == 1,
|
||||||
|
canvas);
|
||||||
|
} else
|
||||||
|
draw_card_space(
|
||||||
|
columns[1][0],
|
||||||
|
columns[1][1],
|
||||||
|
game_state->selectRow == 0 && game_state->selectColumn == 1,
|
||||||
|
canvas);
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < 4; i++) {
|
||||||
|
Card current = game_state->top_cards[i];
|
||||||
|
bool selected = game_state->selectRow == 0 && game_state->selectColumn == (i + 3);
|
||||||
|
if(current.disabled) {
|
||||||
|
draw_card_space(columns[i + 3][0], columns[i + 3][1], selected, canvas);
|
||||||
|
} else {
|
||||||
|
draw_card_at(
|
||||||
|
columns[i + 3][0], columns[i + 3][1], current.pip, current.character, canvas);
|
||||||
|
if(selected) {
|
||||||
|
draw_rounded_box(
|
||||||
|
canvas, columns[i + 3][0], columns[i + 3][1], CARD_WIDTH, CARD_HEIGHT, Inverse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < 7; i++) {
|
||||||
|
bool selected = game_state->selectRow == 1 && game_state->selectColumn == i;
|
||||||
|
int8_t index = (game_state->bottom_columns[i].index - 1 - game_state->selected_card);
|
||||||
|
if(index < 0) index = 0;
|
||||||
|
draw_hand_column(
|
||||||
|
game_state->bottom_columns[i],
|
||||||
|
columns[i][0],
|
||||||
|
columns[i][2],
|
||||||
|
selected ? index : -1,
|
||||||
|
canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t pos[2] = {
|
||||||
|
columns[game_state->selectColumn][0],
|
||||||
|
columns[game_state->selectColumn][game_state->selectRow + 1]};
|
||||||
|
|
||||||
|
/* draw_icon_clip(canvas, &I_card_graphics, pos[0] + CARD_HALF_WIDTH, pos[1] + CARD_HALF_HEIGHT, 30, 5, 5, 5,
|
||||||
|
Filled);*/
|
||||||
|
|
||||||
|
if(game_state->dragging_hand.index > 0) {
|
||||||
|
draw_hand_column(
|
||||||
|
game_state->dragging_hand,
|
||||||
|
pos[0] + CARD_HALF_WIDTH + 3,
|
||||||
|
pos[1] + CARD_HALF_HEIGHT + 3,
|
||||||
|
-1,
|
||||||
|
canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_animation(Canvas* const canvas, const GameState* game_state) {
|
||||||
|
if(!game_state->animation.started) {
|
||||||
|
draw_scene(canvas, game_state);
|
||||||
|
} else {
|
||||||
|
clone_buffer(game_state->animation.buffer, get_buffer(canvas));
|
||||||
|
|
||||||
|
draw_card_at(
|
||||||
|
(int8_t)game_state->animation.x,
|
||||||
|
(int8_t)game_state->animation.y,
|
||||||
|
game_state->animation.card.pip,
|
||||||
|
game_state->animation.card.character,
|
||||||
|
canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
clone_buffer(get_buffer(canvas), game_state->animation.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||||
|
const GameState* game_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||||||
|
if(game_state == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(game_state->state) {
|
||||||
|
case GameStateAnimate:
|
||||||
|
draw_animation(canvas, game_state);
|
||||||
|
break;
|
||||||
|
case GameStateStart:
|
||||||
|
canvas_draw_icon(canvas, 0, 0, &I_solitaire_main);
|
||||||
|
break;
|
||||||
|
case GameStatePlay:
|
||||||
|
draw_scene(canvas, game_state);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
release_mutex((ValueMutex*)ctx, game_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_drag(GameState* game_state) {
|
||||||
|
if(game_state->dragging_deck) {
|
||||||
|
remove_from_deck(game_state->deck.index, &(game_state->deck));
|
||||||
|
game_state->dragging_deck = false;
|
||||||
|
} else if(game_state->dragging_column < 7) {
|
||||||
|
game_state->dragging_column = 8;
|
||||||
|
}
|
||||||
|
game_state->dragging_hand.index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleInput(GameState* game_state) {
|
||||||
|
Hand currentHand = game_state->bottom_columns[game_state->selectColumn];
|
||||||
|
switch(game_state->input) {
|
||||||
|
case InputKeyUp:
|
||||||
|
if(game_state->selectRow > 0) {
|
||||||
|
int first = first_non_flipped_card(currentHand);
|
||||||
|
first = currentHand.index - first;
|
||||||
|
if((first - 1) > game_state->selected_card && game_state->dragging_hand.index == 0 &&
|
||||||
|
!game_state->longPress) {
|
||||||
|
game_state->selected_card++;
|
||||||
|
} else {
|
||||||
|
game_state->selectRow--;
|
||||||
|
game_state->selected_card = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InputKeyDown:
|
||||||
|
if(game_state->selectRow < 1) {
|
||||||
|
game_state->selectRow++;
|
||||||
|
game_state->selected_card = 0;
|
||||||
|
} else {
|
||||||
|
if(game_state->selected_card > 0) {
|
||||||
|
if(game_state->longPress)
|
||||||
|
game_state->selected_card = 0;
|
||||||
|
else
|
||||||
|
game_state->selected_card--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InputKeyRight:
|
||||||
|
if(game_state->selectColumn < 6) {
|
||||||
|
game_state->selectColumn++;
|
||||||
|
game_state->selected_card = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InputKeyLeft:
|
||||||
|
if(game_state->selectColumn > 0) {
|
||||||
|
game_state->selectColumn--;
|
||||||
|
game_state->selected_card = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InputKeyOk:
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(game_state->selectRow == 0 && game_state->selectColumn == 2) {
|
||||||
|
if(game_state->input == InputKeyRight)
|
||||||
|
game_state->selectColumn++;
|
||||||
|
else
|
||||||
|
game_state->selectColumn--;
|
||||||
|
}
|
||||||
|
if(game_state->dragging_hand.index > 0) game_state->selected_card = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool place_on_top(Card* where, Card what) {
|
||||||
|
if(where->disabled && what.character == 12) {
|
||||||
|
where->disabled = what.disabled;
|
||||||
|
where->pip = what.pip;
|
||||||
|
where->character = what.character;
|
||||||
|
return true;
|
||||||
|
} else if(where->pip == what.pip) {
|
||||||
|
int8_t a_letter = (int8_t)where->character;
|
||||||
|
int8_t b_letter = (int8_t)what.character;
|
||||||
|
if(a_letter == 12) a_letter = -1;
|
||||||
|
if(b_letter == 12) b_letter = -1;
|
||||||
|
if((a_letter + 1) == b_letter) {
|
||||||
|
where->disabled = what.disabled;
|
||||||
|
where->pip = what.pip;
|
||||||
|
where->character = what.character;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tick(GameState* game_state, NotificationApp* notification) {
|
||||||
|
game_state->last_tick = furi_get_tick();
|
||||||
|
uint8_t row = game_state->selectRow;
|
||||||
|
uint8_t column = game_state->selectColumn;
|
||||||
|
if(game_state->state != GameStatePlay && game_state->state != GameStateAnimate) return;
|
||||||
|
bool wasAction = false;
|
||||||
|
if(game_state->state == GameStatePlay) {
|
||||||
|
if(game_state->top_cards[0].character == 11 && game_state->top_cards[1].character == 11 &&
|
||||||
|
game_state->top_cards[2].character == 11 && game_state->top_cards[3].character == 11) {
|
||||||
|
game_state->state = GameStateAnimate;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(handleInput(game_state)) {
|
||||||
|
if(game_state->state == GameStatePlay) {
|
||||||
|
if(game_state->longPress && game_state->dragging_hand.index == 1) {
|
||||||
|
for(uint8_t i = 0; i < 4; i++) {
|
||||||
|
if(place_on_top(
|
||||||
|
&(game_state->top_cards[i]), game_state->dragging_hand.cards[0])) {
|
||||||
|
remove_drag(game_state);
|
||||||
|
wasAction = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(row == 0 && column == 0 && game_state->dragging_hand.index == 0) {
|
||||||
|
FURI_LOG_D(APP_NAME, "Drawing card");
|
||||||
|
game_state->deck.index++;
|
||||||
|
wasAction = true;
|
||||||
|
if(game_state->deck.index >= (game_state->deck.card_count))
|
||||||
|
game_state->deck.index = -1;
|
||||||
|
}
|
||||||
|
//pick/place from deck
|
||||||
|
else if(row == 0 && column == 1) {
|
||||||
|
//place
|
||||||
|
if(game_state->dragging_deck) {
|
||||||
|
wasAction = true;
|
||||||
|
game_state->dragging_deck = false;
|
||||||
|
game_state->dragging_hand.index = 0;
|
||||||
|
}
|
||||||
|
//pick
|
||||||
|
else {
|
||||||
|
if(game_state->dragging_hand.index == 0 && game_state->deck.index >= 0) {
|
||||||
|
wasAction = true;
|
||||||
|
game_state->dragging_deck = true;
|
||||||
|
add_to_hand(
|
||||||
|
&(game_state->dragging_hand),
|
||||||
|
game_state->deck.cards[game_state->deck.index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//place on top row
|
||||||
|
else if(row == 0 && game_state->dragging_hand.index == 1) {
|
||||||
|
column -= 3;
|
||||||
|
Card currCard = game_state->dragging_hand.cards[0];
|
||||||
|
wasAction = place_on_top(&(game_state->top_cards[column]), currCard);
|
||||||
|
if(wasAction) remove_drag(game_state);
|
||||||
|
}
|
||||||
|
//pick/place from bottom
|
||||||
|
else if(row == 1) {
|
||||||
|
Hand* curr_hand = &(game_state->bottom_columns[column]);
|
||||||
|
//pick up
|
||||||
|
if(game_state->dragging_hand.index == 0) {
|
||||||
|
Card curr_card = curr_hand->cards[curr_hand->index - 1];
|
||||||
|
if(curr_card.flipped) {
|
||||||
|
curr_hand->cards[curr_hand->index - 1].flipped = false;
|
||||||
|
wasAction = true;
|
||||||
|
} else {
|
||||||
|
if(curr_hand->index > 0) {
|
||||||
|
extract_hand_region(
|
||||||
|
curr_hand,
|
||||||
|
&(game_state->dragging_hand),
|
||||||
|
curr_hand->index - game_state->selected_card - 1);
|
||||||
|
game_state->selected_card = 0;
|
||||||
|
game_state->dragging_column = column;
|
||||||
|
wasAction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//place
|
||||||
|
else {
|
||||||
|
Card first = game_state->dragging_hand.cards[0];
|
||||||
|
if(game_state->dragging_column == column ||
|
||||||
|
(curr_hand->index == 0 && first.character == 11) ||
|
||||||
|
can_place_card(curr_hand->cards[curr_hand->index - 1], first)) {
|
||||||
|
add_hand_region(curr_hand, &(game_state->dragging_hand));
|
||||||
|
remove_drag(game_state);
|
||||||
|
wasAction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!wasAction) {
|
||||||
|
notification_message(notification, &sequence_fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(game_state->state == GameStateAnimate) {
|
||||||
|
if(game_state->animation.started && !game_state->longPress &&
|
||||||
|
game_state->input == InputKeyOk) {
|
||||||
|
init(game_state);
|
||||||
|
game_state->state = GameStateStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
game_state->animation.started = true;
|
||||||
|
if(game_state->animation.x < -20 || game_state->animation.x > 128) {
|
||||||
|
game_state->animation.deck++;
|
||||||
|
if(game_state->animation.deck > 3) game_state->animation.deck = 0;
|
||||||
|
int8_t cardIndex = 11 - game_state->animation.indexes[game_state->animation.deck];
|
||||||
|
|
||||||
|
if(game_state->animation.indexes[0] == 13 && game_state->animation.indexes[1] == 13 &&
|
||||||
|
game_state->animation.indexes[2] == 13 && game_state->animation.indexes[3] == 13) {
|
||||||
|
init(game_state);
|
||||||
|
game_state->state = GameStateStart;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cardIndex == -1) cardIndex = 12;
|
||||||
|
game_state->animation.card = (Card){
|
||||||
|
game_state->top_cards[game_state->animation.deck].pip, cardIndex, false, false};
|
||||||
|
game_state->animation.indexes[game_state->animation.deck]++;
|
||||||
|
game_state->animation.vx = -(rand() % 3 + 1) * (rand() % 2 == 1 ? 1 : -1);
|
||||||
|
game_state->animation.vy = (rand() % 3 + 1);
|
||||||
|
game_state->animation.x = columns[game_state->animation.deck + 3][0];
|
||||||
|
game_state->animation.y = columns[game_state->animation.deck + 3][1];
|
||||||
|
}
|
||||||
|
game_state->animation.x += game_state->animation.vx;
|
||||||
|
game_state->animation.y -= game_state->animation.vy;
|
||||||
|
game_state->animation.vy -= 1;
|
||||||
|
if(game_state->animation.vy < -10) game_state->animation.vy = -10;
|
||||||
|
|
||||||
|
if(game_state->animation.y > 41) {
|
||||||
|
game_state->animation.y = 41;
|
||||||
|
game_state->animation.vy = -(game_state->animation.vy * 0.7f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(GameState* game_state) {
|
||||||
|
game_state->selectColumn = 0;
|
||||||
|
game_state->selected_card = 0;
|
||||||
|
game_state->selectRow = 0;
|
||||||
|
generate_deck(&(game_state->deck), 1);
|
||||||
|
shuffle_deck(&(game_state->deck));
|
||||||
|
game_state->dragging_deck = false;
|
||||||
|
game_state->animation.started = false;
|
||||||
|
game_state->animation.deck = -1;
|
||||||
|
game_state->animation.x = -21;
|
||||||
|
game_state->state = GameStatePlay;
|
||||||
|
game_state->dragging_column = 8;
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < 7; i++) {
|
||||||
|
free_hand(&(game_state->bottom_columns[i]));
|
||||||
|
init_hand(&(game_state->bottom_columns[i]), 21);
|
||||||
|
game_state->bottom_columns[i].index = 0;
|
||||||
|
for(uint8_t j = 0; j <= i; j++) {
|
||||||
|
Card cur = remove_from_deck(0, &(game_state->deck));
|
||||||
|
cur.flipped = i != j;
|
||||||
|
add_to_hand(&(game_state->bottom_columns[i]), cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < 4; i++) {
|
||||||
|
game_state->animation.indexes[i] = 0;
|
||||||
|
game_state->top_cards[i] = (Card){0, 0, true, false};
|
||||||
|
}
|
||||||
|
game_state->deck.index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_start(GameState* game_state) {
|
||||||
|
game_state->input = InputKeyMAX;
|
||||||
|
for(uint8_t i = 0; i < 7; i++) init_hand(&(game_state->bottom_columns[i]), 21);
|
||||||
|
|
||||||
|
init_hand(&(game_state->dragging_hand), 13);
|
||||||
|
game_state->animation.buffer = make_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t solitaire_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));
|
||||||
|
init_start(game_state);
|
||||||
|
set_card_graphics(&I_card_graphics);
|
||||||
|
|
||||||
|
game_state->state = GameStateStart;
|
||||||
|
|
||||||
|
game_state->processing = true;
|
||||||
|
ValueMutex state_mutex;
|
||||||
|
if(!init_mutex(&state_mutex, game_state, sizeof(GameState))) {
|
||||||
|
FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
|
||||||
|
return_code = 255;
|
||||||
|
goto free_and_exit;
|
||||||
|
}
|
||||||
|
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
|
||||||
|
notification_message_block(notification, &sequence_display_backlight_enforce_on);
|
||||||
|
|
||||||
|
ViewPort* view_port = view_port_alloc();
|
||||||
|
view_port_draw_callback_set(view_port, render_callback, &state_mutex);
|
||||||
|
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() / 30);
|
||||||
|
|
||||||
|
Gui* gui = furi_record_open("gui");
|
||||||
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||||
|
|
||||||
|
AppEvent event;
|
||||||
|
for(bool processing = true; processing;) {
|
||||||
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 150);
|
||||||
|
GameState* localstate = (GameState*)acquire_mutex_block(&state_mutex);
|
||||||
|
bool hadChange = false;
|
||||||
|
if(event_status == FuriStatusOk) {
|
||||||
|
if(event.type == EventTypeKey) {
|
||||||
|
if(event.input.type == InputTypeLong) {
|
||||||
|
game_state->longPress = true;
|
||||||
|
switch(event.input.key) {
|
||||||
|
case InputKeyUp:
|
||||||
|
case InputKeyDown:
|
||||||
|
case InputKeyRight:
|
||||||
|
case InputKeyLeft:
|
||||||
|
case InputKeyOk:
|
||||||
|
localstate->input = event.input.key;
|
||||||
|
break;
|
||||||
|
case InputKeyBack:
|
||||||
|
processing = false;
|
||||||
|
return_code = 1;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if(event.input.type == InputTypePress) {
|
||||||
|
game_state->longPress = false;
|
||||||
|
switch(event.input.key) {
|
||||||
|
case InputKeyUp:
|
||||||
|
case InputKeyDown:
|
||||||
|
case InputKeyRight:
|
||||||
|
case InputKeyLeft:
|
||||||
|
case InputKeyOk:
|
||||||
|
if(event.input.key == InputKeyOk && localstate->state == GameStateStart) {
|
||||||
|
localstate->state = GameStatePlay;
|
||||||
|
init(game_state);
|
||||||
|
} else {
|
||||||
|
hadChange = true;
|
||||||
|
localstate->input = event.input.key;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case InputKeyBack:
|
||||||
|
init(game_state);
|
||||||
|
processing = false;
|
||||||
|
return_code = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(event.type == EventTypeTick) {
|
||||||
|
tick(localstate, notification);
|
||||||
|
processing = localstate->processing;
|
||||||
|
localstate->input = InputKeyMAX;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//FURI_LOG_W(APP_NAME, "osMessageQueue: event timeout");
|
||||||
|
// event timeout
|
||||||
|
}
|
||||||
|
if(hadChange || game_state->state == GameStateAnimate) view_port_update(view_port);
|
||||||
|
release_mutex(&state_mutex, localstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
notification_message_block(notification, &sequence_display_backlight_enforce_auto);
|
||||||
|
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);
|
||||||
|
delete_mutex(&state_mutex);
|
||||||
|
|
||||||
|
free_and_exit:
|
||||||
|
free(game_state->animation.buffer);
|
||||||
|
ui_cleanup();
|
||||||
|
for(uint8_t i = 0; i < 7; i++) free_hand(&(game_state->bottom_columns[i]));
|
||||||
|
|
||||||
|
free(game_state->deck.cards);
|
||||||
|
free(game_state);
|
||||||
|
furi_message_queue_free(event_queue);
|
||||||
|
return return_code;
|
||||||
|
}
|
BIN
applications/plugins/solitaire/solitaire_10px.png
Normal file
After Width: | Height: | Size: 121 B |