Add (a rather sizable chunk of) code for determining the possible best and worst...
[ccbs] / bigscreen / groupscreen.cpp
1 #include <algorithm>
2
3 #include "groupscreen.h"
4 #include "fetch_group.h"
5 #include "fetch_max_score_for_song.h"
6 #include "fetch_max_score_for_player.h"
7 #include "fonts.h"
8
9 GroupScreen::GroupScreen(pqxx::connection &conn, unsigned tournament, unsigned round, unsigned parallel)
10         : tournament(tournament), round(round), parallel(parallel), scores_changed(conn, "scores"), conn(conn), valid(false)
11 {
12 }
13
14 GroupScreen::~GroupScreen()
15 {
16 }
17
18 bool GroupScreen::check_invalidated()
19 {
20         // we might want to do this slightly more sophisticated later, but for now this will do
21         return !valid || scores_changed.get_flag();
22 }
23
24 void GroupScreen::draw(unsigned char *buf)
25 {
26         scores_changed.reset_flag();
27
28         Group group;
29         conn.perform(FetchGroup(tournament, round, parallel, &group));
30
31         memset(buf, 0, 800 * 600 * 4);
32
33         // main heading
34         char heading[64];
35         if (parallel == 0) {
36                 sprintf(heading, "Round %u", round);
37         } else {
38                 sprintf(heading, "Round %u, Group %u", round, parallel);
39         }
40
41         {
42                 unsigned width = my_draw_text(heading, NULL, 48.0);
43                 my_draw_text(heading, buf, 48.0, 800/2 - width/2, 60);
44         }
45         
46         // Find out how wide each column has to be. First try unlimited width (ie.
47         // long titles for everything); if that gets too long, try again with short
48         // titles for chosen songs.
49         unsigned width[16], num_scores;
50         unsigned max_num_width = my_draw_text("8888", NULL, 22.0);
51         unsigned mode;
52         for (mode = 0; mode < 2; ++mode) {
53                 for (unsigned i = 0; i < 16; ++i)
54                         width[i] = 0;
55
56                 for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
57                         unsigned col = 1;
58                         width[0] = std::max(width[0], my_draw_text(i->nick, NULL, 18.0));
59
60                         for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j, ++col) {
61                                 if (j->chosen) {
62                                         width[col] = std::max(width[col], my_draw_text((mode == 0) ? j->song.title : j->song.short_title, NULL, 12.0) + 
63                                                         max_num_width + 10);
64                                 } else {                
65                                         width[col] = std::max(width[col], my_draw_text(j->song.short_title, NULL, 12.0));
66                                         width[col] = std::max(width[col], max_num_width);
67                                 }
68                         }
69                 }
70
71                 num_scores = group.players[0].scores.size();
72
73                 width[num_scores + 1] = std::max(my_draw_text("Total", NULL, 12.0), max_num_width);
74                 width[num_scores + 2] = my_draw_text("Rank", NULL, 12.0);
75
76                 // if we're at long titles and that works, don't try the short ones
77                 if (mode == 0) {
78                         unsigned sumwidth = 0;
79                         for (unsigned i = 0; i <= num_scores + 2; ++i)
80                                 sumwidth += width[i] + 20;
81                         
82                         if (sumwidth < 800)
83                                 break;
84                 }
85         }
86
87         // make column headings from the first player's songs
88         unsigned col = 1;
89         unsigned x = 40 + width[0];
90         for (std::vector<Score>::const_iterator i = group.players[0].scores.begin(); i != group.players[0].scores.end(); ++i, ++col) {
91                 if (!i->chosen) {
92                         unsigned this_width = my_draw_text(i->song.short_title, NULL, 12.0);
93                         my_draw_text(i->song.short_title, buf, 12.0, x + width[col] / 2 - this_width / 2, 100);
94                 }
95                 x += width[col] + 20;
96         }
97
98         my_draw_text("Total", buf, 12.0, x + width[num_scores + 1] / 2 - my_draw_text("Total", NULL, 12.0) / 2, 100);
99         x += width[num_scores + 1] + 20;
100         my_draw_text("Rank", buf, 12.0, x + width[num_scores + 2] / 2 - my_draw_text("Rank", NULL, 12.0) / 2, 100);
101         
102         // show all the players and the scores
103         unsigned y = 140;
104         for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
105                 my_draw_text(i->nick, buf, 18.0, 20, y);
106
107                 unsigned x = 40 + width[0];
108
109                 unsigned col = 1;
110                 for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j, ++col) {
111                         char text[16];
112                         sprintf(text, "%u", j->score);
113         
114                         unsigned this_width = my_draw_text(text, NULL, 22.0);
115                         if (j->chosen) {
116                                 if (j->score != -1) {
117                                         my_draw_text(text, buf, 22.0, x + max_num_width - this_width, y);
118                                 }
119                                 my_draw_text((mode == 0) ? j->song.title : j->song.short_title, buf, 12.0, x + max_num_width + 10, y);
120                         } else {
121                                 if (j->score != -1) {
122                                         my_draw_text(text, buf, 22.0, x + width[col] / 2 - this_width / 2, y);
123                                 }
124                         }
125                         x += width[col] + 20;
126                 }
127
128                 // draw total
129                 {
130                         char text[16];
131                         sprintf(text, "%u", i->total);
132                         
133                         unsigned this_width = my_draw_text(text, NULL, 22.0);
134                         my_draw_text(text, buf, 22.0, x + width[num_scores + 1] / 2 - this_width / 2, y);
135                         x += width[num_scores + 1] + 20;
136                 }
137
138                 y += 40;
139         }
140         
141         /*
142          * Approximate (but probably working quite well in practice) heuristic
143          * for finding the min and max rank of a player works as follows:
144          *
145          * First of all, find out, for each player in the group, what the
146          * maximum remaining score possibly can be (the minimum score is of
147          * course identical to the player's current total). For a random song,
148          * this is of course 1000 * (maximum feet rating) (but of course, that
149          * depends on whether we can play single or double! for now, assume
150          * double is okay, but this logic will be deferred to FetchMaxScore
151          * anyhow); for a random song, we simply pick the highest-ranking song
152          * we can find, EXCEPT those the player has chosen earlier AND the
153          * random songs this round, AND all random songs from elimination rounds
154          * (ie. rounds with only one group). (Phew!) This doesn't solve problems
155          * we'd face with more than one chosen song, but it should be good enough.
156          *
157          * After we've found the max and min scores for all players, it's a simple
158          * matter of sorting; the best attainable rank for player X is obtained if 
159          * X gets max score and all others get min score, the worst attainable rank
160          * is obtained if X gets min score and all others get max score.
161          *
162          * This is a bit SQL-heavy, but heck...
163          */
164         std::vector<unsigned> max_score, min_score;
165         for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
166                 unsigned min_score_tp = 0, max_score_tp = 0;
167                 for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j, ++col) {
168                         if (j->score != -1) {
169                                 // already given
170                                 min_score_tp += j->score;
171                                 max_score_tp += j->score;
172                         } else {
173                                 unsigned max_score_this_song;
174                                 if (j->song.id != -1) {
175                                         // random song, or we know what song the player picked
176                                         conn.perform(FetchMaxScoreForSong(tournament, j->song.id, &max_score_this_song));
177                                 } else {
178                                         conn.perform(FetchMaxScoreForPlayer(tournament, i->id, round, &max_score_this_song));
179                                 }
180                                 max_score_tp += max_score_this_song;
181                         }
182                 }
183                 max_score.push_back(max_score_tp);
184                 min_score.push_back(min_score_tp);
185         }
186
187         // now finally find min and max rank, and draw it all
188         y = 140;
189         for (unsigned i = 0; i < group.players.size(); ++i) {
190                 unsigned best_rank = 1, worst_rank = 1;
191                 for (unsigned j = 0; j < group.players.size(); ++j) {
192                         if (i == j)
193                                 continue;
194
195                         if (max_score[i] < min_score[j])
196                                 ++best_rank;
197                         if (min_score[i] <= max_score[j])
198                                 ++worst_rank;
199                 }
200
201                 char text[16];
202                 if (best_rank == worst_rank)
203                         std::sprintf(text, "%u", best_rank);
204                 else
205                         std::sprintf(text, "%u-%u", best_rank, worst_rank);
206                 
207                 unsigned this_width = my_draw_text(text, NULL, 22.0);
208                 my_draw_text(text, buf, 22.0, x + width[num_scores + 2] / 2 - this_width / 2, y);
209
210                 y += 40;
211         }
212                 
213         valid = true;
214 }
215