#include #include #include #include #include #include #include #include "bomberduck_icons.h" int max(int a, int b) { return (a > b) ? a : b; } int min(int a, int b) { return (a < b) ? a : b; } FuriMutex* mutex; #define WorldSizeX 12 #define WorldSizeY 6 #define BombTime 4 #define BombRange 1 typedef struct { int row; int col; } Cell; typedef struct { Cell cells[WorldSizeY * WorldSizeX]; int front; int rear; } Queue; void enqueue(Queue* q, Cell c) { q->cells[q->rear] = c; q->rear++; } Cell dequeue(Queue* q) { Cell c = q->cells[q->front]; q->front++; return c; } bool is_empty(Queue* q) { return q->front == q->rear; } typedef struct { int x; int y; int planted; } Bomb; typedef struct { int x; int y; bool side; } Player; typedef struct { int x; int y; int last; bool side; int level; } Enemy; typedef struct { int matrix[WorldSizeY][WorldSizeX]; Player* player; bool running; int level; Enemy enemies[10]; int enemies_count; Bomb bombs[100]; int bombs_count; int endx; int endy; } World; Player player = {0, 0, 1}; World world = {{{0}}, &player, 1, 0, {}, 0, {}, 0, 0, 0}; void init(){ player.x = 1; player.y = 1; world.endx = 3 + rand()%8; world.endy = rand()%6; for (int i = 0; i < WorldSizeY; i++) { for (int j = 0; j < WorldSizeX; j++) { world.matrix[i][j] = rand() % 3; } } world.running = 1; for(int j = max(0, player.y-BombRange); j < min(WorldSizeY, player.y+BombRange+1); j++){ world.matrix[j][player.x] = 0; } for(int j = max(0, player.x-BombRange); j < min(WorldSizeX, player.x+BombRange+1); j++){ world.matrix[player.y][j] = 0; } world.enemies_count=0; for(int j = 0; j < rand()%2 + world.level/5; j++){ Enemy enemy; enemy.x = 3 + rand()%8; enemy.y = rand()%6; enemy.last = 0; enemy.side = 1; enemy.level = 0; world.enemies[j] = enemy; world.enemies_count++; for(int m = max(0, world.enemies[j].y-BombRange); m < min(WorldSizeY, world.enemies[j].y+BombRange+1); m++){ world.matrix[m][world.enemies[j].x] = 0; } for(int m = max(0, world.enemies[j].x-BombRange); m < min(WorldSizeX, world.enemies[j].x+BombRange+1); m++){ world.matrix[world.enemies[j].y][m] = 0; } } world.matrix[world.endy][world.endx] = 1; } const NotificationSequence end = { &message_vibro_on, &message_note_ds4, &message_delay_10, &message_sound_off, &message_delay_10, &message_note_ds4, &message_delay_10, &message_sound_off, &message_delay_10, &message_note_ds4, &message_delay_10, &message_sound_off, &message_delay_10, &message_vibro_off, NULL, }; void intToStr(int num, char* str) { int i = 0, sign = 0; if (num < 0) { num = -num; sign = 1; } do { str[i++] = num % 10 + '0'; num /= 10; } while (num > 0); if (sign) { str[i++] = '-'; } str[i] = '\0'; // Reverse the string int j, len = i; char temp; for (j = 0; j < len / 2; j++) { temp = str[j]; str[j] = str[len - j - 1]; str[len - j - 1] = temp; } } bool BFS() { // Initialize visited array and queue int visited[WorldSizeY][WorldSizeX] = {0}; Queue q = {.front = 0, .rear = 0}; // Mark the starting cell as visited and enqueue it visited[world.player->y][world.player->x] = 1; Cell startCell = {.row = world.player->y, .col = world.player->x}; enqueue(&q, startCell); // Traverse the field while (!is_empty(&q)) { // Dequeue a cell from the queue Cell currentCell = dequeue(&q); // Check if the current cell is the destination cell if (currentCell.row == world.endy && currentCell.col == world.endx) { return true; } // Check the neighboring cells for (int rowOffset = -1; rowOffset <= 1; rowOffset++) { for (int colOffset = -1; colOffset <= 1; colOffset++) { // Skip diagonals and the current cell if (rowOffset == 0 && colOffset == 0) { continue; } if (rowOffset != 0 && colOffset != 0) { continue; } // Calculate the row and column of the neighboring cell int neighborRow = currentCell.row + rowOffset; int neighborCol = currentCell.col + colOffset; // Skip out-of-bounds cells and already visited cells if (neighborRow < 0 || neighborRow >= WorldSizeY || neighborCol < 0 || neighborCol >= WorldSizeX) { continue; } if (visited[neighborRow][neighborCol]) { continue; } // Mark the neighboring cell as visited and enqueue it if (world.matrix[neighborRow][neighborCol] != 2){ visited[neighborRow][neighborCol] = 1; Cell neighborCell = {.row = neighborRow, .col = neighborCol}; enqueue(&q, neighborCell); } } } } return false; } static void draw_callback(Canvas* canvas, void* ctx) { UNUSED(ctx); furi_mutex_acquire(mutex, FuriWaitForever); if(!BFS()){ init(); } canvas_clear(canvas); canvas_draw_icon(canvas, world.endx*10+4, world.endy*10+2, &I_end); if(world.running){ for (size_t i = 0; i < WorldSizeY; i++) { for (size_t j = 0; j < WorldSizeX; j++) { switch (world.matrix[i][j]) { case 0: break; case 1: canvas_draw_icon(canvas, j*10+4, i*10+2, &I_box); break; case 2: canvas_draw_icon(canvas, j*10+4, i*10+2, &I_unbreakbox); break; case 3: canvas_draw_icon(canvas, j*10+4, i*10+2, &I_bomb0); break; case 4: canvas_draw_icon(canvas, j*10+4, i*10+2, &I_bomb1); break; case 5: canvas_draw_icon(canvas, j*10+4, i*10+2, &I_bomb2); break; case 6: canvas_draw_icon(canvas, j*10+4, i*10+2, &I_explore); world.matrix[i][j] = 0; break; } } } if(world.player->side){ canvas_draw_icon(canvas, world.player->x*10+4, world.player->y*10+2, &I_playerright); }else{ canvas_draw_icon(canvas, world.player->x*10+4, world.player->y*10+2, &I_playerleft); } for (int i = 0; i < world.enemies_count; i++) { if(world.enemies[i].level>0){ canvas_draw_icon(canvas, world.enemies[i].x*10+4, world.enemies[i].y*10+2, &I_enemy1); } else { if(world.enemies[i].side){ canvas_draw_icon(canvas, world.enemies[i].x*10+4, world.enemies[i].y*10+2, &I_enemyright); }else{ canvas_draw_icon(canvas, world.enemies[i].x*10+4, world.enemies[i].y*10+2, &I_enemyleft); } } } }else{ canvas_set_font(canvas, FontPrimary); if(world.player->x == world.endx && world.player->y == world.endy){ if(world.level == 20){ canvas_draw_str(canvas, 30, 35, "You win!"); } canvas_draw_str(canvas, 30, 35, "Next level!"); char str[20]; intToStr(world.level, str); canvas_draw_str(canvas, 90, 35, str); } else { canvas_draw_str(canvas, 30, 35, "Game over!"); } } furi_mutex_release(mutex); } static void input_callback(InputEvent* input_event, void* ctx) { // Проверяем, что контекст не нулевой furi_assert(ctx); FuriMessageQueue* event_queue = ctx; furi_message_queue_put(event_queue, input_event, FuriWaitForever); } int32_t bomberduck_app(void* p) { UNUSED(p); // Текущее событие типа InputEvent InputEvent event; // Очередь событий на 8 элементов размера InputEvent FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); // Создаем новый view port ViewPort* view_port = view_port_alloc(); // Создаем callback отрисовки, без контекста view_port_draw_callback_set(view_port, draw_callback, NULL); // Создаем callback нажатий на клавиши, в качестве контекста передаем // нашу очередь сообщений, чтоб запихивать в неё эти события view_port_input_callback_set(view_port, input_callback, event_queue); mutex = furi_mutex_alloc(FuriMutexTypeNormal);// // Создаем GUI приложения Gui* gui = furi_record_open(RECORD_GUI); // Подключаем view port к GUI в полноэкранном режиме gui_add_view_port(gui, view_port, GuiLayerFullscreen); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message_block(notification, &sequence_display_backlight_enforce_on); init(); // Бесконечный цикл обработки очереди событий while(1) { if (furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk){ furi_mutex_acquire(mutex, FuriWaitForever); // Если нажата кнопка "назад", то выходим из цикла, а следовательно и из приложения if(event.type == InputTypePress) { if(event.key == InputKeyOk) { if(world.running){ if (world.matrix[world.player->y][world.player->x]==0 && world.bombs_count<2) { world.matrix[world.player->y][world.player->x] = 3; Bomb bomb = {world.player->x, world.player->y, furi_get_tick()}; world.bombs[world.bombs_count] = bomb; world.bombs_count++; } } else { init(); } } if(event.key == InputKeyUp) { if (world.player->y >0 && world.matrix[world.player->y-1][world.player->x]==0) world.player->y--; } if(event.key == InputKeyDown) { if (world.player->y < WorldSizeY-1 && world.matrix[world.player->y+1][world.player->x]==0) world.player->y++; } if(event.key == InputKeyLeft) { world.player->side=0; if (world.player->x > 0 && world.matrix[world.player->y][world.player->x-1]==0) world.player->x--; } if(event.key == InputKeyRight) { world.player->side=1; if (world.player->x < WorldSizeX-1 && world.matrix[world.player->y][world.player->x+1]==0) world.player->x++; } if(event.key == InputKeyBack) { break; } } } if(world.running){ if(world.player->x == world.endx && world.player->y == world.endy){ notification_message(notification, &end); world.running=0; world.level+=1; } for (int i = 0; i < world.bombs_count; i++) { if(furi_get_tick() - world.bombs[i].planted > 3000){ world.matrix[world.bombs[i].y][world.bombs[i].x] = 6; for(int j = max(0, world.bombs[i].y-BombRange); j < min(WorldSizeY, world.bombs[i].y+BombRange+1); j++){ if(world.matrix[j][world.bombs[i].x]!=2){ world.matrix[j][world.bombs[i].x] = 6; if (j==world.player->y && world.bombs[i].x == world.player->x){ notification_message(notification, &end); world.running=0; } for (int e = 0; e < world.enemies_count; e++) { if (j==world.enemies[e].y && world.bombs[i].x == world.enemies[e].x){ for (int l = e; l < world.enemies_count - 1; l++) { world.enemies[l] = world.enemies[l + 1]; } world.enemies_count--; } } } } for(int j = max(0, world.bombs[i].x-BombRange); j < min(WorldSizeX, world.bombs[i].x+BombRange+1); j++){ if(world.matrix[world.bombs[i].y][j]!=2){ world.matrix[world.bombs[i].y][j] = 6; if (world.bombs[i].y ==world.player->y && j == world.player->x){ notification_message(notification, &end); world.running=0; } for (int e = 0; e < world.enemies_count; e++) { if (world.bombs[i].y == world.enemies[e].y && j == world.enemies[e].x){ for (int l = e; l < world.enemies_count - 1; l++) { world.enemies[l] = world.enemies[l + 1]; } world.enemies_count--; } } } } for (int j = i; j < world.bombs_count - 1; j++) { world.bombs[j] = world.bombs[j + 1]; } world.bombs_count--; }else if (furi_get_tick() - world.bombs[i].planted > 2000) { world.matrix[world.bombs[i].y][world.bombs[i].x] = 5; }else if (furi_get_tick() - world.bombs[i].planted > 1000) { world.matrix[world.bombs[i].y][world.bombs[i].x] = 4; } } for (int e = 0; e < world.enemies_count; e++) { if (world.player->y==world.enemies[e].y && world.player->x == world.enemies[e].x){ notification_message(notification, &end); world.running=0; } } for (int e = 0; e < world.enemies_count; e++) { if(world.enemies[e].level>0){ if (furi_get_tick() - world.enemies[e].last > (unsigned long)(2000 - world.level*100)){ world.enemies[e].last = furi_get_tick(); int move = rand()%4; switch (move) { case 0: if (world.enemies[e].y >0 && world.matrix[world.enemies[e].y-1][world.enemies[e].x]!=2) world.enemies[e].y--; break; case 1: if (world.enemies[e].y < WorldSizeY-1 && world.matrix[world.enemies[e].y+1][world.enemies[e].x]!=2) world.enemies[e].y++; break; case 2: world.enemies[e].side=0; if (world.enemies[e].x > 0 && world.matrix[world.enemies[e].y][world.enemies[e].x-1]!=2) world.enemies[e].x--; break; case 3: world.enemies[e].side=1; if (world.enemies[e].x < WorldSizeX-1 && world.matrix[world.enemies[e].y][world.enemies[e].x+1]!=2) world.enemies[e].x++; default: break; } } } else { if (furi_get_tick() - world.enemies[e].last > (unsigned long)(1000 - world.level*50)){ world.enemies[e].last = furi_get_tick(); int move = rand()%4; switch (move) { case 0: if (world.enemies[e].y >0 && world.matrix[world.enemies[e].y-1][world.enemies[e].x]==0) world.enemies[e].y--; break; case 1: if (world.enemies[e].y < WorldSizeY-1 && world.matrix[world.enemies[e].y+1][world.enemies[e].x]==0) world.enemies[e].y++; break; case 2: world.enemies[e].side=0; if (world.enemies[e].x > 0 && world.matrix[world.enemies[e].y][world.enemies[e].x-1]==0) world.enemies[e].x--; break; case 3: world.enemies[e].side=1; if (world.enemies[e].x < WorldSizeX-1 && world.matrix[world.enemies[e].y][world.enemies[e].x+1]==0) world.enemies[e].x++; default: break; } } } } for (int e = 0; e < world.enemies_count; e++) { for (int h = e + 1; h < world.enemies_count; h++) { if(world.enemies[e].y == world.enemies[h].y && world.enemies[e].x==world.enemies[h].x){ world.enemies[h].level++; for (int l = e; l < world.enemies_count - 1; l++) { world.enemies[l] = world.enemies[l + 1]; } world.enemies_count--; } } } } view_port_update(view_port); furi_mutex_release(mutex); } // Специальная очистка памяти, занимаемой очередью furi_message_queue_free(event_queue); // Чистим созданные объекты, связанные с интерфейсом gui_remove_view_port(gui, view_port); view_port_free(view_port); furi_message_queue_free(event_queue); // Чистим созданные объекты, связанные с интерфейсом gui_remove_view_port(gui, view_port); view_port_free(view_port); furi_mutex_free(mutex); furi_record_close(RECORD_GUI); furi_mutex_free(mutex); furi_record_close(RECORD_GUI); return 0; }