#include "game.h" #include "asteroids.h" #include "collisions.h" #include "entity.h" #include "input.h" #include "physics.h" #include "renderer.h" #include "rng.h" #include "scene.h" #include "text.h" #include #include #include #include #define INIT_LEVEL 1 #define SHIP_COLLIDE_R 0.05 #define SHIP_MASS 0.02 #define FIRE_MEAN -0.15 #define FIRE_JITTER 0.01 #define LIN_PWR 0.0001 #define ROT_PWR 0.002 #define SHOT_VEL 0.03 #define SHOT_COLLIDE_R 0.03 #define SHOT_MASS 0.001 #define ARROW_SCALE 0.025 #define ARROW_WIDTH 4 #define ARROW_HEIGHT 4 #define COUNTER_MASK (1 << 6) #define NELEMS(arr) (sizeof(arr) / sizeof(arr[0])) enum { SHOT_COLLIDE_PRIOR = 1, SHIP_COLLIDE_PRIOR, }; static const vec2_t ship_verts[] = { { 0.0, 0.11 }, { 0.05, -0.07 }, { 0.0, -0.04 }, { -0.05, -0.07 }, }; static const vec2_t fire_verts[] = { { 0.0, -0.15 }, { 0.015, -0.07 }, { -0.015, -0.07 }, }; static const vec2_t shot_verts[] = { { 0.0, -0.02 }, { 0.0, 0.02 } }; static const vec2_t arrow_verts[][MAX_VERTS] = { { { 1, 2 }, { 2, 0 }, { 1, -2 } }, { { -2, 0 }, { 2, 0 } }, }; static const unsigned arrow_counts[NELEMS(arrow_verts)] = { 3, 2 }; static const vec2_t shot_vel = { 0, SHOT_VEL }; static const asteroid_size_dist_entry_t easy_dist[] = { { 0.5, ASTEROID_MEDIUM }, { 0.5, ASTEROID_LARGE }, { 0, 0 }, }; static const asteroid_size_dist_entry_t hard_dist[] = { { 0.4, ASTEROID_MEDIUM }, { 0.5, ASTEROID_LARGE }, { 0.1, ASTEROID_HUGE }, { 0, 0 }, }; static unsigned ship_entity_id; static unsigned fire_scene_id; static unsigned level; static bool dead; static bool clear; static bool paused; static unsigned asteroid_count; static uint8_t counter; static unsigned score; static void cleared() { clear = true; counter = 0; renderer_set_wrap(false); physics_escape(ship_entity_id); } static void die() { dead = true; clear = false; counter = 0; } static void shot_collide(unsigned a, unsigned b, physics_sep_t sep) { entity_mark(a); if (asteroids_check(b)) { ++score; asteroids_hit(a, b, sep); } else { entity_mark(b); } } static void ship_collide(unsigned, unsigned, physics_sep_t) { die(); } static void shoot() { physics_t *ship = physics_get(ship_entity_id); const vec2_t pos = vec2_add(ship->pos, mat2_mul_vec2(ship->dir, ship_verts[0])); const vec2_t vel = vec2_add(ship->vel, mat2_mul_vec2(ship->dir, shot_vel)); const unsigned id = entity_add(); scene_add(id, shot_verts, NELEMS(shot_verts), false); physics_add(id, pos, ship->dir, vel, 0, SHOT_MASS); collisions_add(id, SHOT_COLLIDE_R, SHOT_COLLIDE_PRIOR, shot_collide); const vec2_t p_ship = vec2_scale(ship->vel, ship->mass); const vec2_t p_shot = vec2_scale(vel, SHOT_MASS); ship->vel = vec2_scale(vec2_sub(p_ship, p_shot), 1 / SHIP_MASS); } static void draw_arrow() { const float tx = aspect - ARROW_SCALE * (ARROW_WIDTH / 2.0 + 2); const mat3_t m = { { ARROW_SCALE, 0, 0 }, { 0, ARROW_SCALE, 0 }, { tx, 0, 1 }, }; const vec3_t c_mod = { 0, 0, 1 }; const vec3_t s_mod = { ARROW_WIDTH + 2, ARROW_HEIGHT + 2, 0 }; const vec3_t c = mat3_mul_vec3(m, c_mod); const vec3_t s = mat3_mul_vec3(m, s_mod); renderer_clear_rect(vec3_reduce(c), (vec2_t) { s.x, s.y }); for (unsigned i = 0; i < NELEMS(arrow_verts); ++i) renderer_draw(arrow_verts[i], arrow_counts[i], m, false); } static void anim_fire(vec2_t *verts) { verts[0].y = FIRE_MEAN + FIRE_JITTER * rng_plusminus(); } static void create_field() { dead = false; clear = false; paused = false; asteroid_count = 0; renderer_set_wrap(true); entities_clear(); scene_clear(); physics_init(); collisions_init(); ship_entity_id = entity_add(); scene_add(ship_entity_id, ship_verts, NELEMS(ship_verts), true); physics_add( ship_entity_id, (vec2_t) {}, MAT2_ID, (vec2_t) {}, 0, SHIP_MASS); collisions_add( ship_entity_id, SHIP_COLLIDE_R, SHIP_COLLIDE_PRIOR, ship_collide); fire_scene_id = scene_add(ship_entity_id, fire_verts, NELEMS(fire_verts), true); scene_animate(fire_scene_id, anim_fire); for (unsigned i = 0; i < level; ++i) asteroids_add(level < 4 ? easy_dist : hard_dist); } static void win() { renderer_set_wrap(true); ++level; create_field(); } static void reset() { level = INIT_LEVEL; score = 0; asteroids_clear(); create_field(); } static void pause() { paused = !paused; counter = 0; } static void ship_update() { physics_t *ship = physics_get(ship_entity_id); ship->rot += ROT_PWR * (float)input.spin; if (input.thrust != 0) { const vec2_t thrust = { 0, (float)input.thrust * LIN_PWR }; const vec2_t acc = mat2_mul_vec2(ship->dir, thrust); ship->vel = vec2_add(ship->vel, acc); scene_show(fire_scene_id); } else { scene_hide(fire_scene_id); } if (clear && ship->pos.x > aspect) win(); } void game_init() { input_on_shoot(shoot); input_on_restart(reset); input_on_pause(pause); asteroids_on_clear(cleared); reset(); } void game_update() { if (dead || clear || paused) ++counter; if (dead || paused) return; ship_update(); physics_update(); collisions_update(); scene_update(); entities_purge(); } void game_draw() { renderer_clear(); scene_draw(); text_draw_score(score); if (paused && !(counter & COUNTER_MASK)) { text_draw_centre("PAUSED"); return; } if (dead && !(counter & COUNTER_MASK)) text_draw_centre("GAME OVER"); if (clear) { draw_arrow(); if (!(counter & COUNTER_MASK)) text_draw_centre("CLEAR"); } }