diff --git a/applications/plugins/blackjack/application.fam b/applications/plugins/blackjack/application.fam index 798e240e3..8f14015aa 100644 --- a/applications/plugins/blackjack/application.fam +++ b/applications/plugins/blackjack/application.fam @@ -4,9 +4,10 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="blackjack_app", cdefines=["APP_BLACKJACK"], - requires=["gui"], - stack_size=1 * 1024, + requires=["gui","storage"], + stack_size=2 * 1024, order=30, fap_icon="blackjack_10px.png", fap_category="Games", + fap_icon_assets="assets" ) \ No newline at end of file diff --git a/applications/plugins/blackjack/assets/blackjack.png b/applications/plugins/blackjack/assets/blackjack.png new file mode 100644 index 000000000..bb367f28e Binary files /dev/null and b/applications/plugins/blackjack/assets/blackjack.png differ diff --git a/applications/plugins/blackjack/assets/endscreen.png b/applications/plugins/blackjack/assets/endscreen.png new file mode 100644 index 000000000..7a3abc927 Binary files /dev/null and b/applications/plugins/blackjack/assets/endscreen.png differ diff --git a/applications/plugins/blackjack/blackjack.c b/applications/plugins/blackjack/blackjack.c index 798fa6e5a..5ee273cec 100644 --- a/applications/plugins/blackjack/blackjack.c +++ b/applications/plugins/blackjack/blackjack.c @@ -2,20 +2,22 @@ #include #include #include +#include #include - +#include "util.h" #include "defines.h" #include "card.h" #include "util.h" #include "ui.h" -#define APP_NAME "Blackjack" -#define STARTING_MONEY 200 +#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); @@ -27,31 +29,32 @@ static void draw_ui(Canvas *const canvas, const GameState *game_state) { } } - 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); - switch (game_state->state) { - case GameStateStart: - case GameStateGameOver: - draw_message_scene(canvas, game_state); - break; - case GameStatePlay: - draw_player_scene(canvas, game_state); - break; - case GameStateDealer: - draw_dealer_scene(canvas, game_state); - break; + if (game_state->state == GameStateStart) { + canvas_draw_icon(canvas, 0, 0, &I_blackjack); } - if (game_state->state != GameStateStart && game_state->state != GameStateGameOver) { + 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); animateQueue(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); @@ -64,56 +67,78 @@ Card draw_card(GameState *game_state) { return c; } -char *letters[4] = {"spade", "hearth", "diamond", "club"}; void drawPlayerCard(GameState *game_state) { Card c = draw_card(game_state); game_state->player_cards[game_state->player_card_count] = c; game_state->player_card_count++; + if (game_state->selectedMenu == 0 && + (game_state->player_score < game_state->settings.round_price || game_state->doubled)) + game_state->selectedMenu = 1; } void drawDealerCard(GameState *game_state) { Card c = draw_card(game_state); game_state->dealer_cards[game_state->dealer_card_count] = c; game_state->dealer_card_count++; - FURI_LOG_D(APP_NAME, "drawing dealer %s %i", letters[c.pip], c.character + 2); } //endregion //region queue callbacks -void to_lose_state(const GameState *game_state, Canvas *const canvas) { +void to_lose_state(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { UNUSED(game_state); - popupFrame(canvas); + UNUSED(margin); + if (duration == 0) + return; + popup_frame(canvas); elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost"); } -void to_bust_state(const GameState *game_state, Canvas *const canvas) { +void to_bust_state(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { UNUSED(game_state); - popupFrame(canvas); + UNUSED(margin); + if (duration == 0) + return; + popup_frame(canvas); elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!"); } -void to_draw_state(const GameState *game_state, Canvas *const canvas) { +void to_draw_state(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { UNUSED(game_state); - popupFrame(canvas); + UNUSED(margin); + + if (duration == 0) + return; + popup_frame(canvas); elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw"); } -void to_dealer_turn(const GameState *game_state, Canvas *const canvas) { +void to_dealer_turn(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { UNUSED(game_state); - popupFrame(canvas); + UNUSED(margin); + + if (duration == 0) + return; + popup_frame(canvas); elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn"); } -void to_win_state(const GameState *game_state, Canvas *const canvas) { +void to_win_state(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { UNUSED(game_state); - popupFrame(canvas); + UNUSED(margin); + + if (duration == 0) + return; + popup_frame(canvas); elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win"); } -void to_start(const GameState *game_state, Canvas *const canvas) { +void to_start(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { UNUSED(game_state); - popupFrame(canvas); + UNUSED(margin); + if (duration == 0) + return; + popup_frame(canvas); elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started"); } @@ -130,7 +155,7 @@ void start(GameState *game_state) { void draw(GameState *game_state) { game_state->player_score += game_state->bet; game_state->bet = 0; - queue(game_state, start, before_start, to_start); + queue(game_state, start, before_start, to_start, game_state->settings.message_duration, 0); } void game_over(GameState *game_state) { @@ -140,33 +165,83 @@ void game_over(GameState *game_state) { void lose(GameState *game_state) { game_state->state = GameStatePlay; game_state->bet = 0; - if (game_state->player_score >= ROUND_PRICE) - queue(game_state, start, before_start, to_start); + if (game_state->player_score >= game_state->settings.round_price) + queue(game_state, start, before_start, to_start, game_state->settings.message_duration, 0); else - queue(game_state, game_over, NULL, NULL); + queue(game_state, game_over, NULL, NULL, 0, 0); } void win(GameState *game_state) { game_state->state = GameStatePlay; game_state->player_score += game_state->bet * 2; game_state->bet = 0; - queue(game_state, start, before_start, to_start); + queue(game_state, start, before_start, to_start, game_state->settings.message_duration, 0); } void dealerTurn(GameState *game_state) { game_state->state = GameStateDealer; } + +void dealer_card_animation(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { + float t = (float) (furi_get_tick() - game_state->animationStart) / (duration - margin); + + 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); + if (!is_at_edge(game_state->dealer_card_count)) + end.x -= CARD_HALF_WIDTH; + 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); +// drawPlayerDeck(game_state->dealer_cards, game_state->dealer_card_count, canvas); + } +} + +void dealer_back_card_animation(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { + float t = (float) (furi_get_tick() - game_state->animationStart) / (duration - margin); + Vector currentPos = quadratic_2d((Vector) {32, -CARD_HEIGHT}, (Vector) {64, 32}, (Vector) {13, 5}, t); + drawCardBackAt(currentPos.x, currentPos.y, canvas); +} + +void player_card_animation(const GameState *game_state, Canvas *const canvas, uint32_t duration, uint32_t margin) { + float t = (float) (furi_get_tick() - game_state->animationStart) / (duration - margin); + Card animatingCard = game_state->deck.cards[game_state->deck.index]; + Vector end = card_pos_at_index(game_state->player_card_count); + if (!is_at_edge(game_state->player_card_count)) + end.x -= CARD_HALF_WIDTH; + draw_card_animation(animatingCard, + (Vector) {32, -CARD_HEIGHT}, + (Vector) {0, 32}, + end, + t, + true, + canvas); +// drawPlayerDeck(game_state->dealer_cards, game_state->player_card_count, canvas); +} //endregion void player_tick(GameState *game_state) { uint8_t score = handCount(game_state->player_cards, game_state->player_card_count); if ((game_state->doubled && score <= 21) || score == 21) { - queue(game_state, dealerTurn, NULL, NULL); + queue(game_state, dealerTurn, NULL, NULL, game_state->settings.message_duration, 0); } else if (score > 21) { - queue(game_state, lose, NULL, to_bust_state); + queue(game_state, lose, NULL, to_bust_state, game_state->settings.message_duration, 0); } else { - if (game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) { + if (game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0 && + game_state->player_score >= game_state->settings.round_price && !game_state->doubled) { game_state->selectedMenu--; } if (game_state->selectDirection == DirectionDown && game_state->selectedMenu < 2) { @@ -175,65 +250,154 @@ void player_tick(GameState *game_state) { if (game_state->selectDirection == Select) { //double if (!game_state->doubled && game_state->selectedMenu == 0 && - game_state->player_score >= ROUND_PRICE) { + game_state->player_score >= game_state->settings.round_price) { - game_state->player_score -= ROUND_PRICE; - game_state->bet += ROUND_PRICE; + game_state->player_score -= game_state->settings.round_price; + game_state->bet += game_state->settings.round_price; game_state->doubled = true; game_state->selectedMenu = 1; - queue(game_state, drawPlayerCard, NULL, draw_card_animation); + queue(game_state, drawPlayerCard, NULL, player_card_animation, game_state->settings.animation_duration, + game_state->settings.animation_margin); game_state->player_cards[game_state->player_card_count] = game_state->deck.cards[game_state->deck.index]; score = handCount(game_state->player_cards, game_state->player_card_count + 1); if (score > 21) - queue(game_state, lose, NULL, to_bust_state); + queue(game_state, lose, NULL, to_bust_state, game_state->settings.message_duration, 0); else - queue(game_state, dealerTurn, NULL, to_dealer_turn); + queue(game_state, dealerTurn, NULL, to_dealer_turn, game_state->settings.message_duration, 0); } //hit else if (game_state->selectedMenu == 1) { - queue(game_state, drawPlayerCard, NULL, draw_card_animation); + queue(game_state, drawPlayerCard, NULL, player_card_animation, game_state->settings.animation_duration, + game_state->settings.animation_margin); } //stay else if (game_state->selectedMenu == 2) { - queue(game_state, dealerTurn, NULL, to_dealer_turn); + queue(game_state, dealerTurn, NULL, to_dealer_turn, game_state->settings.message_duration, 0); } } } } - void dealer_tick(GameState *game_state) { uint8_t dealer_score = handCount(game_state->dealer_cards, game_state->dealer_card_count); uint8_t player_score = handCount(game_state->player_cards, game_state->player_card_count); if (dealer_score >= DEALER_MAX) { if (dealer_score > 21 || dealer_score < player_score) - queue(game_state, win, NULL, to_win_state); + queue(game_state, win, NULL, to_win_state, game_state->settings.message_duration, 0); else if (dealer_score > player_score) - queue(game_state, lose, NULL, to_lose_state); + queue(game_state, lose, NULL, to_lose_state, game_state->settings.message_duration, 0); else if (dealer_score == player_score) - queue(game_state, draw, NULL, to_draw_state); + queue(game_state, draw, NULL, to_draw_state, game_state->settings.message_duration, 0); } else { - queue(game_state, drawDealerCard, NULL, draw_card_animation); + queue(game_state, drawDealerCard, NULL, dealer_card_animation, game_state->settings.animation_duration, + game_state->settings.animation_margin); } } +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_margin; + if (game_state->selectDirection == DirectionLeft) + nextScore -= 100; + else + nextScore += 100; + if (nextScore >= 0 && nextScore <= (int) game_state->settings.animation_duration) + game_state->settings.animation_margin = nextScore; + break; + case 3: + nextScore = game_state->settings.animation_duration; + if (game_state->selectDirection == DirectionLeft) + nextScore -= 100; + else + nextScore += 100; + if (nextScore >= (int) game_state->settings.animation_margin && nextScore < 2000) + game_state->settings.animation_duration = nextScore; + break; + case 4: + 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 5: + 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); - if (!game_state->started && game_state->state == GameStatePlay) { - game_state->started = true; - drawDealerCard(game_state); - queue(game_state, drawPlayerCard, NULL, draw_card_animation); - queue(game_state, drawDealerCard, NULL, draw_card_animation); - queue(game_state, drawPlayerCard, NULL, draw_card_animation); - } - - if (!run_queue(game_state)) { - if (game_state->state == GameStatePlay) { - player_tick(game_state); - } else if (game_state->state == GameStateDealer) { - dealer_tick(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; + queue(game_state, drawDealerCard, NULL, dealer_back_card_animation, + game_state->settings.animation_duration, game_state->settings.animation_margin); + queue(game_state, drawPlayerCard, NULL, player_card_animation, game_state->settings.animation_duration, + game_state->settings.animation_margin); + queue(game_state, drawDealerCard, NULL, dealer_card_animation, game_state->settings.animation_duration, + game_state->settings.animation_margin); + queue(game_state, drawPlayerCard, NULL, player_card_animation, game_state->settings.animation_duration, + game_state->settings.animation_margin); + } + 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; @@ -250,18 +414,20 @@ void start_round(GameState *game_state) { game_state->animationStart = 0; shuffleDeck(&(game_state->deck)); game_state->doubled = false; - game_state->bet = ROUND_PRICE; - if (game_state->player_score < ROUND_PRICE) { + 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 -= ROUND_PRICE; + game_state->player_score -= game_state->settings.round_price; } game_state->state = GameStatePlay; } void init(GameState *game_state) { + game_state->settings = load_settings(); game_state->last_tick = 0; - game_state->player_score = STARTING_MONEY; + game_state->processing = true; + game_state->player_score = game_state->settings.starting_money; generateDeck(&(game_state->deck)); start_round(game_state); } @@ -312,13 +478,11 @@ int32_t blackjack_app(void *p) { for (bool processing = true; processing;) { FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); GameState *game_state = (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: game_state->selectDirection = DirectionUp; break; @@ -332,20 +496,20 @@ int32_t blackjack_app(void *p) { game_state->selectDirection = DirectionLeft; break; case InputKeyBack: - processing = false; + if (game_state->state == GameStateSettings) { + game_state->state = GameStateStart; + save_settings(game_state->settings); + } else + processing = false; break; - case InputKeyOk: - if (game_state->state == GameStateGameOver || game_state->state == GameStateStart) { - init(game_state); - } else { - game_state->selectDirection = Select; - } + game_state->selectDirection = Select; break; } } } else if (event.type == EventTypeTick) { tick(game_state); + processing = game_state->processing; } } else { FURI_LOG_D(APP_NAME, "osMessageQueue: event timeout"); diff --git a/applications/plugins/blackjack/card.c b/applications/plugins/blackjack/card.c index 2f43a33f7..ac138a1d0 100644 --- a/applications/plugins/blackjack/card.c +++ b/applications/plugins/blackjack/card.c @@ -1,5 +1,6 @@ -#include "card.h" #include +#include "card.h" +#include "util.h" //region CardDesign bool pips[4][49] = @@ -57,7 +58,9 @@ uint8_t characters[13] = 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A' }; - +uint8_t edge_cards[3] = { + 0, 8, 15 +}; //region Player card positions uint8_t playerCardPositions[22][4] = { //first row @@ -102,38 +105,52 @@ void drawPlayerDeck(const Card cards[21], uint8_t count, Canvas *const canvas) { } } -void drawCardAt(uint8_t pos_x, uint8_t pos_y, uint8_t pip, uint8_t character, CardState state, Canvas *const canvas) { +bool is_at_edge(uint8_t index) { + for (uint8_t i = 0; i < 3; i++) + if (edge_cards[i] == index) return true; + + return false; +} + +Vector card_pos_at_index(uint8_t index) { + return (Vector) { + playerCardPositions[index][0], + playerCardPositions[index][1] + }; +} + +void drawCardAt(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, CardState state, Canvas *const canvas) { if (state == Normal) { canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, pos_x, pos_y, CARD_WIDHT, CARD_HEIGHT); + canvas_draw_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT); canvas_set_color(canvas, ColorBlack); - canvas_draw_frame(canvas, pos_x, pos_y, CARD_WIDHT, CARD_HEIGHT); + canvas_draw_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT); } else { if (state == BottomCut || state == BottomAndRightCut) canvas_draw_line(canvas, pos_x, pos_y, pos_x, pos_y + CARD_HALF_HEIGHT - 1); //half height line if (state == BottomCut) { - canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_WIDHT - 1, pos_y); //full width line - canvas_draw_line(canvas, pos_x + CARD_WIDHT - 1, pos_y, pos_x + CARD_WIDHT - 1, + canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_WIDTH - 1, pos_y); //full width line + canvas_draw_line(canvas, pos_x + CARD_WIDTH - 1, pos_y, pos_x + CARD_WIDTH - 1, pos_y + CARD_HALF_HEIGHT - 1); //half height line } if (state == BottomAndRightCut) { - canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_HALF_WIDHT - 1, pos_y); //half width + canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_HALF_WIDTH - 1, pos_y); //half width } if (state == RightCut) { - canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_HALF_WIDHT - 1, pos_y); //half width + canvas_draw_line(canvas, pos_x, pos_y, pos_x + CARD_HALF_WIDTH - 1, pos_y); //half width canvas_draw_line(canvas, pos_x, pos_y, pos_x, pos_y + CARD_HEIGHT - 1); //full height line - canvas_draw_line(canvas, pos_x, pos_y + CARD_HEIGHT - 1, pos_x + CARD_HALF_WIDHT - 1, + canvas_draw_line(canvas, pos_x, pos_y + CARD_HEIGHT - 1, pos_x + CARD_HALF_WIDTH - 1, pos_y + CARD_HEIGHT - 1); //full height line } } uint8_t left = pos_x + CORNER_MARGIN; - uint8_t right = (pos_x + CARD_WIDHT - CORNER_MARGIN - 7); + uint8_t right = (pos_x + CARD_WIDTH - CORNER_MARGIN - 7); uint8_t top = pos_y + CORNER_MARGIN; uint8_t bottom = (pos_y + CARD_HEIGHT - CORNER_MARGIN - 7); @@ -171,13 +188,13 @@ void drawCardAt(uint8_t pos_x, uint8_t pos_y, uint8_t pip, uint8_t character, Ca //canvas_draw_str(canvas, left, top, drawChar ); } -void drawCardBackAt(uint8_t pos_x, uint8_t pos_y, Canvas *const canvas) { +void drawCardBackAt(int8_t pos_x, int8_t pos_y, Canvas *const canvas) { canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, pos_x, pos_y, CARD_WIDHT, CARD_HEIGHT); + canvas_draw_box(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT); canvas_set_color(canvas, ColorBlack); - canvas_draw_frame(canvas, pos_x, pos_y, CARD_WIDHT, CARD_HEIGHT); - for (uint8_t x = 0; x < CARD_WIDHT - 2; x++) { + canvas_draw_frame(canvas, pos_x, pos_y, CARD_WIDTH, CARD_HEIGHT); + for (uint8_t x = 0; x < CARD_WIDTH - 2; x++) { for (uint8_t y = 0; y < CARD_HEIGHT - 2; y++) { uint8_t _x = x; uint8_t _y = y * 2; @@ -236,4 +253,24 @@ uint8_t handCount(const Card cards[21], uint8_t count) { } 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) { + drawCardAt(currentPos.x, currentPos.y, animatingCard.pip, + animatingCard.character, Normal, canvas); + } else { + if (t < 0.5) + drawCardBackAt(currentPos.x, currentPos.y, canvas); + else + drawCardAt(currentPos.x, currentPos.y, animatingCard.pip, + animatingCard.character, Normal, canvas); + } } \ No newline at end of file diff --git a/applications/plugins/blackjack/card.h b/applications/plugins/blackjack/card.h index aa99784bb..7f5e7d6e8 100644 --- a/applications/plugins/blackjack/card.h +++ b/applications/plugins/blackjack/card.h @@ -6,10 +6,11 @@ #define DECK_COUNT 6 #define CARD_HEIGHT 24 #define CARD_HALF_HEIGHT CARD_HEIGHT/2 -#define CARD_WIDHT 18 -#define CARD_HALF_WIDHT CARD_WIDHT/2 +#define CARD_WIDTH 18 +#define CARD_HALF_WIDTH CARD_WIDTH/2 #define CORNER_MARGIN 3 #define LEGEND_SIZE 10 +typedef struct Vector Vector; typedef enum { Normal, BottomCut, RightCut, BottomAndRightCut, TopCut, LeftCut, TopAndLeftCut @@ -27,14 +28,18 @@ typedef struct { void drawPlayerDeck(const Card cards[21], uint8_t count, Canvas *const canvas); -void drawCardAt(uint8_t pos_x, uint8_t pos_y, uint8_t pip, uint8_t character, CardState state, Canvas *const canvas); +void drawCardAt(int8_t pos_x, int8_t pos_y, uint8_t pip, uint8_t character, CardState state, Canvas *const canvas); -void drawCardBackAt(uint8_t pos_x, uint8_t pos_y, Canvas *const canvas); +void drawCardBackAt(int8_t pos_x, int8_t pos_y, Canvas *const canvas); void generateDeck(Deck *deck_ptr); void shuffleDeck(Deck *deck_ptr); +void draw_card_animation(Card animatingCard, Vector from, Vector control, Vector to, float t, bool extra_margin, Canvas *const canvas); + +Vector card_pos_at_index(uint8_t index); +bool is_at_edge(uint8_t index); uint8_t handCount(const Card cards[21], uint8_t count); #endif \ No newline at end of file diff --git a/applications/plugins/blackjack/defines.h b/applications/plugins/blackjack/defines.h index 36e547eb3..c0f8b2ee4 100644 --- a/applications/plugins/blackjack/defines.h +++ b/applications/plugins/blackjack/defines.h @@ -3,17 +3,35 @@ #include #include #include +#include +#include #include "card.h" -#define ANIMATION_TIME furi_ms_to_ticks(1500) -#define ANIMATION_END_MARGIN furi_ms_to_ticks(200) -#define ROUND_PRICE 10 +#define APP_NAME "Blackjack" +//#define ANIMATION_TIME furi_ms_to_ticks(1500) +//#define ANIMATION_END_MARGIN furi_ms_to_ticks(200) + +#define CONF_ANIMATION_DURATION "AnimationDuration" +#define CONF_ANIMATION_MARGIN "AnimationMargin" +#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_margin; + 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; @@ -23,6 +41,7 @@ typedef enum { GameStateGameOver, GameStateStart, GameStatePlay, + GameStateSettings, GameStateDealer, } PlayState; @@ -42,20 +61,18 @@ typedef struct { uint8_t dealer_card_count; Direction selectDirection; + Settings settings; uint32_t player_score; - uint8_t multiplier; uint32_t bet; - uint8_t player_time; bool doubled; bool animating; bool started; uint8_t selectedMenu; + bool processing; Deck deck; PlayState state; unsigned int last_tick; unsigned int animationStart; - bool dealer_animating; - unsigned int delay_tick; } GameState; diff --git a/applications/plugins/blackjack/ui.c b/applications/plugins/blackjack/ui.c index ab6462473..bfa1b94d7 100644 --- a/applications/plugins/blackjack/ui.c +++ b/applications/plugins/blackjack/ui.c @@ -1,8 +1,13 @@ +#include +#include + #include "ui.h" #include "card.h" -#include #include "util.h" +#define LINE_HEIGHT 16 +#define ITEM_PADDING 4 + const char MoneyMul[4] = { 'K', 'B', 'T', 'S' }; @@ -13,7 +18,8 @@ void draw_player_scene(Canvas *const canvas, const GameState *game_state) { if (max_card > 0) drawPlayerDeck((game_state->player_cards), max_card, canvas); - drawCardBackAt(13, 5, canvas); + if (game_state->dealer_card_count > 0) + drawCardBackAt(13, 5, canvas); max_card = game_state->dealer_card_count; if (max_card > 1) { @@ -27,23 +33,7 @@ void draw_dealer_scene(Canvas *const canvas, const GameState *game_state) { drawPlayerDeck((game_state->dealer_cards), max_card, canvas); } -void draw_card_animation(const GameState *game_state, Canvas *const canvas) { - float t = (float) (furi_get_tick() - game_state->animationStart) / (ANIMATION_TIME - ANIMATION_END_MARGIN); - t *= 2; - Card animatingCard = game_state->deck.cards[game_state->deck.index]; - if (t > 1) { - int cardY = round(lerp(-CARD_HEIGHT, 10, 1)); - drawCardAt(64 - CARD_HALF_WIDHT, cardY, animatingCard.pip, - animatingCard.character, Normal, canvas); - } else { - int cardY = round(lerp(-CARD_HEIGHT, 10, t)); - drawCardAt(64 - CARD_HALF_WIDHT, cardY, animatingCard.pip, - animatingCard.character, Normal, canvas); -// drawCardBackAt(64 - CARD_HALF_WIDHT, cardY, canvas); - } -} - -void popupFrame(Canvas *const 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); @@ -51,30 +41,11 @@ void popupFrame(Canvas *const canvas) { canvas_set_font(canvas, FontSecondary); } -void draw_message_scene(Canvas *const canvas, const GameState *game_state) { - switch (game_state->state) { - case GameStateStart: - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 64, 5, AlignCenter, AlignTop, "Blackjack"); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned(canvas, 64, 24, AlignCenter, AlignTop, "Made by Doofy"); - elements_multiline_text_aligned(canvas, 64, 38, AlignCenter, AlignTop, "Press center button\nto start"); - break; - case GameStateGameOver: - canvas_set_font(canvas, FontPrimary); - elements_multiline_text_aligned(canvas, 64, 5, AlignCenter, AlignTop, "Game Over"); - canvas_set_font(canvas, FontSecondary); - elements_multiline_text_aligned(canvas, 64, 24, AlignCenter, AlignTop, "Press center button\nto start"); - break; - default: - break; - } -} 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 < ROUND_PRICE)) continue; + 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); @@ -96,6 +67,15 @@ void draw_play_menu(Canvas *const canvas, const GameState *game_state) { } } +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); @@ -124,4 +104,85 @@ void draw_money(Canvas *const canvas, uint32_t score) { 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 / 7.0) + ITEM_PADDING * 2; + int scrollPos = 64 / (7.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_margin); + draw_menu(canvas, "Anim. margin", drawChar, + 2 * LINE_HEIGHT + startY, + gameState->settings.animation_margin > 0, + gameState->settings.animation_margin < gameState->settings.animation_duration, + gameState->selectedMenu == 2 + ); + snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.animation_duration); + draw_menu(canvas, "Anim. length", drawChar, + 3 * LINE_HEIGHT + startY, + gameState->settings.animation_duration > gameState->settings.animation_margin, + gameState->settings.animation_duration < 2000, + gameState->selectedMenu == 3 + ); + snprintf(drawChar, sizeof(drawChar), "%li", gameState->settings.message_duration); + draw_menu(canvas, "Popup time", drawChar, + 4 * LINE_HEIGHT + startY, + gameState->settings.message_duration > 0, + gameState->settings.message_duration < 2000, + gameState->selectedMenu == 4 + ); +// draw_menu(canvas, "Sound", gameState->settings.sound_effects ? "Yes" : "No", +// 5 * LINE_HEIGHT + startY, +// true, +// true, +// gameState->selectedMenu == 5 +// ); + } \ No newline at end of file diff --git a/applications/plugins/blackjack/ui.h b/applications/plugins/blackjack/ui.h index 7a9499f99..f65bfa228 100644 --- a/applications/plugins/blackjack/ui.h +++ b/applications/plugins/blackjack/ui.h @@ -1,20 +1,19 @@ #pragma once #include "defines.h" +#include "util.h" #include void draw_player_scene(Canvas *const canvas, const GameState *game_state); void draw_dealer_scene(Canvas *const canvas, const GameState *game_state); -void draw_message_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 draw_card_animation(const GameState *game_state, Canvas *const canvas); - -void popupFrame(Canvas *const canvas); \ No newline at end of file +void popup_frame(Canvas *const canvas); +void draw_screen(Canvas *const canvas, const bool* points); diff --git a/applications/plugins/blackjack/util.c b/applications/plugins/blackjack/util.c index bc260eb54..6f81d5149 100644 --- a/applications/plugins/blackjack/util.c +++ b/applications/plugins/blackjack/util.c @@ -1,61 +1,85 @@ #include "util.h" -static List *afterDelay; +const char *CONFIG_FILE_PATH = EXT_PATH(".blackjack.settings"); + +static QueueItem *afterDelay; 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 + ); +} + + void queue(GameState *game_state, void (*callback)(GameState *game_state), void (*start)(GameState *game_state), - void (*processing)(const GameState *gameState, Canvas *const canvas) + void (*processing)(const GameState *gameState, Canvas *const canvas,uint32_t duration,uint32_t margin), + uint32_t duration,uint32_t margin ) { if (afterDelay == NULL) { game_state->animationStart = game_state->last_tick; - afterDelay = malloc(sizeof(List)); + afterDelay = malloc(sizeof(QueueItem)); afterDelay->callback = callback; afterDelay->processing = processing; afterDelay->start = start; + afterDelay->duration = duration; + afterDelay->margin = margin; afterDelay->next = NULL; } else { - List *next = afterDelay; - while (next->next != NULL) { next = (List *) (next->next); } - next->next = malloc(sizeof(List)); - ((List *) next->next)->callback = callback; - ((List *) next->next)->processing = processing; - ((List *) next->next)->start = start; - ((List *) next->next)->next = NULL; + QueueItem *next = afterDelay; + while (next->next != NULL) { next = (QueueItem *) (next->next); } + next->next = malloc(sizeof(QueueItem)); + ((QueueItem *) next->next)->callback = callback; + ((QueueItem *) next->next)->processing = processing; + ((QueueItem *) next->next)->start = start; + ((QueueItem *) next->next)->duration = duration; + ((QueueItem *) next->next)->margin = margin; + ((QueueItem *) next->next)->next = NULL; } } void queue_clear() { while (afterDelay != NULL) { - List *f = afterDelay; + QueueItem *f = afterDelay; free(f); afterDelay = f->next; } } void dequeue(GameState *game_state) { - ((List *) afterDelay)->callback(game_state); - List *f = afterDelay; - afterDelay = (List *) afterDelay->next; + ((QueueItem *) afterDelay)->callback(game_state); + QueueItem *f = afterDelay; + afterDelay = (QueueItem *) afterDelay->next; free(f); if (afterDelay != NULL && afterDelay->start != NULL)afterDelay->start(game_state); game_state->animationStart = game_state->last_tick; } void animateQueue(const GameState *game_state, Canvas *const canvas) { - if (afterDelay != NULL && ((List *) afterDelay)->processing != NULL) { - ((List *) afterDelay)->processing(game_state, canvas); + if (afterDelay != NULL && ((QueueItem *) afterDelay)->processing != NULL) { + ((QueueItem *) afterDelay)->processing(game_state, canvas, afterDelay->duration, afterDelay->margin); } } bool run_queue(GameState *game_state) { if (afterDelay != NULL) { game_state->animating = true; - if ((game_state->last_tick - game_state->animationStart) > ANIMATION_TIME) { + if ((game_state->last_tick - game_state->animationStart) > afterDelay->duration) { dequeue(game_state); } return true; @@ -63,3 +87,132 @@ bool run_queue(GameState *game_state) { game_state->animating = false; return false; } + +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_ANIMATION_MARGIN, settings.animation_margin); + flipper_format_update_uint32(file, CONF_ANIMATION_MARGIN, &(settings.animation_margin), 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, + "Card animation margin in ms (how long the card will stay besides the played hand)"); + flipper_format_write_uint32(file, CONF_ANIMATION_MARGIN, &(settings->animation_margin), 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_margin = 200; + settings.animation_duration = 1500; + 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_ANIMATION_MARGIN); + if (flipper_format_read_uint32(file, CONF_ANIMATION_MARGIN, &value, 1)) { + settings.animation_margin = value; + FURI_LOG_D(APP_NAME, "Loaded %s: %ld", CONF_ANIMATION_MARGIN, 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; +} \ No newline at end of file diff --git a/applications/plugins/blackjack/util.h b/applications/plugins/blackjack/util.h index 9bd966740..7368135fb 100644 --- a/applications/plugins/blackjack/util.h +++ b/applications/plugins/blackjack/util.h @@ -1,18 +1,34 @@ #pragma once #include "defines.h" +#define CONFIG_FILE_HEADER "Blackjack config file" +#define CONFIG_FILE_VERSION 1 typedef struct{ void (*callback)(GameState *game_state); - void (*processing)(const GameState *game_state, Canvas *const canvas); + void (*processing)(const GameState *game_state, Canvas *const canvas,uint32_t duration,uint32_t margin); void (*start)(GameState *game_state); void *next; -} List; + uint32_t duration; + uint32_t margin; +} QueueItem; + +struct Vector{ + float x; + float y; +}; float lerp(float v0, float v1, float t); void queue(GameState *game_state, void (*callback)(GameState *game_state), void (*start)(GameState *game_state), - void (*processing)(const GameState *gameState, Canvas *const canvas)); + void (*processing)(const GameState *gameState, Canvas *const canvas, uint32_t duration,uint32_t margin), + uint32_t duration,uint32_t margin + ); bool run_queue(GameState *gameState); void animateQueue(const GameState *gameState, Canvas *const canvas); -void queue_clear(); \ No newline at end of file +void queue_clear(); +Vector lerp_2d(Vector start, Vector end, float t); +Vector quadratic_2d(Vector start, Vector control, Vector end, float t); + +void save_settings(Settings settings); +Settings load_settings(); \ No newline at end of file