From 532253867e4fb3e22e3502388cd2b38dd31e9e3e Mon Sep 17 00:00:00 2001 From: Eng1n33r Date: Wed, 1 Jun 2022 20:54:51 +0300 Subject: [PATCH] add tetris game & games menu & 2 icons --- applications/applications.c | 38 +- applications/applications.h | 6 + applications/applications.mk | 6 + applications/loader/loader.c | 38 ++ applications/loader/loader_i.h | 2 + applications/tetris_game/tetris_game.c | 479 +++++++++++++++++++ assets/icons/MainMenu/Snake_14/frame_01.png | Bin 0 -> 146 bytes assets/icons/MainMenu/Snake_14/frame_02.png | Bin 0 -> 148 bytes assets/icons/MainMenu/Snake_14/frame_03.png | Bin 0 -> 146 bytes assets/icons/MainMenu/Snake_14/frame_04.png | Bin 0 -> 148 bytes assets/icons/MainMenu/Snake_14/frame_05.png | Bin 0 -> 160 bytes assets/icons/MainMenu/Snake_14/frame_06.png | Bin 0 -> 148 bytes assets/icons/MainMenu/Snake_14/frame_07.png | Bin 0 -> 160 bytes assets/icons/MainMenu/Snake_14/frame_08.png | Bin 0 -> 96 bytes assets/icons/MainMenu/Snake_14/frame_09.png | Bin 0 -> 114 bytes assets/icons/MainMenu/Snake_14/frame_10.png | Bin 0 -> 129 bytes assets/icons/MainMenu/Snake_14/frame_11.png | Bin 0 -> 139 bytes assets/icons/MainMenu/Snake_14/frame_12.png | Bin 0 -> 138 bytes assets/icons/MainMenu/Snake_14/frame_13.png | Bin 0 -> 138 bytes assets/icons/MainMenu/Snake_14/frame_14.png | Bin 0 -> 140 bytes assets/icons/MainMenu/Snake_14/frame_15.png | Bin 0 -> 141 bytes assets/icons/MainMenu/Snake_14/frame_rate | 1 + assets/icons/MainMenu/Tetris_14/frame_01.png | Bin 0 -> 161 bytes assets/icons/MainMenu/Tetris_14/frame_02.png | Bin 0 -> 159 bytes assets/icons/MainMenu/Tetris_14/frame_03.png | Bin 0 -> 153 bytes assets/icons/MainMenu/Tetris_14/frame_04.png | Bin 0 -> 141 bytes assets/icons/MainMenu/Tetris_14/frame_05.png | Bin 0 -> 124 bytes assets/icons/MainMenu/Tetris_14/frame_06.png | Bin 0 -> 144 bytes assets/icons/MainMenu/Tetris_14/frame_07.png | Bin 0 -> 124 bytes assets/icons/MainMenu/Tetris_14/frame_08.png | Bin 0 -> 144 bytes assets/icons/MainMenu/Tetris_14/frame_09.png | Bin 0 -> 129 bytes assets/icons/MainMenu/Tetris_14/frame_10.png | Bin 0 -> 154 bytes assets/icons/MainMenu/Tetris_14/frame_rate | 1 + 33 files changed, 561 insertions(+), 10 deletions(-) create mode 100644 applications/tetris_game/tetris_game.c create mode 100644 assets/icons/MainMenu/Snake_14/frame_01.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_02.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_03.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_04.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_05.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_06.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_07.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_08.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_09.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_10.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_11.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_12.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_13.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_14.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_15.png create mode 100644 assets/icons/MainMenu/Snake_14/frame_rate create mode 100644 assets/icons/MainMenu/Tetris_14/frame_01.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_02.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_03.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_04.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_05.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_06.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_07.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_08.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_09.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_10.png create mode 100644 assets/icons/MainMenu/Tetris_14/frame_rate 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 0000000000000000000000000000000000000000..a01c871ff558a4b90d401a77bb2d55b44a86a6bb GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}KAtX)AsQ36 zPCCfNpupk0{l)+K%DYM)r`ME)Je#eg%E>9{aKpmLtBsApbkdY3Ia|6WCjMNZK1JQ- sy2mA(Q>GiMdhedQyQ3@h<@@ImSu-NU<-I(TK(;b?y85}Sb4q9e0MURk)&Kwi literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..adc92431c16960996ec426f92bf6348767c652de GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}ex5FlAsQ36 zPP)y-pupk0{lowIoA;c&48`WUr=F9(dsoqc;rgK$h9c$+8)o}?&O4+dv6Acc+OnzI vDaV66jABo1UL4w)_e}N8xlNzH7aOUS)QHP>FFz**>tgu-H~m*LbS%+yzGZuiigTxz%`2TwX}OmdK II;Vst090=~Hvj+t literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..50f2b6c22aa55453afb0f75e0d84492d0a446947 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}ex5FlAsQ36 zPCCfNpupk0{lowIn|VejPW0UKP0Ty=;$0sbOMuv_<1cnFHiS-^6w`V$=%#F*JMY4{ vbw}#UL;ULPEobqWt8yi(&il08Qc`$PqLlc-i}~+?CNp@t`njxgN@xNAm)tT^ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7325060b9d127f23d9f3f56bdf4f77681c5baf7f GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}QJyZ2AsQ36 zPP)x?z<|S9`2YX;=jWMOmHKd<^%gk0G=MpSIWc;Y$y0ZZ6Wi4G9J;(JQs(k3)j*-i zRjMCf|Cm=Vy!o@mMqyb`6Nl$Nit@i*yn1TSyVKJBf9LdY&#~;5d+l7i7HA8Dr>mdK II;Vst090=~Hvj+t literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..73767a1ef5b98ac1498c3416d3cafd47393fbc36 GIT binary patch literal 96 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}Ql2i3AsQ2t s|NQ^|zn;yALD+JZn`vK`jF%MSL=F}SmaTg}16472y85}Sb4q9e0M82;E&u=k literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f55a6a9a1a0187b35598b432c9a61d42f13b079b GIT binary patch literal 114 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}I-V|$AsQ2t z|NQ^|zn;yALD+JZo9U%kWguV%;;*?W2UQ(49C;TVP-J2F`i5PW?J;KwP#=S*tDnm{ Hr-UW|&(Izw literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a5d9251c3e3b3be8a31b281888b2a0e223db6559 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R})}AhoAsQ3! zPCUrTV8Fw4e$wCYZNf_!UoO6qe}P-;w%UY-#T!q@a`G-X3zzStDmBdwcU~Pi6Fg;=Pfb3{ lp`dl0w~PFYYeUX?GG7Z3l(Y*!>kKrI!PC{xWt~$(697wXEdl@l literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d36cc05f32c606429eb2c76dc88e1686361340f9 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}E}kxqAsQ3! zPITmAP~c!mKm5P`rk~@4g%w@TG}b7uzH>KIVS&sY*~Q7s8%`~q)E9aChjTzs#HmAC lw--NKfAEbj|J2EbjM`Fyl1EO3IRVXM@O1TaS?83{1ORt!E@uD$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c584dda797eed7d66b76cdbe44a665e309fef11f GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}E}kxqAsQ3! zPCUrPpupk${m}pask_-UJ0080LXJsGzk8R(;hFmQl!WXol}J_14R>!Hk?(PcIAwZq k)A7t@-=8iC40qkAUVl+g@@&=5QlNPZp00i_>zopr0EcHUi~s-t literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..92070c0a0591ff2382d560d6769d921143c6699b GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}Zk{fVAsQ3! zPV(eqP~c!*-uAyFmuVILu-8g9Ms6Hnv%U} nt=RHeO@-q5S9Y(sIsg5>ZGv)qsrz|=CNg-s`njxgN@xNAk{d4s literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a9f8fb25252233181824417b55ae516dc50c4ee0 GIT binary patch literal 141 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}?w&4=AsQ2p zPCCfNpupk0{l)+K%DYM@F0f5>PqjO~MCG6clLmLVTX6=%1?}mR?o^q5Y@U$v;-Fd@ n_g@RYW}Ri4d%phsR=h~+`vXChm5a4NmN9s``njxgN@xNA+fFZO literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1b17a17fd19e64079a74ead00eef5db4768aab13 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}(Vi}jAsQ36 z_B(Ph7;-TCzWl%cP28(Rlf3kJzbu&K$5dW!Y|J2LwK`CnzpGKInQMW{gibEWHObBS=r%MoArkwTqW@3b#o;qpfwDhu6{1- HoD!MDBr+#u=&ro%2=Bt;BXN!H6 zsGYs!io0%nFSDq*-zw`Wb=6f9@2&i&Q&{oYZSCowS1Opfwgx^e3BLOeXbFR-tDnm{ Hr-UW|c)L3) literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..66502cdb2e5c9fc33b6323f44bca9a738639f720 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}!JaOTAsQ2} zUNYo5V8G)Xxbn-t@PDzgiI4SUSGlsxu`w`gP`N2)Q1)yU`|aFv!;dlQX$`?XJNd5J z9kH_UTs*_uBU5yH&!cPh!tbx#TlSY>eq0R$Q)S*Q&xoaqfyOg=FyJ{-vhe@^O`A+6q;+b(712G`&TrYEX}!8^5qI}&a?icn o+K*-aS%9g_|dw(z=xGPflDb$lzj^wCzag S?gu~<7(8A5T-G@yGywpr$|kA+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2960660ba00e5f48094dcbcfec8ca8ad4e461a09 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}UY;(FAsQ3U z_8;V8P~dTz|NDRaw&072qM|OlPssR9%`G-4RAS=re3^K({q43d7LOTAt{VQfab{qQ rUS|4k!($HnH#d%-`muY3e=Dnouz};F(0j9hrZRZC`njxgN@xNAeXKD; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3704b0bf4e001c2e37bc35de85b9a3003cb1b98a GIT binary patch literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}W}YsNAsQ2t z|NQ^|zn;yALD+JZ1PDkS%9g_|dw(z=xGPflDb$lzj^wCzag S?gu~<7(8A5T-G@yGywpr$|kA+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2960660ba00e5f48094dcbcfec8ca8ad4e461a09 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R}UY;(FAsQ3U z_8;V8P~dTz|NDRaw&072qM|OlPssR9%`G-4RAS=re3^K({q43d7LOTAt{VQfab{qQ rUS|4k!($HnH#d%-`muY3e=Dnouz};F(0j9hrZRZC`njxgN@xNAeXKD; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d554b9fe9f08aa4b722f9f30403738ecc52d9f80 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa#^NA%Cx&(BWL^R})}AhoAsQ2t z|NQ^|zn;yAfm@Nar>93E3CK-PP&!sJWAOx8_bF~O)}GjQ`Mz6IW3%>!&P|0O%6m|lSF~?Cma5mJ7_E zqHegj`Nh&lOqPqZv{OS;|Ek-a?z|UV=YG?I@#