diff --git a/ReadMe.md b/ReadMe.md index d30ce4c6d52..2303be8d16a 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -97,7 +97,7 @@ The Flipper and its community wouldn't be as rich as it is without your contribu | Flappy Bird | ![Games Badge] | [by DroomOne](https://github.com/DroomOne/flipperzerofirmware/tree/dev/applications/flappy_bird) | | [![UFW Badge]](https://lab.flipper.net/apps/flappy_bird) | | Arkanoid | ![Games Badge] | by xMasterX | refactored by xMasterX, [original by gotnull](https://github.com/gotnull/flipperzerofirmwarewPlugins) | [![UFW Badge]](https://lab.flipper.net/apps/arkanoid) | | Tic Tac Toe | ![Games Badge] | by xMasterX | refactored by xMasterX, [original by gotnull](https://github.com/gotnull/flipperzerofirmwarewPlugins) | [![UFW Badge]](https://lab.flipper.net/apps/tictactoe) | -| Tetris (with fixes) | ![Games Badge] | [by jeffplang](https://github.com/jeffplang/flipperzerofirmware/tree/tetris_game/applications/tetris) | | [![UFW Badge]](https://lab.flipper.net/apps/tetris_game) | +| Tetris (with fixes) | ![Games Badge] | [by jeffplang](https://github.com/jeffplang/flipperzerofirmware/tree/tetris_game/applications/tetris) & [noiob](https://github.com/noiob) | | [![UFW Badge]](https://lab.flipper.net/apps/tetris_game) | | Minesweeper | ![Games Badge] | [by panki27](https://github.com/panki27/minesweeper) | | [![UFW Badge]](https://lab.flipper.net/apps/minesweeper) | | Heap Defence (aka Stack Attack) | ![Games Badge] | by xMasterX | ported to latest firmware by xMasterX, [original by wquinoa & Vedmein](https://github.com/Vedmein/flipperzerofirmware/tree/hd/svistoperdelki) | [![UFW Badge]](https://lab.flipper.net/apps/heap_defence) | | Game15 | ![Games Badge] | [by x27](https://github.com/x27/flipperzerogame15) | | [![UFW Badge]](https://lab.flipper.net/apps/game15) | diff --git a/base_pack/tetris_game/application.fam b/base_pack/tetris_game/application.fam index be10a33e869..0869c9841ff 100644 --- a/base_pack/tetris_game/application.fam +++ b/base_pack/tetris_game/application.fam @@ -8,7 +8,8 @@ App( order=20, fap_icon="tetris_10px.png", fap_category="Games", - fap_author="@xMasterX & @jeffplang", - fap_version="1.1", + fap_author="@xMasterX & @jeffplang & @noiob", + fap_version="1.2", fap_description="Tetris Game", + fap_icon_assets="assets" ) diff --git a/base_pack/tetris_game/assets/Pin_back_arrow_10x8.png b/base_pack/tetris_game/assets/Pin_back_arrow_10x8.png new file mode 100644 index 00000000000..3bafabd1448 Binary files /dev/null and b/base_pack/tetris_game/assets/Pin_back_arrow_10x8.png differ diff --git a/base_pack/tetris_game/img/1.png b/base_pack/tetris_game/img/1.png index 3b7450f68b7..6f370f6dc0c 100644 Binary files a/base_pack/tetris_game/img/1.png and b/base_pack/tetris_game/img/1.png differ diff --git a/base_pack/tetris_game/img/2.png b/base_pack/tetris_game/img/2.png index 24065bbba69..bd716e68510 100644 Binary files a/base_pack/tetris_game/img/2.png and b/base_pack/tetris_game/img/2.png differ diff --git a/base_pack/tetris_game/img/3.png b/base_pack/tetris_game/img/3.png index 31ea065adb4..0c9b5821479 100644 Binary files a/base_pack/tetris_game/img/3.png and b/base_pack/tetris_game/img/3.png differ diff --git a/base_pack/tetris_game/tetris_game.c b/base_pack/tetris_game/tetris_game.c index 6d972041339..e4e9543a024 100644 --- a/base_pack/tetris_game/tetris_game.c +++ b/base_pack/tetris_game/tetris_game.c @@ -7,14 +7,18 @@ #include #include #include +#include "tetris_icons.h" #define BORDER_OFFSET 1 #define MARGIN_OFFSET 3 #define BLOCK_HEIGHT 6 #define BLOCK_WIDTH 6 -#define FIELD_WIDTH 11 -#define FIELD_HEIGHT 24 +#define FIELD_WIDTH 10 +#define FIELD_HEIGHT 20 + +#define FIELD_X_OFFSET 3 +#define FIELD_Y_OFFSET 20 #define MAX_FALL_SPEED 500 #define MIN_FALL_SPEED 100 @@ -62,10 +66,12 @@ static Piece shapes[] = { {.p = {{5, 1}, {5, 0}, {6, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeO} // O }; -typedef enum { GameStatePlaying, GameStateGameOver } GameState; +typedef enum { GameStatePlaying, GameStateGameOver, GameStatePaused } GameState; typedef struct { bool playField[FIELD_HEIGHT][FIELD_WIDTH]; + bool bag[7]; + uint8_t next_id; Piece currPiece; uint16_t numLines; uint16_t fallSpeed; @@ -85,48 +91,93 @@ typedef struct { } 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, + FIELD_X_OFFSET, + FIELD_Y_OFFSET, + FIELD_X_OFFSET, + FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 7); + canvas_draw_line( + canvas, + FIELD_X_OFFSET, + FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 7, + FIELD_X_OFFSET + FIELD_WIDTH * 5 + 8, + FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 7); + canvas_draw_line( + canvas, + FIELD_X_OFFSET + FIELD_WIDTH * 5 + 8, + FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 7, + FIELD_X_OFFSET + FIELD_WIDTH * 5 + 8, + FIELD_Y_OFFSET); + + canvas_draw_line( + canvas, + FIELD_X_OFFSET + 2, + FIELD_Y_OFFSET + 0, + FIELD_X_OFFSET + 2, + FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 5); + canvas_draw_line( + canvas, + FIELD_X_OFFSET + 2, + FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 5, + FIELD_X_OFFSET + FIELD_WIDTH * 5 + 6, + FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 5); + canvas_draw_line( + canvas, + FIELD_X_OFFSET + FIELD_WIDTH * 5 + 6, + FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 5, + FIELD_X_OFFSET + FIELD_WIDTH * 5 + 6, + FIELD_Y_OFFSET); +} - 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_block(Canvas* const canvas, uint16_t xOffset, uint16_t yOffset) { + 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_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( + tetris_game_draw_block( 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); + FIELD_X_OFFSET + x * (BLOCK_WIDTH - 1), + FIELD_Y_OFFSET + y * (BLOCK_HEIGHT - 1)); } } } } +static void tetris_game_draw_next_piece(Canvas* const canvas, const TetrisState* tetris_state) { + Piece* next_piece = &shapes[tetris_state->next_id]; + for(int i = 0; i < 4; i++) { + uint8_t x = next_piece->p[i].x; + uint8_t y = next_piece->p[i].y; + + tetris_game_draw_block( + canvas, + FIELD_X_OFFSET + x * (BLOCK_WIDTH - 1) - (BLOCK_WIDTH * 4), + y * (BLOCK_HEIGHT - 1)); + } +} + static void tetris_game_render_callback(Canvas* const canvas, void* ctx) { furi_assert(ctx); const TetrisState* tetris_state = ctx; @@ -134,12 +185,13 @@ static void tetris_game_render_callback(Canvas* const canvas, void* ctx) { tetris_game_draw_border(canvas); tetris_game_draw_playfield(canvas, tetris_state); + tetris_game_draw_next_piece(canvas, tetris_state); // Show score on the game field - if(tetris_state->gameState == GameStatePlaying) { + if(tetris_state->gameState == GameStatePlaying || tetris_state->gameState == GameStatePaused) { char buffer2[6]; snprintf(buffer2, sizeof(buffer2), "%u", tetris_state->numLines); - canvas_draw_str_aligned(canvas, 58, 10, AlignRight, AlignBottom, buffer2); + canvas_draw_str_aligned(canvas, 62, 10, AlignRight, AlignBottom, buffer2); } if(tetris_state->gameState == GameStateGameOver) { @@ -158,6 +210,22 @@ static void tetris_game_render_callback(Canvas* const canvas, void* ctx) { canvas_set_font(canvas, FontSecondary); canvas_draw_str_aligned(canvas, 32, 73, AlignCenter, AlignBottom, buffer); } + + if(tetris_state->gameState == GameStatePaused) { + // 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, "Paused"); + + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 4, 73, "hold to quit"); + canvas_draw_icon(canvas, 22, 66, &I_Pin_back_arrow_10x8); + } furi_mutex_release(tetris_state->mutex); } @@ -168,13 +236,42 @@ static void tetris_game_input_callback(InputEvent* input_event, FuriMessageQueue furi_message_queue_put(event_queue, &event, FuriWaitForever); } +static uint8_t tetris_game_get_next_piece(TetrisState* tetris_state) { + bool full = true; + for(int i = 0; i < 7; i++) { + if(!tetris_state->bag[i]) { + full = false; + break; + } + } + if(full == true) { + for(int i = 0; i < 7; i++) { + tetris_state->bag[i] = false; + } + } + + int next_piece = rand() % 7; + while(tetris_state->bag[next_piece]) { + next_piece = rand() % 7; + } + tetris_state->bag[next_piece] = true; + int result = tetris_state->next_id; + tetris_state->next_id = next_piece; + + return result; +} + static void tetris_game_init_state(TetrisState* tetris_state) { tetris_state->gameState = GameStatePlaying; tetris_state->numLines = 0; tetris_state->fallSpeed = MAX_FALL_SPEED; memset(tetris_state->playField, 0, sizeof(tetris_state->playField)); + memset(tetris_state->bag, 0, sizeof(tetris_state->bag)); - memcpy(&tetris_state->currPiece, &shapes[rand() % 7], sizeof(tetris_state->currPiece)); + // init next_piece display + tetris_game_get_next_piece(tetris_state); + int next_piece_id = tetris_game_get_next_piece(tetris_state); + memcpy(&tetris_state->currPiece, &shapes[next_piece_id], sizeof(tetris_state->currPiece)); furi_timer_start(tetris_state->timer, tetris_state->fallSpeed); } @@ -294,7 +391,8 @@ static void tetris_game_update_timer_callback(FuriMessageQueue* event_queue) { static void tetris_game_process_step(TetrisState* tetris_state, Piece* newPiece, bool wasDownMove) { - if(tetris_state->gameState == GameStateGameOver) return; + if(tetris_state->gameState == GameStateGameOver || tetris_state->gameState == GameStatePaused) + return; tetris_game_remove_curr_piece(tetris_state); @@ -335,7 +433,8 @@ static void } // Check for game over - Piece* spawnedPiece = &shapes[rand() % 7]; + int next_piece_id = tetris_game_get_next_piece(tetris_state); + Piece* spawnedPiece = &shapes[next_piece_id]; if(!tetris_game_is_valid_pos(tetris_state, spawnedPiece->p)) { tetris_state->gameState = GameStateGameOver; } else { @@ -439,7 +538,21 @@ int32_t tetris_game_app() { } break; case InputKeyBack: - processing = false; + if(event.input.type == InputTypeLong) { + processing = false; + } else if(event.input.type == InputTypePress) { + switch(tetris_state->gameState) { + case GameStatePaused: + tetris_state->gameState = GameStatePlaying; + break; + case GameStatePlaying: + tetris_state->gameState = GameStatePaused; + break; + + default: + break; + } + } break; default: break;