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