unleashed-firmware/applications/external/bomberduck/bomberduck.c

649 lines
23 KiB
C
Raw Normal View History

2023-05-01 20:39:37 +03:00
#include <stdio.h>
#include <furi.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <gui/canvas_i.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;
2023-05-01 20:55:09 +03:00
world.bombs_count = 0;
2023-05-01 20:39:37 +03:00
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!");
2023-05-01 20:55:09 +03:00
} else {
2023-05-01 20:39:37 +03:00
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 &&
2023-05-01 20:55:09 +03:00
world.matrix[world.player->y - 1][world.player->x] == 0)
2023-05-01 20:39:37 +03:00
world.player->y--;
}
if(event.key == InputKeyDown) {
if(world.player->y < WorldSizeY - 1 &&
2023-05-01 20:55:09 +03:00
world.matrix[world.player->y + 1][world.player->x] == 0)
2023-05-01 20:39:37 +03:00
world.player->y++;
}
if(event.key == InputKeyLeft) {
world.player->side = 0;
if(world.player->x > 0 &&
2023-05-01 20:55:09 +03:00
world.matrix[world.player->y][world.player->x - 1] == 0)
2023-05-01 20:39:37 +03:00
world.player->x--;
}
if(event.key == InputKeyRight) {
world.player->side = 1;
if(world.player->x < WorldSizeX - 1 &&
2023-05-01 20:55:09 +03:00
world.matrix[world.player->y][world.player->x + 1] == 0)
2023-05-01 20:39:37 +03:00
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;
2023-05-01 20:55:09 +03:00
if(world.level % 5 == 0) {
2023-05-01 20:39:37 +03:00
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--;
2023-05-01 20:55:09 +03:00
} 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;
2023-05-01 20:39:37 +03:00
}
}
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--;
}
}
}
2023-05-01 20:55:09 +03:00
if(vibration) {
2023-05-01 20:39:37 +03:00
notification_message(notification, &vibr1);
}
}
view_port_update(view_port);
furi_mutex_release(bomber_state->mutex);
}
// 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;
}