diff --git a/src/game/instance/game_instance.h b/src/game/instance/game_instance.h index d0823b6..e9a2d17 100644 --- a/src/game/instance/game_instance.h +++ b/src/game/instance/game_instance.h @@ -14,7 +14,6 @@ typedef struct { uint16_t player_id; SQueue input_queue; - Simulation_Game_Input prev_input; // TODO: SS - Remove this and just peek the queue? } Locally_Controlled_Player; typedef struct { diff --git a/src/game/presentation/states/state_ingame.c b/src/game/presentation/states/state_ingame.c index b0ded9e..cd641a2 100644 --- a/src/game/presentation/states/state_ingame.c +++ b/src/game/presentation/states/state_ingame.c @@ -92,21 +92,15 @@ static void state_tick(Presentation_State *state) { .left = IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_A) }; - Locally_Controlled_Player *player = &instance->locally_controlled_players[0]; - - if(!simulation_input_equal(input, player->prev_input)) { + if(!simulation_input_empty(input)) { game_instance_push_local_input(instance, 0, input); - player->prev_input = input; } } else if(instance->amount_of_local_players > 1) { for(uint8_t i = 0; i < instance->amount_of_local_players; i++) { Simulation_Game_Input input = gather_input_for_local_player(i); - Locally_Controlled_Player *player = &instance->locally_controlled_players[i]; - - if(!simulation_input_equal(input, player->prev_input)) { + if(!simulation_input_empty(input)) { game_instance_push_local_input(instance, i, input); - player->prev_input = input; } } } @@ -121,11 +115,8 @@ static void state_tick(Presentation_State *state) { for(uint16_t i = 0; i < instance->amount_of_local_players; i++) { // Pop input from local-player's input_queue and set the player's input to it, if we have one. Simulation_Game_Input input = {0}; - if(game_instance_pop_local_input(instance, i, &input)) { - // TODO: SS - We should probably check if this input is invalid for the snake. (pressing "go right" when going left, for example.) - // If it is invalid, the next input in the queue should be tried. - game_session_set_player_input(session, i, input); - } + game_instance_pop_local_input(instance, i, &input); + game_session_set_player_input(session, i, input); } game_session_tick(session); @@ -237,8 +228,8 @@ static void state_render(Presentation_State *state) { Color tint = (Color) { 255, 255, 255, 255 }; // Set the snake's tint based on player-index. - for(uint16_t i = 0; i < sim_world->max_players; i++) { - Simulation_Player *player = &sim_world->players[i]; + for(uint16_t i = 0; i < world->max_players; i++) { + Game_World_Player *player = &world->players[i]; if(!player->active) { continue; } diff --git a/src/game/presentation/states/state_main_menu.c b/src/game/presentation/states/state_main_menu.c index cace9ce..fb594df 100644 --- a/src/game/presentation/states/state_main_menu.c +++ b/src/game/presentation/states/state_main_menu.c @@ -120,7 +120,7 @@ static void state_render(Presentation_State *state) { } } if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "Cancel")) { - ctx->mode = ctx->session_settings.max_players == 1 ? Main_Menu_Mode_Home : Main_Menu_Mode_Multiplayer; + ctx->mode = Main_Menu_Mode_Home; } break; diff --git a/src/game/session/game_session.c b/src/game/session/game_session.c index 9789194..981af25 100644 --- a/src/game/session/game_session.c +++ b/src/game/session/game_session.c @@ -42,8 +42,8 @@ void game_session_init_default_settings(bool online, Game_Session_Settings *out_ out_settings->seed = 1337; // TODO: SS - Randomize. out_settings->online = online; out_settings->tickrate = 10.0; - out_settings->level_width = 32; - out_settings->level_height = 32; + out_settings->level_width = 64; + out_settings->level_height = 48; out_settings->max_players = online ? 8 : 2; } diff --git a/src/game/shared/game_world.c b/src/game/shared/game_world.c index 1e410ea..686bd49 100644 --- a/src/game/shared/game_world.c +++ b/src/game/shared/game_world.c @@ -7,17 +7,23 @@ #define MIN_LEVEL_WIDTH 8 // TODO: SS - Move these out of here. #define MIN_LEVEL_HEIGHT 8 -Game_World *game_world_create(uint32_t seed, uint8_t amount_of_food_to_spawn_initially, uint16_t level_width, uint16_t level_height) { +Game_World *game_world_create(uint32_t seed, uint16_t max_players, uint16_t level_width, uint16_t level_height) { assert(level_width >= MIN_LEVEL_WIDTH); // We should probably have failed earlier. assert(level_height >= MIN_LEVEL_HEIGHT); Game_World *w = (Game_World *)calloc(1, sizeof(Game_World)); assert(w != NULL); + w->seed = seed; random_init(&w->random_generator, w->seed); + w->max_players = max_players; + w->players = (Game_World_Player *)calloc(w->max_players, sizeof(Game_World_Player)); + assert(w->players != NULL); + w->max_entities = level_width * level_height; w->entities = (Entity *)calloc(w->max_entities, sizeof(Entity)); + assert(w->entities != NULL); // Set up entity-id queue. assert(squeue_init(&w->entity_id_queue, w->max_entities, sizeof(Entity_ID))); @@ -28,8 +34,8 @@ Game_World *game_world_create(uint32_t seed, uint8_t amount_of_food_to_spawn_ini grid_initialize(&w->grid, level_width, level_height); - // Spawn initial food(s). - for(uint16_t i = 0; i < amount_of_food_to_spawn_initially; i++) { + // Spawn initial food(s). One per player. + for(uint16_t i = 0; i < w->max_players; i++) { Entity_ID entity_food; uint16_t x = 0; @@ -51,8 +57,15 @@ void game_world_destroy(Game_World *world) { assert(world != NULL); world->seed = 0; + + world->max_players = 0; + free(world->players); + world->players = NULL; + world->max_entities = 0; free(world->entities); + world->entities = NULL; + grid_dispose(&world->grid); squeue_free(&world->entity_id_queue); @@ -122,6 +135,57 @@ void game_world_destroy_entity(Game_World *world, Entity_ID entity_id, bool dest } } +static void kill_and_respawn_player(Game_World *world, Entity *entity) { + assert(world != NULL); + assert(entity != NULL); + + // Loop over the players to find the owner of this entity. + Game_World_Player *owner_player = NULL; + for(uint16_t i = 0; i < world->max_players; i++) { + Game_World_Player *player = &world->players[i]; + if(!player->active) { + continue; + } + + if(player->entity_id == entity->id) { + owner_player = player; + break; + } + } + assert(owner_player != NULL); + + game_world_destroy_entity(world, entity->id, true); + owner_player->entity_id = INVALID_ENTITY_ID; + + { // Respawn. + Entity_ID entity_snake_id; + + uint16_t spawn_x = 0; + uint16_t spawn_y = 0; + assert(game_world_find_position_to_spawn(world, &spawn_x, &spawn_y)); + + assert(game_world_create_entity( + world, + Entity_Type_Snake_Head, + spawn_x, spawn_y, + &entity_snake_id + )); + + owner_player->entity_id = entity_snake_id; + + Entity *player_entity = game_world_try_get_entity_by_id( + world, + owner_player->entity_id + ); + + // Set initial move-direction for player-entities. + player_entity->move_direction = game_world_choose_initial_move_direction_based_on_coords( + world, + spawn_x, spawn_y + ); + } +} + void game_world_tick(Game_World *world) { // Game-logic! :) @@ -133,7 +197,144 @@ void game_world_tick(Game_World *world) { switch(entity->type) { case Entity_Type_Snake_Head: { + { // Move entity + 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. + + // Figure out what cell we're on and what cell we want to go to. + Grid_Cell *current_cell = grid_get_cell(&world->grid, entity->x, entity->y); + assert(current_cell != NULL); + Grid_Cell *target_cell = grid_get_cell(&world->grid, entity->x + dx, entity->y + dy); + + if(target_cell == NULL) { + // Target cell does not exist. + // TODO: SS - Check the session's settings. + // Maybe each session could decide whether the snake should "loop-around" or not. + // If the snake shouldn't loop around, die. For now, we say that it's not okay to loop around. + bool should_loop_around = false; + if(should_loop_around) { + // TODO: SS - Implement looping around. + } + else { + kill_and_respawn_player(world, entity); + } + } + else { + bool should_move_to_target_cell = false; + bool snake_ate = false; + + 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: { + kill_and_respawn_player(world, entity); + break; + } + case Entity_Type_Snake_Body: { + kill_and_respawn_player(world, entity); + break; + } + case Entity_Type_Food: { + // Eat! + assert(grid_try_remove_entity_from_cell(target_cell)); + + snake_ate = true; + should_move_to_target_cell = true; + + break; + } + } + } + else { + // Target cell is unoccupied and free to enter. + should_move_to_target_cell = true; + } + + if(should_move_to_target_cell) { + { // Move snake recursively. + Entity *e = entity; + Grid_Cell *t = target_cell; + while(e != NULL) { + Grid_Cell *a = grid_get_cell(&world->grid, e->x, e->y); + assert(a != NULL); + + grid_move_entity_from_cell_to_cell(a, t); + + e = game_world_try_get_entity_by_id(world, e->child); + t = a; + } + } + + if(snake_ate) { + Entity *e = entity; + uint16_t child_index = 0; + while(e->child != INVALID_ENTITY_ID) { + e = game_world_try_get_entity_by_id(world, e->child); + child_index += 1; + } + + Entity_ID child_entity = INVALID_ENTITY_ID; + assert(game_world_create_entity( + world, + Entity_Type_Snake_Body, + e->prev_x, + e->prev_y, + &child_entity + )); + + assert(e->child == INVALID_ENTITY_ID); + e->child = child_entity; + + { // Spawn food. + uint16_t food_spawn_x = 0; + uint16_t food_spawn_y = 0; + + if(!game_world_find_position_to_spawn(world, &food_spawn_x, &food_spawn_y)) { + printf("Failed to find a position to spawn food.\n"); + } + else { + Entity_ID entity_food; + assert(game_world_create_entity( + world, + Entity_Type_Food, + food_spawn_x, food_spawn_y, + &entity_food + )); + } + } + } + } + } + } + } break; } case Entity_Type_Snake_Body: { @@ -150,149 +351,41 @@ void game_world_tick(Game_World *world) { } } - { // Move entity - 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. - - // Figure out what cell we're on and what cell we want to go to. - Grid_Cell *current_cell = grid_get_cell(&world->grid, entity->x, entity->y); - assert(current_cell != NULL); - Grid_Cell *target_cell = grid_get_cell(&world->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 - Check the session's settings. - // Maybe each session could decide whether the snake should "loop-around" or not. - // If the snake shouldn't loop around, die. - } - else { - bool should_move_to_target_cell = false; - bool snake_ate = false; - - 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: { - // Eat! - assert(grid_try_remove_entity_from_cell(target_cell)); - - snake_ate = true; - should_move_to_target_cell = true; - - break; - } - } - } - else { - // Target cell is unoccupied and free to enter. - should_move_to_target_cell = true; - } - - if(should_move_to_target_cell) { - { // Move snake recursively. - Entity *e = entity; - Grid_Cell *t = target_cell; - while(e != NULL) { - Grid_Cell *a = grid_get_cell(&world->grid, e->x, e->y); - assert(a != NULL); - - grid_move_entity_from_cell_to_cell(a, t); - - e = game_world_try_get_entity_by_id(world, e->child); - t = a; - } - } - - if(snake_ate) { - Entity *e = entity; - uint16_t child_index = 0; - while(e->child != INVALID_ENTITY_ID) { - e = game_world_try_get_entity_by_id(world, e->child); - child_index += 1; - } - - Entity_ID child_entity = INVALID_ENTITY_ID; - assert(game_world_create_entity( - world, - Entity_Type_Snake_Body, - e->prev_x, - e->prev_y, - &child_entity - )); - - assert(e->child == INVALID_ENTITY_ID); - e->child = child_entity; - - { // Spawn food. - uint16_t food_spawn_x = 0; - uint16_t food_spawn_y = 0; - - if(!game_world_find_position_to_spawn(world, &food_spawn_x, &food_spawn_y)) { - printf("Failed to find a position to spawn food.\n"); - } - else { - Entity_ID entity_food; - assert(game_world_create_entity( - world, - Entity_Type_Food, - food_spawn_x, food_spawn_y, - &entity_food - )); - } - } - } - } - } - } - } } } +bool game_world_add_player(Game_World *world, Game_World_Player **out_gw_player) { + assert(world != NULL); + assert(out_gw_player != NULL); + + for(uint16_t i = 0; i < world->max_players; i++) { + Game_World_Player *player = &world->players[i]; + + if(player->active) { + continue; + } + + // Found free player. + player->active = true; + *out_gw_player = player; + return true; + } + + return false; +} + +bool game_world_remove_player(Game_World *world, Game_World_Player *player) { + assert(world != NULL); + assert(player != NULL); + + game_world_destroy_entity(world, player->entity_id, true); + + memset(player, 0, sizeof(Game_World_Player)); + + return true; +} + + Entity *game_world_try_get_entity_by_id(Game_World *world, Entity_ID id) { assert(world != NULL); diff --git a/src/game/shared/game_world.h b/src/game/shared/game_world.h index 108a14c..02017fd 100644 --- a/src/game/shared/game_world.h +++ b/src/game/shared/game_world.h @@ -6,10 +6,14 @@ #include "grid.h" #include "shared/squeue.h" #include "shared/random.h" +#include "game_world_player.h" typedef struct { uint32_t seed; Random_Generator random_generator; + + Game_World_Player *players; + uint16_t max_players; SQueue entity_id_queue; Entity *entities; @@ -19,7 +23,7 @@ typedef struct { } Game_World; -Game_World *game_world_create(uint32_t seed, uint8_t amount_of_food_to_spawn_initially, uint16_t level_width, uint16_t level_height); +Game_World *game_world_create(uint32_t seed, uint16_t max_players, uint16_t level_width, uint16_t level_height); 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); @@ -27,7 +31,8 @@ void game_world_destroy_entity(Game_World *world, Entity_ID entity_id, bool dest void game_world_tick(Game_World *world); -// TODO: SS - "void game_world_spawn_player(Game_World *world, ..)" +bool game_world_add_player(Game_World *world, Game_World_Player **out_gw_player); +bool game_world_remove_player(Game_World *world, Game_World_Player *player); Entity *game_world_try_get_entity_by_id(Game_World *world, Entity_ID id); diff --git a/src/game/shared/game_world_player.h b/src/game/shared/game_world_player.h new file mode 100644 index 0000000..4e8afbd --- /dev/null +++ b/src/game/shared/game_world_player.h @@ -0,0 +1,10 @@ +#ifndef GAME_WORLD_PLAYER_H +#define GAME_WORLD_PLAYER_H + +typedef struct { + bool active; + + Entity_ID entity_id; // The entity that this player is controlling. Can be INVALID_ENTITY_ID. +} Game_World_Player; + +#endif \ No newline at end of file diff --git a/src/game/simulation/input.h b/src/game/simulation/input.h index 9d6f906..7bacbe5 100644 --- a/src/game/simulation/input.h +++ b/src/game/simulation/input.h @@ -6,11 +6,11 @@ typedef struct { bool left; } Simulation_Game_Input; -static inline bool simulation_input_equal(Simulation_Game_Input a, Simulation_Game_Input b) { - if(a.right != b.right) { +static inline bool simulation_input_empty(Simulation_Game_Input input) { + if(input.right) { return false; } - if(a.left != b.left) { + if(input.left) { return false; } diff --git a/src/game/simulation/player.h b/src/game/simulation/player.h index 4c13bb6..2be3740 100644 --- a/src/game/simulation/player.h +++ b/src/game/simulation/player.h @@ -6,8 +6,10 @@ typedef struct { bool active; - Entity_ID entity_id; + Game_World_Player *game_world_player; + Simulation_Game_Input input; + // score, name etc. } Simulation_Player; diff --git a/src/game/simulation/simulation_world.c b/src/game/simulation/simulation_world.c index 2620397..4d41e85 100644 --- a/src/game/simulation/simulation_world.c +++ b/src/game/simulation/simulation_world.c @@ -14,8 +14,7 @@ Simulation_World simulation_create_world(uint32_t seed, bool online, uint8_t max w.online = online; - uint16_t amount_of_food_to_spawn_initially = max_players; // NOTE: SS - One per player. Might change and/or be customizable. - w.game_world = game_world_create(seed, amount_of_food_to_spawn_initially, width, height); + w.game_world = game_world_create(seed, max_players, width, height); assert(w.game_world != NULL); w.match_state = Simulation_Match_State_Waiting_To_Start; @@ -50,6 +49,9 @@ void simulation_world_tick(Simulation_World *simulation_world) { // printf("TICK: %lu.\n", simulation_world->tick); + Game_World *gw = simulation_world->game_world; + assert(gw != NULL); + for(uint16_t i = 0; i < simulation_world->command_count; i++) { Simulation_Command *cmd = &simulation_world->commands[i]; // printf("Command type: %i, player id: %i.\n", cmd->type, cmd->player_id); @@ -63,9 +65,10 @@ void simulation_world_tick(Simulation_World *simulation_world) { assert(!player->active); player->active = true; - Game_World *gw = simulation_world->game_world; - assert(gw != NULL); - + assert(game_world_add_player(gw, &player->game_world_player)); + + Game_World_Player *gwp = player->game_world_player; + uint16_t spawn_x = 0; uint16_t spawn_y = 0; assert(game_world_find_position_to_spawn(gw, &spawn_x, &spawn_y)); @@ -74,10 +77,10 @@ void simulation_world_tick(Simulation_World *simulation_world) { gw, Entity_Type_Snake_Head, spawn_x, spawn_y, - &player->entity_id + &gwp->entity_id )); - Entity *player_entity = game_world_try_get_entity_by_id(gw, player->entity_id); + Entity *player_entity = game_world_try_get_entity_by_id(gw, gwp->entity_id); // Set initial move-direction for player-entities. player_entity->move_direction = game_world_choose_initial_move_direction_based_on_coords( @@ -92,8 +95,7 @@ void simulation_world_tick(Simulation_World *simulation_world) { assert(player->active); player->active = false; - game_world_destroy_entity(simulation_world->game_world, player->entity_id, true); - + assert(game_world_remove_player(gw, player->game_world_player)); break; } case Simulation_Command_Type_Set_Player_Input: { @@ -136,8 +138,14 @@ void simulation_world_tick(Simulation_World *simulation_world) { // Loop over all players in the simulation. for(uint16_t i = 0; i < simulation_world->max_players; i++) { - Simulation_Player *player = &simulation_world->players[i]; - if(!player->active) { + Simulation_Player *sim_player = &simulation_world->players[i]; + if(!sim_player->active) { + continue; + } + + Game_World_Player *gw_player = sim_player->game_world_player; + assert(gw_player != NULL); + if(!gw_player->active) { continue; } @@ -148,14 +156,17 @@ void simulation_world_tick(Simulation_World *simulation_world) { // player->input.right, player->input.left // ); - Entity *player_entity = game_world_try_get_entity_by_id(gw, player->entity_id); - assert(player_entity != NULL); + Entity *player_entity = game_world_try_get_entity_by_id(gw, gw_player->entity_id); + if(player_entity == NULL) { + // No entity to control. No entity to apply input on. + continue; + } // Snakes can't go backwards. They have to turn. // We only have input 'right' and 'up'. - Simulation_Game_Input input = player->input; - + Simulation_Game_Input input = sim_player->input; + // Turn relative to the player's current move_direction. switch(player_entity->move_direction) { case Entity_Movement_Direction_None: { diff --git a/src/todo.md b/src/todo.md index 268d7ce..f57f6da 100644 --- a/src/todo.md +++ b/src/todo.md @@ -6,12 +6,12 @@ - Right now it's only considering the size of of the grid, but what if we want to have obstacles in the grid? - We also don't take if two players spawn one cell from each other in to consideration. - Add player-death. - - [ ] Went out of bounds. - - [ ] Hit other snake's head. - - [ ] Hit yours/other snake's body. + - [x] Went out of bounds. + - [x] Hit other snake's head. + - [x] Hit yours/other snake's body. - Spawn players at a random position. - [x] When initially joining. - - [ ] When dead and should respawn. + - [x] When dead and should respawn. - [x] Spawn a new food when one was eaten. - [x] When a player disconnects, all children should recursively be destroyed too. Not just the head. - [ ] Send networked input.