#include #include #include #include #include #include #include #include #include #include #include #include #include #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; }