#include #include #include #include #include "defines.h" #include "common/ui.h" #include "solitaire_icons.h" #include #include void init(GameState* game_state); const NotificationSequence sequence_fail = { &message_vibro_on, &message_note_c4, &message_delay_10, &message_vibro_off, &message_sound_off, &message_delay_10, &message_vibro_on, &message_note_a3, &message_delay_10, &message_vibro_off, &message_sound_off, NULL, }; int8_t columns[7][3] = { {1, 1, 25}, {19, 1, 25}, {37, 1, 25}, {55, 1, 25}, {73, 1, 25}, {91, 1, 25}, {109, 1, 25}, }; bool can_place_card(Card where, Card what) { FURI_LOG_D( APP_NAME, "TESTING pip %i, letter %i with pip %i, letter %i", where.pip, where.character, what.pip, what.character); bool a_black = where.pip == 0 || where.pip == 3; bool b_black = what.pip == 0 || what.pip == 3; if(a_black == b_black) return false; int8_t a_letter = (int8_t)where.character; int8_t b_letter = (int8_t)what.character; if(a_letter == 12) a_letter = -1; if(b_letter == 12) b_letter = -1; return (a_letter - 1) == b_letter; } static void draw_scene(Canvas* const canvas, const GameState* game_state) { int deckIndex = game_state->deck.index; if(game_state->dragging_deck) deckIndex--; if((game_state->deck.index < (game_state->deck.card_count - 1) || game_state->deck.index == -1) && game_state->deck.card_count > 0) { draw_card_back_at(columns[0][0], columns[0][1], canvas); if(game_state->selectRow == 0 && game_state->selectColumn == 0) { draw_rounded_box( canvas, columns[0][0] + 1, columns[0][1] + 1, CARD_WIDTH - 2, CARD_HEIGHT - 2, Inverse); } } else draw_card_space( columns[0][0], columns[0][1], game_state->selectRow == 0 && game_state->selectColumn == 0, canvas); //deck side if(deckIndex >= 0) { Card c = game_state->deck.cards[deckIndex]; draw_card_at_colored( columns[1][0], columns[1][1], c.pip, c.character, game_state->selectRow == 0 && game_state->selectColumn == 1, canvas); } else draw_card_space( columns[1][0], columns[1][1], game_state->selectRow == 0 && game_state->selectColumn == 1, canvas); for(uint8_t i = 0; i < 4; i++) { Card current = game_state->top_cards[i]; bool selected = game_state->selectRow == 0 && game_state->selectColumn == (i + 3); if(current.disabled) { draw_card_space(columns[i + 3][0], columns[i + 3][1], selected, canvas); } else { draw_card_at( columns[i + 3][0], columns[i + 3][1], current.pip, current.character, canvas); if(selected) { draw_rounded_box( canvas, columns[i + 3][0], columns[i + 3][1], CARD_WIDTH, CARD_HEIGHT, Inverse); } } } for(uint8_t i = 0; i < 7; i++) { bool selected = game_state->selectRow == 1 && game_state->selectColumn == i; int8_t index = (game_state->bottom_columns[i].index - 1 - game_state->selected_card); if(index < 0) index = 0; draw_hand_column( game_state->bottom_columns[i], columns[i][0], columns[i][2], selected ? index : -1, canvas); } int8_t pos[2] = { columns[game_state->selectColumn][0], columns[game_state->selectColumn][game_state->selectRow + 1]}; /* draw_icon_clip(canvas, &I_card_graphics, pos[0] + CARD_HALF_WIDTH, pos[1] + CARD_HALF_HEIGHT, 30, 5, 5, 5, Filled);*/ if(game_state->dragging_hand.index > 0) { draw_hand_column( game_state->dragging_hand, pos[0] + CARD_HALF_WIDTH + 3, pos[1] + CARD_HALF_HEIGHT + 3, -1, canvas); } } static void draw_animation(Canvas* const canvas, const GameState* game_state) { if(!game_state->animation.started) { draw_scene(canvas, game_state); } else { clone_buffer(game_state->animation.buffer, get_buffer(canvas)); draw_card_at( (int8_t)game_state->animation.x, (int8_t)game_state->animation.y, game_state->animation.card.pip, game_state->animation.card.character, canvas); } clone_buffer(get_buffer(canvas), game_state->animation.buffer); } static void render_callback(Canvas* const canvas, void* ctx) { furi_assert(ctx); const GameState* game_state = ctx; furi_mutex_acquire(game_state->mutex, FuriWaitForever); switch(game_state->state) { case GameStateAnimate: draw_animation(canvas, game_state); break; case GameStateStart: canvas_draw_icon(canvas, 0, 0, &I_solitaire_main); break; case GameStatePlay: draw_scene(canvas, game_state); break; default: break; } furi_mutex_release(game_state->mutex); } void remove_drag(GameState* game_state) { if(game_state->dragging_deck) { remove_from_deck(game_state->deck.index, &(game_state->deck)); game_state->dragging_deck = false; } else if(game_state->dragging_column < 7) { game_state->dragging_column = 8; } game_state->dragging_hand.index = 0; } bool handleInput(GameState* game_state) { Hand currentHand = game_state->bottom_columns[game_state->selectColumn]; switch(game_state->input) { case InputKeyUp: if(game_state->selectRow > 0) { int first = first_non_flipped_card(currentHand); first = currentHand.index - first; if((first - 1) > game_state->selected_card && game_state->dragging_hand.index == 0 && !game_state->longPress) { game_state->selected_card++; } else { game_state->selectRow--; game_state->selected_card = 0; } } break; case InputKeyDown: if(game_state->selectRow < 1) { game_state->selectRow++; game_state->selected_card = 0; } else { if(game_state->selected_card > 0) { if(game_state->longPress) game_state->selected_card = 0; else game_state->selected_card--; } } break; case InputKeyRight: if(game_state->selectColumn < 6) { game_state->selectColumn++; game_state->selected_card = 0; } break; case InputKeyLeft: if(game_state->selectColumn > 0) { game_state->selectColumn--; game_state->selected_card = 0; } break; case InputKeyOk: return true; break; default: break; } if(game_state->selectRow == 0 && game_state->selectColumn == 2) { if(game_state->input == InputKeyRight) game_state->selectColumn++; else game_state->selectColumn--; } if(game_state->dragging_hand.index > 0) game_state->selected_card = 0; return false; } bool place_on_top(Card* where, Card what) { if(where->disabled && what.character == 12) { where->disabled = what.disabled; where->pip = what.pip; where->character = what.character; return true; } else if(where->pip == what.pip) { int8_t a_letter = (int8_t)where->character; int8_t b_letter = (int8_t)what.character; if(a_letter == 12) a_letter = -1; if(b_letter == 12) b_letter = -1; if(where->disabled && b_letter != -1) return false; if((a_letter + 1) == b_letter) { where->disabled = what.disabled; where->pip = what.pip; where->character = what.character; return true; } } return false; } void tick(GameState* game_state, NotificationApp* notification) { game_state->last_tick = furi_get_tick(); uint8_t row = game_state->selectRow; uint8_t column = game_state->selectColumn; if(game_state->state != GameStatePlay && game_state->state != GameStateAnimate) return; bool wasAction = false; if(game_state->state == GameStatePlay) { if(game_state->top_cards[0].character == 11 && game_state->top_cards[1].character == 11 && game_state->top_cards[2].character == 11 && game_state->top_cards[3].character == 11) { dolphin_deed(DolphinDeedPluginGameWin); game_state->state = GameStateAnimate; return; } } if(handleInput(game_state)) { if(game_state->state == GameStatePlay) { if(game_state->longPress && game_state->dragging_hand.index == 1) { for(uint8_t i = 0; i < 4; i++) { if(place_on_top( &(game_state->top_cards[i]), game_state->dragging_hand.cards[0])) { remove_drag(game_state); wasAction = true; break; } } } else { if(row == 0 && column == 0 && game_state->dragging_hand.index == 0) { FURI_LOG_D(APP_NAME, "Drawing card"); game_state->deck.index++; wasAction = true; if(game_state->deck.index >= (game_state->deck.card_count)) game_state->deck.index = -1; } //pick/place from deck else if(row == 0 && column == 1) { //place if(game_state->dragging_deck) { wasAction = true; game_state->dragging_deck = false; game_state->dragging_hand.index = 0; } //pick else { if(game_state->dragging_hand.index == 0 && game_state->deck.index >= 0) { wasAction = true; game_state->dragging_deck = true; add_to_hand( &(game_state->dragging_hand), game_state->deck.cards[game_state->deck.index]); } } } //place on top row else if(row == 0 && game_state->dragging_hand.index == 1) { column -= 3; Card currCard = game_state->dragging_hand.cards[0]; wasAction = place_on_top(&(game_state->top_cards[column]), currCard); if(wasAction) remove_drag(game_state); } //pick/place from bottom else if(row == 1) { Hand* curr_hand = &(game_state->bottom_columns[column]); //pick up if(game_state->dragging_hand.index == 0) { Card curr_card = curr_hand->cards[curr_hand->index - 1]; if(curr_card.flipped) { curr_hand->cards[curr_hand->index - 1].flipped = false; wasAction = true; } else { if(curr_hand->index > 0) { extract_hand_region( curr_hand, &(game_state->dragging_hand), curr_hand->index - game_state->selected_card - 1); game_state->selected_card = 0; game_state->dragging_column = column; wasAction = true; } } } //place else { Card first = game_state->dragging_hand.cards[0]; if(game_state->dragging_column == column || (curr_hand->index == 0 && first.character == 11) || can_place_card(curr_hand->cards[curr_hand->index - 1], first)) { add_hand_region(curr_hand, &(game_state->dragging_hand)); remove_drag(game_state); wasAction = true; } } } } if(!wasAction) { notification_message(notification, &sequence_fail); } } } if(game_state->state == GameStateAnimate) { if(game_state->animation.started && !game_state->longPress && game_state->input == InputKeyOk) { init(game_state); game_state->state = GameStateStart; } game_state->animation.started = true; if(game_state->animation.x < -20 || game_state->animation.x > 128) { game_state->animation.deck++; if(game_state->animation.deck > 3) game_state->animation.deck = 0; int8_t cardIndex = 11 - game_state->animation.indexes[game_state->animation.deck]; if(game_state->animation.indexes[0] == 13 && game_state->animation.indexes[1] == 13 && game_state->animation.indexes[2] == 13 && game_state->animation.indexes[3] == 13) { init(game_state); game_state->state = GameStateStart; return; } if(cardIndex == -1) cardIndex = 12; game_state->animation.card = (Card){ game_state->top_cards[game_state->animation.deck].pip, cardIndex, false, false}; game_state->animation.indexes[game_state->animation.deck]++; game_state->animation.vx = -(rand() % 3 + 1) * (rand() % 2 == 1 ? 1 : -1); game_state->animation.vy = (rand() % 3 + 1); game_state->animation.x = columns[game_state->animation.deck + 3][0]; game_state->animation.y = columns[game_state->animation.deck + 3][1]; } game_state->animation.x += game_state->animation.vx; game_state->animation.y -= game_state->animation.vy; game_state->animation.vy -= 1; if(game_state->animation.vy < -10) game_state->animation.vy = -10; if(game_state->animation.y > 41) { game_state->animation.y = 41; game_state->animation.vy = -(game_state->animation.vy * 0.7f); } } } void init(GameState* game_state) { game_state->selectColumn = 0; game_state->selected_card = 0; game_state->selectRow = 0; generate_deck(&(game_state->deck), 1); shuffle_deck(&(game_state->deck)); game_state->dragging_deck = false; game_state->animation.started = false; game_state->animation.deck = -1; game_state->animation.x = -21; game_state->state = GameStatePlay; game_state->dragging_column = 8; for(uint8_t i = 0; i < 7; i++) { free_hand(&(game_state->bottom_columns[i])); init_hand(&(game_state->bottom_columns[i]), 21); game_state->bottom_columns[i].index = 0; for(uint8_t j = 0; j <= i; j++) { Card cur = remove_from_deck(0, &(game_state->deck)); cur.flipped = i != j; add_to_hand(&(game_state->bottom_columns[i]), cur); } } for(uint8_t i = 0; i < 4; i++) { game_state->animation.indexes[i] = 0; game_state->top_cards[i] = (Card){0, 0, true, false}; } game_state->deck.index = -1; } void init_start(GameState* game_state) { game_state->input = InputKeyMAX; for(uint8_t i = 0; i < 7; i++) init_hand(&(game_state->bottom_columns[i]), 21); init_hand(&(game_state->dragging_hand), 13); game_state->animation.buffer = make_buffer(); } static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { furi_assert(event_queue); AppEvent event = {.type = EventTypeKey, .input = *input_event}; furi_message_queue_put(event_queue, &event, FuriWaitForever); } static void update_timer_callback(FuriMessageQueue* event_queue) { furi_assert(event_queue); AppEvent event = {.type = EventTypeTick}; furi_message_queue_put(event_queue, &event, 0); } int32_t solitaire_app(void* p) { UNUSED(p); int32_t return_code = 0; FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(AppEvent)); GameState* game_state = malloc(sizeof(GameState)); init_start(game_state); set_card_graphics(&I_card_graphics); game_state->state = GameStateStart; game_state->processing = true; 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; } NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message_block(notification, &sequence_display_backlight_enforce_on); ViewPort* view_port = view_port_alloc(); view_port_draw_callback_set(view_port, render_callback, 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() / 30); 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, 150); furi_mutex_acquire(game_state->mutex, FuriWaitForever); bool hadChange = false; if(event_status == FuriStatusOk) { if(event.type == EventTypeKey) { if(event.input.type == InputTypeLong) { game_state->longPress = true; switch(event.input.key) { case InputKeyUp: case InputKeyDown: case InputKeyRight: case InputKeyLeft: case InputKeyOk: game_state->input = event.input.key; break; case InputKeyBack: processing = false; return_code = 1; default: break; } } else if(event.input.type == InputTypePress) { game_state->longPress = false; switch(event.input.key) { case InputKeyUp: case InputKeyDown: case InputKeyRight: case InputKeyLeft: case InputKeyOk: if(event.input.key == InputKeyOk && game_state->state == GameStateStart) { game_state->state = GameStatePlay; init(game_state); } else { hadChange = true; game_state->input = event.input.key; } break; case InputKeyBack: init(game_state); processing = false; return_code = 1; break; default: break; } } } else if(event.type == EventTypeTick) { tick(game_state, notification); processing = game_state->processing; game_state->input = InputKeyMAX; } } else { //FURI_LOG_W(APP_NAME, "osMessageQueue: event timeout"); // event timeout } if(hadChange || game_state->state == GameStateAnimate) view_port_update(view_port); furi_mutex_release(game_state->mutex); } notification_message_block(notification, &sequence_display_backlight_enforce_auto); furi_timer_free(timer); view_port_enabled_set(view_port, false); gui_remove_view_port(gui, view_port); furi_record_close(RECORD_GUI); furi_record_close(RECORD_NOTIFICATION); view_port_free(view_port); furi_mutex_free(game_state->mutex); free_and_exit: free(game_state->animation.buffer); ui_cleanup(); for(uint8_t i = 0; i < 7; i++) free_hand(&(game_state->bottom_columns[i])); free(game_state->deck.cards); free(game_state); furi_message_queue_free(event_queue); return return_code; }