race.c 12 KB

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