#include "plugin.h" #include #include #include #include #include #include #include static const vec2 PAD_SIZE = (vec2){120, 20}; static const R PLAYER_Y_OFFSET = 50; static const R PLAYER_SPEED = 800; static const R ENEMY_SPEED = 2; static const R BALL_SIZE = 18; static const R BALL_SPEED = 360; // In each dimension. static const R EPS = (R)1e-3; typedef struct Player { vec2 position; } Player; typedef struct Ball { vec2 position; vec2 velocity; } Ball; typedef struct State { bool game_started; Player human; Player enemy; Ball ball; mat4 viewProjection; } State; bool init(Game* game, State** pp_state) { assert(game); State* state = calloc(1, sizeof(State)); if (!state) { return false; } *pp_state = state; return true; cleanup: free(state); return false; } void shutdown(Game* game, State* state) { assert(game); assert(state); } static void move_ball(Ball* ball, R dt, int width, int height) { assert(ball); const R offset = BALL_SIZE / 2; ball->position = vec2_add(ball->position, vec2_scale(ball->velocity, dt)); // Right wall. if (ball->position.x + offset > (R)width) { ball->position.x = (R)width - offset - EPS; ball->velocity.x = -ball->velocity.x; } // Left wall. else if (ball->position.x - offset < 0) { ball->position.x = offset + EPS; ball->velocity.x = -ball->velocity.x; } // Top wall. if (ball->position.y + offset > (R)height) { ball->position.y = (R)height - offset - EPS; ball->velocity.y = -ball->velocity.y; } // Bottom wall. else if (ball->position.y - offset < 0) { ball->position.y = offset + EPS; ball->velocity.y = -ball->velocity.y; } } void move_enemy_player(int width, Player* player, R t) { const R half_width = (R)width / 2; const R amplitude = half_width - (PAD_SIZE.x / 2); player->position.x = half_width + amplitude * sinf(t * ENEMY_SPEED); } void move_human_player(Player* player, R dt) { assert(player); R speed = 0; if (gfx_is_key_pressed('a')) { speed -= PLAYER_SPEED; } if (gfx_is_key_pressed('d')) { speed += PLAYER_SPEED; } player->position.x += speed * dt; } void clamp_player(Player* player, int width) { assert(player); const R offset = PAD_SIZE.x / 2; // Left wall. if (player->position.x + offset > (R)width) { player->position.x = (R)width - offset; } // Right wall. else if (player->position.x - offset < 0) { player->position.x = offset; } } void collide_ball(vec2 old_ball_position, const Player* player, Ball* ball) { assert(player); assert(ball); // Discrete but simple collision. Checks for intersection and moves the ball // back by a small epsilon. // Player bounding box. const vec2 player_pmin = vec2_make( player->position.x - PAD_SIZE.x / 2, player->position.y - PAD_SIZE.y / 2); const vec2 player_pmax = vec2_make( player->position.x + PAD_SIZE.x / 2, player->position.y + PAD_SIZE.y / 2); // Ball bounding box. const vec2 ball_pmin = vec2_make( ball->position.x - BALL_SIZE / 2, ball->position.y - BALL_SIZE / 2); const vec2 ball_pmax = vec2_make( ball->position.x + BALL_SIZE / 2, ball->position.y + BALL_SIZE / 2); // Check for intersection and update ball. if (!((ball_pmax.x < player_pmin.x) || (ball_pmin.x > player_pmax.x) || (ball_pmax.y < player_pmin.y) || (ball_pmin.y > player_pmax.y))) { ball->position = vec2_add(old_ball_position, vec2_scale(ball->velocity, -EPS)); ball->velocity.y = -ball->velocity.y; } } void update(Game* game, State* state, double t, double dt) { assert(game); assert(state); // TODO: Move game width/height to GfxApp query functions? const vec2 old_ball_position = state->ball.position; move_ball(&state->ball, (R)dt, game->width, game->height); move_human_player(&state->human, (R)dt); move_enemy_player(game->width, &state->enemy, (R)t); clamp_player(&state->human, game->width); collide_ball(old_ball_position, &state->human, &state->ball); collide_ball(old_ball_position, &state->enemy, &state->ball); } static void draw_player(ImmRenderer* imm, const Player* player) { assert(imm); assert(player); const vec2 half_box = vec2_div(PAD_SIZE, vec2_make(2, 2)); const vec2 pmin = vec2_sub(player->position, half_box); const vec2 pmax = vec2_add(player->position, half_box); const aabb2 box = aabb2_make(pmin, pmax); gfx_imm_draw_aabb2(imm, box); } static void draw_ball(ImmRenderer* imm, const Ball* ball) { assert(imm); assert(ball); const vec2 half_box = vec2_make(BALL_SIZE / 2, BALL_SIZE / 2); const vec2 pmin = vec2_sub(ball->position, half_box); const vec2 pmax = vec2_add(ball->position, half_box); const aabb2 box = aabb2_make(pmin, pmax); gfx_imm_draw_aabb2(imm, box); } void render(const Game* game, const State* state) { assert(game); assert(state); ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); gfx_imm_start(imm); gfx_imm_set_view_projection_matrix(imm, &state->viewProjection); gfx_imm_load_identity(imm); gfx_imm_set_colour(imm, vec4_make(1, 1, 1, 1)); draw_player(imm, &state->human); draw_player(imm, &state->enemy); draw_ball(imm, &state->ball); gfx_imm_end(imm); } static R clamp_to_width(int width, R x, R extent) { return min(x, (R)width - extent); } void resize(Game* game, State* state, int width, int height) { assert(game); assert(state); state->viewProjection = mat4_ortho(0, (R)width, 0, (R)height, -1, 1); state->human.position.y = PLAYER_Y_OFFSET; state->enemy.position.y = (R)height - PLAYER_Y_OFFSET; if (!state->game_started) { state->human.position.x = (R)width / 2; state->enemy.position.x = (R)width / 2; state->ball.position = vec2_div(vec2_make((R)width, (R)height), vec2_make(2, 2)); state->ball.velocity = vec2_make(BALL_SPEED, BALL_SPEED); state->game_started = true; } else { state->human.position.x = clamp_to_width(width, state->human.position.x, PAD_SIZE.x / 2); state->enemy.position.x = clamp_to_width(width, state->enemy.position.x, PAD_SIZE.x / 2); } }