bomberman.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. #include <stdio.h>
  2. #include <furi.h>
  3. #include <gui/gui.h>
  4. #include <input/input.h>
  5. #include <notification/notification.h>
  6. #include <notification/notification_messages.h>
  7. #include <gui/canvas_i.h>
  8. #include "bomberduck_icons.h"
  9. int max(int a, int b) {
  10. return (a > b) ? a : b;
  11. }
  12. int min(int a, int b) {
  13. return (a < b) ? a : b;
  14. }
  15. FuriMutex* mutex;
  16. #define WorldSizeX 12
  17. #define WorldSizeY 6
  18. #define BombTime 4
  19. #define BombRange 1
  20. typedef struct {
  21. int row;
  22. int col;
  23. } Cell;
  24. typedef struct {
  25. Cell cells[WorldSizeY * WorldSizeX];
  26. int front;
  27. int rear;
  28. } Queue;
  29. void enqueue(Queue* q, Cell c) {
  30. q->cells[q->rear] = c;
  31. q->rear++;
  32. }
  33. Cell dequeue(Queue* q) {
  34. Cell c = q->cells[q->front];
  35. q->front++;
  36. return c;
  37. }
  38. bool is_empty(Queue* q) {
  39. return q->front == q->rear;
  40. }
  41. typedef struct {
  42. int x;
  43. int y;
  44. int planted;
  45. } Bomb;
  46. typedef struct {
  47. int x;
  48. int y;
  49. bool side;
  50. } Player;
  51. typedef struct {
  52. int x;
  53. int y;
  54. int last;
  55. bool side;
  56. int level;
  57. } Enemy;
  58. typedef struct {
  59. int matrix[WorldSizeY][WorldSizeX];
  60. Player* player;
  61. bool running;
  62. int level;
  63. Enemy enemies[10];
  64. int enemies_count;
  65. Bomb bombs[100];
  66. int bombs_count;
  67. int endx;
  68. int endy;
  69. } World;
  70. Player player = {0, 0, 1};
  71. World world = {{{0}}, &player, 1, 0, {}, 0, {}, 0, 0, 0};
  72. void init(){
  73. player.x = 1;
  74. player.y = 1;
  75. world.endx = 3 + rand()%8;
  76. world.endy = rand()%6;
  77. for (int i = 0; i < WorldSizeY; i++) {
  78. for (int j = 0; j < WorldSizeX; j++) {
  79. world.matrix[i][j] = rand() % 3;
  80. }
  81. }
  82. world.running = 1;
  83. for(int j = max(0, player.y-BombRange); j < min(WorldSizeY, player.y+BombRange+1); j++){
  84. world.matrix[j][player.x] = 0;
  85. }
  86. for(int j = max(0, player.x-BombRange); j < min(WorldSizeX, player.x+BombRange+1); j++){
  87. world.matrix[player.y][j] = 0;
  88. }
  89. world.enemies_count=0;
  90. for(int j = 0; j < rand()%2 + world.level/5; j++){
  91. Enemy enemy;
  92. enemy.x = 3 + rand()%8;
  93. enemy.y = rand()%6;
  94. enemy.last = 0;
  95. enemy.side = 1;
  96. enemy.level = 0;
  97. world.enemies[j] = enemy;
  98. world.enemies_count++;
  99. for(int m = max(0, world.enemies[j].y-BombRange); m < min(WorldSizeY, world.enemies[j].y+BombRange+1); m++){
  100. world.matrix[m][world.enemies[j].x] = 0;
  101. }
  102. for(int m = max(0, world.enemies[j].x-BombRange); m < min(WorldSizeX, world.enemies[j].x+BombRange+1); m++){
  103. world.matrix[world.enemies[j].y][m] = 0;
  104. }
  105. }
  106. world.matrix[world.endy][world.endx] = 1;
  107. }
  108. const NotificationSequence end = {
  109. &message_vibro_on,
  110. &message_note_ds4,
  111. &message_delay_10,
  112. &message_sound_off,
  113. &message_delay_10,
  114. &message_note_ds4,
  115. &message_delay_10,
  116. &message_sound_off,
  117. &message_delay_10,
  118. &message_note_ds4,
  119. &message_delay_10,
  120. &message_sound_off,
  121. &message_delay_10,
  122. &message_vibro_off,
  123. NULL,
  124. };
  125. void intToStr(int num, char* str) {
  126. int i = 0, sign = 0;
  127. if (num < 0) {
  128. num = -num;
  129. sign = 1;
  130. }
  131. do {
  132. str[i++] = num % 10 + '0';
  133. num /= 10;
  134. } while (num > 0);
  135. if (sign) {
  136. str[i++] = '-';
  137. }
  138. str[i] = '\0';
  139. // Reverse the string
  140. int j, len = i;
  141. char temp;
  142. for (j = 0; j < len / 2; j++) {
  143. temp = str[j];
  144. str[j] = str[len - j - 1];
  145. str[len - j - 1] = temp;
  146. }
  147. }
  148. bool BFS() {
  149. // Initialize visited array and queue
  150. int visited[WorldSizeY][WorldSizeX] = {0};
  151. Queue q = {.front = 0, .rear = 0};
  152. // Mark the starting cell as visited and enqueue it
  153. visited[world.player->y][world.player->x] = 1;
  154. Cell startCell = {.row = world.player->y, .col = world.player->x};
  155. enqueue(&q, startCell);
  156. // Traverse the field
  157. while (!is_empty(&q)) {
  158. // Dequeue a cell from the queue
  159. Cell currentCell = dequeue(&q);
  160. // Check if the current cell is the destination cell
  161. if (currentCell.row == world.endy && currentCell.col == world.endx) {
  162. return true;
  163. }
  164. // Check the neighboring cells
  165. for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
  166. for (int colOffset = -1; colOffset <= 1; colOffset++) {
  167. // Skip diagonals and the current cell
  168. if (rowOffset == 0 && colOffset == 0) {
  169. continue;
  170. }
  171. if (rowOffset != 0 && colOffset != 0) {
  172. continue;
  173. }
  174. // Calculate the row and column of the neighboring cell
  175. int neighborRow = currentCell.row + rowOffset;
  176. int neighborCol = currentCell.col + colOffset;
  177. // Skip out-of-bounds cells and already visited cells
  178. if (neighborRow < 0 || neighborRow >= WorldSizeY ||
  179. neighborCol < 0 || neighborCol >= WorldSizeX) {
  180. continue;
  181. }
  182. if (visited[neighborRow][neighborCol]) {
  183. continue;
  184. }
  185. // Mark the neighboring cell as visited and enqueue it
  186. if (world.matrix[neighborRow][neighborCol] != 2){
  187. visited[neighborRow][neighborCol] = 1;
  188. Cell neighborCell = {.row = neighborRow, .col = neighborCol};
  189. enqueue(&q, neighborCell);
  190. }
  191. }
  192. }
  193. }
  194. return false;
  195. }
  196. static void draw_callback(Canvas* canvas, void* ctx) {
  197. UNUSED(ctx);
  198. furi_mutex_acquire(mutex, FuriWaitForever);
  199. if(!BFS()){
  200. init();
  201. }
  202. canvas_clear(canvas);
  203. canvas_draw_icon(canvas, world.endx*10+4, world.endy*10+2, &I_end);
  204. if(world.running){
  205. for (size_t i = 0; i < WorldSizeY; i++)
  206. {
  207. for (size_t j = 0; j < WorldSizeX; j++)
  208. {
  209. switch (world.matrix[i][j])
  210. {
  211. case 0:
  212. break;
  213. case 1:
  214. canvas_draw_icon(canvas, j*10+4, i*10+2, &I_box);
  215. break;
  216. case 2:
  217. canvas_draw_icon(canvas, j*10+4, i*10+2, &I_unbreakbox);
  218. break;
  219. case 3:
  220. canvas_draw_icon(canvas, j*10+4, i*10+2, &I_bomb0);
  221. break;
  222. case 4:
  223. canvas_draw_icon(canvas, j*10+4, i*10+2, &I_bomb1);
  224. break;
  225. case 5:
  226. canvas_draw_icon(canvas, j*10+4, i*10+2, &I_bomb2);
  227. break;
  228. case 6:
  229. canvas_draw_icon(canvas, j*10+4, i*10+2, &I_explore);
  230. world.matrix[i][j] = 0;
  231. break;
  232. }
  233. }
  234. }
  235. if(world.player->side){
  236. canvas_draw_icon(canvas, world.player->x*10+4, world.player->y*10+2, &I_playerright);
  237. }else{
  238. canvas_draw_icon(canvas, world.player->x*10+4, world.player->y*10+2, &I_playerleft);
  239. }
  240. for (int i = 0; i < world.enemies_count; i++)
  241. {
  242. if(world.enemies[i].level>0){
  243. canvas_draw_icon(canvas, world.enemies[i].x*10+4, world.enemies[i].y*10+2, &I_enemy1);
  244. } else {
  245. if(world.enemies[i].side){
  246. canvas_draw_icon(canvas, world.enemies[i].x*10+4, world.enemies[i].y*10+2, &I_enemyright);
  247. }else{
  248. canvas_draw_icon(canvas, world.enemies[i].x*10+4, world.enemies[i].y*10+2, &I_enemyleft);
  249. }
  250. }
  251. }
  252. }else{
  253. canvas_set_font(canvas, FontPrimary);
  254. if(world.player->x == world.endx && world.player->y == world.endy){
  255. if(world.level == 20){
  256. canvas_draw_str(canvas, 30, 35, "You win!");
  257. }
  258. canvas_draw_str(canvas, 30, 35, "Next level!");
  259. char str[20];
  260. intToStr(world.level, str);
  261. canvas_draw_str(canvas, 90, 35, str);
  262. } else {
  263. canvas_draw_str(canvas, 30, 35, "Game over!");
  264. }
  265. }
  266. furi_mutex_release(mutex);
  267. }
  268. static void input_callback(InputEvent* input_event, void* ctx) {
  269. // Проверяем, что контекст не нулевой
  270. furi_assert(ctx);
  271. FuriMessageQueue* event_queue = ctx;
  272. furi_message_queue_put(event_queue, input_event, FuriWaitForever);
  273. }
  274. int32_t bomberduck_app(void* p) {
  275. UNUSED(p);
  276. // Текущее событие типа InputEvent
  277. InputEvent event;
  278. // Очередь событий на 8 элементов размера InputEvent
  279. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  280. // Создаем новый view port
  281. ViewPort* view_port = view_port_alloc();
  282. // Создаем callback отрисовки, без контекста
  283. view_port_draw_callback_set(view_port, draw_callback, NULL);
  284. // Создаем callback нажатий на клавиши, в качестве контекста передаем
  285. // нашу очередь сообщений, чтоб запихивать в неё эти события
  286. view_port_input_callback_set(view_port, input_callback, event_queue);
  287. mutex = furi_mutex_alloc(FuriMutexTypeNormal);//
  288. // Создаем GUI приложения
  289. Gui* gui = furi_record_open(RECORD_GUI);
  290. // Подключаем view port к GUI в полноэкранном режиме
  291. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  292. NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
  293. notification_message_block(notification, &sequence_display_backlight_enforce_on);
  294. init();
  295. // Бесконечный цикл обработки очереди событий
  296. while(1) {
  297. if (furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk){
  298. furi_mutex_acquire(mutex, FuriWaitForever);
  299. // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения
  300. if(event.type == InputTypePress) {
  301. if(event.key == InputKeyOk) {
  302. if(world.running){
  303. if (world.matrix[world.player->y][world.player->x]==0 && world.bombs_count<2)
  304. {
  305. world.matrix[world.player->y][world.player->x] = 3;
  306. Bomb bomb = {world.player->x, world.player->y, furi_get_tick()};
  307. world.bombs[world.bombs_count] = bomb;
  308. world.bombs_count++;
  309. }
  310. } else {
  311. init();
  312. }
  313. }
  314. if(event.key == InputKeyUp) {
  315. if (world.player->y >0 && world.matrix[world.player->y-1][world.player->x]==0)
  316. world.player->y--;
  317. }
  318. if(event.key == InputKeyDown) {
  319. if (world.player->y < WorldSizeY-1 && world.matrix[world.player->y+1][world.player->x]==0)
  320. world.player->y++;
  321. }
  322. if(event.key == InputKeyLeft) {
  323. world.player->side=0;
  324. if (world.player->x > 0 && world.matrix[world.player->y][world.player->x-1]==0)
  325. world.player->x--;
  326. }
  327. if(event.key == InputKeyRight) {
  328. world.player->side=1;
  329. if (world.player->x < WorldSizeX-1 && world.matrix[world.player->y][world.player->x+1]==0)
  330. world.player->x++;
  331. }
  332. if(event.key == InputKeyBack) {
  333. break;
  334. }
  335. }
  336. }
  337. if(world.running){
  338. if(world.player->x == world.endx && world.player->y == world.endy){
  339. notification_message(notification, &end);
  340. world.running=0;
  341. world.level+=1;
  342. }
  343. for (int i = 0; i < world.bombs_count; i++)
  344. {
  345. if(furi_get_tick() - world.bombs[i].planted > 3000){
  346. world.matrix[world.bombs[i].y][world.bombs[i].x] = 6;
  347. for(int j = max(0, world.bombs[i].y-BombRange); j < min(WorldSizeY, world.bombs[i].y+BombRange+1); j++){
  348. if(world.matrix[j][world.bombs[i].x]!=2){
  349. world.matrix[j][world.bombs[i].x] = 6;
  350. if (j==world.player->y && world.bombs[i].x == world.player->x){
  351. notification_message(notification, &end);
  352. world.running=0;
  353. }
  354. for (int e = 0; e < world.enemies_count; e++)
  355. {
  356. if (j==world.enemies[e].y && world.bombs[i].x == world.enemies[e].x){
  357. for (int l = e; l < world.enemies_count - 1; l++) {
  358. world.enemies[l] = world.enemies[l + 1];
  359. }
  360. world.enemies_count--;
  361. }
  362. }
  363. }
  364. }
  365. for(int j = max(0, world.bombs[i].x-BombRange); j < min(WorldSizeX, world.bombs[i].x+BombRange+1); j++){
  366. if(world.matrix[world.bombs[i].y][j]!=2){
  367. world.matrix[world.bombs[i].y][j] = 6;
  368. if (world.bombs[i].y ==world.player->y && j == world.player->x){
  369. notification_message(notification, &end);
  370. world.running=0;
  371. }
  372. for (int e = 0; e < world.enemies_count; e++)
  373. {
  374. if (world.bombs[i].y == world.enemies[e].y && j == world.enemies[e].x){
  375. for (int l = e; l < world.enemies_count - 1; l++) {
  376. world.enemies[l] = world.enemies[l + 1];
  377. }
  378. world.enemies_count--;
  379. }
  380. }
  381. }
  382. }
  383. for (int j = i; j < world.bombs_count - 1; j++) {
  384. world.bombs[j] = world.bombs[j + 1];
  385. }
  386. world.bombs_count--;
  387. }else if (furi_get_tick() - world.bombs[i].planted > 2000)
  388. {
  389. world.matrix[world.bombs[i].y][world.bombs[i].x] = 5;
  390. }else if (furi_get_tick() - world.bombs[i].planted > 1000)
  391. {
  392. world.matrix[world.bombs[i].y][world.bombs[i].x] = 4;
  393. }
  394. }
  395. for (int e = 0; e < world.enemies_count; e++)
  396. {
  397. if (world.player->y==world.enemies[e].y && world.player->x == world.enemies[e].x){
  398. notification_message(notification, &end);
  399. world.running=0;
  400. }
  401. }
  402. for (int e = 0; e < world.enemies_count; e++)
  403. {
  404. if(world.enemies[e].level>0){
  405. if (furi_get_tick() - world.enemies[e].last > (unsigned long)(2000 - world.level*100)){
  406. world.enemies[e].last = furi_get_tick();
  407. int move = rand()%4;
  408. switch (move)
  409. {
  410. case 0:
  411. if (world.enemies[e].y >0 && world.matrix[world.enemies[e].y-1][world.enemies[e].x]!=2)
  412. world.enemies[e].y--;
  413. break;
  414. case 1:
  415. if (world.enemies[e].y < WorldSizeY-1 && world.matrix[world.enemies[e].y+1][world.enemies[e].x]!=2)
  416. world.enemies[e].y++;
  417. break;
  418. case 2:
  419. world.enemies[e].side=0;
  420. if (world.enemies[e].x > 0 && world.matrix[world.enemies[e].y][world.enemies[e].x-1]!=2)
  421. world.enemies[e].x--;
  422. break;
  423. case 3:
  424. world.enemies[e].side=1;
  425. if (world.enemies[e].x < WorldSizeX-1 && world.matrix[world.enemies[e].y][world.enemies[e].x+1]!=2)
  426. world.enemies[e].x++;
  427. default:
  428. break;
  429. }
  430. }
  431. } else {
  432. if (furi_get_tick() - world.enemies[e].last > (unsigned long)(1000 - world.level*50)){
  433. world.enemies[e].last = furi_get_tick();
  434. int move = rand()%4;
  435. switch (move)
  436. {
  437. case 0:
  438. if (world.enemies[e].y >0 && world.matrix[world.enemies[e].y-1][world.enemies[e].x]==0)
  439. world.enemies[e].y--;
  440. break;
  441. case 1:
  442. if (world.enemies[e].y < WorldSizeY-1 && world.matrix[world.enemies[e].y+1][world.enemies[e].x]==0)
  443. world.enemies[e].y++;
  444. break;
  445. case 2:
  446. world.enemies[e].side=0;
  447. if (world.enemies[e].x > 0 && world.matrix[world.enemies[e].y][world.enemies[e].x-1]==0)
  448. world.enemies[e].x--;
  449. break;
  450. case 3:
  451. world.enemies[e].side=1;
  452. if (world.enemies[e].x < WorldSizeX-1 && world.matrix[world.enemies[e].y][world.enemies[e].x+1]==0)
  453. world.enemies[e].x++;
  454. default:
  455. break;
  456. }
  457. }
  458. }
  459. }
  460. for (int e = 0; e < world.enemies_count; e++)
  461. {
  462. for (int h = e + 1; h < world.enemies_count; h++)
  463. {
  464. if(world.enemies[e].y == world.enemies[h].y && world.enemies[e].x==world.enemies[h].x){
  465. world.enemies[h].level++;
  466. for (int l = e; l < world.enemies_count - 1; l++) {
  467. world.enemies[l] = world.enemies[l + 1];
  468. }
  469. world.enemies_count--;
  470. }
  471. }
  472. }
  473. }
  474. view_port_update(view_port);
  475. furi_mutex_release(mutex);
  476. }
  477. // Специальная очистка памяти, занимаемой очередью
  478. furi_message_queue_free(event_queue);
  479. // Чистим созданные объекты, связанные с интерфейсом
  480. gui_remove_view_port(gui, view_port);
  481. view_port_free(view_port); furi_message_queue_free(event_queue);
  482. // Чистим созданные объекты, связанные с интерфейсом
  483. gui_remove_view_port(gui, view_port);
  484. view_port_free(view_port);
  485. furi_mutex_free(mutex);
  486. furi_record_close(RECORD_GUI);
  487. furi_mutex_free(mutex);
  488. furi_record_close(RECORD_GUI);
  489. return 0;
  490. }