mirror of
https://github.com/UberGuidoZ/Flipper.git
synced 2024-12-23 15:00:13 +00:00
1457 lines
46 KiB
C
1457 lines
46 KiB
C
|
#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 "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;
|
||
|
} 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) {
|
||
|
const TanksState* tanks_state = acquire_mutex((ValueMutex*)ctx, 25);
|
||
|
if(tanks_state == NULL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
|
||
|
release_mutex((ValueMutex*)ctx, tanks_state);
|
||
|
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);
|
||
|
|
||
|
release_mutex((ValueMutex*)ctx, tanks_state);
|
||
|
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
|
||
|
|
||
|
release_mutex((ValueMutex*)ctx, tanks_state);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
ValueMutex state_mutex;
|
||
|
if(!init_mutex(&state_mutex, tanks_state, sizeof(TanksState))) {
|
||
|
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, &state_mutex);
|
||
|
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_tx_rx_worker_start(subghz_txrx, frequency);
|
||
|
furi_hal_power_suppress_charge_enter();
|
||
|
|
||
|
for(bool processing = true; processing;) {
|
||
|
FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
|
||
|
|
||
|
TanksState* tanks_state = (TanksState*)acquire_mutex_block(&state_mutex);
|
||
|
|
||
|
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 = NULL;
|
||
|
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)));
|
||
|
|
||
|
} 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 = NULL;
|
||
|
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)));
|
||
|
} else {
|
||
|
tanks_state->p1->moving = true;
|
||
|
tanks_state->p1->direction = DirectionDown;
|
||
|
}
|
||
|
break;
|
||
|
case InputKeyRight:
|
||
|
if(tanks_state->state == GameStateCooperativeClient) {
|
||
|
FuriString* goesRight = NULL;
|
||
|
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)));
|
||
|
} else {
|
||
|
tanks_state->p1->moving = true;
|
||
|
tanks_state->p1->direction = DirectionRight;
|
||
|
}
|
||
|
break;
|
||
|
case InputKeyLeft:
|
||
|
if(tanks_state->state == GameStateCooperativeClient) {
|
||
|
FuriString* goesLeft = NULL;
|
||
|
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)));
|
||
|
} 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 = NULL;
|
||
|
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)));
|
||
|
} 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 = NULL;
|
||
|
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)));
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
view_port_update(view_port);
|
||
|
release_mutex(&state_mutex, tanks_state);
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
delete_mutex(&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;
|
||
|
}
|