]> git.sesse.net Git - ccbs/blob - bigscreen/groupscreen.cpp
Various changes and fixes for CC1.
[ccbs] / bigscreen / groupscreen.cpp
1 #include <cstdio>
2 #include <algorithm>
3 #include <map>
4
5 #include "resolution.h"
6 #include "groupscreen.h"
7 #include "fetch_group.h"
8 #include "fetch_max_score_for_songs.h"
9 #include "fetch_max_score_for_players.h"
10 #include "fetch_needs_update.h"
11 #include "fetch_highscore.h"
12 #include "fonts.h"
13
14 GroupScreen::GroupScreen(pqxx::connection &conn, unsigned tournament, unsigned round, unsigned parallel, unsigned machine, unsigned num_machines, unsigned players_per_machine)
15         : tournament(tournament), round(round), parallel(parallel), machine(machine), num_machines(num_machines), players_per_machine(players_per_machine), scores_changed(conn, "scores"), conn(conn), valid(false)
16 {
17 }
18
19 GroupScreen::~GroupScreen()
20 {
21 }
22
23 bool GroupScreen::check_invalidated()
24 {
25         if (!valid)
26                 return true;
27         if (!scores_changed.get_flag())
28                 return false;
29
30         bool needs_update;
31         conn.perform(FetchNeedsUpdate(last_updated, tournament, round, parallel, &needs_update));
32
33         if (!needs_update)
34                 scores_changed.reset_flag();
35         
36         return needs_update;
37 }
38
39 unsigned GroupScreen::get_show_players(const Group &group)
40 {
41         unsigned num_players_this_machine = (group.players.size() + num_machines - machine - 1) / num_machines;
42         return std::min(num_players_this_machine, 9U);
43 }
44
45 void GroupScreen::draw_main_heading(std::vector<TextDefer> &td)
46 {
47         char heading[64];
48         if (num_machines == 1) {
49                 if (parallel == 0) {
50                         std::sprintf(heading, "Round %u", round);
51                 } else {
52                         std::sprintf(heading, "Round %u, Group %u", round, parallel);
53                 }
54         } else {
55                 if (parallel == 0) {
56                         std::sprintf(heading, "Round %u, Machine %u", round, machine + 1);
57                 } else {
58                         std::sprintf(heading, "Round %u, Group %u, Machine %u", round, parallel, machine + 1);
59                 }
60         }
61
62         unsigned width = my_draw_text(heading, NULL, 40.0);
63         my_draw_text_deferred(td, heading, 40.0, LOGICAL_SCREEN_WIDTH/2 - width/2, 60);
64 }
65
66 // make column headings from the first player's songs
67 void GroupScreen::draw_column_headings(std::vector<TextDefer> &td, const Group &group, const std::vector<unsigned> &colwidth)
68 {
69         unsigned num_scores = group.players[0].scores.size();
70
71         unsigned col = 1;
72         unsigned x = 40 + colwidth[0];
73         for (std::vector<Score>::const_iterator i = group.players[0].scores.begin(); i != group.players[0].scores.end(); ++i, ++col) {
74                 if (!i->chosen) {
75                         unsigned this_width = my_draw_text(i->song.short_title, NULL, 12.0);
76                         my_draw_text_deferred(td, i->song.short_title, 12.0, x + colwidth[col] / 2 - this_width / 2, 100);
77                 }
78                 x += colwidth[col] + 20;
79         }
80
81         if (num_scores > 1) {
82                 my_draw_text_deferred(td, "Total", 12.0, x + colwidth[num_scores + 1] / 2 - my_draw_text("Total", NULL, 12.0) / 2, 100);
83                 x += colwidth[num_scores + 1] + 20;
84         }
85         my_draw_text_deferred(td, "Rank", 12.0, x + colwidth[num_scores + 2] / 2 - my_draw_text("Rank", NULL, 12.0) / 2, 100);
86 }       
87         
88 // show all the players and the scores
89 void GroupScreen::draw_scores(std::vector<TextDefer> &td, const Group &group, const std::vector<unsigned> &colwidth)
90 {
91         unsigned max_num_width = my_draw_text("8888", NULL, 22.0);
92         unsigned num_scores = group.players[0].scores.size();
93         unsigned show_players = get_show_players(group);
94         unsigned y = (show_players <= 7) ? 140 : (140 - (show_players - 7) * 5);
95         
96         unsigned row = 0, m = 0, x;
97         for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end() && row < 9; ++i) {
98                 if (m++ % num_machines != machine)
99                         continue;
100
101                 my_draw_text_deferred(td, i->nick, 18.0, 20, y);
102
103                 x = 40 + colwidth[0];
104
105                 unsigned col = 1;
106                 for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j, ++col) {
107                         char text[16] = "";
108                         if (j->score != -1) {
109                                 std::sprintf(text, "%u", j->score);
110                         }
111         
112                         unsigned this_width = my_draw_text(text, NULL, 22.0);
113                         if (j->chosen) {
114                                 my_draw_text_deferred(td, text, 22.0, x + max_num_width - this_width, y);
115
116                                 // draw the long name if we can, otherwise use the short one
117                                 if (my_draw_text(j->song.title, NULL, 12.0) > (colwidth[col] - 10 - max_num_width)) {
118                                         my_draw_text_deferred(td, j->song.short_title, 12.0, x + max_num_width + 10, y);
119                                 } else {
120                                         my_draw_text_deferred(td, j->song.title, 12.0, x + max_num_width + 10, y);
121                                 }
122                         } else {
123                                 my_draw_text_deferred(td, text, 22.0, x + colwidth[col] / 2 - this_width / 2, y);
124                         }
125                         x += colwidth[col] + 20;
126                 }
127
128                 // draw total
129                 if (num_scores > 1) {
130                         char text[16];
131                         std::sprintf(text, "%u", i->total);
132                         
133                         unsigned this_width = my_draw_text(text, NULL, 22.0);
134                         my_draw_text_deferred(td, text, 22.0, x + colwidth[num_scores + 1] / 2 - this_width / 2, y);
135                         x += colwidth[num_scores + 1] + 20;
136                 }
137
138                 if (show_players > 7)
139                         y += 40 - (show_players - 7) * 4;
140                 else 
141                         y += 40;
142                 ++row;
143         }
144 }       
145
146 /*
147  * Find out how wide each column has to be. First try unlimited width (ie.
148  * long titles for everything); if that gets too long, try again with short
149  * titles for chosen songs.
150  */
151 void GroupScreen::find_column_widths(const Group &group, std::vector<unsigned> &colwidth)
152 {
153         unsigned num_scores;
154         unsigned max_num_width = my_draw_text("8888", NULL, 22.0);
155         unsigned sumcolwidth;
156         
157         for (unsigned mode = 0; mode < 2; ++mode) {
158                 for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
159                         unsigned col = 1;
160                         
161                         if (colwidth.size() == 0)
162                                 colwidth.push_back(0);
163                         
164                         colwidth[0] = std::max(colwidth[0], my_draw_text(i->nick, NULL, 18.0));
165
166                         for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j, ++col) {
167                                 if (colwidth.size() < col+1)
168                                         colwidth.push_back(0);
169                                         
170                                 if (j->chosen) {
171                                         colwidth[col] = std::max(colwidth[col], my_draw_text((mode == 0) ? j->song.title : j->song.short_title, NULL, 12.0) + 
172                                                         max_num_width + 10);
173                                 } else {                
174                                         colwidth[col] = std::max(colwidth[col], my_draw_text(j->song.short_title, NULL, 12.0));
175                                         colwidth[col] = std::max(colwidth[col], max_num_width);
176                                 }
177                         }
178                 }
179
180                 num_scores = group.players[0].scores.size();
181
182                 if (colwidth.size() < num_scores + 2) {
183                         colwidth.push_back(0);
184                         colwidth.push_back(0);
185                 }
186         
187                 if (num_scores > 1) {
188                         colwidth[num_scores + 1] = std::max(my_draw_text("Total", NULL, 12.0), max_num_width);
189                 }
190                 colwidth[num_scores + 2] = my_draw_text("Rank", NULL, 12.0);
191
192                 // if we're at long titles and that works, don't try the short ones
193                 sumcolwidth = 0;
194                         
195                 for (unsigned i = 0; i <= num_scores + 2; ++i)
196                         sumcolwidth += colwidth[i] + 20;
197                 
198                 if (sumcolwidth < 780)
199                         break;
200
201                 if (mode == 0) {
202                         colwidth.erase(colwidth.begin(), colwidth.end());
203                 }
204         }
205
206         /* 
207          * If we have space to go, distribute as much as we can to the chosen song column, so we won't have
208          * total and rank jumping around.
209          */
210         if (sumcolwidth < 780) {
211                 int first_chosen_col = -1;
212                 unsigned col = 1;
213
214                 for (std::vector<Score>::const_iterator i = group.players[0].scores.begin(); i != group.players[0].scores.end(); ++i, ++col) {
215                         if (i->chosen) {
216                                 first_chosen_col = col;
217                                 break;
218                         }
219                 }
220
221                 if (first_chosen_col != -1) {
222                         colwidth[first_chosen_col] += 780 - sumcolwidth;
223                 }
224         }
225 }
226
227 /* Find the first player with the fewest songs played and part of this machine. */
228 const Player *GroupScreen::get_next_player(const Group &group)
229 {
230         unsigned min_played_songs = 9999;
231         const Player *next_player = NULL;
232         unsigned m = 0;
233         for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
234                 unsigned this_played = 0;
235                 for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j) {
236                         if (j->score != -1)
237                                 ++this_played;
238                 }
239
240                 if ((m++ % num_machines == machine) && this_played < min_played_songs) {
241                         min_played_songs = this_played;
242                         next_player = &(*i);
243                 }
244         }
245
246         return next_player;
247 }
248
249 /*
250  * At the bottom, for a single player, is "who's playing, what will he/she be
251  * playing, and optionally, how much to lead/win and how much to secure
252  * qualification" (the last one only in the final round). We assume playing is
253  * done in a modified zigzag; all the random songs are played first in
254  * zigzag/wrapping order (player 1 song 1, player 2 song 2, player 3 song 3,
255  * player 1 song 2, player 2 song 3, player 3 song 1, etc... assuming three
256  * songs and three players) and then all the chosen songs are played (we assume
257  * only one chosen song).
258  *
259  * The lines are as follows:
260  *
261  * <player>
262  * <song>
263  * High score: <hs> by <hsplayer> at <hsevent>
264  * Needs to lead: <leadscore>
265  * Needs to secure qualification: <qualscore>
266  * Needs to win group: <winscore>
267  */
268 void GroupScreen::draw_next_up_single(unsigned char *buf, const Group &group,
269         std::map<unsigned, unsigned> &song_scores, std::map<unsigned, unsigned> &player_scores,
270         const std::vector<unsigned> &max_score, const std::vector<unsigned> &min_score)
271 {
272         unsigned num_scores = group.players[0].scores.size();
273         
274         // Find out how many random songs there are (equal for all players).
275         unsigned num_random_songs = 0;
276         for (std::vector<Score>::const_iterator i = group.players[0].scores.begin(); i != group.players[0].scores.end(); ++i) {
277                 if (!i->chosen)
278                         ++num_random_songs;
279         }
280
281         /* 
282          * Find out which player is next, and what song he she is supposed to play. First
283          * try random songs.
284          */
285         const Player *next_player = get_next_player(group);
286         const Score *next_song = NULL;
287
288         for (unsigned i = 0; i < num_random_songs; ++i) {
289                 unsigned j = (i + next_player->position - 1) % num_random_songs;
290                 if (next_player->scores[j].score == -1) {
291                         next_song = &(next_player->scores[j]);
292                         break;
293                 }
294         }
295
296         // then all songs, if that didn't work out (slightly icky, but hey)
297         if (next_song == NULL) {
298                 for (unsigned i = 0; i < num_scores; ++i) {
299                         unsigned j = (i + next_player->position) % num_scores;
300                         if (next_player->scores[j].score == -1) {
301                                 next_song = &(next_player->scores[j]);
302                                 break;
303                         }
304                 }
305         }
306
307         if (next_song != NULL) {
308                 // find out how many songs we've played in all
309                 unsigned num_played = 0;
310                 for (unsigned i = 0; i < num_scores; ++i) {
311                         if (next_player->scores[i].score != -1) {
312                                 ++num_played;
313                         }
314                 }
315         
316                 bool last_song = (num_played == num_scores - 1);
317                         
318                 draw_next_up_player(buf, group, *next_player, *next_song, last_song, song_scores, player_scores, max_score, min_score);
319         }
320 }
321
322 /*
323  * Some tournaments allow versus play in the initial rounds to save time; this is
324  * of course for random songs only. In this case, the scheme from draw_next_up_single()
325  * is somewhat changed, as we zig-zag across pairs instead of players. (If there's a
326  * stray person left in the group, that player plays the song by him-/herself as in
327  * a usual single tournament.
328  */
329 void GroupScreen::draw_next_up_versus(unsigned char *buf, const Group &group,
330         std::map<unsigned, unsigned> &song_scores, std::map<unsigned, unsigned> &player_scores,
331         const std::vector<unsigned> &max_score, const std::vector<unsigned> &min_score)
332 {
333         // Find out how many random songs there are (equal for all players).
334         unsigned num_random_songs = 0;
335         for (std::vector<Score>::const_iterator i = group.players[0].scores.begin(); i != group.players[0].scores.end(); ++i) {
336                 if (!i->chosen)
337                         ++num_random_songs;
338         }
339
340         // Find the next player and what song he/she is supposed to play, if any.
341         const Player *next_player = get_next_player(group);
342         const Score *next_song = NULL;
343         unsigned song_num;
344
345         for (unsigned i = 0; i < num_random_songs; ++i) {
346                 unsigned j = (i + (next_player->position - 1) / 2) % num_random_songs;
347                 if (next_player->scores[j].score == -1) {
348                         next_song = &(next_player->scores[j]);
349                         song_num = j;
350                         break;
351                 }
352         }
353         
354         /*
355          * If there's no match, we're on the chosen songs (or done),
356          * so just delegate to draw_up_single().
357          */
358         if (next_song == NULL) {
359                 draw_next_up_single(buf, group, song_scores, player_scores, max_score, min_score);
360                 return;
361         }
362         
363         /*
364          * Look for a player with the same amount of random songs played _and_ missing
365          * the same song.
366          */ 
367         unsigned num_songs_played = 0;
368         for (unsigned i = 0; i < num_random_songs; ++i) {
369                 if (next_player->scores[i].score != -1) {
370                         ++num_songs_played;
371                 }
372         }
373         
374         unsigned m = 0;
375         const Player *other_player = NULL;
376         for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
377                 if ((m++ % num_machines != machine))
378                         continue;
379                 if (i->id == next_player->id)
380                         continue;
381                 
382                 unsigned this_songs_played = 0;
383                 for (unsigned j = 0; j < num_random_songs; ++j) {
384                         if (i->scores[j].score != -1) {
385                                 ++this_songs_played;
386                         }
387                 }
388
389                 if (this_songs_played != num_songs_played)
390                         continue;
391
392                 if (i->scores[song_num].score == -1) {
393                         other_player = &(*i);
394                         break;
395                 }       
396         }
397
398         // If we didn't find another player, just draw the one we have as usual.
399         if (other_player == NULL) {
400                 draw_next_up_player(buf, group, *next_player, *next_song, false,
401                         song_scores, player_scores, max_score, min_score);
402                 return;
403         }
404         
405         // OK, we have two players. Draw their nicks and the scores
406         widestring text = widestring("Next players: ") + next_player->nick + widestring(" and ") + other_player->nick;
407         unsigned this_width = my_draw_text(text, NULL, 24.0);
408         my_draw_text(text, buf, 24.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, 420);
409
410         if (next_song->song.id != -1) {
411                 this_width = my_draw_text(next_song->song.title, NULL, 20.0);
412                 my_draw_text(next_song->song.title, buf, 20.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, 457);
413
414                 Highscore hs;
415                 conn.perform(FetchHighscore(next_song->song.id, &hs));
416
417                 if (hs.score != -1) {
418                         text = widestring("High score: ") + widestring(pqxx::to_string(hs.score)) +
419                                 widestring(", by ") + hs.nick + widestring(" in ") + hs.tournament_name;
420                         this_width = my_draw_text(text, NULL, 16.0);
421                         my_draw_text(text, buf, 16.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, 487);
422                 }
423         }
424 }
425
426 void GroupScreen::draw_next_up_player(unsigned char *buf, const Group &group, const Player &player, const Score &song, bool last_song,
427         std::map<unsigned, unsigned> &song_scores, std::map<unsigned, unsigned> &player_scores,
428         const std::vector<unsigned> &max_score, const std::vector<unsigned> &min_score)
429 {
430         widestring text = widestring("Next player: ") + player.nick;
431         unsigned this_width = my_draw_text(text, NULL, 24.0);
432         my_draw_text(text, buf, 24.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, 420);
433
434         if (song.song.id != -1) {
435                 this_width = my_draw_text(song.song.title, NULL, 20.0);
436                 my_draw_text(song.song.title, buf, 20.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, 457);
437
438                 Highscore hs;
439                 conn.perform(FetchHighscore(song.song.id, &hs));
440
441                 if (hs.score != -1) {
442                         text = widestring("High score: ") + widestring(pqxx::to_string(hs.score)) +
443                                 widestring(", by ") + hs.nick + widestring(" in ") + hs.tournament_name;
444                         this_width = my_draw_text(text, NULL, 16.0);
445                         my_draw_text(text, buf, 16.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, 487);
446                 }
447         }
448
449         // only show lead/win/qualify for the last song
450         if (last_song) {
451                 /*
452                  * Find out how much we need to lead, how much we need to be guaranteed
453                  * to win the group, and how much we need to secure qualification.
454                  */
455
456                 // find the best score we can get
457                 unsigned max_score_this_song;
458                 if (song.song.id != -1) {
459                         // random song, or we know what song the player picked
460                         max_score_this_song = song_scores[song.song.id];
461                 } else {
462                         max_score_this_song = player_scores[player.id];
463                 }
464
465                 unsigned y = 520;
466
467                 // see what score this player must beat to lead
468                 unsigned lead_beat = 0, win_beat = 0;
469                 for (unsigned i = 0; i < group.players.size(); ++i) {
470                         if (group.players[i].id == player.id)
471                                 continue;
472
473                         lead_beat = std::max(lead_beat, group.players[i].total);
474                 }
475
476                 // find the best max score among the others
477                 for (unsigned i = 0; i < group.players.size(); ++i) {
478                         if (group.players[i].id == player.id)
479                                 continue;
480
481                         win_beat = std::max(win_beat, max_score[i]);
482                 }
483
484                 /*
485                  * There's a somewhat subtle point here. Normally, what a player would be interested in
486                  * with regard to qualification would be a set of three values:
487                  *
488                  * 1. How much is the absolute minimum required to qualify, given that all others
489                  *    fail?
490                  * 2. How much will give a reasonable chance of qualifying, given the expected performance
491                  *    of all the others?
492                  * 3. How much will be enough to secure qualification, no matter what?
493                  *
494                  * Given perfect guessing, #2 would be "how much is needed to qualify"; however, it is
495                  * completely impossible to give an exact value for that, and we're not into the guessing
496                  * games. :-) #1 is often so low it's completely unrealistic (ie. far enough from #2 that
497                  * it's not interesting), but #3, the most conservative estimate, is often a good measure.
498                  * #3 is "how much is needed to _secure_ qualification", and that is usually what we
499                  * print out when it's possible.
500                  *
501                  * However, in a few situations, #1 and #3 will be the exact same value, from which it
502                  * follows (from the squeeze law, or just common sense :-) ) that #2 will be the same
503                  * value as #1 and #3. (This usually happens near or at the end of a group.) In that
504                  * case, we know the value we seek (ie. "how much is needed to qualify"), so we drop
505                  * the word "secure" and just print it as-is.
506                  *
507                  * To find #1 and #3, we sort and pick out the values we need to beat in the best and
508                  * the worst case.
509                  */
510                 int qualify_beat_worst_case = -1, qualify_beat_best_case = -1;
511
512                 if (group.num_qualifying > 0) {
513                         std::vector<unsigned> tmp;
514
515                         for (unsigned i = 0; i < group.players.size(); ++i) {
516                                 if (group.players[i].id == player.id)
517                                         continue;
518                                 tmp.push_back(max_score[i]);
519                         }
520                         std::sort(tmp.begin(), tmp.end());
521                         qualify_beat_worst_case = tmp[tmp.size() - group.num_qualifying];
522
523                         std::vector<unsigned> tmp2;
524                         for (unsigned i = 0; i < group.players.size(); ++i) {
525                                 if (group.players[i].id == player.id)
526                                         continue;
527                                 tmp2.push_back(min_score[i]);
528                         }
529
530                         std::sort(tmp2.begin(), tmp2.end());
531                         qualify_beat_best_case = tmp2[tmp2.size() - group.num_qualifying];
532                 }
533
534                 // print out the lines we can attain
535                 if (player.total + max_score_this_song > lead_beat && (lead_beat != win_beat)) {
536                         int lead_need = std::max(lead_beat - player.total + 1, 0U);
537
538                         if (lead_need > 1) {
539                                 text = widestring("Needs to lead: ") + widestring(pqxx::to_string(lead_need));
540                                 this_width = my_draw_text(text, NULL, 18.0);
541                                 my_draw_text(text, buf, 18.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, y);
542
543                                 y += 30;
544                         }
545                 }
546
547                 if (player.total + max_score_this_song > win_beat) {
548                         int win_need = std::max(win_beat - player.total + 1, 0U);
549
550                         if (win_need > 0) {
551                                 text = widestring("Needs to win: ") + widestring(pqxx::to_string(win_need));
552
553                                 this_width = my_draw_text(text, NULL, 18.0);
554                                 my_draw_text(text, buf, 18.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, y);
555
556                                 y += 30;
557                         }
558                 }
559
560                 if (group.num_qualifying > 0 &&
561                     group.num_qualifying != group.players.size() &&
562                                 player.total + max_score_this_song > unsigned(qualify_beat_worst_case) &&
563                                 (unsigned(qualify_beat_worst_case) != win_beat)) {
564                         int qual_need = std::max(qualify_beat_worst_case - player.total + 1, 0U);
565
566                         if (qual_need > 0) {
567                                 if (qualify_beat_worst_case == qualify_beat_best_case) {
568                                         text = widestring("Needs to qualify: ") + widestring(pqxx::to_string(qual_need));
569                                 } else {
570                                         text = widestring("Needs to secure qualification: ") + widestring(pqxx::to_string(qual_need));
571                                 }
572
573                                 this_width = my_draw_text(text, NULL, 18.0);
574                                 my_draw_text(text, buf, 18.0, (LOGICAL_SCREEN_WIDTH/2) - this_width/2, y);
575
576                                 y += 30;
577                         }
578                 }
579         }
580 }
581
582 // some refactoring done, more should be
583 void GroupScreen::draw(unsigned char *buf, unsigned width, unsigned height)
584 {
585         std::vector<TextDefer> td;
586         
587         scores_changed.reset_flag();
588         set_screen_size(width, height);
589
590         /*
591          * We'll probably need some values from here later on (although not all), just fetch them
592          * all while we're at it.
593          */
594         std::map<unsigned, unsigned> song_scores, player_scores;
595         conn.perform(FetchMaxScoreForSongs(tournament, &song_scores));
596         conn.perform(FetchMaxScoreForPlayers(tournament, round, &player_scores));
597         
598         Group group;
599         conn.perform(FetchGroup(tournament, round, parallel, &group));
600         gettimeofday(&last_updated, NULL);
601
602         memset(buf, 0, width * height * 4);
603
604         std::vector<unsigned> colwidth;
605         
606         draw_main_heading(td);
607         find_column_widths(group, colwidth);
608         draw_column_headings(td, group, colwidth);
609         draw_scores(td, group, colwidth);
610         
611         unsigned num_scores = group.players[0].scores.size();
612
613         /*
614          * Approximate (but probably working quite well in practice) heuristic
615          * for finding the min and max rank of a player works as follows:
616          *
617          * First of all, find out, for each player in the group, what the
618          * maximum remaining score possibly can be (the minimum score is of
619          * course identical to the player's current total). For a random song,
620          * this is of course 1000 * (maximum feet rating) (but of course, that
621          * depends on whether we can play single or double! for now, assume
622          * double is okay, but this logic will be deferred to FetchMaxScore
623          * anyhow); for a random song, we simply pick the highest-ranking song
624          * we can find, EXCEPT those the player has chosen earlier AND the
625          * random songs this round, AND all random songs from elimination rounds
626          * (ie. rounds with only one group). (Phew!) This doesn't solve problems
627          * we'd face with more than one chosen song, but it should be good enough.
628          *
629          * After we've found the max and min scores for all players, it's a simple
630          * matter of sorting; the best attainable rank for player X is obtained if 
631          * X gets max score and all others get min score, the worst attainable rank
632          * is obtained if X gets min score and all others get max score.
633          */
634         std::vector<unsigned> max_score, min_score;
635         for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
636                 unsigned min_score_tp = 0, max_score_tp = 0;
637                 for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j) {
638                         if (j->score != -1) {
639                                 // already given
640                                 min_score_tp += j->score;
641                                 max_score_tp += j->score;
642                         } else {
643                                 unsigned max_score_this_song;
644                                 if (j->song.id != -1) {
645                                         // random song, or we know what song the player picked
646                                         max_score_this_song = song_scores[j->song.id];
647                                 } else {
648                                         max_score_this_song = player_scores[i->id];
649                                 }
650                                 max_score_tp += max_score_this_song;
651                         }
652                 }
653                 max_score.push_back(max_score_tp);
654                 min_score.push_back(min_score_tp);
655         }
656
657         // now finally find min and max rank, and draw it all
658         unsigned show_players = get_show_players(group);
659         unsigned y = (show_players <= 7) ? 140 : (140 - (show_players - 7) * 5);
660         for (unsigned i = 0; i < group.players.size() && (i/num_machines) < show_players; ++i) {
661                 unsigned best_rank = 1, worst_rank = 1;
662                 for (unsigned j = 0; j < group.players.size(); ++j) {
663                         if (i == j)
664                                 continue;
665
666                         if (max_score[i] < min_score[j])
667                                 ++best_rank;
668                         if (min_score[i] <= max_score[j])
669                                 ++worst_rank;
670                 }
671
672                 char text[16];
673                 if (best_rank == worst_rank)
674                         std::sprintf(text, "%u", best_rank);
675                 else
676                         std::sprintf(text, "%u-%u", best_rank, worst_rank);
677                 
678                 if (i % num_machines != machine)
679                         continue;
680                 
681                 // find out where to place this
682                 unsigned x = 40 + colwidth[0];
683                 for (unsigned j = 1; j <= num_scores + 1; ++j)
684                         x += colwidth[j] + 20;
685
686                 // minor correction :-)
687                 if (num_scores <= 1)
688                         x -= 20;
689                 
690                 unsigned this_width = my_draw_text(text, NULL, 22.0);
691                 my_draw_text_deferred(td, text, 22.0, x + colwidth[num_scores + 2] / 2 - this_width / 2, y);
692
693                 if (show_players > 7)
694                         y += 40 - (show_players - 7) * 4;
695                 else 
696                         y += 40;
697         }
698
699         if (players_per_machine == 2)
700                 draw_next_up_versus(buf, group, song_scores, player_scores, max_score, min_score);
701         else 
702                 draw_next_up_single(buf, group, song_scores, player_scores, max_score, min_score);
703         
704         valid = true;
705         draw_all_deferred_text(buf, td, last_text);
706         last_text = td;
707 }
708
709 int GroupScreen::get_priority()
710 {
711         return 10;
712 }