#include "web_dom.h" #include "web_match.h" #include "web_misc.h" #include "web_scoreboard.h" #include "checkouts.h" #include "match.h" #include #include #include #include #include static struct { bufstr name, rem, sugg, avg; } *bufs; #define NUM_SLOTS 2 static int slots[NUM_SLOTS]; static int active_pn; static void flush_slot(int slot) { int pn = slots[slot - 1]; if (!pn) return; // FIXME: should be combined with flushing player info char sel[32]; int len = sprintf(sel, "#info-slot-%d .", slot); strcpy(sel + len, "name"); dom_set_content(sel, bufstr_flush(&bufs[pn-1].name)); strcpy(sel + len, "rem"); dom_set_content(sel, bufstr_flush(&bufs[pn-1].rem)); strcpy(sel + len, "sugg"); dom_set_content(sel, bufstr_flush(&bufs[pn-1].sugg)); strcpy(sel + len, "avg"); dom_set_content(sel, bufstr_flush(&bufs[pn-1].avg)); } static int player_slot(int pn) { for (int i = 0; i < NUM_SLOTS; ++i) { if (slots[i] == pn) return i + 1; } return 0; } static void set_slot_player(int slot, int pn) { if (slots[slot - 1] != pn) { slots[slot - 1] = pn; flush_slot(slot); } } static void set_slot_active(int slot) { char buf[64], *sel = buf; if (slot) sprintf(sel, "#info-slot-%d", slot); else sel = NULL; dom_set_uniq_class(sel, "active", ".info-slot"); } EMSCRIPTEN_KEEPALIVE void scoreboard_prev_slot() { for (int i = NUM_SLOTS; i > 1; --i) set_slot_player(i, slots[i - 2]); int prev = slots[0]; set_slot_player(1, prev == 1 ? match_num_players() : prev - 1); set_slot_active(player_slot(active_pn)); } EMSCRIPTEN_KEEPALIVE void scoreboard_next_slot() { for (int i = 1; i < NUM_SLOTS; ++i) set_slot_player(i, slots[i]); int prev = slots[NUM_SLOTS - 1]; set_slot_player(NUM_SLOTS, prev == match_num_players() ? 1 : prev + 1); set_slot_active(player_slot(active_pn)); } void scoreboard_set_player_active(int pn) { active_pn = pn; if (!pn) { set_slot_active(0); return; } int np = match_num_players(); if (np > NUM_SLOTS) { for (int i = 0; i < NUM_SLOTS; ++i) set_slot_player(i + 1, ((pn + i - 1) % np) + 1); set_slot_active(1); } else { set_slot_active(pn); } } void scoreboard_set_player_name(int pn, char *str) { bufstr_buf(&bufs[pn-1].name, str); } void scoreboard_set_player_rem(int pn, char *str) { bufstr_buf(&bufs[pn-1].rem, str); } void scoreboard_set_player_sugg(int pn, char *str) { bufstr_buf(&bufs[pn-1].sugg, str); } void scoreboard_set_player_avg(int pn, double avg) { char str[64]; sprintf(str, "%.2f", avg); bufstr_buf(&bufs[pn-1].avg, str); } void scoreboard_flush_player_info(int pn) { int slot = player_slot(pn); if (!slot) return; char sel[32]; int len = sprintf(sel, "#info-slot-%d .", slot); if (bufstr_changed(&bufs[pn-1].name)) { strcpy(sel + len, "name"); dom_set_content(sel, bufstr_flush(&bufs[pn-1].name)); } if (bufstr_changed(&bufs[pn-1].rem)) { strcpy(sel + len, "rem"); dom_set_content(sel, bufstr_flush(&bufs[pn-1].rem)); } if (bufstr_changed(&bufs[pn-1].sugg)) { strcpy(sel + len, "sugg"); dom_set_content(sel, bufstr_flush(&bufs[pn-1].sugg)); } if (bufstr_changed(&bufs[pn-1].avg)) { strcpy(sel + len, "avg"); dom_set_content(sel, bufstr_flush(&bufs[pn-1].avg)); } } void scoreboard_flush() { // FIXME unnecessary slot lookups for (int i = 0; i < NUM_SLOTS; ++i) { int pn = slots[i]; if (pn) scoreboard_flush_player_info(pn); } } void update_player_name(int pn, char *str) { scoreboard_set_player_name(pn, str); } void update_player_sugg(int pn, int rem) { char str[15] = {0}, *p = str; if (rem > 1 && rem <= 170) { char *target; int i = 3, trem = rem; while (i && trem && (target = CHECKOUTS[--i][trem-1])) { p += sprintf(p, i == 2 ? "%s" : "-%s", target); trem -= segment_points(segment_from_name(target)); } } scoreboard_set_player_sugg(pn, str); } static double calc_avg(struct leg *l, int n_darts) { if (!l->n_visits) return 0; if (l->rem > 0) return ((double)(l->start - l->rem) / l->n_visits); return ((double)l->start / (((l->n_visits - 1) * 3) + n_darts) * 3); } void update_player_avg(int pn, int n_darts) { struct leg *l = state->legs[pn-1]; scoreboard_set_player_avg(pn, calc_avg(l, n_darts)); } void update_player_rem(int pn, int rem) { char str[5]; if (rem < 0 || rem == 1) strcpy(str, "BUST"); else sprintf(str, "%d", rem); scoreboard_set_player_rem(pn, str); if (!match_player_is_comp(pn)) update_player_sugg(pn, rem); } void scoreboard_show_info(int num_players) { if (bufs) free(bufs); bufs = calloc(num_players, sizeof(*bufs)); for (int i = 1; i <= num_players && i <= NUM_SLOTS; ++i) { set_slot_player(i, i); char buf[64]; sprintf(buf, "#info-slot-%d .inner", i); dom_add_class(buf, "visible"); } if (num_players > NUM_SLOTS) { dom_add_class("#info-slot-prev", "visible"); dom_add_class("#info-slot-next", "visible"); } } void scoreboard_hide_info() { free(bufs); bufs = NULL; memset(slots, 0, sizeof(slots)); dom_set_uniq_class(NULL, "visible", ".info-slot .inner"); dom_remove_class("#info-slot-prev", "visible"); dom_remove_class("#info-slot-next", "visible"); } static struct dom_elem *create_div(char *str, char *class) { struct dom_elem *e = dom_elem_init(NULL, "div", 1); e->content = strdup(str); dom_elem_add_attr(e, "class", class); return e; } static const char *points_class(int pts) { return (pts >= 180) ? "p180" : (pts >= 140) ? "p140" : (pts >= 100) ? "p100" : (pts >= 60) ? "p60" : (pts >= 40) ? "p40" : (pts >= 20) ? "p20" : "p0"; } static void buf_darts(char *buf, size_t size, struct visit *v) { char *ptr = buf; for (int j = 0; j < v->n_darts; ++j) { char *n = segment_name(v->darts[j]); ptr += snprintf(ptr, size - (ptr - buf), j == 0 ? "%s" : "-%s", n); free(n); } } void draw_visits_dense() { dom_set_content("#visits", NULL); dom_add_class("#visits", "dense"); int np = match_num_players(); dom_set_style_property_int("--num-players", np); // FIXME int n_visits = state->legs[0]->n_visits > state->legs[1]->n_visits ? state->legs[0]->n_visits : state->legs[1]->n_visits; struct dom_elem **elemv = malloc( (np * 2 + 1) * (2 * n_visits + 2) * sizeof(*elemv)); int elemc = 0; elemv[elemc++] = create_div("", ""); for (int i = 0; i < np; ++i) { elemv[elemc++] = create_div(state->legs[i]->name, "visit-name"); } char buf[32], buf2[32]; elemv[elemc++] = create_div("0", "visit-n"); for (int i = 0; i < np; ++i) { if (i + 1 == match_opts->throws_first) elemv[elemc++] = create_div("", "throws-first"); else elemv[elemc++] = create_div("", ""); snprintf(buf, sizeof(buf), "%d", state->legs[i]->start); elemv[elemc++] = create_div(buf, ""); } for (int i = 0; i < n_visits; ++i) { snprintf(buf, sizeof(buf), "%d", i + 1); elemv[elemc++] = create_div(buf, "visit-n"); for (int j = 0; j < np; ++j) { if (i >= state->legs[j]->n_visits) { elemv[elemc++] = create_div("", ""); elemv[elemc++] = create_div("", ""); continue; } struct visit *v = state->legs[j]->visits + i; snprintf(buf, sizeof(buf), "%d", v->points); snprintf(buf2, sizeof(buf2), "%s", points_class(v->points)); elemv[elemc++] = create_div(buf, buf2); snprintf(buf, sizeof(buf), "%d", v->rem); elemv[elemc++] = create_div(buf, ""); } bool n_darts = false; for (int j = 0; j < np; ++j) { if (i < state->legs[j]->n_visits && state->legs[j]->visits[i].n_darts) n_darts = true; } if (!n_darts) continue; elemv[elemc++] = create_div("", ""); for (int j = 0; j < np; ++j) { if (i >= state->legs[j]->n_visits) { elemv[elemc++] = create_div("", "visit-dense-darts"); continue; } struct visit *v = state->legs[j]->visits + i; if (v->n_darts) { buf_darts(buf, sizeof(buf), v); elemv[elemc++] = create_div(buf, "visit-dense-darts"); } else { elemv[elemc++] = create_div("", "visit-dense-darts"); } } } dom_append_elemv("#visits", elemc, elemv); for (int i = 0; i < elemc; ++i) dom_elem_free(elemv[i]); free(elemv); dom_scroll_to_bottom("#visits"); dom_scroll_to_center_child("#visits", ".visit-n"); } void draw_visits() { if (match_num_players() > 2) { draw_visits_dense(); return; } dom_set_content("#visits", NULL); dom_remove_class("#visits", "dense"); int n_visits = state->legs[0]->n_visits > state->legs[1]->n_visits ? state->legs[0]->n_visits : state->legs[1]->n_visits; struct dom_elem **elemv = malloc(7 * (n_visits + 1) * sizeof(*elemv)); int elemc = 0; elemv[elemc++] = create_div("", "visit-p1-spacer"); elemv[elemc++] = create_div(state->legs[0]->name, "visit-p1-name"); if (match_num_players() != 1) elemv[elemc++] = create_div(state->legs[1]->name, "visit-p2-name"); elemv[elemc++] = create_div("", "visit-p2-spacer"); char buf[32], buf2[32]; snprintf(buf, sizeof(buf), "%d", state->legs[0]->start); if (match_opts->throws_first == 1) elemv[elemc++] = create_div("", // content added via CSS "visit-p1-pts throws-first"); elemv[elemc++] = create_div(buf, "visit-p1-rem"); elemv[elemc++] = create_div("0", "visit-n"); if (match_num_players() != 1) { snprintf(buf, sizeof(buf), "%d", state->legs[1]->start); elemv[elemc++] = create_div(buf, "visit-p2-rem"); if (match_opts->throws_first == 2) elemv[elemc++] = create_div("", "visit-p2-pts throws-first"); } for (int i = 0; i < n_visits; ++i) { struct visit *v; if (i >= state->legs[0]->n_visits) goto p2; v = state->legs[0]->visits + i; if (v->n_darts) { buf_darts(buf, sizeof(buf), v); elemv[elemc++] = create_div(buf, "visit-p1-darts"); } snprintf(buf, sizeof(buf), "%d", v->points); snprintf(buf2, sizeof(buf2), "%s %s", "visit-p1-pts", points_class(v->points)); elemv[elemc++] = create_div(buf, buf2); snprintf(buf, sizeof(buf), "%d", v->rem); elemv[elemc++] = create_div(buf, "visit-p1-rem"); p2: snprintf(buf, sizeof(buf), "%d", i + 1); elemv[elemc++] = create_div(buf, "visit-n"); if (i >= state->legs[1]->n_visits) continue; v = state->legs[1]->visits + i; snprintf(buf, sizeof(buf), "%d", v->rem); elemv[elemc++] = create_div(buf, "visit-p2-rem"); snprintf(buf, sizeof(buf), "%d", v->points); snprintf(buf2, sizeof(buf2), "%s %s", "visit-p2-pts", points_class(v->points)); elemv[elemc++] = create_div(buf, buf2); if (v->n_darts) { buf_darts(buf, sizeof(buf), v); elemv[elemc++] = create_div(buf, "visit-p2-darts"); } } dom_append_elemv("#visits", elemc, elemv); for (int i = 0; i < elemc; ++i) dom_elem_free(elemv[i]); free(elemv); dom_scroll_to_bottom("#visits"); dom_scroll_to_center_child("#visits", ".visit-n"); }