Compare commits
13 Commits
76e5b3b282
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0748b75d66 | |||
| 2b381f7151 | |||
| cd507c544d | |||
| 2a999a12bf | |||
| 0074ecd57b | |||
| 5e6b042921 | |||
| 49732d8b27 | |||
| c1f43fdc61 | |||
| 199afff820 | |||
| 4e807afa3c | |||
| d0d1a470b9 | |||
| fb885c0fc3 | |||
| d0727955d5 |
2
Makefile
2
Makefile
@@ -19,9 +19,7 @@ LIB_DIR := $(abspath libs/$(PLATFORM))
|
|||||||
CFLAGS += -I$(LIB_DIR)
|
CFLAGS += -I$(LIB_DIR)
|
||||||
LDFLAGS += -L$(LIB_DIR)
|
LDFLAGS += -L$(LIB_DIR)
|
||||||
|
|
||||||
ifeq ($(PLATFORM), linux)
|
|
||||||
LIBS += -lraylib -lm -lraygui
|
LIBS += -lraylib -lm -lraygui
|
||||||
endif
|
|
||||||
|
|
||||||
SRC := $(shell find -L $(SRC_DIR) -type f -name '*.c')
|
SRC := $(shell find -L $(SRC_DIR) -type f -name '*.c')
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# snejk
|
# snejk
|
||||||
My take on the classic game "Snake". Can be played solo or with friends online.
|
My take on the classic game "Snake". Can be played solo or with friends locally/online.
|
||||||
|
|
||||||
So far, only Linux is supported.
|
So far, only Linux is supported.
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,27 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#define INPUT_QUEUE_CAPACITY 8
|
|
||||||
|
|
||||||
void game_instance_init(Game_Instance *instance) {
|
void game_instance_init(Game_Instance *instance) {
|
||||||
instance->local_player_id = 0xFFFF;
|
instance->amount_of_local_players = 0;
|
||||||
// TODO: SS - Init input-queue.
|
|
||||||
instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context.
|
instance->simulation_accumulator = 0.0f; // TODO: SS - Should probably be moved to the Ingame context.
|
||||||
instance->game_session = NULL;
|
instance->game_session = NULL;
|
||||||
memset(&instance->game_networking, 0, sizeof(Game_Networking));
|
memset(&instance->game_networking, 0, sizeof(Game_Networking));
|
||||||
|
|
||||||
squeue_init(&instance->local_input_queue, 4, sizeof(Simulation_Game_Input));
|
for(uint8_t i = 0; i < MAX_LOCAL_PLAYERS; i++) {
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[i];
|
||||||
|
squeue_init(
|
||||||
|
&locally_controlled_player->input_queue,
|
||||||
|
MAX_INPUTS_IN_QUEUE_PER_LOCAL_PLAYER,
|
||||||
|
sizeof(Simulation_Game_Input)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_instance_dispose(Game_Instance *instance) {
|
void game_instance_dispose(Game_Instance *instance) {
|
||||||
squeue_free(&instance->local_input_queue);
|
for(uint8_t i = 0; i < MAX_LOCAL_PLAYERS; i++) {
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[i];
|
||||||
|
squeue_free(&locally_controlled_player->input_queue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings) {
|
bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings) {
|
||||||
@@ -33,7 +40,7 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session)); // LEAK
|
instance->game_session = (Game_Session *)calloc(1, sizeof(Game_Session));
|
||||||
|
|
||||||
if(settings.online) {
|
if(settings.online) {
|
||||||
if(instance->game_networking.api != NULL) {
|
if(instance->game_networking.api != NULL) {
|
||||||
@@ -52,14 +59,25 @@ bool game_instance_host_session(Game_Instance *instance, const Game_Session_Sett
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
instance->local_player_id = host_player_id;
|
game_instance_clear_local_players(instance);
|
||||||
|
assert(game_instance_setup_local_player(instance, host_player_id));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if(settings.max_players > MAX_LOCAL_PLAYERS) {
|
||||||
|
printf("Failed. Max amount of players (when playing locally) is %u. Max amount of players in the settings is %u.\n", MAX_LOCAL_PLAYERS, settings.max_players);
|
||||||
|
free(instance->game_session);
|
||||||
|
instance->game_session = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
game_session_init(instance->game_session, settings);
|
game_session_init(instance->game_session, settings);
|
||||||
|
|
||||||
for(uint32_t i = 0; i < settings.max_players; i++) {
|
|
||||||
// When playing locally/offline, create the player(s) here.
|
// When playing locally/offline, create the player(s) here.
|
||||||
assert(game_session_create_player(instance->game_session, &instance->local_player_id)); // TODO: SS - Fix me! Always assigning the newest player to be the active 'local_player_id' which isn't correct.
|
game_instance_clear_local_players(instance);
|
||||||
|
for(uint32_t i = 0; i < settings.max_players; i++) {
|
||||||
|
uint16_t player_id = 0xFFFF;
|
||||||
|
assert(game_session_create_player(instance->game_session, &player_id));
|
||||||
|
assert(game_instance_setup_local_player(instance, player_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Created %u local players.\n", settings.max_players);
|
printf("Created %u local players.\n", settings.max_players);
|
||||||
@@ -116,13 +134,44 @@ void game_instance_stop_session(Game_Instance *instance) {
|
|||||||
instance->game_session = NULL;
|
instance->game_session = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool game_instance_push_local_input(Game_Instance *instance, Simulation_Game_Input input) {
|
void game_instance_clear_local_players(Game_Instance *instance) {
|
||||||
return squeue_push(&instance->local_input_queue, (void *)&input);
|
instance->amount_of_local_players = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool game_instance_pop_local_input(Game_Instance *instance, Simulation_Game_Input *out_input) {
|
bool game_instance_setup_local_player(Game_Instance *instance, uint16_t player_id) {
|
||||||
|
if(instance->amount_of_local_players >= MAX_LOCAL_PLAYERS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[instance->amount_of_local_players];
|
||||||
|
assert(locally_controlled_player != NULL);
|
||||||
|
instance->amount_of_local_players += 1;
|
||||||
|
|
||||||
|
locally_controlled_player->player_id = player_id;
|
||||||
|
squeue_clear(&locally_controlled_player->input_queue);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game_instance_push_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input input) {
|
||||||
|
assert(local_player_index < MAX_LOCAL_PLAYERS);
|
||||||
|
assert(local_player_index < instance->amount_of_local_players);
|
||||||
|
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[local_player_index];
|
||||||
|
assert(locally_controlled_player != NULL);
|
||||||
|
|
||||||
|
return squeue_push(&locally_controlled_player->input_queue, (void *)&input);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool game_instance_pop_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input *out_input) {
|
||||||
|
assert(local_player_index < MAX_LOCAL_PLAYERS);
|
||||||
|
assert(local_player_index < instance->amount_of_local_players);
|
||||||
|
|
||||||
|
Locally_Controlled_Player *locally_controlled_player = &instance->locally_controlled_players[local_player_index];
|
||||||
|
assert(locally_controlled_player != NULL);
|
||||||
|
|
||||||
Simulation_Game_Input input = {0};
|
Simulation_Game_Input input = {0};
|
||||||
if(!squeue_pop(&instance->local_input_queue, (void *)&input)) {
|
if(!squeue_pop(&locally_controlled_player->input_queue, (void *)&input)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,17 @@
|
|||||||
|
|
||||||
#include "shared/squeue.h"
|
#include "shared/squeue.h"
|
||||||
|
|
||||||
|
#define MAX_LOCAL_PLAYERS 4
|
||||||
|
#define MAX_INPUTS_IN_QUEUE_PER_LOCAL_PLAYER 4
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint16_t local_player_id; // TODO: SS - We need to modify this to support local multiplayer.
|
uint16_t player_id;
|
||||||
SQueue local_input_queue;
|
SQueue input_queue;
|
||||||
|
} Locally_Controlled_Player;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Locally_Controlled_Player locally_controlled_players[MAX_LOCAL_PLAYERS];
|
||||||
|
uint8_t amount_of_local_players;
|
||||||
|
|
||||||
double simulation_accumulator;
|
double simulation_accumulator;
|
||||||
|
|
||||||
@@ -23,10 +31,12 @@ void game_instance_dispose(Game_Instance *instance);
|
|||||||
|
|
||||||
bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings);
|
bool game_instance_host_session(Game_Instance *instance, const Game_Session_Settings settings);
|
||||||
bool game_instance_join_session(Game_Instance *instance, const char *session_id);
|
bool game_instance_join_session(Game_Instance *instance, const char *session_id);
|
||||||
|
|
||||||
void game_instance_stop_session(Game_Instance *instance);
|
void game_instance_stop_session(Game_Instance *instance);
|
||||||
|
|
||||||
bool game_instance_push_local_input(Game_Instance *instance, Simulation_Game_Input input);
|
void game_instance_clear_local_players(Game_Instance *instance);
|
||||||
bool game_instance_pop_local_input(Game_Instance *instance, Simulation_Game_Input *out_input);
|
bool game_instance_setup_local_player(Game_Instance *instance, uint16_t player_id);
|
||||||
|
|
||||||
|
bool game_instance_push_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input input);
|
||||||
|
bool game_instance_pop_local_input(Game_Instance *instance, uint8_t local_player_index, Simulation_Game_Input *out_input);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -26,7 +26,7 @@ static void state_enter(Presentation_State *state) {
|
|||||||
.offset = (Vector2) { 0, 0 },
|
.offset = (Vector2) { 0, 0 },
|
||||||
.target = (Vector2) { 0, 0 },
|
.target = (Vector2) { 0, 0 },
|
||||||
.rotation = 0.0f,
|
.rotation = 0.0f,
|
||||||
.zoom = 3.0f
|
.zoom = 2.0f
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: SS - Maybe put the textures in an array and index them using an enum?
|
// TODO: SS - Maybe put the textures in an array and index them using an enum?
|
||||||
@@ -43,6 +43,39 @@ static void state_enter(Presentation_State *state) {
|
|||||||
assert(IsTextureValid(ctx->texture_snake_body));
|
assert(IsTextureValid(ctx->texture_snake_body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Simulation_Game_Input gather_input_for_local_player(uint8_t local_player_index) {
|
||||||
|
switch(local_player_index) {
|
||||||
|
case 0: {
|
||||||
|
return (Simulation_Game_Input) {
|
||||||
|
.right = IsKeyPressed(KEY_D),
|
||||||
|
.left = IsKeyPressed(KEY_A)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
return (Simulation_Game_Input) {
|
||||||
|
.right = IsKeyPressed(KEY_RIGHT),
|
||||||
|
.left = IsKeyPressed(KEY_LEFT)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
return (Simulation_Game_Input) {
|
||||||
|
.right = IsKeyPressed(KEY_L),
|
||||||
|
.left = IsKeyPressed(KEY_J)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
return (Simulation_Game_Input) {
|
||||||
|
.right = IsKeyPressed(KEY_H),
|
||||||
|
.left = IsKeyPressed(KEY_F)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
assert(false);
|
||||||
|
return (Simulation_Game_Input) {0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void state_tick(Presentation_State *state) {
|
static void state_tick(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;
|
||||||
@@ -53,16 +86,23 @@ static void state_tick(Presentation_State *state) {
|
|||||||
assert(session != NULL);
|
assert(session != NULL);
|
||||||
|
|
||||||
{ // Push local input to queue.
|
{ // 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.
|
if(instance->amount_of_local_players == 1) { // If playing alone (either singleplayer or online-multiplayer), more inputs are allowed.
|
||||||
.up = IsKeyDown(KEY_UP) || IsKeyDown(KEY_W),
|
Simulation_Game_Input input = (Simulation_Game_Input) {
|
||||||
.down = IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S),
|
.right = IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_D),
|
||||||
.right = IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D),
|
.left = IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_A)
|
||||||
.left = IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!simulation_input_equal(input, ctx->prev_local_input)) {
|
if(!simulation_input_empty(input)) {
|
||||||
game_instance_push_local_input(instance, input);
|
game_instance_push_local_input(instance, 0, input);
|
||||||
ctx->prev_local_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);
|
||||||
|
if(!simulation_input_empty(input)) {
|
||||||
|
game_instance_push_local_input(instance, i, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,24 +112,26 @@ static void state_tick(Presentation_State *state) {
|
|||||||
const double sim_dt = 1.0f / session->settings.tickrate;
|
const double sim_dt = 1.0f / session->settings.tickrate;
|
||||||
|
|
||||||
while (ctx->simulation_accumulator >= sim_dt) {
|
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.
|
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};
|
Simulation_Game_Input input = {0};
|
||||||
if(game_instance_pop_local_input(instance, &input)) {
|
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.)
|
game_session_set_player_input(session, i, input);
|
||||||
// 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);
|
game_session_tick(session);
|
||||||
ctx->simulation_accumulator -= sim_dt;
|
ctx->simulation_accumulator -= sim_dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: SS - I believe that this is where we should send game-messages to the clients and the host.
|
||||||
|
|
||||||
{ // TEMP: SS
|
{ // TEMP: SS
|
||||||
if(IsKeyPressed(KEY_TAB)) {
|
if(IsKeyPressed(KEY_TAB)) {
|
||||||
ctx->debug_draw_session_details = !ctx->debug_draw_session_details;
|
ctx->debug_draw_session_details = !ctx->debug_draw_session_details;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(IsKeyPressed(KEY_ESCAPE)) {
|
if(IsKeyPressed(KEY_ESCAPE)) {
|
||||||
|
// TODO: SS - Show pause-menu instead.
|
||||||
presentation_state_machine_go_to(&presentation_state_main_menu);
|
presentation_state_machine_go_to(&presentation_state_main_menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,11 +141,32 @@ uint32_t floor_texture_variant(uint32_t x, uint32_t y, uint32_t seed, uint32_t n
|
|||||||
return (wang_hash(x * 73856093u ^ y * 19349663u ^ seed)) % num_variants;
|
return (wang_hash(x * 73856093u ^ y * 19349663u ^ seed)) % num_variants;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color get_tint_for_player_id(uint16_t player_id) {
|
Color get_tint_for_player_id(uint16_t player_id) { // TODO: SS - Could get values from 'the active color-palette'.
|
||||||
switch(player_id) {
|
switch(player_id) {
|
||||||
case 0: {
|
case 0: {
|
||||||
return (Color) { 255, 135, 102, 255 };
|
return (Color) { 255, 135, 102, 255 };
|
||||||
}
|
}
|
||||||
|
case 1: {
|
||||||
|
return (Color) { 230, 204, 138, 255 };
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
return (Color) { 240, 236, 226, 255 };
|
||||||
|
}
|
||||||
|
case 4: {
|
||||||
|
return (Color) { 77, 106, 148, 255 };
|
||||||
|
}
|
||||||
|
case 5: {
|
||||||
|
return (Color) { 36, 43, 74, 255 };
|
||||||
|
}
|
||||||
|
case 6: {
|
||||||
|
return (Color) { 153, 92, 149, 255 };
|
||||||
|
}
|
||||||
|
case 7: {
|
||||||
|
return (Color) { 245, 79, 79, 255 };
|
||||||
|
}
|
||||||
|
case 8: {
|
||||||
|
return (Color) { 53, 121, 133, 255 };
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return (Color) { 82, 33, 110, 255 };
|
return (Color) { 82, 33, 110, 255 };
|
||||||
}
|
}
|
||||||
@@ -165,8 +228,8 @@ static void state_render(Presentation_State *state) {
|
|||||||
|
|
||||||
Color tint = (Color) { 255, 255, 255, 255 };
|
Color tint = (Color) { 255, 255, 255, 255 };
|
||||||
// Set the snake's tint based on player-index.
|
// Set the snake's tint based on player-index.
|
||||||
for(uint16_t i = 0; i < sim_world->max_players; i++) {
|
for(uint16_t i = 0; i < world->max_players; i++) {
|
||||||
Simulation_Player *player = &sim_world->players[i];
|
Game_World_Player *player = &world->players[i];
|
||||||
if(!player->active) {
|
if(!player->active) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -301,6 +364,9 @@ static void state_render(Presentation_State *state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: SS - Let the main-camera follow the entity you're controlling?
|
||||||
|
|
||||||
|
|
||||||
// TODO: SS - Switch on 'sim_world->match_state'.
|
// TODO: SS - Switch on 'sim_world->match_state'.
|
||||||
|
|
||||||
if(ctx->debug_draw_session_details) {
|
if(ctx->debug_draw_session_details) {
|
||||||
@@ -338,7 +404,7 @@ static void state_render(Presentation_State *state) {
|
|||||||
networking->is_host ? "true" : "false",
|
networking->is_host ? "true" : "false",
|
||||||
networking->session_id,
|
networking->session_id,
|
||||||
networking->local_client_id,
|
networking->local_client_id,
|
||||||
instance->local_player_id,
|
instance->locally_controlled_players[0].player_id,
|
||||||
game_session_get_amount_of_active_players(session), sim_world->max_players
|
game_session_get_amount_of_active_players(session), sim_world->max_players
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
Game_Instance *game_instance;
|
Game_Instance *game_instance;
|
||||||
double simulation_accumulator;
|
double simulation_accumulator;
|
||||||
Simulation_Game_Input prev_local_input;
|
|
||||||
|
|
||||||
Camera2D main_camera;
|
Camera2D main_camera;
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,15 @@ static void state_enter(Presentation_State *state) {
|
|||||||
(void)ctx;
|
(void)ctx;
|
||||||
|
|
||||||
ctx->mode = Main_Menu_Mode_Home;
|
ctx->mode = Main_Menu_Mode_Home;
|
||||||
|
|
||||||
|
SetExitKey(KEY_ESCAPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void state_tick(Presentation_State *state) {
|
static void state_tick(Presentation_State *state) {
|
||||||
Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context;
|
Presentation_State_Main_Menu_Context *ctx = (Presentation_State_Main_Menu_Context *)state->context;
|
||||||
|
|
||||||
|
switch(ctx->mode) {
|
||||||
|
case Main_Menu_Mode_Home: {
|
||||||
{ // DEBUG
|
{ // DEBUG
|
||||||
if(IsKeyPressed(KEY_P)) {
|
if(IsKeyPressed(KEY_P)) {
|
||||||
printf("Shortcut to singleplayer.\n");
|
printf("Shortcut to singleplayer.\n");
|
||||||
@@ -47,6 +51,13 @@ static void state_tick(Presentation_State *state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void state_render(Presentation_State *state) {
|
static void state_render(Presentation_State *state) {
|
||||||
@@ -118,7 +129,7 @@ static void state_render(Presentation_State *state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (GuiButton((Rectangle){ 64, 64 + (BUTTON_HEIGHT * 1), 128, BUTTON_HEIGHT }, "Cancel")) {
|
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;
|
break;
|
||||||
@@ -163,6 +174,8 @@ static void state_render(Presentation_State *state) {
|
|||||||
static void state_exit(Presentation_State *state) {
|
static void state_exit(Presentation_State *state) {
|
||||||
(void)state;
|
(void)state;
|
||||||
printf("Exited main menu\n");
|
printf("Exited main menu\n");
|
||||||
|
|
||||||
|
SetExitKey(KEY_NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
Presentation_State presentation_state_main_menu;
|
Presentation_State presentation_state_main_menu;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ void game_session_init_default_settings(bool online, Game_Session_Settings *out_
|
|||||||
out_settings->online = online;
|
out_settings->online = online;
|
||||||
out_settings->tickrate = 10.0;
|
out_settings->tickrate = 10.0;
|
||||||
out_settings->level_width = 64;
|
out_settings->level_width = 64;
|
||||||
out_settings->level_height = 32;
|
out_settings->level_height = 48;
|
||||||
out_settings->max_players = online ? 8 : 2;
|
out_settings->max_players = online ? 8 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ int mp_api_join(MultiplayerApi *api,
|
|||||||
return joinAccepted ? MP_API_OK : MP_API_ERR_REJECTED;
|
return joinAccepted ? MP_API_OK : MP_API_ERR_REJECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mp_api_game(MultiplayerApi *api, json_t *data, const char* destination) {
|
int mp_api_game(MultiplayerApi *api, json_t *data, const char* target_client_id) {
|
||||||
if (!api || !data) return MP_API_ERR_ARGUMENT;
|
if (!api || !data) return MP_API_ERR_ARGUMENT;
|
||||||
if (api->sockfd < 0 || !api->session_id) return MP_API_ERR_STATE;
|
if (api->sockfd < 0 || !api->session_id) return MP_API_ERR_STATE;
|
||||||
|
|
||||||
@@ -403,8 +403,8 @@ int mp_api_game(MultiplayerApi *api, json_t *data, const char* destination) {
|
|||||||
json_object_set_new(root, "session", json_string(api->session_id));
|
json_object_set_new(root, "session", json_string(api->session_id));
|
||||||
json_object_set_new(root, "cmd", json_string("game"));
|
json_object_set_new(root, "cmd", json_string("game"));
|
||||||
|
|
||||||
if(destination)
|
if(target_client_id)
|
||||||
json_object_set_new(root, "destination", json_string(destination));
|
json_object_set_new(root, "target_client_id", json_string(target_client_id));
|
||||||
|
|
||||||
json_t *data_copy;
|
json_t *data_copy;
|
||||||
if (json_is_object(data)) {
|
if (json_is_object(data)) {
|
||||||
@@ -612,7 +612,7 @@ static void process_line(MultiplayerApi *api, const char *line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(cmd, "joined") != 0 &&
|
if (strcmp(cmd, "joined") != 0 &&
|
||||||
strcmp(cmd, "leaved") != 0 &&
|
strcmp(cmd, "left") != 0 &&
|
||||||
strcmp(cmd, "game") != 0) {
|
strcmp(cmd, "game") != 0) {
|
||||||
json_decref(root);
|
json_decref(root);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ int mp_api_join(MultiplayerApi *api,
|
|||||||
json_t **out_data);
|
json_t **out_data);
|
||||||
|
|
||||||
/* Skickar ett "game"‑meddelande med godtycklig JSON‑data till sessionen. */
|
/* Skickar ett "game"‑meddelande med godtycklig JSON‑data till sessionen. */
|
||||||
int mp_api_game(MultiplayerApi *api, json_t *data, const char* destination);
|
int mp_api_game(MultiplayerApi *api, json_t *data, const char* target_client_id);
|
||||||
|
|
||||||
/* Registrerar en lyssnare för inkommande events.
|
/* Registrerar en lyssnare för inkommande events.
|
||||||
Returnerar ett positivt listener‑ID, eller −1 vid fel. */
|
Returnerar ett positivt listener‑ID, eller −1 vid fel. */
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ static void listen_callback(
|
|||||||
|
|
||||||
(void)data;
|
(void)data;
|
||||||
|
|
||||||
printf("#%li :: Event: '%s' from '%s'.\n", messsage_id, event, client_id);
|
printf("%s * #%li :: Event: '%s' from '%s'.\n",
|
||||||
|
networking->is_host ? "Host" : "Client",
|
||||||
|
messsage_id,
|
||||||
|
event,
|
||||||
|
client_id
|
||||||
|
);
|
||||||
|
|
||||||
assert(networking != NULL);
|
assert(networking != NULL);
|
||||||
|
|
||||||
@@ -35,8 +40,17 @@ static void listen_callback(
|
|||||||
// TODO: SS - Send a 'game' message to the joinee with information about the session?
|
// TODO: SS - Send a 'game' message to the joinee with information about the session?
|
||||||
|
|
||||||
printf("Added client-id '%s' (player-index: %u) to the map.\n", client_id, player_index);
|
printf("Added client-id '%s' (player-index: %u) to the map.\n", client_id, player_index);
|
||||||
|
|
||||||
|
{ // TEMP: SS - Testing to see if the joinee gets a message.
|
||||||
|
json_t *gameData = json_object();
|
||||||
|
json_object_set_new(gameData, "temp", json_integer(4));
|
||||||
|
|
||||||
|
mp_api_game(networking->api, gameData, NULL);
|
||||||
|
|
||||||
|
json_decref(gameData);
|
||||||
}
|
}
|
||||||
else if(strcmp(event, "leaved") == 0) {
|
}
|
||||||
|
else if(strcmp(event, "left") == 0) {
|
||||||
// TODO: SS - Index the hashmap using 'client_id' as the key and attempt to get the
|
// TODO: SS - Index the hashmap using 'client_id' as the key and attempt to get the
|
||||||
// player-index for that client. Run 'game_session_destroy_player()'.
|
// player-index for that client. Run 'game_session_destroy_player()'.
|
||||||
|
|
||||||
@@ -97,7 +111,7 @@ static bool start_listening(Game_Networking *networking) {
|
|||||||
assert(networking != NULL);
|
assert(networking != NULL);
|
||||||
assert(networking->api != NULL);
|
assert(networking->api != NULL);
|
||||||
|
|
||||||
int listener_id = mp_api_listen(networking->api, listen_callback, (void *)networking); // TODO: SS - Change context.
|
int listener_id = mp_api_listen(networking->api, listen_callback, (void *)networking);
|
||||||
if(listener_id < 0) {
|
if(listener_id < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -129,12 +143,21 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
json_t *payload = json_object();
|
||||||
|
json_object_set_new(payload, "test", json_integer(settings.max_players));
|
||||||
|
|
||||||
|
json_t *data = json_object();
|
||||||
|
json_object_set_new(data, "name", json_string("My session")); // TODO: SS - Use 'settings.name'.
|
||||||
|
json_object_set_new(data, "private", json_boolean(false)); // TODO: SS - Use 'settings.private' or '!settings.public'.
|
||||||
|
json_object_set_new(data, "maxClients", json_integer(settings.max_players));
|
||||||
|
json_object_set_new(data, "payload", payload);
|
||||||
|
|
||||||
char *session_id = NULL;
|
char *session_id = NULL;
|
||||||
char *local_client_id = NULL;
|
char *local_client_id = NULL;
|
||||||
json_t *response_data = NULL;
|
json_t *response_data = NULL;
|
||||||
int host_result = mp_api_host(
|
int host_result = mp_api_host(
|
||||||
networking->api,
|
networking->api,
|
||||||
NULL, // TODO: SS - Send data to server that contains the game-session's settings.
|
data,
|
||||||
&session_id,
|
&session_id,
|
||||||
&local_client_id,
|
&local_client_id,
|
||||||
&response_data
|
&response_data
|
||||||
@@ -142,6 +165,13 @@ bool networking_try_host(Game_Networking *networking, Game_Session *session, Gam
|
|||||||
if(host_result != MP_API_OK) {
|
if(host_result != MP_API_OK) {
|
||||||
printf("Failed to host; Got result: %i.\n", host_result);
|
printf("Failed to host; Got result: %i.\n", host_result);
|
||||||
shutdown_api(networking);
|
shutdown_api(networking);
|
||||||
|
|
||||||
|
if(response_data != NULL) {
|
||||||
|
char *response = json_dumps(response_data, 0);
|
||||||
|
printf("Response: '%s'.\n", response);
|
||||||
|
free(response);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,9 +221,10 @@ bool networking_try_join(Game_Networking *networking, Game_Session *session, con
|
|||||||
|
|
||||||
printf("Trying to join session using code: '%s' ...\n", session_id);
|
printf("Trying to join session using code: '%s' ...\n", session_id);
|
||||||
|
|
||||||
char *result_session_id;
|
char *result_session_id = NULL;
|
||||||
char *local_client_id;
|
char *local_client_id = NULL;
|
||||||
json_t *received_json;
|
json_t *received_json = NULL;
|
||||||
|
|
||||||
int join_result = mp_api_join(
|
int join_result = mp_api_join(
|
||||||
networking->api, // API.
|
networking->api, // API.
|
||||||
session_id, // Session-code.
|
session_id, // Session-code.
|
||||||
@@ -210,29 +241,63 @@ bool networking_try_join(Game_Networking *networking, Game_Session *session, con
|
|||||||
assert(strcmp(result_session_id, session_id) == 0);
|
assert(strcmp(result_session_id, session_id) == 0);
|
||||||
|
|
||||||
printf("Joined session '%s', local_client_id is '%s'.\n", result_session_id, local_client_id);
|
printf("Joined session '%s', local_client_id is '%s'.\n", result_session_id, local_client_id);
|
||||||
networking->is_host = true;
|
networking->is_host = false;
|
||||||
networking->game_session = session;
|
networking->game_session = session;
|
||||||
networking->session_id = result_session_id;
|
networking->session_id = result_session_id;
|
||||||
networking->local_client_id = local_client_id;
|
networking->local_client_id = local_client_id;
|
||||||
|
|
||||||
|
assert(start_listening(networking));
|
||||||
|
|
||||||
map_init(&networking->client_id_to_player_index);
|
map_init(&networking->client_id_to_player_index);
|
||||||
|
|
||||||
// TODO: SS - Decide how to network this..
|
// TODO: SS - Decide how to network this..
|
||||||
// Should we just receive the session's settings as well as a long list of commands of what has happened throughout the match (so far) - at least one per tick?
|
// Should we just receive the session's settings as well as a long list of commands of what has happened throughout the match (so far) - at least one per tick?
|
||||||
// Or, should this client receive the current state of the session?
|
// Or, should this client receive the current state of the session?
|
||||||
|
|
||||||
if(received_json != NULL) {
|
|
||||||
// TODO: SS - Read this json because it might contain valuable information.
|
|
||||||
json_decref(received_json);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(start_listening(networking));
|
|
||||||
|
|
||||||
// TODO: SS - Set up session based on what we got in the json.
|
// TODO: SS - Set up session based on what we got in the json.
|
||||||
// game_session_init(
|
Game_Session_Settings settings;
|
||||||
// session,
|
memset(&settings, 0, sizeof(Game_Session_Settings));
|
||||||
// settings
|
settings.online = true;
|
||||||
// );
|
{ // TEMP: SS - Get values from json.
|
||||||
|
settings.seed = 1337; // TODO: SS - Get from json.
|
||||||
|
settings.level_width = 64; // TODO: SS - Get from json.
|
||||||
|
settings.level_height = 48; // TODO: SS - Get from json.
|
||||||
|
settings.tickrate = 10.0f; // TODO: SS - Get from json.
|
||||||
|
settings.max_players = 8; // TODO: SS - Get from json.
|
||||||
|
}
|
||||||
|
|
||||||
|
if(received_json != NULL) {
|
||||||
|
// TODO: SS - Read this json. It should contain valuable information about the session.
|
||||||
|
printf("received_json is NOT NULL.\n");
|
||||||
|
|
||||||
|
char *recv_str = json_dumps(received_json, 0);
|
||||||
|
printf("Received JSON: '%s'.\n", recv_str);
|
||||||
|
|
||||||
|
printf("trying to get maxClients.\n");
|
||||||
|
json_t* max_clients_json = json_object_get(received_json, "maxClients");
|
||||||
|
if(max_clients_json != NULL) {
|
||||||
|
printf("found maxClients.\n");
|
||||||
|
if(json_is_integer(max_clients_json)) {
|
||||||
|
printf("maxClients is integer.\n");
|
||||||
|
long max_clients = json_integer_value(max_clients_json);
|
||||||
|
printf("Max clients is: '%lu'.\n", max_clients);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("maxClients is NOT an integer.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_decref(received_json);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("received_json is NULL.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
game_session_init(
|
||||||
|
session,
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,23 @@
|
|||||||
#define MIN_LEVEL_WIDTH 8 // TODO: SS - Move these out of here.
|
#define MIN_LEVEL_WIDTH 8 // TODO: SS - Move these out of here.
|
||||||
#define MIN_LEVEL_HEIGHT 8
|
#define MIN_LEVEL_HEIGHT 8
|
||||||
|
|
||||||
Game_World *game_world_create(uint32_t seed, 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_width >= MIN_LEVEL_WIDTH); // We should probably have failed earlier.
|
||||||
assert(level_height >= MIN_LEVEL_HEIGHT);
|
assert(level_height >= MIN_LEVEL_HEIGHT);
|
||||||
|
|
||||||
Game_World *w = (Game_World *)calloc(1, sizeof(Game_World));
|
Game_World *w = (Game_World *)calloc(1, sizeof(Game_World));
|
||||||
assert(w != NULL);
|
assert(w != NULL);
|
||||||
|
|
||||||
w->seed = seed;
|
w->seed = seed;
|
||||||
random_init(&w->random_generator, w->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->max_entities = level_width * level_height;
|
||||||
w->entities = (Entity *)calloc(w->max_entities, sizeof(Entity));
|
w->entities = (Entity *)calloc(w->max_entities, sizeof(Entity));
|
||||||
|
assert(w->entities != NULL);
|
||||||
|
|
||||||
// Set up entity-id queue.
|
// Set up entity-id queue.
|
||||||
assert(squeue_init(&w->entity_id_queue, w->max_entities, sizeof(Entity_ID)));
|
assert(squeue_init(&w->entity_id_queue, w->max_entities, sizeof(Entity_ID)));
|
||||||
@@ -28,18 +34,21 @@ Game_World *game_world_create(uint32_t seed, uint16_t level_width, uint16_t leve
|
|||||||
|
|
||||||
grid_initialize(&w->grid, level_width, level_height);
|
grid_initialize(&w->grid, level_width, level_height);
|
||||||
|
|
||||||
{ // TEMP: SS - Testing ..
|
// Spawn initial food(s). One per player.
|
||||||
for(uint16_t i = 0; i < 8; i++) {
|
for(uint16_t i = 0; i < w->max_players; i++) {
|
||||||
Entity_ID entity_food;
|
Entity_ID entity_food;
|
||||||
|
|
||||||
|
uint16_t x = 0;
|
||||||
|
uint16_t y = 0;
|
||||||
|
assert(game_world_find_position_to_spawn(w, &x, &y));
|
||||||
|
|
||||||
assert(game_world_create_entity(
|
assert(game_world_create_entity(
|
||||||
w,
|
w,
|
||||||
Entity_Type_Food,
|
Entity_Type_Food,
|
||||||
(uint16_t)random_u32_range(&w->random_generator, 0, level_width),
|
x, y,
|
||||||
(uint16_t)random_u32_range(&w->random_generator, 0, level_height),
|
|
||||||
&entity_food
|
&entity_food
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
@@ -48,8 +57,15 @@ void game_world_destroy(Game_World *world) {
|
|||||||
assert(world != NULL);
|
assert(world != NULL);
|
||||||
|
|
||||||
world->seed = 0;
|
world->seed = 0;
|
||||||
|
|
||||||
|
world->max_players = 0;
|
||||||
|
free(world->players);
|
||||||
|
world->players = NULL;
|
||||||
|
|
||||||
world->max_entities = 0;
|
world->max_entities = 0;
|
||||||
free(world->entities);
|
free(world->entities);
|
||||||
|
world->entities = NULL;
|
||||||
|
|
||||||
grid_dispose(&world->grid);
|
grid_dispose(&world->grid);
|
||||||
squeue_free(&world->entity_id_queue);
|
squeue_free(&world->entity_id_queue);
|
||||||
|
|
||||||
@@ -91,22 +107,284 @@ bool game_world_create_entity(Game_World *world, Entity_Type type, uint16_t x, u
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void game_world_destroy_entity(Game_World *world, Entity_ID entity_id) {
|
void game_world_destroy_entity(Game_World *world, Entity_ID entity_id, bool destroy_children) {
|
||||||
assert(world != NULL);
|
assert(world != NULL);
|
||||||
|
assert(entity_id != INVALID_ENTITY_ID);
|
||||||
|
|
||||||
Entity *entity = game_world_try_get_entity_by_id(
|
Entity_ID id_to_remove = entity_id;
|
||||||
|
|
||||||
|
while(id_to_remove != INVALID_ENTITY_ID) {
|
||||||
|
Entity *e = game_world_try_get_entity_by_id(
|
||||||
world,
|
world,
|
||||||
entity_id
|
id_to_remove
|
||||||
);
|
);
|
||||||
assert(entity != NULL);
|
assert(e != NULL);
|
||||||
memset(&entity, 0, sizeof(Entity));
|
|
||||||
|
|
||||||
Grid_Cell *cell = grid_get_cell(&world->grid, entity->x, entity->y);
|
Grid_Cell *cell = grid_get_cell(&world->grid, e->x, e->y);
|
||||||
assert(cell != NULL);
|
assert(cell != NULL);
|
||||||
assert(grid_try_remove_entity_from_cell(cell));
|
assert(grid_try_remove_entity_from_cell(cell));
|
||||||
|
|
||||||
squeue_push(&world->entity_id_queue, (void *)&entity_id);
|
squeue_push(&world->entity_id_queue, (void *)&e->id);
|
||||||
|
|
||||||
|
id_to_remove = e->child;
|
||||||
|
memset(e, 0, sizeof(Entity));
|
||||||
|
|
||||||
|
if(!destroy_children) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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! :)
|
||||||
|
|
||||||
|
for(uint16_t i = 0; i < world->max_entities; i++) {
|
||||||
|
Entity *entity = &world->entities[i];
|
||||||
|
if(!entity->active) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: {
|
||||||
|
// Skip.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Entity_Type_Food: {
|
||||||
|
// Skip (for now).
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
printf("Unhandled entity-type %i.\n", entity->type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
Entity *game_world_try_get_entity_by_id(Game_World *world, Entity_ID id) {
|
||||||
assert(world != NULL);
|
assert(world != NULL);
|
||||||
@@ -117,3 +395,62 @@ Entity *game_world_try_get_entity_by_id(Game_World *world, Entity_ID id) {
|
|||||||
|
|
||||||
return &world->entities[id];
|
return &world->entities[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool game_world_find_position_to_spawn(Game_World *world, uint16_t *out_x, uint16_t *out_y) {
|
||||||
|
assert(world != NULL);
|
||||||
|
assert(out_x != NULL);
|
||||||
|
assert(out_y != NULL);
|
||||||
|
|
||||||
|
uint32_t attempts = world->grid.width * world->grid.height;
|
||||||
|
while(attempts > 0) {
|
||||||
|
uint16_t potential_x = random_u32_range(&world->random_generator, 0, (uint32_t) world->grid.width - 1);
|
||||||
|
uint16_t potential_y = random_u32_range(&world->random_generator, 0, (uint32_t) world->grid.height - 1);
|
||||||
|
|
||||||
|
Grid_Cell *cell = grid_get_cell(&world->grid, potential_x, potential_y);
|
||||||
|
assert(cell != NULL);
|
||||||
|
|
||||||
|
// Check if cell is free.
|
||||||
|
if(cell->entity == NULL) {
|
||||||
|
*out_x = potential_x;
|
||||||
|
*out_y = potential_y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(attempts > 0) {
|
||||||
|
attempts -= 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_x = 0;
|
||||||
|
*out_y = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity_Movement_Direction game_world_choose_initial_move_direction_based_on_coords(Game_World *world, uint16_t x, uint16_t y) {
|
||||||
|
assert(world != NULL);
|
||||||
|
|
||||||
|
Grid *grid = &world->grid;
|
||||||
|
|
||||||
|
// TODO: SS - Consider potential obstacles in the grid (lava, walls etc) if we ever have them.
|
||||||
|
|
||||||
|
if (x == 0) {
|
||||||
|
return Entity_Movement_Direction_Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x == grid->width - 1) {
|
||||||
|
return Entity_Movement_Direction_Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y == 0) {
|
||||||
|
return Entity_Movement_Direction_Down;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y == grid->height - 1) {
|
||||||
|
return Entity_Movement_Direction_Up;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Entity_Movement_Direction_Up;
|
||||||
|
}
|
||||||
@@ -6,11 +6,15 @@
|
|||||||
#include "grid.h"
|
#include "grid.h"
|
||||||
#include "shared/squeue.h"
|
#include "shared/squeue.h"
|
||||||
#include "shared/random.h"
|
#include "shared/random.h"
|
||||||
|
#include "game_world_player.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t seed;
|
uint32_t seed;
|
||||||
Random_Generator random_generator;
|
Random_Generator random_generator;
|
||||||
|
|
||||||
|
Game_World_Player *players;
|
||||||
|
uint16_t max_players;
|
||||||
|
|
||||||
SQueue entity_id_queue;
|
SQueue entity_id_queue;
|
||||||
Entity *entities;
|
Entity *entities;
|
||||||
uint16_t max_entities;
|
uint16_t max_entities;
|
||||||
@@ -19,14 +23,20 @@ typedef struct {
|
|||||||
|
|
||||||
} Game_World;
|
} Game_World;
|
||||||
|
|
||||||
Game_World *game_world_create(uint32_t seed, 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);
|
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);
|
bool game_world_create_entity(Game_World *world, Entity_Type type, uint16_t x, uint16_t y, Entity_ID *out_entity_id);
|
||||||
void game_world_destroy_entity(Game_World *world, Entity_ID entity_id);
|
void game_world_destroy_entity(Game_World *world, Entity_ID entity_id, bool destroy_children);
|
||||||
|
|
||||||
// TODO: SS - "void game_world_spawn_player(Game_World *world, ..)"
|
void game_world_tick(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);
|
Entity *game_world_try_get_entity_by_id(Game_World *world, Entity_ID id);
|
||||||
|
|
||||||
|
bool game_world_find_position_to_spawn(Game_World *world, uint16_t *out_x, uint16_t *out_y);
|
||||||
|
Entity_Movement_Direction game_world_choose_initial_move_direction_based_on_coords(Game_World *world, uint16_t x, uint16_t y);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
10
src/game/shared/game_world_player.h
Normal file
10
src/game/shared/game_world_player.h
Normal file
@@ -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
|
||||||
@@ -55,3 +55,8 @@ bool squeue_peek(const SQueue *q, void *out) {
|
|||||||
memcpy(out, src, q->element_size);
|
memcpy(out, src, q->element_size);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void squeue_clear(SQueue *q) {
|
||||||
|
void *t = NULL;
|
||||||
|
while(squeue_pop(q, t)) {}
|
||||||
|
}
|
||||||
@@ -24,4 +24,6 @@ bool squeue_push(SQueue *q, const void *elem);
|
|||||||
bool squeue_pop(SQueue *q, void *out);
|
bool squeue_pop(SQueue *q, void *out);
|
||||||
bool squeue_peek(const SQueue *q, void *out);
|
bool squeue_peek(const SQueue *q, void *out);
|
||||||
|
|
||||||
|
void squeue_clear(SQueue *q);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -2,24 +2,15 @@
|
|||||||
#define SIM_GAME_INPUT_H
|
#define SIM_GAME_INPUT_H
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// Movement can probably just be one byte. For a traditional snake-game, only one directional-input is OK.
|
|
||||||
bool up;
|
|
||||||
bool down;
|
|
||||||
bool right;
|
bool right;
|
||||||
bool left;
|
bool left;
|
||||||
} Simulation_Game_Input;
|
} Simulation_Game_Input;
|
||||||
|
|
||||||
static inline bool simulation_input_equal(Simulation_Game_Input a, Simulation_Game_Input b) {
|
static inline bool simulation_input_empty(Simulation_Game_Input input) {
|
||||||
if(a.up != b.up) {
|
if(input.right) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(a.down != b.down) {
|
if(input.left) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(a.right != b.right) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(a.left != b.left) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
bool active;
|
bool active;
|
||||||
|
|
||||||
Entity_ID entity_id;
|
Game_World_Player *game_world_player;
|
||||||
|
|
||||||
Simulation_Game_Input input;
|
Simulation_Game_Input input;
|
||||||
|
|
||||||
// score, name etc.
|
// score, name etc.
|
||||||
|
|
||||||
} Simulation_Player;
|
} Simulation_Player;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Simulation_World simulation_create_world(uint32_t seed, bool online, uint8_t max
|
|||||||
|
|
||||||
w.online = online;
|
w.online = online;
|
||||||
|
|
||||||
w.game_world = game_world_create(seed, width, height);
|
w.game_world = game_world_create(seed, max_players, width, height);
|
||||||
assert(w.game_world != NULL);
|
assert(w.game_world != NULL);
|
||||||
|
|
||||||
w.match_state = Simulation_Match_State_Waiting_To_Start;
|
w.match_state = Simulation_Match_State_Waiting_To_Start;
|
||||||
@@ -49,6 +49,9 @@ void simulation_world_tick(Simulation_World *simulation_world) {
|
|||||||
|
|
||||||
// printf("TICK: %lu.\n", simulation_world->tick);
|
// 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++) {
|
for(uint16_t i = 0; i < simulation_world->command_count; i++) {
|
||||||
Simulation_Command *cmd = &simulation_world->commands[i];
|
Simulation_Command *cmd = &simulation_world->commands[i];
|
||||||
// printf("Command type: %i, player id: %i.\n", cmd->type, cmd->player_id);
|
// printf("Command type: %i, player id: %i.\n", cmd->type, cmd->player_id);
|
||||||
@@ -62,18 +65,28 @@ void simulation_world_tick(Simulation_World *simulation_world) {
|
|||||||
assert(!player->active);
|
assert(!player->active);
|
||||||
player->active = true;
|
player->active = true;
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
assert(game_world_create_entity(
|
assert(game_world_create_entity(
|
||||||
simulation_world->game_world,
|
gw,
|
||||||
Entity_Type_Snake_Head,
|
Entity_Type_Snake_Head,
|
||||||
i+6, 15, // NOTE: SS - Hardcoded spawn-position.
|
spawn_x, spawn_y,
|
||||||
&player->entity_id
|
&gwp->entity_id
|
||||||
));
|
));
|
||||||
|
|
||||||
Entity *player_entity = game_world_try_get_entity_by_id(simulation_world->game_world, player->entity_id);
|
Entity *player_entity = game_world_try_get_entity_by_id(gw, gwp->entity_id);
|
||||||
|
|
||||||
// Set initial move-direction for player-entities.
|
// Set initial move-direction for player-entities.
|
||||||
// The initial direction should probably take the spawn-position in to account.
|
player_entity->move_direction = game_world_choose_initial_move_direction_based_on_coords(
|
||||||
player_entity->move_direction = Entity_Movement_Direction_Up;
|
gw,
|
||||||
|
spawn_x, spawn_y
|
||||||
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -82,8 +95,7 @@ void simulation_world_tick(Simulation_World *simulation_world) {
|
|||||||
assert(player->active);
|
assert(player->active);
|
||||||
player->active = false;
|
player->active = false;
|
||||||
|
|
||||||
game_world_destroy_entity(simulation_world->game_world, player->entity_id);
|
assert(game_world_remove_player(gw, player->game_world_player));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Simulation_Command_Type_Set_Player_Input: {
|
case Simulation_Command_Type_Set_Player_Input: {
|
||||||
@@ -126,44 +138,75 @@ void simulation_world_tick(Simulation_World *simulation_world) {
|
|||||||
|
|
||||||
// Loop over all players in the simulation.
|
// Loop over all players in the simulation.
|
||||||
for(uint16_t i = 0; i < simulation_world->max_players; i++) {
|
for(uint16_t i = 0; i < simulation_world->max_players; i++) {
|
||||||
Simulation_Player *player = &simulation_world->players[i];
|
Simulation_Player *sim_player = &simulation_world->players[i];
|
||||||
if(!player->active) {
|
if(!sim_player->active) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// printf("* Input for player %i - up: %i, down: %i, right: %i, left: %i.\n",
|
Game_World_Player *gw_player = sim_player->game_world_player;
|
||||||
|
assert(gw_player != NULL);
|
||||||
|
if(!gw_player->active) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: SS - Not sure if this should be moved to game-world.. This is input that comes from the simulation.
|
||||||
|
|
||||||
|
// printf("* Input for player %i - right: %i, left: %i.\n",
|
||||||
// i,
|
// i,
|
||||||
// player->input.up, player->input.down, player->input.right, player->input.left
|
// player->input.right, player->input.left
|
||||||
// );
|
// );
|
||||||
|
|
||||||
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, gw_player->entity_id);
|
||||||
assert(player_entity != NULL);
|
if(player_entity == NULL) {
|
||||||
|
// No entity to control. No entity to apply input on.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Snakes can't go backwards. They have to turn.
|
// Snakes can't go backwards. They have to turn.
|
||||||
|
|
||||||
|
// We only have input 'right' and 'up'.
|
||||||
|
Simulation_Game_Input input = sim_player->input;
|
||||||
|
|
||||||
|
// Turn relative to the player's current move_direction.
|
||||||
switch(player_entity->move_direction) {
|
switch(player_entity->move_direction) {
|
||||||
case Entity_Movement_Direction_None: {
|
case Entity_Movement_Direction_None: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Entity_Movement_Direction_Up:
|
case Entity_Movement_Direction_Up: {
|
||||||
case Entity_Movement_Direction_Down: // Snakes can only go right/left if it's currently moving up/down.
|
if(input.right) {
|
||||||
{
|
|
||||||
if(player->input.right) {
|
|
||||||
player_entity->move_direction = Entity_Movement_Direction_Right;
|
player_entity->move_direction = Entity_Movement_Direction_Right;
|
||||||
}
|
}
|
||||||
else if(player->input.left) {
|
else if(input.left) {
|
||||||
player_entity->move_direction = Entity_Movement_Direction_Left;
|
player_entity->move_direction = Entity_Movement_Direction_Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Entity_Movement_Direction_Right:
|
case Entity_Movement_Direction_Down: {
|
||||||
case Entity_Movement_Direction_Left: // Snakes can only go up/down if it's currently moving right/left.
|
if(input.right) {
|
||||||
{
|
player_entity->move_direction = Entity_Movement_Direction_Left;
|
||||||
if(player->input.up) {
|
}
|
||||||
|
else if(input.left) {
|
||||||
|
player_entity->move_direction = Entity_Movement_Direction_Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Entity_Movement_Direction_Right: {
|
||||||
|
if(input.right) {
|
||||||
|
player_entity->move_direction = Entity_Movement_Direction_Down;
|
||||||
|
}
|
||||||
|
else if(input.left) {
|
||||||
player_entity->move_direction = Entity_Movement_Direction_Up;
|
player_entity->move_direction = Entity_Movement_Direction_Up;
|
||||||
}
|
}
|
||||||
else if(player->input.down) {
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Entity_Movement_Direction_Left: {
|
||||||
|
if(input.right) {
|
||||||
|
player_entity->move_direction = Entity_Movement_Direction_Up;
|
||||||
|
}
|
||||||
|
else if(input.left) {
|
||||||
player_entity->move_direction = Entity_Movement_Direction_Down;
|
player_entity->move_direction = Entity_Movement_Direction_Down;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,156 +215,7 @@ void simulation_world_tick(Simulation_World *simulation_world) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Game-logic! :)
|
game_world_tick(gw);
|
||||||
|
|
||||||
for(uint16_t i = 0; i < gw->max_entities; i++) {
|
|
||||||
Entity *entity = &gw->entities[i];
|
|
||||||
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 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(&gw->grid, entity->x, entity->y);
|
|
||||||
assert(current_cell != NULL);
|
|
||||||
Grid_Cell *target_cell = grid_get_cell(&gw->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_should_grow = 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_should_grow = 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(&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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(snake_should_grow) {
|
|
||||||
Entity *e = entity;
|
|
||||||
uint16_t child_index = 0;
|
|
||||||
while(e->child != INVALID_ENTITY_ID) {
|
|
||||||
e = game_world_try_get_entity_by_id(gw, e->child);
|
|
||||||
child_index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entity_ID child_entity = INVALID_ENTITY_ID;
|
|
||||||
assert(game_world_create_entity(
|
|
||||||
gw,
|
|
||||||
Entity_Type_Snake_Body,
|
|
||||||
e->prev_x,
|
|
||||||
e->prev_y,
|
|
||||||
&child_entity
|
|
||||||
));
|
|
||||||
|
|
||||||
assert(e->child == INVALID_ENTITY_ID);
|
|
||||||
e->child = child_entity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -46,9 +46,9 @@ int main() {
|
|||||||
presentation_state_machine.current->tick(presentation_state_machine.current);
|
presentation_state_machine.current->tick(presentation_state_machine.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(IsKeyPressed(KEY_F)) {
|
// if(IsKeyPressed(KEY_F)) { // TODO: SS - Add this back, but not 'F'.
|
||||||
ToggleFullscreen();
|
// ToggleFullscreen();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Render.
|
// Render.
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
|
|||||||
28
src/todo.md
Normal file
28
src/todo.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# TODO
|
||||||
|
- [x] find_good_position_to_spawn()
|
||||||
|
- A lot of gameplay-functionality depends on this.
|
||||||
|
- [x] Set initial move-direction based on where you spawn.
|
||||||
|
- Can be improved and take more things in to account.
|
||||||
|
- 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.
|
||||||
|
- [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.
|
||||||
|
- [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.
|
||||||
|
- [ ] Set up session when joining the host.
|
||||||
|
- [x] Change player-movement?
|
||||||
|
- Only inputs needed are RIGHT and LEFT. Turn relative to your head.
|
||||||
|
- Right now we're moving relative to world-up.
|
||||||
|
- This would fix issues with invalid inputs, for example when the player inputs RIGHT when going LEFT.
|
||||||
|
- Make this an option later?
|
||||||
|
- [ ] Customize the session.
|
||||||
|
- [ ] Replays.
|
||||||
|
- [ ] Sound effects + music.
|
||||||
|
- [ ] Options menu.
|
||||||
|
- [ ] Scoreboard.
|
||||||
Reference in New Issue
Block a user