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