Merge branch 'dev' of https://github.com/clipboard1/unleashed-firmware into dev
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 лень
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# flipperzero-bomberduck
|
||||
Bomberman clone on flipper zero!
|
||||
@@ -0,0 +1,14 @@
|
||||
App(
|
||||
appid="bomberduck",
|
||||
name="Bomberduck",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="bomberduck_app",
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
stack_size=1 * 1024,
|
||||
order=90,
|
||||
fap_icon="bomb.png",
|
||||
fap_category="Games",
|
||||
fap_icon_assets="assets",
|
||||
)
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,648 @@
|
||||
#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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -14,6 +14,7 @@ enum HidDebugSubmenuIndex {
|
||||
HidSubmenuIndexTikTok,
|
||||
HidSubmenuIndexYTShorts,
|
||||
HidSubmenuIndexMouse,
|
||||
HidSubmenuIndexMouseClicker,
|
||||
HidSubmenuIndexMouseJiggler,
|
||||
};
|
||||
|
||||
@@ -44,6 +45,9 @@ static void hid_submenu_callback(void* context, uint32_t index) {
|
||||
} else if(index == HidSubmenuIndexYTShorts) {
|
||||
app->view_id = BtHidViewYTShorts;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BtHidViewYTShorts);
|
||||
} else if(index == HidSubmenuIndexMouseClicker) {
|
||||
app->view_id = HidViewMouseClicker;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseClicker);
|
||||
} else if(index == HidSubmenuIndexMouseJiggler) {
|
||||
app->view_id = HidViewMouseJiggler;
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler);
|
||||
@@ -67,6 +71,7 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con
|
||||
hid_numpad_set_connected_status(hid->hid_numpad, connected);
|
||||
hid_media_set_connected_status(hid->hid_media, connected);
|
||||
hid_mouse_set_connected_status(hid->hid_mouse, connected);
|
||||
hid_mouse_clicker_set_connected_status(hid->hid_mouse_clicker, connected);
|
||||
hid_mouse_jiggler_set_connected_status(hid->hid_mouse_jiggler, connected);
|
||||
hid_tiktok_set_connected_status(hid->hid_tiktok, connected);
|
||||
hid_ytshorts_set_connected_status(hid->hid_ytshorts, connected);
|
||||
@@ -143,6 +148,12 @@ Hid* hid_alloc(HidTransport transport) {
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
}
|
||||
submenu_add_item(
|
||||
app->device_type_submenu,
|
||||
"Mouse Clicker",
|
||||
HidSubmenuIndexMouseClicker,
|
||||
hid_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->device_type_submenu,
|
||||
"Mouse Jiggler",
|
||||
@@ -222,6 +233,14 @@ Hid* hid_app_alloc_view(void* context) {
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse));
|
||||
|
||||
// Mouse clicker view
|
||||
app->hid_mouse_clicker = hid_mouse_clicker_alloc(app);
|
||||
view_set_previous_callback(
|
||||
hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view);
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher,
|
||||
HidViewMouseClicker,
|
||||
hid_mouse_clicker_get_view(app->hid_mouse_clicker));
|
||||
// Mouse jiggler view
|
||||
app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app);
|
||||
view_set_previous_callback(
|
||||
@@ -259,6 +278,8 @@ void hid_free(Hid* app) {
|
||||
hid_media_free(app->hid_media);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouse);
|
||||
hid_mouse_free(app->hid_mouse);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseClicker);
|
||||
hid_mouse_clicker_free(app->hid_mouse_clicker);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewMouseJiggler);
|
||||
hid_mouse_jiggler_free(app->hid_mouse_jiggler);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "views/hid_mouse_jiggler.h"
|
||||
#include "views/hid_tiktok.h"
|
||||
#include "views/hid_ytshorts.h"
|
||||
#include "views/hid_mouse_clicker.h"
|
||||
|
||||
#define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys"
|
||||
|
||||
@@ -48,6 +49,7 @@ struct Hid {
|
||||
HidNumpad* hid_numpad;
|
||||
HidMedia* hid_media;
|
||||
HidMouse* hid_mouse;
|
||||
HidMouseClicker* hid_mouse_clicker;
|
||||
HidMouseJiggler* hid_mouse_jiggler;
|
||||
HidTikTok* hid_tiktok;
|
||||
HidYTShorts* hid_ytshorts;
|
||||
|
||||
@@ -6,6 +6,7 @@ typedef enum {
|
||||
HidViewNumpad,
|
||||
HidViewMedia,
|
||||
HidViewMouse,
|
||||
HidViewMouseClicker,
|
||||
HidViewMouseJiggler,
|
||||
BtHidViewTikTok,
|
||||
BtHidViewYTShorts,
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
#include "hid_mouse_clicker.h"
|
||||
#include <gui/elements.h>
|
||||
#include "../hid.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidMouseClicker"
|
||||
#define DEFAULT_CLICK_RATE 1
|
||||
#define MAXIMUM_CLICK_RATE 60
|
||||
|
||||
struct HidMouseClicker {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
FuriTimer* timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
bool connected;
|
||||
bool running;
|
||||
int rate;
|
||||
HidTransport transport;
|
||||
} HidMouseClickerModel;
|
||||
|
||||
static void hid_mouse_clicker_start_or_restart_timer(void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClicker* hid_mouse_clicker = context;
|
||||
|
||||
if(furi_timer_is_running(hid_mouse_clicker->timer)) {
|
||||
furi_timer_stop(hid_mouse_clicker->timer);
|
||||
}
|
||||
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
furi_timer_start(
|
||||
hid_mouse_clicker->timer, furi_kernel_get_tick_frequency() / model->rate);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void hid_mouse_clicker_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClickerModel* model = context;
|
||||
|
||||
// Header
|
||||
if(model->transport == HidTransportBle) {
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
}
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text_aligned(canvas, 17, 3, AlignLeft, AlignTop, "Mouse Clicker");
|
||||
|
||||
// Ok
|
||||
canvas_draw_icon(canvas, 63, 25, &I_Space_65x18);
|
||||
if(model->running) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
FuriString* rate_label = furi_string_alloc();
|
||||
furi_string_printf(rate_label, "%d clicks/s\n\nUp / Down", model->rate);
|
||||
elements_multiline_text(canvas, AlignLeft, 35, furi_string_get_cstr(rate_label));
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
furi_string_free(rate_label);
|
||||
|
||||
elements_slightly_rounded_box(canvas, 66, 27, 60, 13);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
} else {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
elements_multiline_text(canvas, AlignLeft, 35, "Press Start\nto start\nclicking");
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
}
|
||||
canvas_draw_icon(canvas, 74, 29, &I_Ok_btn_9x9);
|
||||
if(model->running) {
|
||||
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Stop");
|
||||
} else {
|
||||
elements_multiline_text_aligned(canvas, 91, 36, AlignLeft, AlignBottom, "Start");
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Back
|
||||
canvas_draw_icon(canvas, 74, 49, &I_Pin_back_arrow_10x8);
|
||||
elements_multiline_text_aligned(canvas, 91, 57, AlignLeft, AlignBottom, "Quit");
|
||||
}
|
||||
|
||||
static void hid_mouse_clicker_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClicker* hid_mouse_clicker = context;
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
if(model->running) {
|
||||
hid_hal_mouse_press(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
|
||||
hid_hal_mouse_release(hid_mouse_clicker->hid, HID_MOUSE_BTN_LEFT);
|
||||
}
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
static void hid_mouse_clicker_enter_callback(void* context) {
|
||||
hid_mouse_clicker_start_or_restart_timer(context);
|
||||
}
|
||||
|
||||
static void hid_mouse_clicker_exit_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClicker* hid_mouse_clicker = context;
|
||||
furi_timer_stop(hid_mouse_clicker->timer);
|
||||
}
|
||||
|
||||
static bool hid_mouse_clicker_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidMouseClicker* hid_mouse_clicker = context;
|
||||
|
||||
bool consumed = false;
|
||||
bool rate_changed = false;
|
||||
|
||||
if(event->type != InputTypeRelease) {
|
||||
return false;
|
||||
}
|
||||
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
switch(event->key) {
|
||||
case InputKeyOk:
|
||||
model->running = !model->running;
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyUp:
|
||||
if(model->rate < MAXIMUM_CLICK_RATE) {
|
||||
model->rate++;
|
||||
}
|
||||
rate_changed = true;
|
||||
consumed = true;
|
||||
break;
|
||||
case InputKeyDown:
|
||||
if(model->rate > 1) {
|
||||
model->rate--;
|
||||
}
|
||||
rate_changed = true;
|
||||
consumed = true;
|
||||
break;
|
||||
default:
|
||||
consumed = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
if(rate_changed) {
|
||||
hid_mouse_clicker_start_or_restart_timer(context);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
HidMouseClicker* hid_mouse_clicker_alloc(Hid* hid) {
|
||||
HidMouseClicker* hid_mouse_clicker = malloc(sizeof(HidMouseClicker));
|
||||
|
||||
hid_mouse_clicker->view = view_alloc();
|
||||
view_set_context(hid_mouse_clicker->view, hid_mouse_clicker);
|
||||
view_allocate_model(
|
||||
hid_mouse_clicker->view, ViewModelTypeLocking, sizeof(HidMouseClickerModel));
|
||||
view_set_draw_callback(hid_mouse_clicker->view, hid_mouse_clicker_draw_callback);
|
||||
view_set_input_callback(hid_mouse_clicker->view, hid_mouse_clicker_input_callback);
|
||||
view_set_enter_callback(hid_mouse_clicker->view, hid_mouse_clicker_enter_callback);
|
||||
view_set_exit_callback(hid_mouse_clicker->view, hid_mouse_clicker_exit_callback);
|
||||
|
||||
hid_mouse_clicker->hid = hid;
|
||||
|
||||
hid_mouse_clicker->timer = furi_timer_alloc(
|
||||
hid_mouse_clicker_timer_callback, FuriTimerTypePeriodic, hid_mouse_clicker);
|
||||
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{
|
||||
model->transport = hid->transport;
|
||||
model->rate = DEFAULT_CLICK_RATE;
|
||||
},
|
||||
true);
|
||||
|
||||
return hid_mouse_clicker;
|
||||
}
|
||||
|
||||
void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker) {
|
||||
furi_assert(hid_mouse_clicker);
|
||||
|
||||
furi_timer_stop(hid_mouse_clicker->timer);
|
||||
furi_timer_free(hid_mouse_clicker->timer);
|
||||
|
||||
view_free(hid_mouse_clicker->view);
|
||||
|
||||
free(hid_mouse_clicker);
|
||||
}
|
||||
|
||||
View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker) {
|
||||
furi_assert(hid_mouse_clicker);
|
||||
return hid_mouse_clicker->view;
|
||||
}
|
||||
|
||||
void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected) {
|
||||
furi_assert(hid_mouse_clicker);
|
||||
with_view_model(
|
||||
hid_mouse_clicker->view,
|
||||
HidMouseClickerModel * model,
|
||||
{ model->connected = connected; },
|
||||
true);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidMouseClicker HidMouseClicker;
|
||||
|
||||
HidMouseClicker* hid_mouse_clicker_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_mouse_clicker_free(HidMouseClicker* hid_mouse_clicker);
|
||||
|
||||
View* hid_mouse_clicker_get_view(HidMouseClicker* hid_mouse_clicker);
|
||||
|
||||
void hid_mouse_clicker_set_connected_status(HidMouseClicker* hid_mouse_clicker, bool connected);
|
||||
@@ -109,7 +109,6 @@ static void hid_ytshorts_draw_callback(Canvas* canvas, void* context) {
|
||||
elements_multiline_text_aligned(canvas, 13, 62, AlignLeft, AlignBottom, "Hold to exit");
|
||||
}
|
||||
|
||||
|
||||
static void hid_ytshorts_reset_cursor(HidYTShorts* hid_ytshorts) {
|
||||
// Set cursor to the phone's left up corner
|
||||
// Delays to guarantee one packet per connection interval
|
||||
|
||||
@@ -3166,6 +3166,11 @@ int32_t swd_probe_app_main(void* p) {
|
||||
furi_message_queue_free(app->event_queue);
|
||||
furi_mutex_free(app->gui_mutex);
|
||||
furi_mutex_free(app->swd_mutex);
|
||||
|
||||
// Reset GPIO pins to default state
|
||||
for(int io = 0; io < 8; io++) {
|
||||
furi_hal_gpio_init(gpios[io], GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
free(app);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
@@ -30,7 +30,7 @@ static uint32_t get_keypress_delay(TokenAutomationFeature features) {
|
||||
}
|
||||
|
||||
static void totp_type_code_worker_press_key(
|
||||
uint8_t key,
|
||||
uint16_t key,
|
||||
TOTP_AUTOMATION_KEY_HANDLER key_press_fn,
|
||||
TOTP_AUTOMATION_KEY_HANDLER key_release_fn,
|
||||
TokenAutomationFeature features) {
|
||||
@@ -47,8 +47,6 @@ void totp_type_code_worker_execute_automation(
|
||||
TokenAutomationFeature features) {
|
||||
furi_delay_ms(500);
|
||||
uint8_t i = 0;
|
||||
totp_type_code_worker_press_key(
|
||||
HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features);
|
||||
|
||||
while(i < code_buffer_size && code_buffer[i] != 0) {
|
||||
uint8_t char_index = CONVERT_CHAR_TO_DIGIT(code_buffer[i]);
|
||||
@@ -58,7 +56,11 @@ void totp_type_code_worker_execute_automation(
|
||||
|
||||
if(char_index > 35) break;
|
||||
|
||||
uint8_t hid_kb_key = hid_number_keys[char_index];
|
||||
uint16_t hid_kb_key = hid_number_keys[char_index];
|
||||
if(char_index > 9) {
|
||||
hid_kb_key |= KEY_MOD_LEFT_SHIFT;
|
||||
}
|
||||
|
||||
totp_type_code_worker_press_key(hid_kb_key, key_press_fn, key_release_fn, features);
|
||||
furi_delay_ms(get_keystroke_delay(features));
|
||||
i++;
|
||||
@@ -74,7 +76,4 @@ void totp_type_code_worker_execute_automation(
|
||||
furi_delay_ms(get_keystroke_delay(features));
|
||||
totp_type_code_worker_press_key(HID_KEYBOARD_TAB, key_press_fn, key_release_fn, features);
|
||||
}
|
||||
|
||||
totp_type_code_worker_press_key(
|
||||
HID_KEYBOARD_CAPS_LOCK, key_press_fn, key_release_fn, features);
|
||||
}
|
||||