From 2a0e5779cb838aafde9915e156a6ec6d624c943b Mon Sep 17 00:00:00 2001 From: samstalhandske Date: Fri, 12 Dec 2025 13:21:05 +0100 Subject: [PATCH] Body-part now follows it's parent. You can also eat food. Shortcut in the main-menu to play default singleplayer, press P. --- src/presentation/states/state_ingame.c | 36 ++++++++++- src/presentation/states/state_main_menu.c | 18 +++++- src/shared/entity.h | 5 ++ src/shared/game_world.c | 5 +- src/shared/random.h | 34 ++++++++++ src/shared/wang_hash.h | 13 ++++ src/simulation/simulation_world.c | 76 ++++++++++++++++++++++- 7 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 src/shared/random.h create mode 100644 src/shared/wang_hash.h diff --git a/src/presentation/states/state_ingame.c b/src/presentation/states/state_ingame.c index 0e25e3d..1827971 100644 --- a/src/presentation/states/state_ingame.c +++ b/src/presentation/states/state_ingame.c @@ -10,6 +10,8 @@ #include "session/game_session.h" +#include "shared/wang_hash.h" + #define GRID_CELL_SIZE 8 #define ENTITY_PRESENTATION_Y_OFFSET -2 @@ -84,6 +86,10 @@ static void state_tick(Presentation_State *state) { } } +uint32_t floor_texture_variant(uint32_t x, uint32_t y, uint32_t seed, uint32_t num_variants) { + return (wang_hash(x * 73856093u ^ y * 19349663u ^ seed)) % num_variants; +} + static void state_render(Presentation_State *state) { Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; (void)ctx; @@ -108,7 +114,7 @@ static void state_render(Presentation_State *state) { uint32_t pres_x = x * GRID_CELL_SIZE; uint32_t pres_y = y * GRID_CELL_SIZE; - uint32_t random_floor_index = i; // TODO: SS - Get a deterministic random value based on seed. + uint32_t random_floor_index = floor_texture_variant(x, y, world->seed, 4); DrawTextureRec( ctx->texture_grass, @@ -116,7 +122,7 @@ static void state_render(Presentation_State *state) { (Vector2) { pres_x, pres_y }, WHITE ); - DrawRectangleLines(pres_x, pres_y, GRID_CELL_SIZE, GRID_CELL_SIZE, (Color) { 0, 0, 0, 8 }); + DrawRectangleLines(pres_x, pres_y, GRID_CELL_SIZE, GRID_CELL_SIZE, (Color) { 0, 0, 0, 2 }); // TODO: SS - Let the user customize the alpha locally. } for(uint32_t i = 0; i < grid_total_size; i++) { @@ -159,6 +165,30 @@ static void state_render(Presentation_State *state) { (Color) { 0, 0, 0, 32 } ); + float rotation = 0.0f; + switch(entity->move_direction){ + case Entity_Movement_Direction_None: { + rotation = 0.0f; + break; + } + case Entity_Movement_Direction_Up: { + rotation = -90.0f; + break; + } + case Entity_Movement_Direction_Down: { + rotation = 90.0f; + break; + } + case Entity_Movement_Direction_Right: { + rotation = 0.0f; + break; + } + case Entity_Movement_Direction_Left: { + rotation = 180.0f; + break; + } + } + DrawTexturePro( *texture, (Rectangle) { // Source. @@ -171,7 +201,7 @@ static void state_render(Presentation_State *state) { GRID_CELL_SIZE }, origin, // Origin. - 0, // Rotation. + rotation, // Rotation. tint // Tint. ); diff --git a/src/presentation/states/state_main_menu.c b/src/presentation/states/state_main_menu.c index c391874..05ae0a3 100644 --- a/src/presentation/states/state_main_menu.c +++ b/src/presentation/states/state_main_menu.c @@ -14,7 +14,23 @@ static void state_enter(Presentation_State *state) { } static void state_tick(Presentation_State *state) { - (void)state; + Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context; + + { // DEBUG + if(IsKeyPressed(KEY_P)) { + ctx->is_singleplayer = true; + ctx->mode = Main_Menu_Mode_Game_Setup; + game_session_init_default_settings(ctx->is_singleplayer, &ctx->session_settings); + + game_session_create( + ctx->is_singleplayer, + !ctx->is_singleplayer, + ctx->session_settings + ); + + presentation_state_machine_go_to(&presentation_state_ingame); + } + } } static void state_render(Presentation_State *state) { diff --git a/src/shared/entity.h b/src/shared/entity.h index f5fa351..01af87f 100644 --- a/src/shared/entity.h +++ b/src/shared/entity.h @@ -25,12 +25,17 @@ typedef enum { typedef struct { bool active; + // TODO: SS - Maybe add an ID here. + Entity_Type type; uint16_t x; uint16_t y; Entity_Movement_Direction move_direction; + Entity_Movement_Direction prev_move_direction; + + Entity_ID child; // TODO: SS - Color/tint? } Entity; diff --git a/src/shared/game_world.c b/src/shared/game_world.c index 42207f1..a1cd01f 100644 --- a/src/shared/game_world.c +++ b/src/shared/game_world.c @@ -66,10 +66,8 @@ bool game_world_create_entity(Game_World *world, Entity_Type type, uint16_t x, u return false; } - Entity_ID id = 0; - bool got_id = squeue_pop(&world->entity_id_queue, (void *)&id); - if(!got_id) { + if(!squeue_pop(&world->entity_id_queue, (void *)&id)) { printf("No free entity ids.\n"); return false; } @@ -77,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, + .child = INVALID_ENTITY_ID }; Grid_Cell *cell = grid_get_cell(&world->grid, x, y); // TEMP: SS - Hardcoded coordinates. // TODO: SS - Find good coordinate. diff --git a/src/shared/random.h b/src/shared/random.h new file mode 100644 index 0000000..08e5af5 --- /dev/null +++ b/src/shared/random.h @@ -0,0 +1,34 @@ +#ifndef RANDOM_H +#define RANDOM_H + +typedef struct { + uint32_t seed; + uint32_t state; +} Random_Generator; + +static inline void random_init(Random_Generator *random_generator, uint32_t seed) { + if(seed == 0) { + seed = 1; + } + random_generator->seed = seed; + random_generator->state = random_generator->seed; +} + +static inline uint32_t random_next_u32(Random_Generator *random_generator) { + uint32_t x = random_generator->state; + + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + + random_generator->state = x; + + return x; +} + +static inline uint32_t random_u64_range(Random_Generator *random_generator, uint32_t min, uint32_t max) { + return min + (random_next_u32(rng) % (max - min + 1)); +} + + +#endif \ No newline at end of file diff --git a/src/shared/wang_hash.h b/src/shared/wang_hash.h new file mode 100644 index 0000000..831f2c3 --- /dev/null +++ b/src/shared/wang_hash.h @@ -0,0 +1,13 @@ +#ifndef WANG_HASH_H +#define WANG_HASH_H + +static inline uint32_t wang_hash(uint32_t v) { + v = (v ^ 61) ^ (v >> 16); + v = v + (v << 3); + v = v ^ (v >> 4); + v = v * 0x27d4eb2d; + v = v ^ (v >> 15); + return v; +} + +#endif \ No newline at end of file diff --git a/src/simulation/simulation_world.c b/src/simulation/simulation_world.c index 675d758..b1c4fc3 100644 --- a/src/simulation/simulation_world.c +++ b/src/simulation/simulation_world.c @@ -180,8 +180,35 @@ void simulation_world_tick(Simulation_World *simulation_world) { if(!entity->active) { continue; } + + switch(entity->type) { + case Entity_Type_Snake_Head: { + + break; + } + case Entity_Type_Snake_Body: { + // Skip. + break; + } + case Entity_Type_Food: { + // Skip (for now). + break; + } + default: { + printf("Unhandled entity-type %i.\n", entity->type); + break; + } + } { // Move entities. + // Entity *entity_to_follow = entity; + // if(entity->parent != INVALID_ENTITY_ID) { + // entity_to_follow = game_world_try_get_entity_by_id(gw, entity->parent); + // assert(entity_to_follow != NULL); + + // entity->move_direction = entity_to_follow->prev_move_direction; + // } + int16_t dx = 0; int16_t dy = 0; @@ -206,12 +233,15 @@ void simulation_world_tick(Simulation_World *simulation_world) { break; } } + + entity->prev_move_direction = entity->move_direction; 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(&gw->grid, entity->x, entity->y); + Grid_Cell *start_cell = current_cell; assert(current_cell != NULL); Grid_Cell *target_cell = grid_get_cell(&gw->grid, entity->x + dx, entity->y + dy); @@ -227,9 +257,14 @@ void simulation_world_tick(Simulation_World *simulation_world) { if(target_cell == NULL) { // Target cell does not exist. - // TODO: SS - Die. + // 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_should_grow = false; + if(target_cell->entity != NULL) { // Target cell is occupied. @@ -244,14 +279,49 @@ void simulation_world_tick(Simulation_World *simulation_world) { break; } case Entity_Type_Food: { - // TODO: SS - Eat! + // Eat! + assert(grid_try_remove_entity_from_cell(target_cell)); + + snake_should_grow = true; + should_move_to_target_cell = true; + break; } } } else { // Target cell is unoccupied and free to enter. - grid_move_entity_from_cell_to_cell(current_cell, target_cell); + 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(&gw->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(gw, e->child); + t = a; + } + } + // // TODO: SS - Recurse through this entity's children and move them too. + // if(entity->child != INVALID_ENTITY_ID) { + // printf("TODO: SS - MOVE CHILDREN TOO!\n"); + // } + + + if(snake_should_grow) { + // TODO: SS - Spawn the entity and make it a child of the snake's last child. + Entity_ID child_entity = INVALID_ENTITY_ID; + assert(game_world_create_entity(gw, Entity_Type_Snake_Body, start_cell->x, start_cell->y, &child_entity)); + + assert(entity->child == INVALID_ENTITY_ID); + entity->child = child_entity; + } } } }