tetris_game.c 17 KB

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