| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647 |
- #include <stdio.h>
- #include <furi.h>
- #include <gui/gui.h>
- #include <input/input.h>
- #include <notification/notification.h>
- #include <notification/notification_messages.h>
- #include "bomberduck_icons.h"
- #include <dolphin/dolphin.h>
- int max(int a, int b) {
- return (a > b) ? a : b;
- }
- int min(int a, int b) {
- return (a < b) ? a : b;
- }
- #define WorldSizeX 12
- #define WorldSizeY 6
- #define BombRange 1
- typedef struct {
- FuriMutex* mutex;
- } BomberState;
- 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};
- bool vibration = false;
- void init() {
- player.x = 1;
- player.y = 1;
- world.endx = 4 + 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;
- world.bombs_count = 0;
- vibration = false;
- 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() % 4 + world.level / 5; j++) {
- Enemy enemy;
- enemy.x = 4 + rand() % 7;
- 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,
- };
- static const NotificationSequence bomb2 = {
- &message_vibro_on,
- &message_delay_25,
- &message_vibro_off,
- NULL,
- };
- static const NotificationSequence bomb_explore = {
- &message_vibro_on,
- &message_delay_50,
- &message_vibro_off,
- NULL,
- };
- static const NotificationSequence vibr1 = {
- &message_vibro_on,
- &message_delay_10,
- &message_vibro_off,
- &message_delay_10,
- &message_vibro_on,
- &message_delay_10,
- &message_vibro_off,
- &message_delay_10,
- 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) {
- furi_assert(ctx);
- const BomberState* bomber_state = ctx;
- furi_mutex_acquire(bomber_state->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!");
- } else {
- 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, "You died :(");
- }
- }
- furi_mutex_release(bomber_state->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));
- BomberState* bomber_state = malloc(sizeof(BomberState));
- bomber_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); // Alloc Mutex
- if(!bomber_state->mutex) {
- FURI_LOG_E("BomberDuck", "cannot create mutex\r\n");
- furi_message_queue_free(event_queue);
- free(bomber_state);
- return 255;
- }
- dolphin_deed(DolphinDeedPluginGameStart);
- // Создаем новый view port
- ViewPort* view_port = view_port_alloc();
- // Создаем callback отрисовки, без контекста
- view_port_draw_callback_set(view_port, draw_callback, bomber_state);
- // Создаем callback нажатий на клавиши, в качестве контекста передаем
- // нашу очередь сообщений, чтоб запихивать в неё эти события
- view_port_input_callback_set(view_port, input_callback, event_queue);
- // Создаем 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(bomber_state->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) {
- notification_message(notification, &bomb2);
- 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(world.running) {
- 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++;
- }
- }
- } else if(event.type == InputTypeLong) {
- 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;
- if(world.level % 5 == 0) {
- dolphin_deed(DolphinDeedPluginGameWin);
- }
- }
- for(int i = 0; i < world.bombs_count; i++) {
- if(furi_get_tick() - world.bombs[i].planted >
- (unsigned long)max((3000 - world.level * 150), 1000)) {
- vibration = false;
- world.matrix[world.bombs[i].y][world.bombs[i].x] = 6;
- notification_message(notification, &bomb_explore);
- 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) {
- if(world.enemies[e].level > 0) {
- world.enemies[e].level--;
- } else {
- 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) {
- if(world.enemies[e].level > 0) {
- world.enemies[e].level--;
- } else {
- 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 >
- (unsigned long)max((3000 - world.level * 150) * 2 / 3, 666) &&
- world.matrix[world.bombs[i].y][world.bombs[i].x] != 5) {
- world.matrix[world.bombs[i].y][world.bombs[i].x] = 5;
- vibration = true;
- } else if(
- furi_get_tick() - world.bombs[i].planted >
- (unsigned long)max((3000 - world.level * 150) / 3, 333) &&
- world.matrix[world.bombs[i].y][world.bombs[i].x] != 4) {
- 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)max((2000 - world.level * 100), 1000)) {
- 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)max((1000 - world.level * 50), 500)) {
- 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--;
- }
- }
- }
- if(vibration) {
- notification_message(notification, &vibr1);
- }
- }
- furi_mutex_release(bomber_state->mutex);
- view_port_update(view_port);
- }
- // Return to normal backlight settings
- notification_message_block(notification, &sequence_display_backlight_enforce_auto);
- furi_record_close(RECORD_NOTIFICATION);
- // Специальная очистка памяти, занимаемой очередью
- furi_message_queue_free(event_queue);
- // Чистим созданные объекты, связанные с интерфейсом
- gui_remove_view_port(gui, view_port);
- view_port_free(view_port);
- furi_mutex_free(bomber_state->mutex);
- furi_record_close(RECORD_GUI);
- free(bomber_state);
- return 0;
- }
|