From 6ac2fa6c4f117a81a31f296fc5f415250106508c Mon Sep 17 00:00:00 2001
From: Sara <sara@saragerretsen.nl>
Date: Sun, 1 Oct 2023 15:22:27 +0200
Subject: [PATCH] added keyboard input event handling

---
 src/fencer.c  |  54 +++++++++++++++++++++++-
 src/input.c   | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/input.h   |  19 +++++++++
 src/program.c |  65 +++++++++++++++++++++++-----
 src/program.h |  10 ++++-
 5 files changed, 250 insertions(+), 13 deletions(-)
 create mode 100644 src/input.c
 create mode 100644 src/input.h

diff --git a/src/fencer.c b/src/fencer.c
index 2777243..c2a0bf8 100644
--- a/src/fencer.c
+++ b/src/fencer.c
@@ -1,11 +1,63 @@
+#include "camera.h"
+#include "input.h"
 #include "program.h"
+#include "spritesheet.h"
+#include "sprite.h"
 #include "tilemap.h"
+#include <assert.h>
+
+static Spritesheet* spr_player_standing = NULL;
+static Sprite* player = NULL;
+static Transform player_trans = IdentityTransform;
+
+static Level* level = NULL;
+
+static Vector cam_speed = ZeroVector;
+
+static
+void cam_move_h(int val) {
+    cam_speed.x = -val * 0.01f;
+}
+static
+void cam_move_v(int val) {
+    cam_speed.y = val * 0.01f;
+}
+
+static
+void play() {
+    spr_player_standing = spritesheet_load("assets/sprites/player.png", (IVector){128, 128});
+    player = sprite_from_spritesheet(spr_player_standing, 0);
+    sprite_set_origin(player, (Vector){0.5f, 1.0f});
+    player_trans.scale = OneVector;
+    assert(player != NULL && "Player failed to load");
+
+    level = level_load("level_0");
+    assert(level != NULL && "Level failed to load");
+
+    input_add_axis_action(SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, &cam_move_h);
+    input_add_axis_action(SDL_SCANCODE_DOWN, SDL_SCANCODE_UP, &cam_move_v);
+    g_camera.width = 20;
+}
+
+static
+void tick() {
+    g_camera.centre = vaddf(g_camera.centre, cam_speed);
+}
+
+static
+void draw() {
+    level_draw(level);
+    sprite_draw(player, player_trans);
+}
 
 int main(int argc, char* argv[]) {
     struct ProgramSettings config = {
         .target_fps = 0, // unbounded speed
         .title = "fencer",
-        .view_resolution = {1920, 1080}
+        .view_resolution = {1920, 1080},
+        .on_play = &play,
+        .on_tick = &tick,
+        .on_draw = &draw
     };
     return program_run(&config);
 }
diff --git a/src/input.c b/src/input.c
new file mode 100644
index 0000000..ccfcffc
--- /dev/null
+++ b/src/input.c
@@ -0,0 +1,115 @@
+#include "input.h"
+#include "debug.h"
+#include <stdint.h>
+
+typedef struct InputAction {
+    const uint8_t* positive;
+    const uint8_t* negative;
+    int last;
+    InputActionDelegate delegate;
+} InputAction;
+
+static InputAction* _actions = NULL;
+static size_t _actions_len = 0;
+static size_t _actions_cap = 0;
+
+static const uint8_t* _keys;
+
+void input_init() {
+    _actions = malloc(8 * sizeof(InputAction));
+    if(_actions == NULL) {
+        LOG_ERROR("Failed to allocate memory for input actions array");
+        return;
+    }
+    _actions_cap = 8;
+
+    _keys = SDL_GetKeyboardState(NULL);
+}
+
+void input_clean() {
+    free(_actions);
+    _actions_len = 0;
+    _actions_cap = 0;
+}
+
+static
+void _key_changed_event(SDL_Event evt) {
+    const uint8_t* keyptr = _keys + evt.key.keysym.scancode;
+    for(InputAction* action = _actions; action < _actions + _actions_len; ++action) {
+        if(keyptr == action->positive || keyptr == action->negative) {
+            int val = *action->positive - *action->negative;
+            if(val != action->last) {
+                action->last = val;
+                action->delegate(val);
+            }
+        }
+    }
+}
+
+void input_handle_event(SDL_Event event) {
+    switch(event.type) {
+        default:return;
+        case SDL_KEYDOWN:
+        case SDL_KEYUP:
+            _key_changed_event(event);
+        break;
+    }
+}
+
+static
+void _resize_actions_if_needed(size_t needed_amount) {
+    if(_actions_cap > needed_amount)
+        return;
+
+    size_t next_amount = _actions_cap;
+    while(next_amount <= needed_amount) {
+        next_amount *= 2;
+    }
+
+    InputAction* new = realloc(_actions, next_amount);
+    if(new == NULL) {
+        LOG_ERROR("Failed to allocate enough space for adding a new input actions");
+        return;
+    }
+    _actions = new;
+    _actions_cap = next_amount;
+}
+
+void input_add_axis_action(SDL_Scancode negative, SDL_Scancode positive, InputActionDelegate delegate) {
+    _resize_actions_if_needed(_actions_len + 1);
+    _actions[_actions_len] = (InputAction){
+        .negative = _keys + negative,
+        .positive = _keys + positive,
+        .delegate = delegate
+    };
+    ++_actions_len;
+}
+
+void input_add_key_action(SDL_Scancode key, InputActionDelegate delegate) {
+    _resize_actions_if_needed(_actions_len + 1);
+    _actions[_actions_len] = (InputAction) {
+        .negative = NULL,
+        .positive = _keys + key,
+        .delegate = delegate
+    };
+    ++_actions_len;
+}
+
+static
+void _remove_element(InputAction* item) {
+    InputAction* next = item + 1;
+
+    if(next < _actions + _actions_len) {
+        memmove(item, next, (_actions + _actions_len) - next);
+    }
+
+    --_actions_len;
+}
+
+void input_remove_actions(InputActionDelegate delegate) {
+    for(InputAction* action = _actions; action < _actions + _actions_len; ++action) {
+        if(action->delegate == delegate) {
+            _remove_element(action);
+        }
+    }
+}
diff --git a/src/input.h b/src/input.h
new file mode 100644
index 0000000..c550526
--- /dev/null
+++ b/src/input.h
@@ -0,0 +1,19 @@
+#ifndef _fencer_input_h
+#define _fencer_input_h
+
+#include <SDL2/SDL_events.h>
+#include <SDL2/SDL_scancode.h>
+#include <SDL2/SDL_keyboard.h>
+
+typedef void (*InputActionDelegate)(int value);
+
+extern void input_init();
+extern void input_clean();
+
+extern void input_handle_event(SDL_Event event);
+
+extern void input_add_axis_action(SDL_Scancode negative, SDL_Scancode positive, InputActionDelegate delegate);
+extern void input_add_key_action(SDL_Scancode key, InputActionDelegate delegate);
+extern void input_remove_actions(InputActionDelegate delegate);
+
+#endif // !_fencer_input_h
diff --git a/src/program.c b/src/program.c
index 969dbb2..a044811 100644
--- a/src/program.c
+++ b/src/program.c
@@ -1,18 +1,44 @@
 #include "program.h"
-#include "tilemap.h"
 #include "camera.h"
 #include "sprite.h"
+#include "time.h"
+#include "assets.h"
+#include "debug.h"
+#include "input.h"
+#include <assert.h>
 #include <SDL2/SDL_video.h>
 #include <SDL2/SDL_image.h>
 
 SDL_Window* g_window;
-double g_delta_time;
-double g_frame_start;
+static double _delta_time;
+static double _frame_start;
+static double _game_start_time;
 
 
 #define INITFLAGS SDL_INIT_EVENTS | SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER
 
+static inline
+double tstos(struct timespec ts) {
+    return (double)ts.tv_sec + (double)ts.tv_nsec * 1E-09;
+}
+
+struct timespec get_time() {
+    struct timespec ts;
+    timespec_get(&ts, TIME_UTC);
+    return ts;
+}
+
+double get_time_s() {
+    return tstos(get_time());
+}
+
 int program_run(const struct ProgramSettings* settings) {
+    LOG_INFO("Starting program...");
+    float target_dt = 1.0f/settings->target_fps;
+    if(settings->target_fps <= 0) {
+        target_dt = 0;
+    }
+    _game_start_time = _frame_start = get_time_s();
     SDL_Init(INITFLAGS);
 
     g_window = SDL_CreateWindow(
@@ -21,26 +47,41 @@ int program_run(const struct ProgramSettings* settings) {
         SDL_WINDOWPOS_CENTERED_DISPLAY(0),
         settings->view_resolution.x,
         settings->view_resolution.y,
-        SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE);
+        SDL_WINDOW_MAXIMIZED | SDL_WINDOW_RESIZABLE);
 
     render_init(g_window, settings);
+    assets_init();
     camera_init();
+    input_init();
 
-    Spritesheet* sheet = spritesheet_from_texture("resources/player.png", (IVector){64, 64});
-    Sprite* sprite = sprite_from_spritesheet(sheet);
+    LOG_INFO("settings->on_play");
+    settings->on_play();
 
+    LOG_INFO("Starting program loop");
     for(;;) {
-        SDL_Delay(1);
-        program_handle_events();
-        sprite_draw(sprite, IdentityTransform);
         render_present();
+        double current;
+        do {
+            current  = get_time_s();
+            _delta_time = current - _frame_start;
+            SDL_Delay(1);
+            program_handle_events();
+        } while(_delta_time <= target_dt);
+
+        _frame_start = current;
+
+        settings->on_tick();
+        settings->on_draw();
     }
 
-    return 1;
+    LOG_ERROR("Failed to exit");
+    abort();
 }
 
 void program_quit() {
+    assets_clean();
     render_clean();
+    input_clean();
     SDL_DestroyWindow(g_window);
     SDL_Quit();
     exit(0);
@@ -51,6 +92,10 @@ void program_handle_events() {
     while(SDL_PollEvent(&event)) {
         switch(event.type) {
             default: break;
+            case SDL_KEYUP:
+            case SDL_KEYDOWN:
+                input_handle_event(event);
+            break;
             case SDL_WINDOWEVENT:
                 if(event.window.windowID == SDL_GetWindowID(g_window)) {
                     program_handle_windowevent(&event.window);
diff --git a/src/program.h b/src/program.h
index b9ae660..7c1b355 100644
--- a/src/program.h
+++ b/src/program.h
@@ -6,15 +6,21 @@
 #include "vmath.h"
 #include "render.h"
 
+typedef void(*TickCallback)();
+typedef void(*PlayCallback)();
+typedef void(*DrawCallback)();
+
 struct ProgramSettings {
     const char* title;
     IVector view_resolution;
     int target_fps;
+
+    TickCallback on_tick;
+    PlayCallback on_play;
+    DrawCallback on_draw;
 };
 
 extern SDL_Window* g_window;
-extern double g_delta_time;
-extern double g_frame_start;
 
 extern int program_run(const struct ProgramSettings* settings);
 extern void program_quit();