#include #include #include #include #include #include "util.h" #include "defines.h" #include "card.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, 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); 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); 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); } //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(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++; } //endregion //region queue callbacks void to_lose_state( const GameState* game_state, Canvas* const canvas, uint32_t duration, uint32_t margin) { UNUSED(game_state); 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, uint32_t duration, uint32_t margin) { UNUSED(game_state); 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, uint32_t duration, uint32_t margin) { UNUSED(game_state); 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, uint32_t duration, uint32_t margin) { UNUSED(game_state); 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, uint32_t duration, uint32_t margin) { UNUSED(game_state); 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, uint32_t duration, uint32_t margin) { UNUSED(game_state); UNUSED(margin); if(duration == 0) return; popup_frame(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, game_state->settings.message_duration, 0); } 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 >= 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, 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, 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, game_state->settings.message_duration, 0); } else if(score > 21) { queue(game_state, lose, NULL, to_bust_state, game_state->settings.message_duration, 0); } else { 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) { game_state->selectedMenu++; } if(game_state->selectDirection == Select) { //double if(!game_state->doubled && game_state->selectedMenu == 0 && 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; game_state->selectedMenu = 1; 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, game_state->settings.message_duration, 0); else 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, 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, 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, game_state->settings.message_duration, 0); else if(dealer_score > player_score) 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, game_state->settings.message_duration, 0); } else { 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); 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; } 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 = 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) { game_state->settings = load_settings(); game_state->last_tick = 0; game_state->processing = true; game_state->player_score = game_state->settings.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: if(game_state->state == GameStateSettings) { game_state->state = GameStateStart; save_settings(game_state->settings); } else processing = false; break; case InputKeyOk: game_state->selectDirection = Select; break; } } } else if(event.type == EventTypeTick) { tick(game_state); processing = game_state->processing; } } 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; }