/* * Copyright (C) 2019 Jordan Halase * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #define SCREEN_WIDTH 800 #define SCREEN_HEIGHT 600 #define RENDER_SCALE 16.0f #define SCREEN_WIDTH_F (SCREEN_WIDTH/RENDER_SCALE) #define SCREEN_HEIGHT_F (SCREEN_HEIGHT/RENDER_SCALE) #define SCREEN_CENTER_X (SCREEN_WIDTH_F/2) #define SCREEN_CENTER_Y (SCREEN_HEIGHT_F/2) #define TWOPI 6.283185307f #define PIOVER2 1.570796327f #define INVSQRT2 0.707106781f void fatal_sdl() { fprintf(stderr, "%s\n", SDL_GetError()); SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "Fatal Error", SDL_GetError(), NULL ); exit(1); } #define CHECK_SDL(f) do { if ((f) < 0) { fatal_sdl(); } } while (0) typedef struct vec2 { float x, y; } vec2; #define vec2(x, y) ((vec2){(x), (y)}) #define vadd(a, b) vec2((a).x+(b).x, (a).y+(b).y) #define vscale(v, s) vec2((v).x*(s), (v).y*(s)) #define vcomplex(a, b) vec2((a).x*(b).x-(a).y*(b).y, (a).x*(b).y+(a).y*(b).x) #define vdot(a, b) ((a).x*(b).x + (a).y*(b).y) #define vdotself(v) vdot(v, v) #define viszero(v) ((v).x == 0.0f && (v).y == 0.0f) enum CollisionType { COLLISION_TYPE_NONE = 0x0, COLLISION_TYPE_PLAYER = 0x1, COLLISION_TYPE_ASTEROID = 0x2, COLLISION_TYPE_UFO = 0x4, COLLISION_TYPE_PLAYER_BULLET = 0x8, COLLISION_TYPE_UFO_BULLET = 0x10 }; struct Bullet { vec2 position; vec2 velocity; // Zero velocity bullets are vacant }; #define MAX_BULLETS 8 /* Zero is initialization */ struct BulletPool { uint32_t used; // Number of bullets currently in use uint32_t index; // Index of possibly vacant bullet struct Bullet bullets[MAX_BULLETS]; }; struct BulletPool player_bullets; bool bullets_shoot(struct BulletPool *pool, const vec2 position, const vec2 velocity) { if (pool->used >= MAX_BULLETS) { return false; } uint32_t i; for (i = pool->index; i < MAX_BULLETS; ++i) { if (viszero(pool->bullets[i].velocity)) { goto found; } } for (i = 0; i < pool->index; ++i) { if (viszero(pool->bullets[i].velocity)) { goto found; } } return false; found: pool->bullets[i].position = position; pool->bullets[i].velocity = velocity; pool->index = (pool->index + 1) % MAX_BULLETS; ++pool->used; return true; } static bool is_point_in_screen(const vec2 position) { if (position.x > SCREEN_WIDTH_F || position.y > SCREEN_HEIGHT_F || position.x < 0.0f || position.y < 0.0f) { return false; } return true; } void bullets_integrate(struct BulletPool *pool) { for (uint32_t i = 0; i < MAX_BULLETS; ++i) { if (!viszero(pool->bullets[i].velocity)) { pool->bullets[i].position = vadd(pool->bullets[i].position, pool->bullets[i].velocity); if (!is_point_in_screen(pool->bullets[i].position)) { pool->bullets[i].velocity = vec2(0.0f, 0.0f); --pool->used; pool->index = i; } } } } void bullets_draw(const struct BulletPool *pool, SDL_Renderer *ren) { uint32_t num_bullets = 0; SDL_Point points[MAX_BULLETS]; for (uint32_t i = 0; i < MAX_BULLETS; ++i) { if (!viszero(pool->bullets[i].velocity)) { const vec2 v = vscale(pool->bullets[i].position, RENDER_SCALE); points[num_bullets].x = lroundf(v.x); points[num_bullets].y = lroundf(v.y); ++num_bullets; } } SDL_RenderDrawPoints(ren, points, num_bullets); } struct KinematicBody { vec2 position; vec2 velocity; vec2 acceleration; float angle; float angular_velocity; float k_max_speed; float collision_radius; uint16_t collision_listen_mask; uint16_t collision_create_mask; }; struct Player { struct KinematicBody body; vec2 direction; float k_acceleration; float k_angular_speed; bool can_shoot; bool is_thrusting; bool show_thrust; uint8_t thrust_counter; }; #define PLAYER_RENDER_RADIUS INVSQRT2 void player_create(struct Player *player, const vec2 position, const float update_rate) { *player = (struct Player){ .body = { .position = position, .angle = -PIOVER2, .collision_radius = PLAYER_RENDER_RADIUS, .k_max_speed = 0.375f }, .direction = vec2(0.0f, -1.0f), .k_acceleration = 0.375f/update_rate, .k_angular_speed = TWOPI/update_rate, // 1 revolution per second .can_shoot = true }; } void player_input(struct Player *player, const Uint8 *keys) { struct KinematicBody *body = &player->body; bool update_direction = false; if (keys[SDL_SCANCODE_LEFT] && !keys[SDL_SCANCODE_RIGHT]) { body->angular_velocity = -player->k_angular_speed; update_direction = true; } else if (keys[SDL_SCANCODE_RIGHT] && !keys[SDL_SCANCODE_LEFT]) { body->angular_velocity = player->k_angular_speed; update_direction = true; } else { body->angular_velocity = 0.0f; } if (keys[SDL_SCANCODE_UP]) { body->acceleration = vscale(player->direction, player->k_acceleration); player->is_thrusting = true; } else { body->acceleration = vec2(0.0f, 0.0f); player->is_thrusting = false; } if (keys[SDL_SCANCODE_Z]) { if (player->can_shoot) { // TODO const vec2 nose = vec2(INVSQRT2, 0.0f); // Fire out the nose of the ship const vec2 pos = vadd(vcomplex(nose, player->direction), body->position); if (bullets_shoot(&player_bullets, pos, vadd(vscale(player->direction, 0.375f), body->velocity))) { // Successful fire } player->can_shoot = false; } } else { player->can_shoot = true; } if (update_direction) { player->direction.x = cosf(body->angle); player->direction.y = sinf(body->angle); } } void kinematic_integrate(struct KinematicBody *body) { body->angle += body->angular_velocity; while (body->angle < 0.0f) body->angle += TWOPI; while (body->angle >= TWOPI) body->angle -= TWOPI; body->velocity = vadd(body->velocity, body->acceleration); if (body->velocity.x > body->k_max_speed) { body->velocity.x = body->k_max_speed; } else if (-body->velocity.x > body->k_max_speed) { body->velocity.x = -body->k_max_speed; } if (body->velocity.y > body->k_max_speed) { body->velocity.y = body->k_max_speed; } else if (-body->velocity.y > body->k_max_speed) { body->velocity.y = -body->k_max_speed; } body->position = vadd(body->position, body->velocity); } void kinematic_wrap(struct KinematicBody *body) { while (body->position.x > SCREEN_WIDTH_F + body->collision_radius) { body->position.x -= SCREEN_WIDTH_F; } while (body->position.x < -body->collision_radius) { body->position.x += SCREEN_WIDTH_F; } while (body->position.y > SCREEN_HEIGHT_F + body->collision_radius) { body->position.y -= SCREEN_HEIGHT_F; } while (body->position.y < -body->collision_radius) { body->position.y += SCREEN_HEIGHT_F; } } /* Exact center of mass to set the origin of the player 1 - 1/sqrt(2) */ #define PLAYER_ORIGIN 0.292893219f static const vec2 player_lines[] = { vec2(1.0f - PLAYER_ORIGIN, 0.0f), vec2(-0.25f - PLAYER_ORIGIN, -0.5f), vec2(1.0f - PLAYER_ORIGIN, 0.0f), vec2(-0.25f - PLAYER_ORIGIN, 0.5f), vec2(0.0f - PLAYER_ORIGIN, -0.4f), vec2( 0.0f - PLAYER_ORIGIN, 0.4f), vec2(0.0f - PLAYER_ORIGIN, -0.2f), vec2(-0.75f, 0.0f), vec2(0.0f - PLAYER_ORIGIN, 0.2f), vec2(-0.75f, 0.0f) }; static void player_draw(struct Player *player, SDL_Renderer *const ren) { struct KinematicBody *body = &player->body; const int num_points = player->is_thrusting && player->show_thrust ? 10 : 6; for (int i = 0; i < num_points; i += 2) { const vec2 v1 = vscale(vadd(vcomplex(player_lines[i], player->direction), body->position), RENDER_SCALE); const vec2 v2 = vscale(vadd(vcomplex(player_lines[i+1], player->direction), body->position), RENDER_SCALE); SDL_RenderDrawLine( ren, lroundf(v1.x), lroundf(v1.y), lroundf(v2.x), lroundf(v2.y) ); } if (++player->thrust_counter > 1) { player->thrust_counter = 0; player->show_thrust = !player->show_thrust; } } int main(int argc, char **argv) { (void)argc; (void)argv; CHECK_SDL(SDL_Init(SDL_INIT_EVERYTHING)); SDL_Window *win = SDL_CreateWindow( "Asteroids Clone", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, 0 ); if (!win) fatal_sdl(); SDL_Renderer *ren = SDL_CreateRenderer( win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC ); if (!ren) fatal_sdl(); struct Player player; player_create(&player, vec2(SCREEN_CENTER_X, SCREEN_CENTER_Y), 60.0f); SDL_Event e; for (;;) { while(SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) goto quit; if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { goto quit; } } const Uint8 *keys = SDL_GetKeyboardState(NULL); player_input(&player, keys); kinematic_integrate(&player.body); bullets_integrate(&player_bullets); kinematic_wrap(&player.body); SDL_SetRenderDrawColor(ren, 0, 0, 0, 255); SDL_RenderClear(ren); SDL_SetRenderDrawColor(ren, 255, 255, 255, 255); player_draw(&player, ren); bullets_draw(&player_bullets, ren); SDL_RenderPresent(ren); } quit: SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Quit(); return 0; }