Преглед на файлове

Add tanks from https://github.com/xMasterX/all-the-plugins

git-subtree-dir: tanks
git-subtree-mainline: 4e4bbfce5017b8025eb1bc7bb33b53138078a974
git-subtree-split: 7204c54b2f90c5d1486b94d66e374a5527542386
Willy-JL преди 2 години
родител
ревизия
075e2e6e81

+ 1 - 0
tanks/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/xMasterX/all-the-plugins dev non_catalog_apps/tanksgame

+ 13 - 0
tanks/application.fam

@@ -0,0 +1,13 @@
+App(
+    appid="tanks",
+    name="Tanks",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="tanks_game_app",
+    cdefines=["APP_TANKS_GAME"],
+    requires=["gui", "subghz"],
+    stack_size=4 * 1024,
+    order=730,
+    fap_icon="tanksIcon.png",
+    fap_category="Games",
+    fap_icon_assets="images",
+)

+ 19 - 0
tanks/constants.h

@@ -0,0 +1,19 @@
+#ifndef FLIPPERZERO_FIRMWARE_CONSTANTS_H
+#define FLIPPERZERO_FIRMWARE_CONSTANTS_H
+
+const uint8_t SCREEN_WIDTH_TANKS = 128;
+const uint8_t SCREEN_HEIGHT_TANKS = 64;
+
+const uint8_t FIELD_WIDTH = 16;
+const uint8_t FIELD_HEIGHT = 11;
+
+const uint16_t TURN_LENGTH = 300;
+const uint16_t LONG_PRESS_LENGTH = 10;
+
+const uint8_t SHOT_COOLDOWN = 5;
+const uint8_t RESPAWN_COOLDOWN = 8;
+const uint8_t PLAYER_RESPAWN_COOLDOWN = 1;
+
+const uint8_t CELL_LENGTH_PIXELS = 6;
+
+#endif

+ 64 - 0
tanks/helpers/radio_device_loader.c

@@ -0,0 +1,64 @@
+#include "radio_device_loader.h"
+
+#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
+#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
+
+static void radio_device_loader_power_on() {
+    uint8_t attempts = 0;
+    while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
+        furi_hal_power_enable_otg();
+        //CC1101 power-up time
+        furi_delay_ms(10);
+    }
+}
+
+static void radio_device_loader_power_off() {
+    if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
+}
+
+bool radio_device_loader_is_connect_external(const char* name) {
+    bool is_connect = false;
+    bool is_otg_enabled = furi_hal_power_is_otg_enabled();
+
+    if(!is_otg_enabled) {
+        radio_device_loader_power_on();
+    }
+
+    const SubGhzDevice* device = subghz_devices_get_by_name(name);
+    if(device) {
+        is_connect = subghz_devices_is_connect(device);
+    }
+
+    if(!is_otg_enabled) {
+        radio_device_loader_power_off();
+    }
+    return is_connect;
+}
+
+const SubGhzDevice* radio_device_loader_set(
+    const SubGhzDevice* current_radio_device,
+    SubGhzRadioDeviceType radio_device_type) {
+    const SubGhzDevice* radio_device;
+
+    if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
+       radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
+        radio_device_loader_power_on();
+        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
+        subghz_devices_begin(radio_device);
+    } else if(current_radio_device == NULL) {
+        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
+    } else {
+        radio_device_loader_end(current_radio_device);
+        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
+    }
+
+    return radio_device;
+}
+
+void radio_device_loader_end(const SubGhzDevice* radio_device) {
+    furi_assert(radio_device);
+    radio_device_loader_power_off();
+    if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) {
+        subghz_devices_end(radio_device);
+    }
+}

+ 15 - 0
tanks/helpers/radio_device_loader.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include <lib/subghz/devices/devices.h>
+
+/** SubGhzRadioDeviceType */
+typedef enum {
+    SubGhzRadioDeviceTypeInternal,
+    SubGhzRadioDeviceTypeExternalCC1101,
+} SubGhzRadioDeviceType;
+
+const SubGhzDevice* radio_device_loader_set(
+    const SubGhzDevice* current_radio_device,
+    SubGhzRadioDeviceType radio_device_type);
+
+void radio_device_loader_end(const SubGhzDevice* radio_device);

BIN
tanks/images/HappyFlipper_128x64.png


BIN
tanks/images/TanksSplashScreen_128x64.png


BIN
tanks/images/enemy_down.png


BIN
tanks/images/enemy_left.png


BIN
tanks/images/enemy_right.png


BIN
tanks/images/enemy_up.png


BIN
tanks/images/projectile_down.png


BIN
tanks/images/projectile_left.png


BIN
tanks/images/projectile_right.png


BIN
tanks/images/projectile_up.png


BIN
tanks/images/tank_base.png


BIN
tanks/images/tank_down.png


BIN
tanks/images/tank_explosion.png


BIN
tanks/images/tank_hedgehog.png


BIN
tanks/images/tank_left.png


BIN
tanks/images/tank_right.png


BIN
tanks/images/tank_stone.png


BIN
tanks/images/tank_up.png


BIN
tanks/images/tank_wall.png


BIN
tanks/tanksIcon.png


+ 1475 - 0
tanks/tanks_game.c

@@ -0,0 +1,1475 @@
+#include <furi.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <string.h>
+#include <lib/toolbox/args.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/subghz_keystore.h>
+#include <lib/subghz/protocols/base.h>
+#include <lib/subghz/protocols/raw.h>
+#include <lib/subghz/protocols/princeton.h>
+#include <lib/subghz/subghz_tx_rx_worker.h>
+#include <tanks_icons.h>
+#include "helpers/radio_device_loader.h"
+
+#include "constants.h"
+
+typedef struct {
+    //    +-----x
+    //    |
+    //    |
+    //    y
+    uint8_t x;
+    uint8_t y;
+} Point;
+
+typedef enum {
+    CellEmpty = 1,
+    CellWall,
+    CellExplosion,
+    CellTankUp,
+    CellTankRight,
+    CellTankDown,
+    CellTankLeft,
+    CellEnemyUp,
+    CellEnemyRight,
+    CellEnemyDown,
+    CellEnemyLeft,
+    CellProjectileUp,
+    CellProjectileRight,
+    CellProjectileDown,
+    CellProjectileLeft,
+} GameCellState;
+
+typedef enum {
+    MenuStateSingleMode,
+    MenuStateCooperativeServerMode,
+    MenuStateCooperativeClientMode,
+} MenuState;
+
+typedef enum {
+    GameStateMenu,
+    GameStateSingle,
+    GameStateCooperativeServer,
+    GameStateCooperativeClient,
+    GameStateGameOver,
+} GameState;
+
+typedef enum {
+    DirectionUp,
+    DirectionRight,
+    DirectionDown,
+    DirectionLeft,
+} Direction;
+
+typedef enum {
+    ModeSingle,
+    ModeCooperative,
+} Mode;
+
+typedef struct {
+    Point coordinates;
+    Direction direction;
+    bool explosion;
+    bool is_p1;
+    bool is_p2;
+} ProjectileState;
+
+typedef struct {
+    Point coordinates;
+    uint16_t score;
+    uint8_t lives;
+    Direction direction;
+    bool moving;
+    bool shooting;
+    bool live;
+    uint8_t cooldown;
+    uint8_t respawn_cooldown;
+} PlayerState;
+
+typedef struct {
+    // char map[FIELD_WIDTH][FIELD_HEIGHT];
+    char thisMap[16][11];
+    Point team_one_respawn_points[3];
+    Point team_two_respawn_points[3];
+    Mode mode;
+    bool server;
+    GameState state;
+    MenuState menu_state;
+    ProjectileState* projectiles[100];
+    PlayerState* bots[6];
+    uint8_t enemies_left;
+    uint8_t enemies_live;
+    uint8_t enemies_respawn_cooldown;
+    uint8_t received;
+    uint8_t sent;
+    PlayerState* p1;
+    PlayerState* p2;
+    FuriMutex* mutex;
+} TanksState;
+
+typedef enum {
+    EventTypeTick,
+    EventTypeKey,
+} EventType;
+
+typedef struct {
+    EventType type;
+    InputEvent input;
+} TanksEvent;
+
+typedef enum {
+    GoesUp,
+    GoesRight,
+    GoesDown,
+    GoesLeft,
+    Shoots,
+} ClientAction;
+
+//char map[FIELD_HEIGHT][FIELD_WIDTH + 1] = {
+char thisMap[11][16 + 1] = {
+    "*       -  *   -",
+    "  -  -  =       ",
+    "        -  -   2",
+    "1    =     - -- ",
+    "--   =     - -- ",
+    "a-1  =  -  =   2",
+    "--   =     - -- ",
+    "1    =     - -- ",
+    "        -  -   2",
+    "  -  -  =       ",
+    "*       -  *   -",
+};
+
+static void tanks_game_write_cell(unsigned char* data, int8_t x, int8_t y, GameCellState cell) {
+    uint8_t index = y * 16 + x;
+    data[index] = cell;
+    //    if (x % 2) {
+    //        data[index] = (data[index] & 0b00001111) + (cell << 4);
+    //    } else {
+    //        data[index] = (data[index] & 0b0000) + cell;
+    //    }
+}
+
+// Enum with < 16 items => 4 bits in cell, 2 cells in byte
+unsigned char* tanks_game_serialize(const TanksState* const tanks_state) {
+    static unsigned char result[11 * 16 + 1];
+
+    for(int8_t x = 0; x < FIELD_WIDTH; x++) {
+        for(int8_t y = 0; y < FIELD_HEIGHT; y++) {
+            result[(y * FIELD_WIDTH + x)] = 0;
+
+            GameCellState cell = CellEmpty;
+
+            if(tanks_state->thisMap[x][y] == '-') {
+                cell = CellWall;
+
+                tanks_game_write_cell(result, x, y, cell);
+            }
+        }
+    }
+
+    for(uint8_t i = 0; i < 6; i++) {
+        if(tanks_state->bots[i] != NULL) {
+            GameCellState cell = CellEmpty;
+
+            switch(tanks_state->bots[i]->direction) {
+            case DirectionUp:
+                cell = CellEnemyUp;
+                break;
+            case DirectionDown:
+                cell = CellEnemyDown;
+                break;
+            case DirectionRight:
+                cell = CellEnemyRight;
+                break;
+            case DirectionLeft:
+                cell = CellEnemyLeft;
+                break;
+            default:
+                break;
+            }
+
+            tanks_game_write_cell(
+                result,
+                tanks_state->bots[i]->coordinates.x,
+                tanks_state->bots[i]->coordinates.y,
+                cell);
+        }
+    }
+
+    for(int8_t x = 0; x < 100; x++) {
+        if(tanks_state->projectiles[x] != NULL) {
+            GameCellState cell = CellEmpty;
+
+            switch(tanks_state->projectiles[x]->direction) {
+            case DirectionUp:
+                cell = CellProjectileUp;
+                break;
+            case DirectionDown:
+                cell = CellProjectileDown;
+                break;
+            case DirectionRight:
+                cell = CellProjectileRight;
+                break;
+            case DirectionLeft:
+                cell = CellProjectileLeft;
+                break;
+            default:
+                break;
+            }
+
+            tanks_game_write_cell(
+                result,
+                tanks_state->projectiles[x]->coordinates.x,
+                tanks_state->projectiles[x]->coordinates.y,
+                cell);
+        }
+    }
+
+    if(tanks_state->p1 != NULL && tanks_state->p1->live) {
+        GameCellState cell = CellEmpty;
+
+        switch(tanks_state->p1->direction) {
+        case DirectionUp:
+            cell = CellTankUp;
+            break;
+        case DirectionDown:
+            cell = CellTankDown;
+            break;
+        case DirectionRight:
+            cell = CellTankRight;
+            break;
+        case DirectionLeft:
+            cell = CellTankLeft;
+            break;
+        default:
+            break;
+        }
+
+        tanks_game_write_cell(
+            result, tanks_state->p1->coordinates.x, tanks_state->p1->coordinates.y, cell);
+    }
+
+    if(tanks_state->p2 != NULL && tanks_state->p2->live) {
+        GameCellState cell = CellEmpty;
+
+        switch(tanks_state->p2->direction) {
+        case DirectionUp:
+            cell = CellTankUp;
+            break;
+        case DirectionDown:
+            cell = CellTankDown;
+            break;
+        case DirectionRight:
+            cell = CellTankRight;
+            break;
+        case DirectionLeft:
+            cell = CellTankLeft;
+            break;
+        default:
+            break;
+        }
+
+        tanks_game_write_cell(
+            result, tanks_state->p2->coordinates.x, tanks_state->p2->coordinates.y, cell);
+    }
+
+    return result;
+}
+
+static void
+    tanks_game_render_cell(GameCellState cell, uint8_t x, uint8_t y, Canvas* const canvas) {
+    const Icon* icon;
+
+    if(cell == CellEmpty) {
+        return;
+    }
+
+    switch(cell) {
+    case CellWall:
+        icon = &I_tank_wall;
+        break;
+    case CellExplosion:
+        icon = &I_tank_explosion;
+        break;
+    case CellTankUp:
+        icon = &I_tank_up;
+        break;
+    case CellTankRight:
+        icon = &I_tank_right;
+        break;
+    case CellTankDown:
+        icon = &I_tank_down;
+        break;
+    case CellTankLeft:
+        icon = &I_tank_left;
+        break;
+    case CellEnemyUp:
+        icon = &I_enemy_up;
+        break;
+    case CellEnemyRight:
+        icon = &I_enemy_right;
+        break;
+    case CellEnemyDown:
+        icon = &I_enemy_down;
+        break;
+    case CellEnemyLeft:
+        icon = &I_enemy_left;
+        break;
+    case CellProjectileUp:
+        icon = &I_projectile_up;
+        break;
+    case CellProjectileRight:
+        icon = &I_projectile_right;
+        break;
+    case CellProjectileDown:
+        icon = &I_projectile_down;
+        break;
+    case CellProjectileLeft:
+        icon = &I_projectile_left;
+        break;
+    default:
+        return;
+        break;
+    }
+
+    canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, icon);
+}
+
+static void tanks_game_render_constant_cells(Canvas* const canvas) {
+    for(int8_t x = 0; x < FIELD_WIDTH; x++) {
+        for(int8_t y = 0; y < FIELD_HEIGHT; y++) {
+            char cell = thisMap[y][x];
+
+            if(cell == '=') {
+                canvas_draw_icon(
+                    canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_stone);
+                continue;
+            }
+
+            if(cell == '*') {
+                canvas_draw_icon(
+                    canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_hedgehog);
+                continue;
+            }
+
+            if(cell == 'a') {
+                canvas_draw_icon(
+                    canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_base);
+                continue;
+            }
+        }
+    }
+}
+
+void tanks_game_deserialize_and_write_to_state(unsigned char* data, TanksState* const tanks_state) {
+    for(uint8_t i = 0; i < 11 * 16; i++) {
+        uint8_t x = i % 16;
+        uint8_t y = i / 16;
+        tanks_state->thisMap[x][y] = data[i];
+    }
+}
+
+void tanks_game_deserialize_and_render(unsigned char* data, Canvas* const canvas) {
+    //for (uint8_t i = 0; i < 11 * 16 / 2; i++) {
+    for(uint8_t i = 0; i < 11 * 16; i++) {
+        char cell = data[i];
+        uint8_t x = i % 16; // One line (16 cells) = 8 bytes
+        uint8_t y = i / 16;
+
+        //        GameCellState first = cell >> 4;
+        //        GameCellState second = cell & 0b00001111;
+
+        tanks_game_render_cell(cell, x, y, canvas);
+        //        tanks_game_render_cell(second, x + 1, y, canvas);
+    }
+
+    tanks_game_render_constant_cells(canvas);
+}
+
+static void tanks_game_render_callback(Canvas* const canvas, void* ctx) {
+    furi_assert(ctx);
+    const TanksState* tanks_state = ctx;
+    furi_mutex_acquire(tanks_state->mutex, FuriWaitForever);
+
+    // Before the function is called, the state is set with the canvas_reset(canvas)
+    if(tanks_state->state == GameStateMenu) {
+        canvas_draw_icon(canvas, 0, 0, &I_TanksSplashScreen_128x64);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 124, 10, AlignRight, AlignBottom, "Single");
+        canvas_draw_str_aligned(canvas, 124, 25, AlignRight, AlignBottom, "Co-op S");
+        canvas_draw_str_aligned(canvas, 124, 40, AlignRight, AlignBottom, "Co-op C");
+
+        switch(tanks_state->menu_state) {
+        case MenuStateSingleMode:
+            canvas_draw_icon(canvas, 74, 3, &I_tank_right);
+            break;
+        case MenuStateCooperativeServerMode:
+            canvas_draw_icon(canvas, 74, 18, &I_tank_right);
+            break;
+        case MenuStateCooperativeClientMode:
+            canvas_draw_icon(canvas, 74, 33, &I_tank_right);
+            break;
+        }
+
+        canvas_draw_frame(canvas, 0, 0, 128, 64);
+
+        furi_mutex_release(tanks_state->mutex);
+        return;
+    }
+
+    // Field right border
+    canvas_draw_box(canvas, FIELD_WIDTH * CELL_LENGTH_PIXELS, 0, 2, SCREEN_HEIGHT_TANKS);
+
+    // Cooperative client
+    if(tanks_state->mode == ModeCooperative && !tanks_state->server) {
+        for(int8_t x = 0; x < FIELD_WIDTH; x++) {
+            for(int8_t y = 0; y < FIELD_HEIGHT; y++) {
+                tanks_game_render_cell(tanks_state->thisMap[x][y], x, y, canvas);
+            }
+        }
+
+        tanks_game_render_constant_cells(canvas);
+
+        furi_mutex_release(tanks_state->mutex);
+        return;
+    }
+
+    // Player
+    //    Point coordinates = tanks_state->p1->coordinates;
+    //    const Icon *icon;
+    //    switch (tanks_state->p1->direction) {
+    //    case DirectionUp:
+    //        icon = &I_tank_up;
+    //        break;
+    //    case DirectionDown:
+    //        icon = &I_tank_down;
+    //        break;
+    //    case DirectionRight:
+    //        icon = &I_tank_right;
+    //        break;
+    //    case DirectionLeft:
+    //        icon = &I_tank_left;
+    //        break;
+    //    default:
+    //        icon = &I_tank_explosion;
+    //    }
+
+    //    if (tanks_state->p1->live) {
+    //        canvas_draw_icon(canvas, coordinates.x * CELL_LENGTH_PIXELS, coordinates.y * CELL_LENGTH_PIXELS - 1, icon);
+    //    }
+    //
+    //    for(int8_t x = 0; x < FIELD_WIDTH; x++) {
+    //        for(int8_t y = 0; y < FIELD_HEIGHT; y++) {
+    //            switch (tanks_state->thisMap[x][y]) {
+    //            case '-':
+    //                canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_wall);
+    //                break;
+    //
+    //            case '=':
+    //                canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_stone);
+    //                break;
+    //
+    //            case '*':
+    //                canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_hedgehog);
+    //                break;
+    //
+    //            case 'a':
+    //                canvas_draw_icon(canvas, x * CELL_LENGTH_PIXELS, y * CELL_LENGTH_PIXELS - 1, &I_tank_base);
+    //                break;
+    //            }
+    //        }
+    //    }
+
+    //    for (
+    //        uint8_t i = 0;
+    //        i < 6;
+    //        i++
+    //    ) {
+    //        if (tanks_state->bots[i] != NULL) {
+    //            const Icon *icon;
+    //
+    //            switch(tanks_state->bots[i]->direction) {
+    //            case DirectionUp:
+    //                icon = &I_enemy_up;
+    //                break;
+    //            case DirectionDown:
+    //                icon = &I_enemy_down;
+    //                break;
+    //            case DirectionRight:
+    //                icon = &I_enemy_right;
+    //                break;
+    //            case DirectionLeft:
+    //                icon = &I_enemy_left;
+    //                break;
+    //            default:
+    //                icon = &I_tank_explosion;
+    //            }
+    //
+    //            canvas_draw_icon(
+    //                canvas,
+    //                tanks_state->bots[i]->coordinates.x * CELL_LENGTH_PIXELS,
+    //                tanks_state->bots[i]->coordinates.y * CELL_LENGTH_PIXELS - 1,
+    //                icon);
+    //        }
+    //    }
+
+    //    for(int8_t x = 0; x < 100; x++) {
+    //        if (tanks_state->projectiles[x] != NULL) {
+    //            ProjectileState *projectile = tanks_state->projectiles[x];
+    //
+    //            if (projectile->explosion) {
+    //                canvas_draw_icon(
+    //                    canvas,
+    //                    projectile->coordinates.x * CELL_LENGTH_PIXELS,
+    //                    projectile->coordinates.y * CELL_LENGTH_PIXELS - 1,
+    //                    &I_tank_explosion);
+    //                continue;
+    //            }
+    //
+    //            const Icon *icon;
+    //
+    //            switch(projectile->direction) {
+    //            case DirectionUp:
+    //                icon = &I_projectile_up;
+    //                break;
+    //            case DirectionDown:
+    //                icon = &I_projectile_down;
+    //                break;
+    //            case DirectionRight:
+    //                icon = &I_projectile_right;
+    //                break;
+    //            case DirectionLeft:
+    //                icon = &I_projectile_left;
+    //                break;
+    //            default:
+    //                icon = &I_tank_explosion;
+    //            }
+    //
+    //            canvas_draw_icon(
+    //                canvas,
+    //                projectile->coordinates.x * CELL_LENGTH_PIXELS,
+    //                projectile->coordinates.y * CELL_LENGTH_PIXELS - 1,
+    //                icon);
+    //        }
+    //    }
+
+    // Info
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    char buffer1[13];
+    snprintf(buffer1, sizeof(buffer1), "live: %u", tanks_state->enemies_live);
+    canvas_draw_str_aligned(canvas, 127, 8, AlignRight, AlignBottom, buffer1);
+
+    snprintf(buffer1, sizeof(buffer1), "left: %u", tanks_state->enemies_left);
+    canvas_draw_str_aligned(canvas, 127, 18, AlignRight, AlignBottom, buffer1);
+
+    snprintf(buffer1, sizeof(buffer1), "p1 l: %u", tanks_state->p1->lives);
+    canvas_draw_str_aligned(canvas, 127, 28, AlignRight, AlignBottom, buffer1);
+
+    snprintf(buffer1, sizeof(buffer1), "p1 s: %u", tanks_state->p1->score);
+    canvas_draw_str_aligned(canvas, 127, 38, AlignRight, AlignBottom, buffer1);
+
+    if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2) {
+        snprintf(buffer1, sizeof(buffer1), "rec: %u", tanks_state->received);
+        canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1);
+
+        snprintf(buffer1, sizeof(buffer1), "snt: %u", tanks_state->sent);
+        canvas_draw_str_aligned(canvas, 127, 58, AlignRight, AlignBottom, buffer1);
+        //        snprintf(buffer1, sizeof(buffer1), "p2 l: %u", tanks_state->p2->lives);
+        //        canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1);
+        //
+        //        snprintf(buffer1, sizeof(buffer1), "p2 s: %u", tanks_state->p2->score);
+        //        canvas_draw_str_aligned(canvas, 127, 58, AlignRight, AlignBottom, buffer1);
+    }
+
+    if(tanks_state->state == GameStateCooperativeClient) {
+        snprintf(buffer1, sizeof(buffer1), "rec: %u", tanks_state->received);
+        canvas_draw_str_aligned(canvas, 127, 48, AlignRight, AlignBottom, buffer1);
+    }
+
+    // Game Over banner
+    if(tanks_state->state == GameStateGameOver) {
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_box(canvas, 34, 20, 62, 24);
+
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_frame(canvas, 34, 20, 62, 24);
+        canvas_set_font(canvas, FontPrimary);
+
+        if(tanks_state->enemies_left == 0 && tanks_state->enemies_live == 0) {
+            canvas_draw_str(canvas, 37, 31, "You win!");
+        } else {
+            canvas_draw_str(canvas, 37, 31, "Game Over");
+        }
+
+        canvas_set_font(canvas, FontSecondary);
+        char buffer[13];
+        snprintf(buffer, sizeof(buffer), "Score: %u", tanks_state->p1->score);
+        canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignBottom, buffer);
+    }
+
+    // TEST start
+    unsigned char* data = tanks_game_serialize(tanks_state);
+    tanks_game_deserialize_and_render(data, canvas);
+    // TEST enf
+
+    furi_mutex_release(tanks_state->mutex);
+}
+
+static void tanks_game_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    TanksEvent event = {.type = EventTypeKey, .input = *input_event};
+    furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void tanks_game_update_timer_callback(FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    TanksEvent event = {.type = EventTypeTick};
+    furi_message_queue_put(event_queue, &event, 0);
+}
+
+static bool tanks_get_cell_is_free(TanksState* const tanks_state, Point point) {
+    // Tiles
+    if(tanks_state->thisMap[point.x][point.y] != ' ') {
+        return false;
+    }
+
+    // Projectiles
+    for(int8_t x = 0; x < 100; x++) {
+        if(tanks_state->projectiles[x] != NULL) {
+            if(tanks_state->projectiles[x]->coordinates.x == point.x &&
+               tanks_state->projectiles[x]->coordinates.y == point.y) {
+                return false;
+            }
+        }
+    }
+
+    // Player 1
+    if(tanks_state->p1 != NULL) {
+        if(tanks_state->p1->coordinates.x == point.x &&
+           tanks_state->p1->coordinates.y == point.y) {
+            return false;
+        }
+    }
+
+    // Player 2
+    if(tanks_state->p2 != NULL) {
+        if(tanks_state->p2->coordinates.x == point.x &&
+           tanks_state->p2->coordinates.y == point.y) {
+            return false;
+        }
+    }
+
+    // Bots
+    for(int8_t x = 0; x < 6; x++) {
+        if(tanks_state->bots[x] != NULL) {
+            if(tanks_state->bots[x]->coordinates.x == point.x &&
+               tanks_state->bots[x]->coordinates.y == point.y) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+static uint8_t tanks_get_random_free_respawn_point_index(
+    TanksState* const tanks_state,
+    Point respawn_points[3]) {
+    uint8_t first = rand() % 3;
+    int8_t add = rand() % 2 ? +1 : -1;
+    int8_t second = first + add;
+    uint8_t third;
+
+    if(second == 4) {
+        second = 0;
+    } else if(second == -1) {
+        second = 3;
+    }
+
+    for(uint8_t i = 0; i < 3; i++) {
+        if(i != first && i != second) {
+            third = i;
+        }
+    }
+
+    if(tanks_get_cell_is_free(tanks_state, respawn_points[first])) {
+        return first;
+    }
+
+    if(tanks_get_cell_is_free(tanks_state, respawn_points[second])) {
+        return second;
+    }
+
+    if(tanks_get_cell_is_free(tanks_state, respawn_points[third])) {
+        return third;
+    }
+
+    return -1;
+}
+
+static void tanks_game_init_game(TanksState* const tanks_state, GameState type) {
+    srand(DWT->CYCCNT);
+
+    tanks_state->state = type;
+
+    for(int8_t x = 0; x < 100; x++) {
+        if(tanks_state->projectiles[x] != NULL) {
+            free(tanks_state->projectiles[x]);
+            tanks_state->projectiles[x] = NULL;
+        }
+    }
+
+    int8_t team_one_respawn_points_counter = 0;
+    int8_t team_two_respawn_points_counter = 0;
+
+    for(int8_t x = 0; x < FIELD_WIDTH; x++) {
+        for(int8_t y = 0; y < FIELD_HEIGHT; y++) {
+            tanks_state->thisMap[x][y] = ' ';
+
+            if(thisMap[y][x] == '1') {
+                Point respawn = {x, y};
+                tanks_state->team_one_respawn_points[team_one_respawn_points_counter++] = respawn;
+            }
+
+            if(thisMap[y][x] == '2') {
+                Point respawn = {x, y};
+                tanks_state->team_two_respawn_points[team_two_respawn_points_counter++] = respawn;
+            }
+
+            if(thisMap[y][x] == '-') {
+                tanks_state->thisMap[x][y] = '-';
+            }
+
+            if(thisMap[y][x] == '=') {
+                tanks_state->thisMap[x][y] = '=';
+            }
+
+            if(thisMap[y][x] == '*') {
+                tanks_state->thisMap[x][y] = '*';
+            }
+
+            if(thisMap[y][x] == 'a') {
+                tanks_state->thisMap[x][y] = 'a';
+            }
+        }
+    }
+
+    uint8_t index1 = tanks_get_random_free_respawn_point_index(
+        tanks_state, tanks_state->team_one_respawn_points);
+    Point c = {
+        tanks_state->team_one_respawn_points[index1].x,
+        tanks_state->team_one_respawn_points[index1].y};
+
+    PlayerState p1 = {
+        c,
+        0,
+        4,
+        DirectionRight,
+        0,
+        0,
+        1,
+        SHOT_COOLDOWN,
+        PLAYER_RESPAWN_COOLDOWN,
+    };
+
+    PlayerState* p1_state = malloc(sizeof(PlayerState));
+    *p1_state = p1;
+
+    tanks_state->p1 = p1_state;
+
+    if(type == GameStateCooperativeServer) {
+        int8_t index2 = tanks_get_random_free_respawn_point_index(
+            tanks_state, tanks_state->team_one_respawn_points);
+        Point c = {
+            tanks_state->team_one_respawn_points[index2].x,
+            tanks_state->team_one_respawn_points[index2].y};
+
+        PlayerState p2 = {
+            c,
+            0,
+            4,
+            DirectionRight,
+            0,
+            0,
+            1,
+            SHOT_COOLDOWN,
+            PLAYER_RESPAWN_COOLDOWN,
+        };
+
+        PlayerState* p2_state = malloc(sizeof(PlayerState));
+        *p2_state = p2;
+
+        tanks_state->p2 = p2_state;
+    }
+
+    tanks_state->enemies_left = 5;
+    tanks_state->enemies_live = 0;
+    tanks_state->enemies_respawn_cooldown = RESPAWN_COOLDOWN;
+    tanks_state->received = 0;
+    tanks_state->sent = 0;
+
+    if(type == GameStateCooperativeClient) {
+        for(int8_t x = 0; x < FIELD_WIDTH; x++) {
+            for(int8_t y = 0; y < FIELD_HEIGHT; y++) {
+                tanks_state->thisMap[x][y] = CellEmpty;
+            }
+        }
+    }
+}
+
+static bool
+    tanks_game_collision(Point const next_step, bool shoot, TanksState const* const tanks_state) {
+    if((int8_t)next_step.x < 0 || (int8_t)next_step.y < 0) {
+        return true;
+    }
+
+    if(next_step.x >= FIELD_WIDTH || next_step.y >= FIELD_HEIGHT) {
+        return true;
+    }
+
+    char tile = tanks_state->thisMap[next_step.x][next_step.y];
+
+    if(tile == '*' && !shoot) {
+        return true;
+    }
+
+    if(tile == '-' || tile == '=' || tile == 'a') {
+        return true;
+    }
+
+    for(uint8_t i = 0; i < 6; i++) {
+        if(tanks_state->bots[i] != NULL) {
+            if(tanks_state->bots[i]->coordinates.x == next_step.x &&
+               tanks_state->bots[i]->coordinates.y == next_step.y) {
+                return true;
+            }
+        }
+    }
+
+    if(tanks_state->p1 != NULL && tanks_state->p1->live &&
+       tanks_state->p1->coordinates.x == next_step.x &&
+       tanks_state->p1->coordinates.y == next_step.y) {
+        return true;
+    }
+
+    if(tanks_state->p2 != NULL && tanks_state->p2->live &&
+       tanks_state->p2->coordinates.x == next_step.x &&
+       tanks_state->p2->coordinates.y == next_step.y) {
+        return true;
+    }
+
+    return false;
+}
+
+static Point tanks_game_get_next_step(Point coordinates, Direction direction) {
+    Point next_step = {coordinates.x, coordinates.y};
+
+    switch(direction) {
+    // +-----x
+    // |
+    // |
+    // y
+    case DirectionUp:
+        next_step.y--;
+        break;
+    case DirectionRight:
+        next_step.x++;
+        break;
+    case DirectionDown:
+        next_step.y++;
+        break;
+    case DirectionLeft:
+        next_step.x--;
+        break;
+    default:
+        break;
+    }
+    return next_step;
+}
+
+static uint8_t tanks_game_get_free_projectile_index(TanksState* const tanks_state) {
+    uint8_t freeProjectileIndex;
+    for(freeProjectileIndex = 0; freeProjectileIndex < 100; freeProjectileIndex++) {
+        if(tanks_state->projectiles[freeProjectileIndex] == NULL) {
+            return freeProjectileIndex;
+        }
+    }
+
+    return 0;
+}
+
+static void tanks_game_shoot(
+    TanksState* const tanks_state,
+    PlayerState* tank_state,
+    bool is_p1,
+    bool is_p2) {
+    tank_state->cooldown = SHOT_COOLDOWN;
+
+    uint8_t freeProjectileIndex = tanks_game_get_free_projectile_index(tanks_state);
+
+    ProjectileState* projectile_state = malloc(sizeof(ProjectileState));
+    Point next_step = tanks_game_get_next_step(tank_state->coordinates, tank_state->direction);
+
+    projectile_state->direction = tank_state->direction;
+    projectile_state->coordinates = next_step;
+    projectile_state->is_p1 = is_p1;
+    projectile_state->is_p2 = is_p2;
+
+    bool crush = tanks_game_collision(projectile_state->coordinates, true, tanks_state);
+    projectile_state->explosion = crush;
+
+    tanks_state->projectiles[freeProjectileIndex] = projectile_state;
+}
+
+static void tanks_game_process_game_step(TanksState* const tanks_state) {
+    if(tanks_state->state == GameStateMenu) {
+        return;
+    }
+
+    if(tanks_state->enemies_left == 0 && tanks_state->enemies_live == 0) {
+        tanks_state->state = GameStateGameOver;
+    }
+
+    if(!tanks_state->p1->live && tanks_state->p1->lives == 0) {
+        tanks_state->state = GameStateGameOver;
+    }
+
+    if(tanks_state->state == GameStateGameOver) {
+        return;
+    }
+
+    if(tanks_state->p1 != NULL) {
+        if(!tanks_state->p1->live && tanks_state->p1->respawn_cooldown > 0) {
+            tanks_state->p1->respawn_cooldown--;
+        }
+    }
+
+    // Player 1 spawn
+    if(tanks_state->p1 && !tanks_state->p1->live && tanks_state->p1->lives > 0) {
+        int8_t index = tanks_get_random_free_respawn_point_index(
+            tanks_state, tanks_state->team_one_respawn_points);
+
+        if(index != -1) {
+            Point point = tanks_state->team_one_respawn_points[index];
+            Point c = {point.x, point.y};
+            tanks_state->p1->coordinates = c;
+            tanks_state->p1->live = true;
+            tanks_state->p1->direction = DirectionRight;
+            tanks_state->p1->cooldown = SHOT_COOLDOWN;
+            tanks_state->p1->respawn_cooldown = SHOT_COOLDOWN;
+        }
+    }
+
+    // Player 2 spawn
+    if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2 &&
+       !tanks_state->p2->live && tanks_state->p2->lives > 0) {
+        int8_t index = tanks_get_random_free_respawn_point_index(
+            tanks_state, tanks_state->team_one_respawn_points);
+
+        if(index != -1) {
+            Point point = tanks_state->team_one_respawn_points[index];
+            Point c = {point.x, point.y};
+            tanks_state->p2->coordinates = c;
+            tanks_state->p2->live = true;
+            tanks_state->p2->direction = DirectionRight;
+            tanks_state->p2->cooldown = SHOT_COOLDOWN;
+            tanks_state->p2->respawn_cooldown = SHOT_COOLDOWN;
+        }
+    }
+
+    // Bot turn
+    for(uint8_t i = 0; i < 6; i++) {
+        if(tanks_state->bots[i] != NULL) {
+            PlayerState* bot = tanks_state->bots[i];
+            if(bot->cooldown) {
+                bot->cooldown--;
+            }
+
+            // Rotate
+            if(rand() % 3 == 0) {
+                bot->direction = (rand() % 4);
+            }
+
+            // Move
+            if(rand() % 2 == 0) {
+                Point next_step = tanks_game_get_next_step(bot->coordinates, bot->direction);
+                bool crush = tanks_game_collision(next_step, false, tanks_state);
+
+                if(!crush) {
+                    bot->coordinates = next_step;
+                }
+            }
+
+            // Shoot
+            if(bot->cooldown == 0 && rand() % 3 != 0) {
+                tanks_game_shoot(tanks_state, bot, false, false);
+            }
+        }
+    }
+
+    // Bot spawn
+    if(tanks_state->enemies_respawn_cooldown) {
+        tanks_state->enemies_respawn_cooldown--;
+    }
+
+    if(tanks_state->enemies_left > 0 && tanks_state->enemies_live <= 4 &&
+       tanks_state->enemies_respawn_cooldown == 0) {
+        int8_t index = tanks_get_random_free_respawn_point_index(
+            tanks_state, tanks_state->team_two_respawn_points);
+
+        if(index != -1) {
+            tanks_state->enemies_left--;
+            tanks_state->enemies_live++;
+            tanks_state->enemies_respawn_cooldown = RESPAWN_COOLDOWN;
+            Point point = tanks_state->team_two_respawn_points[index];
+
+            Point c = {point.x, point.y};
+
+            PlayerState bot = {
+                c,
+                0,
+                0,
+                DirectionLeft,
+                0,
+                0,
+                1,
+                SHOT_COOLDOWN,
+                PLAYER_RESPAWN_COOLDOWN,
+            };
+
+            uint8_t freeEnemyIndex;
+            for(freeEnemyIndex = 0; freeEnemyIndex < 6; freeEnemyIndex++) {
+                if(tanks_state->bots[freeEnemyIndex] == NULL) {
+                    break;
+                }
+            }
+
+            PlayerState* bot_state = malloc(sizeof(PlayerState));
+            *bot_state = bot;
+
+            tanks_state->bots[freeEnemyIndex] = bot_state;
+        }
+    }
+
+    if(tanks_state->p1 != NULL && tanks_state->p1->live && tanks_state->p1->moving) {
+        Point next_step =
+            tanks_game_get_next_step(tanks_state->p1->coordinates, tanks_state->p1->direction);
+        bool crush = tanks_game_collision(next_step, false, tanks_state);
+
+        if(!crush) {
+            tanks_state->p1->coordinates = next_step;
+        }
+    }
+
+    // Player 2 spawn
+    if(tanks_state->state == GameStateCooperativeServer && tanks_state->p2 &&
+       tanks_state->p2->live && tanks_state->p2->moving) {
+        Point next_step =
+            tanks_game_get_next_step(tanks_state->p2->coordinates, tanks_state->p2->direction);
+        bool crush = tanks_game_collision(next_step, false, tanks_state);
+
+        if(!crush) {
+            tanks_state->p2->coordinates = next_step;
+        }
+    }
+
+    for(int8_t x = 0; x < 100; x++) {
+        if(tanks_state->projectiles[x] != NULL) {
+            ProjectileState* projectile = tanks_state->projectiles[x];
+            Point c = projectile->coordinates;
+
+            if(projectile->explosion) {
+                // Break a wall
+                if(tanks_state->thisMap[c.x][c.y] == '-') {
+                    tanks_state->thisMap[c.x][c.y] = ' ';
+                }
+
+                // Kill a bot
+                for(uint8_t i = 0; i < 6; i++) {
+                    if(tanks_state->bots[i] != NULL) {
+                        if(tanks_state->bots[i]->coordinates.x == c.x &&
+                           tanks_state->bots[i]->coordinates.y == c.y) {
+                            if(projectile->is_p1) {
+                                tanks_state->p1->score++;
+                            }
+
+                            if(projectile->is_p2) {
+                                tanks_state->p2->score++;
+                            }
+
+                            // No friendly fire
+                            if(projectile->is_p1 || projectile->is_p2) {
+                                tanks_state->enemies_live--;
+                                free(tanks_state->bots[i]);
+                                tanks_state->bots[i] = NULL;
+                            }
+                        }
+                    }
+                }
+
+                // Destroy the flag
+                if(tanks_state->thisMap[c.x][c.y] == 'a') {
+                    tanks_state->state = GameStateGameOver;
+                    return;
+                }
+
+                // Kill a player 1
+                if(tanks_state->p1 != NULL) {
+                    if(tanks_state->p1->live && tanks_state->p1->coordinates.x == c.x &&
+                       tanks_state->p1->coordinates.y == c.y) {
+                        tanks_state->p1->live = false;
+                        tanks_state->p1->lives--;
+                        tanks_state->p1->respawn_cooldown = PLAYER_RESPAWN_COOLDOWN;
+                    }
+                }
+
+                // Kill a player 2
+                if(tanks_state->p2 != NULL) {
+                    if(tanks_state->p2->live && tanks_state->p2->coordinates.x == c.x &&
+                       tanks_state->p2->coordinates.y == c.y) {
+                        tanks_state->p2->live = false;
+                        tanks_state->p2->lives--;
+                        tanks_state->p2->respawn_cooldown = PLAYER_RESPAWN_COOLDOWN;
+                    }
+                }
+
+                // Delete projectile
+                free(tanks_state->projectiles[x]);
+                tanks_state->projectiles[x] = NULL;
+                continue;
+            }
+
+            Point next_step =
+                tanks_game_get_next_step(projectile->coordinates, projectile->direction);
+            bool crush = tanks_game_collision(next_step, true, tanks_state);
+            projectile->coordinates = next_step;
+
+            if(crush) {
+                projectile->explosion = true;
+            }
+        }
+    }
+
+    if(tanks_state->p1->cooldown > 0) {
+        tanks_state->p1->cooldown--;
+    }
+
+    if(tanks_state->p2 != NULL && tanks_state->p2->cooldown > 0) {
+        tanks_state->p2->cooldown--;
+    }
+
+    if(tanks_state->p1 != NULL && tanks_state->p1->live && tanks_state->p1->shooting &&
+       tanks_state->p1->cooldown == 0) {
+        tanks_game_shoot(tanks_state, tanks_state->p1, true, false);
+    }
+
+    tanks_state->p1->moving = false;
+    tanks_state->p1->shooting = false;
+
+    if(tanks_state->p2 != NULL) {
+        tanks_state->p2->moving = false;
+        tanks_state->p2->shooting = false;
+    }
+}
+
+int32_t tanks_game_app(void* p) {
+    UNUSED(p);
+    srand(DWT->CYCCNT);
+
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TanksEvent));
+
+    TanksState* tanks_state = malloc(sizeof(TanksState));
+
+    tanks_state->state = GameStateMenu;
+    tanks_state->menu_state = MenuStateSingleMode;
+
+    tanks_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!tanks_state->mutex) {
+        FURI_LOG_E("Tanks", "cannot create mutex\r\n");
+        furi_message_queue_free(event_queue);
+        free(tanks_state);
+        return 255;
+    }
+
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, tanks_game_render_callback, tanks_state);
+    view_port_input_callback_set(view_port, tanks_game_input_callback, event_queue);
+
+    FuriTimer* timer =
+        furi_timer_alloc(tanks_game_update_timer_callback, FuriTimerTypePeriodic, event_queue);
+    furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4);
+
+    // Open GUI and register view_port
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    TanksEvent event;
+
+    // Initialize network thing.
+    uint32_t frequency = 433920000;
+    size_t message_max_len = 180;
+    uint8_t incomingMessage[180] = {0};
+    SubGhzTxRxWorker* subghz_txrx = subghz_tx_rx_worker_alloc();
+
+    subghz_devices_init();
+    const SubGhzDevice* subghz_device =
+        radio_device_loader_set(NULL, SubGhzRadioDeviceTypeExternalCC1101);
+
+    subghz_devices_reset(subghz_device);
+    subghz_devices_load_preset(subghz_device, FuriHalSubGhzPresetOok650Async, NULL);
+
+    subghz_tx_rx_worker_start(subghz_txrx, subghz_device, frequency);
+    furi_hal_power_suppress_charge_enter();
+
+    for(bool processing = true; processing;) {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+
+        furi_mutex_acquire(tanks_state->mutex, FuriWaitForever);
+
+        if(event_status == FuriStatusOk) {
+            // press events
+            if(event.type == EventTypeKey) {
+                if(event.input.type == InputTypePress) {
+                    switch(event.input.key) {
+                    case InputKeyUp:
+                        if(tanks_state->state == GameStateMenu) {
+                            if(tanks_state->menu_state == MenuStateCooperativeServerMode) {
+                                tanks_state->menu_state = MenuStateSingleMode;
+                            } else if(tanks_state->menu_state == MenuStateCooperativeClientMode) {
+                                tanks_state->menu_state = MenuStateCooperativeServerMode;
+                            }
+                        } else if(tanks_state->state == GameStateCooperativeClient) {
+                            FuriString* goesUp = furi_string_alloc();
+                            char arr[2];
+                            arr[0] = GoesUp;
+                            arr[1] = 0;
+                            furi_string_set(goesUp, (char*)&arr);
+
+                            subghz_tx_rx_worker_write(
+                                subghz_txrx,
+                                (uint8_t*)furi_string_get_cstr(goesUp),
+                                strlen(furi_string_get_cstr(goesUp)));
+                            furi_string_free(goesUp);
+                        } else {
+                            tanks_state->p1->moving = true;
+                            tanks_state->p1->direction = DirectionUp;
+                        }
+                        break;
+                    case InputKeyDown:
+                        if(tanks_state->state == GameStateMenu) {
+                            if(tanks_state->menu_state == MenuStateSingleMode) {
+                                tanks_state->menu_state = MenuStateCooperativeServerMode;
+                            } else if(tanks_state->menu_state == MenuStateCooperativeServerMode) {
+                                tanks_state->menu_state = MenuStateCooperativeClientMode;
+                            }
+                        } else if(tanks_state->state == GameStateCooperativeClient) {
+                            FuriString* goesDown = furi_string_alloc();
+                            char arr[2];
+                            arr[0] = GoesDown;
+                            arr[1] = 0;
+                            furi_string_set(goesDown, (char*)&arr);
+
+                            subghz_tx_rx_worker_write(
+                                subghz_txrx,
+                                (uint8_t*)furi_string_get_cstr(goesDown),
+                                strlen(furi_string_get_cstr(goesDown)));
+                            furi_string_free(goesDown);
+                        } else {
+                            tanks_state->p1->moving = true;
+                            tanks_state->p1->direction = DirectionDown;
+                        }
+                        break;
+                    case InputKeyRight:
+                        if(!(tanks_state->state == GameStateMenu)) {
+                            if(tanks_state->state == GameStateCooperativeClient) {
+                                FuriString* goesRight = furi_string_alloc();
+                                char arr[2];
+                                arr[0] = GoesRight;
+                                arr[1] = 0;
+                                furi_string_set(goesRight, (char*)&arr);
+
+                                subghz_tx_rx_worker_write(
+                                    subghz_txrx,
+                                    (uint8_t*)furi_string_get_cstr(goesRight),
+                                    strlen(furi_string_get_cstr(goesRight)));
+                                furi_string_free(goesRight);
+                            } else {
+                                tanks_state->p1->moving = true;
+                                tanks_state->p1->direction = DirectionRight;
+                            }
+                        }
+                        break;
+                    case InputKeyLeft:
+                        if(!(tanks_state->state == GameStateMenu)) {
+                            if(tanks_state->state == GameStateCooperativeClient) {
+                                FuriString* goesLeft = furi_string_alloc();
+                                char arr[2];
+                                arr[0] = GoesLeft;
+                                arr[1] = 0;
+                                furi_string_set(goesLeft, (char*)&arr);
+
+                                subghz_tx_rx_worker_write(
+                                    subghz_txrx,
+                                    (uint8_t*)furi_string_get_cstr(goesLeft),
+                                    strlen(furi_string_get_cstr(goesLeft)));
+                                furi_string_free(goesLeft);
+                            } else {
+                                tanks_state->p1->moving = true;
+                                tanks_state->p1->direction = DirectionLeft;
+                            }
+                        }
+                        break;
+                    case InputKeyOk:
+                        if(tanks_state->state == GameStateMenu) {
+                            if(tanks_state->menu_state == MenuStateSingleMode) {
+                                tanks_state->server = true;
+                                tanks_game_init_game(tanks_state, GameStateSingle);
+                                break;
+                            } else if(tanks_state->menu_state == MenuStateCooperativeServerMode) {
+                                tanks_state->server = true;
+                                tanks_game_init_game(tanks_state, GameStateCooperativeServer);
+                                break;
+                            } else if(tanks_state->menu_state == MenuStateCooperativeClientMode) {
+                                tanks_state->server = false;
+                                tanks_game_init_game(tanks_state, GameStateCooperativeClient);
+                                break;
+                            }
+                        } else if(tanks_state->state == GameStateGameOver) {
+                            tanks_game_init_game(tanks_state, tanks_state->state);
+                        } else if(tanks_state->state == GameStateCooperativeClient) {
+                            FuriString* shoots = furi_string_alloc();
+                            char arr[2];
+                            arr[0] = Shoots;
+                            arr[1] = 0;
+                            furi_string_set(shoots, (char*)&arr);
+
+                            subghz_tx_rx_worker_write(
+                                subghz_txrx,
+                                (uint8_t*)furi_string_get_cstr(shoots),
+                                strlen(furi_string_get_cstr(shoots)));
+                            furi_string_free(shoots);
+                        } else {
+                            tanks_state->p1->shooting = true;
+                        }
+                        break;
+                    case InputKeyBack:
+                        processing = false;
+                        break;
+                    default:
+                        break;
+                    }
+                }
+            } else if(event.type == EventTypeTick) {
+                if(tanks_state->state == GameStateCooperativeServer) {
+                    if(subghz_tx_rx_worker_available(subghz_txrx)) {
+                        memset(incomingMessage, 0x00, message_max_len);
+                        subghz_tx_rx_worker_read(subghz_txrx, incomingMessage, message_max_len);
+
+                        if(incomingMessage != NULL) {
+                            tanks_state->received++;
+
+                            switch(incomingMessage[0]) {
+                            case GoesUp:
+                                tanks_state->p2->moving = true;
+                                tanks_state->p2->direction = DirectionUp;
+                                break;
+                            case GoesRight:
+                                tanks_state->p2->moving = true;
+                                tanks_state->p2->direction = DirectionRight;
+                                break;
+                            case GoesDown:
+                                tanks_state->p2->moving = true;
+                                tanks_state->p2->direction = DirectionDown;
+                                break;
+                            case GoesLeft:
+                                tanks_state->p2->moving = true;
+                                tanks_state->p2->direction = DirectionLeft;
+                                break;
+                            case Shoots:
+                                tanks_state->p2->shooting = true;
+                                break;
+                            default:
+                                break;
+                            }
+                        }
+                    }
+
+                    tanks_game_process_game_step(tanks_state);
+
+                    FuriString* serializedData = furi_string_alloc();
+                    unsigned char* data = tanks_game_serialize(tanks_state);
+                    char arr[11 * 16 + 1];
+
+                    for(uint8_t i = 0; i < 11 * 16; i++) {
+                        arr[i] = data[i];
+                    }
+
+                    arr[11 * 16] = 0;
+
+                    furi_string_set(serializedData, (char*)&arr);
+
+                    subghz_tx_rx_worker_write(
+                        subghz_txrx,
+                        (uint8_t*)furi_string_get_cstr(serializedData),
+                        strlen(furi_string_get_cstr(serializedData)));
+                    furi_string_free(serializedData);
+                    tanks_state->sent++;
+                } else if(tanks_state->state == GameStateSingle) {
+                    tanks_game_process_game_step(tanks_state);
+                } else if(tanks_state->state == GameStateCooperativeClient) {
+                    if(subghz_tx_rx_worker_available(subghz_txrx)) {
+                        memset(incomingMessage, 0x00, message_max_len);
+                        subghz_tx_rx_worker_read(subghz_txrx, incomingMessage, message_max_len);
+
+                        tanks_state->received++;
+
+                        tanks_game_deserialize_and_write_to_state(
+                            (unsigned char*)incomingMessage, tanks_state);
+                    }
+                }
+            }
+        } else {
+            // event timeout
+        }
+
+        furi_mutex_release(tanks_state->mutex);
+        view_port_update(view_port);
+        furi_delay_ms(1);
+    }
+
+    furi_delay_ms(10);
+    furi_hal_power_suppress_charge_exit();
+
+    if(subghz_tx_rx_worker_is_running(subghz_txrx)) {
+        subghz_tx_rx_worker_stop(subghz_txrx);
+        subghz_tx_rx_worker_free(subghz_txrx);
+    }
+
+    subghz_devices_deinit();
+
+    furi_timer_free(timer);
+    view_port_enabled_set(view_port, false);
+    gui_remove_view_port(gui, view_port);
+    furi_record_close(RECORD_GUI);
+    view_port_free(view_port);
+    furi_message_queue_free(event_queue);
+    furi_mutex_free(tanks_state->mutex);
+
+    if(tanks_state->p1 != NULL) {
+        free(tanks_state->p1);
+    }
+
+    if(tanks_state->p2 != NULL) {
+        free(tanks_state->p2);
+    }
+
+    free(tanks_state);
+
+    return 0;
+}