From 607e46e757535a75489b4ca78acdd10cbb5f7fe0 Mon Sep 17 00:00:00 2001 From: Haseo Date: Thu, 13 Oct 2022 21:17:26 +0200 Subject: [PATCH] Add Blackjack --- .../plugins/blackjack/application.fam | 12 + applications/plugins/blackjack/blackjack.c | 372 ++++++++++++++++++ .../plugins/blackjack/blackjack_10px.png | Bin 0 -> 119 bytes applications/plugins/blackjack/card.c | 239 +++++++++++ applications/plugins/blackjack/card.h | 40 ++ applications/plugins/blackjack/defines.h | 61 +++ applications/plugins/blackjack/ui.c | 127 ++++++ applications/plugins/blackjack/ui.h | 20 + applications/plugins/blackjack/util.c | 65 +++ applications/plugins/blackjack/util.h | 18 + 10 files changed, 954 insertions(+) create mode 100644 applications/plugins/blackjack/application.fam create mode 100644 applications/plugins/blackjack/blackjack.c create mode 100644 applications/plugins/blackjack/blackjack_10px.png create mode 100644 applications/plugins/blackjack/card.c create mode 100644 applications/plugins/blackjack/card.h create mode 100644 applications/plugins/blackjack/defines.h create mode 100644 applications/plugins/blackjack/ui.c create mode 100644 applications/plugins/blackjack/ui.h create mode 100644 applications/plugins/blackjack/util.c create mode 100644 applications/plugins/blackjack/util.h diff --git a/applications/plugins/blackjack/application.fam b/applications/plugins/blackjack/application.fam new file mode 100644 index 000000000..798e240e3 --- /dev/null +++ b/applications/plugins/blackjack/application.fam @@ -0,0 +1,12 @@ +App( + appid="blackjack", + name="Blackjack", + apptype=FlipperAppType.EXTERNAL, + entry_point="blackjack_app", + cdefines=["APP_BLACKJACK"], + requires=["gui"], + stack_size=1 * 1024, + order=30, + fap_icon="blackjack_10px.png", + fap_category="Games", +) \ No newline at end of file diff --git a/applications/plugins/blackjack/blackjack.c b/applications/plugins/blackjack/blackjack.c new file mode 100644 index 000000000..798fa6e5a --- /dev/null +++ b/applications/plugins/blackjack/blackjack.c @@ -0,0 +1,372 @@ + +#include +#include +#include +#include + +#include "defines.h" +#include "card.h" +#include "util.h" +#include "ui.h" + +#define APP_NAME "Blackjack" +#define STARTING_MONEY 200 +#define DEALER_MAX 17 + + +void start_round(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, handCount(game_state->player_cards, game_state->player_card_count)); + + if (!game_state->animating && game_state->state == GameStatePlay) { + draw_play_menu(canvas, 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 && game_state->state != GameStateGameOver) { + animateQueue(game_state, canvas); + draw_ui(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; +} + +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++; +} + +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) { + UNUSED(game_state); + popupFrame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost"); +} + +void to_bust_state(const GameState *game_state, Canvas *const canvas) { + UNUSED(game_state); + popupFrame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!"); +} + +void to_draw_state(const GameState *game_state, Canvas *const canvas) { + UNUSED(game_state); + popupFrame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw"); +} + +void to_dealer_turn(const GameState *game_state, Canvas *const canvas) { + UNUSED(game_state); + popupFrame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn"); +} + +void to_win_state(const GameState *game_state, Canvas *const canvas) { + UNUSED(game_state); + popupFrame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win"); +} + +void to_start(const GameState *game_state, Canvas *const canvas) { + UNUSED(game_state); + popupFrame(canvas); + elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started"); +} + +void before_start(GameState *gameState) { + gameState->dealer_card_count = 0; + gameState->player_card_count = 0; +} + + +void start(GameState *game_state) { + start_round(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); +} + +void game_over(GameState *game_state) { + game_state->state = GameStateGameOver; +} + +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); + else + queue(game_state, game_over, NULL, NULL); +} + +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); +} + + +void dealerTurn(GameState *game_state) { + game_state->state = GameStateDealer; +} +//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); + } else if (score > 21) { + queue(game_state, lose, NULL, to_bust_state); + } else { + if (game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) { + game_state->selectedMenu--; + } + if (game_state->selectDirection == DirectionDown && game_state->selectedMenu < 2) { + game_state->selectedMenu++; + } + if (game_state->selectDirection == Select) { + //double + if (!game_state->doubled && game_state->selectedMenu == 0 && + game_state->player_score >= ROUND_PRICE) { + + game_state->player_score -= ROUND_PRICE; + game_state->bet += ROUND_PRICE; + game_state->doubled = true; + game_state->selectedMenu = 1; + queue(game_state, drawPlayerCard, NULL, draw_card_animation); + 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); + else + queue(game_state, dealerTurn, NULL, to_dealer_turn); + + } //hit + else if (game_state->selectedMenu == 1) { + queue(game_state, drawPlayerCard, NULL, draw_card_animation); + } //stay + else if (game_state->selectedMenu == 2) { + queue(game_state, dealerTurn, NULL, to_dealer_turn); + } + } + } +} + + +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); + else if (dealer_score > player_score) + queue(game_state, lose, NULL, to_lose_state); + else if (dealer_score == player_score) + queue(game_state, draw, NULL, to_draw_state); + } else { + queue(game_state, drawDealerCard, NULL, draw_card_animation); + } +} + +void tick(GameState *game_state) { + game_state->last_tick = furi_get_tick(); + + 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); + } + } + + game_state->selectDirection = None; + +} + +void start_round(GameState *game_state) { + game_state->player_card_count = 0; + game_state->dealer_card_count = 0; + game_state->selectedMenu = 0; + game_state->started = false; + game_state->doubled = false; + game_state->animating = true; + 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->state = GameStateGameOver; + } else { + game_state->player_score -= ROUND_PRICE; + } + game_state->state = GameStatePlay; +} + +void init(GameState *game_state) { + game_state->last_tick = 0; + game_state->player_score = STARTING_MONEY; + generateDeck(&(game_state->deck)); + 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); +} + +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)); + init(game_state); + 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 *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; + case InputKeyDown: + game_state->selectDirection = DirectionDown; + break; + case InputKeyRight: + game_state->selectDirection = DirectionRight; + break; + case InputKeyLeft: + game_state->selectDirection = DirectionLeft; + break; + case InputKeyBack: + processing = false; + break; + + case InputKeyOk: + if (game_state->state == GameStateGameOver || game_state->state == GameStateStart) { + init(game_state); + } else { + game_state->selectDirection = Select; + } + break; + } + } + } else if (event.type == EventTypeTick) { + tick(game_state); + } + } else { + FURI_LOG_D(APP_NAME, "osMessageQueue: event timeout"); + // event timeout + } + view_port_update(view_port); + release_mutex(&state_mutex, game_state); + } + + + 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: + queue_clear(); + free(game_state); + furi_message_queue_free(event_queue); + + return return_code; +} \ No newline at end of file diff --git a/applications/plugins/blackjack/blackjack_10px.png b/applications/plugins/blackjack/blackjack_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..7382d6358bde65d970575fb40995145d5d3e26d0 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&F%}28J29*~C-V}>VGHmHasB`Q zKad%E=yDy9lJ|6R4B?oWoZ!IP)59UwbM^#Bva!*jn+{A(9T(Wx892Nd)DK8X1_G5b Nc)I$ztaD0e0sxqvA7ual literal 0 HcmV?d00001 diff --git a/applications/plugins/blackjack/card.c b/applications/plugins/blackjack/card.c new file mode 100644 index 000000000..2f43a33f7 --- /dev/null +++ b/applications/plugins/blackjack/card.c @@ -0,0 +1,239 @@ +#include "card.h" +#include + +//region CardDesign +bool pips[4][49] = + { + { + //spades + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 0, 1, 1, + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 0 + }, + { + //hearts + 0, 1, 0, 0, 0, 1, 0, + 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 0, + }, + { + //diamonds + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 0, + 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 1, 0, 0, 0 + }, + { + //clubs + 0, 0, 1, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 0, 0, + 1, 1, 0, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 0, 1, 1, + 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 1, 1, 0, 0 + } + }; + +bool backDesign[4] = { + 0, 1, + 1, 0 +}; +//endregion + +uint8_t characters[13] = + { + 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K', 'A' + }; + + +//region Player card positions +uint8_t playerCardPositions[22][4] = { + //first row + {108, 38, 0, 0}, + {98, 38, 0, 1}, + {88, 38, 0, 1}, + {78, 38, 0, 1}, + {68, 38, 0, 1}, + {58, 38, 0, 1}, + {48, 38, 0, 1}, + {38, 38, 0, 1}, + //second row + {104, 26, 1, 0}, + {94, 26, 1, 1}, + {84, 26, 1, 1}, + {74, 26, 1, 1}, + {64, 26, 1, 1}, + {54, 26, 1, 1}, + {44, 26, 1, 1}, + //third row + {99, 14, 1, 0}, + {89, 14, 1, 1}, + {79, 14, 1, 1}, + {69, 14, 1, 1}, + {59, 14, 1, 1}, + {49, 14, 1, 1}, +}; +//endregion + + +void drawPlayerDeck(const Card cards[21], uint8_t count, Canvas *const canvas) { + for (uint8_t i = 0; i < count; i++) { + CardState state = Normal; + if (playerCardPositions[i][2] == 1 && playerCardPositions[i][3] == 1) + state = BottomAndRightCut; + else if (playerCardPositions[i][3] == 1) + state = RightCut; + else if (playerCardPositions[i][2] == 1) + state = BottomCut; + drawCardAt(playerCardPositions[i][0], playerCardPositions[i][1], cards[i].pip, cards[i].character, state, + canvas); + } +} + +void drawCardAt(uint8_t pos_x, uint8_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_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, pos_x, pos_y, CARD_WIDHT, 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, + 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 + } + + 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, 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, + 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 top = pos_y + CORNER_MARGIN; + uint8_t bottom = (pos_y + CARD_HEIGHT - CORNER_MARGIN - 7); + + for (uint8_t x = 0; x < 7; x++) { + for (uint8_t y = 0; y < 7; y++) { + if (pips[pip][x + y * 7]) { + if (state == Normal || state == BottomCut) + canvas_draw_dot(canvas, right + x + 1, top + y); + if (state == Normal || state == RightCut) + canvas_draw_dot(canvas, left + x - 1, bottom + y); + } + } + } + + canvas_set_font(canvas, FontSecondary); + char drawChar[3]; + if (character < 9) + snprintf(drawChar, sizeof(drawChar), "%i", character + 2); + else { + snprintf(drawChar, sizeof(drawChar), "%c", characters[character]); + } + + canvas_set_font_direction(canvas, CanvasDirectionLeftToRight); + canvas_draw_str_aligned(canvas, left + 2, top + 3, AlignCenter, AlignCenter, drawChar); + + canvas_set_font_direction(canvas, CanvasDirectionRightToLeft); + if (state == Normal) { //flipper crashes on non center aligned text when upside down + uint8_t margin = 9; + if (character == 8) //10 needs bigger margin + margin = 12; + canvas_draw_str_aligned(canvas, right + margin, bottom - 3, AlignCenter, AlignCenter, drawChar); + } + + canvas_set_font_direction(canvas, CanvasDirectionLeftToRight); + //canvas_draw_str(canvas, left, top, drawChar ); +} + +void drawCardBackAt(uint8_t pos_x, uint8_t pos_y, Canvas *const canvas) { + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, pos_x, pos_y, CARD_WIDHT, 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++) { + for (uint8_t y = 0; y < CARD_HEIGHT - 2; y++) { + uint8_t _x = x; + uint8_t _y = y * 2; + if (backDesign[(_x + _y) % 4]) { + canvas_draw_dot(canvas, pos_x + x + 1, pos_y + y + 1); + } + } + } +} + +void generateDeck(Deck *deck_ptr) { + uint16_t counter = 0; + 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 + }; + counter++; + } + } + } +} + +void shuffleDeck(Deck *deck_ptr) { + srand(DWT->CYCCNT); + deck_ptr->index = 0; + int max = 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 handCount(const Card cards[21], 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; +} \ No newline at end of file diff --git a/applications/plugins/blackjack/card.h b/applications/plugins/blackjack/card.h new file mode 100644 index 000000000..aa99784bb --- /dev/null +++ b/applications/plugins/blackjack/card.h @@ -0,0 +1,40 @@ +#ifndef _card_h +#define _card_h + +#include + +#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 CORNER_MARGIN 3 +#define LEGEND_SIZE 10 + +typedef enum { + Normal, BottomCut, RightCut, BottomAndRightCut, TopCut, LeftCut, TopAndLeftCut +} CardState; + +typedef struct { + uint8_t pip; + uint8_t character; +} Card; + +typedef struct { + Card cards[52 * DECK_COUNT]; + int index; +} Deck; + +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 drawCardBackAt(uint8_t pos_x, uint8_t pos_y, Canvas *const canvas); + +void generateDeck(Deck *deck_ptr); + +void shuffleDeck(Deck *deck_ptr); + +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 new file mode 100644 index 000000000..36e547eb3 --- /dev/null +++ b/applications/plugins/blackjack/defines.h @@ -0,0 +1,61 @@ +#pragma once + +#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 + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} AppEvent; + +typedef enum { + GameStateGameOver, + GameStateStart, + GameStatePlay, + GameStateDealer, +} PlayState; + +typedef enum { + DirectionUp, + DirectionRight, + DirectionDown, + DirectionLeft, + Select, + None +} Direction; + +typedef struct { + Card player_cards[21]; + Card dealer_cards[21]; + uint8_t player_card_count; + uint8_t dealer_card_count; + + Direction selectDirection; + + uint32_t player_score; + uint8_t multiplier; + uint32_t bet; + uint8_t player_time; + bool doubled; + bool animating; + bool started; + uint8_t selectedMenu; + 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 new file mode 100644 index 000000000..ab6462473 --- /dev/null +++ b/applications/plugins/blackjack/ui.c @@ -0,0 +1,127 @@ +#include "ui.h" +#include "card.h" +#include +#include "util.h" + +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) + drawPlayerDeck((game_state->player_cards), max_card, canvas); + + drawCardBackAt(13, 5, canvas); + + max_card = game_state->dealer_card_count; + if (max_card > 1) { + drawCardAt(2, 2, game_state->dealer_cards[1].pip, game_state->dealer_cards[1].character, Normal, + canvas); + } +} + +void draw_dealer_scene(Canvas *const canvas, const GameState *game_state) { + uint8_t max_card = game_state->dealer_card_count; + 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) { + 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_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; + 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_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[10]; + 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); +} \ No newline at end of file diff --git a/applications/plugins/blackjack/ui.h b/applications/plugins/blackjack/ui.h new file mode 100644 index 000000000..7a9499f99 --- /dev/null +++ b/applications/plugins/blackjack/ui.h @@ -0,0 +1,20 @@ +#pragma once + +#include "defines.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 draw_card_animation(const GameState *game_state, Canvas *const canvas); + +void popupFrame(Canvas *const canvas); \ No newline at end of file diff --git a/applications/plugins/blackjack/util.c b/applications/plugins/blackjack/util.c new file mode 100644 index 000000000..bc260eb54 --- /dev/null +++ b/applications/plugins/blackjack/util.c @@ -0,0 +1,65 @@ +#include "util.h" + +static List *afterDelay; + +float lerp(float v0, float v1, float t) { + return (1 - t) * v0 + t * v1; +} + +void queue(GameState *game_state, + void (*callback)(GameState *game_state), + void (*start)(GameState *game_state), + void (*processing)(const GameState *gameState, Canvas *const canvas) +) { + if (afterDelay == NULL) { + game_state->animationStart = game_state->last_tick; + afterDelay = malloc(sizeof(List)); + afterDelay->callback = callback; + afterDelay->processing = processing; + afterDelay->start = start; + 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; + } +} + +void queue_clear() { + while (afterDelay != NULL) { + List *f = afterDelay; + free(f); + afterDelay = f->next; + } +} + +void dequeue(GameState *game_state) { + ((List *) afterDelay)->callback(game_state); + List *f = afterDelay; + afterDelay = (List *) 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); + } +} + +bool run_queue(GameState *game_state) { + if (afterDelay != NULL) { + game_state->animating = true; + if ((game_state->last_tick - game_state->animationStart) > ANIMATION_TIME) { + dequeue(game_state); + } + return true; + } + game_state->animating = false; + return false; +} diff --git a/applications/plugins/blackjack/util.h b/applications/plugins/blackjack/util.h new file mode 100644 index 000000000..9bd966740 --- /dev/null +++ b/applications/plugins/blackjack/util.h @@ -0,0 +1,18 @@ +#pragma once +#include "defines.h" + +typedef struct{ + void (*callback)(GameState *game_state); + void (*processing)(const GameState *game_state, Canvas *const canvas); + void (*start)(GameState *game_state); + void *next; +} List; + +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)); +bool run_queue(GameState *gameState); +void animateQueue(const GameState *gameState, Canvas *const canvas); +void queue_clear(); \ No newline at end of file