tetris_game.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #include <furi.h>
  2. #include <gui/gui.h>
  3. #include <input/input.h>
  4. #include <stdlib.h>
  5. #include <stdbool.h>
  6. #include <string.h>
  7. #include <furi_hal_resources.h>
  8. #include <furi_hal_gpio.h>
  9. #include <dolphin/dolphin.h>
  10. #include "tetris_icons.h"
  11. #include <assets_icons.h>
  12. #define BORDER_OFFSET 1
  13. #define MARGIN_OFFSET 3
  14. #define BLOCK_HEIGHT 6
  15. #define BLOCK_WIDTH 6
  16. #define FIELD_WIDTH 10
  17. #define FIELD_HEIGHT 20
  18. #define FIELD_X_OFFSET 3
  19. #define FIELD_Y_OFFSET 20
  20. #define MAX_FALL_SPEED 500
  21. #define MIN_FALL_SPEED 100
  22. typedef struct Point {
  23. // Also used for offset data, which is sometimes negative
  24. int8_t x, y;
  25. } Point;
  26. // Rotation logic taken from
  27. // https://www.youtube.com/watch?v=yIpk5TJ_uaI
  28. typedef enum {
  29. OffsetTypeCommon,
  30. OffsetTypeI,
  31. OffsetTypeO
  32. } OffsetType;
  33. // Since we only support rotating clockwise, these are actual translation values,
  34. // not values to be subtracted to get translation values
  35. static const Point rotOffsetTranslation[3][4][5] = {
  36. {{{0, 0}, {-1, 0}, {-1, -1}, {0, 2}, {-1, 2}},
  37. {{0, 0}, {1, 0}, {1, 1}, {0, -2}, {1, -2}},
  38. {{0, 0}, {1, 0}, {1, -1}, {0, 2}, {1, 2}},
  39. {{0, 0}, {-1, 0}, {-1, 1}, {0, -2}, {-1, -2}}},
  40. {{{1, 0}, {-1, 0}, {2, 0}, {-1, 1}, {2, -2}},
  41. {{0, 1}, {-1, 1}, {2, 1}, {-1, -1}, {2, 2}},
  42. {{-1, 0}, {1, 0}, {-2, 0}, {1, -1}, {-2, 2}},
  43. {{0, -1}, {1, -1}, {-2, -1}, {1, 1}, {-2, -2}}},
  44. {{{0, -1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
  45. {{1, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
  46. {{0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}},
  47. {{-1, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}}};
  48. typedef struct {
  49. Point p[4];
  50. uint8_t rotIdx;
  51. OffsetType offsetType;
  52. } Piece;
  53. // Shapes @ spawn locations, rotation point first
  54. static Piece shapes[] = {
  55. {.p = {{5, 1}, {4, 0}, {5, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // Z
  56. {.p = {{5, 1}, {4, 1}, {5, 0}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // S
  57. {.p = {{5, 1}, {4, 1}, {6, 1}, {6, 0}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // L
  58. {.p = {{5, 1}, {4, 0}, {4, 1}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // J
  59. {.p = {{5, 1}, {4, 1}, {5, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeCommon}, // T
  60. {.p = {{5, 1}, {4, 1}, {6, 1}, {7, 1}}, .rotIdx = 0, .offsetType = OffsetTypeI}, // I
  61. {.p = {{5, 1}, {5, 0}, {6, 0}, {6, 1}}, .rotIdx = 0, .offsetType = OffsetTypeO} // O
  62. };
  63. typedef enum {
  64. GameStatePlaying,
  65. GameStateGameOver,
  66. GameStatePaused
  67. } GameState;
  68. typedef struct {
  69. bool playField[FIELD_HEIGHT][FIELD_WIDTH];
  70. bool bag[7];
  71. uint8_t next_id;
  72. Piece currPiece;
  73. uint16_t numLines;
  74. uint16_t fallSpeed;
  75. bool hardDropping;
  76. GameState gameState;
  77. FuriTimer* timer;
  78. FuriMutex* mutex;
  79. } TetrisState;
  80. typedef enum {
  81. EventTypeTick,
  82. EventTypeKey,
  83. } EventType;
  84. typedef struct {
  85. EventType type;
  86. InputEvent input;
  87. } TetrisEvent;
  88. static void tetris_game_draw_border(Canvas* const canvas) {
  89. canvas_draw_line(
  90. canvas,
  91. FIELD_X_OFFSET,
  92. FIELD_Y_OFFSET,
  93. FIELD_X_OFFSET,
  94. FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 7);
  95. canvas_draw_line(
  96. canvas,
  97. FIELD_X_OFFSET,
  98. FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 7,
  99. FIELD_X_OFFSET + FIELD_WIDTH * 5 + 8,
  100. FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 7);
  101. canvas_draw_line(
  102. canvas,
  103. FIELD_X_OFFSET + FIELD_WIDTH * 5 + 8,
  104. FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 7,
  105. FIELD_X_OFFSET + FIELD_WIDTH * 5 + 8,
  106. FIELD_Y_OFFSET);
  107. canvas_draw_line(
  108. canvas,
  109. FIELD_X_OFFSET + 2,
  110. FIELD_Y_OFFSET + 0,
  111. FIELD_X_OFFSET + 2,
  112. FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 5);
  113. canvas_draw_line(
  114. canvas,
  115. FIELD_X_OFFSET + 2,
  116. FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 5,
  117. FIELD_X_OFFSET + FIELD_WIDTH * 5 + 6,
  118. FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 5);
  119. canvas_draw_line(
  120. canvas,
  121. FIELD_X_OFFSET + FIELD_WIDTH * 5 + 6,
  122. FIELD_Y_OFFSET + FIELD_HEIGHT * 5 + 5,
  123. FIELD_X_OFFSET + FIELD_WIDTH * 5 + 6,
  124. FIELD_Y_OFFSET);
  125. }
  126. static void tetris_game_draw_block(Canvas* const canvas, uint16_t xOffset, uint16_t yOffset) {
  127. canvas_draw_rframe(
  128. canvas,
  129. BORDER_OFFSET + MARGIN_OFFSET + xOffset,
  130. BORDER_OFFSET + MARGIN_OFFSET + yOffset - 1,
  131. BLOCK_WIDTH,
  132. BLOCK_HEIGHT,
  133. 1);
  134. canvas_draw_dot(
  135. canvas,
  136. BORDER_OFFSET + MARGIN_OFFSET + xOffset + 2,
  137. BORDER_OFFSET + MARGIN_OFFSET + yOffset + 1);
  138. canvas_draw_dot(
  139. canvas,
  140. BORDER_OFFSET + MARGIN_OFFSET + xOffset + 3,
  141. BORDER_OFFSET + MARGIN_OFFSET + yOffset + 1);
  142. canvas_draw_dot(
  143. canvas,
  144. BORDER_OFFSET + MARGIN_OFFSET + xOffset + 2,
  145. BORDER_OFFSET + MARGIN_OFFSET + yOffset + 2);
  146. }
  147. static void tetris_game_draw_playfield(Canvas* const canvas, const TetrisState* tetris_state) {
  148. for(int y = 0; y < FIELD_HEIGHT; y++) {
  149. for(int x = 0; x < FIELD_WIDTH; x++) {
  150. if(tetris_state->playField[y][x]) {
  151. tetris_game_draw_block(
  152. canvas,
  153. FIELD_X_OFFSET + x * (BLOCK_WIDTH - 1),
  154. FIELD_Y_OFFSET + y * (BLOCK_HEIGHT - 1));
  155. }
  156. }
  157. }
  158. }
  159. static void tetris_game_draw_next_piece(Canvas* const canvas, const TetrisState* tetris_state) {
  160. Piece* next_piece = &shapes[tetris_state->next_id];
  161. for(int i = 0; i < 4; i++) {
  162. uint8_t x = next_piece->p[i].x;
  163. uint8_t y = next_piece->p[i].y;
  164. tetris_game_draw_block(
  165. canvas,
  166. FIELD_X_OFFSET + x * (BLOCK_WIDTH - 1) - (BLOCK_WIDTH * 4),
  167. y * (BLOCK_HEIGHT - 1));
  168. }
  169. }
  170. static void tetris_game_render_callback(Canvas* const canvas, void* ctx) {
  171. furi_assert(ctx);
  172. const TetrisState* tetris_state = ctx;
  173. furi_mutex_acquire(tetris_state->mutex, FuriWaitForever);
  174. tetris_game_draw_border(canvas);
  175. tetris_game_draw_playfield(canvas, tetris_state);
  176. tetris_game_draw_next_piece(canvas, tetris_state);
  177. // Show score on the game field
  178. if(tetris_state->gameState == GameStatePlaying || tetris_state->gameState == GameStatePaused) {
  179. char buffer2[6];
  180. snprintf(buffer2, sizeof(buffer2), "%u", tetris_state->numLines);
  181. canvas_draw_str_aligned(canvas, 62, 10, AlignRight, AlignBottom, buffer2);
  182. }
  183. if(tetris_state->gameState == GameStateGameOver) {
  184. // 128 x 64
  185. canvas_set_color(canvas, ColorWhite);
  186. canvas_draw_box(canvas, 1, 52, 62, 24);
  187. canvas_set_color(canvas, ColorBlack);
  188. canvas_draw_frame(canvas, 1, 52, 62, 24);
  189. canvas_set_font(canvas, FontPrimary);
  190. canvas_draw_str(canvas, 4, 63, "Game Over");
  191. char buffer[13];
  192. snprintf(buffer, sizeof(buffer), "Lines: %u", tetris_state->numLines);
  193. canvas_set_font(canvas, FontSecondary);
  194. canvas_draw_str_aligned(canvas, 32, 73, AlignCenter, AlignBottom, buffer);
  195. }
  196. if(tetris_state->gameState == GameStatePaused) {
  197. // 128 x 64
  198. canvas_set_color(canvas, ColorWhite);
  199. canvas_draw_box(canvas, 1, 52, 62, 24);
  200. canvas_set_color(canvas, ColorBlack);
  201. canvas_draw_frame(canvas, 1, 52, 62, 24);
  202. canvas_set_font(canvas, FontPrimary);
  203. canvas_draw_str(canvas, 4, 63, "Paused");
  204. canvas_set_font(canvas, FontSecondary);
  205. canvas_draw_str(canvas, 4, 73, "hold to quit");
  206. canvas_draw_icon(canvas, 22, 66, &I_Pin_back_arrow_10x8);
  207. }
  208. furi_mutex_release(tetris_state->mutex);
  209. }
  210. static void tetris_game_input_callback(InputEvent* input_event, void* ctx) {
  211. furi_assert(ctx);
  212. FuriMessageQueue* event_queue = ctx;
  213. TetrisEvent event = {.type = EventTypeKey, .input = *input_event};
  214. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  215. }
  216. static uint8_t tetris_game_get_next_piece(TetrisState* tetris_state) {
  217. bool full = true;
  218. for(int i = 0; i < 7; i++) {
  219. if(!tetris_state->bag[i]) {
  220. full = false;
  221. break;
  222. }
  223. }
  224. if(full == true) {
  225. for(int i = 0; i < 7; i++) {
  226. tetris_state->bag[i] = false;
  227. }
  228. }
  229. int next_piece = rand() % 7;
  230. while(tetris_state->bag[next_piece]) {
  231. next_piece = rand() % 7;
  232. }
  233. tetris_state->bag[next_piece] = true;
  234. int result = tetris_state->next_id;
  235. tetris_state->next_id = next_piece;
  236. return result;
  237. }
  238. static void tetris_game_init_state(TetrisState* tetris_state) {
  239. tetris_state->gameState = GameStatePlaying;
  240. tetris_state->numLines = 0;
  241. tetris_state->fallSpeed = MAX_FALL_SPEED;
  242. tetris_state->hardDropping = false;
  243. memset(tetris_state->playField, 0, sizeof(tetris_state->playField));
  244. memset(tetris_state->bag, 0, sizeof(tetris_state->bag));
  245. // init next_piece display
  246. tetris_game_get_next_piece(tetris_state);
  247. int next_piece_id = tetris_game_get_next_piece(tetris_state);
  248. memcpy(&tetris_state->currPiece, &shapes[next_piece_id], sizeof(tetris_state->currPiece));
  249. furi_timer_start(tetris_state->timer, tetris_state->fallSpeed);
  250. }
  251. static void tetris_game_remove_curr_piece(TetrisState* tetris_state) {
  252. for(int i = 0; i < 4; i++) {
  253. uint8_t x = tetris_state->currPiece.p[i].x;
  254. uint8_t y = tetris_state->currPiece.p[i].y;
  255. tetris_state->playField[y][x] = false;
  256. }
  257. }
  258. static void tetris_game_render_curr_piece(TetrisState* tetris_state) {
  259. for(int i = 0; i < 4; i++) {
  260. uint8_t x = tetris_state->currPiece.p[i].x;
  261. uint8_t y = tetris_state->currPiece.p[i].y;
  262. tetris_state->playField[y][x] = true;
  263. }
  264. }
  265. static void tetris_game_rotate_shape(Point currShape[], Point newShape[]) {
  266. // Copy shape data
  267. for(int i = 0; i < 4; i++) {
  268. newShape[i] = currShape[i];
  269. }
  270. for(int i = 1; i < 4; i++) {
  271. int8_t relX = currShape[i].x - currShape[0].x;
  272. int8_t relY = currShape[i].y - currShape[0].y;
  273. // Matrix rotation thing
  274. int8_t newRelX = (relX * 0) + (relY * -1);
  275. int8_t newRelY = (relX * 1) + (relY * 0);
  276. newShape[i].x = currShape[0].x + newRelX;
  277. newShape[i].y = currShape[0].y + newRelY;
  278. }
  279. }
  280. static void tetris_game_apply_kick(Point points[], Point kick) {
  281. for(int i = 0; i < 4; i++) {
  282. points[i].x += kick.x;
  283. points[i].y += kick.y;
  284. }
  285. }
  286. static bool tetris_game_is_valid_pos(TetrisState* tetris_state, Point* shape) {
  287. for(int i = 0; i < 4; i++) {
  288. if(shape[i].x < 0 || shape[i].x > (FIELD_WIDTH - 1) ||
  289. tetris_state->playField[shape[i].y][shape[i].x] == true) {
  290. return false;
  291. }
  292. }
  293. return true;
  294. }
  295. static void tetris_game_try_rotation(TetrisState* tetris_state, Piece* newPiece) {
  296. uint8_t currRotIdx = tetris_state->currPiece.rotIdx;
  297. Point* rotatedShape = malloc(sizeof(Point) * 4);
  298. Point* kickedShape = malloc(sizeof(Point) * 4);
  299. memcpy(rotatedShape, &tetris_state->currPiece.p, sizeof(tetris_state->currPiece.p));
  300. tetris_game_rotate_shape(tetris_state->currPiece.p, rotatedShape);
  301. for(int i = 0; i < 5; i++) {
  302. memcpy(kickedShape, rotatedShape, (sizeof(Point) * 4));
  303. tetris_game_apply_kick(
  304. kickedShape, rotOffsetTranslation[newPiece->offsetType][currRotIdx][i]);
  305. if(tetris_game_is_valid_pos(tetris_state, kickedShape)) {
  306. memcpy(&newPiece->p, kickedShape, sizeof(newPiece->p));
  307. newPiece->rotIdx = (newPiece->rotIdx + 1) % 4;
  308. break;
  309. }
  310. }
  311. free(rotatedShape);
  312. free(kickedShape);
  313. }
  314. static bool tetris_game_row_is_line(bool row[]) {
  315. for(int i = 0; i < FIELD_WIDTH; i++) {
  316. if(row[i] == false) return false;
  317. }
  318. return true;
  319. }
  320. static void
  321. tetris_game_check_for_lines(TetrisState* tetris_state, uint8_t* lines, uint8_t* numLines) {
  322. for(int i = 0; i < FIELD_HEIGHT; i++) {
  323. if(tetris_game_row_is_line(tetris_state->playField[i])) {
  324. *(lines++) = i;
  325. *numLines += 1;
  326. }
  327. }
  328. }
  329. static bool tetris_game_piece_at_bottom(TetrisState* tetris_state, Piece* newPiece) {
  330. for(int i = 0; i < 4; i++) {
  331. Point* pos = (Point*)&newPiece->p;
  332. if(pos[i].y >= FIELD_HEIGHT || tetris_state->playField[pos[i].y][pos[i].x] == true) {
  333. return true;
  334. }
  335. }
  336. return false;
  337. }
  338. static void tetris_game_update_timer_callback(void* ctx) {
  339. furi_assert(ctx);
  340. FuriMessageQueue* event_queue = ctx;
  341. TetrisEvent event = {.type = EventTypeTick};
  342. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  343. }
  344. static void
  345. tetris_game_process_step(TetrisState* tetris_state, Piece* newPiece, bool wasDownMove) {
  346. if(tetris_state->gameState == GameStateGameOver || tetris_state->gameState == GameStatePaused)
  347. return;
  348. tetris_game_remove_curr_piece(tetris_state);
  349. if(wasDownMove) {
  350. if(tetris_game_piece_at_bottom(tetris_state, newPiece)) {
  351. furi_timer_stop(tetris_state->timer);
  352. tetris_state->hardDropping = false;
  353. tetris_game_render_curr_piece(tetris_state);
  354. uint8_t numLines = 0;
  355. uint8_t lines[] = {0, 0, 0, 0};
  356. uint16_t nextFallSpeed;
  357. tetris_game_check_for_lines(tetris_state, lines, &numLines);
  358. if(numLines > 0) {
  359. for(int i = 0; i < numLines; i++) {
  360. // zero out row
  361. for(int j = 0; j < FIELD_WIDTH; j++) {
  362. tetris_state->playField[lines[i]][j] = false;
  363. }
  364. // move all above rows down
  365. for(int k = lines[i]; k >= 0; k--) {
  366. for(int m = 0; m < FIELD_WIDTH; m++) {
  367. tetris_state->playField[k][m] =
  368. (k == 0) ? false : tetris_state->playField[k - 1][m];
  369. }
  370. }
  371. }
  372. uint16_t oldNumLines = tetris_state->numLines;
  373. tetris_state->numLines += numLines;
  374. if((oldNumLines / 10) % 10 != (tetris_state->numLines / 10) % 10) {
  375. nextFallSpeed =
  376. tetris_state->fallSpeed - (100 / (tetris_state->numLines / 10));
  377. if(nextFallSpeed >= MIN_FALL_SPEED) {
  378. tetris_state->fallSpeed = nextFallSpeed;
  379. }
  380. }
  381. }
  382. // Check for game over
  383. int next_piece_id = tetris_game_get_next_piece(tetris_state);
  384. Piece* spawnedPiece = &shapes[next_piece_id];
  385. if(!tetris_game_is_valid_pos(tetris_state, spawnedPiece->p)) {
  386. tetris_state->gameState = GameStateGameOver;
  387. } else {
  388. memcpy(&tetris_state->currPiece, spawnedPiece, sizeof(tetris_state->currPiece));
  389. furi_timer_start(tetris_state->timer, tetris_state->fallSpeed);
  390. }
  391. }
  392. }
  393. if(tetris_game_is_valid_pos(tetris_state, newPiece->p)) {
  394. memcpy(&tetris_state->currPiece, newPiece, sizeof(tetris_state->currPiece));
  395. }
  396. tetris_game_render_curr_piece(tetris_state);
  397. }
  398. int32_t tetris_game_app() {
  399. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TetrisEvent));
  400. TetrisState* tetris_state = malloc(sizeof(TetrisState));
  401. tetris_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  402. if(!tetris_state->mutex) {
  403. FURI_LOG_E("TetrisGame", "cannot create mutex\r\n");
  404. furi_message_queue_free(event_queue);
  405. free(tetris_state);
  406. return 255;
  407. }
  408. // Not doing this eventually causes issues with TimerSvc due to not sleeping/yielding enough in this task
  409. furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
  410. ViewPort* view_port = view_port_alloc();
  411. view_port_set_orientation(view_port, ViewPortOrientationVertical);
  412. view_port_draw_callback_set(view_port, tetris_game_render_callback, tetris_state);
  413. view_port_input_callback_set(view_port, tetris_game_input_callback, event_queue);
  414. // Open GUI and register view_port
  415. Gui* gui = furi_record_open(RECORD_GUI);
  416. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  417. tetris_state->timer =
  418. furi_timer_alloc(tetris_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
  419. tetris_game_init_state(tetris_state);
  420. TetrisEvent event;
  421. Piece* newPiece = malloc(sizeof(Piece));
  422. uint8_t downRepeatCounter = 0;
  423. // Call dolphin deed on game start
  424. dolphin_deed(DolphinDeedPluginGameStart);
  425. for(bool processing = true; processing;) {
  426. // This 10U implicitly sets the game loop speed. downRepeatCounter relies on this value
  427. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 10U);
  428. furi_mutex_acquire(tetris_state->mutex, FuriWaitForever);
  429. memcpy(newPiece, &tetris_state->currPiece, sizeof(tetris_state->currPiece));
  430. bool wasDownMove = false;
  431. if(!furi_hal_gpio_read(&gpio_button_right)) {
  432. if(downRepeatCounter > 3) {
  433. for(int i = 0; i < 4; i++) {
  434. newPiece->p[i].y += 1;
  435. }
  436. downRepeatCounter = 0;
  437. wasDownMove = true;
  438. } else {
  439. downRepeatCounter++;
  440. }
  441. }
  442. if(tetris_state->hardDropping) {
  443. for(int i = 0; i < 4; i++) {
  444. newPiece->p[i].y += 1;
  445. }
  446. wasDownMove = true;
  447. }
  448. if(event_status == FuriStatusOk) {
  449. if(event.type == EventTypeKey) {
  450. if(event.input.type == InputTypePress || event.input.type == InputTypeLong ||
  451. event.input.type == InputTypeRepeat) {
  452. switch(event.input.key) {
  453. case InputKeyUp:
  454. tetris_state->hardDropping = true;
  455. break;
  456. case InputKeyDown:
  457. break;
  458. case InputKeyRight:
  459. for(int i = 0; i < 4; i++) {
  460. newPiece->p[i].x += 1;
  461. }
  462. break;
  463. case InputKeyLeft:
  464. for(int i = 0; i < 4; i++) {
  465. newPiece->p[i].x -= 1;
  466. }
  467. break;
  468. case InputKeyOk:
  469. if(tetris_state->gameState == GameStatePlaying) {
  470. tetris_game_remove_curr_piece(tetris_state);
  471. tetris_game_try_rotation(tetris_state, newPiece);
  472. tetris_game_render_curr_piece(tetris_state);
  473. } else {
  474. tetris_game_init_state(tetris_state);
  475. }
  476. break;
  477. case InputKeyBack:
  478. if(event.input.type == InputTypeLong) {
  479. processing = false;
  480. } else if(event.input.type == InputTypePress) {
  481. switch(tetris_state->gameState) {
  482. case GameStatePaused:
  483. tetris_state->gameState = GameStatePlaying;
  484. break;
  485. case GameStatePlaying:
  486. tetris_state->gameState = GameStatePaused;
  487. break;
  488. default:
  489. break;
  490. }
  491. }
  492. break;
  493. default:
  494. break;
  495. }
  496. }
  497. } else if(event.type == EventTypeTick) {
  498. // TODO: This is inverted. it returns true when the button is not pressed.
  499. // see macro in input.c and do that
  500. if(furi_hal_gpio_read(&gpio_button_right)) {
  501. for(int i = 0; i < 4; i++) {
  502. newPiece->p[i].y += 1;
  503. }
  504. wasDownMove = true;
  505. }
  506. }
  507. }
  508. tetris_game_process_step(tetris_state, newPiece, wasDownMove);
  509. furi_mutex_release(tetris_state->mutex);
  510. view_port_update(view_port);
  511. }
  512. furi_timer_free(tetris_state->timer);
  513. view_port_enabled_set(view_port, false);
  514. gui_remove_view_port(gui, view_port);
  515. furi_record_close(RECORD_GUI);
  516. view_port_free(view_port);
  517. furi_message_queue_free(event_queue);
  518. furi_mutex_free(tetris_state->mutex);
  519. furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
  520. free(newPiece);
  521. free(tetris_state);
  522. return 0;
  523. }