feat: implement safe state presentation and bounded serialization
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
# without default 'CMakeLists.txt' file.
|
||||
|
||||
idf_component_register(
|
||||
SRCS "main.c" "application.c" "command_queue.c" "fleet_generator.c" "game_engine.c" "bot_player.c" "session_manager.c" "game_lifecycle.c"
|
||||
SRCS "main.c" "application.c" "command_queue.c" "fleet_generator.c" "game_engine.c" "bot_player.c" "session_manager.c" "game_lifecycle.c" "state_presenter.c"
|
||||
INCLUDE_DIRS "../include"
|
||||
REQUIRES esp_event esp_http_server esp_netif esp_wifi esp_littlefs nvs_flash
|
||||
)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "state_presenter.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct { char data[kStateMessageCapacity]; size_t length; } state_writer_t;
|
||||
|
||||
static bool append_character(state_writer_t *writer, char character) {
|
||||
if (writer->length + 1U >= sizeof(writer->data)) return false;
|
||||
writer->data[writer->length++] = character;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool append_text(state_writer_t *writer, const char *text) {
|
||||
while (*text != '\0') if (!append_character(writer, *text++)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool append_u32(state_writer_t *writer, uint32_t value) {
|
||||
char digits[10];
|
||||
uint8_t count = 0;
|
||||
do { digits[count++] = (char)('0' + value % 10U); value /= 10U; } while (value != 0U);
|
||||
while (count > 0U) if (!append_character(writer, digits[--count])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *phase_name(phase_t phase) {
|
||||
static const char *const names[] = {"lobby", "preparing", "in_progress", "finished", "rematch_wait"};
|
||||
return phase <= PHASE_REMATCH_WAIT ? names[phase] : NULL;
|
||||
}
|
||||
|
||||
static const char *mode_name(game_mode_t mode) {
|
||||
static const char *const names[] = {"human", "bot"};
|
||||
return mode <= MODE_BOT ? names[mode] : NULL;
|
||||
}
|
||||
|
||||
static const char *role_name(role_t role) {
|
||||
static const char *const names[] = {"player1", "player2", "spectator"};
|
||||
return role <= ROLE_SPECTATOR ? names[role] : NULL;
|
||||
}
|
||||
|
||||
static char present_cell(cell_t cell, bool reveal_ships) {
|
||||
if (cell == CELL_MISS) return '2';
|
||||
if (cell == CELL_HIT) return '3';
|
||||
return cell == CELL_SHIP && reveal_ships ? '1' : '0';
|
||||
}
|
||||
|
||||
static bool append_board(state_writer_t *writer, const board_t *board, bool reveal_ships) {
|
||||
for (uint8_t index = 0; index < kBoardCellCount; ++index) {
|
||||
if (!append_character(writer, present_cell(board->cells[index], reveal_ships))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write_state(const game_state_t *state, role_t viewer, const cumulative_statistics_t *cumulative,
|
||||
char *output, size_t output_size, size_t *written) {
|
||||
if (written != NULL) *written = 0;
|
||||
if (state == NULL || output == NULL || output_size == 0U || phase_name(state->phase) == NULL ||
|
||||
mode_name(state->mode) == NULL || role_name(viewer) == NULL || state->current_player >= kPlayerCapacity) return false;
|
||||
state_writer_t writer = {0};
|
||||
const bool finished = state->phase == PHASE_FINISHED;
|
||||
const uint16_t wins_0 = cumulative == NULL ? 0U : cumulative[0].wins;
|
||||
const uint16_t wins_1 = cumulative == NULL ? 0U : cumulative[1].wins;
|
||||
const bool reveal[2] = {finished || viewer == ROLE_PLAYER_1, finished || viewer == ROLE_PLAYER_2};
|
||||
if (!append_text(&writer, "{\"type\":\"state\",\"version\":" ) || !append_u32(&writer, state->version) ||
|
||||
!append_text(&writer, ",\"gameId\":") || !append_u32(&writer, state->game_id) ||
|
||||
!append_text(&writer, ",\"phase\":\"") || !append_text(&writer, phase_name(state->phase)) ||
|
||||
!append_text(&writer, "\",\"mode\":\"") || !append_text(&writer, mode_name(state->mode)) ||
|
||||
!append_text(&writer, "\",\"viewer\":\"") || !append_text(&writer, role_name(viewer)) ||
|
||||
!append_text(&writer, "\",\"turn\":\"") || !append_text(&writer, role_name((role_t)state->current_player)) ||
|
||||
!append_text(&writer, "\",\"boards\":[\"") || !append_board(&writer, &state->boards[0], reveal[0]) ||
|
||||
!append_text(&writer, "\",\"") || !append_board(&writer, &state->boards[1], reveal[1]) ||
|
||||
!append_text(&writer, "\"],\"wins\":[") || !append_u32(&writer, wins_0) ||
|
||||
!append_character(&writer, ',') || !append_u32(&writer, wins_1) || !append_text(&writer, "]}")) return false;
|
||||
if (writer.length + 1U > output_size) return false;
|
||||
writer.data[writer.length] = '\0';
|
||||
memcpy(output, writer.data, writer.length + 1U);
|
||||
if (written != NULL) *written = writer.length;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool state_presenter_write(const game_state_t *state, role_t viewer, char *output, size_t output_size, size_t *written) {
|
||||
return write_state(state, viewer, NULL, output, output_size, written);
|
||||
}
|
||||
|
||||
bool state_presenter_write_lifecycle(const game_lifecycle_t *lifecycle, role_t viewer,
|
||||
char *output, size_t output_size, size_t *written) {
|
||||
return lifecycle != NULL && write_state(&lifecycle->game.state, viewer, lifecycle->cumulative,
|
||||
output, output_size, written);
|
||||
}
|
||||
Reference in New Issue
Block a user