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