From 1d414cc39231ef6d8ca69af565998d226e7a983f Mon Sep 17 00:00:00 2001 From: samstalhandske Date: Thu, 11 Dec 2025 17:29:42 +0100 Subject: [PATCH] Input is more responsive. Fixed bug with creating session-players. --- src/presentation/states/state_ingame.c | 15 ++- src/session/game_session.c | 113 +++++++++++++++------ src/session/game_session.h | 13 ++- src/shared/entity.h | 17 +++- src/shared/game_world.c | 38 +------ src/shared/grid.c | 25 ++++- src/shared/grid.h | 2 + src/shared/squeue.h | 6 +- src/simulation/input.h | 17 ++++ src/simulation/simulation_world.c | 133 ++++++++++++++++++++++++- 10 files changed, 302 insertions(+), 77 deletions(-) diff --git a/src/presentation/states/state_ingame.c b/src/presentation/states/state_ingame.c index bda6dfc..0310df6 100644 --- a/src/presentation/states/state_ingame.c +++ b/src/presentation/states/state_ingame.c @@ -62,8 +62,20 @@ static void state_enter(Presentation_State *state) { static void state_tick(Presentation_State *state) { Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; (void)ctx; + + Game_Session *session = g_current_session; - game_session_tick(g_current_session); + { // Push local input to queue. + game_session_enqueue_player_input(session, session->local_player_index, (Simulation_Game_Input) { + .up = IsKeyDown(KEY_UP) || IsKeyDown(KEY_W), + .down = IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S), + .right = IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D), + .left = IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A) + }); + } + + double delta_time = GetFrameTime(); + game_session_update(session, delta_time); { // TEMP: SS if(IsKeyPressed(KEY_ESCAPE)) { @@ -204,7 +216,6 @@ static void state_render(Presentation_State *state) { DrawText(buf, 32, 32, 12, RED); } - } EndMode2D(); } diff --git a/src/session/game_session.c b/src/session/game_session.c index e480d20..74ac55d 100644 --- a/src/session/game_session.c +++ b/src/session/game_session.c @@ -6,7 +6,9 @@ #include #include -#include "raylib.h" +#include "shared/squeue.h" + +#define INPUT_QUEUE_CAPACITY 8 Game_Session *g_current_session = NULL; @@ -28,11 +30,23 @@ void game_session_create(bool is_singleplayer, bool is_host, Game_Session_Settin session->players = (Game_Session_Player *)calloc(session->settings.max_players, sizeof(Game_Session_Player)); session->players_prev = (Game_Session_Player *)calloc(session->settings.max_players, sizeof(Game_Session_Player)); + for(uint16_t i = 0; i < session->settings.max_players; i++) { + assert(squeue_init( + &session->players[i].input_queue, + INPUT_QUEUE_CAPACITY, + sizeof(Simulation_Game_Input) + )); + + // Intentionally not initializing players_prev's input_queue. + } + { // TEMP session->local_player_index = 0; assert(game_session_create_player(session, &session->local_player_index)); } + session->simulation_accumulator = 0.0f; + g_current_session = session; printf("New Game_Session created.\n"); @@ -47,6 +61,12 @@ void game_session_destroy() { simulation_destroy_world(&session->simulation_world); + for(uint16_t i = 0; i < session->settings.max_players; i++) { + squeue_free(&session->players[i].input_queue); + + // Intentionally not freeing players_prev's input_queue. + } + free(session->players); session->players = NULL; free(session->players_prev); @@ -58,30 +78,23 @@ void game_session_destroy() { void game_session_init_default_settings(bool is_singleplayer, Game_Session_Settings *out_settings) { out_settings->seed = 1337; // TODO: SS - Randomize. + out_settings->tickrate = 10.0; out_settings->level_width = 32; out_settings->level_height = 32; out_settings->max_players = is_singleplayer ? 1 : 8; } -void game_session_tick(Game_Session *session) { - Game_Session_Player *local_session_player = game_session_get_local_player(session); - assert(local_session_player != NULL); - local_session_player->input = (Simulation_Game_Input) { // TODO: SS - Move this somewhere else, maybe. - .up = IsKeyDown(KEY_UP) || IsKeyDown(KEY_W), - .down = IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S), - .right = IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D), - .left = IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A) - }; +static void game_session_tick(Game_Session *session) { + // Update input. + for(uint16_t i = 0; i < session->settings.max_players; i++) { + Game_Session_Player *session_player = &session->players[i]; - { // TODO: SS - Use delta-time to accumulate time. Tick the simulation in a constant tick-rate. - // Update input. - for(uint16_t i = 0; i < session->settings.max_players; i++) { + { // Check if any players have joined/disconnected by comparing against the previous session_player. Game_Session_Player *session_player_prev = &session->players_prev[i]; - Game_Session_Player *session_player = &session->players[i]; - + bool was_active = session_player_prev->active; bool is_active = session_player->active; - + if (!was_active && is_active) { simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) { .type = Simulation_Command_Type_Add_Player, @@ -93,31 +106,57 @@ void game_session_tick(Game_Session *session) { .player_id = i }); } + } + + if(!session_player->active) { + continue; + } - if(!session_player->active) { - continue; - } + Simulation_Game_Input input = {0}; + if(squeue_pop(&session_player->input_queue, &input)) { + // We got an input. simulation_world_enqueue_command(&session->simulation_world, (Simulation_Command) { .type = Simulation_Command_Type_Set_Player_Input, .player_id = i, - .player_input = session_player->input, + .player_input = input, }); } - - // Tick. - simulation_world_tick(&session->simulation_world); - - // Copy 'players' from session to 'players_prev'. - memcpy(session->players_prev, session->players, sizeof(Game_Session_Player) * session->settings.max_players); } + + // Tick. + simulation_world_tick(&session->simulation_world); + + // Copy 'players' from session to 'players_prev'. + memcpy(session->players_prev, session->players, sizeof(Game_Session_Player) * session->settings.max_players); +} + +void game_session_update(Game_Session *session, const double delta_time) { + session->simulation_accumulator += delta_time; + + const double sim_dt = 1.0 / session->settings.tickrate; + + while (session->simulation_accumulator >= sim_dt) { + game_session_tick(session); + session->simulation_accumulator -= sim_dt; + } +} + +static Game_Session_Player *game_session_get_player_from_id(Game_Session *session, uint16_t player_index) { + assert(session != NULL); + if(player_index >= session->settings.max_players) { + return NULL; + } + + return &session->players[player_index]; } Game_Session_Player *game_session_get_local_player(Game_Session *session) { assert(session != NULL); - return &session->players[session->local_player_index]; + return game_session_get_player_from_id(session, session->local_player_index); } + bool game_session_create_player(Game_Session *session, uint16_t *out_player_index) { assert(session != NULL); @@ -126,8 +165,10 @@ bool game_session_create_player(Game_Session *session, uint16_t *out_player_inde Game_Session_Player *session_player = &session->players[i]; assert(session_player != NULL); if(!session_player->active) { - session_player->active = true; found_index = i; + + session_player->active = true; + break; } } @@ -148,4 +189,20 @@ void game_session_destroy_player(Game_Session *session, uint16_t player_index) { assert(player->active); memset(player, 0, sizeof(Game_Session_Player)); +} + +bool game_session_enqueue_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input) { + Game_Session_Player *player = game_session_get_player_from_id(session, player_index); + assert(player != NULL); + + if(simulation_input_equal(player->most_recent_input, input)) { + return false; + } + + if(!squeue_push(&player->input_queue, (void *)&input)) { + return false; + } + + player->most_recent_input = input; + return true; } \ No newline at end of file diff --git a/src/session/game_session.h b/src/session/game_session.h index ca003cd..c399ceb 100644 --- a/src/session/game_session.h +++ b/src/session/game_session.h @@ -4,6 +4,8 @@ #include #include +#include "shared/squeue.h" + #include "simulation/simulation_world.h" #include "simulation/input.h" #include "simulation/player.h" @@ -11,12 +13,15 @@ typedef struct { bool active; - Simulation_Game_Input input; + SQueue input_queue; + Simulation_Game_Input most_recent_input; // Exclusively used to make sure that the input-queue doesn't get filled with duplicated inputs. } Game_Session_Player; typedef struct { uint32_t seed; + double tickrate; + uint16_t level_width; uint16_t level_height; @@ -32,6 +37,7 @@ typedef struct { Game_Session_Settings settings; Simulation_World simulation_world; + double simulation_accumulator; Game_Session_Player *players; Game_Session_Player *players_prev; @@ -47,11 +53,14 @@ void game_session_destroy(); void game_session_init_default_settings(bool is_singleplayer, Game_Session_Settings *out_settings); -void game_session_tick(Game_Session *session); +void game_session_update(Game_Session *session, const double delta_time); Game_Session_Player *game_session_get_local_player(Game_Session *session); bool game_session_create_player(Game_Session *session, uint16_t *out_player_index); void game_session_destroy_player(Game_Session *session, uint16_t player_index); +bool game_session_enqueue_player_input(Game_Session *session, uint16_t player_index, Simulation_Game_Input input); + + #endif \ No newline at end of file diff --git a/src/shared/entity.h b/src/shared/entity.h index 41bcfaa..f5fa351 100644 --- a/src/shared/entity.h +++ b/src/shared/entity.h @@ -2,10 +2,19 @@ #define ENTITY_H #include +#include typedef uint16_t Entity_ID; -#define INVALID_ENTITY_ID Entity_ID(65535) +#define INVALID_ENTITY_ID (Entity_ID)65535 + +typedef enum { + Entity_Movement_Direction_None, + Entity_Movement_Direction_Up, + Entity_Movement_Direction_Down, + Entity_Movement_Direction_Right, + Entity_Movement_Direction_Left, +} Entity_Movement_Direction; typedef enum { Entity_Type_Snake_Head, @@ -14,11 +23,15 @@ typedef enum { } Entity_Type; typedef struct { + bool active; + Entity_Type type; uint16_t x; uint16_t y; - + + Entity_Movement_Direction move_direction; + // TODO: SS - Color/tint? } Entity; diff --git a/src/shared/game_world.c b/src/shared/game_world.c index ec7448f..42207f1 100644 --- a/src/shared/game_world.c +++ b/src/shared/game_world.c @@ -37,38 +37,6 @@ Game_World *game_world_create(uint32_t seed, uint16_t level_width, uint16_t leve )); } - // { // TODO: SS - Create the level. - // { - // Grid_Cell *cell = grid_get_cell(&w->grid, 5, 3); - // assert(cell != NULL); - // cell->entity.type = Entity_Type_Food; - // } - - // { - // Grid_Cell *cell = grid_get_cell(&w->grid, 11, 4); - // assert(cell != NULL); - // cell->entity.type = Entity_Type_Food; - // } - - // { - // Grid_Cell *cell = grid_get_cell(&w->grid, 15, 9); - // assert(cell != NULL); - // cell->entity.type = Entity_Type_Food; - // } - - // { - // Grid_Cell *cell = grid_get_cell(&w->grid, 24, 15); - // assert(cell != NULL); - // cell->entity.type = Entity_Type_Snake_Head; - // } - // { - // Grid_Cell *cell = grid_get_cell(&w->grid, 23, 15); - // assert(cell != NULL); - // cell->entity.type = Entity_Type_Snake_Body; - // } - // } - - return w; } @@ -87,6 +55,8 @@ void game_world_destroy(Game_World *world) { bool game_world_create_entity(Game_World *world, Entity_Type type, uint16_t x, uint16_t y, Entity_ID *out_entity_id) { assert(world != NULL); + *out_entity_id = INVALID_ENTITY_ID; + if(x >= world->grid.width) { printf("Failed to create entity; Invalid x-coordinate.\n"); return false; @@ -105,6 +75,7 @@ bool game_world_create_entity(Game_World *world, Entity_Type type, uint16_t x, u } world->entities[id] = (Entity) { + .active = true, .type = type, }; @@ -124,12 +95,13 @@ void game_world_destroy_entity(Game_World *world, Entity_ID entity_id) { entity_id ); assert(entity != NULL); + entity->active = false; Grid_Cell *cell = grid_get_cell(&world->grid, entity->x, entity->y); assert(cell != NULL); assert(grid_try_remove_entity_from_cell(cell)); - squeue_push(&world->entity_id_queue, entity_id); + squeue_push(&world->entity_id_queue, (void *)&entity_id); } Entity *game_world_try_get_entity_by_id(Game_World *world, Entity_ID id) { diff --git a/src/shared/grid.c b/src/shared/grid.c index 7eab07c..3c70c10 100644 --- a/src/shared/grid.c +++ b/src/shared/grid.c @@ -49,8 +49,12 @@ static bool to_grid_index(Grid *grid, uint16_t x, uint16_t y, uint16_t *out_grid assert(grid != NULL); assert(out_grid_index != NULL); - if (!grid || !out_grid_index) return false; - if (x >= grid->width || y >= grid->height) return false; + if(!grid || !out_grid_index) { + return false; + } + if(x >= grid->width || y >= grid->height) { + return false; + } *out_grid_index = y * grid->width + x; return true; @@ -58,7 +62,9 @@ static bool to_grid_index(Grid *grid, uint16_t x, uint16_t y, uint16_t *out_grid Grid_Cell *grid_get_cell(Grid *grid, uint16_t x, uint16_t y) { assert(grid != NULL); - if(x >= grid->width || y >= grid->height) return NULL; + if(x >= grid->width || y >= grid->height) { + return NULL; + } uint16_t grid_index = 0; if(!to_grid_index(grid, x, y, &grid_index)) { @@ -98,4 +104,17 @@ bool grid_try_remove_entity_from_cell(Grid_Cell *cell) { cell->entity = NULL; return true; +} + +void grid_move_entity_from_cell_to_cell(Grid_Cell *cell_a, Grid_Cell *cell_b) { + assert(cell_a != NULL); + assert(cell_b != NULL); + + assert(cell_a->entity != NULL); // We expect cell A to have an Entity to move. + assert(cell_b->entity == NULL); // We expect cell B to NOT have an Entity. + + Entity *entity_to_be_moved = cell_a->entity; + + assert(grid_try_remove_entity_from_cell(cell_a)); + assert(grid_try_add_entity_to_cell(cell_b, entity_to_be_moved)); } \ No newline at end of file diff --git a/src/shared/grid.h b/src/shared/grid.h index 3325d83..d39e694 100644 --- a/src/shared/grid.h +++ b/src/shared/grid.h @@ -28,4 +28,6 @@ Grid_Cell *grid_get_cell(Grid *grid, uint16_t x, uint16_t y); bool grid_try_add_entity_to_cell(Grid_Cell *cell, Entity *entity); bool grid_try_remove_entity_from_cell(Grid_Cell *cell); +void grid_move_entity_from_cell_to_cell(Grid_Cell *cell_a, Grid_Cell *cell_b); + #endif \ No newline at end of file diff --git a/src/shared/squeue.h b/src/shared/squeue.h index 1d5213a..7b90bf5 100644 --- a/src/shared/squeue.h +++ b/src/shared/squeue.h @@ -1,11 +1,13 @@ -#ifndef QUEUE_H -#define QUEUE_H +#ifndef SQUEUE_H +#define SQUEUE_H #include #include #include #include +// FIFO. + typedef struct { void *buffer; uint16_t head; diff --git a/src/simulation/input.h b/src/simulation/input.h index 9157f34..4362d06 100644 --- a/src/simulation/input.h +++ b/src/simulation/input.h @@ -9,4 +9,21 @@ typedef struct { bool left; } Simulation_Game_Input; +static inline bool simulation_input_equal(Simulation_Game_Input a, Simulation_Game_Input b) { + if(a.up != b.up) { + return false; + } + if(a.down != b.down) { + return false; + } + if(a.right != b.right) { + return false; + } + if(a.left != b.left) { + return false; + } + + return true; +} + #endif \ No newline at end of file diff --git a/src/simulation/simulation_world.c b/src/simulation/simulation_world.c index 53c9540..11f8b67 100644 --- a/src/simulation/simulation_world.c +++ b/src/simulation/simulation_world.c @@ -45,7 +45,7 @@ void simulation_destroy_world(Simulation_World *simulation_world) { void simulation_world_tick(Simulation_World *simulation_world) { assert(simulation_world != NULL); - printf("TICK: %lu.\n", simulation_world->tick); + // printf("TICK: %lu.\n", simulation_world->tick); for(uint16_t i = 0; i < simulation_world->command_count; i++) { Simulation_Command *cmd = &simulation_world->commands[i]; @@ -60,14 +60,19 @@ void simulation_world_tick(Simulation_World *simulation_world) { assert(!player->active); player->active = true; - // TODO: SS - Create entity. assert(game_world_create_entity( simulation_world->game_world, Entity_Type_Snake_Head, - 10, 15, + i+6, 15, // NOTE: SS - Hardcoded spawn-position. &player->entity_id )); + Entity *player_entity = game_world_try_get_entity_by_id(simulation_world->game_world, player->entity_id); + + // Set initial move-direction for player-entities. + // The initial direction should probably take the spawn-position in to account. + player_entity->move_direction = Entity_Movement_Direction_Up; + break; } case Simulation_Command_Type_Remove_Player: { @@ -89,6 +94,9 @@ void simulation_world_tick(Simulation_World *simulation_world) { } } simulation_world->command_count = 0; + + + Game_World *gw = simulation_world->game_world; // Loop over all players in the simulation. for(uint16_t i = 0; i < simulation_world->max_players; i++) { @@ -102,14 +110,129 @@ void simulation_world_tick(Simulation_World *simulation_world) { player->input.up, player->input.down, player->input.right, player->input.left ); - Entity *player_entity = game_world_try_get_entity_by_id(simulation_world->game_world, player->entity_id); + Entity *player_entity = game_world_try_get_entity_by_id(gw, player->entity_id); assert(player_entity != NULL); - printf("Entity %i ~ x: %u, y: %u.\n", player->entity_id, player_entity->x, player_entity->y); + // Snakes can't go backwards. They have to turn. + + switch(player_entity->move_direction) { + case Entity_Movement_Direction_Up: + case Entity_Movement_Direction_Down: // Snakes can only go right/left if it's currently moving up/down. + { + if(player->input.right) { + player_entity->move_direction = Entity_Movement_Direction_Right; + } + else if(player->input.left) { + player_entity->move_direction = Entity_Movement_Direction_Left; + } + + break; + } + case Entity_Movement_Direction_Right: + case Entity_Movement_Direction_Left: // Snakes can only go up/down if it's currently moving right/left. + { + if(player->input.up) { + player_entity->move_direction = Entity_Movement_Direction_Up; + } + else if(player->input.down) { + player_entity->move_direction = Entity_Movement_Direction_Down; + } + + break; + } + } + + // printf("Entity %i ~ x: %u, y: %u. Move-dir: %i.\n", player->entity_id, player_entity->x, player_entity->y, player_entity->move_direction); } // TODO: SS - Game-logic! :) + for(uint16_t i = 0; i < gw->max_entities; i++) { + Entity *entity = &gw->entities[i]; + if(!entity->active) { + continue; + } + + printf("Ticking entity %i (x: %u, y: %u).\n", i, entity->x, entity->y); + + { // Move entities. + int16_t dx = 0; + int16_t dy = 0; + + switch(entity->move_direction) { + case Entity_Movement_Direction_None: { + break; + } + case Entity_Movement_Direction_Up: { + dy = -1; + break; + } + case Entity_Movement_Direction_Down: { + dy = 1; + break; + } + case Entity_Movement_Direction_Right: { + dx = 1; + break; + } + case Entity_Movement_Direction_Left: { + dx = -1; + break; + } + } + + if(dx != 0 || dy != 0) { + // Try moving. + printf("Trying to move %i.\n", i); + + // Figure out what cell we're on and what cell we want to go to. + Grid_Cell *current_cell = grid_get_cell(&gw->grid, entity->x, entity->y); + assert(current_cell != NULL); + Grid_Cell *target_cell = grid_get_cell(&gw->grid, entity->x + dx, entity->y + dy); + + { // Debug-logging. + printf("Current cell = (x: %u, y: %u, entity: %p).\n", current_cell->x, current_cell->y, current_cell->entity); + if(target_cell != NULL) { + printf("Target cell = (x: %u, y: %u, entity: %p).\n", target_cell->x, target_cell->y, target_cell->entity); + } + else { + printf("Target cell = NULL!\n"); + } + } + + if(target_cell == NULL) { + // Target cell does not exist. + // TODO: SS - Die. + } + else { + if(target_cell->entity != NULL) { + // Target cell is occupied. + + // Check what type of entity it is and determine what should happen. + switch(target_cell->entity->type) { + case Entity_Type_Snake_Head: { + // TODO: SS - Die. + break; + } + case Entity_Type_Snake_Body: { + // TODO: SS - Die. + break; + } + case Entity_Type_Food: { + // TODO: SS - Eat! + break; + } + } + } + else { + // Target cell is unoccupied and free to enter. + grid_move_entity_from_cell_to_cell(current_cell, target_cell); + } + } + } + } + } + simulation_world->tick += 1; }