274 lines
9.0 KiB
C
274 lines
9.0 KiB
C
#include "maze32_lobby.h"
|
|
|
|
#include <string.h>
|
|
|
|
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";
|
|
}
|
|
}
|