Compare commits

..

43 Commits

Author SHA1 Message Date
Camden Dixie O'Brien 0c510b88d0 Don't spawn small asteroids 2025-10-15 14:45:08 +01:00
Camden Dixie O'Brien 2a55eb17ad Add huge asteroids 2025-10-15 14:44:29 +01:00
Camden Dixie O'Brien 6d16ba6e27 Add "clear" text when all asteroids destroyed 2025-10-15 14:21:31 +01:00
Camden Dixie O'Brien 7e6637dce6 Create text module for displaying messages 2025-10-15 14:21:31 +01:00
Camden Dixie O'Brien a3486b47c7 Add restart and handle quitting with callback 2025-10-15 13:13:44 +01:00
Camden Dixie O'Brien db1fc63652 Add game over message 2025-10-15 13:04:14 +01:00
Camden Dixie O'Brien 825938b41a Track asteroid count 2025-10-15 13:03:56 +01:00
Camden Dixie O'Brien 53158b25cc Add shooting recoil 2025-10-15 13:02:07 +01:00
Camden Dixie O'Brien f5f25a4463 Add renderer function to clear rectangle 2025-10-15 13:01:18 +01:00
Camden Dixie O'Brien addbe01755 Tweak some parameters 2025-10-14 19:29:32 +01:00
Camden Dixie O'Brien c4d0266673 Wrap all entities and shapes 2025-10-14 19:04:40 +01:00
Camden Dixie O'Brien ce6328e135 Replace big asteroids with smaller ones when shot 2025-10-14 18:29:56 +01:00
Camden Dixie O'Brien 0a64729698 Prevent asteroids from spawning overlapped 2025-10-14 17:38:43 +01:00
Camden Dixie O'Brien 51ce3b43ec Increase min distance asteroids spawn at 2025-10-14 17:28:23 +01:00
Camden Dixie O'Brien 47a0c9ffad Implement asteroid bouncing 2025-10-14 17:19:02 +01:00
Camden Dixie O'Brien b05a4b3fe4 Rename collision_radius to just radius 2025-10-14 16:56:53 +01:00
Camden Dixie O'Brien 7d729e42c4 Handle collisions 2025-10-14 16:51:52 +01:00
Camden Dixie O'Brien d3b1b3f926 Implement collision detection 2025-10-14 16:51:42 +01:00
Camden Dixie O'Brien 85984a0a07 Add collision info to entities 2025-10-14 16:14:35 +01:00
Camden Dixie O'Brien 57f5b345f8 Refactor asteroid generation 2025-10-14 16:09:38 +01:00
Camden Dixie O'Brien 1291ec191f Add more vector operations 2025-10-14 16:04:14 +01:00
Camden Dixie O'Brien c3552ae872 Add asteroids 2025-10-14 14:34:12 +01:00
Camden Dixie O'Brien 8276769446 Move all input handling into input module 2025-10-14 12:29:13 +01:00
Camden Dixie O'Brien 7b31fcfac5 Create RNG module 2025-10-14 12:06:06 +01:00
Camden Dixie O'Brien a698e0af7d Factor game logic into own module 2025-10-13 23:25:46 +01:00
Camden Dixie O'Brien 941cb8745c Increase max shapes and entities 2025-10-13 22:41:13 +01:00
Camden Dixie O'Brien 794a149f00 Allow display wrapping to be specified per shape 2025-10-13 22:41:09 +01:00
Camden Dixie O'Brien bbb13108e0 Add shooting 2025-10-13 22:41:09 +01:00
Camden Dixie O'Brien 94fbbf4636 Add random jitter to fire 2025-10-13 21:21:09 +01:00
Camden Dixie O'Brien f4b2ad38ea Move ship vertices so origin is closer to visual centre 2025-10-13 21:10:43 +01:00
Camden Dixie O'Brien 7d876aaf2d Refactor to seperate shapes and physics 2025-10-13 21:04:50 +01:00
Camden Dixie O'Brien 44d3bbef69 Rename MAX_VERTS_PER_DRAW to MAX_VERTS 2025-10-13 20:14:10 +01:00
Camden Dixie O'Brien 6d02f0947d Grab input device to stop key events reaching terminal 2025-10-13 20:14:07 +01:00
Camden Dixie O'Brien 0dc7d53bbe Add fire when thrust is active 2025-10-13 20:14:07 +01:00
Camden Dixie O'Brien cbea1d73c7 Use warning flags in build script 2025-10-13 18:44:20 +01:00
Camden Dixie O'Brien 9bbe8f71ec Add control / update logic 2025-10-13 18:43:40 +01:00
Camden Dixie O'Brien e6ed2194c3 Fix wrapping 2025-10-13 18:42:18 +01:00
Camden Dixie O'Brien 7097b7fb57 Implement simple line drawing 2025-10-13 16:22:10 +01:00
Camden Dixie O'Brien 87a305c2e5 Create separate input module 2025-10-13 16:09:02 +01:00
Camden Dixie O'Brien 045d0e6085 Create separate renderer and framebuffer modules 2025-10-13 16:09:02 +01:00
Camden Dixie O'Brien cb68a98347 Set up input handling 2025-10-13 13:07:20 +01:00
Camden Dixie O'Brien 1593accd09 Implement back-buffering 2025-10-12 19:17:21 +01:00
Camden Dixie O'Brien 7de7f5bfb0 Set up drawing to framebuffer 2025-10-12 18:22:18 +01:00
26 changed files with 452 additions and 1417 deletions
-108
View File
@@ -1,108 +0,0 @@
# Komorebi License
Version 2.0.0
## Acceptance
In order to get any license under these terms, you must agree
to them as both strict obligations and conditions to all
your licenses.
## Copyright License
The licensor grants you a copyright license for the software
to do everything you might do with the software that would
otherwise infringe the licensor's copyright in it for any
permitted purpose. However, you may only distribute the source
code of the software according to the [Distribution License](
#distribution-license), you may only make changes according
to the [Changes License](#changes-license), and you may not
otherwise distribute the software or new works based on the
software.
## Distribution License
The licensor grants you an additional copyright license to
distribute copies of the source code of the software. Your
license to distribute covers distributing the source code of
the software with changes permitted by the [Changes License](
#changes-license).
## Changes License
The licensor grants you an additional copyright license to
make changes for any permitted purpose.
## Patent License
The licensor grants you a patent license for the software that
covers patent claims the licensor can license, or becomes able
to license, that you would infringe by using the software.
## Personal Uses
Personal use for research, experiment, and testing for
the benefit of public knowledge, personal study, private
entertainment, hobby projects, amateur pursuits, or religious
observance, without any anticipated commercial application,
is use for a permitted purpose.
## Fair Use
You may have "fair use" rights for the software under the
law. These terms do not limit them.
## No Other Rights
These terms do not allow you to sublicense or transfer any of
your licenses to anyone else, or prevent the licensor from
granting licenses to anyone else. These terms do not imply
any other licenses.
## Patent Defense
If you make any written claim that the software infringes or
contributes to infringement of any patent, your patent license
for the software granted under these terms ends immediately. If
your company makes such a claim, your patent license ends
immediately for work on behalf of your company.
## Violations
The first time you are notified in writing that you have
violated any of these terms, or done anything with the software
not covered by your licenses, your licenses can nonetheless
continue if you come into full compliance with these terms,
and take practical steps to correct past violations, within
32 days of receiving notice. Otherwise, all your licenses
end immediately.
## No Liability
***As far as the law allows, the software comes as is, without
any warranty or condition, and the licensor will not be liable
to you for any damages arising out of these terms or the use
or nature of the software, under any kind of legal claim.***
## Definitions
The **licensor** is the individual or entity offering these
terms, and the **software** is the software the licensor makes
available under these terms.
**You** refers to the individual or entity agreeing to these
terms.
**Your company** is any legal entity, sole proprietorship,
or other kind of organization that you work for, plus all
organizations that have control over, are under the control of,
or are under common control with that organization. **Control**
means ownership of substantially all the assets of an entity,
or the power to direct its management and policies by vote,
contract, or otherwise. Control can be direct or indirect.
**Your licenses** are all the licenses granted to you for the
software under these terms.
**Use** means anything you do with the software requiring one
of your licenses.
-27
View File
@@ -1,27 +0,0 @@
ASTEROIDS
This is a clone (ish) of the classic game Asteroids, written in C23
for Linux. To build the program, you'll need the Linux headers, a
C23-compatible compiler and libdrm, along with its headers. There is
a build script that uses GCC; simply running this (./build.sh) is
likely to work on many systems. However, you will likely need to
adjust the input and video devices used (in input.c and renderer.c
respectively) to match your system.
The game directly uses Linux DRM, so it doesn't play nice with X,
display managers etc. You'll likely want to change to a virtual TTY
and run the game from there rather than from inside a graphical
session. You may also need to add your user to the "input" group.
CONTROLS
+--------------------------+
| forward up |
| anti-clockwise left |
| clockwise right |
| fire space |
| |
| pause p |
| quit q |
| restart r |
+--------------------------+
-183
View File
@@ -1,183 +0,0 @@
#include "asteroids.h"
#include "collisions.h"
#include "entity.h"
#include "renderer.h"
#include "rng.h"
#include "scene.h"
#include <assert.h>
#include <math.h>
#include <string.h>
#define MIN_VERTS 5
#define VERT_RANGE (MAX_VERTS - MIN_VERTS)
#define A_JITTER 0.8
#define V_JITTER 0.0005
#define ROT_JITTER 0.005
#define R_JITTER 0.01
#define MIN_DIST 0.8
#define REPLACE_MIN 3
#define REPLACE_MAX 5
#define REPLACE_RANGE (REPLACE_MAX - REPLACE_MIN)
#define REPLACE_R_COEFF 0.8
#define REPLACE_R_JITTER 0.02
#define REPLACE_A_JITTER 0.4
#define REPLACE_V_JITTER 0.0005
#define REPLACE_RADIAL_V_COEFF 0.005
#define REPLACE_ROT_JITTER 0.02
#define COLLIDE_PRIOR 0
typedef struct {
bool valid;
unsigned component_id;
asteroid_size_t size;
} entry_t;
static asteroids_cb_t clear;
static unsigned count;
static entry_t entries[MAX_ENTITIES];
static float radii[] = {
[ASTEROID_SMALL] = 0.025f,
[ASTEROID_MEDIUM] = 0.05f,
[ASTEROID_LARGE] = 0.1f,
[ASTEROID_HUGE] = 0.2f,
};
static void update(unsigned new_entity_id, void *ref)
{
entry_t *old_e = (entry_t *)ref;
entry_t *new_e = entries + new_entity_id;
memcpy(new_e, old_e, sizeof(entry_t));
old_e->valid = false;
entity_update_component(new_entity_id, new_e->component_id, new_e);
}
static void remove(void *ref)
{
entry_t *e = (entry_t *)ref;
e->valid = false;
--count;
if (count == 0)
clear();
}
static void
create_asteroid(vec2_t pos, vec2_t vel, float rot, asteroid_size_t size)
{
const float r_mean = radii[size];
const unsigned n = MIN_VERTS + rng_uint32() % VERT_RANGE;
const float da = 2.0f * PI / n;
vec2_t verts[n];
for (unsigned i = 0; i < n; ++i) {
const float r = r_mean + rng_plusminus() * R_JITTER;
const float a_jitter = A_JITTER * rng_plusminus() * da / 2;
const float a = i * da + a_jitter;
verts[i] = (vec2_t) { r * cosf(a), r * sinf(a) };
}
const unsigned id = entity_add();
scene_add(id, verts, n, true);
physics_add(id, pos, MAT2_ID, vel, rot, r_mean * r_mean);
collisions_add(id, r_mean, COLLIDE_PRIOR, physics_bounce);
entry_t *entry = entries + id;
entry->valid = true;
entry->component_id = entity_add_component(id, update, remove, entry);
entry->size = size;
++count;
}
static asteroid_size_t draw_size(const asteroid_size_dist_entry_t *dist)
{
const float r = rng_canon();
float acc = 0;
for (unsigned i = 0; dist[i].probability != 0.0f; ++i) {
acc += dist[i].probability;
if (r < acc)
return dist[i].size;
}
assert(false);
}
void asteroids_clear()
{
count = 0;
memset(entries, 0, sizeof(entries));
}
void asteroids_on_clear(asteroids_cb_t cb)
{
clear = cb;
}
void asteroids_add(const asteroid_size_dist_entry_t *dist)
{
vec2_t pos;
do {
pos.y = rng_plusminus();
pos.x = aspect * rng_plusminus();
} while (vec2_len(pos) < MIN_DIST);
const vec2_t vel = {
V_JITTER * rng_plusminus(),
V_JITTER * rng_plusminus(),
};
const float rot = ROT_JITTER * rng_plusminus();
const asteroid_size_t size = draw_size(dist);
create_asteroid(pos, vel, rot, size);
}
void asteroids_hit(unsigned shot, unsigned asteroid, physics_sep_t)
{
assert(entries[asteroid].valid);
entity_mark(asteroid);
const asteroid_size_t old_size = entries[asteroid].size;
if (old_size == ASTEROID_SMALL)
return;
const asteroid_size_t size = old_size - 1;
const float mass = radii[size] * radii[size];
const physics_t *phys_shot = physics_get(shot);
const physics_t *phys_ast = physics_get(asteroid);
const vec2_t p_shot = vec2_scale(phys_shot->vel, phys_shot->mass);
const vec2_t p_ast = vec2_scale(phys_ast->vel, phys_ast->mass);
const unsigned n = REPLACE_MIN + rng_uint32() % REPLACE_RANGE;
const float da = 2 * PI / n;
const vec2_t p = vec2_add(p_shot, p_ast);
const vec2_t inherit_v = vec2_scale(p, 1 / (n * mass));
for (unsigned i = 0; i < n; ++i) {
const float r = REPLACE_R_COEFF * radii[old_size]
+ REPLACE_R_JITTER * rng_plusminus();
const float a = i * da + REPLACE_A_JITTER * rng_plusminus();
const vec2_t disp = { r * cosf(a), r * sinf(a) };
const vec2_t radial_v = vec2_scale(
vec2_norm(disp), REPLACE_RADIAL_V_COEFF * rng_canon());
const vec2_t jitter_v = {
REPLACE_V_JITTER * rng_plusminus(),
REPLACE_V_JITTER * rng_plusminus(),
};
const vec2_t pos = vec2_add(phys_ast->pos, disp);
const vec2_t vel = vec2_add(inherit_v, vec2_add(radial_v, jitter_v));
const float rot
= phys_ast->rot / n + REPLACE_ROT_JITTER * rng_plusminus();
create_asteroid(pos, vel, rot, size);
}
}
bool asteroids_check(unsigned id)
{
return entries[id].valid;
}
-27
View File
@@ -1,27 +0,0 @@
#ifndef ASTEROIDS_H
#define ASTEROIDS_H
#include "physics.h"
typedef void (*asteroids_cb_t)();
typedef enum {
ASTEROID_SMALL,
ASTEROID_MEDIUM,
ASTEROID_LARGE,
ASTEROID_HUGE,
} asteroid_size_t;
typedef struct {
float probability;
asteroid_size_t size;
} asteroid_size_dist_entry_t;
void asteroids_clear();
void asteroids_on_clear(asteroids_cb_t cb);
void asteroids_add(const asteroid_size_dist_entry_t *dist);
void asteroids_hit(unsigned shot, unsigned asteroid, physics_sep_t sep);
bool asteroids_check(unsigned id);
#endif
+1 -3
View File
@@ -8,6 +8,4 @@ defs="-D_POSIX_C_SOURCE=200809L"
$cc $warn $flags $libs $defs \ $cc $warn $flags $libs $defs \
-o asteroids \ -o asteroids \
asteroids.c collisions.c entity.c fb.c game.c input.c \ fb.c game.c input.c main.c maths.c renderer.c rng.c text.c
main.c maths.c physics.c scene.c renderer.c rng.c \
self_destruct.c text.c
-87
View File
@@ -1,87 +0,0 @@
#include "collisions.h"
#include "entity.h"
#include <assert.h>
#include <string.h>
typedef struct {
bool valid;
unsigned component_id;
float r;
unsigned priority;
collision_cb_t cb;
} entry_t;
static unsigned max;
static entry_t entries[MAX_ENTITIES];
static void update(unsigned new_entity_id, void *ref)
{
entry_t *old_e = (entry_t *)ref;
entry_t *new_e = entries + new_entity_id;
memcpy(new_e, old_e, sizeof(entry_t));
old_e->valid = false;
entity_update_component(new_entity_id, new_e->component_id, new_e);
}
static void remove(void *ref)
{
entry_t *e = (entry_t *)ref;
e->valid = false;
}
void collisions_init()
{
max = 0;
}
void collisions_update()
{
for (unsigned i = 0; i < max; ++i) {
if (!entries[i].valid)
continue;
for (unsigned j = i + 1; j < max; ++j) {
if (!entries[j].valid)
continue;
const physics_sep_t sep = physics_separation(i, j);
if (sep.dist > entries[i].r + entries[j].r)
continue;
if (sep.va - sep.vb <= 0)
continue;
if (entries[i].priority >= entries[j].priority) {
entries[i].cb(i, j, sep);
} else {
const physics_sep_t flip_sep = {
.dist = sep.dist,
.norm = vec2_scale(sep.norm, -1),
.va = -sep.vb,
.vb = -sep.va,
};
entries[j].cb(j, i, flip_sep);
}
}
}
}
void collisions_add(
unsigned entity, float radius, unsigned priority, collision_cb_t cb)
{
assert(entity < MAX_ENTITIES);
if (entity >= max)
max = entity + 1;
else
assert(!entries[entity].valid);
entry_t *entry = entries + entity;
entry->valid = true;
entry->component_id
= entity_add_component(entity, update, remove, entry);
entry->r = radius;
entry->priority = priority;
entry->cb = cb;
}
-14
View File
@@ -1,14 +0,0 @@
#ifndef COLLISIONS_H
#define COLLISIONS_H
#include "physics.h"
typedef void (*collision_cb_t)(unsigned a, unsigned b, physics_sep_t sep);
void collisions_init();
void collisions_update();
void collisions_add(
unsigned entity, float radius, unsigned priority, collision_cb_t cb);
#endif
-88
View File
@@ -1,88 +0,0 @@
#include "entity.h"
#include <assert.h>
#include <string.h>
#define MAX_COMPONENTS_PER_ENTITY 4U
typedef struct {
update_cb_t update;
remove_cb_t remove;
void *ref;
} component_t;
typedef struct {
bool marked;
unsigned count;
component_t components[MAX_COMPONENTS_PER_ENTITY];
} entity_t;
static unsigned count;
static entity_t entities[MAX_ENTITIES];
void entities_clear()
{
count = 0;
}
void entities_purge()
{
for (unsigned id = count - 1; id < count; --id) {
entity_t *e = entities + id;
if (!e->marked)
continue;
for (unsigned i = 0; i < e->count; ++i)
e->components[i].remove(e->components[i].ref);
const unsigned last = count - 1;
if (id != last) {
memcpy(e, entities + last, sizeof(entity_t));
for (unsigned i = 0; i < e->count; ++i)
e->components[i].update(id, e->components[i].ref);
}
--count;
}
}
unsigned entity_add()
{
assert(count < MAX_ENTITIES);
const unsigned id = count++;
memset(entities + id, 0, sizeof(entity_t));
return id;
}
void entity_mark(unsigned id)
{
assert(id < count);
entities[id].marked = true;
}
unsigned entity_add_component(
unsigned id, update_cb_t update, remove_cb_t remove, void *ref)
{
assert(id < count);
entity_t *e = entities + id;
assert(e->count < MAX_COMPONENTS_PER_ENTITY);
const unsigned component_id = e->count++;
e->components[component_id] = (component_t) {
.update = update,
.remove = remove,
.ref = ref,
};
return component_id;
}
void entity_update_component(
unsigned entity_id, unsigned component_id, void *new_ref)
{
assert(entity_id < count);
entity_t *e = entities + entity_id;
assert(component_id < e->count);
e->components[component_id].ref = new_ref;
}
-20
View File
@@ -1,20 +0,0 @@
#ifndef ENTITY_H
#define ENTITY_H
#define MAX_ENTITIES 256U
typedef void (*update_cb_t)(unsigned new_entity_id, void *ref);
typedef void (*remove_cb_t)(void *ref);
void entities_clear();
void entities_purge();
unsigned entity_add();
void entity_mark(unsigned id);
unsigned entity_add_component(
unsigned id, update_cb_t update, remove_cb_t remove, void *ref);
void entity_update_component(
unsigned entity_id, unsigned component_id, void *new_ref);
#endif
+402 -248
View File
@@ -1,354 +1,508 @@
#include "game.h" #include "game.h"
#include "asteroids.h"
#include "collisions.h"
#include "entity.h"
#include "input.h" #include "input.h"
#include "physics.h"
#include "renderer.h" #include "renderer.h"
#include "rng.h" #include "rng.h"
#include "scene.h"
#include "self_destruct.h"
#include "text.h" #include "text.h"
#include <assert.h> #include <assert.h>
#include <linux/input-event-codes.h> #include <linux/input-event-codes.h>
#include <math.h> #include <math.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#define FPS 60 #define SHIP_COLLIDE_R 0.05
#define SHIP_MASS 0.5
#define INIT_LEVEL 1 #define FIRE_MEAN -0.15
#define FIRE_JITTER 0.01
#define SHIP_COLLIDE_R 0.025 #define ASTEROID_MIN_VERTS 5
#define SHIP_MASS 0.01 #define ASTEROID_MAX_VERTS 8
#define ASTEROID_VERT_RANGE (ASTEROID_MAX_VERTS - ASTEROID_MIN_VERTS)
#define ASTEROID_A_JITTER 0.8
#define ASTEROID_VEL_JITTER 0.001
#define ASTEROID_OMG_JITTER 0.005
#define INIT_ASTEROIDS 1
#define FIRE_JITTER 0.005 #define ASTEROID_SMALL 0.05f
#define ASTEROID_MEDIUM 0.1f
#define ASTEROID_LARGE 0.2f
#define ASTEROID_HUGE 0.4f
#define ASTEROID_R_JITTER 0.02
#define ASTEROID_MIN_DIST 0.8
#define LIN_PWR 0.00005 #define REPLACE_MIN 3
#define REPLACE_MAX 5
#define REPLACE_RANGE (REPLACE_MAX - REPLACE_MIN)
#define REPLACE_R_COEFF 0.8
#define REPLACE_R_JITTER 0.02
#define REPLACE_A_JITTER 0.4
#define REPLACE_V_JITTER 0.0005
#define REPLACE_RADIAL_V_COEFF 0.005
#define REPLACE_OMG_JITTER 0.02
#define LIN_PWR 0.0001
#define ROT_PWR 0.002 #define ROT_PWR 0.002
#define SHOT_VEL 0.03 #define SHOT_VEL 0.04
#define SHOT_COLLIDE_R 0.015 #define SHOT_COLLIDE_R 0.005
#define SHOT_MASS 0.00025 #define SHOT_MASS 0.01
#define SHOT_LIFETIME_S 4
#define MAX_SHAPES 256U
#define MAX_ENTITIES 128U
#define MAX_SHAPES_PER_ENTITY 2
#define MAX_COLLISIONS 128U
#define ARROW_SCALE 0.0125
#define ARROW_WIDTH 4
#define ARROW_HEIGHT 4
#define COUNTER_MASK (1 << 6) #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])) #define NELEMS(arr) (sizeof(arr) / sizeof(arr[0]))
enum { typedef enum {
SHOT_COLLIDE_PRIOR = 1, COLLISION_SHIP,
SHIP_COLLIDE_PRIOR, COLLISION_SHOT,
ITEM_COLLIDE_PRIOR, COLLISION_ASTEROID,
}; } collision_tag_t;
typedef struct {
vec2_t pos;
vec2_t vel;
mat2_t dir;
float omg;
unsigned shapes[MAX_SHAPES_PER_ENTITY];
unsigned shape_count;
float radius;
collision_tag_t tag;
bool dead;
} entity_t;
typedef struct {
bool visible;
int entity;
unsigned vert_count;
vec2_t verts[MAX_VERTS];
bool connect;
} shape_t;
typedef struct {
unsigned entities[2];
float vs[2];
vec2_t normal;
} collision_t;
static const vec2_t ship_verts[] = { static const vec2_t ship_verts[] = {
{ 0.0, 0.055 }, { 0.0, 0.11 },
{ 0.025, -0.035 }, { 0.05, -0.07 },
{ 0.0, -0.02 }, { 0.0, -0.04 },
{ -0.025, -0.035 }, { -0.05, -0.07 },
}; };
static const vec2_t fire_verts[] = { static const vec2_t fire_verts[] = {
{ 0.0, -0.075 }, { 0.0, -0.15 },
{ 0.0075, -0.035 }, { 0.015, -0.07 },
{ -0.0075, -0.035 }, { -0.015, -0.07 },
}; };
static const vec2_t shot_verts[] = { { 0.0, -0.01 }, { 0.0, 0.01 } }; static const vec2_t shot_verts[] = { { 0.0, -0.02 }, { 0.0, 0.02 } };
static const vec2_t arrow_verts[][MAX_VERTS] = { static entity_t entities[MAX_ENTITIES];
{ { 1, 2 }, { 2, 0 }, { 1, -2 } }, static mat3_t transforms[MAX_ENTITIES];
{ { -2, 0 }, { 2, 0 } }, static shape_t shapes[MAX_SHAPES];
};
static const unsigned arrow_counts[NELEMS(arrow_verts)] = { 3, 2 };
static const vec2_t item_verts[][MAX_VERTS] = { static unsigned entity_count;
{ { -0.0125, -0.0125 }, { 0.0125, 0.0125 } }, static unsigned shape_count;
{ { -0.0125, 0.0125 }, { 0.0125, -0.0125 } }, static unsigned asteroid_count;
};
static const unsigned item_counts[NELEMS(item_verts)] = { 2, 2 };
static const vec2_t shot_vel = { 0, SHOT_VEL }; static bool dead;
static float aspect;
static const asteroid_size_dist_entry_t easy_dist[] = { static uint8_t counter;
{ 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 ship_entity_id;
static unsigned fire_scene_id; static unsigned ship_shape_id;
static unsigned fire_shape_id;
static unsigned level; static void restart()
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; game_init(aspect);
counter = 0;
renderer_set_wrap(false);
physics_escape(ship_entity_id);
} }
static void die() static entity_t *add_entity(unsigned *id_out)
{ {
dead = true; const unsigned id = entity_count++;
clear = false; memset(entities + id, 0, sizeof(entity_t));
counter = 0; entities[id].dir = (mat2_t) { { 1, 0 }, { 0, 1 } };
if (id_out != nullptr)
*id_out = id;
return entities + id;
} }
static void upgrade_armour() static shape_t *add_shape(unsigned entity, unsigned *id_out)
{ {
if (armour_level >= 9) assert(entities[entity].shape_count < MAX_SHAPES_PER_ENTITY);
return; const int id = shape_count++;
memset(shapes + id, 0, sizeof(shape_t));
static char armour_msg[] = "ARMOUR X"; shapes[id].entity = entity;
armour_msg[7] = '0' + ++armour_level; entities[entity].shapes[entities[entity].shape_count++] = id;
msg = armour_msg; if (id_out != nullptr)
counter = 0; *id_out = id;
} return shapes + id;
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() static void shoot()
{ {
if (dead || paused) if (entity_count >= MAX_ENTITIES || shape_count >= MAX_SHAPES)
return; return;
physics_t *ship = physics_get(ship_entity_id); entity_t *ship = entities + ship_entity_id;
const shape_t *ship_shape = shapes + ship_shape_id;
const vec2_t pos unsigned id;
= vec2_add(ship->pos, mat2_mul_vec2(ship->dir, ship_verts[0])); entity_t *e = add_entity(&id);
const vec2_t vel e->dir = ship->dir;
= vec2_add(ship->vel, mat2_mul_vec2(ship->dir, shot_vel)); e->pos = vec2_add(
ship->pos, mat2_mul_vec2(ship->dir, ship_shape->verts[0]));
e->vel = vec2_add(
ship->vel, mat2_mul_vec2(ship->dir, (vec2_t) { 0, SHOT_VEL }));
e->radius = SHIP_COLLIDE_R;
e->tag = COLLISION_SHOT;
const unsigned id = entity_add(); shape_t *s = add_shape(id, nullptr);
scene_add(id, shot_verts, NELEMS(shot_verts), false); s->visible = true;
physics_add(id, pos, ship->dir, vel, 0, SHOT_MASS); s->vert_count = NELEMS(shot_verts);
collisions_add(id, SHOT_COLLIDE_R, SHOT_COLLIDE_PRIOR, shot_collide); memcpy(s->verts, shot_verts, sizeof(shot_verts));
self_destruct_add(id, FPS * SHOT_LIFETIME_S);
const vec2_t p_ship = vec2_scale(ship->vel, ship->mass); const vec2_t ship_p = vec2_scale(ship->vel, SHIP_MASS);
const vec2_t p_shot = vec2_scale(vel, SHOT_MASS); const vec2_t shot_p = vec2_scale(e->vel, SHOT_MASS);
ship->vel = vec2_scale(vec2_sub(p_ship, p_shot), 1 / SHIP_MASS); const vec2_t new_ship_p = vec2_sub(ship_p, shot_p);
ship->vel = vec2_scale(new_ship_p, 1 / SHIP_MASS);
} }
static void draw_arrow() static entity_t *gen_asteroid(float r_mean, unsigned *id_out)
{ {
const float tx = aspect - ARROW_SCALE * (ARROW_WIDTH / 2.0 + 2); unsigned id;
const mat3_t m = { entity_t *e = add_entity(&id);
{ ARROW_SCALE, 0, 0 }, e->radius = r_mean;
{ 0, ARROW_SCALE, 0 }, e->tag = COLLISION_ASTEROID;
{ tx, 0, 1 },
shape_t *s = add_shape(id, nullptr);
s->visible = true;
s->connect = true;
const unsigned n
= ASTEROID_MIN_VERTS + rng_uint32() % ASTEROID_VERT_RANGE;
s->vert_count = n;
const float da = 2.0f * PI / n;
for (unsigned i = 0; i < n; ++i) {
const float r = r_mean + rng_plusminus() * ASTEROID_R_JITTER;
const float a
= i * da + ASTEROID_A_JITTER * rng_plusminus() * da / 2;
s->verts[i] = (vec2_t) { r * cosf(a), r * sinf(a) };
}
if (id_out)
*id_out = id;
return e;
}
static bool intersecting(unsigned a, unsigned b)
{
const float sep = vec2_len(vec2_sub(entities[b].pos, entities[a].pos));
return sep <= entities[a].radius + entities[b].radius;
}
static bool intersecting_any(unsigned id)
{
for (unsigned i = 0; i < entity_count; ++i) {
if (i == id)
continue;
if (intersecting(id, i))
return true;
}
return false;
}
static void spawn_asteroid()
{
float r;
const float rnd = rng_canon();
if (rnd < 0.4)
r = ASTEROID_MEDIUM;
else if (rnd < 0.8)
r = ASTEROID_LARGE;
else
r = ASTEROID_HUGE;
unsigned id;
entity_t *e = gen_asteroid(r, &id);
do {
e->pos.y = rng_plusminus();
e->pos.x = aspect * rng_plusminus();
} while (vec2_len(e->pos) < ASTEROID_MIN_DIST || intersecting_any(id));
e->vel = (vec2_t) {
ASTEROID_VEL_JITTER * rng_plusminus(),
ASTEROID_VEL_JITTER * rng_plusminus(),
}; };
e->omg = ASTEROID_OMG_JITTER * rng_plusminus();
for (unsigned i = 0; i < NELEMS(arrow_verts); ++i) ++asteroid_count;
renderer_draw(arrow_verts[i], arrow_counts[i], m, false);
} }
static void anim_fire(vec2_t *verts) static void remove_shape(unsigned id)
{ {
verts[0].y = fire_verts[0].y + FIRE_JITTER * rng_plusminus(); if (id < shape_count - 1) {
const shape_t *last = shapes + shape_count - 1;
memcpy(shapes + id, last, sizeof(shape_t));
for (unsigned i = 0; i < entities[last->entity].shape_count; ++i) {
if (entities[last->entity].shapes[i] == shape_count - 1) {
entities[last->entity].shapes[i] = id;
break;
}
}
}
--shape_count;
} }
static void create_field() static void remove_entity(unsigned id)
{ {
item = false; assert(id != ship_entity_id);
dead = false;
clear = false;
paused = false;
asteroid_count = 0;
msg = nullptr;
renderer_set_wrap(true); entity_t *e = entities + id;
for (unsigned i = 0; i < e->shape_count; ++i)
remove_shape(e->shapes[i]);
entities_clear(); if (id < entity_count - 1) {
scene_clear(); const entity_t *last = entities + entity_count - 1;
physics_init(); memcpy(e, last, sizeof(entity_t));
collisions_init(); for (unsigned i = 0; i < e->shape_count; ++i)
self_destruct_clear(); shapes[e->shapes[i]].entity = id;
}
ship_entity_id = entity_add(); --entity_count;
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() static void ship_update()
{ {
physics_t *ship = physics_get(ship_entity_id); entity_t *ship = entities + ship_entity_id;
ship->rot += ROT_PWR * (float)input.spin; ship->omg += ROT_PWR * (float)input.spin;
if (input.thrust != 0) {
const vec2_t thrust = { 0, (float)input.thrust * LIN_PWR }; const vec2_t thrust = { 0, (float)input.thrust * LIN_PWR };
const vec2_t acc = mat2_mul_vec2(ship->dir, thrust); const vec2_t acc = mat2_mul_vec2(ship->dir, thrust);
ship->vel = vec2_add(ship->vel, acc); ship->vel = vec2_add(ship->vel, acc);
scene_show(fire_scene_id);
} else { shape_t *fire = shapes + fire_shape_id;
scene_hide(fire_scene_id); fire->visible = input.thrust != 0;
fire->verts[0].y = FIRE_MEAN + FIRE_JITTER * rng_plusminus();
}
static unsigned check_collisions(collision_t *out)
{
unsigned count = 0;
for (unsigned i = 0; i < entity_count; ++i) {
for (unsigned j = i + 1; j < entity_count; ++j) {
if (intersecting(i, j)) {
const vec2_t n
= vec2_norm(vec2_sub(entities[j].pos, entities[i].pos));
const float vi = vec2_dot(entities[i].vel, n);
const float vj = vec2_dot(entities[j].vel, n);
if (vi < 0 && vj > 0)
continue;
assert(count < MAX_COLLISIONS);
out[count].entities[0] = i;
out[count].entities[1] = j;
out[count].vs[0] = vi;
out[count].vs[1] = vj;
out[count].normal = n;
++count;
}
}
}
return count;
}
static void bounce(collision_t c)
{
entity_t *a = entities + c.entities[0];
entity_t *b = entities + c.entities[1];
const float ma = a->radius * a->radius;
const float mb = b->radius * b->radius;
const float m = ma + mb;
const vec2_t n = c.normal;
const float va1 = c.vs[0];
const float vb1 = c.vs[1];
const float va2 = (va1 * (ma - mb) + 2 * mb * vb1) / m;
const float vb2 = (vb1 * (mb - ma) + 2 * ma * va1) / m;
a->vel = vec2_add(a->vel, vec2_scale(n, va2 - va1));
b->vel = vec2_add(b->vel, vec2_scale(n, vb2 - vb1));
}
static void destroy_asteroid(entity_t *a, vec2_t shot_vel)
{
a->dead = true;
--asteroid_count;
float r;
if (a->radius == ASTEROID_HUGE)
r = ASTEROID_LARGE;
else if (a->radius == ASTEROID_LARGE)
r = ASTEROID_MEDIUM;
else if (a->radius == ASTEROID_MEDIUM)
r = ASTEROID_SMALL;
else
return;
const unsigned n = REPLACE_MIN + rng_uint32() % REPLACE_RANGE;
const float da = 2 * PI / n;
const float m = a->radius * a->radius;
const vec2_t p
= vec2_add(vec2_scale(a->vel, m), vec2_scale(shot_vel, SHOT_MASS));
const vec2_t inherit_v = vec2_scale(p, 1 / n * (m + SHOT_MASS));
for (unsigned i = 0; i < n; ++i) {
const float disp_r = REPLACE_R_COEFF * a->radius
+ REPLACE_R_JITTER * rng_plusminus();
const float disp_a = i * da + REPLACE_A_JITTER * rng_plusminus();
const vec2_t disp = { disp_r * cosf(disp_a), disp_r * sinf(disp_a) };
const vec2_t radial_v = vec2_scale(
vec2_norm(disp), REPLACE_RADIAL_V_COEFF * rng_canon());
const vec2_t jitter_v = {
REPLACE_V_JITTER * rng_plusminus(),
REPLACE_V_JITTER * rng_plusminus(),
};
entity_t *e = gen_asteroid(r, nullptr);
e->pos = vec2_add(a->pos, disp);
e->vel = vec2_add(inherit_v, vec2_add(radial_v, jitter_v));
e->omg = a->omg / n + REPLACE_OMG_JITTER * rng_plusminus();
++asteroid_count;
} }
if (clear && ship->pos.x > aspect) printf("asteroid count: %u\n", asteroid_count);
win();
} }
static void add_item() static void handle_collisions(const collision_t *collisions, unsigned count)
{ {
const unsigned id = entity_add(); for (unsigned i = 0; i < count; ++i) {
const collision_t c = collisions[i];
entity_t *a = entities + c.entities[0];
entity_t *b = entities + c.entities[1];
for (unsigned i = 0; i < NELEMS(item_verts); ++i) if (a->tag == COLLISION_SHIP || b->tag == COLLISION_SHIP) {
scene_add(id, item_verts[i], item_counts[i], false); dead = true;
continue;
}
const vec2_t pos = { if (a->tag == COLLISION_SHOT && b->tag == COLLISION_SHOT)
.x = ITEM_SPAWN_R * rng_plusminus(), continue;
.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); if (a->tag == COLLISION_ASTEROID && b->tag == COLLISION_ASTEROID) {
bounce(c);
continue;
}
item = true; if (a->tag == COLLISION_SHOT) {
a->dead = true;
destroy_asteroid(b, a->vel);
}
if (b->tag == COLLISION_SHOT) {
b->dead = true;
destroy_asteroid(a, b->vel);
}
}
for (unsigned i = 0; i < entity_count; ++i) {
if (entities[i].dead)
remove_entity(i--);
}
} }
void game_init() void game_init(float _aspect)
{ {
input_on_shoot(shoot); input_on_shoot(shoot);
input_on_restart(reset); input_on_restart(restart);
input_on_pause(pause);
asteroids_on_clear(cleared);
reset(); aspect = _aspect;
dead = false;
counter = 0;
entity_count = shape_count = 0;
asteroid_count = 0;
entity_t *ship = add_entity(&ship_entity_id);
ship->radius = SHIP_COLLIDE_R;
ship->tag = COLLISION_SHIP;
shape_t *ship_shape = add_shape(ship_entity_id, &ship_shape_id);
ship_shape->visible = true;
ship_shape->connect = true;
ship_shape->vert_count = NELEMS(ship_verts);
memcpy(ship_shape->verts, ship_verts, sizeof(ship_verts));
shape_t *fire = add_shape(ship_entity_id, &fire_shape_id);
fire->visible = true;
fire->connect = true;
fire->vert_count = NELEMS(fire_verts);
memcpy(fire->verts, fire_verts, sizeof(fire_verts));
for (unsigned i = 0; i < INIT_ASTEROIDS; ++i)
spawn_asteroid();
} }
void game_update() void game_update()
{ {
if (msg != nullptr || dead || clear || paused) if (dead) {
++counter; ++counter;
if (counter > COUNTER_MASK)
msg = nullptr;
if (dead || paused)
return; return;
}
ship_update(); ship_update();
physics_update();
self_destruct_update();
collisions_update();
scene_update();
entities_purge(); for (unsigned i = 0; i < entity_count; ++i) {
entity_t *e = entities + i;
const bool items_enabled e->dir = mat2_mul_mat2(mat2_rotation(e->omg), e->dir);
= !clear && score >= ITEM_MIN_SCORE && armour_level < 9; e->pos = vec2_add(e->pos, e->vel);
if (items_enabled && !item
&& rng_canon() < 1.0f / (FPS * ITEM_SPAWN_EXP_S)) if (e->pos.y > 1)
add_item(); e->pos.y -= 2;
else if (e->pos.y <= -1)
e->pos.y += 2;
if (e->pos.x >= aspect)
e->pos.x -= 2 * aspect;
else if (e->pos.x < -aspect)
e->pos.x += 2 * aspect;
transforms[i]
= mat3_mul_mat3(mat3_translation(e->pos), mat2_extend(e->dir));
}
static collision_t collisions[MAX_COLLISIONS];
const unsigned ncols = check_collisions(collisions);
handle_collisions(collisions, ncols);
} }
void game_draw() void game_draw()
{ {
renderer_clear(); renderer_clear();
scene_draw(); for (unsigned i = 0; i < shape_count; ++i) {
if (!shapes[i].visible)
continue;
text_draw_score(score); const mat3_t transform = transforms[shapes[i].entity];
if (clear) renderer_draw(
draw_arrow(); shapes[i].verts, shapes[i].vert_count, transform,
const bool display_text = !(counter & COUNTER_MASK); shapes[i].connect);
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);
} }
if (asteroid_count == 0)
text_draw("CLEAR");
if (dead && !(counter & COUNTER_MASK))
text_draw("GAME OVER");
} }
+1 -1
View File
@@ -1,7 +1,7 @@
#ifndef GAME_H #ifndef GAME_H
#define GAME_H #define GAME_H
void game_init(); void game_init(float aspect);
void game_update(); void game_update();
void game_draw(); void game_draw();
+1 -10
View File
@@ -19,7 +19,6 @@ static int input_fd;
static input_callback_t restart; static input_callback_t restart;
static input_callback_t quit; static input_callback_t quit;
static input_callback_t shoot; static input_callback_t shoot;
static input_callback_t pause_;
static void key_press(int key) static void key_press(int key)
{ {
@@ -47,9 +46,6 @@ static void key_press(int key)
if (shoot != nullptr) if (shoot != nullptr)
shoot(); shoot();
break; break;
case KEY_P:
if (pause_ != nullptr)
pause_();
} }
} }
@@ -78,7 +74,7 @@ static void key_repeat(int key)
int input_init() int input_init()
{ {
memset(&input, 0, sizeof(input)); memset(&input, 0, sizeof(input));
shoot = restart = quit = pause_ = nullptr; shoot = nullptr;
input_fd = open("/dev/input/event0", O_RDONLY); input_fd = open("/dev/input/event0", O_RDONLY);
assert(input_fd != -1); assert(input_fd != -1);
@@ -109,11 +105,6 @@ void input_on_quit(input_callback_t cb)
quit = cb; quit = cb;
} }
void input_on_pause(input_callback_t cb)
{
pause_ = cb;
}
void input_handle() void input_handle()
{ {
struct input_event ev; struct input_event ev;
-1
View File
@@ -16,7 +16,6 @@ void input_cleanup();
void input_on_shoot(input_callback_t cb); void input_on_shoot(input_callback_t cb);
void input_on_restart(input_callback_t cb); void input_on_restart(input_callback_t cb);
void input_on_quit(input_callback_t cb); void input_on_quit(input_callback_t cb);
void input_on_pause(input_callback_t cb);
void input_handle(); void input_handle();
+3 -2
View File
@@ -18,15 +18,16 @@ static void quit()
int main() int main()
{ {
const int input_fd = input_init(); const int input_fd = input_init();
const int drm_fd = renderer_init(); const renderer_params_t renderer_params = renderer_init();
rng_init(); rng_init();
game_init(); game_init(renderer_params.aspect);
input_on_quit(quit); input_on_quit(quit);
renderer_clear(); renderer_clear();
renderer_swap(); renderer_swap();
const int drm_fd = renderer_params.drm_fd;
const int max_fd = MAX(input_fd, drm_fd); const int max_fd = MAX(input_fd, drm_fd);
fd_set set; fd_set set;
-5
View File
@@ -41,11 +41,6 @@ float vec2_dot(vec2_t v1, vec2_t v2)
return v1.x * v2.x + v1.y * v2.y; return v1.x * v2.x + v1.y * v2.y;
} }
vec2_t vec3_reduce(vec3_t v)
{
return (vec2_t) { v.x / v.z, v.y / v.z };
}
mat2_t mat2_rotation(float theta) mat2_t mat2_rotation(float theta)
{ {
return (mat2_t) { return (mat2_t) {
-5
View File
@@ -3,9 +3,6 @@
#define PI 3.14159265358979323846264 #define PI 3.14159265358979323846264
#define MAT2_ID ((mat2_t) { { 1, 0 }, { 0, 1 } })
#define MAT3_ID ((mat3_t) { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } })
typedef struct { typedef struct {
float x, y; float x, y;
} vec2_t; } vec2_t;
@@ -30,8 +27,6 @@ vec3_t vec2_extend(vec2_t v);
vec2_t vec2_norm(vec2_t v); vec2_t vec2_norm(vec2_t v);
float vec2_dot(vec2_t v1, vec2_t v2); float vec2_dot(vec2_t v1, vec2_t v2);
vec2_t vec3_reduce(vec3_t v);
mat2_t mat2_rotation(float theta); mat2_t mat2_rotation(float theta);
vec2_t mat2_mul_vec2(mat2_t m, vec2_t v); vec2_t mat2_mul_vec2(mat2_t m, vec2_t v);
mat2_t mat2_mul_mat2(mat2_t m1, mat2_t m2); mat2_t mat2_mul_mat2(mat2_t m1, mat2_t m2);
-152
View File
@@ -1,152 +0,0 @@
#include "physics.h"
#include "entity.h"
#include "renderer.h"
#include <assert.h>
#include <math.h>
#include <string.h>
typedef struct {
bool valid;
unsigned component_id;
physics_t state;
} entry_t;
static unsigned max;
static unsigned escape_entity;
static entry_t entries[MAX_ENTITIES];
static void update(unsigned new_entity_id, void *ref)
{
entry_t *old_e = (entry_t *)ref;
entry_t *new_e = entries + new_entity_id;
memcpy(new_e, old_e, sizeof(entry_t));
old_e->valid = false;
entity_update_component(new_entity_id, new_e->component_id, new_e);
}
static void remove(void *ref)
{
entry_t *e = (entry_t *)ref;
e->valid = false;
}
void physics_init()
{
max = 0;
escape_entity = MAX_ENTITIES;
}
void physics_update()
{
for (unsigned i = 0; i < max; ++i) {
if (!entries[i].valid)
continue;
physics_t *st = &entries[i].state;
st->dir = mat2_mul_mat2(mat2_rotation(st->rot), st->dir);
st->pos = vec2_add(st->pos, st->vel);
if (st->pos.y > 1)
st->pos.y -= 2;
else if (st->pos.y <= -1)
st->pos.y += 2;
if (st->pos.x >= aspect && i != escape_entity)
st->pos.x -= 2 * aspect;
else if (st->pos.x < -aspect)
st->pos.x += 2 * aspect;
}
}
void physics_add(
unsigned entity, vec2_t pos, mat2_t dir, vec2_t vel, float rot,
float mass)
{
assert(entity < MAX_ENTITIES);
if (entity >= max)
max = entity + 1;
else
assert(!entries[entity].valid);
entry_t *entry = entries + entity;
entry->valid = true;
entry->component_id
= entity_add_component(entity, update, remove, entry);
entry->state = (physics_t) {
.pos = pos,
.dir = dir,
.vel = vel,
.rot = rot,
.mass = mass,
};
}
physics_t *physics_get(unsigned entity)
{
assert(entity < max);
assert(entries[entity].valid);
return &entries[entity].state;
}
void physics_escape(unsigned entity)
{
assert(entity < max);
assert(entries[entity].valid);
escape_entity = entity;
}
static void pos_views(vec2_t a, vec2_t b, vec2_t out[4])
{
const float dx = 2 * aspect;
const float dy = 2;
out[0] = b;
out[1] = (vec2_t) { b.x + copysign(dx, a.x), b.y };
out[2] = (vec2_t) { b.x, b.y + copysign(dy, a.y) };
out[3] = (vec2_t) { b.x + copysign(dx, a.x), b.y + copysign(dy, a.y) };
}
physics_sep_t physics_separation(unsigned a, unsigned b)
{
const physics_t *pa = physics_get(a);
const physics_t *pb = physics_get(b);
vec2_t pos[4];
pos_views(pa->pos, pb->pos, pos);
float min_dist = INFINITY;
vec2_t min_disp;
for (unsigned i = 0; i < 4; ++i) {
const vec2_t disp = vec2_sub(pos[i], pa->pos);
const float dist = vec2_len(disp);
if (dist < min_dist) {
min_disp = disp;
min_dist = dist;
}
}
const vec2_t norm = vec2_norm(min_disp);
return (physics_sep_t) {
.dist = min_dist,
.norm = norm,
.va = vec2_dot(pa->vel, norm),
.vb = vec2_dot(pb->vel, norm),
};
}
void physics_bounce(unsigned a, unsigned b, physics_sep_t sep)
{
physics_t *pa = physics_get(a);
physics_t *pb = physics_get(b);
const float ma = pa->mass;
const float mb = pb->mass;
const float ms = ma + mb;
const float va = (sep.va * (ma - mb) + 2 * mb * sep.vb) / ms;
const float vb = (sep.vb * (mb - ma) + 2 * ma * sep.va) / ms;
pa->vel = vec2_add(pa->vel, vec2_scale(sep.norm, va - sep.va));
pb->vel = vec2_add(pb->vel, vec2_scale(sep.norm, vb - sep.vb));
}
-31
View File
@@ -1,31 +0,0 @@
#ifndef PHYSICS_H
#define PHYSICS_H
#include "maths.h"
typedef struct {
vec2_t pos;
mat2_t dir;
vec2_t vel;
float rot;
float mass;
} physics_t;
typedef struct {
float dist, va, vb;
vec2_t norm;
} physics_sep_t;
void physics_init();
void physics_update();
void physics_add(
unsigned entity, vec2_t pos, mat2_t dir, vec2_t vel, float rot,
float mass);
physics_t *physics_get(unsigned entity);
void physics_escape(unsigned entity);
physics_sep_t physics_separation(unsigned a, unsigned b);
void physics_bounce(unsigned a, unsigned b, physics_sep_t sep);
#endif
+8 -27
View File
@@ -9,8 +9,6 @@
#include <unistd.h> #include <unistd.h>
#include <xf86drm.h> #include <xf86drm.h>
float aspect;
static int drm_fd; static int drm_fd;
static drmModeConnector *conn; static drmModeConnector *conn;
static drmModeCrtc *crtc; static drmModeCrtc *crtc;
@@ -22,8 +20,6 @@ static int front, back;
static uint32_t width, height; static uint32_t width, height;
static mat3_t view; static mat3_t view;
static bool wrap;
static vec3_t vert_buf[MAX_VERTS]; static vec3_t vert_buf[MAX_VERTS];
static void page_flip_handler(int, unsigned, unsigned, unsigned, void *) static void page_flip_handler(int, unsigned, unsigned, unsigned, void *)
@@ -38,13 +34,6 @@ static void set_pixel(uint32_t x, uint32_t y)
static void draw_line(vec3_t v1, vec3_t v2) static void draw_line(vec3_t v1, vec3_t v2)
{ {
v1.x /= v1.z;
v1.y /= v1.z;
v1.z = 1;
v2.x /= v2.z;
v2.y /= v2.z;
v2.z = 1;
const float delta_x = v2.x - v1.x; const float delta_x = v2.x - v1.x;
const float delta_y = v2.y - v1.y; const float delta_y = v2.y - v1.y;
const float step = fmaxf(fabsf(delta_x), fabsf(delta_y)); const float step = fmaxf(fabsf(delta_x), fabsf(delta_y));
@@ -54,20 +43,16 @@ static void draw_line(vec3_t v1, vec3_t v2)
for (unsigned i = 0; i <= step; ++i) { for (unsigned i = 0; i <= step; ++i) {
float x = roundf(v1.x + i * step_x); float x = roundf(v1.x + i * step_x);
float y = roundf(v1.y + i * step_y);
if (wrap) {
if (x >= width) if (x >= width)
x -= width; x -= width;
else if (x < 0) else if (x < 0)
x += width; x += width;
float y = roundf(v1.y + i * step_y);
if (y >= height) if (y >= height)
y -= height; y -= height;
else if (y < 0) else if (y < 0)
y += height; y += height;
} else if (x >= width || x < 0 || y >= height || y < 0) {
continue;
}
set_pixel(x, y); set_pixel(x, y);
} }
@@ -76,7 +61,7 @@ static void draw_line(vec3_t v1, vec3_t v2)
} }
} }
int renderer_init() renderer_params_t renderer_init()
{ {
drm_fd = open("/dev/dri/card1", O_RDWR | O_CLOEXEC); drm_fd = open("/dev/dri/card1", O_RDWR | O_CLOEXEC);
assert(drm_fd != -1); assert(drm_fd != -1);
@@ -104,9 +89,7 @@ int renderer_init()
width = mode.hdisplay; width = mode.hdisplay;
height = mode.vdisplay; height = mode.vdisplay;
wrap = true; const float aspect = (float)width / (float)height;
aspect = (float)width / (float)height;
const float scale = (float)height / 2.0f; const float scale = (float)height / 2.0f;
view = (mat3_t) { view = (mat3_t) {
{ scale, 0, 0 }, { scale, 0, 0 },
@@ -114,7 +97,10 @@ int renderer_init()
{ aspect * scale, scale, 1 }, { aspect * scale, scale, 1 },
}; };
return drm_fd; return (renderer_params_t) {
.drm_fd = drm_fd,
.aspect = aspect,
};
} }
void renderer_cleanup() void renderer_cleanup()
@@ -150,11 +136,6 @@ void renderer_swap()
back = (back + 1) & 1; back = (back + 1) & 1;
} }
void renderer_set_wrap(bool enable)
{
wrap = enable;
}
void renderer_clear() void renderer_clear()
{ {
memset(fbs[back].buf, 0, fbs[back].size); memset(fbs[back].buf, 0, fbs[back].size);
+6 -3
View File
@@ -7,17 +7,20 @@
#define MAX_VERTS 8U #define MAX_VERTS 8U
extern float aspect; typedef struct {
int drm_fd;
float aspect;
} renderer_params_t;
int renderer_init(); renderer_params_t renderer_init();
void renderer_cleanup(); void renderer_cleanup();
void renderer_handle(); void renderer_handle();
void renderer_swap(); void renderer_swap();
void renderer_set_wrap(bool enable);
void renderer_clear(); void renderer_clear();
void renderer_clear_rect(vec2_t centre, vec2_t size); void renderer_clear_rect(vec2_t centre, vec2_t size);
void renderer_draw( void renderer_draw(
const vec2_t *vs, unsigned count, mat3_t model, bool connect); const vec2_t *vs, unsigned count, mat3_t model, bool connect);
-105
View File
@@ -1,105 +0,0 @@
#include "scene.h"
#include "entity.h"
#include "physics.h"
#include "renderer.h"
#include <assert.h>
#include <string.h>
#define MAX_SHAPES 256U
typedef struct {
unsigned entity_id;
unsigned component_id;
unsigned vert_count;
vec2_t verts[MAX_VERTS];
scene_anim_cb_t anim_cb;
bool hidden;
bool connect;
} shape_t;
static unsigned count;
static shape_t shapes[MAX_SHAPES];
static void update(unsigned new_entity_id, void *ref)
{
shape_t *s = (shape_t *)ref;
s->entity_id = new_entity_id;
}
static void remove(void *ref)
{
shape_t *s = (shape_t *)ref;
const shape_t *last = shapes + (count - 1);
if (s < last) {
memcpy(s, last, sizeof(shape_t));
entity_update_component(s->entity_id, s->component_id, s);
}
--count;
}
static mat3_t get_transform(unsigned entity_id)
{
const physics_t *phys = physics_get(entity_id);
return mat3_mul_mat3(
mat3_translation(phys->pos), mat2_extend(phys->dir));
}
void scene_clear()
{
count = 0;
}
void scene_update()
{
for (unsigned i = 0; i < count; ++i) {
if (shapes[i].anim_cb != nullptr)
shapes[i].anim_cb(shapes[i].verts);
}
}
void scene_draw()
{
for (unsigned i = 0; i < count; ++i) {
const shape_t *s = shapes + i;
if (!s->hidden) {
const mat3_t m = get_transform(s->entity_id);
renderer_draw(s->verts, s->vert_count, m, s->connect);
}
}
}
unsigned scene_add(
unsigned entity, const vec2_t *verts, unsigned vert_count, bool connect)
{
assert(count < MAX_SHAPES);
const unsigned id = count++;
shape_t *s = shapes + id;
*s = (shape_t) {
.entity_id = entity,
.vert_count = vert_count,
.connect = connect,
};
memcpy(s->verts, verts, vert_count * sizeof(vec2_t));
s->component_id = entity_add_component(entity, update, remove, s);
return id;
}
void scene_hide(unsigned id)
{
assert(id < count);
shapes[id].hidden = true;
}
void scene_show(unsigned id)
{
assert(id < count);
shapes[id].hidden = false;
}
void scene_animate(unsigned id, scene_anim_cb_t cb)
{
assert(id < count);
shapes[id].anim_cb = cb;
}
-18
View File
@@ -1,18 +0,0 @@
#ifndef SCENE_H
#define SCENE_H
#include "maths.h"
typedef void (*scene_anim_cb_t)(vec2_t *verts);
void scene_clear();
void scene_update();
void scene_draw();
unsigned scene_add(
unsigned entity, const vec2_t *verts, unsigned vert_count, bool connect);
void scene_hide(unsigned id);
void scene_show(unsigned id);
void scene_animate(unsigned id, scene_anim_cb_t cb);
#endif
-60
View File
@@ -1,60 +0,0 @@
#include "self_destruct.h"
#include "entity.h"
#include <assert.h>
#include <string.h>
#define MAX 256U
typedef struct {
unsigned entity_id;
unsigned component_id;
unsigned rem;
} cmp_t;
static unsigned count;
static cmp_t cmps[MAX];
static void update(unsigned new_entity_id, void *ref)
{
cmp_t *c = (cmp_t *)ref;
c->entity_id = new_entity_id;
}
static void remove(void *ref)
{
cmp_t *c = (cmp_t *)ref;
const cmp_t *last = cmps + (count - 1);
if (c < last) {
memcpy(c, last, sizeof(cmp_t));
entity_update_component(c->entity_id, c->component_id, c);
}
--count;
}
void self_destruct_clear()
{
count = 0;
}
void self_destruct_update()
{
for (unsigned i = 0; i < count; ++i) {
if (cmps[i].rem == 0)
entity_mark(cmps[i].entity_id);
else
--cmps[i].rem;
}
}
void self_destruct_add(unsigned id, unsigned frames)
{
assert(count < MAX);
cmp_t *c = cmps + count++;
*c = (cmp_t) {
.entity_id = id,
.rem = frames,
.component_id = entity_add_component(id, update, remove, c),
};
}
-8
View File
@@ -1,8 +0,0 @@
#ifndef SELF_DESTRUCT_H
#define SELF_DESTRUCT_H
void self_destruct_clear();
void self_destruct_update();
void self_destruct_add(unsigned id, unsigned frames);
#endif
+19 -172
View File
@@ -9,12 +9,10 @@
#define MAX_LINES 2 #define MAX_LINES 2
#define TEXT_HEIGHT 8 #define TEXT_HEIGHT 8
#define TEXT_SCALE 0.0125 #define TEXT_SCALE 0.025
#define LETTER_WIDTH 3 #define LETTER_WIDTH 3
#define SPACE_WIDTH 1 #define SPACE_WIDTH 1
#define MAX_SCORE_CHARS 4
typedef struct { typedef struct {
unsigned line_count; unsigned line_count;
unsigned line_lens[MAX_LINES]; unsigned line_lens[MAX_LINES];
@@ -28,93 +26,6 @@ static const mat3_t text_transform = {
}; };
static const glyph_t font[] = { static const glyph_t font[] = {
['0'] = {
.line_count = 1,
.line_lens = { 5 },
.lines = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 }, { -1, 4 } },
},
},
['1'] = {
.line_count = 2,
.line_lens = { 3, 2 },
.lines = {
{ { -1, 4 }, { 0, 4 }, { 0, -4 } },
{ { -1, -4 }, { 1, -4 } },
},
},
['2'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ -1, 4 }, { 1, 4 }, { 1, -2 },
{ -1, -2 }, { -1, -4 }, { 1, -4 },
},
},
},
['3'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ -1, 4 }, { 1, 4 }, { 0, -2 },
{ 1, -2 }, { 1, -4 }, { -1, -4 },
},
},
},
['4'] = {
.line_count = 1,
.line_lens = { 4 },
.lines = {
{ { 1, -2 }, { -1, -2 }, { 1, 4 }, { 1, -4 } }
},
},
['5'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ 1, 4 }, { -1, 4 }, { -1, -2 },
{ 1, -2 }, { 1, -4 }, { -1, -4 },
},
},
},
['6'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ 1, 4 }, { -1, 4 }, { -1, -4 },
{ 1, -4 }, { 1, -2 }, { -1, -2 },
},
},
},
['7'] = {
.line_count = 1,
.line_lens = { 3 },
.lines = {
{ { -1, 4 }, { 1, 4 }, { 0, -4 } },
},
},
['8'] = {
.line_count = 2,
.line_lens = { 5, 2 },
.lines = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 }, { -1, 4 } },
{ { -1, -2 }, { 1, -2 } },
},
},
['9'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ 1, -2 }, { -1, -2 }, { -1, 4 },
{ 1, 4 }, { 1, -4 }, { -1, -4 },
},
},
},
['A'] = { ['A'] = {
.line_count = 2, .line_count = 2,
.line_lens = { 3, 2 }, .line_lens = { 3, 2 },
@@ -130,13 +41,6 @@ static const glyph_t font[] = {
{ { 1, 4 }, { -1, 4 }, { -1, -4 }, { 1, -4 } }, { { 1, 4 }, { -1, 4 }, { -1, -4 }, { 1, -4 } },
}, },
}, },
['D'] = {
.line_count = 1,
.line_lens = { 5 },
.lines = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 0, 4 }, { -1, 4 } },
},
},
['E'] = { ['E'] = {
.line_count = 2, .line_count = 2,
.line_lens = { 4, 2 }, .line_lens = { 4, 2 },
@@ -173,13 +77,6 @@ static const glyph_t font[] = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 }, { -1, 4 } }, { { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 }, { -1, 4 } },
}, },
}, },
['P'] = {
.line_count = 1,
.line_lens = { 5 },
.lines = {
{ { -1, -4 }, { -1, 4 }, { 1, 4 }, { 1, -2 }, { -1, -2 } },
},
},
['R'] = { ['R'] = {
.line_count = 1, .line_count = 1,
.line_lens = { 6 }, .line_lens = { 6 },
@@ -190,23 +87,6 @@ static const glyph_t font[] = {
}, },
}, },
}, },
['S'] = {
.line_count = 1,
.line_lens = { 6 },
.lines = {
{
{ 1, 4 }, { -1, 4 }, { -1, -2 },
{ 1, -2 }, { 1, -4 }, { -1, -4 },
},
},
},
['U'] = {
.line_count = 1,
.line_lens = { 4 },
.lines = {
{ { -1, 4 }, { -1, -4 }, { 1, -4 }, { 1, 4 } },
},
},
['V'] = { ['V'] = {
.line_count = 1, .line_count = 1,
.line_lens = { 3 }, .line_lens = { 3 },
@@ -216,29 +96,7 @@ static const glyph_t font[] = {
}, },
}; };
static void draw_glyph(unsigned c, vec2_t pos) void text_draw(const char *s)
{
assert(c < NELEMS(font) && font[c].line_count != 0);
const glyph_t *g = font + c;
const mat3_t m = mat3_mul_mat3(mat3_translation(pos), text_transform);
for (unsigned i = 0; i < g->line_count; ++i)
renderer_draw(g->lines[i], g->line_lens[i], m, false);
}
static void draw_text(const char *s, vec2_t pos)
{
for (const char *p = s; *p != '\0'; ++p) {
if (*p == ' ') {
pos.x += TEXT_SCALE * SPACE_WIDTH;
} else {
draw_glyph((unsigned)*p, pos);
pos.x += TEXT_SCALE * LETTER_WIDTH;
}
}
}
void text_draw_centre(const char *s)
{ {
int width = 0; int width = 0;
for (const char *p = s; *p != '\0'; ++p) for (const char *p = s; *p != '\0'; ++p)
@@ -251,34 +109,23 @@ void text_draw_centre(const char *s)
}; };
renderer_clear_rect((vec2_t) { 0, 0 }, bg_size); renderer_clear_rect((vec2_t) { 0, 0 }, bg_size);
const vec2_t pos = { TEXT_SCALE * (-width / 2.0f + 1), 0 }; int x = -width / 2 + 1;
draw_text(s, pos); for (const char *p = s; *p != '\0'; ++p) {
} if (*p == ' ') {
x += SPACE_WIDTH;
} else {
const unsigned c = (unsigned)*p;
assert(c < NELEMS(font) && font[c].line_count != 0);
const glyph_t *g = font + c;
void text_draw_score(unsigned score) const vec2_t t = { .x = x };
{ const mat3_t m
char buf[MAX_SCORE_CHARS + 1]; = mat3_mul_mat3(text_transform, mat3_translation(t));
buf[MAX_SCORE_CHARS] = '\0';
for (unsigned i = 0; i < MAX_SCORE_CHARS; ++i) { for (unsigned i = 0; i < g->line_count; ++i)
const unsigned v = score % 10; renderer_draw(g->lines[i], g->line_lens[i], m, false);
score /= 10;
buf[MAX_SCORE_CHARS - 1 - i] = '0' + v; x += LETTER_WIDTH;
}
} }
const int width = LETTER_WIDTH * MAX_SCORE_CHARS - 1;
const vec2_t bg_centre = {
.x = TEXT_SCALE * (width / 2.0f + 2) - aspect,
.y = 1.0f - TEXT_SCALE * (TEXT_HEIGHT / 2.0f + 2),
};
const vec2_t bg_size = {
.x = TEXT_SCALE * (width + 2),
.y = TEXT_SCALE * (TEXT_HEIGHT + 2),
};
renderer_clear_rect(bg_centre, bg_size);
const vec2_t text_pos = {
.x = TEXT_SCALE * 3 - aspect,
.y = bg_centre.y,
};
draw_text(buf, text_pos);
} }
+1 -2
View File
@@ -1,7 +1,6 @@
#ifndef TEXT_H #ifndef TEXT_H
#define TEXT_H #define TEXT_H
void text_draw_centre(const char *s); void text_draw(const char *s);
void text_draw_score(unsigned score);
#endif #endif