race.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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(FuriMessageQueue* event_queue) {
  141. furi_assert(event_queue);
  142. RaceGameEvent event = {.type = EventTypeTick};
  143. furi_message_queue_put(event_queue, &event, 0);
  144. }
  145. static void race_game_init_state(RaceState* race_state) {
  146. race_state->gameState = GameStatePlaying;
  147. race_state->score = 0;
  148. race_state->level = 0;
  149. race_state->roadStart = 0;
  150. race_state->motionSpeed = 500;
  151. Point p = {.x = 5, .y = 20};
  152. race_state->headPosition = p;
  153. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  154. Obstacle obstacle = {.type = CarObstacle, .isAlive = false, .position = {.x = 0, .y = 0}};
  155. race_state->obstacles[i] = obstacle;
  156. }
  157. memset(race_state->playField, 0, sizeof(race_state->playField));
  158. furi_timer_start(race_state->timer, race_state->motionSpeed);
  159. }
  160. static void race_game_draw_car(RaceState* race_state, Point p, bool changeState) {
  161. static Point pointsToCheck[] = {{0, 0}, {0, 1}, {-1, 1}, {1, 1}, {0, 2}, {-1, 3}, {1, 3}};
  162. for(int i = 0; i < 7; i++) {
  163. if(p.x + pointsToCheck[i].x > -1 && p.y + pointsToCheck[i].y > -1 &&
  164. p.x + pointsToCheck[i].x < FIELD_WIDTH && p.y + pointsToCheck[i].y < FIELD_HEIGHT) {
  165. if(changeState &&
  166. race_state->playField[p.y + pointsToCheck[i].y][p.x + pointsToCheck[i].x]) {
  167. race_state->gameState = GameStateGameOver;
  168. }
  169. race_state->playField[p.y + pointsToCheck[i].y][p.x + pointsToCheck[i].x] = true;
  170. }
  171. }
  172. }
  173. static void race_game_move_obstacles(RaceState* race_state) {
  174. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  175. if(race_state->obstacles[i].isAlive) {
  176. race_state->obstacles[i].position.y++;
  177. if(race_state->obstacles[i].position.y > (FIELD_HEIGHT - 1))
  178. race_state->obstacles[i].isAlive = false;
  179. }
  180. }
  181. }
  182. static void race_game_spawn_obstacles(RaceState* race_state) {
  183. if(race_state->score % 90 == 0) {
  184. int aliveObjects = 0;
  185. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  186. if(race_state->obstacles[i].isAlive) {
  187. aliveObjects++;
  188. }
  189. }
  190. if(aliveObjects < PARALLEL_OBSTACLES) {
  191. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  192. if(!race_state->obstacles[i].isAlive) {
  193. race_state->obstacles[i].isAlive = true;
  194. race_state->obstacles[i].position.y = -4;
  195. race_state->obstacles[i].position.x = (rand() % 3) * 3 + 2;
  196. break;
  197. }
  198. }
  199. }
  200. }
  201. }
  202. static void race_game_process_step(RaceState* race_state, bool moveRoad) {
  203. if(race_state->gameState == GameStateGameOver) return;
  204. // calculate field boundaries
  205. if(race_state->headPosition.x < 2) race_state->headPosition.x = 2;
  206. if(race_state->headPosition.x > (FIELD_WIDTH - 3))
  207. race_state->headPosition.x = (FIELD_WIDTH - 3);
  208. if(race_state->headPosition.y < 0) race_state->headPosition.y = 0;
  209. if(race_state->headPosition.y > (FIELD_HEIGHT - 4))
  210. race_state->headPosition.y = (FIELD_HEIGHT - 4);
  211. for(int y = 0; y < FIELD_HEIGHT; y++) {
  212. for(int x = 0; x < FIELD_WIDTH; x++) {
  213. race_state->playField[y][x] = false;
  214. }
  215. }
  216. if(moveRoad) {
  217. race_state->score += 10;
  218. race_state->roadStart++;
  219. if(race_state->roadStart == 4) race_state->roadStart = 0;
  220. race_game_spawn_obstacles(race_state);
  221. race_game_move_obstacles(race_state);
  222. if(race_state->score % 500 == 0) {
  223. if(race_state->level < 9) {
  224. race_state->level++;
  225. race_state->motionSpeed = race_state->motionSpeed - 50;
  226. furi_timer_stop(race_state->timer);
  227. furi_timer_start(race_state->timer, race_state->motionSpeed);
  228. }
  229. }
  230. }
  231. for(int i = 0; i < PARALLEL_OBSTACLES; i++) {
  232. if(race_state->obstacles[i].isAlive) {
  233. race_game_draw_car(race_state, race_state->obstacles[i].position, false);
  234. }
  235. }
  236. race_game_draw_car(race_state, race_state->headPosition, true);
  237. race_game_init_road(race_state);
  238. }
  239. int32_t race_app(void* p) {
  240. UNUSED(p);
  241. srand(DWT->CYCCNT);
  242. // current custom event element
  243. RaceGameEvent event;
  244. RaceState* race_state = malloc(sizeof(RaceState));
  245. FuriMutex* state_mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
  246. // Queue on 8 events
  247. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(RaceGameEvent));
  248. race_state->timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, event_queue);
  249. race_game_init_state(race_state);
  250. // Creating viewport
  251. ViewPort* view_port = view_port_alloc();
  252. // Set viewpoint orientation to vertical
  253. view_port_set_orientation(view_port, ViewPortOrientationVertical);
  254. // creating draw callback without context
  255. // view_port_draw_callback_set(view_port, draw_callback, &state_mutex);
  256. view_port_draw_callback_set(view_port, draw_callback, race_state);
  257. // setting up and all input events go to our event queue
  258. view_port_input_callback_set(view_port, input_callback, event_queue);
  259. // creating gui
  260. Gui* gui = furi_record_open(RECORD_GUI);
  261. // adding our view port to gui in full screen mode
  262. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  263. // Бесконечный цикл обработки очереди событий
  264. for(bool processing = true; processing;) {
  265. furi_mutex_acquire(state_mutex, 1000);
  266. // RaceState* race_state = (RaceState*)furi_mutex_block(&state_mutex, 1000);
  267. // Выбираем событие из очереди в переменную event (ждем бесконечно долго, если очередь пуста)
  268. // и проверяем, что у нас получилось это сделать
  269. furi_check(furi_message_queue_get(event_queue, &event, FuriWaitForever) == FuriStatusOk);
  270. bool moveRoad = false;
  271. if(event.type == EventTypeKey) {
  272. // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения
  273. switch(event.input.key) {
  274. case InputKeyBack:
  275. processing = false;
  276. break;
  277. case InputKeyOk:
  278. if(race_state->gameState == GameStateGameOver) {
  279. race_game_init_state(race_state);
  280. }
  281. break;
  282. case InputKeyUp:
  283. race_state->headPosition.y -= 1;
  284. break;
  285. case InputKeyDown:
  286. race_state->headPosition.y += 1;
  287. break;
  288. case InputKeyRight:
  289. race_state->headPosition.x += 1;
  290. break;
  291. case InputKeyLeft:
  292. race_state->headPosition.x -= 1;
  293. break;
  294. default:
  295. break;
  296. }
  297. // Наше событие — это сработавший таймер
  298. } else if(event.type == EventTypeTick) {
  299. //Move road and obstacles with timer ticks
  300. moveRoad = true;
  301. }
  302. race_game_process_step(race_state, moveRoad);
  303. view_port_update(view_port);
  304. furi_mutex_release(state_mutex);
  305. }
  306. // clearing everything on game exit
  307. // clear game timer
  308. furi_timer_free(race_state->timer);
  309. // clear message queue
  310. furi_message_queue_free(event_queue);
  311. // clearing ui
  312. gui_remove_view_port(gui, view_port);
  313. view_port_free(view_port);
  314. furi_record_close(RECORD_GUI);
  315. return 0;
  316. }