Body-part now follows it's parent. You can also eat food. Shortcut in the main-menu to play default singleplayer, press P.

This commit is contained in:
2025-12-12 13:21:05 +01:00
parent 0045d2788c
commit 2a0e5779cb
7 changed files with 177 additions and 10 deletions

View File

@@ -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.
);

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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.

34
src/shared/random.h Normal file
View File

@@ -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

13
src/shared/wang_hash.h Normal file
View File

@@ -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

View File

@@ -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;
}
}
}
}