355 lines
7.0 KiB
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);
|
|
}
|
|
}
|