-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgame.cpp
499 lines (417 loc) · 16.7 KB
/
game.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
#include "precomp.h" // include (only) this in every .cpp file
constexpr auto num_tanks_blue = 2048;
constexpr auto num_tanks_red = 2048;
constexpr auto tank_max_health = 1000;
constexpr auto rocket_hit_value = 60;
constexpr auto particle_beam_hit_value = 50;
constexpr auto tank_max_speed = 1.0;
constexpr auto health_bar_width = 70;
constexpr auto max_frames = 2000;
//Global performance timer
constexpr auto REF_PERFORMANCE = 114757; //UPDATE THIS WITH YOUR REFERENCE PERFORMANCE (see console after 2k frames)
static timer perf_timer;
static float duration;
//Load sprite files and initialize sprites
static Surface* tank_red_img = new Surface("assets/Tank_Proj2.png");
static Surface* tank_blue_img = new Surface("assets/Tank_Blue_Proj2.png");
static Surface* rocket_red_img = new Surface("assets/Rocket_Proj2.png");
static Surface* rocket_blue_img = new Surface("assets/Rocket_Blue_Proj2.png");
static Surface* particle_beam_img = new Surface("assets/Particle_Beam.png");
static Surface* smoke_img = new Surface("assets/Smoke.png");
static Surface* explosion_img = new Surface("assets/Explosion.png");
static Sprite tank_red(tank_red_img, 12);
static Sprite tank_blue(tank_blue_img, 12);
static Sprite rocket_red(rocket_red_img, 12);
static Sprite rocket_blue(rocket_blue_img, 12);
static Sprite smoke(smoke_img, 4);
static Sprite explosion(explosion_img, 9);
static Sprite particle_beam_sprite(particle_beam_img, 3);
const static vec2 tank_size(7, 9);
const static vec2 rocket_size(6, 6);
const static float tank_radius = 3.f;
const static float rocket_radius = 5.f;
// -----------------------------------------------------------
// Initialize the simulation state
// This function does not count for the performance multiplier
// (Feel free to optimize anyway though ;) )
// -----------------------------------------------------------
void Game::init()
{
frame_count_font = new Font("assets/digital_small.png", "ABCDEFGHIJKLMNOPQRSTUVWXYZ:?!=-0123456789.");
tanks.reserve(num_tanks_blue + num_tanks_red);
uint max_rows = 24;
float start_blue_x = tank_size.x + 40.0f;
float start_blue_y = tank_size.y + 30.0f;
float start_red_x = 1088.0f;
float start_red_y = tank_size.y + 30.0f;
float spacing = 7.5f;
//Spawn blue tanks
for (int i = 0; i < num_tanks_blue; i++)
{
vec2 position{ start_blue_x + ((i % max_rows) * spacing), start_blue_y + ((i / max_rows) * spacing) };
tanks.push_back(Tank(position.x, position.y, BLUE, &tank_blue, &smoke, 1100.f, position.y + 16, tank_radius, tank_max_health, tank_max_speed));
}
//Spawn red tanks
for (int i = 0; i < num_tanks_red; i++)
{
vec2 position{ start_red_x + ((i % max_rows) * spacing), start_red_y + ((i / max_rows) * spacing) };
tanks.push_back(Tank(position.x, position.y, RED, &tank_red, &smoke, 100.f, position.y + 16, tank_radius, tank_max_health, tank_max_speed));
}
particle_beams.push_back(Particle_beam(vec2(590, 327), vec2(100, 50), &particle_beam_sprite, particle_beam_hit_value));
particle_beams.push_back(Particle_beam(vec2(64, 64), vec2(100, 50), &particle_beam_sprite, particle_beam_hit_value));
particle_beams.push_back(Particle_beam(vec2(1200, 600), vec2(100, 50), &particle_beam_sprite, particle_beam_hit_value));
}
// -----------------------------------------------------------
// Close down application
// -----------------------------------------------------------
void Game::shutdown()
{
}
// -----------------------------------------------------------
// Iterates through all tanks and returns the closest enemy tank for the given tank
// -----------------------------------------------------------
Tank& Game::find_closest_enemy(Tank& current_tank)
{
float closest_distance = numeric_limits<float>::infinity();
int closest_index = 0;
for (int i = 0; i < tanks.size(); i++)
{
if (tanks.at(i).allignment != current_tank.allignment && tanks.at(i).active)
{
float sqr_dist = fabsf((tanks.at(i).get_position() - current_tank.get_position()).sqr_length());
if (sqr_dist < closest_distance)
{
closest_distance = sqr_dist;
closest_index = i;
}
}
}
return tanks.at(closest_index);
}
//Checks if a point lies on the left of an arbitrary angled line
bool Tmpl8::Game::left_of_line(vec2 line_start, vec2 line_end, vec2 point)
{
return ((line_end.x - line_start.x) * (point.y - line_start.y) - (line_end.y - line_start.y) * (point.x - line_start.x)) < 0;
}
// -----------------------------------------------------------
// Update the game state:
// Move all objects
// Update sprite frames
// Collision detection
// Targeting etc..
// -----------------------------------------------------------
void Game::update(float deltaTime)
{
//Calculate the route to the destination for each tank using BFS
//Initializing routes here so it gets counted for performance..
if (frame_count == 0)
{
for (Tank& t : tanks)
{
t.set_route(background_terrain.get_route(t, t.target));
}
}
//Check tank collision and nudge tanks away from each other
for (Tank& tank : tanks)
{
if (tank.active)
{
for (Tank& other_tank : tanks)
{
if (&tank == &other_tank || !other_tank.active) continue;
vec2 dir = tank.get_position() - other_tank.get_position();
float dir_squared_len = dir.sqr_length();
float col_squared_len = (tank.get_collision_radius() + other_tank.get_collision_radius());
col_squared_len *= col_squared_len;
if (dir_squared_len < col_squared_len)
{
tank.push(dir.normalized(), 1.f);
}
}
}
}
//Update tanks
for (Tank& tank : tanks)
{
if (tank.active)
{
//Move tanks according to speed and nudges (see above) also reload
tank.tick(background_terrain);
//Shoot at closest target if reloaded
if (tank.rocket_reloaded())
{
Tank& target = find_closest_enemy(tank);
rockets.push_back(Rocket(tank.position, (target.get_position() - tank.position).normalized() * 3, rocket_radius, tank.allignment, ((tank.allignment == RED) ? &rocket_red : &rocket_blue)));
tank.reload_rocket();
}
}
}
//Update smoke plumes
for (Smoke& smoke : smokes)
{
smoke.tick();
}
//Calculate "forcefield" around active tanks
forcefield_hull.clear();
//Find first active tank (this loop is a bit disgusting, fix?)
int first_active = 0;
for (Tank& tank : tanks)
{
if (tank.active)
{
break;
}
first_active++;
}
vec2 point_on_hull = tanks.at(first_active).position;
//Find left most tank position
for (Tank& tank : tanks)
{
if (tank.active)
{
if (tank.position.x <= point_on_hull.x)
{
point_on_hull = tank.position;
}
}
}
//Calculate convex hull for 'rocket barrier'
while (true)
{
//Add last found point
forcefield_hull.push_back(point_on_hull);
//Loop through all points replacing the endpoint with the current iteration every time
//it lies left of the current segment formed by point_on_hull and the current endpoint.
//By the end we have a segment with no points on the left and thus a point on the convex hull.
vec2 endpoint = tanks.at(first_active).position;
for (Tank& tank : tanks)
{
if (tank.active)
{
if ((endpoint == point_on_hull) || left_of_line(point_on_hull, endpoint, tank.position))
{
endpoint = tank.position;
}
}
}
//Set the starting point of the next segment to the found endpoint.
point_on_hull = endpoint;
//If we went all the way around we are done.
if (endpoint == forcefield_hull.at(0))
{
break;
}
}
//Update rockets
for (Rocket& rocket : rockets)
{
rocket.tick();
//Check if rocket collides with enemy tank, spawn explosion, and if tank is destroyed spawn a smoke plume
for (Tank& tank : tanks)
{
if (tank.active && (tank.allignment != rocket.allignment) && rocket.intersects(tank.position, tank.collision_radius))
{
explosions.push_back(Explosion(&explosion, tank.position));
if (tank.hit(rocket_hit_value))
{
smokes.push_back(Smoke(smoke, tank.position - vec2(7, 24)));
}
rocket.active = false;
break;
}
}
}
//Disable rockets if they collide with the "forcefield"
//Hint: A point to convex hull intersection test might be better here? :) (Disable if outside)
for (Rocket& rocket : rockets)
{
if (rocket.active)
{
for (size_t i = 0; i < forcefield_hull.size(); i++)
{
if (circle_segment_intersect(forcefield_hull.at(i), forcefield_hull.at((i + 1) % forcefield_hull.size()), rocket.position, rocket.collision_radius))
{
explosions.push_back(Explosion(&explosion, rocket.position));
rocket.active = false;
}
}
}
}
//Remove exploded rockets with remove erase idiom
rockets.erase(std::remove_if(rockets.begin(), rockets.end(), [](const Rocket& rocket) { return !rocket.active; }), rockets.end());
//Update particle beams
for (Particle_beam& particle_beam : particle_beams)
{
particle_beam.tick(tanks);
//Damage all tanks within the damage window of the beam (the window is an axis-aligned bounding box)
for (Tank& tank : tanks)
{
if (tank.active && particle_beam.rectangle.intersects_circle(tank.get_position(), tank.get_collision_radius()))
{
if (tank.hit(particle_beam.damage))
{
smokes.push_back(Smoke(smoke, tank.position - vec2(0, 48)));
}
}
}
}
//Update explosion sprites and remove when done with remove erase idiom
for (Explosion& explosion : explosions)
{
explosion.tick();
}
explosions.erase(std::remove_if(explosions.begin(), explosions.end(), [](const Explosion& explosion) { return explosion.done(); }), explosions.end());
}
// -----------------------------------------------------------
// Draw all sprites to the screen
// (It is not recommended to multi-thread this function)
// -----------------------------------------------------------
void Game::draw()
{
// clear the graphics window
screen->clear(0);
//Draw background
background_terrain.draw(screen);
//Draw sprites
for (int i = 0; i < num_tanks_blue + num_tanks_red; i++)
{
tanks.at(i).draw(screen);
vec2 tank_pos = tanks.at(i).get_position();
}
for (Rocket& rocket : rockets)
{
rocket.draw(screen);
}
for (Smoke& smoke : smokes)
{
smoke.draw(screen);
}
for (Particle_beam& particle_beam : particle_beams)
{
particle_beam.draw(screen);
}
for (Explosion& explosion : explosions)
{
explosion.draw(screen);
}
//Draw forcefield (mostly for debugging, its kinda ugly..)
for (size_t i = 0; i < forcefield_hull.size(); i++)
{
vec2 line_start = forcefield_hull.at(i);
vec2 line_end = forcefield_hull.at((i + 1) % forcefield_hull.size());
line_start.x += HEALTHBAR_OFFSET;
line_end.x += HEALTHBAR_OFFSET;
screen->line(line_start, line_end, 0x0000ff);
}
//Draw sorted health bars
for (int t = 0; t < 2; t++)
{
const int NUM_TANKS = ((t < 1) ? num_tanks_blue : num_tanks_red);
const int begin = ((t < 1) ? 0 : num_tanks_blue);
std::vector<const Tank*> sorted_tanks;
insertion_sort_tanks_health(tanks, sorted_tanks, begin, begin + NUM_TANKS);
sorted_tanks.erase(std::remove_if(sorted_tanks.begin(), sorted_tanks.end(), [](const Tank* tank) { return !tank->active; }), sorted_tanks.end());
draw_health_bars(sorted_tanks, t);
}
}
// -----------------------------------------------------------
// Sort tanks by health value using insertion sort
// -----------------------------------------------------------
void Tmpl8::Game::insertion_sort_tanks_health(const std::vector<Tank>& original, std::vector<const Tank*>& sorted_tanks, int begin, int end)
{
const int NUM_TANKS = end - begin;
sorted_tanks.reserve(NUM_TANKS);
sorted_tanks.emplace_back(&original.at(begin));
for (int i = begin + 1; i < (begin + NUM_TANKS); i++)
{
const Tank& current_tank = original.at(i);
for (int s = (int)sorted_tanks.size() - 1; s >= 0; s--)
{
const Tank* current_checking_tank = sorted_tanks.at(s);
if ((current_checking_tank->compare_health(current_tank) <= 0))
{
sorted_tanks.insert(1 + sorted_tanks.begin() + s, ¤t_tank);
break;
}
if (s == 0)
{
sorted_tanks.insert(sorted_tanks.begin(), ¤t_tank);
break;
}
}
}
}
// -----------------------------------------------------------
// Draw the health bars based on the given tanks health values
// -----------------------------------------------------------
void Tmpl8::Game::draw_health_bars(const std::vector<const Tank*>& sorted_tanks, const int team)
{
int health_bar_start_x = (team < 1) ? 0 : (SCRWIDTH - HEALTHBAR_OFFSET) - 1;
int health_bar_end_x = (team < 1) ? health_bar_width : health_bar_start_x + health_bar_width - 1;
for (int i = 0; i < SCRHEIGHT - 1; i++)
{
//Health bars are 1 pixel each
int health_bar_start_y = i * 1;
int health_bar_end_y = health_bar_start_y + 1;
screen->bar(health_bar_start_x, health_bar_start_y, health_bar_end_x, health_bar_end_y, REDMASK);
}
//Draw the <SCRHEIGHT> least healthy tank health bars
int draw_count = std::min(SCRHEIGHT, (int)sorted_tanks.size());
for (int i = 0; i < draw_count - 1; i++)
{
//Health bars are 1 pixel each
int health_bar_start_y = i * 1;
int health_bar_end_y = health_bar_start_y + 1;
float health_fraction = (1 - ((double)sorted_tanks.at(i)->health / (double)tank_max_health));
if (team == 0) { screen->bar(health_bar_start_x + (int)((double)health_bar_width * health_fraction), health_bar_start_y, health_bar_end_x, health_bar_end_y, GREENMASK); }
else { screen->bar(health_bar_start_x, health_bar_start_y, health_bar_end_x - (int)((double)health_bar_width * health_fraction), health_bar_end_y, GREENMASK); }
}
}
// -----------------------------------------------------------
// When we reach max_frames print the duration and speedup multiplier
// Updating REF_PERFORMANCE at the top of this file with the value
// on your machine gives you an idea of the speedup your optimizations give
// -----------------------------------------------------------
void Tmpl8::Game::measure_performance()
{
char buffer[128];
if (frame_count >= max_frames)
{
if (!lock_update)
{
duration = perf_timer.elapsed();
cout << "Duration was: " << duration << " (Replace REF_PERFORMANCE with this value)" << endl;
lock_update = true;
}
frame_count--;
}
if (lock_update)
{
screen->bar(420 + HEALTHBAR_OFFSET, 170, 870 + HEALTHBAR_OFFSET, 430, 0x030000);
int ms = (int)duration % 1000, sec = ((int)duration / 1000) % 60, min = ((int)duration / 60000);
sprintf(buffer, "%02i:%02i:%03i", min, sec, ms);
frame_count_font->centre(screen, buffer, 200);
sprintf(buffer, "SPEEDUP: %4.1f", REF_PERFORMANCE / duration);
frame_count_font->centre(screen, buffer, 340);
}
}
// -----------------------------------------------------------
// Main application tick function
// -----------------------------------------------------------
void Game::tick(float deltaTime)
{
if (!lock_update)
{
update(deltaTime);
}
draw();
measure_performance();
// print something in the graphics window
//screen->Print("hello world", 2, 2, 0xffffff);
// print something to the text window
//cout << "This goes to the console window." << std::endl;
//Print frame count
frame_count++;
string frame_count_string = "FRAME: " + std::to_string(frame_count);
frame_count_font->print(screen, frame_count_string.c_str(), 350, 580);
}