-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.cc
1643 lines (1376 loc) · 54.3 KB
/
main.cc
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
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// DONE: Add stats printing as in nosdl. DONE. Refactor along. Fix the SDL bug.
// DONE: Fix cosmetic error when drawing borders for statlets (don't copy the
// top line). Add #TIME. DONE!
// DONE: Make it count errors. DONE.
// DONE: Make it pass the upper CPU limit into run_round so that we can do
// fastcpu easily. DONE. Also fix the keyboard polling for console. DONE.
// DONE: Add matchID parameter (Z?) and additional parameter to display r4-style
// stats for each round, used to find troublesome rounds. DONE
// DONE: Fix the error reporting problem and add the maxlines constraint. We'll
// probably need a constraint class, then... that error_container takes
// as a parameter when constructing its human-readable error message.
// Picked another way of doing this; first one DONE, now maxlines.. DONE!
//
// DONE: Implement + and -. Then test against the tst* robots.., check todos
// elsewhere, and afterwards, we're done! Kinda done, but I think + and
// - are reversed: look them up in the ATR2 docs to see if I'm right.
// + is slower, - is faster. DONE (except checking todos
// elsewhere)
// DONE: Test against unit tests, ATR2 standard bots, and robots/* for
// discrepancies. Find out why it's so slow at compiling (probably that
// lookup unit getting reinited again and again..) DONE and N/A
// DONE: Give it a title. Do test against unit tests. Run a lot to find that
// finite(u) && finite(up) assertion break, and fix that.
// Test shields and heat evolution (doesn't work very well right now,
// though damage transmission seems to), and fix the segfault on incorrect
// filenames. Also, fix the statlet problem (offset > robots.size() -
// statlets.size() without having to know statlets.size(). Maybe
// renderer.get_last_visible_robot?). Bluesky, add small statlets
// and have it handle that seamlessly.
// Scrolling no longer works at all, fix that. Otherwise,
// and except the BLUESKY, we're all done here. DONE.
// Oh, and is there a problem with the colors? They don't seem to be
// subdividing correctly. Also, it might be better to have a radix 3
// subdivision - fits more readily into (red, green, blue).
// Bluesky that, but should be possible to find a closed form. For the
// real bonus points, use LCbCr instead of HSV.
// DONE: Test the get_num_statlets thing we fixed now (that there aren't any
// duplicates when we scroll down). DONE
// DONE: Fix the display thing where it doesn't show robots completely on the
// edge. There are two solutions: fix edge_collide so that the
// collision radius is != 0, or lower the size of the robot
// widget so that they won't get clipped.
// Is that a real error? Make some robots that always
// crash with the edge, then check.
// Done, I can't reproduce the error.
// Also make a parameter to show progress (cout with \r) and
// one to enable line-buffering (don't init consolekbd).
// Not sure if I should make a progress one, but I
// really should make a line-buffering one.
// DONE line-buffering. Not sure if I should do progress;
// in that case, it should be on -v, with -vv
// for showing per-cycle stats.
//
// Also name the robot when he's showing errors and the error
// is dumped to console (e.g instead of saying "Error at line 27"
// say "Error at blah.at2:27" or somesuch. DONE, although it
// doesn't add the extension.
// Check various robots for errors and whether these
// errors also appear in ATR2 native.
//
// And also fix whatever it is that hides one of the robots at the
// end of a porttst regression test. The mine is visible yet the
// robot is not, somehow.
// FIXED. Break breaks the loop, not just the if.
//
// BLUESKY : If the user wants a progress report, include ETA in it. This
// overloads kbd_timer since it's no longer a kbd_timer, it's a
// "anything periodic requiring user input or output" timer.
// Could use the window title hack for this, but it's
// getting pretty kludgy..
// DONE: Allow things like ktr sduck sduck to resolve to sduck.at2 sduck.at2.
// This probably would take the form of does_file_exist, where
// each filename is run through it, and then with at2 appended.
// A version supporting locked robots would also check against
// atl, unless that'd make redundancy (where both q.at2 and q.atl
// exists and he types ktr q; it would then say "don't know q" or
// "don't know what file you mean").
// DONE: Fix segmentation fault with extreme number of robots (preotr/*)
// Actually, there's a c* robot that does it: comms crash on insert with
// channel < 0.
// This is the last resort truetype path. If we don't find it in any other
// location, try it here, and if that too fails, abort with an error.
#ifndef LRTTF_LOC
#define LRTTF_LOC "/usr/share/fonts/freetype/"
#endif
// There's a memory leak here somewhere. Fix later. (FIXED)
// The big culprit, as far as graphics sloth is concerned, is blit. Our
// multiple-display strategy didn't really work; ultimately, we may need to
// rework widgets.
#include "handler.cc"
#include "consolekbd.cc"
#include "color.cc"
#include "colorman.cc"
#include "display.cc"
#include "font.cc"
#include "widgets.cc"
#include "coordinate.cc"
#include "coord_tools.cc"
#include "stored_cores.cc"
#include "global_stats.cc"
#include "presenter.cc"
#include "arena_disp.cc"
#include "mover.cc"
#include "object.cc"
#include "robot.cc"
#include "collision.cc"
#include "ticktimer.cc"
#include "scanner.cc"
#include "missile.cc"
#include "blast.cc"
#include "mine.cc"
#include "configorder.h"
#include "game_balance.cc"
#include "cpu/corelogic.cc"
#include "cpu/compiler.cc"
#include <iostream>
#include <fstream>
#include <vector>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <getopt.h>
using namespace std;
// We should have a Display class that takes an Arena class and some aux info.
// Not yet, though, so we emulate the vector<stats> and all that stuff
// internally.
// Can we use AABBs for scanning? Probably not, but we can approximate it as
// a triangle. That's still n^2 though. We need a sophisticated space
// partitioning structure to beat n^2; it's dubious how much we'll gain since
// most fights are one-on-one anyway, but it'd also be very hard to restructure
// once the partitioning structure is in place.
// ------- These functions advance the game ------
// Advance the movement of robots, missiles, and mines, handling explosions and
// crashes. This basically invokes the correct collision detection routines.
void advance_movement(vector<robot> & robots, list<robot *> & live_robots,
list<missile> & missiles,
list<mine> & mines, blasts & explosions, double cycles_elapsed,
double absolute_time_at_start, int robot_radius,
int crash_range, int missile_hit_range, const coordinate &
arena_size) {
/*vector<robot>::iterator rp;
for (rp = robots.begin(); rp != robots.end(); ++rp) {
rp->set_time_units(cycles_elapsed);
rp->set_crash(false);
}*/
list<robot *>::iterator rp;
for (rp = live_robots.begin(); rp != live_robots.end(); ++rp) {
(*rp)->set_time_units(cycles_elapsed);
(*rp)->set_crash(false);
}
collider test_collision;
// First find out if any robots will crash into each other.
test_collision.set_track_limits(live_robots, robot_radius,
crash_range, arena_size);
// Then deal damage from missiles that hit.
test_collision.handle_missile_crashes(robots, live_robots,
cycles_elapsed, missiles, missile_hit_range,
arena_size, absolute_time_at_start, explosions);
// Also deal damage from mines.
test_collision.handle_mine_crashes(robots, live_robots, cycles_elapsed,
mines, explosions);
for (rp = live_robots.begin(); rp != live_robots.end(); ++rp) {
robot * cur_live = *rp; // Dereference so it isn't so ugly.
// First advance
cur_live->move(cur_live->time_units());
// Then register the crash if there was one.
// (Here we don't need to specify the time, but we do so.)
if (cur_live->crashed())
cur_live->register_crash(cur_live->time_units());
}
// Finally, advance missiles. (There's no need to "advance" mines).
for (list<missile>::iterator pos = missiles.begin();
pos != missiles.end(); ++pos)
pos->move(cycles_elapsed);
}
void advance_CPUs(core_storage & robot_cores, vector<robot> & robots,
list<Unit *> & live_robots, list<missile> & missiles,
list<mine> & mines, vector<set<robot *> > & comms_lookup,
const cmd_parse & aux_disassembler, matchid_t match_id,
const coordinate arena_size, const bool verbose,
const bool report_errors) {
vector<corelogic>::iterator core_iter, core_begin = robot_cores.
cores.begin();
core_iter = core_begin;
for (vector<robot>::iterator rp = robots.begin(); rp != robots.end();
++rp) {
run_error cpu_error(ERR_NOERR);
int cycles_permitted = rp->withdraw_CPU_cycles();
// Execute until we're done
while (cycles_permitted > 0 && !rp->dead()) {
if (!core_iter->execute_multiple(cycles_permitted, *rp,
live_robots, missiles, mines,
comms_lookup, 1, 1, arena_size,
false, cpu_error, cycles_permitted)) {
// We go here if there's an error.
// "Proper" ATR2 does a pause here, probably
// part of the same logic that breaks on debug.
// Let it be known what's acting up.
// Doesn't work if the error was at the last
// line, in which case get_IP()-1 will be -1
// (wraparound).
rp->update_error(cpu_error);
if (verbose || report_errors) {
int idx = rp - robots.begin();
cout << "Error " << (int)cpu_error
<< " in robot " << idx
<< "(" << rp->get_local_stats().
robot_name << ") (matchID " << match_id << "), at ";
int eff_line = core_iter->get_old_IP();
int source_line = robot_cores.
lookup_line_number(idx,
eff_line);
// If we can look up the real line,
// print it. Otherwise print the
// "effective" line (IP).
if (source_line != -1)
cout << "line " << source_line;
else cout << "effective line " <<
eff_line;
cout << ", disasm: "
<< aux_disassembler.disassemble
(core_iter->
get_instr_at_oldIP()) << endl;
}
}
}
++core_iter;
}
}
// --- These generate robot bodies, and give them a location in the arena ---
// Note that device_weighting must not be & and not be const; otherwise,
// completion will cause side effects. It really shouldn't be necessary to
// do completion (since we did it in core_store), but we still do it to be
// safe.
// ?? Do we really need "kills" and "deaths", now that we have local and global
// score stores? Now removed.
// Should this be just remove_extension, both, or neither?
string snip_extraneous(string filename) {
return(remove_path(remove_extension(filename)));
}
// Instantiate a robot.
// Insanity is -2 for no "insane missiles" setting, otherwise the insanity
// amount. (Insanity level -2 makes missiles stand almost still. Lower
// levels would make them go backwards, except that mover doesn't support it.
// Implement if amused.)
#define INSANITY_SANE -10
robot generate_robot(matchid_t matchid, const coordinate arena_size,
single_rand & initstate_randomizer, string fn,
int robot_radius, int & count, string message,
vector<int> device_weighting, int maxweight,
bool old_shields, int CPU_cycles_per_cycle,
global_round_info & this_robot_global_stats,
int insanity) {
double start_heading = initstate_randomizer.irand() % 256;
double start_throttle = 0;
game_balance limits;
// Default scan range.
int scanrange = limits.get_scanner_range(5);
int default_mines = limits.get_num_mines(0);
color_manager colorman;
double missile_speed;
if (insanity != INSANITY_SANE)
missile_speed = 100.1 + 50 * insanity;
else missile_speed = limits.get_missile_speed();
robot to_ret(matchid, count++, snip_extraneous(fn),
limits.get_min_throttle(), limits.get_max_throttle(),
limits.get_speed_multiplier(), limits.get_turn_rate(),
limits.get_accel_value(),
start_heading, start_throttle, robot_radius, scanrange,
16, default_mines, CPU_cycles_per_cycle,
limits.get_heat_shutdown(),
limits.get_heat_hysteresis(), limits.get_sonar_range(),
missile_speed, old_shields, &this_robot_global_stats);
// -1 because count starts at 1 and robot_color starts at 0. The robot
// itself is fully saturated and bright.
to_ret.set_colors(colorman.robot_color(count - 1, 1, 1),
colorman.shield_color(count - 1));
to_ret.set_message(message);
to_ret.set_desired_throttle(0);
to_ret.turret_heading = initstate_randomizer.irand() % 256;
// Add message and reweight strengths according to #config.
limits.complete_config_values(device_weighting);
// Check that we're not cheating. While we did this once already, in
// core_store, this is here just to be certain.
int sum = 0;
for (size_t counter = 0; counter < device_weighting.size(); ++counter)
sum += device_weighting[counter];
assert (sum <= maxweight);
to_ret.adjust_balance(device_weighting, limits);
return(to_ret);
}
robot generate_robot(matchid_t matchid, const coordinate arena_size,
single_rand & initstate_randomizer, string fn,
int robot_radius, int & count, const core_storage & cores,
int core_idx, int maxweight, bool old_shields,
int max_CPU_cycles_per_cycle,
global_round_info & this_robot_global_stats, int insanity) {
return(generate_robot(matchid, arena_size, initstate_randomizer,
fn, robot_radius, count,
cores.get_message(core_idx),
cores.get_weighting(core_idx), maxweight,
old_shields, cores.get_CPU_speed(core_idx,
max_CPU_cycles_per_cycle,
max_CPU_cycles_per_cycle),
this_robot_global_stats, insanity));
}
void place_robot_randomly(single_rand & initstate_randomizer,
const coordinate arena_size, vector<robot> & robots_so_far,
int index, int crash_range) {
if (index < 0 || index >= robots_so_far.size()) return;
coordinate rndpos;
bool colliding;
collider edge_tester;
do {
// Note, this actually returns a value on [0..arena_size],
// which is not what we want. The edge test removes the, err,
// edge cases though, so we can do it with impunity.
rndpos.x = initstate_randomizer.drand() * arena_size.x;
rndpos.y = initstate_randomizer.drand() * arena_size.y;
// Check if this new proposed location collides.
colliding = edge_tester.inside_wall(rndpos, crash_range,
arena_size);
// If it collided with the edge, this never gets executed.
// Nifty!
for (size_t counter = 0; counter < robots_so_far.size()
&& !colliding; ++counter) {
if (counter == index) continue;
// Margin of safety.
if (rndpos.distance(robots_so_far[counter].get_pos())
< crash_range * 1.2)
colliding = true;
}
} while (colliding);
robots_so_far[index].set_pos(rndpos);
}
// Others
// Don't include dead people here.
template<typename T> void alias(vector<robot> & input,
T & output) {
T toRet;
for (vector<robot>::iterator rp = input.begin(); rp != input.end();
++rp)
toRet.push_back(&*rp);
output = toRet;
}
template<typename T> void realias(T & to_check) {
typename T::iterator curpos = to_check.begin();
while (curpos != to_check.end())
if ((*curpos)->dead())
curpos = to_check.erase(curpos);
else ++curpos;
}
// Guess filenames to find out if they're incompletely specified, so as to
// permit things like ./ktr2 sduck sduck to resolve to sduck.at2 sduck.at2.
// The function returns those filenames that were accessible.
vector<string> guess_filename(string base_filename) {
ifstream inf(base_filename.c_str());
if (inf) return(vector<string>(1, base_filename));
vector<string> to_attempt, success;
to_attempt.push_back(base_filename + ".at2");
to_attempt.push_back(base_filename + ".AT2");
to_attempt.push_back(base_filename + ".atl");
to_attempt.push_back(base_filename + ".ATL");
for (int counter = 0; counter < to_attempt.size(); ++counter) {
ifstream test(to_attempt[counter].c_str());
compile_error err = CER_NOERR;
if (!test)
err = check_filename(to_attempt[counter], CER_NOERR);
// Add to the success list if it's anything but "file not found"
// to avoid bizarre cases where ./ktr2 sduck says "sduck not
// found" when sduck.at2 exists but can't be accessed.
if (err != CER_NOFOUND)
success.push_back(to_attempt[counter]);
}
return(success);
}
bool compile_robots(const vector<string> filenames, int maxdevices,
int maxlines, bool verbose, bool strict_compile,
core_storage & out) {
error_container retval(CER_NOERR);
vector<string> prospective;
for (size_t counter = 0; counter < filenames.size(); ++counter) {
ifstream inf(filenames[counter].c_str());
retval = out.insert_core(inf, filenames[counter], maxdevices,
maxlines, verbose, strict_compile);
// If it didn't open, try some of the completion rules. If more
// than one file is openable there, return "ambiguous name".
if (retval.error == CER_NOFOUND)
prospective = guess_filename(filenames[counter]);
// If the filename guesser didn't find anything at all, return
// "not found", skipping this loop. Otherwise, check if
// ambiguous. (This will cause odd behavior when someone does
// ./ktr2 sduck and sduck.at2 exists but has incorrect
// permissions, while sduck.atl exists and has correct
// permissions, but returning "ambiguous" might be the right
// thing to do after all.)
if (retval.error == CER_NOFOUND && prospective.size() >= 1) {
if (prospective.size() > 1)
retval = error_container(filenames[counter],
prospective.size(),
CER_F_AMBIGUOUS);
else {
ifstream infx(prospective[0].c_str());
retval = out.insert_core(infx, prospective[0],
maxdevices, maxlines, verbose,
strict_compile);
}
}
// DONE: Do something with the constraint problem (-1 here),
// so that it gives maxdevices if the error is that it cheats,
// max # lines if that's the problem, etc.
if (retval.error != CER_NOERR) {
cerr << filenames[counter] << "\tError! " << retval.
construct_error_message() << endl;
return(false);
}
}
return(true);
}
// Returns -1 if it's not being used by the game, otherwise char code.
int translate_SDL_keypress(int SDL_keypress) {
switch(SDL_keypress) {
case SDLK_ESCAPE:
case SDLK_q: return('Q');
case SDLK_SPACE:
case SDLK_BACKSPACE: return(' ');
case SDLK_a: return('A');
case SDLK_PLUS:
case SDLK_KP_PLUS: return('+');
case SDLK_MINUS:
case SDLK_KP_MINUS: return('-');
case SDLK_w: return('W');
case SDLK_d:
case SDLK_DOWN: return('D');
case SDLK_u:
case SDLK_UP: return('U');
default: return(-1);
}
}
// DONE: Rewrite this comment block.
// Runs a single round. The parameters are:
// print_outcomes: If true, prints the round outcome.
// report_errors: If true, reports errors when they happen.
// verbose: If true, show per-cycle debugging info.
// graphics: If true, access graphics structure and try to render each
// frame.
// show_true: If true and graphics is true, show scanning arcs when
// rendering.
// cycles_per_step: How many cycles to execute per CPU and collision
// detection step. Higher is faster, but may become inaccurate
// with weird collision errors. Only 1 has been checked.
// framerate: Display frame rate. Lock the display to at most this many
// frames per second.
// per_round_tinfo: Value of per-round tournament info to show to console
// after round is done, or -1 if we shouldn't show it.
// core_storage: Storage structure for robot cores (programs/CPUs).
// explosions: Structure to keep count of explosions and explosion
// locations, for rendering.
// filenames: Filenames of the robots.
// comms_lookup: Communications structure, for making comms transmission
// and reception take constant time instead of logarithmic or
// linear time. (Maybe YAGNI?)
// old_shields: If true, enable old shields (where robots are invulnerable
// with shields on)
// max_CPU_speed: Maximum CPU speed, in CPU cycles per game cycle.
// robot_radius: Radius of robots.
// crash_range: How close the robots can approach before they crash.
// missile_hit_radius: Maximum distance at which a missile can explode
// against a robot instead of passing by.
// missile_insanity: Insanity level for "insane missiles" (really quick
// missiles), or INSANITY_SANE for normal play.
// arena_size: Size (in game units) of the arena.
// maxweight: Maximum possible weight (points) allowed for the improvement
// of robot parts.
// maxcycles: How many cycles to run before declaring a tie.
// min_victory_margin: ???
// curmatch: Current match number (not ID).
// maxmatch: Maximum match number
// matchid: Match ID (random seed for replaying matches/examining odd
// results more closely).
// renderer: Pointer to the class that draws (renders) the graphics mode
// content.
// palette: Palette map between color names and actual colors.
// robot_disp_radius: Display robots (in graphics mode) as if they had a
// radius of this size. The variable is usually greater than the
// true robot radius so that it'll be easier to see the robots in
// graphics mode.
// buffer_thickness: How much space to give to the buffer -- area that
// can't be driven to, but is displayed so that large objects
// don't get smushed up against the edge of the screen or clipped.
// scan_lag: Duration, in msecs, to show the scanning arc after the actual
// scan was performed by the robot. Use this so users can see when
// scans happen without getting a horrible flickering effect.
// disassembler: Class used for disassembling, for instance when referring
// to errors.
// balancer: Balance class containing information about how much
// various hardware weights grant of benefits, maximum and minimum
// speeds, etc.
// stdfont: GUI rendering font.
// SDLc: SDL interface class.
// ckbd: Class for gathering console keypresses.
// global_robot_stats: Statistics class to be updated. It contains the
// score and stats of the robots thus far.
// time_passed: Will be set to the number of cycles that did pass
// before either a tie or victory was declared.
// kbd_trigger: Timer to make keyboard checking trigger at regular
// intervals and thus not slow down graphics-less execution too
// much.
bool run_round(bool print_outcomes, bool report_errors, bool verbose,
bool graphics, bool show_scanarcs, double cycles_per_step,
int framerate, int per_round_tinfo,
core_storage & core_store, blasts explosions,
const vector<string> filenames, vector<set<robot *> > &
comms_lookup, bool old_shields, int max_CPU_speed,
int robot_radius, int crash_range, int missile_hit_radius,
int missile_insanity, const coordinate arena_size,
int maxweight, int maxcycles, int min_victory_margin,
int curmatch, int maxmatch, matchid_t matchid, arena_disp *
renderer, const map<string, Color> & palette,
int robot_disp_radius, int buffer_thickness, double scan_lag,
const cmd_parse & disassembler, const game_balance & balancer,
Font & stdfont, SDLHandler & SDLc, ConsoleKeyboard * ckbd,
vector<global_round_info> &
global_robot_stats, int & time_passed,
ticktimer & kbd_trigger) {
size_t counter;
single_rand robot_state_rng(matchid, 0, RND_INIT);
// Reset the robot CPUs as we don't want anything to carry over.
for (vector<corelogic>::iterator pos = core_store.cores.begin(); pos !=
core_store.cores.end(); ++pos)
pos->reset_CPU();
// Set up the structures we're going to use.
list<missile> missiles;
list<mine> mines;
vector<robot> robots;
//vector<set<robot *> > comms_lookup(32767); // used for quick comms;
// the set is of robots at the channel given by the vector's index.
int UID_count = 1; // Not 0, since ATR2 counts from 1
// DONE: Is the weighting/maxweight part really needed? After all,
// compile checks it now.. But it might be useful in the case that
// we're going to optimize stuff, in which case we'll be altering
// the weight profile after compilation.
// Refactor in any case, so that it refers to core_store instead of
// through get_message/get_weighting.
for (counter = 0; counter < filenames.size(); ++counter) {
// Side effect: increments UID_count.
robots.push_back(generate_robot(matchid, arena_size,
robot_state_rng, filenames[counter],
robot_radius, UID_count, core_store,
counter, maxweight, old_shields,
max_CPU_speed, global_robot_stats
[counter],missile_insanity));
// Now set a random position for this bot.
place_robot_randomly(robot_state_rng, arena_size, robots,
counter, crash_range);
}
int statlet_offset = 0;
vector<robot>::iterator robot_pos;
list<robot *>::iterator lrobot_pos;
// Note that this must happen *AFTER* filling up the robots array,
// since push_back can alter pointers, and communications_channel
// uses pointers as reference. We should probably encapsulate this
// properly.
for (robot_pos = robots.begin(); robot_pos != robots.end();
++robot_pos)
// Register for comms
robot_pos->set_communications_channel(robot_pos->
get_communications_channel(), comms_lookup);
// Done initing robots.
// Set all as alive.
list<robot *> live_robots;
list<Unit *> live_units;
alias(robots, live_robots);
alias(robots, live_units);
double current_cycle = 0;
double timeslice = cycles_per_step;
double cycles_per_sec = timeslice * framerate;
// Set some cosmetic defaults.
double sonar_lag = scan_lag * 3; // Because sonar takes so long time
double radar_lag = scan_lag * 2.5; // Because the effect would be missed
// otherwise.
double x_separation = 0.01;
int time_last_death = -1;
bool finished = false, abort = false;
Color black = palette.find("black")->second,
midgrey = palette.find("midgrey")->second,
border_grey = palette.find("border_grey")->second,
lightgrey = palette.find("lightgrey")->second,
dkblue = palette.find("dkblue")->second,
cyan = palette.find("cyan")->second,
white(1, 1, 1),
yellow(0, 1, 1);
// BLUESKY: Replace this with some nifty double precision "virtual
// framerate", where if real fps < virtual framerate, the rest is
// handled through frameskip.
// Say that maxfps = 10, we want 11. Then the residue is (11 - 10)/10,
// and that's the frameskip, i.e skip one frame every ten frames.
double frameskip = 0; // how much to skip when he keeps asking to
// speed up after we're at max framerate.
int max_framerate = 60;
double frameskip_counter = 0;
double ccstep = 0;
// Independent of granularity, although
// it shouldn't be. The point here is that incrementing it
// is much quicker than fussing with current_cycle (which is
// double).
bool only_one_at_start = (++live_robots.begin() == live_robots.end());
string roundstats = "Match " + itos(curmatch) + "/" + itos(maxmatch) +
" (Match ID " + lltos(matchid) + ")";
if (&SDLc)
SDLc.set_title("K-Robots - " + roundstats, "K-Robots");
bool blocking = false;
// DEBUG
cout << roundstats << endl;
double cps = 0;
while (!finished) {
// Check whether this round is over. Should perhaps be
// done after we've run the turn, to avoid off-by-ones.
if (current_cycle >= maxcycles) finished = true;
// Check if there's only one, and if so, if he's kept himself
// alive longer than the "simultaneous destruction" threshold.
// (If there're nobody left, we're done.)
if (live_robots.empty())
finished = true;
// Don't do this test if there was only one robot when we
// called it; presumably, this is a test of that the robot
// doesn't blow itself up, and thus it should be checked until
// the time runs out.
if (time_last_death == -1) {
if (++live_robots.begin() == live_robots.end() &&
!only_one_at_start)
time_last_death = current_cycle;
} else
if (current_cycle - time_last_death >=
min_victory_margin)
finished = true;
if (finished) continue;
// Report debugging info if desired. Note that this is O(n)
// for mines and missiles.
if (verbose) {
cout << "[Main] Live robots left: " << live_robots.
size() << endl;
cout << "[Main] Missiles left: " << missiles.size() <<
endl;
cout << "[Main] Mines left: " << mines.size() << endl;
cout << "[Main] Cycle: " << current_cycle << endl;
}
// Advance explosions (display effect) and robots before we
// render anything.
if (graphics)
explosions.update_all(current_cycle);
// Advance ordnance state
advance_movement(robots, live_robots, missiles, mines,
explosions, timeslice, current_cycle,
robot_radius, crash_range, missile_hit_radius,
arena_size);
// Advance robot state and also see if someone died. If someone
// died, then we'll have to prune the live robot list later on,
// but there's no point in pruning it if not.
bool someone_died = false;
for (lrobot_pos = live_robots.begin(); lrobot_pos !=
live_robots.end(); ++lrobot_pos)
if (!(*lrobot_pos)->advance_internally(timeslice,
balancer, explosions, robots))
someone_died = true;
// Advance CPU
advance_CPUs(core_store, robots, live_units, missiles,
mines, comms_lookup, disassembler, matchid,
arena_size, verbose, report_errors);
// Now that everything has been advanced by a step, set the
// clocks to match.
for (lrobot_pos = live_robots.begin(); lrobot_pos !=
live_robots.end(); ++lrobot_pos) {
// DEBUG: Check that time is correct.
assert((*lrobot_pos)->get_time() == current_cycle);
// Then advance the clock.
(*lrobot_pos)->tick(timeslice);
}
// Remove dead robots, and advance the global clock.
// DONE: some sort of bool that tells us someone died. Maybe
// advance_internally returning false upon encountering
// someone who's dead.
if (someone_died) {
realias(live_robots);
realias(live_units);
}
current_cycle += timeslice;
// Register the dead.
// Not really needed, since bot stats do the stuff now.
// Refactor!
//register_dead(robots, time_of_death, current_cycle);
// Finally, render.
if (graphics && frameskip_counter > frameskip) {
if (frameskip_counter > framerate)
frameskip_counter = 0;
if (!renderer->render_all(x_separation, border_grey,
white, black, dkblue, midgrey,
lightgrey, lightgrey, white,
yellow, cyan, matchid,
current_cycle, maxcycles,
curmatch, maxmatch, robots,
missiles, mines, explosions,
robot_disp_radius, arena_size,
buffer_thickness,
cycles_per_sec, scan_lag,
sonar_lag, radar_lag,
show_scanarcs, statlet_offset)){
cerr << "Error: Can't draw display!" << endl;
// Return empty result?
} else
renderer->display();
SDLc.wait_for_frame_refresh(); // delay to fill fps
}
// Check the timer as to whether we should check for a keypress.
// Utopia would be to use a separate thread, but I don't know
// multithreading and I don't know if SDL is thread-safe anyway.
kbd_trigger.increment_count();
if (kbd_trigger.ready()) {
kbd_trigger.reset();
if (graphics && &SDLc) {
cps = cps * 0.8 + kbd_trigger.get_cps() * 0.2;
SDLc.set_title("K-Robots - " + roundstats +
" [" + dtos(cps, 1) +
" cycles/sec]",
"K-Robots");
}
int keypress = -1;
// This gets rid of MOUSEOVER and other irrelevant
// events. It slows down graphics a bit, but this isn't
// where the bottleneck lies.
while (graphics && (SDLc.get_next_event() != -1) &&
!blocking) {
int px;
switch(SDLc.get_last_event()) {
case SDL_VIDEORESIZE:
px = SDLc.get_event_resize().w;
renderer->resize_displays(px,
arena_size);
break;
case SDL_QUIT:
keypress = 'Q';
blocking = true;
break;
case SDL_KEYDOWN:
keypress =
translate_SDL_keypress(
SDLc.
get_event_keypress());
break;
}
}
// In case of console, the while above passes through
// and we go here. Doing an if(graphics) and then
// nesting everything another step to the right would
// squish things too far to the right. We pay by that
// if the user presses, say, "Q", and then A really
// quickly, only the A is registered.
if (!graphics && ckbd != NULL)
keypress = ckbd->getchar();
if (keypress != -1) keypress = toupper(keypress);
switch (keypress) {
case ' ':
case '\b':
finished = true;
break;
case 'Q':
case '\e':
finished = true;
abort = true;
break;
case 'W':
print_outcomes = !print_outcomes;
break;
case 'G':
// Toggle display of graphics.
// Impossible if we're in text mode,
// since SDL, etc, haven't been
// allocated.
// Also impossible in SDL because we
// can't hide the main window. So it
// does nothing for now.
break;
case 'A':
show_scanarcs = !show_scanarcs;
break;
case '+':
// Game runs slower. But only in GFX.
if (frameskip > 0) frameskip --;
else if (framerate > 1)
SDLc.set_framerate(--framerate);
break;
case '-':
// Game runs faster. But only in GFX.
if (framerate < max_framerate)
SDLc.set_framerate(++framerate);
else frameskip ++;
break;
case 'U':
if (statlet_offset > 0)
--statlet_offset;
break;
case 'D':
if (renderer->can_scroll_statlets(
statlet_offset
+1, robots.
size()))
++statlet_offset;
break;
}
}
if (graphics)
frameskip_counter += timeslice;
}
time_passed = current_cycle;
// Okay, the round has been played. If there's only one left standing,
// he's the victor, so increment his victory count. Also, increment the
// rounds counter for all, and merge the local stats into the global
// stats.
list<robot *>::const_iterator vpos = live_robots.begin();
vpos++;
bool only_one_survivor = (vpos == live_robots.end() &&
!live_robots.empty());
for (counter = 0; counter < robots.size(); ++counter) {
robots[counter].record_end_stats(true, maxcycles);
if (only_one_survivor && !robots[counter].dead())
robots[counter].record_victory();
global_robot_stats[counter].add_information(robots[counter].
get_local_stats(), false);
}
// All done, output local stats.
if (print_outcomes) {
presenter present;
cout << "Match " << curmatch << "/" << maxmatch << " (Match ID "
<< matchid << ") results:";