#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; const GameStateParameters game_default_params = { .paddles_w = 0.01f, .paddles_h = 0.10f, .paddle_l_x = (1.0f / 16), .ball_w = 0.005f, .ball_pos = { 0.5f, 0.5f }, .paddle_speed = 0.7f, .ball_speed = 0.7f, }; const char *GameStateEvent_to_str(const GameStateEvent evt) { #define X(evt) \ case evt: \ return #evt; switch (evt) { XGameStateEvent(X) } #undef X } GameState GameState_new( const GameStateParameters params, const bool ai_enabled ) { #define BALL_START_DIR { -1.0f, 0.0f } // clang-format off GameState state = { .ai_enabled = ai_enabled, .paddle_l = { .pos = { params.paddle_l_x, 0.5f }, .w = params.paddles_w, .h = params.paddles_h, .dirv = GLM_VEC2_ZERO_INIT, .speed = params.paddle_speed }, .paddle_r = { .pos = { 1.0f - params.paddle_l_x, 0.5f }, .w = params.paddles_w, .h = params.paddles_h, .dirv = GLM_VEC2_ZERO_INIT, .speed = params.paddle_speed }, .ball = { // .pos = { 0.5f, 0.5f }, .pos = { params.ball_pos[0], params.ball_pos[1] }, .w = params.ball_w, .h = params.ball_w, .dirv = BALL_START_DIR, .speed = params.ball_speed }, .ball_start_pos = { params.ball_pos[0], params.ball_pos[1] }, .ball_start_dir = BALL_START_DIR, .score_l = 0, .score_r = 0 }; // 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_change_dir_ai(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 GameStateEvent 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) { return GSEVENT_R_SCORE; } else if (new_pos[0] - wd2 >= 1.0f) { return GSEVENT_L_SCORE; } if (new_pos[1] - hd2 < 0.0f) { new_pos[1] = hd2; glmc_vec2_reflect(ball->dirv, (vec2) { 0.0f, -1.0f }, ball->dirv); return GSEVENT_BALL_DIR_CHANGE; } 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 GSEVENT_BALL_DIR_CHANGE; } return GSEVENT_NONE; } static GameStateEvent 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) { // SE_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) { // SE_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) { // SE_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 GSEVENT_BALL_DIR_CHANGE; }; 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; return GSEVENT_BALL_DIR_CHANGE; } return GSEVENT_NONE; } static GameStateEvent 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); GameStateEvent event = ball_bounds_collision_check(state, new_pos); if (event != GSEVENT_NONE) { return event; } event = ball_paddles_collision_check(state, new_pos); glmc_vec2_copy(new_pos, state->ball.pos); return event; } void GameState_paddle_l_change_dir(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_paddle_r_change_dir(GameState *const state, const SEDirection dir) { switch (dir) { case SE_UP: state->paddle_r.dirv[1] = -1.0f; break; case SE_DOWN: state->paddle_r.dirv[1] = 1.0f; break; case SE_NONE: state->paddle_r.dirv[1] = 0.0f; default: break; } } GameStateEvent 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); if (state->ai_enabled) { paddle_r_change_dir_ai(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 const GameStateEvent event = ball_move(state, delta); switch (event) { case GSEVENT_L_SCORE: state->score_l++; game_next_round(state); if (state->score_l > 5) { return GSEVENT_L_WIN; } else { return event; } case GSEVENT_R_SCORE: state->score_r++; game_next_round(state); if (state->score_r > 5) { return GSEVENT_R_WIN; } else { return event; } case GSEVENT_NONE: case GSEVENT_BALL_DIR_CHANGE: break; default: break; } return event; } SEDirection GameState_paddle_l_dir_get(const GameState *const state) { if (state->paddle_l.dirv[1] == -1.0f) { return SE_UP; } else if (state->paddle_l.dirv[1] == 1.0f) { return SE_DOWN; } else { return SE_NONE; } }