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 "session/game_session.h"
#include "shared/wang_hash.h"
#define GRID_CELL_SIZE 8 #define GRID_CELL_SIZE 8
#define ENTITY_PRESENTATION_Y_OFFSET -2 #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) { static void state_render(Presentation_State *state) {
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context; Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
(void)ctx; (void)ctx;
@@ -108,7 +114,7 @@ static void state_render(Presentation_State *state) {
uint32_t pres_x = x * GRID_CELL_SIZE; uint32_t pres_x = x * GRID_CELL_SIZE;
uint32_t pres_y = y * 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( DrawTextureRec(
ctx->texture_grass, ctx->texture_grass,
@@ -116,7 +122,7 @@ static void state_render(Presentation_State *state) {
(Vector2) { pres_x, pres_y }, (Vector2) { pres_x, pres_y },
WHITE 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++) { 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 } (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( DrawTexturePro(
*texture, *texture,
(Rectangle) { // Source. (Rectangle) { // Source.
@@ -171,7 +201,7 @@ static void state_render(Presentation_State *state) {
GRID_CELL_SIZE GRID_CELL_SIZE
}, },
origin, // Origin. origin, // Origin.
0, // Rotation. rotation, // Rotation.
tint // Tint. tint // Tint.
); );

View File

@@ -14,7 +14,23 @@ static void state_enter(Presentation_State *state) {
} }
static void state_tick(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) { static void state_render(Presentation_State *state) {

View File

@@ -25,12 +25,17 @@ typedef enum {
typedef struct { typedef struct {
bool active; bool active;
// TODO: SS - Maybe add an ID here.
Entity_Type type; Entity_Type type;
uint16_t x; uint16_t x;
uint16_t y; uint16_t y;
Entity_Movement_Direction move_direction; Entity_Movement_Direction move_direction;
Entity_Movement_Direction prev_move_direction;
Entity_ID child;
// TODO: SS - Color/tint? // TODO: SS - Color/tint?
} Entity; } Entity;

View File

@@ -66,10 +66,8 @@ bool game_world_create_entity(Game_World *world, Entity_Type type, uint16_t x, u
return false; return false;
} }
Entity_ID id = 0; Entity_ID id = 0;
bool got_id = squeue_pop(&world->entity_id_queue, (void *)&id); if(!squeue_pop(&world->entity_id_queue, (void *)&id)) {
if(!got_id) {
printf("No free entity ids.\n"); printf("No free entity ids.\n");
return false; 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) { world->entities[id] = (Entity) {
.active = true, .active = true,
.type = type, .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. 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

@@ -181,7 +181,34 @@ void simulation_world_tick(Simulation_World *simulation_world) {
continue; 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. { // 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 dx = 0;
int16_t dy = 0; int16_t dy = 0;
@@ -207,11 +234,14 @@ void simulation_world_tick(Simulation_World *simulation_world) {
} }
} }
entity->prev_move_direction = entity->move_direction;
if(dx != 0 || dy != 0) { if(dx != 0 || dy != 0) {
// Try moving. // Try moving.
// Figure out what cell we're on and what cell we want to go to. // 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 *current_cell = grid_get_cell(&gw->grid, entity->x, entity->y);
Grid_Cell *start_cell = current_cell;
assert(current_cell != NULL); assert(current_cell != NULL);
Grid_Cell *target_cell = grid_get_cell(&gw->grid, entity->x + dx, entity->y + dy); 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) { if(target_cell == NULL) {
// Target cell does not exist. // 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 { else {
bool should_move_to_target_cell = false;
bool snake_should_grow = false;
if(target_cell->entity != NULL) { if(target_cell->entity != NULL) {
// Target cell is occupied. // Target cell is occupied.
@@ -244,14 +279,49 @@ void simulation_world_tick(Simulation_World *simulation_world) {
break; break;
} }
case Entity_Type_Food: { 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; break;
} }
} }
} }
else { else {
// Target cell is unoccupied and free to enter. // 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;
}
} }
} }
} }