mirror of
https://github.com/DarkFlippers/unleashed-firmware
synced 2024-12-22 18:53:18 +00:00
28eb4d1060
Not full refactoring, only small issues is fixed and moved all plugins to furi mutex instead of valuemutex Many small issues was found and fixed due mutex upgrade OFW removed 60 lines of code and it was painful
633 lines
No EOL
20 KiB
C
633 lines
No EOL
20 KiB
C
|
|
#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) {
|
|
furi_assert(ctx);
|
|
const GameState* game_state = ctx;
|
|
furi_mutex_acquire(game_state->mutex, FuriWaitForever);
|
|
|
|
canvas_set_color(canvas, ColorBlack);
|
|
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
|
|
|
if(game_state->state == GameStateStart) {
|
|
canvas_draw_icon(canvas, 0, 0, &I_blackjack);
|
|
}
|
|
if(game_state->state == GameStateGameOver) {
|
|
canvas_draw_icon(canvas, 0, 0, &I_endscreen);
|
|
}
|
|
|
|
if(game_state->state == GameStatePlay || game_state->state == GameStateDealer) {
|
|
if(game_state->state == GameStatePlay)
|
|
draw_player_scene(canvas, game_state);
|
|
else
|
|
draw_dealer_scene(canvas, game_state);
|
|
render_queue(&(game_state->queue_state), game_state, canvas);
|
|
draw_ui(canvas, game_state);
|
|
} else if(game_state->state == GameStateSettings) {
|
|
settings_page(canvas, game_state);
|
|
}
|
|
|
|
furi_mutex_release(game_state->mutex);
|
|
}
|
|
|
|
//region card draw
|
|
Card draw_card(GameState* game_state) {
|
|
Card c = game_state->deck.cards[game_state->deck.index];
|
|
game_state->deck.index++;
|
|
return c;
|
|
}
|
|
|
|
void drawPlayerCard(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
Card c = draw_card(game_state);
|
|
game_state->player_cards[game_state->player_card_count] = c;
|
|
game_state->player_card_count++;
|
|
if(game_state->player_score < game_state->settings.round_price || game_state->doubled) {
|
|
set_menu_state(game_state->menu, 0, false);
|
|
}
|
|
}
|
|
|
|
void drawDealerCard(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
Card c = draw_card(game_state);
|
|
game_state->dealer_cards[game_state->dealer_card_count] = c;
|
|
game_state->dealer_card_count++;
|
|
}
|
|
//endregion
|
|
|
|
//region queue callbacks
|
|
void to_lose_state(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
if(game_state->settings.message_duration == 0) return;
|
|
popup_frame(canvas);
|
|
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You lost");
|
|
}
|
|
|
|
void to_bust_state(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
if(game_state->settings.message_duration == 0) return;
|
|
popup_frame(canvas);
|
|
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Busted!");
|
|
}
|
|
|
|
void to_draw_state(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
if(game_state->settings.message_duration == 0) return;
|
|
popup_frame(canvas);
|
|
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Draw");
|
|
}
|
|
|
|
void to_dealer_turn(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
if(game_state->settings.message_duration == 0) return;
|
|
popup_frame(canvas);
|
|
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Dealers turn");
|
|
}
|
|
|
|
void to_win_state(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
if(game_state->settings.message_duration == 0) return;
|
|
popup_frame(canvas);
|
|
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "You win");
|
|
}
|
|
|
|
void to_start(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
if(game_state->settings.message_duration == 0) return;
|
|
popup_frame(canvas);
|
|
elements_multiline_text_aligned(canvas, 64, 22, AlignCenter, AlignCenter, "Round started");
|
|
}
|
|
|
|
void before_start(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
game_state->dealer_card_count = 0;
|
|
game_state->player_card_count = 0;
|
|
}
|
|
|
|
void start(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
start_round(game_state);
|
|
}
|
|
|
|
void draw(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
game_state->player_score += game_state->bet;
|
|
game_state->bet = 0;
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
start,
|
|
before_start,
|
|
to_start,
|
|
game_state->settings.message_duration);
|
|
}
|
|
|
|
void game_over(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
game_state->state = GameStateGameOver;
|
|
}
|
|
|
|
void lose(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
game_state->state = GameStatePlay;
|
|
game_state->bet = 0;
|
|
if(game_state->player_score >= game_state->settings.round_price) {
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
start,
|
|
before_start,
|
|
to_start,
|
|
game_state->settings.message_duration);
|
|
} else {
|
|
enqueue(&(game_state->queue_state), game_state, game_over, NULL, NULL, 0);
|
|
}
|
|
}
|
|
|
|
void win(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
game_state->state = GameStatePlay;
|
|
game_state->player_score += game_state->bet * 2;
|
|
game_state->bet = 0;
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
start,
|
|
before_start,
|
|
to_start,
|
|
game_state->settings.message_duration);
|
|
}
|
|
|
|
void dealerTurn(void* ctx) {
|
|
GameState* game_state = ctx;
|
|
game_state->state = GameStateDealer;
|
|
}
|
|
|
|
float animationTime(const GameState* game_state) {
|
|
return (float)(furi_get_tick() - game_state->queue_state.start) /
|
|
(float)(game_state->settings.animation_duration);
|
|
}
|
|
|
|
void dealer_card_animation(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
float t = animationTime(game_state);
|
|
|
|
Card animatingCard = game_state->deck.cards[game_state->deck.index];
|
|
if(game_state->dealer_card_count > 1) {
|
|
Vector end = card_pos_at_index(game_state->dealer_card_count);
|
|
draw_card_animation(animatingCard, (Vector){0, 64}, (Vector){0, 32}, end, t, true, canvas);
|
|
} else {
|
|
draw_card_animation(
|
|
animatingCard,
|
|
(Vector){32, -CARD_HEIGHT},
|
|
(Vector){64, 32},
|
|
(Vector){2, 2},
|
|
t,
|
|
false,
|
|
canvas);
|
|
}
|
|
}
|
|
|
|
void dealer_back_card_animation(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
float t = animationTime(game_state);
|
|
|
|
Vector currentPos =
|
|
quadratic_2d((Vector){32, -CARD_HEIGHT}, (Vector){64, 32}, (Vector){13, 5}, t);
|
|
draw_card_back_at(currentPos.x, currentPos.y, canvas);
|
|
}
|
|
|
|
void player_card_animation(const void* ctx, Canvas* const canvas) {
|
|
const GameState* game_state = ctx;
|
|
float t = animationTime(game_state);
|
|
|
|
Card animatingCard = game_state->deck.cards[game_state->deck.index];
|
|
Vector end = card_pos_at_index(game_state->player_card_count);
|
|
|
|
draw_card_animation(
|
|
animatingCard, (Vector){32, -CARD_HEIGHT}, (Vector){0, 32}, end, t, true, canvas);
|
|
}
|
|
//endregion
|
|
|
|
void player_tick(GameState* game_state) {
|
|
uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count);
|
|
if((game_state->doubled && score <= 21) || score == 21) {
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
dealerTurn,
|
|
NULL,
|
|
to_dealer_turn,
|
|
game_state->settings.message_duration);
|
|
} else if(score > 21) {
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
lose,
|
|
NULL,
|
|
to_bust_state,
|
|
game_state->settings.message_duration);
|
|
} else {
|
|
if(game_state->selectDirection == DirectionUp ||
|
|
game_state->selectDirection == DirectionDown) {
|
|
move_menu(game_state->menu, game_state->selectDirection == DirectionUp ? -1 : 1);
|
|
}
|
|
|
|
if(game_state->selectDirection == Select) {
|
|
activate_menu(game_state->menu, game_state);
|
|
}
|
|
}
|
|
}
|
|
|
|
void dealer_tick(GameState* game_state) {
|
|
uint8_t dealer_score = hand_count(game_state->dealer_cards, game_state->dealer_card_count);
|
|
uint8_t player_score = hand_count(game_state->player_cards, game_state->player_card_count);
|
|
|
|
if(dealer_score >= DEALER_MAX) {
|
|
if(dealer_score > 21 || dealer_score < player_score) {
|
|
DOLPHIN_DEED(DolphinDeedPluginGameWin);
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
win,
|
|
NULL,
|
|
to_win_state,
|
|
game_state->settings.message_duration);
|
|
} else if(dealer_score > player_score) {
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
lose,
|
|
NULL,
|
|
to_lose_state,
|
|
game_state->settings.message_duration);
|
|
} else if(dealer_score == player_score) {
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
draw,
|
|
NULL,
|
|
to_draw_state,
|
|
game_state->settings.message_duration);
|
|
}
|
|
} else {
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
drawDealerCard,
|
|
NULL,
|
|
dealer_card_animation,
|
|
game_state->settings.animation_duration);
|
|
}
|
|
}
|
|
|
|
void settings_tick(GameState* game_state) {
|
|
if(game_state->selectDirection == DirectionDown && game_state->selectedMenu < 4) {
|
|
game_state->selectedMenu++;
|
|
}
|
|
if(game_state->selectDirection == DirectionUp && game_state->selectedMenu > 0) {
|
|
game_state->selectedMenu--;
|
|
}
|
|
|
|
if(game_state->selectDirection == DirectionLeft ||
|
|
game_state->selectDirection == DirectionRight) {
|
|
int nextScore = 0;
|
|
switch(game_state->selectedMenu) {
|
|
case 0:
|
|
nextScore = game_state->settings.starting_money;
|
|
if(game_state->selectDirection == DirectionLeft)
|
|
nextScore -= 10;
|
|
else
|
|
nextScore += 10;
|
|
if(nextScore >= (int)game_state->settings.round_price && nextScore < 400)
|
|
game_state->settings.starting_money = nextScore;
|
|
break;
|
|
case 1:
|
|
nextScore = game_state->settings.round_price;
|
|
if(game_state->selectDirection == DirectionLeft)
|
|
nextScore -= 10;
|
|
else
|
|
nextScore += 10;
|
|
if(nextScore >= 5 && nextScore <= (int)game_state->settings.starting_money)
|
|
game_state->settings.round_price = nextScore;
|
|
break;
|
|
case 2:
|
|
nextScore = game_state->settings.animation_duration;
|
|
if(game_state->selectDirection == DirectionLeft)
|
|
nextScore -= 100;
|
|
else
|
|
nextScore += 100;
|
|
if(nextScore >= 0 && nextScore < 2000)
|
|
game_state->settings.animation_duration = nextScore;
|
|
break;
|
|
case 3:
|
|
nextScore = game_state->settings.message_duration;
|
|
if(game_state->selectDirection == DirectionLeft)
|
|
nextScore -= 100;
|
|
else
|
|
nextScore += 100;
|
|
if(nextScore >= 0 && nextScore < 2000)
|
|
game_state->settings.message_duration = nextScore;
|
|
break;
|
|
case 4:
|
|
game_state->settings.sound_effects = !game_state->settings.sound_effects;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void tick(GameState* game_state) {
|
|
game_state->last_tick = furi_get_tick();
|
|
bool queue_ran = run_queue(&(game_state->queue_state), game_state);
|
|
|
|
switch(game_state->state) {
|
|
case GameStateGameOver:
|
|
case GameStateStart:
|
|
if(game_state->selectDirection == Select)
|
|
init(game_state);
|
|
else if(game_state->selectDirection == DirectionRight) {
|
|
game_state->selectedMenu = 0;
|
|
game_state->state = GameStateSettings;
|
|
}
|
|
break;
|
|
case GameStatePlay:
|
|
if(!game_state->started) {
|
|
game_state->selectedMenu = 0;
|
|
game_state->started = true;
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
drawDealerCard,
|
|
NULL,
|
|
dealer_back_card_animation,
|
|
game_state->settings.animation_duration);
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
drawPlayerCard,
|
|
NULL,
|
|
player_card_animation,
|
|
game_state->settings.animation_duration);
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
drawDealerCard,
|
|
NULL,
|
|
dealer_card_animation,
|
|
game_state->settings.animation_duration);
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
drawPlayerCard,
|
|
NULL,
|
|
player_card_animation,
|
|
game_state->settings.animation_duration);
|
|
}
|
|
if(!queue_ran) player_tick(game_state);
|
|
break;
|
|
case GameStateDealer:
|
|
if(!queue_ran) dealer_tick(game_state);
|
|
break;
|
|
case GameStateSettings:
|
|
settings_tick(game_state);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
game_state->selectDirection = None;
|
|
}
|
|
|
|
void start_round(GameState* game_state) {
|
|
game_state->menu->current_menu = 1;
|
|
game_state->player_card_count = 0;
|
|
game_state->dealer_card_count = 0;
|
|
set_menu_state(game_state->menu, 0, true);
|
|
game_state->menu->enabled = true;
|
|
game_state->started = false;
|
|
game_state->doubled = false;
|
|
game_state->queue_state.running = true;
|
|
shuffle_deck(&(game_state->deck));
|
|
game_state->doubled = false;
|
|
game_state->bet = game_state->settings.round_price;
|
|
if(game_state->player_score < game_state->settings.round_price) {
|
|
game_state->state = GameStateGameOver;
|
|
} else {
|
|
game_state->player_score -= game_state->settings.round_price;
|
|
}
|
|
game_state->state = GameStatePlay;
|
|
}
|
|
|
|
void init(GameState* game_state) {
|
|
set_menu_state(game_state->menu, 0, true);
|
|
game_state->menu->enabled = true;
|
|
game_state->menu->current_menu = 1;
|
|
game_state->settings = load_settings();
|
|
game_state->last_tick = 0;
|
|
game_state->processing = true;
|
|
game_state->selectedMenu = 0;
|
|
game_state->player_score = game_state->settings.starting_money;
|
|
generate_deck(&(game_state->deck), 6);
|
|
start_round(game_state);
|
|
}
|
|
|
|
static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
|
|
furi_assert(event_queue);
|
|
AppEvent event = {.type = EventTypeKey, .input = *input_event};
|
|
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
|
}
|
|
|
|
static void update_timer_callback(FuriMessageQueue* event_queue) {
|
|
furi_assert(event_queue);
|
|
AppEvent event = {.type = EventTypeTick};
|
|
furi_message_queue_put(event_queue, &event, 0);
|
|
}
|
|
|
|
void doubleAction(void* state) {
|
|
GameState* game_state = state;
|
|
if(!game_state->doubled && game_state->player_score >= game_state->settings.round_price) {
|
|
game_state->player_score -= game_state->settings.round_price;
|
|
game_state->bet += game_state->settings.round_price;
|
|
game_state->doubled = true;
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
drawPlayerCard,
|
|
NULL,
|
|
player_card_animation,
|
|
game_state->settings.animation_duration);
|
|
game_state->player_cards[game_state->player_card_count] =
|
|
game_state->deck.cards[game_state->deck.index];
|
|
uint8_t score = hand_count(game_state->player_cards, game_state->player_card_count + 1);
|
|
if(score > 21) {
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
lose,
|
|
NULL,
|
|
to_bust_state,
|
|
game_state->settings.message_duration);
|
|
} else {
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
dealerTurn,
|
|
NULL,
|
|
to_dealer_turn,
|
|
game_state->settings.message_duration);
|
|
}
|
|
set_menu_state(game_state->menu, 0, false);
|
|
}
|
|
}
|
|
|
|
void hitAction(void* state) {
|
|
GameState* game_state = state;
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
drawPlayerCard,
|
|
NULL,
|
|
player_card_animation,
|
|
game_state->settings.animation_duration);
|
|
}
|
|
void stayAction(void* state) {
|
|
GameState* game_state = state;
|
|
enqueue(
|
|
&(game_state->queue_state),
|
|
game_state,
|
|
dealerTurn,
|
|
NULL,
|
|
to_dealer_turn,
|
|
game_state->settings.message_duration);
|
|
}
|
|
|
|
int32_t blackjack_app(void* p) {
|
|
UNUSED(p);
|
|
|
|
int32_t return_code = 0;
|
|
|
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent));
|
|
|
|
GameState* game_state = malloc(sizeof(GameState));
|
|
game_state->menu = malloc(sizeof(Menu));
|
|
game_state->menu->menu_width = 40;
|
|
init(game_state);
|
|
add_menu(game_state->menu, "Double", doubleAction);
|
|
add_menu(game_state->menu, "Hit", hitAction);
|
|
add_menu(game_state->menu, "Stay", stayAction);
|
|
set_card_graphics(&I_card_graphics);
|
|
|
|
game_state->state = GameStateStart;
|
|
|
|
game_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
|
if(!game_state->mutex) {
|
|
FURI_LOG_E(APP_NAME, "cannot create mutex\r\n");
|
|
return_code = 255;
|
|
goto free_and_exit;
|
|
}
|
|
|
|
ViewPort* view_port = view_port_alloc();
|
|
view_port_draw_callback_set(view_port, render_callback, game_state);
|
|
view_port_input_callback_set(view_port, input_callback, event_queue);
|
|
|
|
FuriTimer* timer = furi_timer_alloc(update_timer_callback, FuriTimerTypePeriodic, event_queue);
|
|
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 25);
|
|
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
|
AppEvent event;
|
|
|
|
// Call dolphin deed on game start
|
|
DOLPHIN_DEED(DolphinDeedPluginGameStart);
|
|
|
|
for(bool processing = true; processing;) {
|
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
|
furi_mutex_acquire(game_state->mutex, FuriWaitForever);
|
|
if(event_status == FuriStatusOk) {
|
|
if(event.type == EventTypeKey) {
|
|
if(event.input.type == InputTypePress) {
|
|
switch(event.input.key) {
|
|
case InputKeyUp:
|
|
game_state->selectDirection = DirectionUp;
|
|
break;
|
|
case InputKeyDown:
|
|
game_state->selectDirection = DirectionDown;
|
|
break;
|
|
case InputKeyRight:
|
|
game_state->selectDirection = DirectionRight;
|
|
break;
|
|
case InputKeyLeft:
|
|
game_state->selectDirection = DirectionLeft;
|
|
break;
|
|
case InputKeyBack:
|
|
if(game_state->state == GameStateSettings) {
|
|
game_state->state = GameStateStart;
|
|
save_settings(game_state->settings);
|
|
} else
|
|
processing = false;
|
|
break;
|
|
case InputKeyOk:
|
|
game_state->selectDirection = Select;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else if(event.type == EventTypeTick) {
|
|
tick(game_state);
|
|
processing = game_state->processing;
|
|
}
|
|
}
|
|
view_port_update(view_port);
|
|
furi_mutex_release(game_state->mutex);
|
|
}
|
|
|
|
furi_timer_free(timer);
|
|
view_port_enabled_set(view_port, false);
|
|
gui_remove_view_port(gui, view_port);
|
|
furi_record_close(RECORD_GUI);
|
|
view_port_free(view_port);
|
|
furi_mutex_free(game_state->mutex);
|
|
|
|
free_and_exit:
|
|
free(game_state->deck.cards);
|
|
free_menu(game_state->menu);
|
|
queue_clear(&(game_state->queue_state));
|
|
free(game_state);
|
|
furi_message_queue_free(event_queue);
|
|
|
|
return return_code;
|
|
} |