+ if (!valid)
+ return true;
+ if (!scores_changed.get_flag())
+ return false;
+ scores_changed.reset_flag();
+
+ bool needs_update;
+ conn.perform(FetchNeedsUpdate(last_updated, tournament, round, parallel, &needs_update));
+
+ valid = !needs_update;
+ return needs_update;
+}
+
+unsigned GroupScreen::get_show_players(const Group &group)
+{
+ unsigned num_players_this_machine = (group.players.size() + num_machines - machine - 1) / num_machines;
+ return std::min(num_players_this_machine, 9U);
+}
+
+void GroupScreen::draw_main_heading(std::vector<TextDefer> &td)
+{
+ char heading[64];
+ if (num_machines == 1) {
+ if (parallel == 0) {
+ std::sprintf(heading, "Round %u", round);
+ } else {
+ std::sprintf(heading, "Round %u, Group %u", round, parallel);
+ }
+ } else {
+ if (parallel == 0) {
+ std::sprintf(heading, "Round %u, Machine %u", round, machine + 1);
+ } else {
+ std::sprintf(heading, "Round %u, Group %u, Machine %u", round, parallel, machine + 1);
+ }
+ }
+
+ unsigned width = my_draw_text(heading, NULL, 40.0, "mainheading");
+ my_draw_text_deferred(td, heading, 40.0, "mainheading", "mainheading", LOGICAL_SCREEN_WIDTH/2 - width/2, 60);
+}
+
+// make column headings from the first player's songs
+void GroupScreen::draw_column_headings(std::vector<TextDefer> &td, const Group &group, const std::vector<unsigned> &colwidth)
+{
+ unsigned num_scores = group.players[0].scores.size();
+
+ unsigned col = 1;
+ unsigned x = 40 + colwidth[0];
+ for (std::vector<Score>::const_iterator i = group.players[0].scores.begin(); i != group.players[0].scores.end(); ++i, ++col) {
+ if (!i->chosen) {
+ unsigned this_width = my_draw_text(i->song.short_title, NULL, 12.0, "columnheading");
+ my_draw_text_deferred(td, i->song.short_title, 12.0, "columnheading", "columnheading", x + colwidth[col] / 2 - this_width / 2, 100);
+ }
+ x += colwidth[col] + 20;
+ }
+
+ if (num_scores > 1) {
+ unsigned this_width = my_draw_text("Total", NULL, 12.0, "columnheading");
+ my_draw_text_deferred(td, "Total", 12.0, "columnheading", "columnheading", x + colwidth[num_scores + 1] / 2 - this_width / 2, 100);
+ x += colwidth[num_scores + 1] + 20;
+ }
+ unsigned this_width = my_draw_text("Rank", NULL, 12.0, "columnheading");
+ my_draw_text_deferred(td, "Rank", 12.0, "columnheading", "columnheading", x + colwidth[num_scores + 2] / 2 - this_width / 2, 100);
+}
+
+// show all the players and the scores
+void GroupScreen::draw_scores(std::vector<TextDefer> &td, const Group &group, unsigned min_player, const std::vector<unsigned> &colwidth)
+{
+ unsigned max_num_width = my_draw_text("8888", NULL, 22.0, "score");
+ unsigned num_scores = group.players[0].scores.size();
+ unsigned show_players = get_show_players(group);
+ unsigned y = (show_players <= 7) ? 140 : (140 - (show_players - 7) * 5);
+
+ unsigned row = 0, m = 0, x;
+ for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end() && row < 9; ++i) {
+ if (m++ % num_machines != machine)
+ continue;
+ if (m-1 < min_player)
+ continue;
+
+ std::string suffix = theme_suffix_from_row(row);
+
+ my_draw_text_deferred(td, i->nick, 18.0, "rowheading" + suffix, "rowheading" + suffix, 20, y);
+
+ x = 40 + colwidth[0];
+
+ unsigned col = 1;
+ for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j, ++col) {
+ char text[16] = "";
+ if (j->score != -1) {
+ std::sprintf(text, "%u", j->score);
+ }
+
+ unsigned this_width = my_draw_text(text, NULL, 22.0, "score" + suffix);
+ if (j->chosen) {
+ my_draw_text_deferred(td, text, 22.0, "chosensongname" + suffix, "freshchosensongname" + suffix, x + max_num_width - this_width, y);
+
+ // draw the long name if we can, otherwise use the short one
+ if (my_draw_text(j->song.title, NULL, 12.0, "chosensongname") > (colwidth[col] - 10 - max_num_width)) {
+ my_draw_text_deferred(td, j->song.short_title, 12.0, "chosensongname" + suffix, "freshchosensongname" + suffix, x + max_num_width + 10, y);
+ } else {
+ my_draw_text_deferred(td, j->song.title, 12.0, "chosensongname" + suffix, "freshchosensongname" + suffix, x + max_num_width + 10, y);
+ }
+ } else {
+ my_draw_text_deferred(td, text, 22.0, "score" + suffix, "freshscore" + suffix, x + colwidth[col] / 2 - this_width / 2, y);
+ }
+ x += colwidth[col] + 20;
+ }
+
+ // draw total
+ if (num_scores > 1) {
+ char text[16];
+ std::sprintf(text, "%u", i->total);
+
+ unsigned this_width = my_draw_text(text, NULL, 22.0, "totalscore" + suffix);
+ my_draw_text_deferred(td, text, 22.0, "totalscore" + suffix, "freshtotalscore" + suffix, x + colwidth[num_scores + 1] / 2 - this_width / 2, y);
+ x += colwidth[num_scores + 1] + 20;
+ }
+
+ if (show_players > 7)
+ y += 40 - (show_players - 7) * 4;
+ else
+ y += 40;
+ ++row;
+ }
+}
+
+/*
+ * Find out how wide each column has to be. First try unlimited width (ie.
+ * long titles for everything); if that gets too long, try again with short
+ * titles for chosen songs.
+ */
+void GroupScreen::find_column_widths(const Group &group, std::vector<unsigned> &colwidth)
+{
+ unsigned num_scores;
+ unsigned max_num_width = my_draw_text("8888", NULL, 22.0, "score");
+ unsigned sumcolwidth;
+
+ for (unsigned mode = 0; mode < 2; ++mode) {
+ for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
+ unsigned col = 1;
+
+ if (colwidth.size() == 0)
+ colwidth.push_back(0);
+
+ colwidth[0] = std::max(colwidth[0], my_draw_text(i->nick, NULL, 18.0, "rowheading"));
+
+ for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j, ++col) {
+ if (colwidth.size() < col+1)
+ colwidth.push_back(0);
+
+ if (j->chosen) {
+ colwidth[col] = std::max(colwidth[col], my_draw_text((mode == 0) ? j->song.title : j->song.short_title, NULL, 12.0, "chosensongname") +
+ max_num_width + 10);
+ } else {
+ colwidth[col] = std::max(colwidth[col], my_draw_text(j->song.short_title, NULL, 12.0, "randomsongname"));
+ colwidth[col] = std::max(colwidth[col], max_num_width);
+ }
+ }
+ }
+
+ num_scores = group.players[0].scores.size();
+
+ if (colwidth.size() < num_scores + 2) {
+ colwidth.push_back(0);
+ colwidth.push_back(0);
+ }
+
+ if (num_scores > 1) {
+ colwidth[num_scores + 1] = std::max(my_draw_text("Total", NULL, 12.0, "columnheading"), max_num_width);
+ }
+ colwidth[num_scores + 2] = my_draw_text("Rank", NULL, 12.0, "columnheading");
+
+ // if we're at long titles and that works, don't try the short ones
+ sumcolwidth = 0;
+
+ for (unsigned i = 0; i <= num_scores + 2; ++i)
+ sumcolwidth += colwidth[i] + 20;
+
+ if (sumcolwidth < LOGICAL_SCREEN_WIDTH - 20)
+ break;
+
+ if (mode == 0) {
+ colwidth.erase(colwidth.begin(), colwidth.end());
+ }
+ }
+
+ /*
+ * If we have space to go, distribute as much as we can to the chosen song column, so we won't have
+ * total and rank jumping around.
+ */
+ if (sumcolwidth < LOGICAL_SCREEN_WIDTH - 20) {
+ int first_chosen_col = -1;
+ unsigned col = 1;
+
+ for (std::vector<Score>::const_iterator i = group.players[0].scores.begin(); i != group.players[0].scores.end(); ++i, ++col) {
+ if (i->chosen) {
+ first_chosen_col = col;
+ break;
+ }
+ }
+
+ if (first_chosen_col != -1) {
+ colwidth[first_chosen_col] += LOGICAL_SCREEN_WIDTH - 20 - sumcolwidth;
+ }
+ }
+}
+
+/* Find the first player with the fewest songs played and part of this machine. */
+const Player *GroupScreen::get_next_player(const Group &group)
+{
+ unsigned min_played_songs = 9999;
+ const Player *next_player = NULL;
+ unsigned m = 0;
+ for (std::vector<Player>::const_iterator i = group.players.begin(); i != group.players.end(); ++i) {
+ unsigned this_played = 0;
+ for (std::vector<Score>::const_iterator j = i->scores.begin(); j != i->scores.end(); ++j) {
+ if (j->score != -1)
+ ++this_played;
+ }
+
+ if ((m++ % num_machines == machine) && this_played < min_played_songs) {
+ min_played_songs = this_played;
+ next_player = &(*i);
+ }
+ }
+
+ return next_player;
+}
+
+/*
+ * At the bottom, for a single player, is "who's playing, what will he/she be
+ * playing, and optionally, how much to lead/win and how much to secure
+ * qualification" (the last one only in the final round). We assume playing is
+ * done in a modified zigzag; all the random songs are played first in
+ * zigzag/wrapping order (player 1 song 1, player 2 song 2, player 3 song 3,
+ * player 1 song 2, player 2 song 3, player 3 song 1, etc... assuming three
+ * songs and three players) and then all the chosen songs are played (we assume
+ * only one chosen song).
+ *
+ * The lines are as follows:
+ *
+ * <player>
+ * <song>
+ * High score: <hs> by <hsplayer> at <hsevent>
+ * Needs to lead: <leadscore>
+ * Needs to secure qualification: <qualscore>
+ * Needs to win group: <winscore>
+ */
+void GroupScreen::draw_next_up_single(unsigned char *buf, const Group &group,
+ std::map<unsigned, unsigned> &song_scores, std::map<unsigned, unsigned> &player_scores,
+ const std::vector<unsigned> &max_score, const std::vector<unsigned> &min_score)
+{
+ unsigned num_scores = group.players[0].scores.size();
+
+ // Find out how many random songs there are (equal for all players).
+ unsigned num_random_songs = 0;
+ for (std::vector<Score>::const_iterator i = group.players[0].scores.begin(); i != group.players[0].scores.end(); ++i) {
+ if (!i->chosen)
+ ++num_random_songs;
+ }
+
+ /*
+ * Find out which player is next, and what song he she is supposed to play. First
+ * try random songs.
+ */
+ const Player *next_player = get_next_player(group);
+ const Score *next_song = NULL;
+
+ for (unsigned i = 0; i < num_random_songs; ++i) {
+ unsigned j = (i + next_player->position - 1) % num_random_songs;
+ if (next_player->scores[j].score == -1) {
+ next_song = &(next_player->scores[j]);
+ break;
+ }
+ }
+
+ // then all songs, if that didn't work out (slightly icky, but hey)
+ if (next_song == NULL) {
+ for (unsigned i = 0; i < num_scores; ++i) {
+ unsigned j = (i + next_player->position) % num_scores;
+ if (next_player->scores[j].score == -1) {
+ next_song = &(next_player->scores[j]);
+ break;
+ }
+ }
+ }
+
+ if (next_song != NULL) {
+ // find out how many songs we've played in all
+ unsigned num_played = 0;
+ for (unsigned i = 0; i < num_scores; ++i) {
+ if (next_player->scores[i].score != -1) {
+ ++num_played;
+ }
+ }
+
+ bool last_song = (num_played == num_scores - 1);
+
+ draw_next_up_player(buf, group, *next_player, *next_song, last_song, song_scores, player_scores, max_score, min_score);
+ }