|
|
@@ -0,0 +1,271 @@
|
|
|
+#include "cglm/call/vec2.h"
|
|
|
+
|
|
|
+#include "game.h"
|
|
|
+#include "scufeng/engine.h"
|
|
|
+#include "scufeng/util.h"
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ BCR_PADDLE_L_GOAL,
|
|
|
+ BCR_PADDLE_R_GOAL,
|
|
|
+} BALL_COLLISION_RESULT;
|
|
|
+
|
|
|
+GameState GameState_new(
|
|
|
+ const float paddles_w,
|
|
|
+ const float paddles_h,
|
|
|
+ const float paddle_l_x,
|
|
|
+ const float paddle_speed,
|
|
|
+ const float ball_w,
|
|
|
+ const vec2 ball_pos,
|
|
|
+ const float ball_speed
|
|
|
+) {
|
|
|
+
|
|
|
+#define BALL_START_DIR { -1.0f, 0.0f }
|
|
|
+
|
|
|
+ // clang-format off
|
|
|
+ GameState state = {
|
|
|
+ .paddle_l = {
|
|
|
+ .pos = { paddle_l_x, 0.5f },
|
|
|
+ .w = paddles_w,
|
|
|
+ .h = paddles_h,
|
|
|
+ .dirv = GLM_VEC2_ZERO_INIT,
|
|
|
+ .speed = paddle_speed
|
|
|
+ },
|
|
|
+ .paddle_r = {
|
|
|
+ .pos = { 1.0f - paddle_l_x, 0.5f },
|
|
|
+ .w = paddles_w,
|
|
|
+ .h = paddles_h,
|
|
|
+ .dirv = GLM_VEC2_ZERO_INIT,
|
|
|
+ .speed = paddle_speed
|
|
|
+ },
|
|
|
+ .ball = {
|
|
|
+ // .pos = { 0.5f, 0.5f },
|
|
|
+ .pos = { ball_pos[0], ball_pos[1] },
|
|
|
+ .w = ball_w,
|
|
|
+ .h = ball_w,
|
|
|
+ .dirv = BALL_START_DIR,
|
|
|
+ .speed = ball_speed
|
|
|
+ },
|
|
|
+ .ball_start_pos = { ball_pos[0], ball_pos[1] },
|
|
|
+ .ball_start_dir = BALL_START_DIR
|
|
|
+ };
|
|
|
+ // clang-format on
|
|
|
+
|
|
|
+ return state;
|
|
|
+}
|
|
|
+
|
|
|
+static void paddle_collision_check(MovingObject* const paddle) {
|
|
|
+ if (paddle->pos[1] - paddle->h / 2.0f < 0.0f) {
|
|
|
+ paddle->pos[1] = paddle->h / 2.0f;
|
|
|
+ }
|
|
|
+ if (paddle->pos[1] + paddle->h / 2.0f > 1.0f) {
|
|
|
+ paddle->pos[1] = 1.0f - paddle->h / 2.0f;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void paddle_r_move(GameState* const state) {
|
|
|
+ const float ball_y = state->ball.pos[1];
|
|
|
+
|
|
|
+ const float target_zone_range = state->paddle_r.h / 2.0f;
|
|
|
+ const float target_zone_top = state->paddle_r.pos[1] - target_zone_range;
|
|
|
+ const float target_zone_middle = state->paddle_r.pos[1];
|
|
|
+ const float target_zone_bottom = state->paddle_r.pos[1] + target_zone_range;
|
|
|
+
|
|
|
+ const bool ball_is_below = ball_y > target_zone_bottom;
|
|
|
+ const bool ball_is_above = ball_y < target_zone_top;
|
|
|
+ const bool ball_is_centered = ball_y <= target_zone_middle + target_zone_range / 2.0f
|
|
|
+ && ball_y >= target_zone_middle - target_zone_range / 2.0f;
|
|
|
+ // printf("%d%d\n", ball_is_below, ball_is_above);
|
|
|
+
|
|
|
+ if (ball_is_below) {
|
|
|
+ state->paddle_r.dirv[1] = 1.0f;
|
|
|
+ } else if (ball_is_above) {
|
|
|
+ state->paddle_r.dirv[1] = -1.0f;
|
|
|
+ } else if (ball_is_centered) {
|
|
|
+ state->paddle_r.dirv[1] = 0.0f;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void game_next_round(GameState* const state) {
|
|
|
+ glmc_vec2_copy(state->ball_start_pos, state->ball.pos);
|
|
|
+ glmc_vec2_copy(state->ball_start_dir, state->ball.dirv);
|
|
|
+}
|
|
|
+
|
|
|
+static bool ball_bounds_collision_check(GameState* const state, vec2 new_pos) {
|
|
|
+ MovingObject* const ball = &state->ball;
|
|
|
+
|
|
|
+ const float wd2 = ball->w / 2.0f;
|
|
|
+ const float hd2 = ball->h / 2.0f;
|
|
|
+
|
|
|
+ if (new_pos[0] + wd2 <= 0.0f) {
|
|
|
+ state->score_r++;
|
|
|
+ game_next_round(state);
|
|
|
+ return true;
|
|
|
+ } else if (new_pos[0] - wd2 >= 1.0f) {
|
|
|
+ state->score_l++;
|
|
|
+ game_next_round(state);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (new_pos[1] - hd2 < 0.0f) {
|
|
|
+ new_pos[1] = hd2;
|
|
|
+ glmc_vec2_reflect(ball->dirv, (vec2) { 0.0f, -1.0f }, ball->dirv);
|
|
|
+ }
|
|
|
+ if (new_pos[1] + hd2 > 1.0f) {
|
|
|
+ new_pos[1] = 1.0f - hd2;
|
|
|
+ glmc_vec2_reflect(ball->dirv, (vec2) { 0.0f, 1.0f }, ball->dirv);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void ball_paddles_collision_check(GameState* const state, vec2 new_pos) {
|
|
|
+ static const float possible_y_directions[] = {
|
|
|
+ 0.0f,
|
|
|
+ 0.5f,
|
|
|
+ -0.5f
|
|
|
+ };
|
|
|
+
|
|
|
+ MovingObject* const ball = &state->ball;
|
|
|
+ const MovingObject* const paddle_l = &state->paddle_l;
|
|
|
+ const MovingObject* const paddle_r = &state->paddle_r;
|
|
|
+
|
|
|
+ const float ball_wd2 = ball->w / 2.0f;
|
|
|
+ const float ball_hd2 = ball->h / 2.0f;
|
|
|
+ const float paddle_wd2 = paddle_l->w / 2.0f;
|
|
|
+ const float paddle_hd2 = paddle_l->h / 2.0f;
|
|
|
+ vec2 paddle_side_top;
|
|
|
+ vec2 paddle_side_bottom;
|
|
|
+ vec2 ball_side_top;
|
|
|
+ vec2 ball_side_bottom;
|
|
|
+ float paddle_back_x;
|
|
|
+ vec2 ball_pos;
|
|
|
+ vec2 new_pos_adj;
|
|
|
+ paddle_side_top[0] = paddle_l->pos[0] + paddle_wd2;
|
|
|
+ paddle_side_top[1] = paddle_l->pos[1] - paddle_hd2;
|
|
|
+ paddle_side_bottom[0] = paddle_side_top[0];
|
|
|
+ paddle_side_bottom[1] = paddle_l->pos[1] + paddle_hd2;
|
|
|
+ ball_side_top[0] = ball->pos[0] - ball_wd2;
|
|
|
+ ball_side_top[1] = ball->pos[1] - ball_hd2;
|
|
|
+ ball_side_bottom[0] = ball_side_top[0];
|
|
|
+ ball_side_bottom[1] = ball->pos[1] + ball_hd2;
|
|
|
+ paddle_back_x = paddle_l->pos[0] - paddle_wd2;
|
|
|
+ glmc_vec2_copy(ball->pos, ball_pos);
|
|
|
+ glmc_vec2_copy(new_pos, new_pos_adj);
|
|
|
+
|
|
|
+ bool collision = false;
|
|
|
+ bool mirror_ip = false;
|
|
|
+ vec2 ip;
|
|
|
+
|
|
|
+ if (state->ball.dirv[0] > 0.0f) {
|
|
|
+ mirror_ip = true;
|
|
|
+ paddle_side_top[0] = 1.0f - (paddle_r->pos[0] - paddle_r->w / 2.0f);
|
|
|
+ paddle_side_top[1] = (paddle_r->pos[1] - paddle_r->h / 2.0f);
|
|
|
+ paddle_side_bottom[0] = paddle_side_top[0];
|
|
|
+ paddle_side_bottom[1] = paddle_side_top[1] + paddle_r->h;
|
|
|
+ ball_pos[0] = 1.0f - ball_pos[0];
|
|
|
+ ball_side_top[0] = ball_pos[0] - ball_wd2;
|
|
|
+ ball_side_bottom[0] = ball_side_top[0];
|
|
|
+ paddle_back_x = 1.0f - (paddle_r->pos[0] + paddle_r->w / 2.0f);
|
|
|
+ new_pos_adj[0] = 1.0f - new_pos_adj[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ // SEResult res = se_render_line_segment_norm_coord(
|
|
|
+ // paddle_side_top,
|
|
|
+ // paddle_side_bottom,
|
|
|
+ // (vec4) { 0.0f, 1.0f, 0.0f, 1.0f }
|
|
|
+ // );
|
|
|
+ // if (res != SE_SUCCESS) {
|
|
|
+ // LOG_ERRORF("Failed to draw segment: %d", res);
|
|
|
+ // }
|
|
|
+ // res = se_render_line_segment_norm_coord(
|
|
|
+ // ball_side_top,
|
|
|
+ // ball_side_bottom,
|
|
|
+ // (vec4) { 1.0f, 0.0f, 0.0f, 1.0f }
|
|
|
+ // );
|
|
|
+ // if (res != SE_SUCCESS) {
|
|
|
+ // LOG_ERRORF("Failed to draw segment: %d", res);
|
|
|
+ // }
|
|
|
+ // res = se_render_line_segment_norm_coord(
|
|
|
+ // (vec2) { paddle_back_x, 0.0f },
|
|
|
+ // (vec2) { paddle_back_x, 1.0f },
|
|
|
+ // (vec4) { 0.0f, 1.0f, 0.0f, 1.0f }
|
|
|
+ // );
|
|
|
+ // if (res != SE_SUCCESS) {
|
|
|
+ // LOG_ERRORF("Failed to draw segment: %d", res);
|
|
|
+ // }
|
|
|
+
|
|
|
+ const bool inside_paddle = (ball_side_top[0] <= paddle_side_top[0] && ball_side_top[0] >= paddle_back_x)
|
|
|
+ && ((ball_side_top[1] <= paddle_side_bottom[1] && ball_side_top[1] >= paddle_side_top[1])
|
|
|
+ || (ball_side_bottom[1] <= paddle_side_bottom[1] && ball_side_bottom[1] >= paddle_side_top[1]));
|
|
|
+
|
|
|
+ if (inside_paddle) {
|
|
|
+ ball->dirv[0] *= -1.0f;
|
|
|
+ ball->dirv[1] = possible_y_directions[se_rand_int() % 3];
|
|
|
+ return;
|
|
|
+ };
|
|
|
+
|
|
|
+ collision = se_line_segments_intersect(
|
|
|
+ ball_pos,
|
|
|
+ new_pos_adj,
|
|
|
+ paddle_side_top,
|
|
|
+ paddle_side_bottom,
|
|
|
+ ip
|
|
|
+ );
|
|
|
+
|
|
|
+ if (collision) {
|
|
|
+ new_pos[0] = mirror_ip ? 1.0f - ip[0] : ip[0];
|
|
|
+ new_pos[1] = ip[1];
|
|
|
+ state->ball.dirv[0] *= -1.0f;
|
|
|
+ state->ball.dirv[1] = possible_y_directions[se_rand_int() % 3];
|
|
|
+ // state->ball.dirv[1] = 0.0f;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void ball_move(GameState* const state, const double delta) {
|
|
|
+ vec2 ball_movement;
|
|
|
+ const float ball_movement_scale = delta * state->ball.speed;
|
|
|
+ // glmc_vec2_normalize(state->ball.dirv);
|
|
|
+ glmc_vec2_scale(state->ball.dirv, ball_movement_scale, ball_movement);
|
|
|
+
|
|
|
+ vec2 new_pos;
|
|
|
+ glmc_vec2_add(state->ball.pos, ball_movement, new_pos);
|
|
|
+
|
|
|
+ if (ball_bounds_collision_check(state, new_pos)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ball_paddles_collision_check(state, new_pos);
|
|
|
+
|
|
|
+ glmc_vec2_copy(new_pos, state->ball.pos);
|
|
|
+}
|
|
|
+
|
|
|
+void GameState_move_paddle_l(GameState* const state, const SEDirection dir) {
|
|
|
+ switch (dir) {
|
|
|
+ case SE_UP:
|
|
|
+ state->paddle_l.dirv[1] = -1.0f;
|
|
|
+ break;
|
|
|
+ case SE_DOWN:
|
|
|
+ state->paddle_l.dirv[1] = 1.0f;
|
|
|
+ break;
|
|
|
+ case SE_NONE:
|
|
|
+ state->paddle_l.dirv[1] = 0.0f;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void GameState_update(GameState* const state, const double delta) {
|
|
|
+ // Paddles
|
|
|
+ vec2 paddle_movement;
|
|
|
+ const float paddle_movement_scale = delta * state->paddle_l.speed;
|
|
|
+ glmc_vec2_scale(state->paddle_l.dirv, paddle_movement_scale, paddle_movement);
|
|
|
+ glmc_vec2_add(state->paddle_l.pos, paddle_movement, state->paddle_l.pos);
|
|
|
+ paddle_collision_check(&state->paddle_l);
|
|
|
+
|
|
|
+ paddle_r_move(state);
|
|
|
+ glmc_vec2_scale(state->paddle_r.dirv, paddle_movement_scale, paddle_movement);
|
|
|
+ glmc_vec2_add(state->paddle_r.pos, paddle_movement, state->paddle_r.pos);
|
|
|
+ paddle_collision_check(&state->paddle_r);
|
|
|
+
|
|
|
+ // Ball
|
|
|
+ ball_move(state, delta);
|
|
|
+}
|