race.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. #include <stdio.h>
  2. #include <furi.h>
  3. #include <gui/gui.h>
  4. #include <input/input.h>
  5. #include <stdlib.h>
  6. #include <stdbool.h>
  7. #include <string.h>
  8. #include <furi_hal_resources.h>
  9. #include <furi_hal_gpio.h>
  10. #include <notification/notification_messages.h>
  11. #define BORDER_OFFSET 1
  12. #define MARGIN_OFFSET 3
  13. #define BLOCK_HEIGHT 6
  14. #define BLOCK_WIDTH 6
  15. #define FIELD_WIDTH 11
  16. #define FIELD_HEIGHT 24
  17. #define PARALLEL_OBSTACLES 3
  18. typedef enum { GameStatePlaying, GameStateGameOver } GameState;
  19. typedef struct Point {
  20. // Also used for offset data, which is sometimes negative
  21. int8_t x, y;
  22. } Point;
  23. typedef enum { CarObstacle } ObstacleType;
  24. typedef struct Obstacle {
  25. ObstacleType type;
  26. Point position;
  27. bool isAlive;
  28. } Obstacle;
  29. typedef struct {
  30. bool playField[FIELD_HEIGHT][FIELD_WIDTH];
  31. uint16_t score;
  32. Obstacle obstacles[PARALLEL_OBSTACLES];
  33. int8_t roadStart;
  34. int8_t level;
  35. Point headPosition;
  36. uint16_t motionSpeed;
  37. GameState gameState;
  38. FuriTimer* timer;
  39. } RaceState;
  40. typedef enum {
  41. EventTypeTick,
  42. EventTypeKey,
  43. } EventType;
  44. typedef struct {
  45. EventType type;
  46. InputEvent input;
  47. } RaceGameEvent;
  48. static void race_game_draw_playfield(Canvas* const canvas, RaceState* race_state) {
  49. // Playfield: 11 x 24
  50. for(int y = 0; y < FIELD_HEIGHT; y++) {
  51. for(int x = 0; x < FIELD_WIDTH; x++) {
  52. if(race_state->playField[y][x]) {
  53. uint16_t xOffset = x * 5;
  54. uint16_t yOffset = y * 5;
  55. canvas_draw_rframe(
  56. canvas,
  57. BORDER_OFFSET + MARGIN_OFFSET + xOffset,
  58. BORDER_OFFSET + MARGIN_OFFSET + yOffset - 1,
  59. BLOCK_WIDTH,
  60. BLOCK_HEIGHT,
  61. 1);
  62. canvas_draw_dot(
  63. canvas,
  64. BORDER_OFFSET + MARGIN_OFFSET + xOffset + 2,
  65. BORDER_OFFSET + MARGIN_OFFSET + yOffset + 1);
  66. canvas_draw_dot(
  67. canvas,
  68. BORDER_OFFSET + MARGIN_OFFSET + xOffset + 3,
  69. BORDER_OFFSET + MARGIN_OFFSET + yOffset + 1);
  70. canvas_draw_dot(
  71. canvas,
  72. BORDER_OFFSET + MARGIN_OFFSET + xOffset + 2,
  73. BORDER_OFFSET + MARGIN_OFFSET + yOffset + 2);
  74. }
  75. }
  76. }
  77. }
  78. static void race_game_draw_border(Canvas* canvas) {
  79. canvas_draw_line(canvas, 0, 0, 0, 127);
  80. canvas_draw_line(canvas, 0, 127, 63, 127);
  81. canvas_draw_line(canvas, 63, 127, 63, 0);
  82. canvas_draw_line(canvas, 2, 0, 2, 125);
  83. canvas_draw_line(canvas, 2, 125, 61, 125);
  84. canvas_draw_line(canvas, 61, 125, 61, 0);
  85. }
  86. static void race_game_init_road(RaceState* race_state) {
  87. int leftRoad = race_state->roadStart;
  88. int rightRoad = race_state->roadStart + 1;
  89. if(rightRoad == 4) rightRoad = 0;
  90. for(int y = 0; y < FIELD_HEIGHT; y++) {
  91. leftRoad++;
  92. rightRoad++;
  93. if(leftRoad < 4) {
  94. race_state->playField[y][0] = true;
  95. }
  96. if(rightRoad < 4) {
  97. race_state->playField[y][10] = true;
  98. }
  99. if(rightRoad == 4) rightRoad = 0;
  100. if(leftRoad == 4) leftRoad = 0;
  101. }
  102. }
  103. static void draw_callback(Canvas* canvas, void* ctx) {
  104. RaceState* race_state = ctx;
  105. if(race_state == NULL) {
  106. FURI_LOG_E("RaceGame", "state is null");
  107. return;
  108. }
  109. canvas_clear(canvas);
  110. race_game_draw_border(canvas);
  111. race_game_draw_playfield(canvas, race_state);
  112. /*
  113. // output player score, looks not good
  114. if(race_state->gameState == GameStatePlaying) {
  115. char buffer2[6];
  116. snprintf(buffer2, sizeof(buffer2), "%u", race_state->score);
  117. canvas_draw_str_aligned(canvas, 48, 10, AlignRight, AlignBottom, buffer2);
  118. }
  119. else */
  120. if(race_state->gameState == GameStateGameOver) {
  121. // 128 x 64
  122. canvas_set_color(canvas, ColorWhite);
  123. canvas_draw_box(canvas, 1, 52, 62, 24);
  124. canvas_set_color(canvas, ColorBlack);
  125. canvas_draw_frame(canvas, 1, 52, 62, 24);
  126. canvas_set_font(canvas, FontPrimary);
  127. canvas_draw_str(canvas, 4, 63, "Game Over");
  128. char buffer[13];
  129. snprintf(buffer, sizeof(buffer), "Score: %u", race_state->score);
  130. canvas_set_font(canvas, FontSecondary);
  131. canvas_draw_str_aligned(canvas, 32, 73, AlignCenter, AlignBottom, buffer);
  132. }
  133. }
  134. static void input_callback(InputEvent* input_event, void* ctx) {
  135. furi_assert(ctx);
  136. FuriMessageQueue* event_queue = ctx;
  137. RaceGameEvent event = {.type = EventTypeKey, .input = *input_event};
  138. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  139. }
  140. static void timer_callback(void* ctx) {
  141. furi_assert(ctx);
  142. FuriMessageQueue* event_queue = ctx;
  143. RaceGameEvent event = {.type = EventTypeTick};
  144. furi_message_queue_put(event_queue, &event, 0);
  145. }
  146. static void race_game_init_state(RaceState* race_state) {
  147. race_state->gameState = GameStatePlaying;
  148. race_state->score = 0;
  149. race_state->level = 0;
  150. race_state->roadStart = 0;
  151. race_state->motionSpeed = 500;
  152. Point p = {.x = 5, .y = 20};
  153. race_state->headPosition = p;
  154. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  155. Obstacle obstacle = {.type = CarObstacle, .isAlive = false, .position = {.x = 0, .y = 0}};
  156. race_state->obstacles[i] = obstacle;
  157. }
  158. memset(race_state->playField, 0, sizeof(race_state->playField));
  159. furi_timer_start(race_state->timer, race_state->motionSpeed);
  160. }
  161. static void race_game_draw_car(RaceState* race_state, Point p, bool changeState) {
  162. static Point pointsToCheck[] = {{0, 0}, {0, 1}, {-1, 1}, {1, 1}, {0, 2}, {-1, 3}, {1, 3}};
  163. for(int i = 0; i < 7; i++) {
  164. if(p.x + pointsToCheck[i].x > -1 && p.y + pointsToCheck[i].y > -1 &&
  165. p.x + pointsToCheck[i].x < FIELD_WIDTH && p.y + pointsToCheck[i].y < FIELD_HEIGHT) {
  166. if(changeState &&
  167. race_state->playField[p.y + pointsToCheck[i].y][p.x + pointsToCheck[i].x]) {
  168. race_state->gameState = GameStateGameOver;
  169. }
  170. race_state->playField[p.y + pointsToCheck[i].y][p.x + pointsToCheck[i].x] = true;
  171. }
  172. }
  173. }
  174. static void race_game_move_obstacles(RaceState* race_state) {
  175. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  176. if(race_state->obstacles[i].isAlive) {
  177. race_state->obstacles[i].position.y++;
  178. if(race_state->obstacles[i].position.y > (FIELD_HEIGHT - 1))
  179. race_state->obstacles[i].isAlive = false;
  180. }
  181. }
  182. }
  183. static void race_game_spawn_obstacles(RaceState* race_state) {
  184. if(race_state->score % 90 == 0) {
  185. int aliveObjects = 0;
  186. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  187. if(race_state->obstacles[i].isAlive) {
  188. aliveObjects++;
  189. }
  190. }
  191. if(aliveObjects < PARALLEL_OBSTACLES) {
  192. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  193. if(!race_state->obstacles[i].isAlive) {
  194. race_state->obstacles[i].isAlive = true;
  195. race_state->obstacles[i].position.y = -4;
  196. race_state->obstacles[i].position.x = (rand() % 3) * 3 + 2;
  197. break;
  198. }
  199. }
  200. }
  201. }
  202. }
  203. static void race_game_process_step(RaceState* race_state, bool moveRoad) {
  204. if(race_state->gameState == GameStateGameOver) return;
  205. // calculate field boundaries
  206. if(race_state->headPosition.x < 2) race_state->headPosition.x = 2;
  207. if(race_state->headPosition.x > (FIELD_WIDTH - 3))
  208. race_state->headPosition.x = (FIELD_WIDTH - 3);
  209. if(race_state->headPosition.y < 0) race_state->headPosition.y = 0;
  210. if(race_state->headPosition.y > (FIELD_HEIGHT - 4))
  211. race_state->headPosition.y = (FIELD_HEIGHT - 4);
  212. for(int y = 0; y < FIELD_HEIGHT; y++) {
  213. for(int x = 0; x < FIELD_WIDTH; x++) {
  214. race_state->playField[y][x] = false;
  215. }
  216. }
  217. if(moveRoad) {
  218. race_state->score += 10;
  219. race_state->roadStart++;
  220. if(race_state->roadStart == 4) race_state->roadStart = 0;
  221. race_game_spawn_obstacles(race_state);
  222. race_game_move_obstacles(race_state);
  223. if(race_state->score % 500 == 0) {
  224. if(race_state->level < 9) {
  225. race_state->level++;
  226. race_state->motionSpeed = race_state->motionSpeed - 50;
  227. furi_timer_stop(race_state->timer);
  228. furi_timer_start(race_state->timer, race_state->motionSpeed);
  229. }
  230. }
  231. }
  232. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  233. if(race_state->obstacles[i].isAlive) {
  234. race_game_draw_car(race_state, race_state->obstacles[i].position, false);
  235. }
  236. }
  237. race_game_draw_car(race_state, race_state->headPosition, true);
  238. race_game_init_road(race_state);
  239. }
  240. int32_t race_app(void* p) {
  241. UNUSED(p);
  242. srand(DWT->CYCCNT);
  243. // current custom event element
  244. RaceGameEvent event;
  245. RaceState* race_state = malloc(sizeof(RaceState));
  246. FuriMutex* state_mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
  247. // Queue on 8 events
  248. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(RaceGameEvent));
  249. race_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
  250. race_game_init_state(race_state);
  251. // Creating viewport
  252. ViewPort* view_port = view_port_alloc();
  253. // Set viewpoint orientation to vertical
  254. view_port_set_orientation(view_port, ViewPortOrientationVertical);
  255. // creating draw callback without context
  256. // view_port_draw_callback_set(view_port, draw_callback, &state_mutex);
  257. view_port_draw_callback_set(view_port, draw_callback, race_state);
  258. // setting up and all input events go to our event queue
  259. view_port_input_callback_set(view_port, input_callback, event_queue);
  260. // creating gui
  261. Gui* gui = furi_record_open(RECORD_GUI);
  262. // adding our view port to gui in full screen mode
  263. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  264. // Бесконечный цикл обработки очереди событий
  265. for(bool processing = true; processing;) {
  266. furi_mutex_acquire(state_mutex, 1000);
  267. // RaceState* race_state = (RaceState*)furi_mutex_block(&state_mutex, 1000);
  268. // Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста)
  269. // и проверяем, что у нас получилось это сделать
  270. furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk);
  271. bool moveRoad = false;
  272. if(event.type == EventTypeKey) {
  273. // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения
  274. switch(event.input.key) {
  275. case InputKeyBack:
  276. processing = false;
  277. break;
  278. case InputKeyOk:
  279. if(race_state->gameState == GameStateGameOver) {
  280. race_game_init_state(race_state);
  281. }
  282. break;
  283. case InputKeyUp:
  284. race_state->headPosition.y -= 1;
  285. break;
  286. case InputKeyDown:
  287. race_state->headPosition.y += 1;
  288. break;
  289. case InputKeyRight:
  290. race_state->headPosition.x += 1;
  291. break;
  292. case InputKeyLeft:
  293. race_state->headPosition.x -= 1;
  294. break;
  295. default:
  296. break;
  297. }
  298. // Наше событие — это сработавший таймер
  299. } else if(event.type == EventTypeTick) {
  300. //Move road and obstacles with timer ticks
  301. moveRoad = true;
  302. }
  303. race_game_process_step(race_state, moveRoad);
  304. furi_mutex_release(state_mutex);
  305. view_port_update(view_port);
  306. }
  307. // clearing everything on game exit
  308. // clear game timer
  309. furi_timer_free(race_state->timer);
  310. // clear message queue
  311. furi_message_queue_free(event_queue);
  312. // clearing ui
  313. gui_remove_view_port(gui, view_port);
  314. view_port_free(view_port);
  315. furi_record_close(RECORD_GUI);
  316. return 0;
  317. }