Moved code and assets for the game to a seperate folder. Moved jansson and raylib to third_party.

This commit is contained in:
2025-12-16 11:47:55 +01:00
parent fd2dbf232d
commit 8cdbd5b162
63 changed files with 14 additions and 14 deletions

View File

@@ -0,0 +1,355 @@
#include "state_ingame.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include "third_party/raylib.h"
#include "third_party/raygui.h"
#include "session/game_session.h"
#include "shared/wang_hash.h"
#define GRID_CELL_SIZE 8
#define ENTITY_PRESENTATION_Y_OFFSET -2
static void state_enter(Presentation_State *state) {
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
(void)ctx;
printf("Entered ingame.\n");
printf("Setting up context ...\n");
ctx->main_camera = (Camera2D) {
.offset = (Vector2) { 0, 0 },
.target = (Vector2) { 0, 0 },
.rotation = 0.0f,
.zoom = 3.0f
};
// TODO: SS - Maybe put the textures in an array and index them using an enum?
// These should probably be loaded at start-up instead of here.
ctx->texture_shadow_basic = LoadTexture("game/assets/sprites/spr_shadow_basic.png");
assert(IsTextureValid(ctx->texture_shadow_basic));
ctx->texture_grass = LoadTexture("game/assets/sprites/spr_floor_grass.png");
assert(IsTextureValid(ctx->texture_grass));
ctx->texture_apple = LoadTexture("game/assets/sprites/spr_food_apple.png");
assert(IsTextureValid(ctx->texture_apple));
ctx->texture_snake_head = LoadTexture("game/assets/sprites/spr_snake_head.png");
assert(IsTextureValid(ctx->texture_snake_head));
ctx->texture_snake_body = LoadTexture("game/assets/sprites/spr_snake_body.png");
assert(IsTextureValid(ctx->texture_snake_body));
}
static void state_tick(Presentation_State *state) {
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
(void)ctx;
Game_Instance *instance = ctx->game_instance;
assert(instance != NULL);
Game_Session *session = instance->game_session;
assert(session != NULL);
{ // Push local input to queue.
Simulation_Game_Input input = (Simulation_Game_Input) { // NOTE: SS - This needs to be slightly modified to support N local players.
.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)
};
if(!simulation_input_equal(input, ctx->prev_local_input)) {
game_instance_push_local_input(instance, input);
ctx->prev_local_input = input;
}
}
const double delta_time = GetFrameTime();
ctx->simulation_accumulator += delta_time;
const double sim_dt = 1.0f / session->settings.tickrate;
while (ctx->simulation_accumulator >= sim_dt) {
// Pop input from instance's local_input_queue and set the local player's input to it, if we have one.
Simulation_Game_Input input = {0};
if(game_instance_pop_local_input(instance, &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, instance->local_player_id, input);
}
game_session_tick(session);
ctx->simulation_accumulator -= sim_dt;
}
{ // TEMP: SS
if(IsKeyPressed(KEY_TAB)) {
ctx->debug_draw_session_details = !ctx->debug_draw_session_details;
}
if(IsKeyPressed(KEY_ESCAPE)) {
presentation_state_machine_go_to(&presentation_state_main_menu);
}
}
}
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;
}
#define SHADOW_ALPHA 64
static void state_render(Presentation_State *state) {
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
(void)ctx;
ClearBackground((Color) { 240, 236, 226, 255 });
Game_Instance *instance = ctx->game_instance;
Game_Session *session = instance->game_session;
Simulation_World *sim_world = &session->simulation_world;
assert(sim_world != NULL);
Game_World *world = sim_world->game_world;
assert(world != NULL);
Grid *grid = &world->grid;
uint32_t grid_total_size = grid->width * grid->height;
BeginMode2D(ctx->main_camera); {
// Render the level.
for(uint32_t i = 0; i < grid_total_size; i++) {
uint32_t x = i % grid->width;
uint32_t y = i / grid->width;
uint32_t pres_x = x * GRID_CELL_SIZE;
uint32_t pres_y = y * GRID_CELL_SIZE;
uint32_t random_floor_index = floor_texture_variant(x, y, world->seed, 4);
DrawTextureRec(
ctx->texture_grass,
(Rectangle) { GRID_CELL_SIZE * random_floor_index, 0, GRID_CELL_SIZE, GRID_CELL_SIZE },
(Vector2) { pres_x, pres_y },
WHITE
);
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++) {
uint32_t x = i % grid->width;
uint32_t y = i / grid->width;
uint32_t pres_x = x * GRID_CELL_SIZE;
uint32_t pres_y = y * GRID_CELL_SIZE;
Grid_Cell *cell = &grid->cells[i];
if(cell->entity != NULL) {
Entity *entity = cell->entity;
switch(entity->type) {
case Entity_Type_Snake_Head: {
Color tint = (Color) { 255, 135, 102, 255 }; // TODO: SS - Get tint based on player ID.
Vector2 origin = (Vector2) { GRID_CELL_SIZE/2, GRID_CELL_SIZE/2 };
Entity *e = entity;
uint16_t i = 0;
while(e != NULL) { // Loop over all the body-parts of the snake, including the head.
float sin_frequency = 0.0f;
float sin_amplitude = 0.0f;
uint32_t pres_x = e->x * GRID_CELL_SIZE;
uint32_t pres_y = e->y * GRID_CELL_SIZE;
Texture2D *texture = NULL;
if(e->type == Entity_Type_Snake_Head) {
texture = &ctx->texture_snake_head;
}
else {
texture = &ctx->texture_snake_body;
sin_frequency = 4.0f;
sin_amplitude = 1.0f;
}
// Draw shadow.
DrawTextureEx(
ctx->texture_shadow_basic,
(Vector2) { pres_x, pres_y },
0.0f,
1.0f,
(Color) { 0, 0, 0, SHADOW_ALPHA }
);
float rotation = 0.0f;
switch(e->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.
0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE
},
(Rectangle) { // Destination.
pres_x + origin.x,
pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - (sin((GetTime() + (float)(i) / 4.0) * sin_frequency)) * sin_amplitude,
GRID_CELL_SIZE,
GRID_CELL_SIZE
},
origin, // Origin.
rotation, // Rotation.
tint // Tint.
);
e = game_world_try_get_entity_by_id(world, e->child);
i += 1;
}
// TODO: SS - Don't draw player-name if playing by yourself.
// TODO: SS - Don't draw your own player-name, only others.
{ // Draw player-name.
const char *player_name = "player"; // NOTE: SS - Hardcoded.
const uint32_t font_size = 8;
int text_width = MeasureText(player_name, font_size);
DrawText(player_name, pres_x - (float)text_width/2.0f + 4, pres_y - 16, font_size, (Color) { 255, 255, 255, 128 });
}
break;
}
case Entity_Type_Snake_Body: {
break;
}
case Entity_Type_Food: {
uint32_t flash = ((sin(GetTime() * 8) + 1)/2) * 32;
Color tint = (Color) { 255-flash, 255-flash, 255-flash, 255 };
Vector2 origin = (Vector2) { GRID_CELL_SIZE/2, GRID_CELL_SIZE/2 };
// Draw shadow.
DrawTextureEx(
ctx->texture_shadow_basic,
(Vector2) { pres_x, pres_y },
0.0f,
1.0f,
(Color) { 0, 0, 0, SHADOW_ALPHA }
);
DrawTexturePro(
ctx->texture_apple,
(Rectangle) { // Source.
0, 0, GRID_CELL_SIZE, GRID_CELL_SIZE
},
(Rectangle) { // Destination.
pres_x + origin.x,
pres_y + origin.y + ENTITY_PRESENTATION_Y_OFFSET - ((sin((GetTime() + (x + y)) * 12) + 1)/2) * 1,
GRID_CELL_SIZE,
GRID_CELL_SIZE
},
origin, // Origin.
0, // Rotation.
tint // Tint.
);
break;
}
}
}
}
// TODO: SS - Switch on 'sim_world->match_state'.
if(ctx->debug_draw_session_details) {
char buf[2048];
// Add generic info about the game to the buffer.
uint32_t i = snprintf(&buf[0], sizeof(buf),
"Match-state: %i\n"
"Tick: %lu\n"
"\n"
"Seed: %u\n"
"Level: %ux%u\n"
"Tickrate: %.2f\n"
,
sim_world->match_state, sim_world->tick,
sim_world->game_world->seed,
sim_world->game_world->grid.width, sim_world->game_world->grid.height,
session->settings.tickrate
);
// Now, write info specific to whether you're online or offline.
if(session->settings.online) {
Game_Networking *networking = &instance->game_networking;
snprintf(&buf[i], sizeof(buf),
"\n"
"[Online]\n"
"Host: %s\n"
"Session-id: %s\n"
"Client-id: %s\n"
"\n"
"Local player: %u\n"
"Player count: %u / %u\n"
,
networking->is_host ? "true" : "false",
networking->session_id,
networking->local_client_id,
instance->local_player_id,
game_session_get_amount_of_active_players(session), sim_world->max_players
);
}
// Finally, draw the buffer to the screen (with nice shadowing!).
DrawText(buf, 17, 17, 8, BLACK);
DrawText(buf, 16, 16, 8, WHITE);
}
}
EndMode2D();
}
static void state_exit(Presentation_State *state) {
Presentation_State_Ingame_Context *ctx = (Presentation_State_Ingame_Context *)state->context;
(void)ctx;
printf("Exiting ingame\n");
UnloadTexture(ctx->texture_shadow_basic);
UnloadTexture(ctx->texture_grass);
UnloadTexture(ctx->texture_apple);
UnloadTexture(ctx->texture_snake_head);
UnloadTexture(ctx->texture_snake_body);
game_instance_stop_session(ctx->game_instance);
ctx->game_instance = NULL;
}
Presentation_State presentation_state_ingame;
void presentation_state_ingame_init(Presentation_State_Ingame_Context *ctx) {
presentation_state_ingame = (Presentation_State) {
.name = "Ingame",
.context = (void *)ctx,
.enter = state_enter,
.tick = state_tick,
.render = state_render,
.exit = state_exit
};
}

View File

@@ -0,0 +1,31 @@
#ifndef PRES_STATE_INGAME_H
#define PRES_STATE_INGAME_H
#include "states.h"
#include "simulation/simulation_world.h"
#include "third_party/raylib.h"
#include "instance/game_instance.h"
typedef struct {
Game_Instance *game_instance;
double simulation_accumulator;
Simulation_Game_Input prev_local_input;
Camera2D main_camera;
// Textures.
Texture2D texture_shadow_basic;
Texture2D texture_grass;
Texture2D texture_apple;
Texture2D texture_snake_head;
Texture2D texture_snake_body;
// Debug.
bool debug_draw_session_details;
} Presentation_State_Ingame_Context;
void presentation_state_ingame_init(Presentation_State_Ingame_Context *ctx);
#endif

View File

@@ -0,0 +1,48 @@
#include "states.h"
#include <stdio.h>
#include <assert.h>
Presentation_State_Machine presentation_state_machine = (Presentation_State_Machine) {
.current = NULL
};
Presentation_State presentation_state_create(
const char *name,
void *context,
Presentation_State_Enter_Callback enter,
Presentation_State_Tick_Callback tick,
Presentation_State_Render_Callback render,
Presentation_State_Exit_Callback exit
) {
return (Presentation_State) {
.name = name,
.context = context,
.enter = enter,
.tick = tick,
.render = render,
.exit = exit,
};
}
bool presentation_state_machine_go_to(Presentation_State *target) {
if(presentation_state_machine.current == target) {
return false;
}
if(presentation_state_machine.current != NULL) {
assert(presentation_state_machine.current->exit != NULL);
presentation_state_machine.current->exit(presentation_state_machine.current);
}
presentation_state_machine.current = target;
if(presentation_state_machine.current == NULL) {
return true; // Return true here even though it's NULL. This is nice for when we want to exit the program and the current state.
}
assert(presentation_state_machine.current->enter != NULL);
presentation_state_machine.current->enter(presentation_state_machine.current);
return true;
}

View File

@@ -0,0 +1,40 @@
#ifndef PRES_STATE_MACHINE_H
#define PRES_STATE_MACHINE_H
#include <stdbool.h>
typedef struct Presentation_State Presentation_State;
typedef void (*Presentation_State_Enter_Callback)(Presentation_State *state);
typedef void (*Presentation_State_Tick_Callback)(Presentation_State *state);
typedef void (*Presentation_State_Render_Callback)(Presentation_State *state);
typedef void (*Presentation_State_Exit_Callback)(Presentation_State *state);
struct Presentation_State {
const char *name;
void *context;
Presentation_State_Enter_Callback enter;
Presentation_State_Tick_Callback tick;
Presentation_State_Render_Callback render;
Presentation_State_Exit_Callback exit;
};
Presentation_State presentation_state_create(
const char *name,
void *context,
Presentation_State_Enter_Callback enter,
Presentation_State_Tick_Callback tick,
Presentation_State_Render_Callback render,
Presentation_State_Exit_Callback exit
);
typedef struct {
Presentation_State *current;
} Presentation_State_Machine;
extern Presentation_State_Machine presentation_state_machine;
bool presentation_state_machine_go_to(Presentation_State *target);
#endif

View File

@@ -0,0 +1,189 @@
#include "state_main_menu.h"
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include "third_party/raylib.h"
#include "third_party/raygui.h"
#include "session/networking.h"
static void state_enter(Presentation_State *state) {
Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context;
(void)ctx;
ctx->mode = Main_Menu_Mode_Home;
// if(ctx->game_session != NULL) {
// if(ctx->game_session->is_singleplayer) {
// }
// else {
// if(ctx->game_session->is_host) {
// networking_stop_hosting(ctx->game_networking);
// }
// else {
// }
// }
// game_session_dispose(ctx->game_session);
// ctx->game_session = NULL;
// }
}
static void state_tick(Presentation_State *state) {
Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context;
// { // DEBUG
// if(IsKeyPressed(KEY_P)) {
// game_session_init_default_settings(true, &ctx->session_settings);
// assert(ctx->game_session == NULL);
// ctx->game_session = (Game_Session *)calloc(1, sizeof(Game_Session));
// game_session_init(
// ctx->game_session,
// ctx->session_settings
// );
// ctx->ingame_ctx->game_instance = ctx->game_instance;
// presentation_state_machine_go_to(&presentation_state_ingame);
// }
// }
}
static void state_render(Presentation_State *state) {
Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context;
ClearBackground((Color) {
230, 204, 138, 255
});
#define BUTTON_HEIGHT 32
switch(ctx->mode) {
case Main_Menu_Mode_Home: {
if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 0), 128, BUTTON_HEIGHT }, "Singleplayer")) {
ctx->mode = Main_Menu_Mode_Singleplayer;
}
if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "Multiplayer")) {
ctx->mode = Main_Menu_Mode_Multiplayer;
}
if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 2), 128, BUTTON_HEIGHT }, "Quit")) {
*(ctx->should_quit_game) = true;
}
break;
}
case Main_Menu_Mode_Singleplayer: {
ctx->mode = Main_Menu_Mode_Game_Setup;
game_session_init_default_settings(false, &ctx->session_settings);
break;
}
case Main_Menu_Mode_Multiplayer: {
if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 0), 128, BUTTON_HEIGHT }, "Host")) {
ctx->mode = Main_Menu_Mode_Game_Setup;
game_session_init_default_settings(true, &ctx->session_settings);
}
if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "Join")) {
ctx->mode = Main_Menu_Mode_Multiplayer_Join;
memset(&ctx->session_id_input, 0, sizeof(ctx->session_id_input));
ctx->session_id_input[0] = 'A';
ctx->session_id_input[1] = 'B';
ctx->session_id_input[2] = 'C';
ctx->session_id_input[3] = 'D';
ctx->session_id_input[4] = 'E';
ctx->session_id_input[5] = 'F';
}
if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 2), 128, BUTTON_HEIGHT }, "Back to Main Menu")) {
ctx->mode = Main_Menu_Mode_Home;
}
break;
}
case Main_Menu_Mode_Game_Setup: {
// TODO: SS - Add options for the game here so players can customize..
// - public or locked session (unlock with password?)
// - max-players
// - game-speed (tickrate)
// - match-time
// - map
// - seed
// - obstacles
// - power-ups
// etc..
// Modify 'ctx->session_settings'.
if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 0), 128, BUTTON_HEIGHT }, "Play")) {
if(game_instance_host_session(ctx->game_instance, ctx->session_settings)) { // Settings indicate whether or not this is an online or offline game.
ctx->ingame_ctx->game_instance = ctx->game_instance;
presentation_state_machine_go_to(&presentation_state_ingame);
}
else {
printf("Failed to play.\n");
}
}
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;
}
break;
}
case Main_Menu_Mode_Multiplayer_Join: {
// TODO: SS - Session-browser? Shows all available sessions and you can just press it to join? Maybe be prompted with a password if it's locked?
int pressed_button_index = GuiTextInputBox(
(Rectangle) { 64, 64, 256, 128 }, // Bounds
"Session-ID", // Title
NULL, // Message
"Join", // Buttons
&ctx->session_id_input[0], // Text
sizeof(ctx->session_id_input) + 1, // Text max size
NULL // secret view active
);
if(pressed_button_index >= 0) {
if(pressed_button_index == 0) {
ctx->mode = Main_Menu_Mode_Multiplayer;
}
else if(pressed_button_index == 1) {
if(game_instance_join_session(ctx->game_instance, &ctx->session_id_input[0])) {
// Ok!
ctx->ingame_ctx->game_instance = ctx->game_instance;
presentation_state_machine_go_to(&presentation_state_ingame);
}
else {
printf("Failed to join session '%s'.\n", &ctx->session_id_input[0]);
}
}
else {
assert(false);
}
}
break;
}
}
}
static void state_exit(Presentation_State *state) {
(void)state;
printf("Exited main menu\n");
}
Presentation_State presentation_state_main_menu;
void presentation_state_main_menu_init(Presentation_State_Main_Menu_Context *ctx) {
presentation_state_main_menu = (Presentation_State) {
.name = "Main Menu",
.context = (void *)ctx,
.enter = state_enter,
.tick = state_tick,
.render = state_render,
.exit = state_exit
};
}

View File

@@ -0,0 +1,33 @@
#ifndef PRES_STATE_MAIN_MENU_H
#define PRES_STATE_MAIN_MENU_H
#include "states.h"
#include "state_ingame.h"
#include "instance/game_instance.h"
typedef enum {
Main_Menu_Mode_Home,
Main_Menu_Mode_Singleplayer,
Main_Menu_Mode_Multiplayer,
Main_Menu_Mode_Game_Setup,
Main_Menu_Mode_Multiplayer_Join,
} Main_Menu_Mode;
typedef struct {
Main_Menu_Mode mode;
Game_Session_Settings session_settings;
Game_Instance *game_instance;
bool *should_quit_game;
Presentation_State_Ingame_Context *ingame_ctx;
char session_id_input[6];
} Presentation_State_Main_Menu_Context;
void presentation_state_main_menu_init(Presentation_State_Main_Menu_Context *ctx);
#endif

View File

@@ -0,0 +1,10 @@
#ifndef PRES_STATES_H
#define PRES_STATES_H
#include "state_machine.h"
extern Presentation_State presentation_state_main_menu;
extern Presentation_State presentation_state_ingame;
#endif