diff --git a/applications/applications.c b/applications/applications.c index e3d4c77df..115eeadb4 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -21,7 +21,6 @@ extern int32_t accessor_app(void* p); extern int32_t archive_app(void* p); extern int32_t bad_usb_app(void* p); extern int32_t u2f_app(void* p); -extern int32_t wav_player_app(void* p); extern int32_t uart_echo_app(void* p); extern int32_t blink_test_app(void* p); extern int32_t bt_debug_app(void* p); @@ -49,7 +48,11 @@ extern int32_t file_browser_app(void* p); // Plugins extern int32_t music_player_app(void* p); +extern int32_t wav_player_app(void* p); + +// Games extern int32_t snake_game_app(void* p); +extern int32_t tetris_game_app(void *p); // On system start hooks declaration extern void bt_on_system_start(); @@ -344,14 +347,6 @@ const FlipperApplication FLIPPER_PLUGINS[] = { .flags = FlipperApplicationFlagDefault}, #endif -#ifdef APP_SNAKE_GAME - {.app = snake_game_app, - .name = "Snake Game", - .stack_size = 1024, - .icon = &A_Plugins_14, - .flags = FlipperApplicationFlagDefault}, -#endif - #ifdef APP_WAV_PLAYER {.app = wav_player_app, .name = "WAV Player", @@ -364,7 +359,30 @@ const FlipperApplication FLIPPER_PLUGINS[] = { const size_t FLIPPER_PLUGINS_COUNT = COUNT_OF(FLIPPER_PLUGINS); -// Plugin menu +// Games menu +const FlipperApplication FLIPPER_GAMES[] = { + +#ifdef APP_SNAKE_GAME + {.app = snake_game_app, + .name = "Snake", + .stack_size = 1024, + .icon = &A_Snake_14, + .flags = FlipperApplicationFlagDefault}, +#endif + +#ifdef APP_TETRIS_GAME + {.app = tetris_game_app, + .name = "Tetris", + .stack_size = 1024, + .icon = &A_Tetris_14, + .flags = FlipperApplicationFlagDefault}, +#endif + +}; + +const size_t FLIPPER_GAMES_COUNT = COUNT_OF(FLIPPER_GAMES); + +// Debug menu const FlipperApplication FLIPPER_DEBUG_APPS[] = { #ifdef APP_BLINK {.app = blink_test_app, diff --git a/applications/applications.h b/applications/applications.h index bebe438ab..72eaf9135 100644 --- a/applications/applications.h +++ b/applications/applications.h @@ -42,6 +42,12 @@ extern const size_t FLIPPER_ON_SYSTEM_START_COUNT; extern const FlipperApplication FLIPPER_PLUGINS[]; extern const size_t FLIPPER_PLUGINS_COUNT; +/* Games list +* Spawned by loader +*/ +extern const FlipperApplication FLIPPER_GAMES[]; +extern const size_t FLIPPER_GAMES_COUNT; + /* Debug menu apps * Spawned by loader */ diff --git a/applications/applications.mk b/applications/applications.mk index aa97f6207..b16bd1f65 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -48,6 +48,7 @@ APP_UPDATER = 1 APP_MUSIC_PLAYER = 1 APP_SNAKE_GAME = 1 APP_WAV_PLAYER = 1 +APP_TETRIS_GAME = 1 # Debug APP_ACCESSOR = 1 @@ -247,6 +248,11 @@ CFLAGS += -DAPP_WAV_PLAYER SRV_GUI = 1 endif +APP_TETRIS_GAME ?= 0 +ifeq ($(APP_TETRIS_GAME), 1) +CFLAGS += -DAPP_TETRIS_GAME +SRV_GUI = 1 +endif APP_IBUTTON ?= 0 ifeq ($(APP_IBUTTON), 1) diff --git a/applications/loader/loader.c b/applications/loader/loader.c index e5d597029..ceca0b352 100644 --- a/applications/loader/loader.c +++ b/applications/loader/loader.c @@ -86,6 +86,10 @@ const FlipperApplication* loader_find_application_by_name(const char* name) { application = loader_find_application_by_name_in_list(name, FLIPPER_PLUGINS, FLIPPER_PLUGINS_COUNT); } + if(!application) { + application = + loader_find_application_by_name_in_list(name, FLIPPER_GAMES, FLIPPER_GAMES_COUNT); + } if(!application) { application = loader_find_application_by_name_in_list( name, FLIPPER_SETTINGS_APPS, FLIPPER_SETTINGS_APPS_COUNT); @@ -155,6 +159,11 @@ void loader_cli_list(Cli* cli, string_t args, Loader* instance) { printf("\t%s\r\n", FLIPPER_PLUGINS[i].name); } + printf("Games:\r\n"); + for(size_t i = 0; i < FLIPPER_GAMES_COUNT; i++) { + printf("\t%s\r\n", FLIPPER_GAMES[i].name); + } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { printf("Debug:\r\n"); for(size_t i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { @@ -321,6 +330,13 @@ static Loader* loader_alloc() { instance->view_dispatcher, LoaderMenuViewPlugins, submenu_get_view(instance->plugins_menu)); + // Games menu + instance->games_menu = submenu_alloc(); + view_set_context(submenu_get_view(instance->games_menu), instance->games_menu); + view_set_previous_callback( + submenu_get_view(instance->games_menu), loader_back_to_primary_menu); + view_dispatcher_add_view( + instance->view_dispatcher, LoaderMenuViewGames, submenu_get_view(instance->games_menu)); // Debug menu instance->debug_menu = submenu_alloc(); view_set_context(submenu_get_view(instance->debug_menu), instance->debug_menu); @@ -358,6 +374,8 @@ static void loader_free(Loader* instance) { view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPrimary); submenu_free(loader_instance->plugins_menu); view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewPlugins); + submenu_free(loader_instance->games_menu); + view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewGames); submenu_free(loader_instance->debug_menu); view_dispatcher_remove_view(loader_instance->view_dispatcher, LoaderMenuViewDebug); submenu_free(loader_instance->settings_menu); @@ -391,6 +409,15 @@ static void loader_build_menu() { loader_submenu_callback, (void*)LoaderMenuViewPlugins); } + if(FLIPPER_GAMES_COUNT != 0) { + menu_add_item( + loader_instance->primary_menu, + "Games", + &A_Games_14, + i++, + loader_submenu_callback, + (void*)LoaderMenuViewGames); + } if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { menu_add_item( loader_instance->primary_menu, @@ -421,6 +448,17 @@ static void loader_build_submenu() { (void*)&FLIPPER_PLUGINS[i]); } + FURI_LOG_I(TAG, "Building games menu"); + size_t i; + for(i = 0; i < FLIPPER_GAMES_COUNT; i++) { + submenu_add_item( + loader_instance->games_menu, + FLIPPER_GAMES[i].name, + i, + loader_menu_callback, + (void*)&FLIPPER_GAMES[i]); + } + FURI_LOG_I(TAG, "Building debug menu"); for(i = 0; i < FLIPPER_DEBUG_APPS_COUNT; i++) { submenu_add_item( diff --git a/applications/loader/loader_i.h b/applications/loader/loader_i.h index 4c937b942..21b3191e1 100644 --- a/applications/loader/loader_i.h +++ b/applications/loader/loader_i.h @@ -27,6 +27,7 @@ struct Loader { ViewDispatcher* view_dispatcher; Menu* primary_menu; Submenu* plugins_menu; + Submenu* games_menu; Submenu* debug_menu; Submenu* settings_menu; @@ -38,6 +39,7 @@ struct Loader { typedef enum { LoaderMenuViewPrimary, LoaderMenuViewPlugins, + LoaderMenuViewGames, LoaderMenuViewDebug, LoaderMenuViewSettings, } LoaderMenuView; diff --git a/applications/tetris_game/tetris_game.c b/applications/tetris_game/tetris_game.c new file mode 100644 index 000000000..18c221b89 --- /dev/null +++ b/applications/tetris_game/tetris_game.c @@ -0,0 +1,479 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BORDER_OFFSET 1 +#define MARGIN_OFFSET 3 +#define BLOCK_HEIGHT 6 +#define BLOCK_WIDTH 6 + +#define FIELD_WIDTH 11 +#define FIELD_HEIGHT 24 + +typedef struct Point { + // Also used for offset data, which is sometimes negative + int8_t x, y; +} Point; + +// Rotation logic taken from +// https://www.youtube.com/watch?v=yIpk5TJ_uaI +typedef enum { + OffsetTypeCommon, + OffsetTypeI, + OffsetTypeO +} OffsetType; + +// Since we only support rotating clockwise, these are actual translation values, +// not values to be subtracted to get translation values + +static const Point rotOffsetTranslation[3][4][5] = { + { + { {0,0}, {-1,0}, {-1,-1}, {0,2}, {-1,2} }, + { {0,0}, {1,0}, {1,1}, {0,-2}, {1,-2} }, + { {0,0}, {1,0}, {1,-1}, {0,2}, {1,2} }, + { {0,0}, {-1,0}, {-1,1}, {0,-2}, {-1,-2} } + }, + { + { {1,0}, {-1,0}, {2,0}, {-1,1}, {2,-2} }, + { {0,1}, {-1,1}, {2,1}, {-1,-1}, {2,2} }, + { {-1,0}, {1,0}, {-2,0}, {1,-1}, {-2,2} }, + { {0,-1}, {1,-1}, {-2,-1}, {1,1}, {-2,-2} } + }, + { + { {0,-1}, {0,0}, {0,0}, {0,0}, {0,0} }, + { {1,0}, {0,0}, {0,0}, {0,0}, {0,0} }, + { {0,1}, {0,0}, {0,0}, {0,0}, {0,0} }, + { {-1,0}, {0,0}, {0,0}, {0,0}, {0,0} } + } +}; + +typedef struct { + Point p[4]; + uint8_t rotIdx; + OffsetType offsetType; +} Piece; + +// Shapes @ spawn locations, rotation point first +static Piece shapes[] = { + { .p = {{5, 1}, {4, 0}, {5, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // Z + { .p = {{5, 1}, {4, 1}, {5, 0}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // S + { .p = {{5, 1}, {4, 1}, {6, 1}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // L + { .p = {{5, 1}, {4, 0}, {4, 1}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // J + { .p = {{5, 1}, {4, 1}, {5, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon }, // T + { .p = {{5, 1}, {4, 1}, {6, 1}, {7, 1}}, .rotIdx = 0, .offsetType = OffsetTypeI }, // I + { .p = {{5, 1}, {5, 0}, {6, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeO } // O +}; + +typedef enum { + GameStatePlaying, + GameStateGameOver +} GameState; + +typedef struct { + bool playField[FIELD_HEIGHT][FIELD_WIDTH]; + Piece currPiece; + uint16_t numLines; + uint16_t fallSpeed; + GameState gameState; + osTimerId_t timer; +} TetrisState; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} TetrisEvent; + +static void tetris_game_draw_border(Canvas* const canvas) { + canvas_draw_line(canvas, 0, 0, 0, 127); + canvas_draw_line(canvas, 0, 127, 63, 127); + canvas_draw_line(canvas, 63, 127, 63, 0); + + canvas_draw_line(canvas, 2, 0, 2, 125); + canvas_draw_line(canvas, 2, 125, 61, 125); + canvas_draw_line(canvas, 61, 125, 61, 0); +} + +static void tetris_game_draw_playfield(Canvas* const canvas, const TetrisState* tetris_state) { + // Playfield: 11 x 24 + + for (int y = 0; y < FIELD_HEIGHT; y++) { + for (int x = 0; x < FIELD_WIDTH; x++) { + if (tetris_state->playField[y][x]) { + uint16_t xOffset = x * 5; + uint16_t yOffset = y * 5; + + canvas_draw_rframe( + canvas, + BORDER_OFFSET + MARGIN_OFFSET + xOffset, + BORDER_OFFSET + MARGIN_OFFSET + yOffset - 1, + BLOCK_WIDTH, + BLOCK_HEIGHT, + 1 + ); + canvas_draw_dot( + canvas, + BORDER_OFFSET + MARGIN_OFFSET + xOffset + 2, + BORDER_OFFSET + MARGIN_OFFSET + yOffset + 1 + ); + canvas_draw_dot( + canvas, + BORDER_OFFSET + MARGIN_OFFSET + xOffset + 3, + BORDER_OFFSET + MARGIN_OFFSET + yOffset + 1 + ); + canvas_draw_dot( + canvas, + BORDER_OFFSET + MARGIN_OFFSET + xOffset + 2, + BORDER_OFFSET + MARGIN_OFFSET + yOffset + 2 + ); + } + } + } +} + +static void tetris_game_render_callback(Canvas* const canvas, void* ctx) { + const TetrisState* tetris_state = acquire_mutex((ValueMutex*)ctx, 25); + if(tetris_state == NULL) { + FURI_LOG_E("TetrisGame", "it null"); + return; + } + + tetris_game_draw_border(canvas); + tetris_game_draw_playfield(canvas, tetris_state); + + if(tetris_state->gameState == GameStateGameOver) { + // 128 x 64 + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 1, 52, 62, 24); + + canvas_set_color(canvas, ColorBlack); + canvas_draw_frame(canvas, 1, 52, 62, 24); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 4, 63, "Game Over"); + + char buffer[13]; + snprintf(buffer, sizeof(buffer), "Lines: %u", tetris_state->numLines); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 32, 73, AlignCenter, AlignBottom, buffer); + } + release_mutex((ValueMutex *)ctx, tetris_state); + } + +static void tetris_game_input_callback(InputEvent* input_event, osMessageQueueId_t event_queue) { + furi_assert(event_queue); + + TetrisEvent event = {.type = EventTypeKey, .input = *input_event}; + osMessageQueuePut(event_queue, &event, 0, osWaitForever); +} + +static void tetris_game_init_state(TetrisState* tetris_state) { + tetris_state->gameState = GameStatePlaying; + tetris_state->numLines = 0; + tetris_state->fallSpeed = 500; + memset(tetris_state->playField, 0, sizeof(tetris_state->playField)); + + memcpy(&tetris_state->currPiece, &shapes[rand() % 7], sizeof(tetris_state->currPiece)); + + osTimerStart(tetris_state->timer, tetris_state->fallSpeed); +} + +static void tetris_game_remove_curr_piece(TetrisState* tetris_state) { + for (int i = 0; i < 4; i++) { + uint8_t x = tetris_state->currPiece.p[i].x; + uint8_t y = tetris_state->currPiece.p[i].y; + + tetris_state->playField[y][x] = false; + } +} + +static void tetris_game_render_curr_piece(TetrisState* tetris_state) { + for (int i = 0; i < 4; i++) { + uint8_t x = tetris_state->currPiece.p[i].x; + uint8_t y = tetris_state->currPiece.p[i].y; + + tetris_state->playField[y][x] = true; + } +} + +static void tetris_game_rotate_shape(Point currShape[], Point newShape[]) { + // Copy shape data + for(int i = 0; i < 4; i++) { + newShape[i] = currShape[i]; + } + + for (int i = 1; i < 4; i++) { + int8_t relX = currShape[i].x - currShape[0].x; + int8_t relY = currShape[i].y - currShape[0].y; + + // Matrix rotation thing + int8_t newRelX = (relX * 0) + (relY * -1); + int8_t newRelY = (relX * 1) + (relY * 0); + + newShape[i].x = currShape[0].x + newRelX; + newShape[i].y = currShape[0].y + newRelY; + } +} + +static void tetris_game_apply_kick(Point points[], Point kick) { + for(int i = 0; i < 4; i++) { + points[i].x += kick.x; + points[i].y += kick.y; + } +} + +static bool tetris_game_is_valid_pos(TetrisState* tetris_state, Point* shape) { + for (int i = 0; i < 4; i++) { + if(shape[i].x < 0 || shape[i].x > (FIELD_WIDTH - 1) || tetris_state->playField[shape[i].y][shape[i].x] == true) { + return false; + } + } + return true; +} + +static void tetris_game_try_rotation(TetrisState* tetris_state, Piece *newPiece) { + uint8_t currRotIdx = tetris_state->currPiece.rotIdx; + + Point *rotatedShape = malloc(sizeof(Point) * 4); + Point *kickedShape = malloc(sizeof(Point) * 4); + + memcpy(rotatedShape, &tetris_state->currPiece.p, sizeof(tetris_state->currPiece.p)); + + tetris_game_rotate_shape(tetris_state->currPiece.p, rotatedShape); + + for(int i = 0; i < 5; i++) { + memcpy(kickedShape, rotatedShape, (sizeof(Point) * 4)); + tetris_game_apply_kick(kickedShape, rotOffsetTranslation[newPiece->offsetType][currRotIdx][i]); + + if(tetris_game_is_valid_pos(tetris_state, kickedShape)) { + memcpy(&newPiece->p, kickedShape, sizeof(newPiece->p)); + newPiece->rotIdx = (newPiece->rotIdx + 1) % 4; + break; + } + } + free(rotatedShape); + free(kickedShape); +} + +static bool tetris_game_row_is_line(bool row[]) { + for(int i = 0; i < FIELD_WIDTH; i++) { + if(row[i] == false) + return false; + } + return true; +} + +static void tetris_game_check_for_lines(TetrisState* tetris_state, uint8_t* lines, uint8_t* numLines) { + for(int i = 0; i < FIELD_HEIGHT; i++) { + if(tetris_game_row_is_line(tetris_state->playField[i])) { + *(lines++) = i; + *numLines += 1; + } + } +} + +static bool tetris_game_piece_at_bottom(TetrisState* tetris_state, Piece* newPiece) { + for (int i = 0; i < 4; i++) { + Point *pos = (Point *)&newPiece->p; + if (pos[i].y >= FIELD_HEIGHT || tetris_state->playField[pos[i].y][pos[i].x] == true) { + return true; + } + } + return false; +} + +static void tetris_game_update_timer_callback(osMessageQueueId_t event_queue) { + furi_assert(event_queue); + + TetrisEvent event = {.type = EventTypeTick}; + osMessageQueuePut(event_queue, &event, 0, osWaitForever); +} + +static void tetris_game_process_step(TetrisState* tetris_state, Piece* newPiece, bool wasDownMove) { + if(tetris_state->gameState == GameStateGameOver) + return; + + tetris_game_remove_curr_piece(tetris_state); + + if(wasDownMove) { + if(tetris_game_piece_at_bottom(tetris_state, newPiece)) { + osTimerStop(tetris_state->timer); + + tetris_game_render_curr_piece(tetris_state); + uint8_t numLines = 0; + uint8_t lines[] = { 0,0,0,0 }; + + tetris_game_check_for_lines(tetris_state, lines, &numLines); + if(numLines > 0) { + for(int i = 0; i < numLines; i++) { + + // zero out row + for(int j = 0; j < FIELD_WIDTH; j++) { + tetris_state->playField[lines[i]][j] = false; + } + // move all above rows down + for(int k = lines[i]; k >= 0 ; k--) { + for(int m = 0; m < FIELD_WIDTH; m++) { + tetris_state->playField[k][m] = (k == 0) ? false : tetris_state->playField[k-1][m]; + } + } + } + + uint16_t oldNumLines = tetris_state->numLines; + tetris_state->numLines += numLines; + if((oldNumLines / 10) % 10 != (tetris_state->numLines / 10) % 10) { + tetris_state->fallSpeed -= 50; + } + } + + // Check for game over + Piece* spawnedPiece = &shapes[rand() % 7]; + if(!tetris_game_is_valid_pos(tetris_state, spawnedPiece->p)) { + tetris_state->gameState = GameStateGameOver; + } else { + memcpy(&tetris_state->currPiece, spawnedPiece, sizeof(tetris_state->currPiece)); + osTimerStart(tetris_state->timer, tetris_state->fallSpeed); + } + } + } + + if(tetris_game_is_valid_pos(tetris_state, newPiece->p)) { + memcpy(&tetris_state->currPiece, newPiece, sizeof(tetris_state->currPiece)); + } + + tetris_game_render_curr_piece(tetris_state); +} + + +int32_t tetris_game_app() { + srand(DWT->CYCCNT); + + osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(TetrisEvent), NULL); + + TetrisState* tetris_state = malloc(sizeof(TetrisState)); + + ValueMutex state_mutex; + if(!init_mutex(&state_mutex, tetris_state, sizeof(TetrisState))) { + FURI_LOG_E("TetrisGame", "cannot create mutex\r\n"); + free(tetris_state); + return 255; + } + + // Not doing this eventually causes issues with TimerSvc due to not sleeping/yielding enough in this task + TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME); + TaskHandle_t curr_task = xTaskGetHandle("Tetris Game"); + + uint32_t origTimerPrio = uxTaskPriorityGet(timer_task); + uint32_t myPrio = uxTaskPriorityGet(curr_task); + vTaskPrioritySet(timer_task, myPrio + 1); + + ViewPort* view_port = view_port_alloc(); + view_port_set_orientation(view_port, ViewPortOrientationVertical); + view_port_draw_callback_set(view_port, tetris_game_render_callback, &state_mutex); + view_port_input_callback_set(view_port, tetris_game_input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open("gui"); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + tetris_state->timer = osTimerNew(tetris_game_update_timer_callback, osTimerPeriodic, event_queue, NULL); + tetris_game_init_state(tetris_state); + + TetrisEvent event; + + Piece *newPiece = malloc(sizeof(Piece)); + uint8_t downRepeatCounter = 0; + + for(bool processing = true; processing;) { + // This 10U implicitly sets the game loop speed. downRepeatCounter relies on this value + osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 10U); + + TetrisState* tetris_state = (TetrisState*)acquire_mutex_block(&state_mutex); + + memcpy(newPiece, &tetris_state->currPiece, sizeof(tetris_state->currPiece)); + bool wasDownMove = false; + + if(!furi_hal_gpio_read(&gpio_button_right)) { + if(downRepeatCounter > 3) { + for (int i = 0; i < 4; i++) { + newPiece->p[i].y += 1; + } + downRepeatCounter = 0; + wasDownMove = true; + } else { + downRepeatCounter++; + } + } + + if(event_status == osOK) { + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress || event.input.type == InputTypeLong || event.input.type == InputTypeRepeat) { + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + for (int i = 0; i < 4; i++) { + newPiece->p[i].x += 1; + } + break; + case InputKeyLeft: + for (int i = 0; i < 4; i++) { + newPiece->p[i].x -= 1; + } + break; + case InputKeyOk: + if(tetris_state->gameState == GameStatePlaying) { + tetris_game_remove_curr_piece(tetris_state); + tetris_game_try_rotation(tetris_state, newPiece); + tetris_game_render_curr_piece(tetris_state); + } else { + tetris_game_init_state(tetris_state); + } + break; + case InputKeyBack: + processing = false; + break; + } + } + } else if(event.type == EventTypeTick) { + // TODO: This is inverted. it returns true when the button is not pressed. + // see macro in input.c and do that + if(furi_hal_gpio_read(&gpio_button_right)) { + for (int i = 0; i < 4; i++) { + newPiece->p[i].y += 1; + } + wasDownMove = true; + } + } + } + + tetris_game_process_step(tetris_state, newPiece, wasDownMove); + + view_port_update(view_port); + release_mutex(&state_mutex, tetris_state); + } + + osTimerDelete(tetris_state->timer); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + osMessageQueueDelete(event_queue); + delete_mutex(&state_mutex); + vTaskPrioritySet(timer_task, origTimerPrio); + free(newPiece); + free(tetris_state); + + return 0; +} diff --git a/assets/icons/MainMenu/Snake_14/frame_01.png b/assets/icons/MainMenu/Snake_14/frame_01.png new file mode 100644 index 000000000..a01c871ff Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_01.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_02.png b/assets/icons/MainMenu/Snake_14/frame_02.png new file mode 100644 index 000000000..adc92431c Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_02.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_03.png b/assets/icons/MainMenu/Snake_14/frame_03.png new file mode 100644 index 000000000..2ee47c365 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_03.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_04.png b/assets/icons/MainMenu/Snake_14/frame_04.png new file mode 100644 index 000000000..50f2b6c22 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_04.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_05.png b/assets/icons/MainMenu/Snake_14/frame_05.png new file mode 100644 index 000000000..7325060b9 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_05.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_06.png b/assets/icons/MainMenu/Snake_14/frame_06.png new file mode 100644 index 000000000..50f2b6c22 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_06.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_07.png b/assets/icons/MainMenu/Snake_14/frame_07.png new file mode 100644 index 000000000..7325060b9 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_07.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_08.png b/assets/icons/MainMenu/Snake_14/frame_08.png new file mode 100644 index 000000000..73767a1ef Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_08.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_09.png b/assets/icons/MainMenu/Snake_14/frame_09.png new file mode 100644 index 000000000..f55a6a9a1 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_09.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_10.png b/assets/icons/MainMenu/Snake_14/frame_10.png new file mode 100644 index 000000000..a5d9251c3 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_10.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_11.png b/assets/icons/MainMenu/Snake_14/frame_11.png new file mode 100644 index 000000000..b75996dd6 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_11.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_12.png b/assets/icons/MainMenu/Snake_14/frame_12.png new file mode 100644 index 000000000..d36cc05f3 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_12.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_13.png b/assets/icons/MainMenu/Snake_14/frame_13.png new file mode 100644 index 000000000..c584dda79 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_13.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_14.png b/assets/icons/MainMenu/Snake_14/frame_14.png new file mode 100644 index 000000000..92070c0a0 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_14.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_15.png b/assets/icons/MainMenu/Snake_14/frame_15.png new file mode 100644 index 000000000..a9f8fb252 Binary files /dev/null and b/assets/icons/MainMenu/Snake_14/frame_15.png differ diff --git a/assets/icons/MainMenu/Snake_14/frame_rate b/assets/icons/MainMenu/Snake_14/frame_rate new file mode 100644 index 000000000..e440e5c84 --- /dev/null +++ b/assets/icons/MainMenu/Snake_14/frame_rate @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/assets/icons/MainMenu/Tetris_14/frame_01.png b/assets/icons/MainMenu/Tetris_14/frame_01.png new file mode 100644 index 000000000..1b17a17fd Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_01.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_02.png b/assets/icons/MainMenu/Tetris_14/frame_02.png new file mode 100644 index 000000000..b9684adda Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_02.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_03.png b/assets/icons/MainMenu/Tetris_14/frame_03.png new file mode 100644 index 000000000..66502cdb2 Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_03.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_04.png b/assets/icons/MainMenu/Tetris_14/frame_04.png new file mode 100644 index 000000000..5dba9156b Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_04.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_05.png b/assets/icons/MainMenu/Tetris_14/frame_05.png new file mode 100644 index 000000000..3704b0bf4 Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_05.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_06.png b/assets/icons/MainMenu/Tetris_14/frame_06.png new file mode 100644 index 000000000..2960660ba Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_06.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_07.png b/assets/icons/MainMenu/Tetris_14/frame_07.png new file mode 100644 index 000000000..3704b0bf4 Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_07.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_08.png b/assets/icons/MainMenu/Tetris_14/frame_08.png new file mode 100644 index 000000000..2960660ba Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_08.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_09.png b/assets/icons/MainMenu/Tetris_14/frame_09.png new file mode 100644 index 000000000..d554b9fe9 Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_09.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_10.png b/assets/icons/MainMenu/Tetris_14/frame_10.png new file mode 100644 index 000000000..960f0ea6c Binary files /dev/null and b/assets/icons/MainMenu/Tetris_14/frame_10.png differ diff --git a/assets/icons/MainMenu/Tetris_14/frame_rate b/assets/icons/MainMenu/Tetris_14/frame_rate new file mode 100644 index 000000000..e440e5c84 --- /dev/null +++ b/assets/icons/MainMenu/Tetris_14/frame_rate @@ -0,0 +1 @@ +3 \ No newline at end of file