examples

Toy examples in single C files.
git clone git://henryandlizzy.uk/examples
Log | Files | Refs

gl-asteroids.cpp (14817B)


      1 #include <GLFW/glfw3.h>
      2 #include <alsa/asoundlib.h>
      3 #include <cmath>
      4 #include <vector>
      5 #include <span>
      6 #include <random>
      7 #include <array>
      8 #include <iostream>
      9 #include <algorithm>
     10 #include <thread>
     11 #include <chrono>
     12 #include <optional>
     13 #include <functional>
     14 
     15 extern "C" char const* __asan_default_options() { return "detect_leaks=0"; }
     16 
     17 /// SOUNDS
     18 
     19 using sample = std::span<short>;
     20 
     21 std::mutex queued_sounds_mutex;
     22 std::vector<sample> queued_sounds;
     23 
     24 void play_sample(sample s)
     25 {
     26 	std::lock_guard lock(queued_sounds_mutex);
     27 	queued_sounds.push_back(s);
     28 }
     29 
     30 void sound_routine(std::stop_token token)
     31 {
     32 	snd_pcm_t* handle;
     33 
     34 	snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
     35 	snd_pcm_set_params(handle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, 1, 48000, 1, 10000);
     36 
     37 	std::vector<sample> playing_sounds;
     38 
     39 	while (not token.stop_requested())
     40 	{
     41 		{
     42 			std::lock_guard lock(queued_sounds_mutex);
     43 			std::ranges::move(queued_sounds, back_inserter(playing_sounds));
     44 			queued_sounds.clear();
     45 		}
     46 
     47 		std::array<short, 2048> buf;
     48 		for (auto& b : buf)
     49 		{
     50 			b = 0;
     51 			std::erase_if(playing_sounds, [](auto s){ return s.empty(); });
     52 			for (auto& s : playing_sounds)
     53 			{
     54 				b += s[0];
     55 				s = sample{s.data()+1, s.size()-1};
     56 			}
     57 		}
     58 
     59 		snd_pcm_writei(handle, buf.data(), buf.size());
     60 	}
     61 	snd_pcm_drain(handle);
     62 	snd_pcm_close(handle);
     63 }
     64 
     65 std::jthread sound(sound_routine);
     66 
     67 /// GRAPHICS
     68 
     69 constexpr int window_size = 700;
     70 
     71 struct coord
     72 {
     73 	coord() = default;
     74 	coord(float s)
     75 	:	x(s)
     76 	,	y(s)
     77 	{}
     78 	coord(float _x, float _y)
     79 	:	x(_x)
     80 	,	y(_y)
     81 	{}
     82 	float x = 0, y = 0;
     83 };
     84 
     85 struct color
     86 {
     87 	float r, g, b;
     88 };
     89 
     90 struct visual
     91 {
     92 	GLint mode;
     93 	std::span<coord const> verts;
     94 	std::span<color const> cols;
     95 };
     96 
     97 coord const shipv[] = {{0, 2}, {1, -1}, {-1, -1}};
     98 color const shipc[] = {{1,1,1}, {.5,.5,.5}};
     99 
    100 coord const flamev[3] = {{0, -2.5}, {.6, -.25}, {-.6, -.25}};
    101 color const flamec[] = {{1,0,0}, {1,1,0}};
    102 
    103 coord const rockv1[] = {{0, 0}, {1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 0}};
    104 coord const rockv2[] = {{0, 0}, {1, 0}, {.7,.7}, {0, 1}, {-1, 0}, {-.9,-.9}, {0, -1}, {1, 0}};
    105 coord const rockv3[] = {{0, 0}, {1, 0}, {.7,.4}, {0, 1}, {-.8,.6}, {-1, 0}, {-.6,-.5}, {0, -1}, {1, 0}};
    106 coord const rockv4[] = {{0, 0}, {1, 0}, {.6,.4}, {0, .5}, {-.3,.6}, {-1, 0}, {-.6,-.5}, {0, -1}, {1, 0}};
    107 
    108 color const rockc[] = {{.5,.5,.5}, {.2,.2,.2}};
    109 color const powerupcb[] = {{.8,.4,.2}, {.4,.2,.1}};
    110 color const powerupcf[] = {{.2,.4,.8}, {.1,.2,.4}};
    111 color const powerupca[] = {{.8,.8,.2}, {.4,.4,.1}};
    112 
    113 coord const bulletv[] = {{0, 1}, {.25, .25}, {-.25, .25}, {.25, -1}, {-.25, -1}};
    114 color const bulletc[] = {{.0,.8,.0}, {.0,.4,.0}, {.0,.4,.0}, {.0,.0,.0}};
    115 
    116 coord starsv[64];
    117 
    118 visual ship{GL_TRIANGLES, shipv, shipc};
    119 visual flame{GL_TRIANGLES, flamev, flamec};
    120 visual bullet(GL_TRIANGLE_STRIP, bulletv, bulletc);
    121 visual rock1{GL_TRIANGLE_FAN, rockv1, rockc};
    122 visual rock2{GL_TRIANGLE_FAN, rockv2, rockc};
    123 visual rock3{GL_TRIANGLE_FAN, rockv3, rockc};
    124 visual rock4{GL_TRIANGLE_FAN, rockv4, rockc};
    125 visual powerup_firerate{GL_TRIANGLE_FAN, rockv1, powerupcf};
    126 visual powerup_bulletspeed{GL_TRIANGLE_FAN, rockv1, powerupcb};
    127 visual powerup_acceleration{GL_TRIANGLE_FAN, rockv1, powerupca};
    128 std::array<visual const*, 4> rock_types = {&rock1, &rock2, &rock3, &rock4};
    129 
    130 float wrap(float n, float min, float max)
    131 {
    132 	if (n >= max)
    133 		return n - max + min;
    134 	else if (n < min)
    135 		return n + max - min;
    136 	else
    137 		return n;
    138 }
    139 
    140 void glCoord(coord c)
    141 {
    142 	glVertex2f(c.x, c.y);
    143 }
    144 void glColor(color c)
    145 {
    146 	glColor3f(c.r, c.g, c.b);
    147 }
    148 
    149 coord operator +(coord lhs, coord rhs)
    150 {
    151 	return {lhs.x + rhs.x, lhs.y + rhs.y};
    152 }
    153 
    154 coord& operator +=(coord& lhs, coord rhs)
    155 {
    156 	return lhs = lhs + rhs;
    157 }
    158 
    159 coord operator -(coord rhs)
    160 {
    161 	return {-rhs.x, -rhs.y};
    162 }
    163 
    164 coord operator - (coord lhs, coord rhs)
    165 {
    166 	return lhs + -rhs;
    167 }
    168 
    169 coord& operator -=(coord& lhs, coord rhs)
    170 {
    171 	return lhs = lhs - rhs;
    172 }
    173 
    174 coord operator *(coord lhs, coord rhs)
    175 {
    176 	return {lhs.x * rhs.x, lhs.y * rhs.y};
    177 }
    178 
    179 coord& operator *=(coord& lhs, coord rhs)
    180 {
    181 	lhs = lhs * rhs;
    182 	return lhs;
    183 }
    184 
    185 /// PHYSICS
    186 
    187 coord rotate(coord o, float a)
    188 {
    189 	float s = sin(a), c = cos(a);
    190 	return {o.x*c + o.y*s, o.y*c - o.x*s};
    191 }
    192 
    193 coord coord_from_rotation(float a)
    194 {
    195 	return {(float)sin(a), (float)cos(a)};
    196 }
    197 
    198 struct physics
    199 {
    200 	coord pos, vel;
    201 	float rot, ang_mom, scale;
    202 	void move(float dt)
    203 	{
    204 		pos += vel * dt;
    205 		rot += ang_mom * dt;
    206 	}
    207 	static void wrap_pos(float& pos, float vel)
    208 	{
    209 		if (pos > 1 && vel > 0)
    210 			pos -= 2;
    211 		else if (pos < -1 && vel < 0)
    212 			pos += 2;
    213 	}
    214 	void wrap_pos(void)
    215 	{
    216 		wrap_pos(pos.x, vel.x);
    217 		wrap_pos(pos.y, vel.y);
    218 	}
    219 	static bool left_axis(float pos, float vel, float scale)
    220 	{
    221 		if (vel < 0)
    222 			pos = -pos;
    223 
    224 		return pos > 1 + scale;
    225 	}
    226 	bool left_arena() const
    227 	{
    228 		return left_axis(pos.x, vel.x, scale) or left_axis(pos.y, vel.y, scale);
    229 	}
    230 };
    231 
    232 bool is_colliding(physics const& a, physics const& b)
    233 {
    234 	float len = a.scale + b.scale;
    235 	auto d = a.pos - b.pos;
    236 
    237 	return d.x * d.x + d.y * d.y < len * len;
    238 }
    239 
    240 void draw(visual const& v, physics const& p)
    241 {
    242 	glBegin(v.mode);
    243 
    244 	for (unsigned i = 0; i < v.verts.size(); ++i)
    245 	{
    246 		if (i < v.cols.size())
    247 			glColor(v.cols[i]);
    248 		glCoord(rotate(v.verts[i] * p.scale, p.rot) + p.pos);
    249 	}
    250 
    251 	glEnd();
    252 }
    253 
    254 void draw_wrapped(visual const& v, physics p)
    255 {
    256 	static coord const offsets[] = {{0,0}, {2,0}, {-2,2}, {-2,-2}, {2,-2}};
    257 
    258 	for (auto o : offsets)
    259 	{
    260 		p.pos += o;
    261 		draw(v, p);
    262 	}
    263 }
    264 
    265 struct entity
    266 {
    267 	visual const* vis;
    268 	physics phys;
    269 
    270 	entity() = delete;
    271 	entity(visual const& _v, physics _p)
    272 	:	vis(&_v)
    273 	,	phys(_p)
    274 	{}
    275 
    276 	void draw()
    277 	{
    278 		::draw(*vis, phys);
    279 	}
    280 };
    281 
    282 static bool left_arena(entity const& e)
    283 {
    284 	return e.phys.left_arena();
    285 }
    286 
    287 std::random_device rd;
    288 std::default_random_engine gen(rd());
    289 
    290 struct particle : entity
    291 {
    292 	float ttl;
    293 
    294 	static particle& make_particle(entity e, float ttl)
    295 	{
    296 		static std::uniform_real_distribution<float> d(.5, 1);
    297 		return particles.emplace_back(e, ttl * d(gen));
    298 	}
    299 	static void move_all(float dt)
    300 	{
    301 		for (auto& p : particles)
    302 		{
    303 			p.ttl -= dt;
    304 			p.phys.move(dt);
    305 		}
    306 		std::erase_if(particles, [](auto& p) { return p.ttl <= 0; });
    307 		std::erase_if(particles, left_arena);
    308 	}
    309 	static void draw_all()
    310 	{
    311 		for (auto& p : particles)
    312 			p.draw();
    313 	}
    314 private:
    315 	static std::vector<particle> particles;
    316 };
    317 
    318 std::vector<particle> particle::particles;
    319 
    320 static GLFWwindow* w;
    321 
    322 bool is_pressed(int key)
    323 {
    324 	return glfwGetKey(w, key) != GLFW_RELEASE;
    325 }
    326 
    327 struct powerup : entity
    328 {
    329 	std::function<void(void)> cb;
    330 
    331 	powerup(entity e, std::function<void(void)> c)
    332 	:	entity(e)
    333 	,	cb(c)
    334 	{}
    335 };
    336 
    337 std::vector<entity> rocks;
    338 std::vector<entity> bullets;
    339 std::optional<powerup> powerups;
    340 
    341 physics ship_phys{{}, {}, 0.f, 0.f, .06f};
    342 bool up = false;
    343 
    344 int lives = 3;
    345 unsigned ammo = 10;
    346 
    347 void draw_scene(float t)
    348 {
    349 	glClearColor(0,0,0,1);
    350 	glClear(GL_COLOR_BUFFER_BIT);
    351 
    352 	glColor({1,1,1});
    353 	glBegin(GL_POINTS);
    354 	for (auto v : starsv)
    355 		glCoord(v);
    356 	glEnd();
    357 
    358 	particle::draw_all();
    359 	for (auto& r : rocks)
    360 		r.draw();
    361 	for (auto& b : bullets)
    362 		b.draw();
    363 	if (powerups)
    364 		powerups->draw();
    365 
    366 	if (up)
    367 	{
    368 		auto f = ship_phys;
    369 		f.scale *= 1 + 0.1 * sin(t * 49) * sin(t * 9);
    370 		draw_wrapped(flame, f);
    371 	}
    372 	draw_wrapped(ship, ship_phys);
    373 
    374 	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    375 
    376 	for (int i = 0; i < lives-1; ++i)
    377 		draw(ship, {{.9f, -.9f + i * .15f}, {}, 0.f, 0.f, .04f});
    378 
    379 	glBegin(GL_LINES);
    380 	glColor({.0,.4,.0});
    381 	glCoord({-.95f, -.9f});
    382 	glCoord({-.95f, ammo * .09f - .9f});
    383 	glEnd();
    384 
    385 	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    386 
    387 	glfwSwapBuffers(w);
    388 	glfwPollEvents();
    389 }
    390 
    391 visual const& random_rock_visual(void)
    392 {
    393 	static unsigned rtype;
    394 
    395 	++rtype;
    396 	rtype %= rock_types.size();
    397 
    398 	return *rock_types[rtype];
    399 }
    400 
    401 void joy_cb(int jid, int event)
    402 {
    403 	switch (event)
    404 	{
    405 	case GLFW_CONNECTED:
    406 		std::cout << 'J' << jid << ": CONNECTED\n";
    407 		return;
    408 	case GLFW_DISCONNECTED:
    409 		std::cout << 'J' << jid << ": DISCONNECTED\n";
    410 		return;
    411 	default:
    412 		std::cout << 'J' << jid << ": " << event << '\n';
    413 	}
    414 }
    415 
    416 int main(int, char* argv[])
    417 {
    418 	signed score = 0;
    419 	float firerate = 2, bullet_speed = 1, acceleration = 0.8;
    420 
    421 	std::uniform_real_distribution<float> dis_p(-1,1);
    422 	std::uniform_real_distribution<float> dis_s(0.05,0.25);
    423 	std::uniform_real_distribution<float> dis_v(0.02,0.05);
    424 	std::uniform_int_distribution<int> dis_d(0,7);
    425 
    426 	std::array<short, 2 * 512> tone;
    427 	for (unsigned i = 0; i < tone.size(); ++i)
    428 		tone[i] = sin(i * 6.283 * 440 / 48000) * 50 * 128;
    429 
    430 	std::array<short, 2 * 512> square;
    431 	for (unsigned i = 0; i < square.size(); ++i)
    432 		square[i] = ((i & 0x80) - 0x40) * 50;
    433 
    434 	std::array<short, 3 * 512> saw;
    435 	for (unsigned i = 0; i < saw.size(); ++i)
    436 		saw[i] = (i % 0x80 - 0x40) * 30;
    437 
    438 	std::array<short, 8 * 512> blip;
    439 	for (unsigned i = 0; i < blip.size(); ++i)
    440 		blip[i] = sin(i * (i + 440) * 6.283 / 10 / 48000) * 50 * 128;
    441 
    442 	std::array<short, 16 * 1024> bang;
    443 	for (unsigned i = 0; i < bang.size(); ++i)
    444 		bang[i] = 2048 * dis_p(gen) / exp(i * .00005) * (2 + cos(i * 6.283 * 15 / 48000));
    445 
    446 	glfwInit();
    447 
    448 	w = glfwCreateWindow(window_size, window_size, argv[0], NULL, NULL);
    449 	glfwSetJoystickCallback(joy_cb);
    450 	int wx, wy;
    451 	glfwGetFramebufferSize(w, &wx, &wy);
    452 	std::cout << "x: " << wx << "    y: " << wy << '\n';
    453 
    454 	glfwMakeContextCurrent(w);
    455 
    456 	float last_t = glfwGetTime();
    457 
    458 	float rock_time = 1, bullet_time = 0, ammo_refill_time = .3, powerup_time = 10;
    459 
    460 	for (auto& c : starsv)
    461 		c.x = dis_p(gen), c.y = dis_p(gen);
    462 
    463 	while (!glfwWindowShouldClose(w) && not is_pressed(GLFW_KEY_ESCAPE))
    464 	{
    465 		float t = glfwGetTime();
    466 		float dt = t - last_t;
    467 
    468 		if (rock_time < t)
    469 		{
    470 			rock_time += (rocks.size() < 3 ? 2 : 6) / log(2*t+1);
    471 
    472 			int dir = dis_d(gen);
    473 
    474 			auto scale = dis_s(gen);
    475 			coord p = {dis_p(gen) / 2 - .5f, -1 - scale};
    476 			coord v(dis_v(gen), dis_v(gen));
    477 
    478 			v *= 1+log(t+1);
    479 
    480 			if (dir & 1)
    481 				v.x = -v.x, p.x = -p.x;
    482 			if (dir & 2)
    483 				p.y = -p.y, v.y = -v.y;
    484 			if (dir & 4)
    485 			{
    486 				std::swap(p.x, p.y);
    487 				std::swap(v.x, v.y);
    488 			}
    489 
    490 			static std::uniform_real_distribution<float> ang(-3,3);
    491 			rocks.emplace_back(random_rock_visual(), physics{p, v, 0, ang(gen), scale});
    492 		}
    493 
    494 		if (powerup_time < t)
    495 		{
    496 			powerup_time += 15;
    497 			int dir = dis_d(gen);
    498 			auto scale = .03f;
    499 			static std::uniform_real_distribution<float> pos(-.9,.9);
    500 			coord p = {pos(gen) / 2 - .5f, -1 - scale};
    501 			coord v = {0, .3f};
    502 			if (dir & 1)
    503 				v.x = -v.x, p.x = -p.x;
    504 			if (dir & 2)
    505 				p.y = -p.y, v.y = -v.y;
    506 			if (dir & 4)
    507 			{
    508 				std::swap(p.x, p.y);
    509 				std::swap(v.x, v.y);
    510 			}
    511 
    512 			visual const* vis;
    513 			std::function<void(void)> cb;
    514 
    515 			static int type;
    516 			switch (type++ % 3)
    517 			{
    518 			case 0:
    519 				vis = &powerup_bulletspeed;
    520 				cb = [&firerate](){ firerate += .5f; };
    521 				break;
    522 			case 1:
    523 				vis = &powerup_firerate;
    524 				cb = [&bullet_speed](){ bullet_speed += .2f; };
    525 				break;
    526 			case 2:
    527 				vis = &powerup_acceleration;
    528 				cb = [&acceleration](){ acceleration += .2f; };
    529 				break;
    530 			}
    531 
    532 			powerups.emplace(entity{*vis, physics{p, v, 0, 0, scale}}, cb);
    533 		}
    534 
    535 		if (ammo < 20 && ammo_refill_time < t)
    536 		{
    537 			++ammo;
    538 			ammo_refill_time += .4;
    539 		}
    540 
    541 		if (is_pressed(GLFW_KEY_SPACE) && bullet_time < t && ammo)
    542 		{
    543 			play_sample(tone);
    544 			--ammo;
    545 			bullet_time = t + 1/firerate;
    546 
    547 			bullets.emplace_back(bullet, ship_phys);
    548 			auto& b = bullets.back().phys;
    549 
    550 			b.ang_mom = 0;
    551 			b.scale /= 1.25;
    552 			auto vel = coord_from_rotation(b.rot) * bullet_speed;
    553 			b.vel += vel;
    554 			ship_phys.vel -= vel * .05f;
    555 		}
    556 
    557 		last_t = t;
    558 
    559 		int kl = is_pressed(GLFW_KEY_LEFT);
    560 		int kr = is_pressed(GLFW_KEY_RIGHT);
    561 
    562 		if (kl ^ kr)
    563 			ship_phys.ang_mom = (kr ? 1 : -1) * (up ? 3 : 2);
    564 		else
    565 			ship_phys.ang_mom = 0;
    566 
    567 		up = is_pressed(GLFW_KEY_UP);
    568 
    569 		if (up)
    570 			ship_phys.vel += coord_from_rotation(ship_phys.rot) * dt * acceleration;
    571 
    572 		// resolve motion
    573 
    574 		for (auto& r : rocks)
    575 			r.phys.move(dt);
    576 		for (auto& b : bullets)
    577 			b.phys.move(dt);
    578 		if (powerups)
    579 			powerups->phys.move(dt);
    580 
    581 		ship_phys.move(dt);
    582 		ship_phys.wrap_pos();
    583 		particle::move_all(dt);
    584 
    585 		// handle collisions
    586 
    587 		for (unsigned i = 0; i < rocks.size();)
    588 		{
    589 			auto r = rocks.begin() + i;
    590 			for (auto b = bullets.begin(); b < bullets.end();)
    591 			{
    592 				if (is_colliding(r->phys, b->phys))
    593 				{
    594 					play_sample(square);
    595 					for (unsigned j = 0; j < 7; ++j)
    596 					{
    597 						static std::uniform_real_distribution<float> scale(0.005,0.025);
    598 						entity debris = *r;
    599 						debris.vis = &random_rock_visual();
    600 						debris.phys.vel.x += .3 * dis_p(gen);
    601 						debris.phys.vel.y += .3 * dis_p(gen);
    602 						debris.phys.scale = scale(gen);
    603 						particle::make_particle(debris, 1);
    604 					}
    605 
    606 					if (r->phys.scale > .13f)
    607 					{
    608 						coord split{dis_v(gen), dis_v(gen)};
    609 
    610 						auto nr = *r;
    611 
    612 						static std::uniform_real_distribution<float> dis_z(.3,.6);
    613 						float sf = dis_z(gen);
    614 
    615 						r->phys.scale *= sf;
    616 						nr.phys.scale *= .9 - sf;
    617 
    618 						r->vis = &random_rock_visual();
    619 						nr.vis = &random_rock_visual();
    620 
    621 						r->phys.vel += 2*split;
    622 						nr.phys.vel -= split;
    623 
    624 						nr.phys.ang_mom *= -1.5f;
    625 
    626 						rocks.push_back(nr);
    627 						++i;
    628 					}
    629 					else
    630 						rocks.erase(r);
    631 
    632 					b = bullets.erase(b);
    633 					score += 3;
    634 					goto bang;
    635 				}
    636 				++b;
    637 			}
    638 			++i;
    639 		bang:	continue;
    640 		}
    641 
    642 		if (powerups)
    643 		{
    644 			for (auto b = bullets.begin(); b < bullets.end(); ++b)
    645 			{
    646 				if (not is_colliding(b->phys, powerups->phys))
    647 					continue;
    648 
    649 				play_sample(saw);
    650 
    651 				for (unsigned j = 0; j < 4; ++j)
    652 				{
    653 					entity debris = *powerups;
    654 					debris.phys.vel.x += .3 * dis_p(gen);
    655 					debris.phys.vel.y += .3 * dis_p(gen);
    656 					debris.phys.scale *= 1.5 * dis_s(gen);
    657 					particle::make_particle(debris, 2);
    658 				}
    659 				powerups.reset();
    660 				score -= 10;
    661 				bullets.erase(b);
    662 				break;
    663 			}
    664 			if (is_colliding(ship_phys, powerups->phys))
    665 			{
    666 				play_sample(blip);
    667 				powerups->cb();
    668 				powerups.reset();
    669 				score += 10;
    670 			}
    671 		}
    672 
    673 		for (auto r = rocks.begin(); r != rocks.end(); /**/)
    674 		{
    675 			if (!is_colliding(ship_phys, r->phys))
    676 			{
    677 				++r;
    678 				continue;
    679 			}
    680 
    681 			play_sample(bang);
    682 			lives--;
    683 
    684 			for (unsigned j = 0; j < 7; ++j)
    685 			{
    686 				static std::uniform_real_distribution<float> scale(0.005,0.025);
    687 				entity debris = *r;
    688 				debris.vis = &random_rock_visual();
    689 				debris.phys.vel.x += .3 * dis_p(gen);
    690 				debris.phys.vel.y += .3 * dis_p(gen);
    691 				debris.phys.scale = scale(gen);
    692 				particle::make_particle(debris, 1);
    693 			}
    694 			r = rocks.erase(r);
    695 		}
    696 
    697 		if (!lives)
    698 		{
    699 			using namespace std::chrono_literals;
    700 			auto timeout = std::chrono::steady_clock::now() + 1s;
    701 			score += t;
    702 			std::cout << "Score: " << score << '\n';
    703 			while (std::chrono::steady_clock::now() < timeout)
    704 				draw_scene(t);
    705 			break;
    706 		}
    707 
    708 		score -= std::erase_if(bullets, left_arena);
    709 		score -= std::erase_if(rocks, left_arena);
    710 
    711 		if (powerups && left_arena(*powerups))
    712 		{
    713 			powerups.reset();
    714 			--score;
    715 		}
    716 
    717 		draw_scene(t);
    718 	}
    719 
    720 	glfwDestroyWindow(w);
    721 	glfwTerminate();
    722 
    723 	return 0;
    724 }