Files
asteroids/game.c

355 lines
7.0 KiB
C

#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 "self_destruct.h"
#include "text.h"
#include <assert.h>
#include <linux/input-event-codes.h>
#include <math.h>
#include <string.h>
#define FPS 60
#define INIT_LEVEL 1
#define SHIP_COLLIDE_R 0.025
#define SHIP_MASS 0.01
#define FIRE_JITTER 0.005
#define LIN_PWR 0.00005
#define ROT_PWR 0.002
#define SHOT_VEL 0.03
#define SHOT_COLLIDE_R 0.015
#define SHOT_MASS 0.00025
#define SHOT_LIFETIME_S 4
#define ARROW_SCALE 0.0125
#define ARROW_WIDTH 4
#define ARROW_HEIGHT 4
#define COUNTER_MASK (1 << 6)
#define ITEM_SPAWN_R 0.9
#define ITEM_COLLIDE_R 0.0125
#define ITEM_SPAWN_EXP_S 40
#define ITEM_MIN_SCORE 32
#define ARMOUR_COEFF 2.5e-6
#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0]))
enum {
SHOT_COLLIDE_PRIOR = 1,
SHIP_COLLIDE_PRIOR,
ITEM_COLLIDE_PRIOR,
};
static const vec2_t ship_verts[] = {
{ 0.0, 0.055 },
{ 0.025, -0.035 },
{ 0.0, -0.02 },
{ -0.025, -0.035 },
};
static const vec2_t fire_verts[] = {
{ 0.0, -0.075 },
{ 0.0075, -0.035 },
{ -0.0075, -0.035 },
};
static const vec2_t shot_verts[] = { { 0.0, -0.01 }, { 0.0, 0.01 } };
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 item_verts[][MAX_VERTS] = {
{ { -0.0125, -0.0125 }, { 0.0125, 0.0125 } },
{ { -0.0125, 0.0125 }, { 0.0125, -0.0125 } },
};
static const unsigned item_counts[NELEMS(item_verts)] = { 2, 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 char *msg;
static unsigned armour_level;
static bool item;
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 upgrade_armour()
{
if (armour_level >= 9)
return;
static char armour_msg[] = "ARMOUR X";
armour_msg[7] = '0' + ++armour_level;
msg = armour_msg;
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 a, unsigned b, physics_sep_t sep)
{
const float v = sep.va - sep.vb;
const float impact = physics_get(b)->mass * v;
const float armour = armour_level * ARMOUR_COEFF;
if (impact < armour)
physics_bounce(a, b, sep);
else
die();
}
static void item_collide(unsigned a, unsigned b, physics_sep_t)
{
item = false;
entity_mark(a);
if (b == ship_entity_id)
upgrade_armour();
}
static void shoot()
{
if (dead || paused)
return;
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);
self_destruct_add(id, FPS * SHOT_LIFETIME_S);
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 },
};
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_verts[0].y + FIRE_JITTER * rng_plusminus();
}
static void create_field()
{
item = false;
dead = false;
clear = false;
paused = false;
asteroid_count = 0;
msg = nullptr;
renderer_set_wrap(true);
entities_clear();
scene_clear();
physics_init();
collisions_init();
self_destruct_clear();
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;
armour_level = 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();
}
static void add_item()
{
const unsigned id = entity_add();
for (unsigned i = 0; i < NELEMS(item_verts); ++i)
scene_add(id, item_verts[i], item_counts[i], false);
const vec2_t pos = {
.x = ITEM_SPAWN_R * rng_plusminus(),
.y = ITEM_SPAWN_R * rng_plusminus(),
};
physics_add(id, pos, MAT2_ID, (vec2_t) {}, 0, 0);
collisions_add(id, ITEM_COLLIDE_R, ITEM_COLLIDE_PRIOR, item_collide);
item = true;
}
void game_init()
{
input_on_shoot(shoot);
input_on_restart(reset);
input_on_pause(pause);
asteroids_on_clear(cleared);
reset();
}
void game_update()
{
if (msg != nullptr || dead || clear || paused)
++counter;
if (counter > COUNTER_MASK)
msg = nullptr;
if (dead || paused)
return;
ship_update();
physics_update();
self_destruct_update();
collisions_update();
scene_update();
entities_purge();
const bool items_enabled
= !clear && score >= ITEM_MIN_SCORE && armour_level < 9;
if (items_enabled && !item
&& rng_canon() < 1.0f / (FPS * ITEM_SPAWN_EXP_S))
add_item();
}
void game_draw()
{
renderer_clear();
scene_draw();
text_draw_score(score);
if (clear)
draw_arrow();
const bool display_text = !(counter & COUNTER_MASK);
if (display_text) {
if (dead)
text_draw_centre("GAME OVER");
if (clear) {
draw_arrow();
if (!(counter & COUNTER_MASK))
text_draw_centre("CLEAR");
}
if (paused)
text_draw_centre("PAUSED");
if (msg != nullptr)
text_draw_centre(msg);
}
}