#include "maze32_lobby.h" #include static int is_valid_name(const char* name) { if (name == NULL) { return 0; } size_t length = strlen(name); if (length == 0U || length > MAZE32_PLAYER_NAME_MAX_BYTES || name[0] == ' ' || name[length - 1U] == ' ') { return 0; } for (size_t index = 0U; index < length; ++index) { unsigned char byte = (unsigned char)name[index]; if (byte < 0x20U || byte == 0x7fU) { return 0; } } return 1; } static int is_valid_token(const char* token) { if (token == NULL || strlen(token) != MAZE32_SESSION_TOKEN_HEX_LENGTH) { return 0; } for (size_t index = 0U; index < MAZE32_SESSION_TOKEN_HEX_LENGTH; ++index) { char character = token[index]; if (!((character >= '0' && character <= '9') || (character >= 'a' && character <= 'f'))) { return 0; } } return 1; } static maze32_session_t* find_player_slot(maze32_lobby_t* lobby, unsigned int slot) { for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { maze32_session_t* session = &lobby->sessions[index]; if (session->role == MAZE32_ROLE_PLAYER && session->player_slot == slot) { return session; } } return NULL; } static maze32_session_t* find_free_session(maze32_lobby_t* lobby) { for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { if (lobby->sessions[index].role == MAZE32_ROLE_NONE) { return &lobby->sessions[index]; } } return NULL; } static maze32_session_t* find_reusable_observer(maze32_lobby_t* lobby) { for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { maze32_session_t* session = &lobby->sessions[index]; if (session->role == MAZE32_ROLE_OBSERVER && !session->connected) { return session; } } return NULL; } void maze32_lobby_init(maze32_lobby_t* lobby) { if (lobby == NULL) { return; } memset(lobby, 0, sizeof(*lobby)); lobby->phase = MAZE32_LOBBY_WAITING; } maze32_lobby_result_t maze32_lobby_join_player(maze32_lobby_t* lobby, int connection_id, const char* name, const char* token, maze32_session_t** session) { if (lobby == NULL || session == NULL || !is_valid_name(name) || !is_valid_token(token)) { return MAZE32_LOBBY_INVALID_NAME; } if (lobby->phase != MAZE32_LOBBY_WAITING) { return MAZE32_LOBBY_INVALID_PHASE; } for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { if (lobby->sessions[index].role == MAZE32_ROLE_PLAYER && strcmp(lobby->sessions[index].name, name) == 0) { return MAZE32_LOBBY_DUPLICATE_NAME; } } unsigned int slot = find_player_slot(lobby, 1U) == NULL ? 1U : 2U; if (find_player_slot(lobby, slot) != NULL) { return MAZE32_LOBBY_ROLE_FULL; } maze32_session_t* target = find_free_session(lobby); if (target == NULL) { return MAZE32_LOBBY_ROLE_FULL; } target->connection_id = connection_id; target->role = MAZE32_ROLE_PLAYER; target->player_slot = slot; target->connected = 1; memcpy(target->name, name, strlen(name) + 1U); memcpy(target->token, token, MAZE32_SESSION_TOKEN_HEX_LENGTH + 1U); lobby->revision++; *session = target; return MAZE32_LOBBY_OK; } maze32_lobby_result_t maze32_lobby_join_observer(maze32_lobby_t* lobby, int connection_id, const char* token, maze32_session_t** session) { if (lobby == NULL || session == NULL || !is_valid_token(token)) { return MAZE32_LOBBY_INVALID_TOKEN; } if (maze32_lobby_connected_observers(lobby) >= MAZE32_OBSERVER_LIMIT) { return MAZE32_LOBBY_ROLE_FULL; } maze32_session_t* target = find_free_session(lobby); if (target == NULL) { target = find_reusable_observer(lobby); } if (target == NULL) { return MAZE32_LOBBY_ROLE_FULL; } memset(target, 0, sizeof(*target)); target->connection_id = connection_id; target->role = MAZE32_ROLE_OBSERVER; target->connected = 1; memcpy(target->token, token, MAZE32_SESSION_TOKEN_HEX_LENGTH + 1U); lobby->revision++; *session = target; return MAZE32_LOBBY_OK; } maze32_lobby_result_t maze32_lobby_restore(maze32_lobby_t* lobby, int connection_id, const char* token, maze32_session_t** session) { if (lobby == NULL || session == NULL || !is_valid_token(token)) { return MAZE32_LOBBY_INVALID_TOKEN; } for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { maze32_session_t* candidate = &lobby->sessions[index]; if (candidate->role != MAZE32_ROLE_NONE && strcmp(candidate->token, token) == 0) { if (candidate->connected) { return MAZE32_LOBBY_SESSION_CONNECTED; } candidate->connected = 1; candidate->connection_id = connection_id; lobby->revision++; *session = candidate; return MAZE32_LOBBY_OK; } } return MAZE32_LOBBY_INVALID_TOKEN; } void maze32_lobby_disconnect(maze32_lobby_t* lobby, int connection_id) { if (lobby == NULL) { return; } for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { maze32_session_t* session = &lobby->sessions[index]; if (session->role != MAZE32_ROLE_NONE && session->connected && session->connection_id == connection_id) { session->connected = 0; session->connection_id = -1; lobby->revision++; return; } } } maze32_lobby_result_t maze32_lobby_start(maze32_lobby_t* lobby, int connection_id, uint64_t now_ms) { if (lobby == NULL || lobby->phase != MAZE32_LOBBY_WAITING) { return MAZE32_LOBBY_INVALID_PHASE; } const maze32_session_t* session = maze32_lobby_session_for_connection(lobby, connection_id); if (session == NULL || session->role != MAZE32_ROLE_PLAYER || session->player_slot != 1U) { return MAZE32_LOBBY_NOT_OWNER; } if (!maze32_lobby_ready(lobby)) { return MAZE32_LOBBY_NOT_READY; } lobby->phase = MAZE32_LOBBY_COUNTDOWN; lobby->countdown_started_ms = now_ms; lobby->activation_ms = now_ms + MAZE32_COUNTDOWN_DURATION_MS; lobby->revision++; return MAZE32_LOBBY_OK; } int maze32_lobby_activate_if_due(maze32_lobby_t* lobby, uint64_t now_ms) { if (lobby == NULL || lobby->phase != MAZE32_LOBBY_COUNTDOWN || now_ms < lobby->activation_ms) { return 0; } lobby->phase = MAZE32_LOBBY_ACTIVE; lobby->revision++; return 1; } void maze32_lobby_reset_match(maze32_lobby_t* lobby) { if (lobby == NULL) { return; } lobby->phase = MAZE32_LOBBY_WAITING; lobby->countdown_started_ms = 0U; lobby->activation_ms = 0U; lobby->revision++; } const maze32_session_t* maze32_lobby_session_for_connection(const maze32_lobby_t* lobby, int connection_id) { if (lobby == NULL) { return NULL; } for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { const maze32_session_t* session = &lobby->sessions[index]; if (session->role != MAZE32_ROLE_NONE && session->connected && session->connection_id == connection_id) { return session; } } return NULL; } unsigned int maze32_lobby_connected_observers(const maze32_lobby_t* lobby) { unsigned int count = 0U; if (lobby == NULL) { return count; } for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { const maze32_session_t* session = &lobby->sessions[index]; if (session->role == MAZE32_ROLE_OBSERVER && session->connected) { count++; } } return count; } int maze32_lobby_ready(const maze32_lobby_t* lobby) { if (lobby == NULL) { return 0; } for (unsigned int slot = 1U; slot <= MAZE32_PLAYER_LIMIT; ++slot) { int found = 0; for (size_t index = 0U; index < MAZE32_SESSION_LIMIT; ++index) { const maze32_session_t* session = &lobby->sessions[index]; if (session->role == MAZE32_ROLE_PLAYER && session->player_slot == slot && session->connected) { found = 1; break; } } if (!found) { return 0; } } return 1; } int maze32_lobby_can_reconfigure(const maze32_lobby_t* lobby) { return lobby != NULL && lobby->phase == MAZE32_LOBBY_WAITING; } const char* maze32_lobby_phase_name(maze32_lobby_phase_t phase) { switch (phase) { case MAZE32_LOBBY_WAITING: return "waiting"; case MAZE32_LOBBY_COUNTDOWN: return "countdown"; case MAZE32_LOBBY_ACTIVE: return "active"; default: return "unknown"; } }